@cleocode/caamp 2026.4.7 → 2026.4.9
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-HEAGCHKU.js → chunk-JC77OAHA.js} +790 -44
- package/dist/chunk-JC77OAHA.js.map +1 -0
- package/dist/cli.js +279 -29
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1103 -290
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunk-HEAGCHKU.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
tokenizeCriteriaValue,
|
|
53
53
|
updateInstructionsSingleOperation,
|
|
54
54
|
validateSkill
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-JC77OAHA.js";
|
|
56
56
|
import {
|
|
57
57
|
buildSkillsMap,
|
|
58
58
|
checkAllInjections,
|
|
@@ -1873,7 +1873,7 @@ function registerMcpCommands(program2) {
|
|
|
1873
1873
|
registerMcpRemoveCommand(mcp);
|
|
1874
1874
|
}
|
|
1875
1875
|
|
|
1876
|
-
// src/commands/pi/
|
|
1876
|
+
// src/commands/pi/cant.ts
|
|
1877
1877
|
import { existsSync as existsSync4 } from "fs";
|
|
1878
1878
|
import { writeFile } from "fs/promises";
|
|
1879
1879
|
import { tmpdir as tmpdir3 } from "os";
|
|
@@ -1989,8 +1989,8 @@ function resolveProjectDir2(tier, explicit) {
|
|
|
1989
1989
|
return process.cwd();
|
|
1990
1990
|
}
|
|
1991
1991
|
|
|
1992
|
-
// src/commands/pi/
|
|
1993
|
-
async function
|
|
1992
|
+
// src/commands/pi/cant.ts
|
|
1993
|
+
async function resolveCantSource(source) {
|
|
1994
1994
|
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../") || source.startsWith("~")) {
|
|
1995
1995
|
const expanded = source.startsWith("~/") ? join5(process.env["HOME"] ?? "", source.slice(2)) : source;
|
|
1996
1996
|
if (!existsSync4(expanded)) {
|
|
@@ -2001,12 +2001,11 @@ async function resolveExtensionSource(source) {
|
|
|
2001
2001
|
false
|
|
2002
2002
|
);
|
|
2003
2003
|
}
|
|
2004
|
-
const inferredName = inferNameFromPath(expanded);
|
|
2005
2004
|
return {
|
|
2006
2005
|
localPath: expanded,
|
|
2007
2006
|
cleanup: async () => {
|
|
2008
2007
|
},
|
|
2009
|
-
inferredName
|
|
2008
|
+
inferredName: inferNameFromPath(expanded)
|
|
2010
2009
|
};
|
|
2011
2010
|
}
|
|
2012
2011
|
if (/^https?:\/\//.test(source)) {
|
|
@@ -2058,7 +2057,7 @@ async function resolveExtensionSource(source) {
|
|
|
2058
2057
|
}
|
|
2059
2058
|
const body = await resp.text();
|
|
2060
2059
|
const baseName = inferNameFromUrl(source);
|
|
2061
|
-
const tmp = join5(tmpdir3(), `caamp-pi-
|
|
2060
|
+
const tmp = join5(tmpdir3(), `caamp-pi-cant-${process.pid}-${Date.now()}-${baseName}.cant`);
|
|
2062
2061
|
await writeFile(tmp, body, "utf8");
|
|
2063
2062
|
return {
|
|
2064
2063
|
localPath: tmp,
|
|
@@ -2093,15 +2092,265 @@ async function resolveExtensionSource(source) {
|
|
|
2093
2092
|
throw new LAFSCommandError(
|
|
2094
2093
|
PI_ERROR_CODES.VALIDATION,
|
|
2095
2094
|
`Unsupported source: ${source}`,
|
|
2096
|
-
"Use a local file path, HTTPS URL, or GitHub shorthand (owner/repo/path.
|
|
2095
|
+
"Use a local file path, HTTPS URL, or GitHub shorthand (owner/repo/path.cant).",
|
|
2097
2096
|
false
|
|
2098
2097
|
);
|
|
2099
2098
|
}
|
|
2100
2099
|
function inferNameFromPath(filePath) {
|
|
2101
2100
|
const base = filePath.split(/[/\\]/).pop() ?? filePath;
|
|
2102
|
-
return base.replace(/\.
|
|
2101
|
+
return base.replace(/\.cant$/, "");
|
|
2103
2102
|
}
|
|
2104
2103
|
function inferNameFromUrl(url) {
|
|
2104
|
+
try {
|
|
2105
|
+
const u = new URL(url);
|
|
2106
|
+
const seg = u.pathname.split("/").filter(Boolean).pop() ?? "profile";
|
|
2107
|
+
return seg.replace(/\.cant$/, "");
|
|
2108
|
+
} catch {
|
|
2109
|
+
return "profile";
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
async function invokeInstallCantProfile(harness, sourcePath, name, tier, projectDir, installOpts) {
|
|
2113
|
+
try {
|
|
2114
|
+
return await harness.installCantProfile(sourcePath, name, tier, projectDir, installOpts);
|
|
2115
|
+
} catch (err) {
|
|
2116
|
+
rethrowAsLafs(err);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function rethrowAsLafs(err) {
|
|
2120
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2121
|
+
if (/already exists/i.test(message)) {
|
|
2122
|
+
throw new LAFSCommandError(
|
|
2123
|
+
PI_ERROR_CODES.CONFLICT,
|
|
2124
|
+
message,
|
|
2125
|
+
"Pass --force to overwrite the existing profile.",
|
|
2126
|
+
false
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
if (/does not exist|not found/i.test(message)) {
|
|
2130
|
+
throw new LAFSCommandError(PI_ERROR_CODES.NOT_FOUND, message, "Check the path.", false);
|
|
2131
|
+
}
|
|
2132
|
+
if (/failed cant-core validation|expected a CANT source file|not a regular file/i.test(message)) {
|
|
2133
|
+
throw new LAFSCommandError(
|
|
2134
|
+
PI_ERROR_CODES.VALIDATION,
|
|
2135
|
+
message,
|
|
2136
|
+
"Run `caamp pi cant validate <path>` to inspect the diagnostics.",
|
|
2137
|
+
false
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
throw new LAFSCommandError(
|
|
2141
|
+
"E_INTERNAL_UNEXPECTED",
|
|
2142
|
+
message,
|
|
2143
|
+
"Inspect the message for the underlying cant-core failure mode.",
|
|
2144
|
+
false
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
function registerPiCantCommands(parent) {
|
|
2148
|
+
const cant = parent.command("cant").description("Manage Pi CANT profiles across tiers");
|
|
2149
|
+
cant.command("list").description("List Pi CANT profiles across project, user, and global tiers").option("--scope <tier>", "Filter to a single tier: project|user|global").option("--project-dir <path>", "Project directory for the project tier (default: cwd)").action(
|
|
2150
|
+
async (opts) => runLafsCommand("pi.cant.list", "standard", async () => {
|
|
2151
|
+
const harness = requirePiHarness();
|
|
2152
|
+
const projectDir = opts.projectDir ?? process.cwd();
|
|
2153
|
+
const allEntries = await harness.listCantProfiles(projectDir);
|
|
2154
|
+
const filterTier = opts.scope === void 0 ? null : parseScope2(opts.scope, "project");
|
|
2155
|
+
const entries = filterTier === null ? allEntries : allEntries.filter((e) => e.tier === filterTier);
|
|
2156
|
+
const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
|
|
2157
|
+
return {
|
|
2158
|
+
count: sorted.length,
|
|
2159
|
+
entries: sorted
|
|
2160
|
+
};
|
|
2161
|
+
})
|
|
2162
|
+
);
|
|
2163
|
+
cant.command("install <source>").description("Install a Pi CANT profile from a local path, HTTPS URL, or GitHub shorthand").option("--scope <tier>", "Install tier: project|user|global (default: project)").option("--name <name>", "Override the inferred profile name").option("--force", "Overwrite an existing profile at the target tier").option("--project-dir <path>", "Project directory for the project tier (default: cwd)").action(
|
|
2164
|
+
async (source, opts) => runLafsCommand("pi.cant.install", "standard", async () => {
|
|
2165
|
+
const harness = requirePiHarness();
|
|
2166
|
+
const tier = parseScope2(opts.scope, "project");
|
|
2167
|
+
const projectDir = resolveProjectDir2(tier, opts.projectDir);
|
|
2168
|
+
const resolved = await resolveCantSource(source);
|
|
2169
|
+
try {
|
|
2170
|
+
const name = opts.name ?? resolved.inferredName;
|
|
2171
|
+
const installOpts = { force: opts.force ?? false };
|
|
2172
|
+
const result = await invokeInstallCantProfile(
|
|
2173
|
+
harness,
|
|
2174
|
+
resolved.localPath,
|
|
2175
|
+
name,
|
|
2176
|
+
tier,
|
|
2177
|
+
projectDir,
|
|
2178
|
+
installOpts
|
|
2179
|
+
);
|
|
2180
|
+
return {
|
|
2181
|
+
installed: {
|
|
2182
|
+
name,
|
|
2183
|
+
tier: result.tier,
|
|
2184
|
+
targetPath: result.targetPath,
|
|
2185
|
+
counts: result.counts,
|
|
2186
|
+
source
|
|
2187
|
+
}
|
|
2188
|
+
};
|
|
2189
|
+
} finally {
|
|
2190
|
+
await resolved.cleanup();
|
|
2191
|
+
}
|
|
2192
|
+
})
|
|
2193
|
+
);
|
|
2194
|
+
cant.command("remove <name>").description("Remove a Pi CANT profile from the given tier").option("--scope <tier>", "Target tier: project|user|global (default: project)").option("--project-dir <path>", "Project directory for the project tier (default: cwd)").action(
|
|
2195
|
+
async (name, opts) => runLafsCommand("pi.cant.remove", "standard", async () => {
|
|
2196
|
+
const harness = requirePiHarness();
|
|
2197
|
+
const tier = parseScope2(opts.scope, "project");
|
|
2198
|
+
const projectDir = resolveProjectDir2(tier, opts.projectDir);
|
|
2199
|
+
const removed = await harness.removeCantProfile(name, tier, projectDir);
|
|
2200
|
+
return {
|
|
2201
|
+
name,
|
|
2202
|
+
tier,
|
|
2203
|
+
removed
|
|
2204
|
+
};
|
|
2205
|
+
})
|
|
2206
|
+
);
|
|
2207
|
+
cant.command("validate <path>").description("Validate a .cant file via cant-core without installing it").action(
|
|
2208
|
+
async (path) => runLafsCommand("pi.cant.validate", "standard", async () => {
|
|
2209
|
+
const harness = requirePiHarness();
|
|
2210
|
+
if (!existsSync4(path)) {
|
|
2211
|
+
throw new LAFSCommandError(
|
|
2212
|
+
PI_ERROR_CODES.NOT_FOUND,
|
|
2213
|
+
`Source file does not exist: ${path}`,
|
|
2214
|
+
"Check the path and try again.",
|
|
2215
|
+
false
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
const result = await harness.validateCantProfile(path);
|
|
2219
|
+
if (!result.valid) {
|
|
2220
|
+
throw new LAFSCommandError(
|
|
2221
|
+
PI_ERROR_CODES.VALIDATION,
|
|
2222
|
+
`cant-core validation failed with ${result.errors.length} diagnostic(s)`,
|
|
2223
|
+
"See the `errors` field for ruleId/line/col/message details.",
|
|
2224
|
+
false,
|
|
2225
|
+
{ valid: false, counts: result.counts, errors: result.errors }
|
|
2226
|
+
);
|
|
2227
|
+
}
|
|
2228
|
+
return {
|
|
2229
|
+
valid: true,
|
|
2230
|
+
counts: result.counts,
|
|
2231
|
+
errors: result.errors
|
|
2232
|
+
};
|
|
2233
|
+
})
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/commands/pi/extensions.ts
|
|
2238
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2239
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
2240
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
2241
|
+
import { join as join6 } from "path";
|
|
2242
|
+
async function resolveExtensionSource(source) {
|
|
2243
|
+
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../") || source.startsWith("~")) {
|
|
2244
|
+
const expanded = source.startsWith("~/") ? join6(process.env["HOME"] ?? "", source.slice(2)) : source;
|
|
2245
|
+
if (!existsSync5(expanded)) {
|
|
2246
|
+
throw new LAFSCommandError(
|
|
2247
|
+
PI_ERROR_CODES.NOT_FOUND,
|
|
2248
|
+
`Source file does not exist: ${expanded}`,
|
|
2249
|
+
"Check the path and try again.",
|
|
2250
|
+
false
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
const inferredName = inferNameFromPath2(expanded);
|
|
2254
|
+
return {
|
|
2255
|
+
localPath: expanded,
|
|
2256
|
+
cleanup: async () => {
|
|
2257
|
+
},
|
|
2258
|
+
inferredName
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
if (/^https?:\/\//.test(source)) {
|
|
2262
|
+
const parsed2 = parseSource(source);
|
|
2263
|
+
if (parsed2.type === "github" && parsed2.owner !== void 0 && parsed2.repo !== void 0) {
|
|
2264
|
+
const cloneResult = await cloneRepo(parsed2.owner, parsed2.repo, parsed2.ref);
|
|
2265
|
+
const filePath = parsed2.path !== void 0 ? join6(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
|
|
2266
|
+
if (!existsSync5(filePath)) {
|
|
2267
|
+
await cloneResult.cleanup();
|
|
2268
|
+
throw new LAFSCommandError(
|
|
2269
|
+
PI_ERROR_CODES.NOT_FOUND,
|
|
2270
|
+
`Source path not found inside cloned repo: ${parsed2.path ?? "(root)"}`,
|
|
2271
|
+
"Check the repository URL and path.",
|
|
2272
|
+
false
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
return {
|
|
2276
|
+
localPath: filePath,
|
|
2277
|
+
cleanup: cloneResult.cleanup,
|
|
2278
|
+
inferredName: inferNameFromPath2(filePath)
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
if (parsed2.type === "gitlab" && parsed2.owner !== void 0 && parsed2.repo !== void 0) {
|
|
2282
|
+
const cloneResult = await cloneGitLabRepo(parsed2.owner, parsed2.repo, parsed2.ref);
|
|
2283
|
+
const filePath = parsed2.path !== void 0 ? join6(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
|
|
2284
|
+
if (!existsSync5(filePath)) {
|
|
2285
|
+
await cloneResult.cleanup();
|
|
2286
|
+
throw new LAFSCommandError(
|
|
2287
|
+
PI_ERROR_CODES.NOT_FOUND,
|
|
2288
|
+
`Source path not found inside cloned repo: ${parsed2.path ?? "(root)"}`,
|
|
2289
|
+
"Check the repository URL and path.",
|
|
2290
|
+
false
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
return {
|
|
2294
|
+
localPath: filePath,
|
|
2295
|
+
cleanup: cloneResult.cleanup,
|
|
2296
|
+
inferredName: inferNameFromPath2(filePath)
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
const resp = await fetchWithTimeout(source);
|
|
2300
|
+
if (!resp.ok) {
|
|
2301
|
+
throw new LAFSCommandError(
|
|
2302
|
+
PI_ERROR_CODES.TRANSIENT,
|
|
2303
|
+
`Failed to download source from ${source}: HTTP ${resp.status}`,
|
|
2304
|
+
"Check the URL and network connectivity.",
|
|
2305
|
+
true
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2308
|
+
const body = await resp.text();
|
|
2309
|
+
const baseName = inferNameFromUrl2(source);
|
|
2310
|
+
const tmp = join6(tmpdir4(), `caamp-pi-ext-${process.pid}-${Date.now()}-${baseName}.ts`);
|
|
2311
|
+
await writeFile2(tmp, body, "utf8");
|
|
2312
|
+
return {
|
|
2313
|
+
localPath: tmp,
|
|
2314
|
+
cleanup: async () => {
|
|
2315
|
+
try {
|
|
2316
|
+
await (await import("fs/promises")).rm(tmp, { force: true });
|
|
2317
|
+
} catch {
|
|
2318
|
+
}
|
|
2319
|
+
},
|
|
2320
|
+
inferredName: baseName
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
const parsed = parseSource(source);
|
|
2324
|
+
if (parsed.type === "github" && parsed.owner !== void 0 && parsed.repo !== void 0) {
|
|
2325
|
+
const cloneResult = await cloneRepo(parsed.owner, parsed.repo, parsed.ref);
|
|
2326
|
+
const filePath = parsed.path !== void 0 ? join6(cloneResult.localPath, parsed.path) : cloneResult.localPath;
|
|
2327
|
+
if (!existsSync5(filePath)) {
|
|
2328
|
+
await cloneResult.cleanup();
|
|
2329
|
+
throw new LAFSCommandError(
|
|
2330
|
+
PI_ERROR_CODES.NOT_FOUND,
|
|
2331
|
+
`Source path not found inside cloned repo: ${parsed.path ?? "(root)"}`,
|
|
2332
|
+
"Check the repository shorthand and path.",
|
|
2333
|
+
false
|
|
2334
|
+
);
|
|
2335
|
+
}
|
|
2336
|
+
return {
|
|
2337
|
+
localPath: filePath,
|
|
2338
|
+
cleanup: cloneResult.cleanup,
|
|
2339
|
+
inferredName: inferNameFromPath2(filePath)
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
throw new LAFSCommandError(
|
|
2343
|
+
PI_ERROR_CODES.VALIDATION,
|
|
2344
|
+
`Unsupported source: ${source}`,
|
|
2345
|
+
"Use a local file path, HTTPS URL, or GitHub shorthand (owner/repo/path.ts).",
|
|
2346
|
+
false
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
function inferNameFromPath2(filePath) {
|
|
2350
|
+
const base = filePath.split(/[/\\]/).pop() ?? filePath;
|
|
2351
|
+
return base.replace(/\.(ts|tsx|mts)$/, "");
|
|
2352
|
+
}
|
|
2353
|
+
function inferNameFromUrl2(url) {
|
|
2105
2354
|
try {
|
|
2106
2355
|
const u = new URL(url);
|
|
2107
2356
|
const seg = u.pathname.split("/").filter(Boolean).pop() ?? "extension";
|
|
@@ -2342,8 +2591,8 @@ function registerPiModelsCommands(parent) {
|
|
|
2342
2591
|
}
|
|
2343
2592
|
|
|
2344
2593
|
// src/commands/pi/prompts.ts
|
|
2345
|
-
import { existsSync as
|
|
2346
|
-
import { join as
|
|
2594
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2595
|
+
import { join as join7, resolve } from "path";
|
|
2347
2596
|
function inferPromptName(sourceDir) {
|
|
2348
2597
|
const normalized = resolve(sourceDir).replace(/[\\/]+$/, "");
|
|
2349
2598
|
const base = normalized.split(/[\\/]/).pop();
|
|
@@ -2365,7 +2614,7 @@ function registerPiPromptsCommands(parent) {
|
|
|
2365
2614
|
const tier = parseScope2(opts.scope, "project");
|
|
2366
2615
|
const projectDir = resolveProjectDir2(tier, opts.projectDir);
|
|
2367
2616
|
const absSource = resolve(source);
|
|
2368
|
-
if (!
|
|
2617
|
+
if (!existsSync6(absSource)) {
|
|
2369
2618
|
throw new LAFSCommandError(
|
|
2370
2619
|
PI_ERROR_CODES.NOT_FOUND,
|
|
2371
2620
|
`Source directory does not exist: ${absSource}`,
|
|
@@ -2373,7 +2622,7 @@ function registerPiPromptsCommands(parent) {
|
|
|
2373
2622
|
false
|
|
2374
2623
|
);
|
|
2375
2624
|
}
|
|
2376
|
-
if (!
|
|
2625
|
+
if (!existsSync6(join7(absSource, "prompt.md"))) {
|
|
2377
2626
|
throw new LAFSCommandError(
|
|
2378
2627
|
PI_ERROR_CODES.VALIDATION,
|
|
2379
2628
|
`Source directory is missing prompt.md: ${absSource}`,
|
|
@@ -2418,7 +2667,7 @@ function registerPiPromptsCommands(parent) {
|
|
|
2418
2667
|
|
|
2419
2668
|
// src/commands/pi/sessions.ts
|
|
2420
2669
|
import { spawn } from "child_process";
|
|
2421
|
-
import { createReadStream, createWriteStream, existsSync as
|
|
2670
|
+
import { createReadStream, createWriteStream, existsSync as existsSync7 } from "fs";
|
|
2422
2671
|
import { createInterface } from "readline/promises";
|
|
2423
2672
|
async function streamSession(filePath, outputPath, transform) {
|
|
2424
2673
|
const writeToFile = outputPath !== void 0 && outputPath.length > 0;
|
|
@@ -2575,7 +2824,7 @@ function registerPiSessionsCommands(parent) {
|
|
|
2575
2824
|
);
|
|
2576
2825
|
}
|
|
2577
2826
|
const piBinary = harness.provider.detection.binary ?? "pi";
|
|
2578
|
-
if (!
|
|
2827
|
+
if (!existsSync7(piBinary) && piBinary === "pi") {
|
|
2579
2828
|
}
|
|
2580
2829
|
const child = spawn(piBinary, ["--session", id], {
|
|
2581
2830
|
stdio: "inherit",
|
|
@@ -2602,7 +2851,7 @@ function registerPiSessionsCommands(parent) {
|
|
|
2602
2851
|
}
|
|
2603
2852
|
|
|
2604
2853
|
// src/commands/pi/themes.ts
|
|
2605
|
-
import { existsSync as
|
|
2854
|
+
import { existsSync as existsSync8, statSync } from "fs";
|
|
2606
2855
|
import { extname, resolve as resolve2 } from "path";
|
|
2607
2856
|
function inferThemeName(sourceFile) {
|
|
2608
2857
|
const base = resolve2(sourceFile).split(/[\\/]/).pop();
|
|
@@ -2626,7 +2875,7 @@ function registerPiThemesCommands(parent) {
|
|
|
2626
2875
|
const tier = parseScope2(opts.scope, "project");
|
|
2627
2876
|
const projectDir = resolveProjectDir2(tier, opts.projectDir);
|
|
2628
2877
|
const absSource = resolve2(source);
|
|
2629
|
-
if (!
|
|
2878
|
+
if (!existsSync8(absSource)) {
|
|
2630
2879
|
throw new LAFSCommandError(
|
|
2631
2880
|
PI_ERROR_CODES.NOT_FOUND,
|
|
2632
2881
|
`Source theme does not exist: ${absSource}`,
|
|
@@ -2680,12 +2929,13 @@ function registerPiThemesCommands(parent) {
|
|
|
2680
2929
|
|
|
2681
2930
|
// src/commands/pi/index.ts
|
|
2682
2931
|
function registerPiCommands(program2) {
|
|
2683
|
-
const pi = program2.command("pi").description("Pi harness operations (extensions, sessions, models, prompts, themes)");
|
|
2932
|
+
const pi = program2.command("pi").description("Pi harness operations (extensions, sessions, models, prompts, themes, cant)");
|
|
2684
2933
|
registerPiExtensionsCommands(pi);
|
|
2685
2934
|
registerPiSessionsCommands(pi);
|
|
2686
2935
|
registerPiModelsCommands(pi);
|
|
2687
2936
|
registerPiPromptsCommands(pi);
|
|
2688
2937
|
registerPiThemesCommands(pi);
|
|
2938
|
+
registerPiCantCommands(pi);
|
|
2689
2939
|
}
|
|
2690
2940
|
|
|
2691
2941
|
// src/commands/providers.ts
|
|
@@ -3277,13 +3527,13 @@ function emitJsonError2(operation, mvi, code, message, category, details = {}) {
|
|
|
3277
3527
|
}
|
|
3278
3528
|
|
|
3279
3529
|
// src/commands/skills/audit.ts
|
|
3280
|
-
import { existsSync as
|
|
3530
|
+
import { existsSync as existsSync9, statSync as statSync2 } from "fs";
|
|
3281
3531
|
import pc7 from "picocolors";
|
|
3282
3532
|
function registerSkillsAudit(parent) {
|
|
3283
3533
|
parent.command("audit").description("Security scan skill files (46+ rules, SARIF output)").argument("[path]", "Path to SKILL.md or directory", ".").option("--sarif", "Output in SARIF format (raw SARIF, not LAFS envelope)").option("--json", "Output as JSON (LAFS envelope)").option("--human", "Output in human-readable format").action(async (path, opts) => {
|
|
3284
3534
|
const operation = "skills.audit";
|
|
3285
3535
|
const mvi = "standard";
|
|
3286
|
-
if (!
|
|
3536
|
+
if (!existsSync9(path)) {
|
|
3287
3537
|
const message = `Path not found: ${path}`;
|
|
3288
3538
|
if (opts.sarif) {
|
|
3289
3539
|
console.error(
|
|
@@ -3855,9 +4105,9 @@ function emitJsonError3(operation, mvi, code, message, category, details = {}) {
|
|
|
3855
4105
|
}
|
|
3856
4106
|
|
|
3857
4107
|
// src/commands/skills/init.ts
|
|
3858
|
-
import { existsSync as
|
|
3859
|
-
import { mkdir, writeFile as
|
|
3860
|
-
import { join as
|
|
4108
|
+
import { existsSync as existsSync10 } from "fs";
|
|
4109
|
+
import { mkdir, writeFile as writeFile3 } from "fs/promises";
|
|
4110
|
+
import { join as join8 } from "path";
|
|
3861
4111
|
import pc10 from "picocolors";
|
|
3862
4112
|
function registerSkillsInit(parent) {
|
|
3863
4113
|
parent.command("init").description("Create a new SKILL.md template").argument("[name]", "Skill name").option("-d, --dir <path>", "Output directory", ".").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(
|
|
@@ -3883,8 +4133,8 @@ function registerSkillsInit(parent) {
|
|
|
3883
4133
|
process.exit(1);
|
|
3884
4134
|
}
|
|
3885
4135
|
const skillName = name ?? "my-skill";
|
|
3886
|
-
const skillDir =
|
|
3887
|
-
if (
|
|
4136
|
+
const skillDir = join8(opts.dir, skillName);
|
|
4137
|
+
if (existsSync10(skillDir)) {
|
|
3888
4138
|
const message = `Directory already exists: ${skillDir}`;
|
|
3889
4139
|
if (format === "json") {
|
|
3890
4140
|
emitJsonError(
|
|
@@ -3926,7 +4176,7 @@ Provide detailed instructions for the AI agent here.
|
|
|
3926
4176
|
|
|
3927
4177
|
Show example inputs and expected outputs.
|
|
3928
4178
|
`;
|
|
3929
|
-
await
|
|
4179
|
+
await writeFile3(join8(skillDir, "SKILL.md"), template, "utf-8");
|
|
3930
4180
|
const result = {
|
|
3931
4181
|
name: skillName,
|
|
3932
4182
|
directory: skillDir,
|
|
@@ -3940,14 +4190,14 @@ Show example inputs and expected outputs.
|
|
|
3940
4190
|
console.log(pc10.green(`\u2713 Created skill template: ${skillDir}/SKILL.md`));
|
|
3941
4191
|
console.log(pc10.dim("\nNext steps:"));
|
|
3942
4192
|
console.log(pc10.dim(" 1. Edit SKILL.md with your instructions"));
|
|
3943
|
-
console.log(pc10.dim(` 2. Validate: caamp skills validate ${
|
|
4193
|
+
console.log(pc10.dim(` 2. Validate: caamp skills validate ${join8(skillDir, "SKILL.md")}`));
|
|
3944
4194
|
console.log(pc10.dim(` 3. Install: caamp skills install ${skillDir}`));
|
|
3945
4195
|
}
|
|
3946
4196
|
);
|
|
3947
4197
|
}
|
|
3948
4198
|
|
|
3949
4199
|
// src/commands/skills/install.ts
|
|
3950
|
-
import { existsSync as
|
|
4200
|
+
import { existsSync as existsSync11 } from "fs";
|
|
3951
4201
|
import pc11 from "picocolors";
|
|
3952
4202
|
function registerSkillsInstall(parent) {
|
|
3953
4203
|
parent.command("install").description("Install a skill from GitHub, URL, marketplace, or registered skill library").argument("[source]", "Skill source (GitHub URL, owner/repo, @author/name, skill-name)").option(
|
|
@@ -4432,7 +4682,7 @@ async function handleMarketplaceSource(source, _providers, _isGlobal, format, op
|
|
|
4432
4682
|
for (const subPath of subPathCandidates) {
|
|
4433
4683
|
try {
|
|
4434
4684
|
const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, subPath);
|
|
4435
|
-
if (subPath && !
|
|
4685
|
+
if (subPath && !existsSync11(result.localPath)) {
|
|
4436
4686
|
await result.cleanup();
|
|
4437
4687
|
continue;
|
|
4438
4688
|
}
|