@kitman-sh/cli 0.9.0 → 0.10.53
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/dist/chunk-11p4wf6n.js +3551 -0
- package/dist/chunk-1mc9vgcr.js +39 -0
- package/dist/chunk-30397r95.js +30 -0
- package/dist/chunk-3bj7sc3v.js +389 -0
- package/dist/chunk-6vns3ej0.js +23 -0
- package/dist/chunk-7thnf14y.js +936 -0
- package/dist/chunk-arvwar2c.js +141 -0
- package/dist/chunk-b6thzgc9.js +32 -0
- package/dist/{chunk-8bvf4fh4.js → chunk-ctasxxz5.js} +26 -11
- package/dist/{chunk-5gjpv3ax.js → chunk-dzh9e6n2.js} +824 -651
- package/dist/{chunk-snn0e1qb.js → chunk-eg8f5ncv.js} +7 -3
- package/dist/chunk-g5bs3f6z.js +173 -0
- package/dist/chunk-jafetf7z.js +109 -0
- package/dist/chunk-jagzz148.js +527 -0
- package/dist/{chunk-1d4316hp.js → chunk-jfvq7s24.js} +38 -25
- package/dist/chunk-mbctzx7e.js +11436 -0
- package/dist/chunk-mej2bx5n.js +100 -0
- package/dist/chunk-p6spvbwq.js +6305 -0
- package/dist/chunk-pp1p3g6e.js +133 -0
- package/dist/chunk-r8p3nxcp.js +231 -0
- package/dist/chunk-txwt6r25.js +1731 -0
- package/dist/chunk-v290mf51.js +5242 -0
- package/dist/chunk-vjmz23rs.js +13 -0
- package/dist/chunk-vyv0p5eq.js +3183 -0
- package/dist/chunk-wh1pa2aa.js +15322 -0
- package/dist/chunk-x0b92bjb.js +222 -0
- package/dist/{chunk-8z5b6fpd.js → chunk-x9dhxfts.js} +216 -152
- package/dist/chunk-z821b2gp.js +164 -0
- package/dist/index.js +13 -5738
- package/dist/opencode-reader.main.ts +4 -0
- package/package.json +6 -5
- package/dist/chunk-3gxahgnq.js +0 -28495
- package/dist/chunk-ehzbtavj.js +0 -247
- package/dist/chunk-f5z8tgss.js +0 -1214
- package/dist/chunk-kdb1cbnx.js +0 -1273
- package/dist/chunk-ke1w0hx7.js +0 -5771
- package/dist/chunk-z6hc4ew9.js +0 -13
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
Reference,
|
|
4
|
+
TaggedError,
|
|
5
|
+
Scope,
|
|
6
|
+
provide,
|
|
7
|
+
forEach,
|
|
8
|
+
succeed6,
|
|
9
|
+
suspend3,
|
|
10
|
+
gen,
|
|
11
|
+
fail7,
|
|
12
|
+
flatMap3,
|
|
13
|
+
exit,
|
|
14
|
+
map8,
|
|
15
|
+
as,
|
|
16
|
+
catch_,
|
|
17
|
+
catchReason,
|
|
18
|
+
mapError2,
|
|
19
|
+
orDie,
|
|
20
|
+
orElseSucceed,
|
|
21
|
+
provideService,
|
|
22
|
+
scoped,
|
|
23
|
+
fnUntraced,
|
|
24
|
+
encodePrettyJsonString,
|
|
25
|
+
decodeJsonStringSync
|
|
26
|
+
} from "./chunk-mbctzx7e.js";
|
|
27
|
+
import {
|
|
28
|
+
isJsonObject,
|
|
29
|
+
isString
|
|
30
|
+
} from "./chunk-arvwar2c.js";
|
|
31
|
+
import {
|
|
32
|
+
FileSystem,
|
|
33
|
+
Path,
|
|
34
|
+
optionalEnv
|
|
35
|
+
} from "./chunk-11p4wf6n.js";
|
|
36
|
+
import {
|
|
37
|
+
AgentHarnessIds,
|
|
38
|
+
sha2562,
|
|
39
|
+
runCommand2
|
|
40
|
+
} from "./chunk-wh1pa2aa.js";
|
|
41
|
+
import {
|
|
42
|
+
NpmRegistry
|
|
43
|
+
} from "./chunk-g5bs3f6z.js";
|
|
44
|
+
|
|
45
|
+
// packages/agent-runtime/src/config-convergence.ts
|
|
46
|
+
var writeAgentEnvironmentConfigFile2 = fnUntraced(function* (file, content, keep = {}) {
|
|
47
|
+
const fs = yield* FileSystem;
|
|
48
|
+
const paths = {
|
|
49
|
+
path: yield* Path,
|
|
50
|
+
home: yield* optionalEnv("HOME")
|
|
51
|
+
};
|
|
52
|
+
yield* fs.makeDirectory(paths.path.dirname(file), { recursive: true });
|
|
53
|
+
const existing = yield* readExistingText(file);
|
|
54
|
+
const merged = yield* gen(function* () {
|
|
55
|
+
if (file.endsWith("AGENTS.md"))
|
|
56
|
+
return mergeCodexAgents(existing, content);
|
|
57
|
+
if (file.endsWith("opencode.json")) {
|
|
58
|
+
const managed = yield* readManagedNamesForConfig(paths, file);
|
|
59
|
+
return mergeOpenCodeConfig(existing, content, managed.mcpNames, without(managed.packageNames, keep.packages));
|
|
60
|
+
}
|
|
61
|
+
if (file.endsWith(".codex/config.toml")) {
|
|
62
|
+
const managed = yield* readManagedNamesForConfig(paths, file);
|
|
63
|
+
return mergeCodexConfig(existing, content, managed.mcpNames, managed.skillPaths, keep.kitmanPlugins === true);
|
|
64
|
+
}
|
|
65
|
+
if (isPiConfigPath(paths.path, file, "settings.json")) {
|
|
66
|
+
const managed = yield* readManagedNamesForConfig(paths, file);
|
|
67
|
+
return mergePiSettingsConfig(existing, content, without(managed.packageNames, keep.packages));
|
|
68
|
+
}
|
|
69
|
+
if (isPiConfigPath(paths.path, file, "mcp.json")) {
|
|
70
|
+
const managed = yield* readManagedNamesForConfig(paths, file);
|
|
71
|
+
return mergePiMcpConfig(existing, content, managed.mcpNames);
|
|
72
|
+
}
|
|
73
|
+
return content;
|
|
74
|
+
});
|
|
75
|
+
yield* fs.writeFileString(file, merged);
|
|
76
|
+
});
|
|
77
|
+
var without = (names, kept = []) => new Set([...names].filter((name) => !kept.includes(name)));
|
|
78
|
+
var KitmanAgentsStartMarker = "<!-- KITMAN_AGENT_ENVIRONMENT_START -->";
|
|
79
|
+
var KitmanAgentsEndMarker = "<!-- KITMAN_AGENT_ENVIRONMENT_END -->";
|
|
80
|
+
var KitmanGeneratedAgentsPrefix = "# Kitman AgentEnvironment\n\nThis file is generated by `kitman sync`";
|
|
81
|
+
function mergeCodexAgents(existingText, generatedText) {
|
|
82
|
+
const block = `${KitmanAgentsStartMarker}
|
|
83
|
+
${generatedText.trimEnd()}
|
|
84
|
+
${KitmanAgentsEndMarker}`;
|
|
85
|
+
const trimmedExisting = existingText.trim();
|
|
86
|
+
if (trimmedExisting.length === 0 || existingText.startsWith(KitmanGeneratedAgentsPrefix))
|
|
87
|
+
return `${block}
|
|
88
|
+
`;
|
|
89
|
+
const start = existingText.indexOf(KitmanAgentsStartMarker);
|
|
90
|
+
const end = existingText.indexOf(KitmanAgentsEndMarker);
|
|
91
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
92
|
+
return `${existingText.slice(0, start)}${block}${existingText.slice(end + KitmanAgentsEndMarker.length).replace(/^\n?/, `
|
|
93
|
+
`)}`;
|
|
94
|
+
}
|
|
95
|
+
return `${existingText.trimEnd()}
|
|
96
|
+
|
|
97
|
+
${block}
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
var readExistingText = (file) => flatMap3(FileSystem, (fs) => fs.readFileString(file).pipe(catchReason("PlatformError", "NotFound", () => succeed6(""))));
|
|
101
|
+
var readManagedNamesForConfig = (paths, file) => gen(function* () {
|
|
102
|
+
const manifest = parseJsonObject(yield* readExistingText(yield* managedManifestPathForConfig(paths, file)));
|
|
103
|
+
const servers = Array.isArray(manifest.mcpServers) ? manifest.mcpServers : [];
|
|
104
|
+
const plugins = Array.isArray(manifest.plugins) ? manifest.plugins : [];
|
|
105
|
+
return {
|
|
106
|
+
mcpNames: new Set(servers.flatMap((entry) => {
|
|
107
|
+
const item = objectValue(objectValue(entry).item);
|
|
108
|
+
return isString(item.name) ? [item.name] : [];
|
|
109
|
+
})),
|
|
110
|
+
packageNames: new Set(plugins.flatMap((entry) => {
|
|
111
|
+
const { source: legacySource, sources } = objectValue(objectValue(entry).item);
|
|
112
|
+
return (Array.isArray(sources) ? sources : legacySource === undefined ? [] : [legacySource]).flatMap((source) => {
|
|
113
|
+
const { kind, packageName } = objectValue(source);
|
|
114
|
+
return kind === "package" && isString(packageName) ? [packageName] : [];
|
|
115
|
+
});
|
|
116
|
+
})),
|
|
117
|
+
skillPaths: manifestSkillPaths(manifest)
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
function manifestSkillPaths(manifest) {
|
|
121
|
+
const materializedSkills = Array.isArray(manifest.materializedSkills) ? manifest.materializedSkills : [];
|
|
122
|
+
return new Set(materializedSkills.flatMap((entry) => {
|
|
123
|
+
const item = objectValue(entry);
|
|
124
|
+
return isString(item.skillPath) ? [item.skillPath] : [];
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
var readManifestSkillPaths2 = (manifestPath) => map8(readExistingText(manifestPath), (text) => manifestSkillPaths(parseJsonObject(text)));
|
|
128
|
+
var managedManifestPathForConfig = (paths, file) => gen(function* () {
|
|
129
|
+
const fs = yield* FileSystem;
|
|
130
|
+
const manifestRoot = managedManifestRootPath(paths, file);
|
|
131
|
+
const manifestPath = paths.path.join(manifestRoot, ".kitman", manifestFileForHarness(managedHarnessForConfigPath(paths, file)));
|
|
132
|
+
const legacyPath = paths.path.join(manifestRoot, ".kitman/agent-environment.manifest.json");
|
|
133
|
+
if (yield* fs.exists(manifestPath))
|
|
134
|
+
return manifestPath;
|
|
135
|
+
return (yield* fs.exists(legacyPath)) ? legacyPath : manifestPath;
|
|
136
|
+
});
|
|
137
|
+
var HomeConfigFiles = [
|
|
138
|
+
[".config/opencode/opencode.json", "opencode"],
|
|
139
|
+
[".codex/AGENTS.md", "codex"],
|
|
140
|
+
[".codex/config.toml", "codex"],
|
|
141
|
+
[".claude/.mcp.json", "claude-code"],
|
|
142
|
+
[".pi/agent/settings.json", "pi"],
|
|
143
|
+
[".pi/agent/mcp.json", "pi"]
|
|
144
|
+
];
|
|
145
|
+
function homeConfigHarness({ path, home }, file) {
|
|
146
|
+
if (home === undefined)
|
|
147
|
+
return;
|
|
148
|
+
const resolved = path.resolve(file);
|
|
149
|
+
return HomeConfigFiles.find(([relative]) => path.resolve(home, relative) === resolved)?.[1];
|
|
150
|
+
}
|
|
151
|
+
function managedManifestRootPath(paths, file) {
|
|
152
|
+
const { path, home } = paths;
|
|
153
|
+
const resolved = path.resolve(file);
|
|
154
|
+
if (home !== undefined && homeConfigHarness(paths, file) !== undefined)
|
|
155
|
+
return home;
|
|
156
|
+
const harness = managedHarnessForConfigPath(paths, file);
|
|
157
|
+
const configDirectory = path.dirname(resolved);
|
|
158
|
+
const configFile = path.basename(resolved);
|
|
159
|
+
if (harness === "codex" && configFile === "config.toml" && path.basename(configDirectory) === ".codex")
|
|
160
|
+
return path.dirname(configDirectory);
|
|
161
|
+
if (harness === "pi" && (configFile === "settings.json" || configFile === "mcp.json") && path.basename(configDirectory) === ".pi")
|
|
162
|
+
return path.dirname(configDirectory);
|
|
163
|
+
return configDirectory;
|
|
164
|
+
}
|
|
165
|
+
function managedHarnessForConfigPath(paths, file) {
|
|
166
|
+
const homeHarness = homeConfigHarness(paths, file);
|
|
167
|
+
if (homeHarness !== undefined)
|
|
168
|
+
return homeHarness;
|
|
169
|
+
const { path } = paths;
|
|
170
|
+
const resolved = path.resolve(file);
|
|
171
|
+
const configFile = path.basename(resolved);
|
|
172
|
+
if (configFile === "opencode.json")
|
|
173
|
+
return "opencode";
|
|
174
|
+
if (configFile === ".mcp.json")
|
|
175
|
+
return "claude-code";
|
|
176
|
+
if ((configFile === "settings.json" || configFile === "mcp.json") && path.basename(path.dirname(resolved)) === ".pi")
|
|
177
|
+
return "pi";
|
|
178
|
+
if (configFile === "AGENTS.md" || configFile === "config.toml")
|
|
179
|
+
return "codex";
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
function manifestFileForHarness(harness) {
|
|
183
|
+
if (harness === undefined)
|
|
184
|
+
return "agent-environment.manifest.json";
|
|
185
|
+
return `agent-environment.${harness}.manifest.json`;
|
|
186
|
+
}
|
|
187
|
+
function mergeOpenCodeConfig(existingText, generatedText, managedMcpNames, managedPackageNames) {
|
|
188
|
+
const existing = parseJsonObject(existingText);
|
|
189
|
+
const generated = parseJsonObject(generatedText);
|
|
190
|
+
const existingMcp = { ...objectValue(existing.mcp) };
|
|
191
|
+
for (const name of managedMcpNames)
|
|
192
|
+
delete existingMcp[name];
|
|
193
|
+
const existingPlugins = uniqueStrings(arrayValue(existing.plugin).filter((entry) => !isManagedPackageReference(entry, managedPackageNames)));
|
|
194
|
+
const merged = {
|
|
195
|
+
...existing,
|
|
196
|
+
...generated,
|
|
197
|
+
mcp: { ...existingMcp, ...objectValue(generated.mcp) },
|
|
198
|
+
plugin: uniqueStrings([...existingPlugins, ...arrayValue(generated.plugin)]),
|
|
199
|
+
skills: {
|
|
200
|
+
...objectValue(existing.skills),
|
|
201
|
+
...objectValue(generated.skills),
|
|
202
|
+
paths: uniqueStrings([
|
|
203
|
+
...arrayValue(objectValue(existing.skills).paths),
|
|
204
|
+
...arrayValue(objectValue(generated.skills).paths)
|
|
205
|
+
])
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
return `${JSON.stringify(merged, null, 2)}
|
|
209
|
+
`;
|
|
210
|
+
}
|
|
211
|
+
function isManagedPackageReference(entry, managedPackageNames, prefix = "") {
|
|
212
|
+
if (!isString(entry))
|
|
213
|
+
return false;
|
|
214
|
+
for (const name of managedPackageNames) {
|
|
215
|
+
if (entry === `${prefix}${name}` || entry.startsWith(`${prefix}${name}@`))
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
function mergePiSettingsConfig(existingText, generatedText, managedPackageNames) {
|
|
221
|
+
const existing = parseJsonObject(existingText);
|
|
222
|
+
const generated = parseJsonObject(generatedText);
|
|
223
|
+
const merged = {
|
|
224
|
+
...existing,
|
|
225
|
+
...generated,
|
|
226
|
+
packages: uniquePackageEntries([
|
|
227
|
+
...packageEntries(existing.packages).filter((entry) => !isManagedPackageReference(entry, managedPackageNames, "npm:")),
|
|
228
|
+
...packageEntries(generated.packages)
|
|
229
|
+
])
|
|
230
|
+
};
|
|
231
|
+
return `${JSON.stringify(merged, null, 2)}
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
function mergePiMcpConfig(existingText, generatedText, managedMcpNames) {
|
|
235
|
+
const existing = parseJsonObject(existingText);
|
|
236
|
+
const generated = parseJsonObject(generatedText);
|
|
237
|
+
const existingMcp = { ...objectValue(existing.mcpServers) };
|
|
238
|
+
for (const name of managedMcpNames)
|
|
239
|
+
delete existingMcp[name];
|
|
240
|
+
const merged = {
|
|
241
|
+
...existing,
|
|
242
|
+
...generated,
|
|
243
|
+
mcpServers: { ...existingMcp, ...objectValue(generated.mcpServers) }
|
|
244
|
+
};
|
|
245
|
+
return `${JSON.stringify(merged, null, 2)}
|
|
246
|
+
`;
|
|
247
|
+
}
|
|
248
|
+
function packageEntries(value) {
|
|
249
|
+
return Array.isArray(value) ? value : [];
|
|
250
|
+
}
|
|
251
|
+
function uniquePackageEntries(values) {
|
|
252
|
+
const seen = new Set;
|
|
253
|
+
const result = [];
|
|
254
|
+
for (const value of values) {
|
|
255
|
+
const key = isString(value) ? `string:${value}` : `json:${JSON.stringify(value)}`;
|
|
256
|
+
if (seen.has(key))
|
|
257
|
+
continue;
|
|
258
|
+
seen.add(key);
|
|
259
|
+
result.push(value);
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
function isPiConfigPath(path, file, name) {
|
|
264
|
+
const parent = path.dirname(file);
|
|
265
|
+
return path.basename(file) === name && (path.basename(parent) === ".pi" || path.basename(parent) === "agent" && path.basename(path.dirname(parent)) === ".pi");
|
|
266
|
+
}
|
|
267
|
+
function mergeCodexConfig(existingText, generatedText, managedMcpNames, managedSkillPaths, keepKitmanPlugins) {
|
|
268
|
+
const preserved = stripTomlTables(existingText, new Set([...managedMcpNames, ...mcpServerNames(generatedText)]), new Set([...managedSkillPaths, ...codexSkillPaths(generatedText)]), keepKitmanPlugins ? undefined : kitmanPluginNames(generatedText)).trimEnd();
|
|
269
|
+
const generated = generatedText.trim();
|
|
270
|
+
if (preserved.length === 0)
|
|
271
|
+
return `${generated}
|
|
272
|
+
`;
|
|
273
|
+
if (generated.length === 0)
|
|
274
|
+
return `${preserved}
|
|
275
|
+
`;
|
|
276
|
+
return `${preserved}
|
|
277
|
+
|
|
278
|
+
${generated}
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
function stripTomlTables(text, removeMcpServerNames, removeSkillPaths, keptKitmanPlugins) {
|
|
282
|
+
const lines = text.split(`
|
|
283
|
+
`);
|
|
284
|
+
const staleHookState = (key) => {
|
|
285
|
+
const plugin = /^([^@]+)@kitman:/u.exec(key ?? "")?.[1];
|
|
286
|
+
return keptKitmanPlugins !== undefined && plugin !== undefined && !keptKitmanPlugins.has(plugin);
|
|
287
|
+
};
|
|
288
|
+
const kept = [];
|
|
289
|
+
for (let index = 0;index < lines.length; ) {
|
|
290
|
+
const line = lines[index] ?? "";
|
|
291
|
+
const table = tomlTableHeader2(line);
|
|
292
|
+
if (table === undefined) {
|
|
293
|
+
kept.push(line);
|
|
294
|
+
index += 1;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const block = [line];
|
|
298
|
+
index += 1;
|
|
299
|
+
while (index < lines.length && tomlTableHeader2(lines[index] ?? "") === undefined) {
|
|
300
|
+
block.push(lines[index] ?? "");
|
|
301
|
+
index += 1;
|
|
302
|
+
}
|
|
303
|
+
const skillPath = table === "skills.config" ? codexSkillPathFromBlock(block) : undefined;
|
|
304
|
+
const [first, second, hookKey, ...rest] = tomlKeyPath2(table);
|
|
305
|
+
const hookState = first === "hooks" && second === "state";
|
|
306
|
+
const drop = keptKitmanPlugins !== undefined && isKitmanPluginTable([first ?? "", second ?? ""]) || hookState && rest.length === 0 && staleHookState(hookKey) || codexMcpServerName(table, removeMcpServerNames) !== undefined || skillPath !== undefined && removeSkillPaths.has(skillPath);
|
|
307
|
+
if (drop)
|
|
308
|
+
continue;
|
|
309
|
+
if (hookState && hookKey === undefined)
|
|
310
|
+
kept.push(...block.filter((entry) => !staleHookState(tomlKeyPath2(/^([^=]+)=/u.exec(entry)?.[1] ?? "")[0])));
|
|
311
|
+
else
|
|
312
|
+
kept.push(...block);
|
|
313
|
+
}
|
|
314
|
+
return kept.join(`
|
|
315
|
+
`);
|
|
316
|
+
}
|
|
317
|
+
function tomlTableHeader2(line) {
|
|
318
|
+
return /^\s*\[\[?((?:"(?:[^"\\]|\\.)*"|'[^']*'|[^"'[\]#])+)\]\]?\s*(?:#.*)?$/u.exec(line)?.[1];
|
|
319
|
+
}
|
|
320
|
+
function kitmanPluginNames(toml) {
|
|
321
|
+
return new Set(toml.split(`
|
|
322
|
+
`).flatMap((line) => {
|
|
323
|
+
const [table, name] = tomlKeyPath2(tomlTableHeader2(line) ?? "");
|
|
324
|
+
return table === "plugins" && name?.endsWith("@kitman") === true ? [name.slice(0, -"@kitman".length)] : [];
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
function isKitmanPluginTable([table, name]) {
|
|
328
|
+
return table === "marketplaces" && name === "kitman" || table === "plugins" && name?.endsWith("@kitman") === true;
|
|
329
|
+
}
|
|
330
|
+
function tomlKeyPath2(key) {
|
|
331
|
+
const part = /\s*(?:"((?:[^"\\]|\\.)*)"|'([^']*)'|([\w-]+))\s*(?:\.|$)/uy;
|
|
332
|
+
const parts = [];
|
|
333
|
+
while (part.lastIndex < key.length) {
|
|
334
|
+
const match = part.exec(key);
|
|
335
|
+
if (match === null)
|
|
336
|
+
return [];
|
|
337
|
+
parts.push(match[3] ?? match[2] ?? unescapeTomlString(match[1] ?? ""));
|
|
338
|
+
}
|
|
339
|
+
return parts;
|
|
340
|
+
}
|
|
341
|
+
function unescapeTomlString(text) {
|
|
342
|
+
const escapes = new Map([
|
|
343
|
+
["b", "\b"],
|
|
344
|
+
["t", "\t"],
|
|
345
|
+
["n", `
|
|
346
|
+
`],
|
|
347
|
+
["f", "\f"],
|
|
348
|
+
["r", "\r"],
|
|
349
|
+
["e", "\x1B"]
|
|
350
|
+
]);
|
|
351
|
+
return text.replace(/\\(?:u([\da-fA-F]{4})|U([\da-fA-F]{8})|(.))/gu, (_, short, long, char) => short !== undefined || long !== undefined ? String.fromCodePoint(Number.parseInt(short ?? long, 16)) : escapes.get(char) ?? char);
|
|
352
|
+
}
|
|
353
|
+
function mcpServerNames(toml) {
|
|
354
|
+
const names = new Set;
|
|
355
|
+
for (const line of toml.split(`
|
|
356
|
+
`)) {
|
|
357
|
+
const table = tomlTableHeader2(line);
|
|
358
|
+
if (table === undefined)
|
|
359
|
+
continue;
|
|
360
|
+
const name = codexMcpServerTableName(table);
|
|
361
|
+
if (name !== undefined)
|
|
362
|
+
names.add(name);
|
|
363
|
+
}
|
|
364
|
+
return names;
|
|
365
|
+
}
|
|
366
|
+
function codexSkillPaths(toml) {
|
|
367
|
+
const paths = new Set;
|
|
368
|
+
const lines = toml.split(`
|
|
369
|
+
`);
|
|
370
|
+
for (let index = 0;index < lines.length; ) {
|
|
371
|
+
const line = lines[index] ?? "";
|
|
372
|
+
if (!/^\s*\[\[\s*skills\.config\s*\]\]/u.test(line)) {
|
|
373
|
+
index += 1;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const block = [line];
|
|
377
|
+
index += 1;
|
|
378
|
+
while (index < lines.length && tomlTableHeader2(lines[index] ?? "") === undefined) {
|
|
379
|
+
block.push(lines[index] ?? "");
|
|
380
|
+
index += 1;
|
|
381
|
+
}
|
|
382
|
+
const path = codexSkillPathFromBlock(block);
|
|
383
|
+
if (path !== undefined)
|
|
384
|
+
paths.add(path);
|
|
385
|
+
}
|
|
386
|
+
return paths;
|
|
387
|
+
}
|
|
388
|
+
function codexSkillPathFromBlock(block) {
|
|
389
|
+
for (const line of block) {
|
|
390
|
+
const match = /^path\s*=\s*"((?:\\"|[^"])*)"/.exec(line.trim());
|
|
391
|
+
const value = match === null ? undefined : decodeJsonStringSync(`"${match[1]}"`);
|
|
392
|
+
if (isString(value))
|
|
393
|
+
return value;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function codexMcpServerName(table, names) {
|
|
397
|
+
const name = codexMcpServerTableName(table);
|
|
398
|
+
return name !== undefined && names.has(name) ? name : undefined;
|
|
399
|
+
}
|
|
400
|
+
function codexMcpServerTableName(table) {
|
|
401
|
+
const match = /^mcp_servers\.([^.]+)(?:\.|$)/.exec(table);
|
|
402
|
+
if (match === null)
|
|
403
|
+
return;
|
|
404
|
+
return match[1];
|
|
405
|
+
}
|
|
406
|
+
function parseJsonObject(text) {
|
|
407
|
+
return text.trim().length === 0 ? {} : objectValue(decodeJsonStringSync(text));
|
|
408
|
+
}
|
|
409
|
+
function objectValue(value) {
|
|
410
|
+
return isJsonObject(value) ? value : {};
|
|
411
|
+
}
|
|
412
|
+
function arrayValue(value) {
|
|
413
|
+
return Array.isArray(value) ? value.filter((item) => isString(item)) : [];
|
|
414
|
+
}
|
|
415
|
+
function uniqueStrings(values) {
|
|
416
|
+
return [...new Set(values)];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// packages/agent-runtime/src/source-resolver.ts
|
|
420
|
+
import { tmpdir } from "os";
|
|
421
|
+
class SourceResolutionFailed extends TaggedError("SourceResolutionFailed") {
|
|
422
|
+
get message() {
|
|
423
|
+
return this.reason;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
var isCommitHash = (ref) => ref !== undefined && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(ref);
|
|
427
|
+
var gitCommit = (url, ref) => gen(function* () {
|
|
428
|
+
const patterns = ref === undefined ? ["HEAD"] : [ref, `${ref}^{}`];
|
|
429
|
+
const output = yield* runCommand2("git", ["ls-remote", "--", url, ...patterns], {
|
|
430
|
+
cwd: tmpdir(),
|
|
431
|
+
timeout: "1 minute"
|
|
432
|
+
}).pipe(mapError2((error) => new SourceResolutionFailed({ reason: error.message })));
|
|
433
|
+
const lines = output.split(`
|
|
434
|
+
`).map((line) => line.split("\t"));
|
|
435
|
+
const names = ref === undefined ? ["HEAD"] : ref.startsWith("refs/") ? [ref] : [`refs/heads/${ref}`, `refs/tags/${ref}`];
|
|
436
|
+
const commit = (lines.find(([, name]) => names.some((candidate) => name === `${candidate}^{}`)) ?? lines.find(([, name]) => names.includes(name ?? "")))?.[0];
|
|
437
|
+
if (!isCommitHash(commit))
|
|
438
|
+
return yield* new SourceResolutionFailed({ reason: `${url} has no ${ref ?? "default branch"}` });
|
|
439
|
+
return commit;
|
|
440
|
+
});
|
|
441
|
+
var npmLatest = (packageName) => NpmRegistry.use((registry) => registry.latest(packageName)).pipe(mapError2((error) => new SourceResolutionFailed({ reason: error.message })));
|
|
442
|
+
var SourceResolver = Reference("kitman/agent-runtime/SourceResolver", {
|
|
443
|
+
defaultValue: () => ({ gitCommit, npmLatest })
|
|
444
|
+
});
|
|
445
|
+
var withSourceResolutionCache2 = (effect) => gen(function* () {
|
|
446
|
+
const resolver = yield* SourceResolver;
|
|
447
|
+
const answers = new Map;
|
|
448
|
+
const once = (key, resolve) => suspend3(() => answers.get(key) ?? resolve.pipe(exit, flatMap3((exit) => {
|
|
449
|
+
answers.set(key, exit);
|
|
450
|
+
return exit;
|
|
451
|
+
})));
|
|
452
|
+
return yield* effect.pipe(provideService(SourceResolver, {
|
|
453
|
+
gitCommit: (url, ref) => once(`git:${url}#${ref ?? ""}`, resolver.gitCommit(url, ref)),
|
|
454
|
+
npmLatest: (packageName) => once(`npm:${packageName}`, resolver.npmLatest(packageName))
|
|
455
|
+
}));
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// packages/agent-runtime/src/skill-materializer.ts
|
|
459
|
+
var gitSourceKey = (source) => `${source.url}#${source.ref ?? ""}:${source.path ?? ""}`;
|
|
460
|
+
var approvedCommits = (approved, source) => approved.flatMap((entry) => {
|
|
461
|
+
const [commit, path] = entry.startsWith(`${source.url}#`) ? entry.slice(source.url.length + 1).split(/:(.*)/su) : [];
|
|
462
|
+
return isCommitHash(commit) && path === (source.path ?? "") ? [commit] : [];
|
|
463
|
+
});
|
|
464
|
+
var resolveGitSource = (subject, source, input) => gen(function* () {
|
|
465
|
+
if (!source.url.startsWith("https://"))
|
|
466
|
+
return { problem: `${subject} must come from an https URL, not ${source.url}.` };
|
|
467
|
+
const resolver = yield* SourceResolver;
|
|
468
|
+
const latest = input.versions === "latest";
|
|
469
|
+
const ref = latest ? undefined : source.ref;
|
|
470
|
+
const resolved = (isCommitHash(ref) ? succeed6(ref) : resolver.gitCommit(source.url, ref)).pipe(map8((commit) => ({ commit })), catch_((error) => succeed6({
|
|
471
|
+
problem: `Kitman could not resolve ${subject} from ${source.url}${ref === undefined ? "" : ` at ${ref}`}: ${error.message}`
|
|
472
|
+
})));
|
|
473
|
+
if (input.requireReviewedSources !== true)
|
|
474
|
+
return yield* resolved;
|
|
475
|
+
const approved = approvedCommits(input.approvedSkillSources ?? [], source);
|
|
476
|
+
const wanted = isCommitHash(ref) ? { commit: ref } : undefined;
|
|
477
|
+
if (ref === undefined && !latest && approved.length > 0)
|
|
478
|
+
return { commit: approved.at(-1) ?? "" };
|
|
479
|
+
const candidate = wanted ?? (yield* resolved);
|
|
480
|
+
if ("problem" in candidate)
|
|
481
|
+
return { rejected: true, problem: `${candidate.problem} Under review Kitman keeps no copy it cannot check.` };
|
|
482
|
+
if (approved.includes(candidate.commit))
|
|
483
|
+
return candidate;
|
|
484
|
+
return {
|
|
485
|
+
rejected: true,
|
|
486
|
+
problem: `${subject} is not approved at ${candidate.commit}; approve it by adding ${source.url}#${candidate.commit}:${source.path ?? ""} to approvedSkillSources in your Kitman user config, or remove it from the Kit.`
|
|
487
|
+
};
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
class AgentEnvironmentMaterializationError2 extends TaggedError("AgentEnvironmentMaterializationError") {
|
|
491
|
+
get message() {
|
|
492
|
+
return `AgentEnvironment materialization failed: ${this.issues.map((issue) => issue.message).join("; ")}`;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
class SkillMaterializationRefused2 extends TaggedError("SkillMaterializationRefused") {
|
|
497
|
+
}
|
|
498
|
+
var previewAgentEnvironmentMaterialization2 = fnUntraced(function* (input) {
|
|
499
|
+
const fs = yield* FileSystem;
|
|
500
|
+
const path = yield* Path;
|
|
501
|
+
const home = input.rootDir ?? (yield* optionalEnv("HOME")) ?? ".";
|
|
502
|
+
const harness = input.environment.harness;
|
|
503
|
+
const sourceRoot = input.cacheRoot ?? path.join(home, ".kitman", "agent-environment", "skills");
|
|
504
|
+
const targetRoot = skillBasePath(path, harness, input.scope, input.rootDir, home);
|
|
505
|
+
const operations = [];
|
|
506
|
+
const materializedSkills = [];
|
|
507
|
+
const materializedPlugins = [];
|
|
508
|
+
const skippedPlugins = [];
|
|
509
|
+
const warnings = [];
|
|
510
|
+
const unresolvedLinks = [];
|
|
511
|
+
for (const skill of input.environment.skills) {
|
|
512
|
+
const skillKey = skillIdentity2(skill.item);
|
|
513
|
+
const targetPath = path.join(targetRoot, `${skillSlug(skill.item.name)}-${hashSegment(skillKey)}`);
|
|
514
|
+
const source = skill.item.source;
|
|
515
|
+
const fromKit = skill.source.label === undefined ? "" : ` (from Kit ${skill.source.label})`;
|
|
516
|
+
const gitSkillPath = path.join(sourceRoot, "git", hashSegment(skillKey), skillSlug(skill.item.name));
|
|
517
|
+
const problem = source.kind === "kitman" ? source.contentMarkdown === undefined ? `Kitman skill ${skill.item.name} has no contentMarkdown to materialize.` : undefined : input.noInstall === true ? `Skipping materialization for ${skill.item.name} because sync was run with noInstall.` : source.kind !== "git" ? `Skill ${skill.item.name}${fromKit} comes from ${source.kind}, which Kitman cannot install yet; only Kitman and git skills are installed. Remove it from the Kit or make it recommended.` : undefined;
|
|
518
|
+
const resolution = problem === undefined && source.kind === "git" ? yield* resolveGitSource(`Git skill ${skill.item.name}${fromKit}`, source, input) : undefined;
|
|
519
|
+
const issueMessage = problem ?? (resolution !== undefined && "problem" in resolution ? resolution.problem : undefined);
|
|
520
|
+
if (issueMessage !== undefined) {
|
|
521
|
+
const issue = { message: issueMessage, skillName: skill.item.name, skillKey };
|
|
522
|
+
if (skill.item.policy === "required")
|
|
523
|
+
return yield* new AgentEnvironmentMaterializationError2({ issues: [issue] });
|
|
524
|
+
warnings.push(issue);
|
|
525
|
+
if (resolution !== undefined && "rejected" in resolution)
|
|
526
|
+
continue;
|
|
527
|
+
unresolvedLinks.push(targetPath);
|
|
528
|
+
if (source.kind === "git" && (yield* fs.exists(gitSkillPath).pipe(orElseSucceed(() => false))))
|
|
529
|
+
materializedSkills.push({
|
|
530
|
+
name: skill.item.name,
|
|
531
|
+
key: skillKey,
|
|
532
|
+
harness,
|
|
533
|
+
source,
|
|
534
|
+
skillPath: gitSkillPath,
|
|
535
|
+
managed: true
|
|
536
|
+
});
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const commit = resolution !== undefined && "commit" in resolution ? resolution.commit : undefined;
|
|
540
|
+
const expose = (skillPath) => {
|
|
541
|
+
operations.push({
|
|
542
|
+
kind: "symlink_skill",
|
|
543
|
+
skillName: skill.item.name,
|
|
544
|
+
skillKey,
|
|
545
|
+
sourcePath: skillPath,
|
|
546
|
+
targetPath,
|
|
547
|
+
sourceRoot,
|
|
548
|
+
targetRoot
|
|
549
|
+
});
|
|
550
|
+
materializedSkills.push({
|
|
551
|
+
name: skill.item.name,
|
|
552
|
+
key: skillKey,
|
|
553
|
+
harness,
|
|
554
|
+
source,
|
|
555
|
+
skillPath,
|
|
556
|
+
managed: true,
|
|
557
|
+
...commit === undefined ? undefined : { commit }
|
|
558
|
+
});
|
|
559
|
+
};
|
|
560
|
+
if (source.kind === "git" && commit !== undefined) {
|
|
561
|
+
operations.push({
|
|
562
|
+
kind: "fetch_git_skill",
|
|
563
|
+
skillName: skill.item.name,
|
|
564
|
+
skillKey,
|
|
565
|
+
url: source.url,
|
|
566
|
+
ref: commit,
|
|
567
|
+
...source.path === undefined ? undefined : { repositoryPath: source.path },
|
|
568
|
+
path: gitSkillPath
|
|
569
|
+
});
|
|
570
|
+
expose(gitSkillPath);
|
|
571
|
+
} else if (source.kind === "kitman" && source.contentMarkdown !== undefined) {
|
|
572
|
+
const skillPath = path.join(sourceRoot, "kitman", hashSegment(source.skillId ?? source.name ?? skill.item.name), skillSlug(skill.item.name));
|
|
573
|
+
operations.push({
|
|
574
|
+
kind: "write_skill",
|
|
575
|
+
skillName: skill.item.name,
|
|
576
|
+
skillKey,
|
|
577
|
+
path: path.join(skillPath, "SKILL.md"),
|
|
578
|
+
content: skillMarkdown(skill.item.name, source.contentMarkdown)
|
|
579
|
+
});
|
|
580
|
+
expose(skillPath);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const codexMarketplace = harness === "codex" && input.scope === "user";
|
|
584
|
+
const marketplaceRoot = path.join(sourceRoot, "codex-marketplace");
|
|
585
|
+
const claudePluginsRoot = path.join(sourceRoot, "git-plugins");
|
|
586
|
+
for (const plugin of harness === "claude-code" || codexMarketplace ? input.environment.plugins : []) {
|
|
587
|
+
const source = plugin.item.sources.find((candidate) => candidate.kind === "git");
|
|
588
|
+
if (source === undefined)
|
|
589
|
+
continue;
|
|
590
|
+
const name = plugin.item.name;
|
|
591
|
+
const pluginKey = `plugin:git:${gitSourceKey(source)}:${name}`;
|
|
592
|
+
const fromKit = plugin.source.label === undefined ? "" : ` (from Kit ${plugin.source.label})`;
|
|
593
|
+
if (input.noInstall === true) {
|
|
594
|
+
skippedPlugins.push(name);
|
|
595
|
+
warnings.push({
|
|
596
|
+
message: `Skipping materialization for plugin ${name} because sync was run with noInstall.`,
|
|
597
|
+
skillName: name,
|
|
598
|
+
skillKey: pluginKey
|
|
599
|
+
});
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
const directoryName = `${skillSlug(name)}-${hashSegment(pluginKey)}`;
|
|
603
|
+
const [pluginPath, manifest] = harness === "codex" ? [path.join(marketplaceRoot, "plugins", directoryName), ".codex-plugin/plugin.json"] : [path.join(claudePluginsRoot, hashSegment(pluginKey), skillSlug(name)), ".claude-plugin/plugin.json"];
|
|
604
|
+
const resolution = yield* resolveGitSource(`Git plugin ${name}${fromKit}`, source, input);
|
|
605
|
+
if ("problem" in resolution) {
|
|
606
|
+
const issue = { message: resolution.problem, skillName: name, skillKey: pluginKey };
|
|
607
|
+
if (plugin.item.policy === "required")
|
|
608
|
+
return yield* new AgentEnvironmentMaterializationError2({ issues: [issue] });
|
|
609
|
+
warnings.push(issue);
|
|
610
|
+
if ("rejected" in resolution)
|
|
611
|
+
continue;
|
|
612
|
+
unresolvedLinks.push(path.join(targetRoot, directoryName));
|
|
613
|
+
if (yield* fs.exists(path.join(pluginPath, manifest)).pipe(orElseSucceed(() => false)))
|
|
614
|
+
materializedPlugins.push({
|
|
615
|
+
name,
|
|
616
|
+
key: pluginKey,
|
|
617
|
+
harness,
|
|
618
|
+
source,
|
|
619
|
+
pluginPath,
|
|
620
|
+
...harness === "codex" ? { marketplaceRoot } : undefined
|
|
621
|
+
});
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
operations.push({
|
|
625
|
+
kind: "fetch_git_plugin",
|
|
626
|
+
pluginName: name,
|
|
627
|
+
pluginKey,
|
|
628
|
+
url: source.url,
|
|
629
|
+
ref: resolution.commit,
|
|
630
|
+
manifest,
|
|
631
|
+
path: pluginPath,
|
|
632
|
+
sourceRoot
|
|
633
|
+
});
|
|
634
|
+
if (harness === "codex") {
|
|
635
|
+
materializedPlugins.push({
|
|
636
|
+
name,
|
|
637
|
+
key: pluginKey,
|
|
638
|
+
harness,
|
|
639
|
+
source,
|
|
640
|
+
pluginPath,
|
|
641
|
+
marketplaceRoot,
|
|
642
|
+
commit: resolution.commit
|
|
643
|
+
});
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
operations.push({
|
|
647
|
+
kind: "symlink_skill",
|
|
648
|
+
skillName: name,
|
|
649
|
+
skillKey: pluginKey,
|
|
650
|
+
sourcePath: pluginPath,
|
|
651
|
+
targetPath: path.join(targetRoot, directoryName),
|
|
652
|
+
sourceRoot,
|
|
653
|
+
targetRoot
|
|
654
|
+
});
|
|
655
|
+
materializedPlugins.push({ name, key: pluginKey, harness, source, pluginPath, commit: resolution.commit });
|
|
656
|
+
}
|
|
657
|
+
if (input.noInstall !== true)
|
|
658
|
+
operations.push({
|
|
659
|
+
kind: "unlink_stale_links",
|
|
660
|
+
path: targetRoot,
|
|
661
|
+
sourceRoot,
|
|
662
|
+
keepPaths: [
|
|
663
|
+
...operations.flatMap((operation) => operation.kind === "symlink_skill" ? [operation.targetPath] : []),
|
|
664
|
+
...unresolvedLinks
|
|
665
|
+
],
|
|
666
|
+
sharedWith: AgentHarnessIds.filter((other) => other !== harness && skillBasePath(path, other, input.scope, input.rootDir, home) === targetRoot).map((other) => ({
|
|
667
|
+
harness: other,
|
|
668
|
+
manifestPath: path.join(input.scope === "user" ? home : input.rootDir ?? ".", ".kitman", `agent-environment.${other}.manifest.json`)
|
|
669
|
+
}))
|
|
670
|
+
});
|
|
671
|
+
const marketplacePath = path.join(marketplaceRoot, ".agents", "plugins", "marketplace.json");
|
|
672
|
+
if (codexMarketplace && input.noInstall !== true)
|
|
673
|
+
operations.push({
|
|
674
|
+
kind: "write_codex_marketplace",
|
|
675
|
+
path: marketplacePath,
|
|
676
|
+
content: `${yield* encodePrettyJsonString({
|
|
677
|
+
name: "kitman",
|
|
678
|
+
interface: { displayName: "Kitman" },
|
|
679
|
+
plugins: materializedPlugins.map((plugin) => ({
|
|
680
|
+
name: plugin.name,
|
|
681
|
+
source: { source: "local", path: `./${path.relative(marketplaceRoot, plugin.pluginPath)}` },
|
|
682
|
+
policy: { installation: "INSTALLED_BY_DEFAULT", authentication: "ON_INSTALL" },
|
|
683
|
+
category: "Productivity"
|
|
684
|
+
}))
|
|
685
|
+
}).pipe(orDie)}
|
|
686
|
+
`,
|
|
687
|
+
sourceRoot
|
|
688
|
+
});
|
|
689
|
+
return {
|
|
690
|
+
harness,
|
|
691
|
+
operations,
|
|
692
|
+
materializedSkills,
|
|
693
|
+
materializedPlugins,
|
|
694
|
+
skippedPlugins,
|
|
695
|
+
warnings,
|
|
696
|
+
...codexMarketplace && materializedPlugins.length > 0 ? { codexHookTrust: { home, marketplacePath } } : undefined
|
|
697
|
+
};
|
|
698
|
+
});
|
|
699
|
+
var gitCheckouts2 = gen(function* () {
|
|
700
|
+
const scope = yield* Scope;
|
|
701
|
+
const trees = new Map;
|
|
702
|
+
const checkout = (url, ref) => gen(function* () {
|
|
703
|
+
const key = `${url}#${ref}`;
|
|
704
|
+
const known = trees.get(key);
|
|
705
|
+
if (known !== undefined)
|
|
706
|
+
return known;
|
|
707
|
+
const tree = yield* checkoutGitTree(url, ref).pipe(provide(scope));
|
|
708
|
+
trees.set(key, tree);
|
|
709
|
+
return tree;
|
|
710
|
+
});
|
|
711
|
+
return checkout;
|
|
712
|
+
});
|
|
713
|
+
var applyAgentEnvironmentMaterialization2 = (plan, checkout) => gen(function* () {
|
|
714
|
+
const checkoutTree = checkout ?? (yield* gitCheckouts2);
|
|
715
|
+
for (const operation of plan.operations) {
|
|
716
|
+
if (operation.kind === "write_skill")
|
|
717
|
+
yield* writeSkill(plan, operation);
|
|
718
|
+
else if (operation.kind === "fetch_git_skill")
|
|
719
|
+
yield* fetchGitSkill(plan, operation, checkoutTree);
|
|
720
|
+
else if (operation.kind === "fetch_git_plugin")
|
|
721
|
+
yield* fetchGitPlugin(operation, checkoutTree);
|
|
722
|
+
else if (operation.kind === "write_codex_marketplace")
|
|
723
|
+
yield* writeCodexMarketplace(operation);
|
|
724
|
+
else if (operation.kind === "unlink_stale_links")
|
|
725
|
+
yield* unlinkStaleLinks(operation);
|
|
726
|
+
else
|
|
727
|
+
yield* exposeSkill(operation);
|
|
728
|
+
}
|
|
729
|
+
return plan;
|
|
730
|
+
}).pipe(scoped);
|
|
731
|
+
var refuse = (message) => fail7(new SkillMaterializationRefused2({ message }));
|
|
732
|
+
var managedSkillPath = (plan, skillKey, file) => gen(function* () {
|
|
733
|
+
const boundary = plan.operations.find((candidate) => candidate.kind === "symlink_skill" && candidate.skillKey === skillKey);
|
|
734
|
+
if (boundary === undefined)
|
|
735
|
+
return yield* refuse("SKILL_SYMLINK_BOUNDARY_MISSING");
|
|
736
|
+
return yield* managedPath(boundary.sourceRoot, file);
|
|
737
|
+
});
|
|
738
|
+
var managedPath = (sourceRoot, file) => gen(function* () {
|
|
739
|
+
const path = yield* Path;
|
|
740
|
+
const root = path.resolve(sourceRoot);
|
|
741
|
+
const target = path.resolve(file);
|
|
742
|
+
if (!isPathWithin2(path, root, target) || target === root)
|
|
743
|
+
return yield* refuse("SKILL_WRITE_OUTSIDE_ROOT");
|
|
744
|
+
yield* assertNoSymlinkComponents(path.dirname(target), "SKILL_WRITE_OUTSIDE_ROOT");
|
|
745
|
+
return target;
|
|
746
|
+
});
|
|
747
|
+
var writeSkill = (plan, operation) => gen(function* () {
|
|
748
|
+
const fs = yield* FileSystem;
|
|
749
|
+
const path = yield* Path;
|
|
750
|
+
const target = yield* managedSkillPath(plan, operation.skillKey, operation.path);
|
|
751
|
+
if (yield* isSymbolicLink(target))
|
|
752
|
+
return yield* refuse("SKILL_WRITE_SYMLINK");
|
|
753
|
+
yield* fs.makeDirectory(path.dirname(target), { recursive: true });
|
|
754
|
+
yield* fs.writeFileString(target, operation.content);
|
|
755
|
+
});
|
|
756
|
+
var checkoutGitTree = (url, ref) => gen(function* () {
|
|
757
|
+
const fs = yield* FileSystem;
|
|
758
|
+
const path = yield* Path;
|
|
759
|
+
const scratch = yield* fs.makeTempDirectoryScoped({ prefix: "kitman-git-" });
|
|
760
|
+
const repository = path.join(scratch, "repository");
|
|
761
|
+
const tree = path.join(scratch, "tree");
|
|
762
|
+
const git = (...args) => runCommand2("git", ["--git-dir", repository, ...args]);
|
|
763
|
+
yield* fs.makeDirectory(tree);
|
|
764
|
+
yield* runCommand2("git", ["init", "--quiet", "--bare", repository]);
|
|
765
|
+
yield* git("fetch", "--quiet", "--depth", "1", "--", url, ref);
|
|
766
|
+
yield* git("--work-tree", tree, "checkout", "FETCH_HEAD", "--", ".");
|
|
767
|
+
return tree;
|
|
768
|
+
});
|
|
769
|
+
var replaceWithCopy = (directory, target) => gen(function* () {
|
|
770
|
+
const fs = yield* FileSystem;
|
|
771
|
+
const path = yield* Path;
|
|
772
|
+
const staged = `${target}.partial`;
|
|
773
|
+
yield* fs.remove(staged, { recursive: true, force: true });
|
|
774
|
+
yield* fs.makeDirectory(path.dirname(target), { recursive: true });
|
|
775
|
+
yield* fs.copy(directory, staged);
|
|
776
|
+
yield* fs.remove(target, { recursive: true, force: true });
|
|
777
|
+
yield* fs.rename(staged, target);
|
|
778
|
+
});
|
|
779
|
+
var fetchGitSkill = (plan, operation, checkout) => gen(function* () {
|
|
780
|
+
const fs = yield* FileSystem;
|
|
781
|
+
const path = yield* Path;
|
|
782
|
+
const target = yield* managedSkillPath(plan, operation.skillKey, operation.path);
|
|
783
|
+
const tree = yield* checkout(operation.url, operation.ref);
|
|
784
|
+
const skillDirectory = path.resolve(tree, operation.repositoryPath ?? ".");
|
|
785
|
+
if (!isPathWithin2(path, tree, skillDirectory))
|
|
786
|
+
return yield* refuse("SKILL_SOURCE_OUTSIDE_ROOT");
|
|
787
|
+
if (yield* containsSymbolicLink(tree, skillDirectory))
|
|
788
|
+
return yield* refuse(`Git skill ${operation.skillName} links to a file outside itself in ${operation.url}@${operation.ref}; Kitman does not install linked files.`);
|
|
789
|
+
if (!(yield* fs.exists(path.join(skillDirectory, "SKILL.md"))))
|
|
790
|
+
return yield* refuse(`Git skill ${operation.skillName} has no SKILL.md at ${operation.repositoryPath ?? "the repository root"} in ${operation.url}@${operation.ref}.`);
|
|
791
|
+
yield* replaceWithCopy(skillDirectory, target);
|
|
792
|
+
});
|
|
793
|
+
var writeCodexMarketplace = (operation) => gen(function* () {
|
|
794
|
+
const fs = yield* FileSystem;
|
|
795
|
+
const path = yield* Path;
|
|
796
|
+
const target = yield* managedPath(operation.sourceRoot, operation.path);
|
|
797
|
+
if (yield* isSymbolicLink(target))
|
|
798
|
+
return yield* refuse("SKILL_WRITE_SYMLINK");
|
|
799
|
+
yield* fs.makeDirectory(path.dirname(target), { recursive: true });
|
|
800
|
+
yield* fs.writeFileString(target, operation.content);
|
|
801
|
+
});
|
|
802
|
+
var fetchGitPlugin = (operation, checkout) => gen(function* () {
|
|
803
|
+
const fs = yield* FileSystem;
|
|
804
|
+
const path = yield* Path;
|
|
805
|
+
const target = yield* managedPath(operation.sourceRoot, operation.path);
|
|
806
|
+
const tree = yield* checkout(operation.url, operation.ref);
|
|
807
|
+
if (yield* containsSymbolicLink(tree, tree))
|
|
808
|
+
return yield* refuse(`Git plugin ${operation.pluginName} links to a file in ${operation.url}@${operation.ref}; Kitman does not install linked files.`);
|
|
809
|
+
if (!(yield* fs.exists(path.join(tree, operation.manifest))))
|
|
810
|
+
return yield* refuse(`Git plugin ${operation.pluginName} has no ${operation.manifest} at the root of ${operation.url}@${operation.ref}.`);
|
|
811
|
+
yield* replaceWithCopy(tree, target);
|
|
812
|
+
});
|
|
813
|
+
var unlinkStaleLinks = (operation) => gen(function* () {
|
|
814
|
+
const fs = yield* FileSystem;
|
|
815
|
+
const path = yield* Path;
|
|
816
|
+
if (!(yield* fs.exists(operation.path)))
|
|
817
|
+
return;
|
|
818
|
+
yield* assertNoSymlinkComponents(operation.path, "SKILL_TARGET_SYMLINK_PARENT");
|
|
819
|
+
const sharedSkillPaths = new Set((yield* forEach(operation.sharedWith, (shared) => readManifestSkillPaths2(shared.manifestPath))).flatMap((paths) => [...paths].map((skillPath) => path.resolve(skillPath))));
|
|
820
|
+
for (const entry of yield* fs.readDirectory(operation.path)) {
|
|
821
|
+
const link = path.join(operation.path, entry);
|
|
822
|
+
if (operation.keepPaths.includes(link) || !(yield* isSymbolicLink(link)))
|
|
823
|
+
continue;
|
|
824
|
+
const linkTarget = path.resolve(operation.path, yield* fs.readLink(link));
|
|
825
|
+
if (isPathWithin2(path, operation.sourceRoot, linkTarget) && !sharedSkillPaths.has(linkTarget))
|
|
826
|
+
yield* fs.remove(link);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
var containsSymbolicLink = (root, directory) => gen(function* () {
|
|
830
|
+
const fs = yield* FileSystem;
|
|
831
|
+
const path = yield* Path;
|
|
832
|
+
for (let current = directory;current !== root; current = path.dirname(current))
|
|
833
|
+
if (yield* isSymbolicLink(current))
|
|
834
|
+
return true;
|
|
835
|
+
if (!(yield* fs.exists(directory)))
|
|
836
|
+
return false;
|
|
837
|
+
for (const entry of yield* fs.readDirectory(directory, { recursive: true }))
|
|
838
|
+
if (yield* isSymbolicLink(path.join(directory, entry)))
|
|
839
|
+
return true;
|
|
840
|
+
return false;
|
|
841
|
+
});
|
|
842
|
+
var exposeSkill = (operation) => gen(function* () {
|
|
843
|
+
const fs = yield* FileSystem;
|
|
844
|
+
const path = yield* Path;
|
|
845
|
+
if (operation.sourceRoot.length === 0 || operation.targetRoot.length === 0)
|
|
846
|
+
return yield* refuse("SKILL_SYMLINK_BOUNDARY_MISSING");
|
|
847
|
+
yield* assertNoSymlinkComponents(operation.sourceRoot, "SKILL_SOURCE_SYMLINK_PARENT");
|
|
848
|
+
const sourceRoot = yield* realPathOr(operation.sourceRoot, "SKILL_SOURCE_ROOT_UNAVAILABLE");
|
|
849
|
+
const sourcePath = yield* realPathOr(operation.sourcePath, "SKILL_SOURCE_UNAVAILABLE");
|
|
850
|
+
if (!isPathWithin2(path, sourceRoot, sourcePath))
|
|
851
|
+
return yield* refuse("SKILL_SOURCE_OUTSIDE_ROOT");
|
|
852
|
+
const skillDocument = path.join(sourcePath, "SKILL.md");
|
|
853
|
+
if ((yield* fs.exists(skillDocument)) && !isPathWithin2(path, sourcePath, yield* fs.realPath(skillDocument)))
|
|
854
|
+
return yield* refuse("SKILL_SOURCE_OUTSIDE_ROOT");
|
|
855
|
+
const targetRoot = path.resolve(operation.targetRoot);
|
|
856
|
+
const targetPath = path.resolve(operation.targetPath);
|
|
857
|
+
const targetParent = path.dirname(targetPath);
|
|
858
|
+
if (!isPathWithin2(path, targetRoot, targetPath) || !isPathWithin2(path, targetRoot, targetParent))
|
|
859
|
+
return yield* refuse("SKILL_TARGET_OUTSIDE_ROOT");
|
|
860
|
+
yield* assertNoSymlinkComponents(targetParent, "SKILL_TARGET_SYMLINK_PARENT");
|
|
861
|
+
yield* fs.makeDirectory(targetParent, { recursive: true });
|
|
862
|
+
if (yield* isSymbolicLink(targetPath)) {
|
|
863
|
+
const linkTarget = yield* fs.readLink(targetPath);
|
|
864
|
+
const resolvedLink = path.resolve(targetParent, linkTarget);
|
|
865
|
+
if ((yield* fs.exists(resolvedLink)) && (yield* fs.realPath(resolvedLink)) === sourcePath)
|
|
866
|
+
return;
|
|
867
|
+
if (!linkTarget.includes("/.kitman/agent-environment/skills/"))
|
|
868
|
+
return yield* refuse(`Refusing to replace non-Kitman skill symlink: ${targetPath}`);
|
|
869
|
+
yield* fs.remove(targetPath);
|
|
870
|
+
} else if (yield* fs.exists(targetPath)) {
|
|
871
|
+
return yield* refuse(`Refusing to replace user-owned skill path: ${targetPath}`);
|
|
872
|
+
}
|
|
873
|
+
yield* fs.symlink(path.resolve(operation.sourcePath), targetPath);
|
|
874
|
+
});
|
|
875
|
+
var isSymbolicLink = (file) => flatMap3(FileSystem, (fs) => fs.readLink(file).pipe(as(true), orElseSucceed(() => false)));
|
|
876
|
+
var realPathOr = (file, reason) => flatMap3(FileSystem, (fs) => fs.realPath(file).pipe(mapError2(() => new SkillMaterializationRefused2({ message: reason }))));
|
|
877
|
+
var assertNoSymlinkComponents = (file, reason) => gen(function* () {
|
|
878
|
+
const path = yield* Path;
|
|
879
|
+
for (let current = path.resolve(file);; current = path.dirname(current)) {
|
|
880
|
+
if (yield* isSymbolicLink(current))
|
|
881
|
+
return yield* refuse(reason);
|
|
882
|
+
if (path.dirname(current) === current)
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
function isPathWithin2(path, root, candidate) {
|
|
887
|
+
const remainder = path.relative(path.resolve(root), path.resolve(candidate));
|
|
888
|
+
return remainder === "" || remainder !== ".." && !remainder.startsWith(`..${path.sep}`) && !path.isAbsolute(remainder);
|
|
889
|
+
}
|
|
890
|
+
function skillIdentity2(skill) {
|
|
891
|
+
const source = skill.source;
|
|
892
|
+
if (source.kind === "skills_sh")
|
|
893
|
+
return `skills_sh:${source.slug}${source.version === undefined ? "" : `@${source.version}`}:${skill.name}`;
|
|
894
|
+
if (source.kind === "kitman")
|
|
895
|
+
return `kitman:${source.skillId ?? source.name ?? skill.name}:${skill.name}`;
|
|
896
|
+
if (source.kind === "git")
|
|
897
|
+
return `git:${source.url}#${source.ref ?? ""}:${source.path ?? ""}:${skill.name}`;
|
|
898
|
+
if (source.kind === "package")
|
|
899
|
+
return `package:${source.packageName}@${source.version}:${source.path ?? ""}:${skill.name}`;
|
|
900
|
+
if (source.kind === "url")
|
|
901
|
+
return `url:${source.sha256}:${skill.name}`;
|
|
902
|
+
return `local_path:${source.path}:${skill.name}`;
|
|
903
|
+
}
|
|
904
|
+
var ProjectSkillDirectories = {
|
|
905
|
+
"claude-code": ".claude/skills",
|
|
906
|
+
codex: ".agents/skills",
|
|
907
|
+
opencode: ".agents/skills",
|
|
908
|
+
pi: ".pi/skills"
|
|
909
|
+
};
|
|
910
|
+
function skillBasePath(path, harness, scope, rootDir, home) {
|
|
911
|
+
if (scope !== "user")
|
|
912
|
+
return path.join(rootDir ?? ".", ProjectSkillDirectories[harness]);
|
|
913
|
+
return harness === "claude-code" ? path.join(home, ".claude", "skills") : path.join(home, ".agents", "skills");
|
|
914
|
+
}
|
|
915
|
+
function skillMarkdown(name, contentMarkdown) {
|
|
916
|
+
const content = contentMarkdown.trimEnd();
|
|
917
|
+
if (content.trimStart().startsWith("---"))
|
|
918
|
+
return `${content}
|
|
919
|
+
`;
|
|
920
|
+
return `---
|
|
921
|
+
name: ${skillSlug(name)}
|
|
922
|
+
description: ${JSON.stringify(name)}
|
|
923
|
+
---
|
|
924
|
+
|
|
925
|
+
${content}
|
|
926
|
+
`;
|
|
927
|
+
}
|
|
928
|
+
function skillSlug(name) {
|
|
929
|
+
const slug = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
930
|
+
return slug.length === 0 ? "kitman-skill" : slug;
|
|
931
|
+
}
|
|
932
|
+
function hashSegment(value) {
|
|
933
|
+
return sha2562(value).slice(0, 16);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export { writeAgentEnvironmentConfigFile2, readManifestSkillPaths2, tomlTableHeader2, tomlKeyPath2, SourceResolver, withSourceResolutionCache2, AgentEnvironmentMaterializationError2, SkillMaterializationRefused2, previewAgentEnvironmentMaterialization2, gitCheckouts2, applyAgentEnvironmentMaterialization2, isPathWithin2, skillIdentity2 };
|