@przeprogramowani/10x-cli 0.6.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +196 -38
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2271,7 +2271,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
2271
2271
|
// package.json
|
|
2272
2272
|
var package_default = {
|
|
2273
2273
|
name: "@przeprogramowani/10x-cli",
|
|
2274
|
-
version: "
|
|
2274
|
+
version: "1.1.0",
|
|
2275
2275
|
description: "Open-source CLI for 10xDevs course content",
|
|
2276
2276
|
repository: {
|
|
2277
2277
|
type: "git",
|
|
@@ -3949,6 +3949,7 @@ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as rea
|
|
|
3949
3949
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
3950
3950
|
var MANIFEST_FILENAME = ".10x-cli-manifest.json";
|
|
3951
3951
|
var CLI_PACKAGE_NAME = "@przeprogramowani/10x-cli";
|
|
3952
|
+
var MANIFEST_VERSION = 2;
|
|
3952
3953
|
function readManifest(dir) {
|
|
3953
3954
|
const manifestPath = join3(dir, MANIFEST_FILENAME);
|
|
3954
3955
|
if (!existsSync3(manifestPath))
|
|
@@ -3986,6 +3987,8 @@ function isManifest(value) {
|
|
|
3986
3987
|
return false;
|
|
3987
3988
|
if (typeof v2["version"] !== "string")
|
|
3988
3989
|
return false;
|
|
3990
|
+
if (v2["manifestVersion"] !== MANIFEST_VERSION)
|
|
3991
|
+
return false;
|
|
3989
3992
|
if (typeof v2["lastApplied"] !== "string")
|
|
3990
3993
|
return false;
|
|
3991
3994
|
if (typeof v2["lessonId"] !== "string")
|
|
@@ -3996,7 +3999,19 @@ function isManifest(value) {
|
|
|
3996
3999
|
if (typeof files !== "object" || files === null)
|
|
3997
4000
|
return false;
|
|
3998
4001
|
const f2 = files;
|
|
3999
|
-
return
|
|
4002
|
+
return isSkillsRecord(f2["skills"]) && isStringArray(f2["prompts"]) && isStringArray(f2["configs"]);
|
|
4003
|
+
}
|
|
4004
|
+
function isSkillsRecord(value) {
|
|
4005
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
4006
|
+
return false;
|
|
4007
|
+
for (const entry of Object.values(value)) {
|
|
4008
|
+
if (typeof entry !== "object" || entry === null)
|
|
4009
|
+
return false;
|
|
4010
|
+
const e2 = entry;
|
|
4011
|
+
if (!isStringArray(e2["files"]))
|
|
4012
|
+
return false;
|
|
4013
|
+
}
|
|
4014
|
+
return true;
|
|
4000
4015
|
}
|
|
4001
4016
|
function isStringArray(value) {
|
|
4002
4017
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
@@ -4010,6 +4025,7 @@ var PROFILES = {
|
|
|
4010
4025
|
toolId: "claude-code",
|
|
4011
4026
|
displayName: "Claude Code",
|
|
4012
4027
|
skillPath: (n) => `.claude/skills/${n}/SKILL.md`,
|
|
4028
|
+
skillDir: (n) => `.claude/skills/${n}`,
|
|
4013
4029
|
promptPath: (n) => `.claude/prompts/${n}.md`,
|
|
4014
4030
|
configPath: (n) => `.claude/config-templates/${n}`,
|
|
4015
4031
|
rulesFile: "CLAUDE.md",
|
|
@@ -4021,6 +4037,7 @@ var PROFILES = {
|
|
|
4021
4037
|
toolId: "cursor",
|
|
4022
4038
|
displayName: "Cursor",
|
|
4023
4039
|
skillPath: (n) => `.cursor/skills/${n}/SKILL.md`,
|
|
4040
|
+
skillDir: (n) => `.cursor/skills/${n}`,
|
|
4024
4041
|
promptPath: (n) => `.cursor/prompts/${n}.md`,
|
|
4025
4042
|
configPath: (n) => `.cursor/config-templates/${n}`,
|
|
4026
4043
|
rulesFile: ".cursor/rules/10x-course.mdc",
|
|
@@ -4032,6 +4049,7 @@ var PROFILES = {
|
|
|
4032
4049
|
toolId: "copilot",
|
|
4033
4050
|
displayName: "GitHub Copilot",
|
|
4034
4051
|
skillPath: (n) => `.github/skills/${n}/SKILL.md`,
|
|
4052
|
+
skillDir: (n) => `.github/skills/${n}`,
|
|
4035
4053
|
promptPath: (n) => `.github/prompts/${n}.md`,
|
|
4036
4054
|
configPath: (n) => `.github/config-templates/${n}`,
|
|
4037
4055
|
rulesFile: ".github/copilot-instructions.md",
|
|
@@ -4043,6 +4061,7 @@ var PROFILES = {
|
|
|
4043
4061
|
toolId: "codex",
|
|
4044
4062
|
displayName: "Codex CLI",
|
|
4045
4063
|
skillPath: (n) => `.agents/skills/${n}/SKILL.md`,
|
|
4064
|
+
skillDir: (n) => `.agents/skills/${n}`,
|
|
4046
4065
|
promptPath: (n) => `.agents/prompts/${n}.md`,
|
|
4047
4066
|
configPath: (n) => `.agents/config-templates/${n}`,
|
|
4048
4067
|
rulesFile: "AGENTS.md",
|
|
@@ -4054,6 +4073,7 @@ var PROFILES = {
|
|
|
4054
4073
|
toolId: "generic",
|
|
4055
4074
|
displayName: "Other / Generic",
|
|
4056
4075
|
skillPath: (n) => `.ai/skills/${n}/SKILL.md`,
|
|
4076
|
+
skillDir: (n) => `.ai/skills/${n}`,
|
|
4057
4077
|
promptPath: (n) => `.ai/prompts/${n}.md`,
|
|
4058
4078
|
configPath: (n) => `.ai/config-templates/${n}`,
|
|
4059
4079
|
rulesFile: "AGENTS.md",
|
|
@@ -4157,7 +4177,7 @@ import {
|
|
|
4157
4177
|
mkdirSync as mkdirSync5,
|
|
4158
4178
|
readFileSync as readFileSync5,
|
|
4159
4179
|
renameSync as renameSync2,
|
|
4160
|
-
rmdirSync,
|
|
4180
|
+
rmdirSync as rmdirSync2,
|
|
4161
4181
|
rmSync as rmSync3,
|
|
4162
4182
|
statSync,
|
|
4163
4183
|
writeFileSync as writeFileSync4
|
|
@@ -4243,9 +4263,12 @@ function stripBlock(content, begin, end, label, warnings) {
|
|
|
4243
4263
|
|
|
4244
4264
|
// src/lib/writer.ts
|
|
4245
4265
|
import {
|
|
4266
|
+
chmodSync as chmodSync2,
|
|
4246
4267
|
existsSync as existsSync5,
|
|
4247
4268
|
mkdirSync as mkdirSync4,
|
|
4248
4269
|
readFileSync as readFileSync4,
|
|
4270
|
+
readdirSync,
|
|
4271
|
+
rmdirSync,
|
|
4249
4272
|
rmSync as rmSync2,
|
|
4250
4273
|
writeFileSync as writeFileSync3
|
|
4251
4274
|
} from "node:fs";
|
|
@@ -4259,19 +4282,30 @@ function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4259
4282
|
const profile = options.profile ?? PROFILES[DEFAULT_TOOL];
|
|
4260
4283
|
const manifestDir = join5(projectRoot, profile.manifestDir);
|
|
4261
4284
|
const prevManifest = readManifest(manifestDir);
|
|
4262
|
-
for (const skill of bundle.skills)
|
|
4285
|
+
for (const skill of bundle.skills) {
|
|
4263
4286
|
assertSafeName(skill.name, "skill");
|
|
4287
|
+
for (const file of skill.files) {
|
|
4288
|
+
assertSafeSkillFilePath(file.path, skill.name);
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4264
4291
|
for (const prompt of bundle.prompts)
|
|
4265
4292
|
assertSafeName(prompt.name, "prompt");
|
|
4266
4293
|
for (const config of bundle.configs)
|
|
4267
4294
|
assertSafeName(config.name, "config");
|
|
4268
4295
|
const skills = bundle.skills.map((skill) => {
|
|
4269
|
-
const
|
|
4270
|
-
const
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4296
|
+
const skillDir = join5(projectRoot, profile.skillDir(skill.name));
|
|
4297
|
+
const fileWrites = skill.files.map((file) => {
|
|
4298
|
+
const target = join5(skillDir, file.path);
|
|
4299
|
+
const action = computeFileAction(target, file.content);
|
|
4300
|
+
if (!dryRun && action !== "unchanged") {
|
|
4301
|
+
writeFileAt(target, file.content);
|
|
4302
|
+
}
|
|
4303
|
+
if (!dryRun && file.executable === true && action !== "unchanged") {
|
|
4304
|
+
chmodSync2(target, 493);
|
|
4305
|
+
}
|
|
4306
|
+
return { path: file.path, absolutePath: target, action };
|
|
4307
|
+
});
|
|
4308
|
+
return { name: skill.name, files: fileWrites };
|
|
4275
4309
|
});
|
|
4276
4310
|
const prompts = bundle.prompts.map((prompt) => {
|
|
4277
4311
|
const target = join5(projectRoot, profile.promptPath(prompt.name));
|
|
@@ -4312,8 +4346,12 @@ function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4312
4346
|
});
|
|
4313
4347
|
if (!dryRun && !partial) {
|
|
4314
4348
|
const removed = computeRemovals(prevManifest, bundle, profile, projectRoot);
|
|
4315
|
-
for (const entry of removed.
|
|
4349
|
+
for (const entry of removed.skillDirs)
|
|
4316
4350
|
rmSync2(entry.path, { recursive: true, force: true });
|
|
4351
|
+
for (const entry of removed.skillFiles) {
|
|
4352
|
+
rmSync2(entry.path, { force: true });
|
|
4353
|
+
removeEmptyParentDirs(entry.path, entry.skillDirAbs);
|
|
4354
|
+
}
|
|
4317
4355
|
for (const entry of removed.prompts)
|
|
4318
4356
|
rmSync2(entry.path, { force: true });
|
|
4319
4357
|
for (const entry of removed.configs)
|
|
@@ -4323,12 +4361,13 @@ function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4323
4361
|
const nextManifest = {
|
|
4324
4362
|
package: CLI_PACKAGE_NAME,
|
|
4325
4363
|
version: CLI_VERSION,
|
|
4364
|
+
manifestVersion: MANIFEST_VERSION,
|
|
4326
4365
|
lastApplied: new Date().toISOString(),
|
|
4327
4366
|
lessonId: bundle.lessonId,
|
|
4328
4367
|
course,
|
|
4329
4368
|
tool: profile.toolId,
|
|
4330
4369
|
files: {
|
|
4331
|
-
skills: bundle.skills.map((s) => s.name),
|
|
4370
|
+
skills: Object.fromEntries(bundle.skills.map((s) => [s.name, { files: s.files.map((f2) => f2.path) }])),
|
|
4332
4371
|
prompts: bundle.prompts.map((p2) => `${p2.name}.md`),
|
|
4333
4372
|
configs: bundle.configs.map((c2) => c2.name)
|
|
4334
4373
|
}
|
|
@@ -4367,20 +4406,43 @@ function writeFileAt(filePath, content) {
|
|
|
4367
4406
|
writeFileSync3(filePath, content);
|
|
4368
4407
|
}
|
|
4369
4408
|
function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
4370
|
-
const empty = {
|
|
4409
|
+
const empty = {
|
|
4410
|
+
skillDirs: [],
|
|
4411
|
+
skillFiles: [],
|
|
4412
|
+
prompts: [],
|
|
4413
|
+
configs: []
|
|
4414
|
+
};
|
|
4371
4415
|
if (!prevManifest)
|
|
4372
4416
|
return empty;
|
|
4373
|
-
const currentSkills = new
|
|
4417
|
+
const currentSkills = new Map(bundle.skills.map((s) => [s.name, new Set(s.files.map((f2) => f2.path))]));
|
|
4374
4418
|
const currentPrompts = new Set(bundle.prompts.map((p2) => `${p2.name}.md`));
|
|
4375
4419
|
const currentConfigs = new Set(bundle.configs.map((c2) => c2.name));
|
|
4376
|
-
const removed = {
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4420
|
+
const removed = {
|
|
4421
|
+
skillDirs: [],
|
|
4422
|
+
skillFiles: [],
|
|
4423
|
+
prompts: [],
|
|
4424
|
+
configs: []
|
|
4425
|
+
};
|
|
4426
|
+
for (const [skillName, entry] of Object.entries(prevManifest.files.skills)) {
|
|
4380
4427
|
if (!isSafeName(skillName))
|
|
4381
4428
|
continue;
|
|
4382
|
-
const
|
|
4383
|
-
|
|
4429
|
+
const skillDirAbs = join5(projectRoot, profile.skillDir(skillName));
|
|
4430
|
+
if (!currentSkills.has(skillName)) {
|
|
4431
|
+
removed.skillDirs.push({ name: skillName, path: skillDirAbs });
|
|
4432
|
+
continue;
|
|
4433
|
+
}
|
|
4434
|
+
const currentFiles = currentSkills.get(skillName);
|
|
4435
|
+
for (const relPath of entry.files) {
|
|
4436
|
+
if (currentFiles.has(relPath))
|
|
4437
|
+
continue;
|
|
4438
|
+
if (!isSafeSkillFilePath(relPath))
|
|
4439
|
+
continue;
|
|
4440
|
+
removed.skillFiles.push({
|
|
4441
|
+
name: `${skillName}/${relPath}`,
|
|
4442
|
+
path: join5(skillDirAbs, relPath),
|
|
4443
|
+
skillDirAbs
|
|
4444
|
+
});
|
|
4445
|
+
}
|
|
4384
4446
|
}
|
|
4385
4447
|
for (const promptFile of prevManifest.files.prompts) {
|
|
4386
4448
|
if (currentPrompts.has(promptFile))
|
|
@@ -4405,6 +4467,19 @@ function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
|
4405
4467
|
}
|
|
4406
4468
|
return removed;
|
|
4407
4469
|
}
|
|
4470
|
+
function removeEmptyParentDirs(filePath, skillDirAbs) {
|
|
4471
|
+
let dir = dirname3(filePath);
|
|
4472
|
+
while (dir.startsWith(skillDirAbs) && dir !== skillDirAbs) {
|
|
4473
|
+
try {
|
|
4474
|
+
if (readdirSync(dir).length > 0)
|
|
4475
|
+
return;
|
|
4476
|
+
rmdirSync(dir);
|
|
4477
|
+
} catch {
|
|
4478
|
+
return;
|
|
4479
|
+
}
|
|
4480
|
+
dir = dirname3(dir);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4408
4483
|
function findOrphanedManifests(projectRoot, currentProfile) {
|
|
4409
4484
|
const out = [];
|
|
4410
4485
|
for (const profile of Object.values(PROFILES)) {
|
|
@@ -4433,6 +4508,29 @@ function assertSafeName(name, kind) {
|
|
|
4433
4508
|
throw new Error(`refused to write unsafe ${kind} name ${JSON.stringify(name)} — must not be empty, contain path separators, or start with '.'`);
|
|
4434
4509
|
}
|
|
4435
4510
|
}
|
|
4511
|
+
function assertSafeSkillFilePath(relPath, skillName) {
|
|
4512
|
+
if (!isSafeSkillFilePath(relPath)) {
|
|
4513
|
+
throw new Error(`refused to write unsafe file path ${JSON.stringify(relPath)} inside skill ${JSON.stringify(skillName)}`);
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
function isSafeSkillFilePath(relPath) {
|
|
4517
|
+
if (typeof relPath !== "string" || relPath.length === 0)
|
|
4518
|
+
return false;
|
|
4519
|
+
if (relPath.startsWith("/") || relPath.startsWith("\\"))
|
|
4520
|
+
return false;
|
|
4521
|
+
if (/^[a-zA-Z]:/.test(relPath))
|
|
4522
|
+
return false;
|
|
4523
|
+
const segments = relPath.split(/[/\\]/);
|
|
4524
|
+
if (segments.length === 0)
|
|
4525
|
+
return false;
|
|
4526
|
+
for (const seg of segments) {
|
|
4527
|
+
if (seg === "" || seg === "." || seg === "..")
|
|
4528
|
+
return false;
|
|
4529
|
+
if (!isSafeName(seg))
|
|
4530
|
+
return false;
|
|
4531
|
+
}
|
|
4532
|
+
return true;
|
|
4533
|
+
}
|
|
4436
4534
|
function isSafeName(name) {
|
|
4437
4535
|
if (typeof name !== "string" || name.length === 0)
|
|
4438
4536
|
return false;
|
|
@@ -4464,16 +4562,30 @@ function migrateArtifacts(projectRoot, orphan, newProfile) {
|
|
|
4464
4562
|
skipped: []
|
|
4465
4563
|
};
|
|
4466
4564
|
const oldProfile = orphan.profile;
|
|
4467
|
-
for (const skillName of orphan.manifest.files.skills) {
|
|
4565
|
+
for (const [skillName, entry] of Object.entries(orphan.manifest.files.skills)) {
|
|
4468
4566
|
if (!isSafeName(skillName)) {
|
|
4469
4567
|
summary.skipped.push({ path: skillName, reason: "unsafe name in manifest" });
|
|
4470
4568
|
continue;
|
|
4471
4569
|
}
|
|
4472
|
-
const
|
|
4473
|
-
const
|
|
4474
|
-
|
|
4475
|
-
|
|
4570
|
+
const fromSkillDir = join6(projectRoot, oldProfile.skillDir(skillName));
|
|
4571
|
+
const toSkillDir = join6(projectRoot, newProfile.skillDir(skillName));
|
|
4572
|
+
let anyMoved = false;
|
|
4573
|
+
for (const relPath of entry.files) {
|
|
4574
|
+
if (!isSafeSkillFilePath(relPath)) {
|
|
4575
|
+
summary.skipped.push({
|
|
4576
|
+
path: `${skillName}/${relPath}`,
|
|
4577
|
+
reason: "unsafe path in manifest"
|
|
4578
|
+
});
|
|
4579
|
+
continue;
|
|
4580
|
+
}
|
|
4581
|
+
const from = join6(fromSkillDir, relPath);
|
|
4582
|
+
const to = join6(toSkillDir, relPath);
|
|
4583
|
+
if (moveIfSafe(from, to, summary.skipped))
|
|
4584
|
+
anyMoved = true;
|
|
4476
4585
|
}
|
|
4586
|
+
tryRemoveEmptyDir(fromSkillDir);
|
|
4587
|
+
if (anyMoved)
|
|
4588
|
+
summary.movedOrRemoved.skills.push(skillName);
|
|
4477
4589
|
}
|
|
4478
4590
|
for (const promptFile of orphan.manifest.files.prompts) {
|
|
4479
4591
|
if (!isSafeName(promptFile)) {
|
|
@@ -4512,15 +4624,23 @@ function deleteArtifacts(projectRoot, orphan) {
|
|
|
4512
4624
|
skipped: []
|
|
4513
4625
|
};
|
|
4514
4626
|
const oldProfile = orphan.profile;
|
|
4515
|
-
for (const skillName of orphan.manifest.files.skills) {
|
|
4627
|
+
for (const [skillName, entry] of Object.entries(orphan.manifest.files.skills)) {
|
|
4516
4628
|
if (!isSafeName(skillName)) {
|
|
4517
4629
|
summary.skipped.push({ path: skillName, reason: "unsafe name in manifest" });
|
|
4518
4630
|
continue;
|
|
4519
4631
|
}
|
|
4520
|
-
const
|
|
4521
|
-
const
|
|
4522
|
-
|
|
4523
|
-
|
|
4632
|
+
const skillDir = join6(projectRoot, oldProfile.skillDir(skillName));
|
|
4633
|
+
for (const relPath of entry.files) {
|
|
4634
|
+
if (!isSafeSkillFilePath(relPath)) {
|
|
4635
|
+
summary.skipped.push({
|
|
4636
|
+
path: `${skillName}/${relPath}`,
|
|
4637
|
+
reason: "unsafe path in manifest"
|
|
4638
|
+
});
|
|
4639
|
+
continue;
|
|
4640
|
+
}
|
|
4641
|
+
rmSync3(join6(skillDir, relPath), { force: true });
|
|
4642
|
+
}
|
|
4643
|
+
rmSync3(skillDir, { recursive: true, force: true });
|
|
4524
4644
|
summary.movedOrRemoved.skills.push(skillName);
|
|
4525
4645
|
}
|
|
4526
4646
|
for (const promptFile of orphan.manifest.files.prompts) {
|
|
@@ -4623,7 +4743,7 @@ function tryRemoveEmptyDir(dir) {
|
|
|
4623
4743
|
if (!existsSync6(dir))
|
|
4624
4744
|
return;
|
|
4625
4745
|
try {
|
|
4626
|
-
|
|
4746
|
+
rmdirSync2(dir);
|
|
4627
4747
|
} catch {}
|
|
4628
4748
|
}
|
|
4629
4749
|
|
|
@@ -4853,7 +4973,14 @@ async function runPrintMode(ctx, lessonId, course, profile, token, lang, options
|
|
|
4853
4973
|
if (ctx.json) {
|
|
4854
4974
|
output(ctx, "", result.data);
|
|
4855
4975
|
} else {
|
|
4856
|
-
|
|
4976
|
+
const data = result.data;
|
|
4977
|
+
if (data.type === "skills") {
|
|
4978
|
+
const skillMd = data.files.find((f2) => f2.path === "SKILL.md");
|
|
4979
|
+
process.stdout.write(skillMd?.content ?? "");
|
|
4980
|
+
emitMultiFileSkillNotice(data.name, data.files);
|
|
4981
|
+
} else {
|
|
4982
|
+
process.stdout.write(data.content);
|
|
4983
|
+
}
|
|
4857
4984
|
}
|
|
4858
4985
|
} else {
|
|
4859
4986
|
verbose(ctx, `fetching lesson ${course}/${lessonId} (filtering by ${options.type})`);
|
|
@@ -4861,17 +4988,40 @@ async function runPrintMode(ctx, lessonId, course, profile, token, lang, options
|
|
|
4861
4988
|
if (!result.ok) {
|
|
4862
4989
|
handleLessonError(ctx, result.status, result.code, result.error, result.payload);
|
|
4863
4990
|
}
|
|
4864
|
-
const
|
|
4865
|
-
if (
|
|
4866
|
-
|
|
4991
|
+
const type = options.type;
|
|
4992
|
+
if (type === "skills") {
|
|
4993
|
+
const skills = result.data.skills;
|
|
4994
|
+
if (ctx.json) {
|
|
4995
|
+
output(ctx, "", skills);
|
|
4996
|
+
} else {
|
|
4997
|
+
const contents = skills.map((s) => s.files.find((f2) => f2.path === "SKILL.md")?.content ?? "");
|
|
4998
|
+
process.stdout.write(contents.join(`
|
|
4999
|
+
---
|
|
5000
|
+
`));
|
|
5001
|
+
for (const skill of skills)
|
|
5002
|
+
emitMultiFileSkillNotice(skill.name, skill.files);
|
|
5003
|
+
}
|
|
4867
5004
|
} else {
|
|
4868
|
-
const
|
|
4869
|
-
|
|
5005
|
+
const artifacts = result.data[type];
|
|
5006
|
+
if (ctx.json) {
|
|
5007
|
+
output(ctx, "", artifacts);
|
|
5008
|
+
} else {
|
|
5009
|
+
const contents = artifacts.map((a2) => a2.content);
|
|
5010
|
+
process.stdout.write(contents.join(`
|
|
4870
5011
|
---
|
|
4871
5012
|
`));
|
|
5013
|
+
}
|
|
4872
5014
|
}
|
|
4873
5015
|
}
|
|
4874
5016
|
}
|
|
5017
|
+
function emitMultiFileSkillNotice(skillName, files) {
|
|
5018
|
+
const extras = files.filter((f2) => f2.path !== "SKILL.md").map((f2) => f2.path);
|
|
5019
|
+
if (extras.length === 0)
|
|
5020
|
+
return;
|
|
5021
|
+
process.stderr.write(`Note: skill "${skillName}" has ${extras.length} additional file${extras.length === 1 ? "" : "s"} not shown in --print: ${extras.join(", ")}.
|
|
5022
|
+
` + `Run without --print to materialize all files.
|
|
5023
|
+
`);
|
|
5024
|
+
}
|
|
4875
5025
|
function handleLessonError(ctx, status, code, error, payload) {
|
|
4876
5026
|
verbose(ctx, `lesson fetch failed: status=${status} code=${code}`);
|
|
4877
5027
|
if (status === 403) {
|
|
@@ -4938,7 +5088,15 @@ function renderGetResult(ctx, bundle, writeResult, dryRun, profile, langMeta = {
|
|
|
4938
5088
|
lines.push("");
|
|
4939
5089
|
lines.push(dryRun ? `Would write to ${targetDir}/:` : `Wrote to ${targetDir}/:`);
|
|
4940
5090
|
for (const skill of writeResult.skills) {
|
|
4941
|
-
|
|
5091
|
+
if (skill.files.length === 1) {
|
|
5092
|
+
const f2 = skill.files[0];
|
|
5093
|
+
lines.push(` [${f2.action}] skill ${f2.absolutePath}`);
|
|
5094
|
+
} else {
|
|
5095
|
+
lines.push(` skill ${skill.name} (${skill.files.length} files)`);
|
|
5096
|
+
for (const f2 of skill.files) {
|
|
5097
|
+
lines.push(` [${f2.action}] ${f2.path}`);
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
4942
5100
|
}
|
|
4943
5101
|
for (const prompt of writeResult.prompts) {
|
|
4944
5102
|
lines.push(` [${prompt.action}] prompt ${prompt.path}`);
|