@ncukondo/slide-generation 0.3.0 → 0.4.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/cli/index.js
CHANGED
|
@@ -941,7 +941,7 @@ var ReferenceManager = class {
|
|
|
941
941
|
return map;
|
|
942
942
|
}
|
|
943
943
|
execCommand(cmd) {
|
|
944
|
-
return new Promise((
|
|
944
|
+
return new Promise((resolve8, reject) => {
|
|
945
945
|
exec(cmd, (error, stdout) => {
|
|
946
946
|
if (error) {
|
|
947
947
|
reject(
|
|
@@ -949,7 +949,7 @@ var ReferenceManager = class {
|
|
|
949
949
|
);
|
|
950
950
|
return;
|
|
951
951
|
}
|
|
952
|
-
|
|
952
|
+
resolve8(stdout.toString());
|
|
953
953
|
});
|
|
954
954
|
});
|
|
955
955
|
}
|
|
@@ -1688,7 +1688,7 @@ var Pipeline = class {
|
|
|
1688
1688
|
};
|
|
1689
1689
|
|
|
1690
1690
|
// src/index.ts
|
|
1691
|
-
var VERSION = "0.
|
|
1691
|
+
var VERSION = "0.4.0";
|
|
1692
1692
|
|
|
1693
1693
|
// src/cli/commands/convert.ts
|
|
1694
1694
|
import { Command } from "commander";
|
|
@@ -3337,8 +3337,8 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
3337
3337
|
|
|
3338
3338
|
// src/cli/commands/preview.ts
|
|
3339
3339
|
import { Command as Command3 } from "commander";
|
|
3340
|
-
import { access as access6,
|
|
3341
|
-
import { basename as basename4, dirname as
|
|
3340
|
+
import { access as access6, readdir as readdir4, mkdir as mkdir2, writeFile as writeFile2, readFile as readFile10, rm } from "fs/promises";
|
|
3341
|
+
import { basename as basename4, dirname as dirname5, join as join9, extname as extname2 } from "path";
|
|
3342
3342
|
import * as path6 from "path";
|
|
3343
3343
|
import { tmpdir } from "os";
|
|
3344
3344
|
import { createServer } from "http";
|
|
@@ -3347,16 +3347,24 @@ import { watch as chokidarWatch } from "chokidar";
|
|
|
3347
3347
|
|
|
3348
3348
|
// src/cli/utils/marp-runner.ts
|
|
3349
3349
|
import { existsSync } from "fs";
|
|
3350
|
-
import { join as join8 } from "path";
|
|
3350
|
+
import { join as join8, resolve, dirname as dirname4 } from "path";
|
|
3351
3351
|
import {
|
|
3352
3352
|
execFileSync,
|
|
3353
3353
|
spawn
|
|
3354
3354
|
} from "child_process";
|
|
3355
3355
|
function getMarpCommand(projectDir) {
|
|
3356
|
-
const
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3356
|
+
const startDir = resolve(projectDir ?? process.cwd());
|
|
3357
|
+
let currentDir = startDir;
|
|
3358
|
+
while (true) {
|
|
3359
|
+
const localMarp = join8(currentDir, "node_modules", ".bin", "marp");
|
|
3360
|
+
if (existsSync(localMarp)) {
|
|
3361
|
+
return localMarp;
|
|
3362
|
+
}
|
|
3363
|
+
const parentDir = dirname4(currentDir);
|
|
3364
|
+
if (parentDir === currentDir) {
|
|
3365
|
+
break;
|
|
3366
|
+
}
|
|
3367
|
+
currentDir = parentDir;
|
|
3360
3368
|
}
|
|
3361
3369
|
try {
|
|
3362
3370
|
execFileSync("marp", ["--version"], { stdio: "ignore", timeout: 5e3 });
|
|
@@ -3513,23 +3521,19 @@ async function collectSlideInfo(dir, baseName, format) {
|
|
|
3513
3521
|
async function checkMarpCliAvailable(projectDir) {
|
|
3514
3522
|
return isMarpAvailable(projectDir);
|
|
3515
3523
|
}
|
|
3516
|
-
function
|
|
3524
|
+
function getTempPreviewDir(inputPath) {
|
|
3517
3525
|
const base = basename4(inputPath, ".yaml");
|
|
3518
|
-
return join9(tmpdir(), `slide-gen-preview-${base}-${Date.now()}
|
|
3526
|
+
return join9(tmpdir(), `slide-gen-preview-${base}-${Date.now()}`);
|
|
3519
3527
|
}
|
|
3520
|
-
function buildMarpCommand(
|
|
3521
|
-
const parts = ["marp", "--
|
|
3522
|
-
if (options.port) {
|
|
3523
|
-
parts.push("-p", String(options.port));
|
|
3524
|
-
}
|
|
3528
|
+
function buildMarpCommand(markdownDir, options) {
|
|
3529
|
+
const parts = ["marp", "--server", "-I", markdownDir];
|
|
3525
3530
|
if (options.watch) {
|
|
3526
3531
|
parts.push("--watch");
|
|
3527
3532
|
}
|
|
3528
|
-
parts.push(markdownPath);
|
|
3529
3533
|
return parts.join(" ");
|
|
3530
3534
|
}
|
|
3531
3535
|
function startStaticServer(baseDir, port, options = {}) {
|
|
3532
|
-
return new Promise((
|
|
3536
|
+
return new Promise((resolve8, reject) => {
|
|
3533
3537
|
const mimeTypes = {
|
|
3534
3538
|
".html": "text/html",
|
|
3535
3539
|
".png": "image/png",
|
|
@@ -3566,7 +3570,7 @@ function startStaticServer(baseDir, port, options = {}) {
|
|
|
3566
3570
|
const prefix = options.messagePrefix ?? "Server";
|
|
3567
3571
|
console.log(`${prefix} running at ${chalk3.cyan(url)}`);
|
|
3568
3572
|
}
|
|
3569
|
-
|
|
3573
|
+
resolve8(server);
|
|
3570
3574
|
});
|
|
3571
3575
|
});
|
|
3572
3576
|
}
|
|
@@ -3583,7 +3587,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
3583
3587
|
return { success: false, errors };
|
|
3584
3588
|
}
|
|
3585
3589
|
console.log("Checking for Marp CLI...");
|
|
3586
|
-
const projectDir =
|
|
3590
|
+
const projectDir = dirname5(inputPath);
|
|
3587
3591
|
const marpAvailable = await checkMarpCliAvailable(projectDir);
|
|
3588
3592
|
if (!marpAvailable) {
|
|
3589
3593
|
console.error(
|
|
@@ -3600,7 +3604,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
3600
3604
|
const configLoader = new ConfigLoader();
|
|
3601
3605
|
let configPath = options.config;
|
|
3602
3606
|
if (!configPath) {
|
|
3603
|
-
configPath = await configLoader.findConfig(
|
|
3607
|
+
configPath = await configLoader.findConfig(dirname5(inputPath));
|
|
3604
3608
|
}
|
|
3605
3609
|
const config = await configLoader.load(configPath);
|
|
3606
3610
|
console.log("Initializing pipeline...");
|
|
@@ -3698,11 +3702,11 @@ Showing ${slides.length} slides in gallery mode`);
|
|
|
3698
3702
|
};
|
|
3699
3703
|
process.on("SIGINT", signalHandler);
|
|
3700
3704
|
process.on("SIGTERM", signalHandler);
|
|
3701
|
-
await new Promise((
|
|
3705
|
+
await new Promise((resolve8) => {
|
|
3702
3706
|
if (options.signal) {
|
|
3703
|
-
options.signal.addEventListener("abort", () =>
|
|
3707
|
+
options.signal.addEventListener("abort", () => resolve8());
|
|
3704
3708
|
}
|
|
3705
|
-
server.on("close", () =>
|
|
3709
|
+
server.on("close", () => resolve8());
|
|
3706
3710
|
});
|
|
3707
3711
|
return { success: true, errors };
|
|
3708
3712
|
}
|
|
@@ -3736,7 +3740,7 @@ async function executePreview(inputPath, options) {
|
|
|
3736
3740
|
return { success: false, errors };
|
|
3737
3741
|
}
|
|
3738
3742
|
console.log("Checking for Marp CLI...");
|
|
3739
|
-
const marpAvailable = await checkMarpCliAvailable(
|
|
3743
|
+
const marpAvailable = await checkMarpCliAvailable(dirname5(inputPath));
|
|
3740
3744
|
if (!marpAvailable) {
|
|
3741
3745
|
console.error(
|
|
3742
3746
|
chalk3.red(
|
|
@@ -3751,7 +3755,7 @@ async function executePreview(inputPath, options) {
|
|
|
3751
3755
|
const configLoader = new ConfigLoader();
|
|
3752
3756
|
let configPath = options.config;
|
|
3753
3757
|
if (!configPath) {
|
|
3754
|
-
configPath = await configLoader.findConfig(
|
|
3758
|
+
configPath = await configLoader.findConfig(dirname5(inputPath));
|
|
3755
3759
|
}
|
|
3756
3760
|
const config = await configLoader.load(configPath);
|
|
3757
3761
|
console.log("Initializing pipeline...");
|
|
@@ -3765,7 +3769,9 @@ async function executePreview(inputPath, options) {
|
|
|
3765
3769
|
process.exitCode = ExitCode.ConversionError;
|
|
3766
3770
|
return { success: false, errors };
|
|
3767
3771
|
}
|
|
3768
|
-
const
|
|
3772
|
+
const tempDir = getTempPreviewDir(inputPath);
|
|
3773
|
+
await mkdir2(tempDir, { recursive: true });
|
|
3774
|
+
const tempMarkdownPath = join9(tempDir, "slides.md");
|
|
3769
3775
|
console.log(`Converting ${chalk3.cyan(inputPath)}...`);
|
|
3770
3776
|
try {
|
|
3771
3777
|
await pipeline.runWithResult(inputPath, { outputPath: tempMarkdownPath });
|
|
@@ -3775,23 +3781,29 @@ async function executePreview(inputPath, options) {
|
|
|
3775
3781
|
console.error(chalk3.red(`Error: Conversion failed: ${message}`));
|
|
3776
3782
|
errors.push(message);
|
|
3777
3783
|
process.exitCode = ExitCode.ConversionError;
|
|
3784
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
3778
3785
|
return { success: false, errors };
|
|
3779
3786
|
}
|
|
3780
3787
|
console.log(`
|
|
3781
3788
|
Starting preview server on port ${chalk3.cyan(port)}...`);
|
|
3782
|
-
const marpCommand = buildMarpCommand(
|
|
3789
|
+
const marpCommand = buildMarpCommand(tempDir, {
|
|
3783
3790
|
...options,
|
|
3784
|
-
|
|
3785
|
-
watch: false
|
|
3786
|
-
// We handle watch ourselves if needed
|
|
3791
|
+
watch: options.watch ?? false
|
|
3787
3792
|
});
|
|
3788
3793
|
if (verbose) {
|
|
3789
3794
|
console.log(`Running: ${marpCommand}`);
|
|
3790
3795
|
}
|
|
3791
|
-
const
|
|
3792
|
-
|
|
3796
|
+
const marpArgs = ["--server", "-I", tempDir];
|
|
3797
|
+
if (options.watch) {
|
|
3798
|
+
marpArgs.push("--watch");
|
|
3799
|
+
}
|
|
3800
|
+
const marpProcess = spawnMarp(marpArgs, {
|
|
3801
|
+
projectDir: dirname5(inputPath),
|
|
3793
3802
|
stdio: "inherit"
|
|
3794
3803
|
});
|
|
3804
|
+
console.log(`
|
|
3805
|
+
Preview available at ${chalk3.cyan(`http://localhost:${port}/slides.md`)}`);
|
|
3806
|
+
console.log("Open this URL in your browser to view the slides.");
|
|
3795
3807
|
let watcher = null;
|
|
3796
3808
|
let debounceTimer = null;
|
|
3797
3809
|
if (options.watch) {
|
|
@@ -3829,7 +3841,7 @@ Watching ${chalk3.cyan(inputPath)} for changes...`);
|
|
|
3829
3841
|
watcher?.close();
|
|
3830
3842
|
marpProcess.kill();
|
|
3831
3843
|
try {
|
|
3832
|
-
await
|
|
3844
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
3833
3845
|
} catch {
|
|
3834
3846
|
}
|
|
3835
3847
|
};
|
|
@@ -3845,13 +3857,13 @@ Watching ${chalk3.cyan(inputPath)} for changes...`);
|
|
|
3845
3857
|
};
|
|
3846
3858
|
process.on("SIGINT", signalHandler);
|
|
3847
3859
|
process.on("SIGTERM", signalHandler);
|
|
3848
|
-
await new Promise((
|
|
3860
|
+
await new Promise((resolve8) => {
|
|
3849
3861
|
marpProcess.on("exit", () => {
|
|
3850
3862
|
cleanup();
|
|
3851
|
-
|
|
3863
|
+
resolve8();
|
|
3852
3864
|
});
|
|
3853
3865
|
if (options.signal) {
|
|
3854
|
-
options.signal.addEventListener("abort", () =>
|
|
3866
|
+
options.signal.addEventListener("abort", () => resolve8());
|
|
3855
3867
|
}
|
|
3856
3868
|
});
|
|
3857
3869
|
return {
|
|
@@ -4396,8 +4408,8 @@ Showing ${templatePreviews.length} template preview(s)`);
|
|
|
4396
4408
|
};
|
|
4397
4409
|
process.on("SIGINT", signalHandler);
|
|
4398
4410
|
process.on("SIGTERM", signalHandler);
|
|
4399
|
-
await new Promise((
|
|
4400
|
-
server.on("close", () =>
|
|
4411
|
+
await new Promise((resolve8) => {
|
|
4412
|
+
server.on("close", () => resolve8());
|
|
4401
4413
|
});
|
|
4402
4414
|
}
|
|
4403
4415
|
function createTemplatesCommand() {
|
|
@@ -5195,7 +5207,7 @@ import { mkdir as mkdir7, writeFile as writeFile7, access as access10, readdir a
|
|
|
5195
5207
|
import { existsSync as existsSync2 } from "fs";
|
|
5196
5208
|
import { execSync, spawnSync } from "child_process";
|
|
5197
5209
|
import { createInterface } from "readline";
|
|
5198
|
-
import { basename as basename7, dirname as
|
|
5210
|
+
import { basename as basename7, dirname as dirname7, join as join16, resolve as resolve6, sep } from "path";
|
|
5199
5211
|
import { fileURLToPath } from "url";
|
|
5200
5212
|
import chalk6 from "chalk";
|
|
5201
5213
|
import ora3 from "ora";
|
|
@@ -6126,6 +6138,49 @@ The \`validate --format llm\` command provides:
|
|
|
6126
6138
|
- Fix examples from template definitions
|
|
6127
6139
|
- Contextual hints for unknown templates/icons
|
|
6128
6140
|
|
|
6141
|
+
## Visual Feedback Loop
|
|
6142
|
+
|
|
6143
|
+
After creating or modifying slides, use this workflow to review and iterate:
|
|
6144
|
+
|
|
6145
|
+
### Step 1: Take Screenshot
|
|
6146
|
+
\`\`\`bash
|
|
6147
|
+
# AI-optimized format (recommended)
|
|
6148
|
+
slide-gen screenshot presentation.yaml --format ai
|
|
6149
|
+
|
|
6150
|
+
# Or contact sheet for overview
|
|
6151
|
+
slide-gen screenshot presentation.yaml --contact-sheet
|
|
6152
|
+
\`\`\`
|
|
6153
|
+
|
|
6154
|
+
### Step 2: Review Images
|
|
6155
|
+
Use the Read tool to view the generated screenshots:
|
|
6156
|
+
\`\`\`
|
|
6157
|
+
Read ./screenshots/presentation.001.jpeg
|
|
6158
|
+
\`\`\`
|
|
6159
|
+
|
|
6160
|
+
### Step 3: Identify Issues
|
|
6161
|
+
Look for:
|
|
6162
|
+
- Layout problems (text overflow, alignment)
|
|
6163
|
+
- Visual balance (too much/little content)
|
|
6164
|
+
- Icon and color appropriateness
|
|
6165
|
+
- Readability of text and diagrams
|
|
6166
|
+
|
|
6167
|
+
### Step 4: Make Adjustments
|
|
6168
|
+
Edit presentation.yaml to fix identified issues.
|
|
6169
|
+
|
|
6170
|
+
### Step 5: Repeat
|
|
6171
|
+
Take new screenshots and verify improvements.
|
|
6172
|
+
|
|
6173
|
+
### Example Iteration Cycle
|
|
6174
|
+
|
|
6175
|
+
1. Create initial slides
|
|
6176
|
+
2. \`slide-gen screenshot presentation.yaml --format ai --slide 3\`
|
|
6177
|
+
3. \`Read ./screenshots/presentation.003.jpeg\`
|
|
6178
|
+
4. Notice: "Title text is too long, wrapping awkwardly"
|
|
6179
|
+
5. Edit presentation.yaml to shorten title
|
|
6180
|
+
6. \`slide-gen screenshot presentation.yaml --format ai --slide 3\`
|
|
6181
|
+
7. \`Read ./screenshots/presentation.003.jpeg\`
|
|
6182
|
+
8. Verify fix, move to next slide
|
|
6183
|
+
|
|
6129
6184
|
## Reference Management
|
|
6130
6185
|
|
|
6131
6186
|
For academic presentations, manage citations and references:
|
|
@@ -6212,7 +6267,24 @@ Read \`.skills/slide-assistant/SKILL.md\` for detailed instructions.
|
|
|
6212
6267
|
- \`/slide-validate\` - Validate slide source file
|
|
6213
6268
|
- \`/slide-preview\` - Preview slides in browser
|
|
6214
6269
|
- \`/slide-screenshot\` - Take screenshots for review
|
|
6270
|
+
- \`/slide-review\` - Visual review and iteration workflow
|
|
6215
6271
|
- \`/slide-theme\` - Adjust theme and styling
|
|
6272
|
+
|
|
6273
|
+
## Important: Visual Review
|
|
6274
|
+
|
|
6275
|
+
**After creating or editing slides, always run visual review:**
|
|
6276
|
+
|
|
6277
|
+
\`\`\`bash
|
|
6278
|
+
/slide-review
|
|
6279
|
+
\`\`\`
|
|
6280
|
+
|
|
6281
|
+
Or manually:
|
|
6282
|
+
1. \`slide-gen screenshot presentation.yaml --format ai\`
|
|
6283
|
+
2. \`Read ./screenshots/presentation.001.jpeg\`
|
|
6284
|
+
3. Check layout, text overflow, visual balance
|
|
6285
|
+
4. Edit and repeat until satisfied
|
|
6286
|
+
|
|
6287
|
+
This ensures slides look correct before delivery.
|
|
6216
6288
|
`;
|
|
6217
6289
|
}
|
|
6218
6290
|
|
|
@@ -6528,6 +6600,69 @@ After user provides images:
|
|
|
6528
6600
|
|
|
6529
6601
|
### Phase 4: Iteration
|
|
6530
6602
|
Handle adjustments (cropping, replacement) as needed.
|
|
6603
|
+
|
|
6604
|
+
## Visual Review Flow
|
|
6605
|
+
|
|
6606
|
+
### When to Use Visual Review
|
|
6607
|
+
|
|
6608
|
+
- After initial slide creation
|
|
6609
|
+
- When adjusting layouts or styling
|
|
6610
|
+
- Before final delivery
|
|
6611
|
+
- When user reports visual issues
|
|
6612
|
+
|
|
6613
|
+
### Quick Review Workflow
|
|
6614
|
+
|
|
6615
|
+
1. **Generate screenshots**:
|
|
6616
|
+
\`\`\`bash
|
|
6617
|
+
slide-gen screenshot presentation.yaml --format ai
|
|
6618
|
+
\`\`\`
|
|
6619
|
+
|
|
6620
|
+
2. **Review each slide**:
|
|
6621
|
+
\`\`\`
|
|
6622
|
+
Read ./screenshots/presentation.001.jpeg
|
|
6623
|
+
Read ./screenshots/presentation.002.jpeg
|
|
6624
|
+
...
|
|
6625
|
+
\`\`\`
|
|
6626
|
+
|
|
6627
|
+
3. **Document issues** found in each slide
|
|
6628
|
+
|
|
6629
|
+
4. **Make batch edits** to presentation.yaml
|
|
6630
|
+
|
|
6631
|
+
5. **Regenerate and verify**:
|
|
6632
|
+
\`\`\`bash
|
|
6633
|
+
slide-gen screenshot presentation.yaml --format ai
|
|
6634
|
+
\`\`\`
|
|
6635
|
+
|
|
6636
|
+
### Contact Sheet Review
|
|
6637
|
+
|
|
6638
|
+
For quick overview of all slides:
|
|
6639
|
+
|
|
6640
|
+
1. **Generate contact sheet**:
|
|
6641
|
+
\`\`\`bash
|
|
6642
|
+
slide-gen screenshot presentation.yaml --contact-sheet
|
|
6643
|
+
\`\`\`
|
|
6644
|
+
|
|
6645
|
+
2. **Review overview**:
|
|
6646
|
+
\`\`\`
|
|
6647
|
+
Read ./screenshots/presentation-contact.png
|
|
6648
|
+
\`\`\`
|
|
6649
|
+
|
|
6650
|
+
3. **Identify slides needing attention**
|
|
6651
|
+
|
|
6652
|
+
4. **Deep dive on specific slides**:
|
|
6653
|
+
\`\`\`bash
|
|
6654
|
+
slide-gen screenshot presentation.yaml --format ai --slide 5
|
|
6655
|
+
\`\`\`
|
|
6656
|
+
|
|
6657
|
+
### Common Visual Issues to Check
|
|
6658
|
+
|
|
6659
|
+
| Issue | What to Look For | Fix |
|
|
6660
|
+
|-------|------------------|-----|
|
|
6661
|
+
| Text overflow | Text cut off or wrapped | Shorten text, use bullet-list |
|
|
6662
|
+
| Empty space | Large blank areas | Add content or use different template |
|
|
6663
|
+
| Cluttered | Too much content | Split into multiple slides |
|
|
6664
|
+
| Poor contrast | Hard to read text | Adjust colors in theme |
|
|
6665
|
+
| Icon mismatch | Icon doesn't fit context | Search for better icon |
|
|
6531
6666
|
`;
|
|
6532
6667
|
}
|
|
6533
6668
|
|
|
@@ -6804,18 +6939,128 @@ slide-gen screenshot $ARGUMENTS
|
|
|
6804
6939
|
|
|
6805
6940
|
If no argument provided:
|
|
6806
6941
|
\`\`\`bash
|
|
6807
|
-
slide-gen screenshot presentation.yaml
|
|
6942
|
+
slide-gen screenshot presentation.yaml --format ai
|
|
6808
6943
|
\`\`\`
|
|
6809
6944
|
|
|
6810
6945
|
## Options
|
|
6811
6946
|
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6947
|
+
| Option | Description | Default |
|
|
6948
|
+
|--------|-------------|---------|
|
|
6949
|
+
| \`--format <fmt>\` | Output format (png/jpeg/ai) | png |
|
|
6950
|
+
| \`--slide <number>\` | Screenshot specific slide only | All |
|
|
6951
|
+
| \`--contact-sheet\` | Generate overview of all slides | false |
|
|
6952
|
+
| \`--columns <num>\` | Contact sheet columns | 2 |
|
|
6953
|
+
| \`--width <pixels>\` | Image width | 1280 (ai: 640) |
|
|
6954
|
+
| \`--quality <num>\` | JPEG quality (1-100) | 80 |
|
|
6955
|
+
| \`--output <dir>\` | Output directory | ./screenshots |
|
|
6956
|
+
|
|
6957
|
+
## AI Optimization Mode
|
|
6958
|
+
|
|
6959
|
+
Use \`--format ai\` for token-efficient screenshots:
|
|
6960
|
+
- 640px width (75% token reduction)
|
|
6961
|
+
- JPEG format
|
|
6962
|
+
- Shows estimated token consumption
|
|
6963
|
+
|
|
6964
|
+
\`\`\`bash
|
|
6965
|
+
# AI-optimized screenshots
|
|
6966
|
+
slide-gen screenshot presentation.yaml --format ai
|
|
6967
|
+
|
|
6968
|
+
# Contact sheet for overview
|
|
6969
|
+
slide-gen screenshot presentation.yaml --contact-sheet
|
|
6970
|
+
|
|
6971
|
+
# AI-optimized contact sheet
|
|
6972
|
+
slide-gen screenshot presentation.yaml --format ai --contact-sheet
|
|
6973
|
+
\`\`\`
|
|
6974
|
+
|
|
6975
|
+
## Token Efficiency
|
|
6815
6976
|
|
|
6816
|
-
|
|
6977
|
+
| Format | Width | Est. Tokens/slide |
|
|
6978
|
+
|--------|-------|-------------------|
|
|
6979
|
+
| png/jpeg | 1280 | ~1,229 |
|
|
6980
|
+
| ai | 640 | ~308 (~75% reduction) |
|
|
6817
6981
|
|
|
6818
|
-
|
|
6982
|
+
## Visual Feedback Workflow
|
|
6983
|
+
|
|
6984
|
+
1. Take screenshot: \`slide-gen screenshot presentation.yaml --format ai\`
|
|
6985
|
+
2. Review image: \`Read ./screenshots/presentation.001.jpeg\`
|
|
6986
|
+
3. Identify issues (layout, text, icons)
|
|
6987
|
+
4. Edit presentation.yaml
|
|
6988
|
+
5. Repeat until satisfied
|
|
6989
|
+
`;
|
|
6990
|
+
}
|
|
6991
|
+
|
|
6992
|
+
// src/cli/templates/ai/commands/slide-review.ts
|
|
6993
|
+
function generateSlideReviewCommand() {
|
|
6994
|
+
return `Review slides visually and iterate on improvements.
|
|
6995
|
+
|
|
6996
|
+
## Workflow
|
|
6997
|
+
|
|
6998
|
+
1. **Take AI-optimized screenshots**:
|
|
6999
|
+
\`\`\`bash
|
|
7000
|
+
slide-gen screenshot $ARGUMENTS --format ai
|
|
7001
|
+
\`\`\`
|
|
7002
|
+
If no argument: \`slide-gen screenshot presentation.yaml --format ai\`
|
|
7003
|
+
|
|
7004
|
+
2. **Review each slide image**:
|
|
7005
|
+
\`\`\`
|
|
7006
|
+
Read ./screenshots/presentation.001.jpeg
|
|
7007
|
+
Read ./screenshots/presentation.002.jpeg
|
|
7008
|
+
...
|
|
7009
|
+
\`\`\`
|
|
7010
|
+
|
|
7011
|
+
3. **Check for issues**:
|
|
7012
|
+
- Text overflow or awkward wrapping
|
|
7013
|
+
- Poor visual balance (too empty / too cluttered)
|
|
7014
|
+
- Icon appropriateness
|
|
7015
|
+
- Color contrast and readability
|
|
7016
|
+
- Diagram clarity
|
|
7017
|
+
|
|
7018
|
+
4. **Report findings** to user with specific slide numbers
|
|
7019
|
+
|
|
7020
|
+
5. **If issues found**, edit presentation.yaml and repeat from step 1
|
|
7021
|
+
|
|
7022
|
+
## Quick Overview
|
|
7023
|
+
|
|
7024
|
+
For a quick overview of all slides:
|
|
7025
|
+
\`\`\`bash
|
|
7026
|
+
slide-gen screenshot presentation.yaml --contact-sheet
|
|
7027
|
+
Read ./screenshots/presentation-contact.png
|
|
7028
|
+
\`\`\`
|
|
7029
|
+
|
|
7030
|
+
## Token Efficiency
|
|
7031
|
+
|
|
7032
|
+
Always use \`--format ai\` for ~75% token reduction:
|
|
7033
|
+
- Default: ~1,229 tokens/slide
|
|
7034
|
+
- AI mode: ~308 tokens/slide
|
|
7035
|
+
|
|
7036
|
+
## Common Issues Checklist
|
|
7037
|
+
|
|
7038
|
+
| Issue | What to Look For | Fix |
|
|
7039
|
+
|-------|------------------|-----|
|
|
7040
|
+
| Text overflow | Text cut off or wrapped | Shorten text, use bullet-list |
|
|
7041
|
+
| Empty space | Large blank areas | Add content or use different template |
|
|
7042
|
+
| Cluttered | Too much content | Split into multiple slides |
|
|
7043
|
+
| Poor contrast | Hard to read text | Adjust colors in theme |
|
|
7044
|
+
| Icon mismatch | Icon doesn't fit context | Search for better icon |
|
|
7045
|
+
|
|
7046
|
+
## Example Session
|
|
7047
|
+
|
|
7048
|
+
\`\`\`bash
|
|
7049
|
+
# Initial review
|
|
7050
|
+
slide-gen screenshot presentation.yaml --format ai
|
|
7051
|
+
|
|
7052
|
+
# Check slide 3
|
|
7053
|
+
Read ./screenshots/presentation.003.jpeg
|
|
7054
|
+
# Notice: "Title text is too long"
|
|
7055
|
+
|
|
7056
|
+
# Edit presentation.yaml to shorten title
|
|
7057
|
+
|
|
7058
|
+
# Re-take screenshot for slide 3
|
|
7059
|
+
slide-gen screenshot presentation.yaml --format ai --slide 3
|
|
7060
|
+
|
|
7061
|
+
# Verify fix
|
|
7062
|
+
Read ./screenshots/presentation.003.jpeg
|
|
7063
|
+
\`\`\`
|
|
6819
7064
|
`;
|
|
6820
7065
|
}
|
|
6821
7066
|
|
|
@@ -6910,7 +7155,7 @@ slide-gen validate presentation.yaml
|
|
|
6910
7155
|
|
|
6911
7156
|
// src/cli/commands/init.ts
|
|
6912
7157
|
function getPackageRoot() {
|
|
6913
|
-
const __dirname =
|
|
7158
|
+
const __dirname = dirname7(fileURLToPath(import.meta.url));
|
|
6914
7159
|
if (__dirname.includes(`${sep}src${sep}`) || __dirname.includes("/src/")) {
|
|
6915
7160
|
return join16(__dirname, "..", "..", "..");
|
|
6916
7161
|
}
|
|
@@ -6923,7 +7168,7 @@ function createInitCommand() {
|
|
|
6923
7168
|
}
|
|
6924
7169
|
async function executeInit(directory, options) {
|
|
6925
7170
|
const spinner = ora3();
|
|
6926
|
-
const targetDir =
|
|
7171
|
+
const targetDir = resolve6(directory);
|
|
6927
7172
|
const includeExamples = options.examples !== false;
|
|
6928
7173
|
const includeAiConfig = options.aiConfig !== false;
|
|
6929
7174
|
const includeSources = options.sources !== false;
|
|
@@ -6972,11 +7217,11 @@ async function executeInit(directory, options) {
|
|
|
6972
7217
|
await sourcesManager.init({
|
|
6973
7218
|
name: "Untitled Project",
|
|
6974
7219
|
setup_pattern: options.fromDirectory ? "A" : void 0,
|
|
6975
|
-
original_source: options.fromDirectory ?
|
|
7220
|
+
original_source: options.fromDirectory ? resolve6(options.fromDirectory) : void 0
|
|
6976
7221
|
});
|
|
6977
7222
|
if (options.fromDirectory) {
|
|
6978
7223
|
const importer = new SourceImporter(targetDir, sourcesManager);
|
|
6979
|
-
const result = await importer.importDirectory(
|
|
7224
|
+
const result = await importer.importDirectory(resolve6(options.fromDirectory), {
|
|
6980
7225
|
recursive: true
|
|
6981
7226
|
});
|
|
6982
7227
|
sourcesImported = result.imported;
|
|
@@ -6997,6 +7242,7 @@ async function executeInit(directory, options) {
|
|
|
6997
7242
|
}
|
|
6998
7243
|
if (includeAiConfig) {
|
|
6999
7244
|
console.log(` ${chalk6.cyan(".skills/")} - AgentSkills configuration`);
|
|
7245
|
+
console.log(` ${chalk6.cyan(".claude/skills/")} - Claude Code skills`);
|
|
7000
7246
|
console.log(` ${chalk6.cyan("CLAUDE.md")} - Claude Code configuration`);
|
|
7001
7247
|
console.log(` ${chalk6.cyan("AGENTS.md")} - OpenCode configuration`);
|
|
7002
7248
|
console.log(` ${chalk6.cyan(".cursorrules")} - Cursor configuration`);
|
|
@@ -7081,16 +7327,16 @@ async function promptMarpInstallChoice() {
|
|
|
7081
7327
|
console.log(` ${chalk6.cyan("2)")} Local install ${chalk6.dim("(creates package.json)")}`);
|
|
7082
7328
|
console.log(` ${chalk6.cyan("3)")} Skip ${chalk6.dim("(I'll install it later)")}`);
|
|
7083
7329
|
console.log("");
|
|
7084
|
-
return new Promise((
|
|
7330
|
+
return new Promise((resolve8) => {
|
|
7085
7331
|
rl.question("Choice [1]: ", (answer) => {
|
|
7086
7332
|
rl.close();
|
|
7087
7333
|
const normalized = answer.trim();
|
|
7088
7334
|
if (normalized === "" || normalized === "1") {
|
|
7089
|
-
|
|
7335
|
+
resolve8("global");
|
|
7090
7336
|
} else if (normalized === "2") {
|
|
7091
|
-
|
|
7337
|
+
resolve8("local");
|
|
7092
7338
|
} else {
|
|
7093
|
-
|
|
7339
|
+
resolve8("skip");
|
|
7094
7340
|
}
|
|
7095
7341
|
});
|
|
7096
7342
|
});
|
|
@@ -7204,30 +7450,35 @@ async function showMarpCliInfo(targetDir) {
|
|
|
7204
7450
|
async function generateAiConfig(targetDir) {
|
|
7205
7451
|
await mkdir7(join16(targetDir, ".skills", "slide-assistant", "references"), { recursive: true });
|
|
7206
7452
|
await mkdir7(join16(targetDir, ".skills", "slide-assistant", "scripts"), { recursive: true });
|
|
7453
|
+
await mkdir7(join16(targetDir, ".claude", "skills", "slide-assistant", "references"), { recursive: true });
|
|
7207
7454
|
await mkdir7(join16(targetDir, ".claude", "commands"), { recursive: true });
|
|
7208
7455
|
await mkdir7(join16(targetDir, ".opencode", "agent"), { recursive: true });
|
|
7209
|
-
|
|
7210
|
-
join16(targetDir, ".skills", "slide-assistant"
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
join16(
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7456
|
+
const skillDirs = [
|
|
7457
|
+
join16(targetDir, ".skills", "slide-assistant"),
|
|
7458
|
+
join16(targetDir, ".claude", "skills", "slide-assistant")
|
|
7459
|
+
];
|
|
7460
|
+
for (const skillDir of skillDirs) {
|
|
7461
|
+
await writeFileIfNotExists(join16(skillDir, "SKILL.md"), generateSkillMd());
|
|
7462
|
+
await writeFileIfNotExists(
|
|
7463
|
+
join16(skillDir, "references", "templates.md"),
|
|
7464
|
+
generateTemplatesRef()
|
|
7465
|
+
);
|
|
7466
|
+
await writeFileIfNotExists(
|
|
7467
|
+
join16(skillDir, "references", "workflows.md"),
|
|
7468
|
+
generateWorkflowsRef()
|
|
7469
|
+
);
|
|
7470
|
+
await writeFileIfNotExists(
|
|
7471
|
+
join16(skillDir, "references", "skill.md"),
|
|
7472
|
+
generateReferenceSkillMd()
|
|
7473
|
+
);
|
|
7474
|
+
}
|
|
7225
7475
|
await writeFileIfNotExists(join16(targetDir, "CLAUDE.md"), generateClaudeMd());
|
|
7226
7476
|
const commandGenerators = {
|
|
7227
7477
|
"slide-create": generateSlideCreateCommand,
|
|
7228
7478
|
"slide-validate": generateSlideValidateCommand,
|
|
7229
7479
|
"slide-preview": generateSlidePreviewCommand,
|
|
7230
7480
|
"slide-screenshot": generateSlideScreenshotCommand,
|
|
7481
|
+
"slide-review": generateSlideReviewCommand,
|
|
7231
7482
|
"slide-theme": generateSlideThemeCommand,
|
|
7232
7483
|
"slide-references": generateSlideReferencesCommand
|
|
7233
7484
|
};
|
|
@@ -7340,7 +7591,7 @@ slides:
|
|
|
7340
7591
|
// src/cli/commands/watch.ts
|
|
7341
7592
|
import { Command as Command7 } from "commander";
|
|
7342
7593
|
import { access as access11 } from "fs/promises";
|
|
7343
|
-
import { basename as basename8, dirname as
|
|
7594
|
+
import { basename as basename8, dirname as dirname8, join as join17 } from "path";
|
|
7344
7595
|
import chalk7 from "chalk";
|
|
7345
7596
|
import { watch as chokidarWatch2 } from "chokidar";
|
|
7346
7597
|
var WatchState = class {
|
|
@@ -7373,7 +7624,7 @@ var WatchState = class {
|
|
|
7373
7624
|
}
|
|
7374
7625
|
};
|
|
7375
7626
|
function getDefaultOutputPath2(inputPath) {
|
|
7376
|
-
const dir =
|
|
7627
|
+
const dir = dirname8(inputPath);
|
|
7377
7628
|
const base = basename8(inputPath, ".yaml");
|
|
7378
7629
|
return join17(dir, `${base}.md`);
|
|
7379
7630
|
}
|
|
@@ -7435,7 +7686,7 @@ async function executeWatch(inputPath, options) {
|
|
|
7435
7686
|
const configLoader = new ConfigLoader();
|
|
7436
7687
|
let configPath = options.config;
|
|
7437
7688
|
if (!configPath) {
|
|
7438
|
-
configPath = await configLoader.findConfig(
|
|
7689
|
+
configPath = await configLoader.findConfig(dirname8(inputPath));
|
|
7439
7690
|
}
|
|
7440
7691
|
const config = await configLoader.load(configPath);
|
|
7441
7692
|
const pipeline = new Pipeline(config);
|
|
@@ -7504,9 +7755,9 @@ async function executeWatch(inputPath, options) {
|
|
|
7504
7755
|
};
|
|
7505
7756
|
process.on("SIGINT", signalHandler);
|
|
7506
7757
|
process.on("SIGTERM", signalHandler);
|
|
7507
|
-
await new Promise((
|
|
7758
|
+
await new Promise((resolve8) => {
|
|
7508
7759
|
if (options.signal) {
|
|
7509
|
-
options.signal.addEventListener("abort", () =>
|
|
7760
|
+
options.signal.addEventListener("abort", () => resolve8());
|
|
7510
7761
|
}
|
|
7511
7762
|
});
|
|
7512
7763
|
return {
|
|
@@ -7519,7 +7770,7 @@ async function executeWatch(inputPath, options) {
|
|
|
7519
7770
|
// src/cli/commands/images.ts
|
|
7520
7771
|
import { Command as Command8 } from "commander";
|
|
7521
7772
|
import { readFile as readFile15, stat as stat2, mkdir as mkdir8 } from "fs/promises";
|
|
7522
|
-
import { dirname as
|
|
7773
|
+
import { dirname as dirname9, basename as basename9, join as join18 } from "path";
|
|
7523
7774
|
import chalk8 from "chalk";
|
|
7524
7775
|
import { stringify as stringifyYaml3 } from "yaml";
|
|
7525
7776
|
function createImagesCommand() {
|
|
@@ -7542,7 +7793,7 @@ function createImagesRequestCommand() {
|
|
|
7542
7793
|
async function executeImagesStatus(inputPath) {
|
|
7543
7794
|
try {
|
|
7544
7795
|
const slides = await loadPresentation(inputPath);
|
|
7545
|
-
const baseDir =
|
|
7796
|
+
const baseDir = dirname9(inputPath);
|
|
7546
7797
|
const validator = new ImageValidator(baseDir);
|
|
7547
7798
|
const imageRefs = validator.extractImageReferences(slides);
|
|
7548
7799
|
if (imageRefs.length === 0) {
|
|
@@ -7642,7 +7893,7 @@ async function outputImageStatus(stats, imageRefs, _validator, baseDir) {
|
|
|
7642
7893
|
async function executeImagesRequest(inputPath, options) {
|
|
7643
7894
|
try {
|
|
7644
7895
|
const slides = await loadPresentation(inputPath);
|
|
7645
|
-
const baseDir =
|
|
7896
|
+
const baseDir = dirname9(inputPath);
|
|
7646
7897
|
const validator = new ImageValidator(baseDir);
|
|
7647
7898
|
const missingImages = await validator.getMissingImages(slides);
|
|
7648
7899
|
if (missingImages.length === 0) {
|
|
@@ -7842,7 +8093,7 @@ async function processSingleFile(filePath, options) {
|
|
|
7842
8093
|
return;
|
|
7843
8094
|
}
|
|
7844
8095
|
const processor = new ImageProcessor();
|
|
7845
|
-
const dir =
|
|
8096
|
+
const dir = dirname9(filePath);
|
|
7846
8097
|
const filename = basename9(filePath);
|
|
7847
8098
|
const outputDir = join18(dir, options.output);
|
|
7848
8099
|
await mkdir8(outputDir, { recursive: true });
|
|
@@ -7940,10 +8191,11 @@ function parseBlurSpec(spec) {
|
|
|
7940
8191
|
|
|
7941
8192
|
// src/cli/commands/screenshot.ts
|
|
7942
8193
|
import { Command as Command9 } from "commander";
|
|
7943
|
-
import { access as access12, mkdir as mkdir9, readdir as readdir7, unlink as
|
|
7944
|
-
import { basename as basename10, dirname as
|
|
8194
|
+
import { access as access12, mkdir as mkdir9, readdir as readdir7, unlink as unlink2 } from "fs/promises";
|
|
8195
|
+
import { basename as basename10, dirname as dirname10, join as join19 } from "path";
|
|
7945
8196
|
import chalk9 from "chalk";
|
|
7946
8197
|
import ora4 from "ora";
|
|
8198
|
+
import sharp2 from "sharp";
|
|
7947
8199
|
async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
7948
8200
|
const slideStr = slideNumber.toString().padStart(3, "0");
|
|
7949
8201
|
const targetFileName = `${baseName}.${slideStr}.${format}`;
|
|
@@ -7962,7 +8214,7 @@ async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
|
7962
8214
|
);
|
|
7963
8215
|
for (const file of slideFiles) {
|
|
7964
8216
|
if (file !== targetFileName) {
|
|
7965
|
-
await
|
|
8217
|
+
await unlink2(join19(outputDir, file));
|
|
7966
8218
|
}
|
|
7967
8219
|
}
|
|
7968
8220
|
return {
|
|
@@ -7973,19 +8225,120 @@ async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
|
7973
8225
|
function checkMarpCliAvailable2(projectDir) {
|
|
7974
8226
|
return isMarpAvailable(projectDir);
|
|
7975
8227
|
}
|
|
8228
|
+
function estimateTokens(width, height) {
|
|
8229
|
+
return Math.ceil(width * height / 750);
|
|
8230
|
+
}
|
|
8231
|
+
function calculateGridDimensions(slideCount, columns) {
|
|
8232
|
+
const rows = Math.ceil(slideCount / columns);
|
|
8233
|
+
return { rows, columns };
|
|
8234
|
+
}
|
|
8235
|
+
function createNumberOverlay(number, width) {
|
|
8236
|
+
const svg = `
|
|
8237
|
+
<svg width="${width}" height="30">
|
|
8238
|
+
<rect width="${width}" height="30" fill="rgba(0,0,0,0.6)"/>
|
|
8239
|
+
<text x="10" y="22" font-family="system-ui, -apple-system, 'Segoe UI', sans-serif" font-size="16" fill="white">
|
|
8240
|
+
Slide ${number}
|
|
8241
|
+
</text>
|
|
8242
|
+
</svg>
|
|
8243
|
+
`;
|
|
8244
|
+
return Buffer.from(svg);
|
|
8245
|
+
}
|
|
8246
|
+
async function generateContactSheet(slides, options) {
|
|
8247
|
+
const {
|
|
8248
|
+
columns,
|
|
8249
|
+
padding = 10,
|
|
8250
|
+
showNumbers = true,
|
|
8251
|
+
slideWidth = 640,
|
|
8252
|
+
slideHeight = 360
|
|
8253
|
+
} = options;
|
|
8254
|
+
if (slides.length === 0) {
|
|
8255
|
+
return { success: false, error: "No slides provided" };
|
|
8256
|
+
}
|
|
8257
|
+
try {
|
|
8258
|
+
const { rows } = calculateGridDimensions(slides.length, columns);
|
|
8259
|
+
const canvasWidth = columns * slideWidth + (columns + 1) * padding;
|
|
8260
|
+
const canvasHeight = rows * slideHeight + (rows + 1) * padding;
|
|
8261
|
+
const composites = [];
|
|
8262
|
+
for (let i = 0; i < slides.length; i++) {
|
|
8263
|
+
const slide = slides[i];
|
|
8264
|
+
const col = i % columns;
|
|
8265
|
+
const row = Math.floor(i / columns);
|
|
8266
|
+
const x = padding + col * (slideWidth + padding);
|
|
8267
|
+
const y = padding + row * (slideHeight + padding);
|
|
8268
|
+
const resized = await sharp2(slide.path).resize(slideWidth, slideHeight, { fit: "contain", background: { r: 255, g: 255, b: 255 } }).toBuffer();
|
|
8269
|
+
composites.push({
|
|
8270
|
+
input: resized,
|
|
8271
|
+
left: x,
|
|
8272
|
+
top: y
|
|
8273
|
+
});
|
|
8274
|
+
if (showNumbers) {
|
|
8275
|
+
const numberOverlay = createNumberOverlay(slide.index, slideWidth);
|
|
8276
|
+
composites.push({
|
|
8277
|
+
input: numberOverlay,
|
|
8278
|
+
left: x,
|
|
8279
|
+
top: y + slideHeight - 30
|
|
8280
|
+
});
|
|
8281
|
+
}
|
|
8282
|
+
}
|
|
8283
|
+
await sharp2({
|
|
8284
|
+
create: {
|
|
8285
|
+
width: canvasWidth,
|
|
8286
|
+
height: canvasHeight,
|
|
8287
|
+
channels: 4,
|
|
8288
|
+
background: { r: 245, g: 245, b: 245, alpha: 1 }
|
|
8289
|
+
}
|
|
8290
|
+
}).composite(composites).png().toFile(options.outputPath);
|
|
8291
|
+
return {
|
|
8292
|
+
success: true,
|
|
8293
|
+
outputPath: options.outputPath
|
|
8294
|
+
};
|
|
8295
|
+
} catch (error) {
|
|
8296
|
+
return {
|
|
8297
|
+
success: false,
|
|
8298
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
8299
|
+
};
|
|
8300
|
+
}
|
|
8301
|
+
}
|
|
8302
|
+
function formatAiOutput(options) {
|
|
8303
|
+
const { files, width, height, outputDir } = options;
|
|
8304
|
+
const tokensPerImage = estimateTokens(width, height);
|
|
8305
|
+
const totalTokens = tokensPerImage * files.length;
|
|
8306
|
+
const imageLabel = files.length === 1 ? "image" : "images";
|
|
8307
|
+
const lines = [
|
|
8308
|
+
"Screenshots saved (AI-optimized):",
|
|
8309
|
+
""
|
|
8310
|
+
];
|
|
8311
|
+
for (const file of files) {
|
|
8312
|
+
lines.push(` ${join19(outputDir, file)}`);
|
|
8313
|
+
}
|
|
8314
|
+
lines.push("");
|
|
8315
|
+
lines.push(`Estimated tokens: ~${totalTokens} (${files.length} ${imageLabel})`);
|
|
8316
|
+
if (files.length > 0) {
|
|
8317
|
+
lines.push("");
|
|
8318
|
+
lines.push("To review in Claude Code:");
|
|
8319
|
+
lines.push(` Read ${join19(outputDir, files[0])}`);
|
|
8320
|
+
}
|
|
8321
|
+
return lines.join("\n");
|
|
8322
|
+
}
|
|
7976
8323
|
function buildMarpCommandArgs(markdownPath, outputDir, options) {
|
|
7977
|
-
const
|
|
7978
|
-
const
|
|
7979
|
-
|
|
7980
|
-
|
|
8324
|
+
const isAiFormat = options.format === "ai";
|
|
8325
|
+
const imageFormat = isAiFormat ? "jpeg" : options.format || "png";
|
|
8326
|
+
const width = isAiFormat ? 640 : options.width || 1280;
|
|
8327
|
+
const args = ["--images", imageFormat];
|
|
8328
|
+
if (width !== 1280) {
|
|
8329
|
+
const scale = width / 1280;
|
|
7981
8330
|
args.push("--image-scale", String(scale));
|
|
7982
8331
|
}
|
|
8332
|
+
if (imageFormat === "jpeg") {
|
|
8333
|
+
const quality = options.quality || 80;
|
|
8334
|
+
args.push("--jpeg-quality", String(quality));
|
|
8335
|
+
}
|
|
7983
8336
|
args.push("-o", outputDir);
|
|
7984
8337
|
args.push(markdownPath);
|
|
7985
8338
|
return args;
|
|
7986
8339
|
}
|
|
7987
8340
|
function createScreenshotCommand() {
|
|
7988
|
-
return new Command9("screenshot").description("Take screenshots of slides (requires Marp CLI)").argument("<input>", "Source YAML file").option("-o, --output <path>", "Output directory", "./screenshots").option("-s, --slide <number>", "Specific slide number (1-based)", parseInt).option("-w, --width <pixels>", "Image width", parseInt, 1280).option("-f, --format <fmt>", "
|
|
8341
|
+
return new Command9("screenshot").description("Take screenshots of slides (requires Marp CLI)").argument("<input>", "Source YAML file").option("-o, --output <path>", "Output directory", "./screenshots").option("-s, --slide <number>", "Specific slide number (1-based)", parseInt).option("-w, --width <pixels>", "Image width", parseInt, 1280).option("-f, --format <fmt>", "Output format (png/jpeg/ai)", "png").option("-q, --quality <num>", "JPEG quality (1-100)", parseInt, 80).option("--contact-sheet", "Generate contact sheet").option("--columns <num>", "Contact sheet columns", parseInt, 2).option("-c, --config <path>", "Config file path").option("-v, --verbose", "Verbose output").action(async (input, options) => {
|
|
7989
8342
|
await executeScreenshot(input, options);
|
|
7990
8343
|
});
|
|
7991
8344
|
}
|
|
@@ -8010,7 +8363,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
8010
8363
|
return { success: false, errors };
|
|
8011
8364
|
}
|
|
8012
8365
|
spinner?.start("Checking for Marp CLI...");
|
|
8013
|
-
if (!checkMarpCliAvailable2(
|
|
8366
|
+
if (!checkMarpCliAvailable2(dirname10(inputPath))) {
|
|
8014
8367
|
spinner?.fail("Marp CLI not found");
|
|
8015
8368
|
const message = "Marp CLI not found. Install it with: npm install -D @marp-team/marp-cli";
|
|
8016
8369
|
console.error(chalk9.red(`Error: ${message}`));
|
|
@@ -8032,7 +8385,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
8032
8385
|
const configLoader = new ConfigLoader();
|
|
8033
8386
|
let configPath = options.config;
|
|
8034
8387
|
if (!configPath) {
|
|
8035
|
-
configPath = await configLoader.findConfig(
|
|
8388
|
+
configPath = await configLoader.findConfig(dirname10(inputPath));
|
|
8036
8389
|
}
|
|
8037
8390
|
const config = await configLoader.load(configPath);
|
|
8038
8391
|
spinner?.succeed("Configuration loaded");
|
|
@@ -8053,7 +8406,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
8053
8406
|
const tempMdPath = inputPath.replace(/\.ya?ml$/i, ".md");
|
|
8054
8407
|
const cleanupTempFile = async () => {
|
|
8055
8408
|
try {
|
|
8056
|
-
await
|
|
8409
|
+
await unlink2(tempMdPath);
|
|
8057
8410
|
} catch {
|
|
8058
8411
|
}
|
|
8059
8412
|
};
|
|
@@ -8076,7 +8429,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
8076
8429
|
}
|
|
8077
8430
|
try {
|
|
8078
8431
|
runMarp(marpArgs, {
|
|
8079
|
-
projectDir:
|
|
8432
|
+
projectDir: dirname10(inputPath),
|
|
8080
8433
|
stdio: options.verbose ? "inherit" : "pipe"
|
|
8081
8434
|
});
|
|
8082
8435
|
spinner?.succeed(`Screenshots saved to ${outputDir}`);
|
|
@@ -8089,15 +8442,18 @@ async function executeScreenshot(inputPath, options) {
|
|
|
8089
8442
|
await cleanupTempFile();
|
|
8090
8443
|
return { success: false, errors };
|
|
8091
8444
|
}
|
|
8445
|
+
const isAiFormat = options.format === "ai";
|
|
8446
|
+
const actualFormat = isAiFormat ? "jpeg" : options.format || "png";
|
|
8447
|
+
let actualWidth = isAiFormat ? 640 : options.width || 1280;
|
|
8448
|
+
let actualHeight = Math.round(actualWidth * 9 / 16);
|
|
8092
8449
|
if (options.slide !== void 0) {
|
|
8093
8450
|
spinner?.start(`Filtering to slide ${options.slide}...`);
|
|
8094
|
-
const
|
|
8095
|
-
const format = options.format || "png";
|
|
8451
|
+
const mdBaseName2 = basename10(tempMdPath, ".md");
|
|
8096
8452
|
const filterResult = await filterToSpecificSlide(
|
|
8097
8453
|
outputDir,
|
|
8098
|
-
|
|
8454
|
+
mdBaseName2,
|
|
8099
8455
|
options.slide,
|
|
8100
|
-
|
|
8456
|
+
actualFormat
|
|
8101
8457
|
);
|
|
8102
8458
|
if (!filterResult.success) {
|
|
8103
8459
|
spinner?.fail("Failed to filter slides");
|
|
@@ -8109,20 +8465,71 @@ async function executeScreenshot(inputPath, options) {
|
|
|
8109
8465
|
}
|
|
8110
8466
|
spinner?.succeed(`Kept slide ${options.slide}: ${filterResult.keptFile}`);
|
|
8111
8467
|
}
|
|
8468
|
+
const allFiles = await readdir7(outputDir);
|
|
8469
|
+
const mdBaseName = basename10(tempMdPath, ".md");
|
|
8470
|
+
const generatedFiles = allFiles.filter((f) => f.startsWith(mdBaseName) && f.endsWith(`.${actualFormat}`)).sort();
|
|
8471
|
+
if (generatedFiles.length > 0) {
|
|
8472
|
+
try {
|
|
8473
|
+
const metadata = await sharp2(join19(outputDir, generatedFiles[0])).metadata();
|
|
8474
|
+
if (metadata.width && metadata.height) {
|
|
8475
|
+
actualWidth = metadata.width;
|
|
8476
|
+
actualHeight = metadata.height;
|
|
8477
|
+
}
|
|
8478
|
+
} catch {
|
|
8479
|
+
}
|
|
8480
|
+
}
|
|
8481
|
+
if (options.contactSheet && generatedFiles.length > 0) {
|
|
8482
|
+
spinner?.start("Generating contact sheet...");
|
|
8483
|
+
const slides = generatedFiles.map((file, index) => ({
|
|
8484
|
+
path: join19(outputDir, file),
|
|
8485
|
+
index: index + 1
|
|
8486
|
+
}));
|
|
8487
|
+
const contactSheetPath = join19(outputDir, `${mdBaseName}-contact.png`);
|
|
8488
|
+
const contactResult = await generateContactSheet(slides, {
|
|
8489
|
+
outputPath: contactSheetPath,
|
|
8490
|
+
columns: options.columns || 2,
|
|
8491
|
+
slideWidth: actualWidth,
|
|
8492
|
+
slideHeight: actualHeight
|
|
8493
|
+
});
|
|
8494
|
+
if (!contactResult.success) {
|
|
8495
|
+
spinner?.fail("Failed to generate contact sheet");
|
|
8496
|
+
console.error(chalk9.red(`Error: ${contactResult.error}`));
|
|
8497
|
+
errors.push(contactResult.error || "Contact sheet generation failed");
|
|
8498
|
+
} else {
|
|
8499
|
+
spinner?.succeed(`Contact sheet saved: ${basename10(contactSheetPath)}`);
|
|
8500
|
+
}
|
|
8501
|
+
}
|
|
8112
8502
|
await cleanupTempFile();
|
|
8113
8503
|
console.log("");
|
|
8114
|
-
|
|
8504
|
+
if (isAiFormat && generatedFiles.length > 0) {
|
|
8505
|
+
const output = formatAiOutput({
|
|
8506
|
+
files: generatedFiles,
|
|
8507
|
+
width: actualWidth,
|
|
8508
|
+
height: actualHeight,
|
|
8509
|
+
outputDir
|
|
8510
|
+
});
|
|
8511
|
+
console.log(output);
|
|
8512
|
+
} else if (isAiFormat) {
|
|
8513
|
+
console.log(`Output: ${chalk9.cyan(outputDir)}`);
|
|
8514
|
+
console.log("No screenshots generated");
|
|
8515
|
+
} else {
|
|
8516
|
+
console.log(`Output: ${chalk9.cyan(outputDir)}`);
|
|
8517
|
+
if (generatedFiles.length > 0) {
|
|
8518
|
+
console.log(`Files: ${generatedFiles.length} screenshot(s)`);
|
|
8519
|
+
}
|
|
8520
|
+
}
|
|
8115
8521
|
return {
|
|
8116
8522
|
success: true,
|
|
8117
8523
|
errors,
|
|
8118
|
-
outputDir
|
|
8524
|
+
outputDir,
|
|
8525
|
+
files: generatedFiles
|
|
8119
8526
|
};
|
|
8120
8527
|
}
|
|
8121
8528
|
|
|
8122
8529
|
// src/cli/commands/sources.ts
|
|
8123
8530
|
import { Command as Command10 } from "commander";
|
|
8124
8531
|
import { access as access13, stat as stat3 } from "fs/promises";
|
|
8125
|
-
import { resolve as
|
|
8532
|
+
import { resolve as resolve7 } from "path";
|
|
8126
8533
|
import chalk10 from "chalk";
|
|
8127
8534
|
import ora5 from "ora";
|
|
8128
8535
|
function createSourcesCommand() {
|
|
@@ -8171,7 +8578,7 @@ function createSourcesSyncCommand() {
|
|
|
8171
8578
|
});
|
|
8172
8579
|
}
|
|
8173
8580
|
async function executeSourcesInit(projectDir, options) {
|
|
8174
|
-
const resolvedDir =
|
|
8581
|
+
const resolvedDir = resolve7(projectDir);
|
|
8175
8582
|
const spinner = ora5("Initializing sources...").start();
|
|
8176
8583
|
try {
|
|
8177
8584
|
const manager = new SourcesManager(resolvedDir);
|
|
@@ -8187,10 +8594,10 @@ async function executeSourcesInit(projectDir, options) {
|
|
|
8187
8594
|
let originalSource;
|
|
8188
8595
|
if (options.fromDirectory) {
|
|
8189
8596
|
setupPattern = "A";
|
|
8190
|
-
originalSource =
|
|
8597
|
+
originalSource = resolve7(options.fromDirectory);
|
|
8191
8598
|
} else if (options.fromFile) {
|
|
8192
8599
|
setupPattern = "B";
|
|
8193
|
-
originalSource =
|
|
8600
|
+
originalSource = resolve7(options.fromFile);
|
|
8194
8601
|
}
|
|
8195
8602
|
await manager.init({
|
|
8196
8603
|
name: projectName,
|
|
@@ -8201,14 +8608,14 @@ async function executeSourcesInit(projectDir, options) {
|
|
|
8201
8608
|
if (options.fromDirectory) {
|
|
8202
8609
|
const importer = new SourceImporter(resolvedDir, manager);
|
|
8203
8610
|
const result = await importer.importDirectory(
|
|
8204
|
-
|
|
8611
|
+
resolve7(options.fromDirectory),
|
|
8205
8612
|
{ recursive: true }
|
|
8206
8613
|
);
|
|
8207
8614
|
filesImported = result.imported;
|
|
8208
8615
|
}
|
|
8209
8616
|
if (options.fromFile) {
|
|
8210
8617
|
const importer = new SourceImporter(resolvedDir, manager);
|
|
8211
|
-
await importer.importFile(
|
|
8618
|
+
await importer.importFile(resolve7(options.fromFile), {
|
|
8212
8619
|
type: "scenario"
|
|
8213
8620
|
});
|
|
8214
8621
|
filesImported = 1;
|
|
@@ -8233,8 +8640,8 @@ async function executeSourcesInit(projectDir, options) {
|
|
|
8233
8640
|
}
|
|
8234
8641
|
}
|
|
8235
8642
|
async function executeSourcesImport(projectDir, sourcePath, options) {
|
|
8236
|
-
const resolvedDir =
|
|
8237
|
-
const resolvedSource =
|
|
8643
|
+
const resolvedDir = resolve7(projectDir);
|
|
8644
|
+
const resolvedSource = resolve7(sourcePath);
|
|
8238
8645
|
const spinner = ora5("Importing files...").start();
|
|
8239
8646
|
try {
|
|
8240
8647
|
const manager = new SourcesManager(resolvedDir);
|
|
@@ -8293,7 +8700,7 @@ async function executeSourcesImport(projectDir, sourcePath, options) {
|
|
|
8293
8700
|
}
|
|
8294
8701
|
}
|
|
8295
8702
|
async function executeSourcesStatus(projectDir, _options) {
|
|
8296
|
-
const resolvedDir =
|
|
8703
|
+
const resolvedDir = resolve7(projectDir);
|
|
8297
8704
|
try {
|
|
8298
8705
|
const manager = new SourcesManager(resolvedDir);
|
|
8299
8706
|
if (!await manager.exists()) {
|
|
@@ -8380,7 +8787,7 @@ async function executeSourcesStatus(projectDir, _options) {
|
|
|
8380
8787
|
}
|
|
8381
8788
|
}
|
|
8382
8789
|
async function executeSourcesSync(projectDir, options) {
|
|
8383
|
-
const resolvedDir =
|
|
8790
|
+
const resolvedDir = resolve7(projectDir);
|
|
8384
8791
|
try {
|
|
8385
8792
|
const manager = new SourcesManager(resolvedDir);
|
|
8386
8793
|
if (!await manager.exists()) {
|