@ncukondo/slide-generation 0.2.5 → 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/README.md +46 -0
- package/README_ja.md +46 -0
- package/dist/cli/index.js +1509 -239
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +21 -7
- package/dist/index.js +264 -91
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/layouts/image-text.yaml +4 -2
- package/templates/layouts/three-column.yaml +4 -2
- package/templates/layouts/two-column.yaml +8 -4
- package/templates/special/bibliography.yaml +4 -4
package/dist/cli/index.js
CHANGED
|
@@ -923,25 +923,25 @@ var ReferenceManager = class {
|
|
|
923
923
|
return items[0] || null;
|
|
924
924
|
}
|
|
925
925
|
/**
|
|
926
|
-
* Get multiple references by IDs
|
|
926
|
+
* Get multiple references by IDs using ref export for better performance
|
|
927
927
|
*/
|
|
928
928
|
async getByIds(ids) {
|
|
929
929
|
if (ids.length === 0) {
|
|
930
930
|
return /* @__PURE__ */ new Map();
|
|
931
931
|
}
|
|
932
|
-
const
|
|
933
|
-
const
|
|
934
|
-
|
|
932
|
+
const idsArg = ids.map((id) => `"${id}"`).join(" ");
|
|
933
|
+
const result = await this.execCommand(
|
|
934
|
+
`${this.command} export ${idsArg}`
|
|
935
|
+
);
|
|
936
|
+
const items = this.parseJSON(result);
|
|
935
937
|
const map = /* @__PURE__ */ new Map();
|
|
936
|
-
for (const item of
|
|
937
|
-
|
|
938
|
-
map.set(item.id, item);
|
|
939
|
-
}
|
|
938
|
+
for (const item of items) {
|
|
939
|
+
map.set(item.id, item);
|
|
940
940
|
}
|
|
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
|
}
|
|
@@ -1080,6 +1080,82 @@ var CitationExtractor = class {
|
|
|
1080
1080
|
}
|
|
1081
1081
|
};
|
|
1082
1082
|
|
|
1083
|
+
// src/references/utils.ts
|
|
1084
|
+
var JAPANESE_PATTERN = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
|
|
1085
|
+
function isJapaneseAuthors(authors) {
|
|
1086
|
+
if (!authors || authors.length === 0) {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
return JAPANESE_PATTERN.test(authors[0].family);
|
|
1090
|
+
}
|
|
1091
|
+
function getYear(item) {
|
|
1092
|
+
const dateParts = item.issued?.["date-parts"];
|
|
1093
|
+
if (dateParts && dateParts[0] && dateParts[0][0]) {
|
|
1094
|
+
return dateParts[0][0];
|
|
1095
|
+
}
|
|
1096
|
+
return 0;
|
|
1097
|
+
}
|
|
1098
|
+
function getIdentifier(item) {
|
|
1099
|
+
if (item.PMID) {
|
|
1100
|
+
return `PMID: ${item.PMID}`;
|
|
1101
|
+
}
|
|
1102
|
+
if (item.DOI) {
|
|
1103
|
+
return `DOI: ${item.DOI}`;
|
|
1104
|
+
}
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
function getFirstAuthorFamily(item) {
|
|
1108
|
+
return item.author?.[0]?.family || "";
|
|
1109
|
+
}
|
|
1110
|
+
function formatAuthorsFull(authors, isJapanese) {
|
|
1111
|
+
if (!authors || authors.length === 0) {
|
|
1112
|
+
return "Unknown";
|
|
1113
|
+
}
|
|
1114
|
+
if (isJapanese) {
|
|
1115
|
+
return authors.map((a) => `${a.family}${a.given || ""}`).join(", ");
|
|
1116
|
+
}
|
|
1117
|
+
if (authors.length === 1) {
|
|
1118
|
+
const a = authors[0];
|
|
1119
|
+
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1120
|
+
return `${a.family}, ${initial}`;
|
|
1121
|
+
}
|
|
1122
|
+
const formatted = authors.map((a, i) => {
|
|
1123
|
+
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1124
|
+
if (i === authors.length - 1) {
|
|
1125
|
+
return `& ${a.family}, ${initial}`;
|
|
1126
|
+
}
|
|
1127
|
+
return `${a.family}, ${initial}`;
|
|
1128
|
+
});
|
|
1129
|
+
return formatted.join(", ").replace(", &", ", &");
|
|
1130
|
+
}
|
|
1131
|
+
function formatFullEntry(item) {
|
|
1132
|
+
const parts = [];
|
|
1133
|
+
const japanese = isJapaneseAuthors(item.author);
|
|
1134
|
+
const authors = formatAuthorsFull(item.author, japanese);
|
|
1135
|
+
parts.push(authors);
|
|
1136
|
+
const year = getYear(item);
|
|
1137
|
+
parts.push(`(${year}).`);
|
|
1138
|
+
if (item.title) {
|
|
1139
|
+
parts.push(`${item.title}.`);
|
|
1140
|
+
}
|
|
1141
|
+
if (item["container-title"]) {
|
|
1142
|
+
const journal = japanese ? item["container-title"] : `*${item["container-title"]}*`;
|
|
1143
|
+
let location = "";
|
|
1144
|
+
if (item.volume) {
|
|
1145
|
+
location = item.issue ? `${item.volume}(${item.issue})` : item.volume;
|
|
1146
|
+
}
|
|
1147
|
+
if (item.page) {
|
|
1148
|
+
location = location ? `${location}, ${item.page}` : item.page;
|
|
1149
|
+
}
|
|
1150
|
+
parts.push(location ? `${journal}, ${location}.` : `${journal}.`);
|
|
1151
|
+
}
|
|
1152
|
+
const identifier = getIdentifier(item);
|
|
1153
|
+
if (identifier) {
|
|
1154
|
+
parts.push(identifier);
|
|
1155
|
+
}
|
|
1156
|
+
return parts.join(" ");
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1083
1159
|
// src/references/formatter.ts
|
|
1084
1160
|
var DEFAULT_CONFIG = {
|
|
1085
1161
|
author: {
|
|
@@ -1094,7 +1170,6 @@ var DEFAULT_CONFIG = {
|
|
|
1094
1170
|
multiSep: "), ("
|
|
1095
1171
|
}
|
|
1096
1172
|
};
|
|
1097
|
-
var JAPANESE_PATTERN = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
|
|
1098
1173
|
var CITATION_BRACKET_PATTERN2 = /\[(@[\w-]+(?:,\s*[^;\]]+)?(?:;\s*@[\w-]+(?:,\s*[^;\]]+)?)*)\]/g;
|
|
1099
1174
|
var SINGLE_CITATION_PATTERN2 = /@([\w-]+)(?:,\s*([^;\]]+))?/g;
|
|
1100
1175
|
var CitationFormatter = class {
|
|
@@ -1106,6 +1181,18 @@ var CitationFormatter = class {
|
|
|
1106
1181
|
};
|
|
1107
1182
|
}
|
|
1108
1183
|
config;
|
|
1184
|
+
availabilityChecked = false;
|
|
1185
|
+
isAvailable = false;
|
|
1186
|
+
/**
|
|
1187
|
+
* Check if reference-manager is available (cached)
|
|
1188
|
+
*/
|
|
1189
|
+
async checkAvailability() {
|
|
1190
|
+
if (!this.availabilityChecked) {
|
|
1191
|
+
this.isAvailable = await this.manager.isAvailable();
|
|
1192
|
+
this.availabilityChecked = true;
|
|
1193
|
+
}
|
|
1194
|
+
return this.isAvailable;
|
|
1195
|
+
}
|
|
1109
1196
|
/**
|
|
1110
1197
|
* Format an inline citation
|
|
1111
1198
|
* e.g., "(Smith et al., 2024; PMID: 12345678)"
|
|
@@ -1125,13 +1212,16 @@ var CitationFormatter = class {
|
|
|
1125
1212
|
if (!item) {
|
|
1126
1213
|
return `[${id}]`;
|
|
1127
1214
|
}
|
|
1128
|
-
return
|
|
1215
|
+
return formatFullEntry(item);
|
|
1129
1216
|
}
|
|
1130
1217
|
/**
|
|
1131
1218
|
* Expand all citations in text
|
|
1132
1219
|
* e.g., "[@smith2024]" -> "(Smith et al., 2024; PMID: 12345678)"
|
|
1133
1220
|
*/
|
|
1134
1221
|
async expandCitations(text) {
|
|
1222
|
+
if (!await this.checkAvailability()) {
|
|
1223
|
+
return text;
|
|
1224
|
+
}
|
|
1135
1225
|
const ids = /* @__PURE__ */ new Set();
|
|
1136
1226
|
CITATION_BRACKET_PATTERN2.lastIndex = 0;
|
|
1137
1227
|
let match;
|
|
@@ -1186,117 +1276,102 @@ var CitationFormatter = class {
|
|
|
1186
1276
|
sortedItems = ids.map((id) => items.get(id)).filter((item) => item !== void 0);
|
|
1187
1277
|
} else if (sort === "author") {
|
|
1188
1278
|
sortedItems = [...items.values()].sort((a, b) => {
|
|
1189
|
-
const authorA =
|
|
1190
|
-
const authorB =
|
|
1279
|
+
const authorA = getFirstAuthorFamily(a);
|
|
1280
|
+
const authorB = getFirstAuthorFamily(b);
|
|
1191
1281
|
return authorA.localeCompare(authorB);
|
|
1192
1282
|
});
|
|
1193
1283
|
} else {
|
|
1194
1284
|
sortedItems = [...items.values()].sort((a, b) => {
|
|
1195
|
-
const yearA =
|
|
1196
|
-
const yearB =
|
|
1285
|
+
const yearA = getYear(a);
|
|
1286
|
+
const yearB = getYear(b);
|
|
1197
1287
|
return yearA - yearB;
|
|
1198
1288
|
});
|
|
1199
1289
|
}
|
|
1200
|
-
return sortedItems.map((item) =>
|
|
1290
|
+
return sortedItems.map((item) => formatFullEntry(item));
|
|
1201
1291
|
}
|
|
1202
1292
|
formatInlineItem(item) {
|
|
1203
1293
|
const author = this.formatAuthorInline(item.author);
|
|
1204
|
-
const year =
|
|
1205
|
-
const identifier =
|
|
1294
|
+
const year = getYear(item);
|
|
1295
|
+
const identifier = getIdentifier(item);
|
|
1206
1296
|
if (identifier) {
|
|
1207
1297
|
return `(${author}, ${year}; ${identifier})`;
|
|
1208
1298
|
}
|
|
1209
1299
|
return `(${author}, ${year})`;
|
|
1210
1300
|
}
|
|
1211
|
-
formatFullItem(item) {
|
|
1212
|
-
const parts = [];
|
|
1213
|
-
const isJapanese = this.isJapaneseAuthors(item.author);
|
|
1214
|
-
const authors = this.formatAuthorsFull(item.author, isJapanese);
|
|
1215
|
-
parts.push(authors);
|
|
1216
|
-
const year = this.getYear(item);
|
|
1217
|
-
parts.push(`(${year}).`);
|
|
1218
|
-
if (item.title) {
|
|
1219
|
-
parts.push(`${item.title}.`);
|
|
1220
|
-
}
|
|
1221
|
-
if (item["container-title"]) {
|
|
1222
|
-
const journal = isJapanese ? item["container-title"] : `*${item["container-title"]}*`;
|
|
1223
|
-
let location = "";
|
|
1224
|
-
if (item.volume) {
|
|
1225
|
-
location = item.issue ? `${item.volume}(${item.issue})` : item.volume;
|
|
1226
|
-
}
|
|
1227
|
-
if (item.page) {
|
|
1228
|
-
location = location ? `${location}, ${item.page}` : item.page;
|
|
1229
|
-
}
|
|
1230
|
-
parts.push(location ? `${journal}, ${location}.` : `${journal}.`);
|
|
1231
|
-
}
|
|
1232
|
-
const identifier = this.getIdentifier(item);
|
|
1233
|
-
if (identifier) {
|
|
1234
|
-
parts.push(identifier);
|
|
1235
|
-
}
|
|
1236
|
-
return parts.join(" ");
|
|
1237
|
-
}
|
|
1238
1301
|
formatAuthorInline(authors) {
|
|
1239
1302
|
if (!authors || authors.length === 0) {
|
|
1240
1303
|
return "Unknown";
|
|
1241
1304
|
}
|
|
1242
|
-
const
|
|
1305
|
+
const japanese = isJapaneseAuthors(authors);
|
|
1243
1306
|
const { etAl, etAlJa, separatorJa } = this.config.author;
|
|
1244
1307
|
const firstAuthor = authors[0];
|
|
1245
1308
|
if (authors.length === 1) {
|
|
1246
1309
|
return firstAuthor.family;
|
|
1247
1310
|
}
|
|
1248
1311
|
if (authors.length === 2) {
|
|
1249
|
-
const separator =
|
|
1312
|
+
const separator = japanese ? separatorJa : " & ";
|
|
1250
1313
|
return `${firstAuthor.family}${separator}${authors[1].family}`;
|
|
1251
1314
|
}
|
|
1252
|
-
const suffix =
|
|
1315
|
+
const suffix = japanese ? etAlJa : ` ${etAl}`;
|
|
1253
1316
|
return `${firstAuthor.family}${suffix}`;
|
|
1254
1317
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1262
|
-
if (authors.length === 1) {
|
|
1263
|
-
const a = authors[0];
|
|
1264
|
-
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1265
|
-
return `${a.family}, ${initial}`;
|
|
1266
|
-
}
|
|
1267
|
-
const formatted = authors.map((a, i) => {
|
|
1268
|
-
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1269
|
-
if (i === authors.length - 1) {
|
|
1270
|
-
return `& ${a.family}, ${initial}`;
|
|
1271
|
-
}
|
|
1272
|
-
return `${a.family}, ${initial}`;
|
|
1273
|
-
});
|
|
1274
|
-
return formatted.join(", ").replace(", &", ", &");
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
// src/references/bibliography.ts
|
|
1321
|
+
var BibliographyGenerator = class {
|
|
1322
|
+
constructor(manager) {
|
|
1323
|
+
this.manager = manager;
|
|
1275
1324
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1325
|
+
/**
|
|
1326
|
+
* Generate bibliography entries from citation IDs
|
|
1327
|
+
*/
|
|
1328
|
+
async generate(citationIds, options = {}) {
|
|
1329
|
+
const { sort = "citation-order" } = options;
|
|
1330
|
+
if (citationIds.length === 0) {
|
|
1331
|
+
return { entries: [], items: [], missing: [] };
|
|
1279
1332
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1333
|
+
const uniqueIds = [...new Set(citationIds)];
|
|
1334
|
+
const itemsMap = await this.manager.getByIds(uniqueIds);
|
|
1335
|
+
const missing = [];
|
|
1336
|
+
const foundItems = [];
|
|
1337
|
+
for (const id of uniqueIds) {
|
|
1338
|
+
const item = itemsMap.get(id);
|
|
1339
|
+
if (item) {
|
|
1340
|
+
foundItems.push(item);
|
|
1341
|
+
} else {
|
|
1342
|
+
missing.push(id);
|
|
1343
|
+
}
|
|
1289
1344
|
}
|
|
1290
|
-
|
|
1345
|
+
const sortedItems = this.sortItems(foundItems, sort);
|
|
1346
|
+
const entries = sortedItems.map((item) => formatFullEntry(item));
|
|
1347
|
+
return {
|
|
1348
|
+
entries,
|
|
1349
|
+
items: sortedItems,
|
|
1350
|
+
missing
|
|
1351
|
+
};
|
|
1291
1352
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1353
|
+
/**
|
|
1354
|
+
* Sort items according to the specified order
|
|
1355
|
+
*/
|
|
1356
|
+
sortItems(items, sort) {
|
|
1357
|
+
switch (sort) {
|
|
1358
|
+
case "citation-order":
|
|
1359
|
+
return items;
|
|
1360
|
+
case "author":
|
|
1361
|
+
return [...items].sort((a, b) => {
|
|
1362
|
+
const authorA = getFirstAuthorFamily(a);
|
|
1363
|
+
const authorB = getFirstAuthorFamily(b);
|
|
1364
|
+
return authorA.localeCompare(authorB);
|
|
1365
|
+
});
|
|
1366
|
+
case "year":
|
|
1367
|
+
return [...items].sort((a, b) => {
|
|
1368
|
+
const yearA = getYear(a);
|
|
1369
|
+
const yearB = getYear(b);
|
|
1370
|
+
return yearA - yearB;
|
|
1371
|
+
});
|
|
1372
|
+
default:
|
|
1373
|
+
return items;
|
|
1298
1374
|
}
|
|
1299
|
-
return null;
|
|
1300
1375
|
}
|
|
1301
1376
|
};
|
|
1302
1377
|
|
|
@@ -1335,6 +1410,7 @@ var Pipeline = class {
|
|
|
1335
1410
|
}
|
|
1336
1411
|
}
|
|
1337
1412
|
);
|
|
1413
|
+
this.bibliographyGenerator = new BibliographyGenerator(this.referenceManager);
|
|
1338
1414
|
this.transformer = new Transformer(
|
|
1339
1415
|
this.templateEngine,
|
|
1340
1416
|
this.templateLoader,
|
|
@@ -1351,6 +1427,7 @@ var Pipeline = class {
|
|
|
1351
1427
|
referenceManager;
|
|
1352
1428
|
citationExtractor;
|
|
1353
1429
|
citationFormatter;
|
|
1430
|
+
bibliographyGenerator;
|
|
1354
1431
|
transformer;
|
|
1355
1432
|
renderer;
|
|
1356
1433
|
warnings = [];
|
|
@@ -1360,9 +1437,10 @@ var Pipeline = class {
|
|
|
1360
1437
|
async run(inputPath, options) {
|
|
1361
1438
|
this.warnings = [];
|
|
1362
1439
|
try {
|
|
1363
|
-
|
|
1440
|
+
let presentation = await this.parseSource(inputPath);
|
|
1364
1441
|
const citationIds = this.collectCitations(presentation);
|
|
1365
1442
|
await this.resolveReferences(citationIds);
|
|
1443
|
+
presentation = await this.processBibliography(presentation, citationIds);
|
|
1366
1444
|
const transformedSlides = await this.transformSlides(presentation);
|
|
1367
1445
|
const output = this.render(transformedSlides, presentation);
|
|
1368
1446
|
if (options?.outputPath) {
|
|
@@ -1386,9 +1464,10 @@ var Pipeline = class {
|
|
|
1386
1464
|
async runWithResult(inputPath, options) {
|
|
1387
1465
|
this.warnings = [];
|
|
1388
1466
|
try {
|
|
1389
|
-
|
|
1467
|
+
let presentation = await this.parseSource(inputPath);
|
|
1390
1468
|
const citationIds = this.collectCitations(presentation);
|
|
1391
1469
|
await this.resolveReferences(citationIds);
|
|
1470
|
+
presentation = await this.processBibliography(presentation, citationIds);
|
|
1392
1471
|
const transformedSlides = await this.transformSlides(presentation);
|
|
1393
1472
|
const output = this.render(transformedSlides, presentation);
|
|
1394
1473
|
if (options?.outputPath) {
|
|
@@ -1464,6 +1543,13 @@ var Pipeline = class {
|
|
|
1464
1543
|
if (!this.config.references.enabled || ids.length === 0) {
|
|
1465
1544
|
return /* @__PURE__ */ new Map();
|
|
1466
1545
|
}
|
|
1546
|
+
const isAvailable = await this.referenceManager.isAvailable();
|
|
1547
|
+
if (!isAvailable) {
|
|
1548
|
+
this.warnings.push(
|
|
1549
|
+
"reference-manager CLI is not available. Install it to enable citation features: npm install -g @ncukondo/reference-manager"
|
|
1550
|
+
);
|
|
1551
|
+
return /* @__PURE__ */ new Map();
|
|
1552
|
+
}
|
|
1467
1553
|
try {
|
|
1468
1554
|
const items = await this.referenceManager.getByIds(ids);
|
|
1469
1555
|
for (const id of ids) {
|
|
@@ -1512,10 +1598,97 @@ var Pipeline = class {
|
|
|
1512
1598
|
);
|
|
1513
1599
|
}
|
|
1514
1600
|
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Process bibliography slides with autoGenerate: true
|
|
1603
|
+
* Populates references array from collected citations
|
|
1604
|
+
*/
|
|
1605
|
+
async processBibliography(presentation, citationIds) {
|
|
1606
|
+
if (!this.config.references.enabled || citationIds.length === 0) {
|
|
1607
|
+
return presentation;
|
|
1608
|
+
}
|
|
1609
|
+
const hasBibliographyAutoGenerate = presentation.slides.some(
|
|
1610
|
+
(slide) => slide.template === "bibliography" && slide.content?.["autoGenerate"] === true
|
|
1611
|
+
);
|
|
1612
|
+
if (!hasBibliographyAutoGenerate) {
|
|
1613
|
+
return presentation;
|
|
1614
|
+
}
|
|
1615
|
+
const isAvailable = await this.referenceManager.isAvailable();
|
|
1616
|
+
if (!isAvailable) {
|
|
1617
|
+
return presentation;
|
|
1618
|
+
}
|
|
1619
|
+
try {
|
|
1620
|
+
const updatedSlides = await Promise.all(
|
|
1621
|
+
presentation.slides.map(async (slide) => {
|
|
1622
|
+
if (slide.template === "bibliography" && slide.content?.["autoGenerate"] === true) {
|
|
1623
|
+
const sort = slide.content["sort"] || "citation-order";
|
|
1624
|
+
const result = await this.bibliographyGenerator.generate(
|
|
1625
|
+
citationIds,
|
|
1626
|
+
{ sort }
|
|
1627
|
+
);
|
|
1628
|
+
for (const id of result.missing) {
|
|
1629
|
+
this.warnings.push(`Bibliography: reference not found: ${id}`);
|
|
1630
|
+
}
|
|
1631
|
+
const references = result.items.map((item) => ({
|
|
1632
|
+
id: item.id,
|
|
1633
|
+
authors: this.formatAuthorsForTemplate(item.author),
|
|
1634
|
+
title: item.title || "",
|
|
1635
|
+
year: this.getYear(item),
|
|
1636
|
+
journal: item["container-title"],
|
|
1637
|
+
volume: item.volume,
|
|
1638
|
+
pages: item.page,
|
|
1639
|
+
doi: item.DOI,
|
|
1640
|
+
url: item.URL
|
|
1641
|
+
}));
|
|
1642
|
+
return {
|
|
1643
|
+
...slide,
|
|
1644
|
+
content: {
|
|
1645
|
+
...slide.content,
|
|
1646
|
+
references,
|
|
1647
|
+
_autoGenerated: true,
|
|
1648
|
+
_generatedEntries: result.entries
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
return slide;
|
|
1653
|
+
})
|
|
1654
|
+
);
|
|
1655
|
+
return {
|
|
1656
|
+
...presentation,
|
|
1657
|
+
slides: updatedSlides
|
|
1658
|
+
};
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
this.warnings.push(
|
|
1661
|
+
`Failed to auto-generate bibliography: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1662
|
+
);
|
|
1663
|
+
return presentation;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Format authors for template-compatible format
|
|
1668
|
+
*/
|
|
1669
|
+
formatAuthorsForTemplate(authors) {
|
|
1670
|
+
if (!authors || authors.length === 0) {
|
|
1671
|
+
return void 0;
|
|
1672
|
+
}
|
|
1673
|
+
return authors.map((a) => {
|
|
1674
|
+
const initial = a.given ? `${a.given.charAt(0)}.` : "";
|
|
1675
|
+
return initial ? `${a.family}, ${initial}` : a.family;
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Get year from CSL item
|
|
1680
|
+
*/
|
|
1681
|
+
getYear(item) {
|
|
1682
|
+
const dateParts = item.issued?.["date-parts"];
|
|
1683
|
+
if (dateParts && dateParts[0] && dateParts[0][0]) {
|
|
1684
|
+
return dateParts[0][0];
|
|
1685
|
+
}
|
|
1686
|
+
return void 0;
|
|
1687
|
+
}
|
|
1515
1688
|
};
|
|
1516
1689
|
|
|
1517
1690
|
// src/index.ts
|
|
1518
|
-
var VERSION = "0.
|
|
1691
|
+
var VERSION = "0.4.0";
|
|
1519
1692
|
|
|
1520
1693
|
// src/cli/commands/convert.ts
|
|
1521
1694
|
import { Command } from "commander";
|
|
@@ -2593,12 +2766,167 @@ import { dirname as dirname3 } from "path";
|
|
|
2593
2766
|
import chalk2 from "chalk";
|
|
2594
2767
|
import ora2 from "ora";
|
|
2595
2768
|
import { parse as parseYaml6, stringify as yamlStringify } from "yaml";
|
|
2769
|
+
|
|
2770
|
+
// src/references/validator.ts
|
|
2771
|
+
var ReferenceValidator = class {
|
|
2772
|
+
constructor(manager) {
|
|
2773
|
+
this.manager = manager;
|
|
2774
|
+
}
|
|
2775
|
+
extractor = new CitationExtractor();
|
|
2776
|
+
availableCache = null;
|
|
2777
|
+
/**
|
|
2778
|
+
* Check if reference-manager is available (cached)
|
|
2779
|
+
*/
|
|
2780
|
+
async checkAvailable() {
|
|
2781
|
+
if (this.availableCache === null) {
|
|
2782
|
+
this.availableCache = await this.manager.isAvailable();
|
|
2783
|
+
}
|
|
2784
|
+
return this.availableCache;
|
|
2785
|
+
}
|
|
2786
|
+
async validateCitations(citationIds) {
|
|
2787
|
+
const available = await this.checkAvailable();
|
|
2788
|
+
if (!available) {
|
|
2789
|
+
return {
|
|
2790
|
+
valid: true,
|
|
2791
|
+
found: [],
|
|
2792
|
+
missing: [],
|
|
2793
|
+
skipped: true,
|
|
2794
|
+
reason: "reference-manager CLI is not available"
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
if (citationIds.length === 0) {
|
|
2798
|
+
return {
|
|
2799
|
+
valid: true,
|
|
2800
|
+
found: [],
|
|
2801
|
+
missing: []
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
const references = await this.manager.getByIds(citationIds);
|
|
2805
|
+
const found = [];
|
|
2806
|
+
const missing = [];
|
|
2807
|
+
for (const id of citationIds) {
|
|
2808
|
+
if (references.has(id)) {
|
|
2809
|
+
found.push(id);
|
|
2810
|
+
} else {
|
|
2811
|
+
missing.push(id);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
return {
|
|
2815
|
+
valid: missing.length === 0,
|
|
2816
|
+
found,
|
|
2817
|
+
missing
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
async validateWithLocations(slides) {
|
|
2821
|
+
const available = await this.checkAvailable();
|
|
2822
|
+
if (!available) {
|
|
2823
|
+
return {
|
|
2824
|
+
valid: true,
|
|
2825
|
+
found: [],
|
|
2826
|
+
missing: [],
|
|
2827
|
+
skipped: true,
|
|
2828
|
+
reason: "reference-manager CLI is not available",
|
|
2829
|
+
missingDetails: []
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
const citationLocations = /* @__PURE__ */ new Map();
|
|
2833
|
+
const allCitationIds = [];
|
|
2834
|
+
for (let i = 0; i < slides.length; i++) {
|
|
2835
|
+
const slide = slides[i];
|
|
2836
|
+
const slideNumber = i + 1;
|
|
2837
|
+
const citations = this.extractor.extractFromSlide(slide);
|
|
2838
|
+
for (const citation of citations) {
|
|
2839
|
+
if (!citationLocations.has(citation.id)) {
|
|
2840
|
+
citationLocations.set(citation.id, []);
|
|
2841
|
+
allCitationIds.push(citation.id);
|
|
2842
|
+
}
|
|
2843
|
+
const text = this.findCitationText(slide, citation.id);
|
|
2844
|
+
citationLocations.get(citation.id).push({
|
|
2845
|
+
slide: slideNumber,
|
|
2846
|
+
text
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
if (allCitationIds.length === 0) {
|
|
2851
|
+
return {
|
|
2852
|
+
valid: true,
|
|
2853
|
+
found: [],
|
|
2854
|
+
missing: [],
|
|
2855
|
+
missingDetails: []
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
const references = await this.manager.getByIds(allCitationIds);
|
|
2859
|
+
const found = [];
|
|
2860
|
+
const missing = [];
|
|
2861
|
+
const missingDetails = [];
|
|
2862
|
+
for (const id of allCitationIds) {
|
|
2863
|
+
if (references.has(id)) {
|
|
2864
|
+
found.push(id);
|
|
2865
|
+
} else {
|
|
2866
|
+
missing.push(id);
|
|
2867
|
+
missingDetails.push({
|
|
2868
|
+
id,
|
|
2869
|
+
locations: citationLocations.get(id) || []
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
return {
|
|
2874
|
+
valid: missing.length === 0,
|
|
2875
|
+
found,
|
|
2876
|
+
missing,
|
|
2877
|
+
missingDetails
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
/**
|
|
2881
|
+
* Generate suggestions for adding missing references
|
|
2882
|
+
*/
|
|
2883
|
+
static generateSuggestions(missingIds) {
|
|
2884
|
+
if (missingIds.length === 0) {
|
|
2885
|
+
return "";
|
|
2886
|
+
}
|
|
2887
|
+
const lines = [
|
|
2888
|
+
"Missing citations:",
|
|
2889
|
+
...missingIds.map((id) => ` - @${id}`),
|
|
2890
|
+
"",
|
|
2891
|
+
"To add these references, use:",
|
|
2892
|
+
" ref add --pmid <pmid> # Add by PubMed ID",
|
|
2893
|
+
' ref add "<doi>" # Add by DOI',
|
|
2894
|
+
" ref add --isbn <isbn> # Add by ISBN"
|
|
2895
|
+
];
|
|
2896
|
+
return lines.join("\n");
|
|
2897
|
+
}
|
|
2898
|
+
findCitationText(slide, citationId) {
|
|
2899
|
+
const searchValue = (value) => {
|
|
2900
|
+
if (typeof value === "string") {
|
|
2901
|
+
if (value.includes(`@${citationId}`)) {
|
|
2902
|
+
return value;
|
|
2903
|
+
}
|
|
2904
|
+
} else if (Array.isArray(value)) {
|
|
2905
|
+
for (const item of value) {
|
|
2906
|
+
const found = searchValue(item);
|
|
2907
|
+
if (found) return found;
|
|
2908
|
+
}
|
|
2909
|
+
} else if (value && typeof value === "object") {
|
|
2910
|
+
for (const v of Object.values(value)) {
|
|
2911
|
+
const found = searchValue(v);
|
|
2912
|
+
if (found) return found;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
return null;
|
|
2916
|
+
};
|
|
2917
|
+
return searchValue(slide.content) || `@${citationId}`;
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
|
|
2921
|
+
// src/cli/commands/validate.ts
|
|
2596
2922
|
function getHintForErrorType(errorType) {
|
|
2597
2923
|
switch (errorType) {
|
|
2598
2924
|
case "unknown_template":
|
|
2599
2925
|
return "Run `slide-gen templates list --format llm` to see available templates.";
|
|
2600
2926
|
case "unknown_icon":
|
|
2601
2927
|
return "Run `slide-gen icons search <query>` to find icons.";
|
|
2928
|
+
case "missing_reference":
|
|
2929
|
+
return 'Run `ref add --pmid <pmid>` or `ref add "<doi>"` to add the reference.';
|
|
2602
2930
|
default:
|
|
2603
2931
|
return null;
|
|
2604
2932
|
}
|
|
@@ -2660,7 +2988,9 @@ async function executeValidate(inputPath, options) {
|
|
|
2660
2988
|
slideCount: 0,
|
|
2661
2989
|
templatesFound: false,
|
|
2662
2990
|
iconsResolved: false,
|
|
2663
|
-
referencesCount: 0
|
|
2991
|
+
referencesCount: 0,
|
|
2992
|
+
referencesValidated: false,
|
|
2993
|
+
missingReferences: []
|
|
2664
2994
|
}
|
|
2665
2995
|
};
|
|
2666
2996
|
try {
|
|
@@ -2841,15 +3171,45 @@ ${formatExampleAsYaml(template.example, 2)}` : void 0;
|
|
|
2841
3171
|
}
|
|
2842
3172
|
}
|
|
2843
3173
|
}
|
|
2844
|
-
const
|
|
2845
|
-
const
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
3174
|
+
const citationExtractor = new CitationExtractor();
|
|
3175
|
+
const extractedCitations = citationExtractor.extractFromPresentation(presentation);
|
|
3176
|
+
const citationIds = citationExtractor.getUniqueIds(extractedCitations);
|
|
3177
|
+
result.stats.referencesCount = citationIds.length;
|
|
3178
|
+
if (config.references?.enabled && citationIds.length > 0) {
|
|
3179
|
+
const refManager = new ReferenceManager();
|
|
3180
|
+
const refValidator = new ReferenceValidator(refManager);
|
|
3181
|
+
const refValidationResult = await refValidator.validateWithLocations(
|
|
3182
|
+
presentation.slides
|
|
3183
|
+
);
|
|
3184
|
+
if (refValidationResult.skipped) {
|
|
3185
|
+
result.warnings.push(
|
|
3186
|
+
`Reference validation skipped: ${refValidationResult.reason}`
|
|
3187
|
+
);
|
|
3188
|
+
} else {
|
|
3189
|
+
result.stats.referencesValidated = true;
|
|
3190
|
+
result.stats.missingReferences = refValidationResult.missing;
|
|
3191
|
+
if (!refValidationResult.valid) {
|
|
3192
|
+
for (const missingDetail of refValidationResult.missingDetails) {
|
|
3193
|
+
for (const location of missingDetail.locations) {
|
|
3194
|
+
const slideLine = result.slideLines[location.slide - 1];
|
|
3195
|
+
result.warnings.push(
|
|
3196
|
+
`Citation not found in library: ${missingDetail.id} (Slide ${location.slide})`
|
|
3197
|
+
);
|
|
3198
|
+
const structuredError = {
|
|
3199
|
+
slide: location.slide,
|
|
3200
|
+
template: presentation.slides[location.slide - 1]?.template || "unknown",
|
|
3201
|
+
message: `Citation not found in library: ${missingDetail.id}`,
|
|
3202
|
+
errorType: "missing_reference"
|
|
3203
|
+
};
|
|
3204
|
+
if (slideLine !== void 0) {
|
|
3205
|
+
structuredError.line = slideLine;
|
|
3206
|
+
}
|
|
3207
|
+
result.structuredErrors.push(structuredError);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
2850
3211
|
}
|
|
2851
3212
|
}
|
|
2852
|
-
result.stats.referencesCount = references.size;
|
|
2853
3213
|
if (result.errors.length > 0) {
|
|
2854
3214
|
result.valid = false;
|
|
2855
3215
|
}
|
|
@@ -2885,7 +3245,16 @@ function outputResult(result, options) {
|
|
|
2885
3245
|
valid: result.valid,
|
|
2886
3246
|
errors: result.errors,
|
|
2887
3247
|
warnings: result.warnings,
|
|
2888
|
-
stats:
|
|
3248
|
+
stats: {
|
|
3249
|
+
yamlSyntax: result.stats.yamlSyntax,
|
|
3250
|
+
metaValid: result.stats.metaValid,
|
|
3251
|
+
slideCount: result.stats.slideCount,
|
|
3252
|
+
templatesFound: result.stats.templatesFound,
|
|
3253
|
+
iconsResolved: result.stats.iconsResolved,
|
|
3254
|
+
referencesCount: result.stats.referencesCount,
|
|
3255
|
+
referencesValidated: result.stats.referencesValidated,
|
|
3256
|
+
missingReferences: result.stats.missingReferences
|
|
3257
|
+
}
|
|
2889
3258
|
},
|
|
2890
3259
|
null,
|
|
2891
3260
|
2
|
|
@@ -2922,6 +3291,22 @@ function outputResult(result, options) {
|
|
|
2922
3291
|
if (result.stats.referencesCount > 0) {
|
|
2923
3292
|
console.log(chalk2.green("\u2713") + ` ${result.stats.referencesCount} references found`);
|
|
2924
3293
|
}
|
|
3294
|
+
if (result.stats.referencesValidated) {
|
|
3295
|
+
if (result.stats.missingReferences.length === 0) {
|
|
3296
|
+
console.log(chalk2.green("\u2713") + " All references validated");
|
|
3297
|
+
} else {
|
|
3298
|
+
console.log(
|
|
3299
|
+
chalk2.yellow("\u26A0") + ` ${result.stats.missingReferences.length} reference(s) not found in library`
|
|
3300
|
+
);
|
|
3301
|
+
const suggestions = ReferenceValidator.generateSuggestions(
|
|
3302
|
+
result.stats.missingReferences
|
|
3303
|
+
);
|
|
3304
|
+
if (suggestions) {
|
|
3305
|
+
console.log("");
|
|
3306
|
+
console.log(chalk2.dim(suggestions));
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
2925
3310
|
for (const error of result.errors) {
|
|
2926
3311
|
console.log(chalk2.red("\u2717") + ` ${error}`);
|
|
2927
3312
|
}
|
|
@@ -2952,8 +3337,8 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
2952
3337
|
|
|
2953
3338
|
// src/cli/commands/preview.ts
|
|
2954
3339
|
import { Command as Command3 } from "commander";
|
|
2955
|
-
import { access as access6,
|
|
2956
|
-
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";
|
|
2957
3342
|
import * as path6 from "path";
|
|
2958
3343
|
import { tmpdir } from "os";
|
|
2959
3344
|
import { createServer } from "http";
|
|
@@ -2962,16 +3347,24 @@ import { watch as chokidarWatch } from "chokidar";
|
|
|
2962
3347
|
|
|
2963
3348
|
// src/cli/utils/marp-runner.ts
|
|
2964
3349
|
import { existsSync } from "fs";
|
|
2965
|
-
import { join as join8 } from "path";
|
|
3350
|
+
import { join as join8, resolve, dirname as dirname4 } from "path";
|
|
2966
3351
|
import {
|
|
2967
3352
|
execFileSync,
|
|
2968
3353
|
spawn
|
|
2969
3354
|
} from "child_process";
|
|
2970
3355
|
function getMarpCommand(projectDir) {
|
|
2971
|
-
const
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
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;
|
|
2975
3368
|
}
|
|
2976
3369
|
try {
|
|
2977
3370
|
execFileSync("marp", ["--version"], { stdio: "ignore", timeout: 5e3 });
|
|
@@ -3128,23 +3521,19 @@ async function collectSlideInfo(dir, baseName, format) {
|
|
|
3128
3521
|
async function checkMarpCliAvailable(projectDir) {
|
|
3129
3522
|
return isMarpAvailable(projectDir);
|
|
3130
3523
|
}
|
|
3131
|
-
function
|
|
3524
|
+
function getTempPreviewDir(inputPath) {
|
|
3132
3525
|
const base = basename4(inputPath, ".yaml");
|
|
3133
|
-
return join9(tmpdir(), `slide-gen-preview-${base}-${Date.now()}
|
|
3526
|
+
return join9(tmpdir(), `slide-gen-preview-${base}-${Date.now()}`);
|
|
3134
3527
|
}
|
|
3135
|
-
function buildMarpCommand(
|
|
3136
|
-
const parts = ["marp", "--
|
|
3137
|
-
if (options.port) {
|
|
3138
|
-
parts.push("-p", String(options.port));
|
|
3139
|
-
}
|
|
3528
|
+
function buildMarpCommand(markdownDir, options) {
|
|
3529
|
+
const parts = ["marp", "--server", "-I", markdownDir];
|
|
3140
3530
|
if (options.watch) {
|
|
3141
3531
|
parts.push("--watch");
|
|
3142
3532
|
}
|
|
3143
|
-
parts.push(markdownPath);
|
|
3144
3533
|
return parts.join(" ");
|
|
3145
3534
|
}
|
|
3146
3535
|
function startStaticServer(baseDir, port, options = {}) {
|
|
3147
|
-
return new Promise((
|
|
3536
|
+
return new Promise((resolve8, reject) => {
|
|
3148
3537
|
const mimeTypes = {
|
|
3149
3538
|
".html": "text/html",
|
|
3150
3539
|
".png": "image/png",
|
|
@@ -3181,7 +3570,7 @@ function startStaticServer(baseDir, port, options = {}) {
|
|
|
3181
3570
|
const prefix = options.messagePrefix ?? "Server";
|
|
3182
3571
|
console.log(`${prefix} running at ${chalk3.cyan(url)}`);
|
|
3183
3572
|
}
|
|
3184
|
-
|
|
3573
|
+
resolve8(server);
|
|
3185
3574
|
});
|
|
3186
3575
|
});
|
|
3187
3576
|
}
|
|
@@ -3198,7 +3587,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
3198
3587
|
return { success: false, errors };
|
|
3199
3588
|
}
|
|
3200
3589
|
console.log("Checking for Marp CLI...");
|
|
3201
|
-
const projectDir =
|
|
3590
|
+
const projectDir = dirname5(inputPath);
|
|
3202
3591
|
const marpAvailable = await checkMarpCliAvailable(projectDir);
|
|
3203
3592
|
if (!marpAvailable) {
|
|
3204
3593
|
console.error(
|
|
@@ -3215,7 +3604,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
3215
3604
|
const configLoader = new ConfigLoader();
|
|
3216
3605
|
let configPath = options.config;
|
|
3217
3606
|
if (!configPath) {
|
|
3218
|
-
configPath = await configLoader.findConfig(
|
|
3607
|
+
configPath = await configLoader.findConfig(dirname5(inputPath));
|
|
3219
3608
|
}
|
|
3220
3609
|
const config = await configLoader.load(configPath);
|
|
3221
3610
|
console.log("Initializing pipeline...");
|
|
@@ -3313,11 +3702,11 @@ Showing ${slides.length} slides in gallery mode`);
|
|
|
3313
3702
|
};
|
|
3314
3703
|
process.on("SIGINT", signalHandler);
|
|
3315
3704
|
process.on("SIGTERM", signalHandler);
|
|
3316
|
-
await new Promise((
|
|
3705
|
+
await new Promise((resolve8) => {
|
|
3317
3706
|
if (options.signal) {
|
|
3318
|
-
options.signal.addEventListener("abort", () =>
|
|
3707
|
+
options.signal.addEventListener("abort", () => resolve8());
|
|
3319
3708
|
}
|
|
3320
|
-
server.on("close", () =>
|
|
3709
|
+
server.on("close", () => resolve8());
|
|
3321
3710
|
});
|
|
3322
3711
|
return { success: true, errors };
|
|
3323
3712
|
}
|
|
@@ -3351,7 +3740,7 @@ async function executePreview(inputPath, options) {
|
|
|
3351
3740
|
return { success: false, errors };
|
|
3352
3741
|
}
|
|
3353
3742
|
console.log("Checking for Marp CLI...");
|
|
3354
|
-
const marpAvailable = await checkMarpCliAvailable(
|
|
3743
|
+
const marpAvailable = await checkMarpCliAvailable(dirname5(inputPath));
|
|
3355
3744
|
if (!marpAvailable) {
|
|
3356
3745
|
console.error(
|
|
3357
3746
|
chalk3.red(
|
|
@@ -3366,7 +3755,7 @@ async function executePreview(inputPath, options) {
|
|
|
3366
3755
|
const configLoader = new ConfigLoader();
|
|
3367
3756
|
let configPath = options.config;
|
|
3368
3757
|
if (!configPath) {
|
|
3369
|
-
configPath = await configLoader.findConfig(
|
|
3758
|
+
configPath = await configLoader.findConfig(dirname5(inputPath));
|
|
3370
3759
|
}
|
|
3371
3760
|
const config = await configLoader.load(configPath);
|
|
3372
3761
|
console.log("Initializing pipeline...");
|
|
@@ -3380,7 +3769,9 @@ async function executePreview(inputPath, options) {
|
|
|
3380
3769
|
process.exitCode = ExitCode.ConversionError;
|
|
3381
3770
|
return { success: false, errors };
|
|
3382
3771
|
}
|
|
3383
|
-
const
|
|
3772
|
+
const tempDir = getTempPreviewDir(inputPath);
|
|
3773
|
+
await mkdir2(tempDir, { recursive: true });
|
|
3774
|
+
const tempMarkdownPath = join9(tempDir, "slides.md");
|
|
3384
3775
|
console.log(`Converting ${chalk3.cyan(inputPath)}...`);
|
|
3385
3776
|
try {
|
|
3386
3777
|
await pipeline.runWithResult(inputPath, { outputPath: tempMarkdownPath });
|
|
@@ -3390,23 +3781,29 @@ async function executePreview(inputPath, options) {
|
|
|
3390
3781
|
console.error(chalk3.red(`Error: Conversion failed: ${message}`));
|
|
3391
3782
|
errors.push(message);
|
|
3392
3783
|
process.exitCode = ExitCode.ConversionError;
|
|
3784
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
3393
3785
|
return { success: false, errors };
|
|
3394
3786
|
}
|
|
3395
3787
|
console.log(`
|
|
3396
3788
|
Starting preview server on port ${chalk3.cyan(port)}...`);
|
|
3397
|
-
const marpCommand = buildMarpCommand(
|
|
3789
|
+
const marpCommand = buildMarpCommand(tempDir, {
|
|
3398
3790
|
...options,
|
|
3399
|
-
|
|
3400
|
-
watch: false
|
|
3401
|
-
// We handle watch ourselves if needed
|
|
3791
|
+
watch: options.watch ?? false
|
|
3402
3792
|
});
|
|
3403
3793
|
if (verbose) {
|
|
3404
3794
|
console.log(`Running: ${marpCommand}`);
|
|
3405
3795
|
}
|
|
3406
|
-
const
|
|
3407
|
-
|
|
3796
|
+
const marpArgs = ["--server", "-I", tempDir];
|
|
3797
|
+
if (options.watch) {
|
|
3798
|
+
marpArgs.push("--watch");
|
|
3799
|
+
}
|
|
3800
|
+
const marpProcess = spawnMarp(marpArgs, {
|
|
3801
|
+
projectDir: dirname5(inputPath),
|
|
3408
3802
|
stdio: "inherit"
|
|
3409
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.");
|
|
3410
3807
|
let watcher = null;
|
|
3411
3808
|
let debounceTimer = null;
|
|
3412
3809
|
if (options.watch) {
|
|
@@ -3444,7 +3841,7 @@ Watching ${chalk3.cyan(inputPath)} for changes...`);
|
|
|
3444
3841
|
watcher?.close();
|
|
3445
3842
|
marpProcess.kill();
|
|
3446
3843
|
try {
|
|
3447
|
-
await
|
|
3844
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
3448
3845
|
} catch {
|
|
3449
3846
|
}
|
|
3450
3847
|
};
|
|
@@ -3460,13 +3857,13 @@ Watching ${chalk3.cyan(inputPath)} for changes...`);
|
|
|
3460
3857
|
};
|
|
3461
3858
|
process.on("SIGINT", signalHandler);
|
|
3462
3859
|
process.on("SIGTERM", signalHandler);
|
|
3463
|
-
await new Promise((
|
|
3860
|
+
await new Promise((resolve8) => {
|
|
3464
3861
|
marpProcess.on("exit", () => {
|
|
3465
3862
|
cleanup();
|
|
3466
|
-
|
|
3863
|
+
resolve8();
|
|
3467
3864
|
});
|
|
3468
3865
|
if (options.signal) {
|
|
3469
|
-
options.signal.addEventListener("abort", () =>
|
|
3866
|
+
options.signal.addEventListener("abort", () => resolve8());
|
|
3470
3867
|
}
|
|
3471
3868
|
});
|
|
3472
3869
|
return {
|
|
@@ -4011,8 +4408,8 @@ Showing ${templatePreviews.length} template preview(s)`);
|
|
|
4011
4408
|
};
|
|
4012
4409
|
process.on("SIGINT", signalHandler);
|
|
4013
4410
|
process.on("SIGTERM", signalHandler);
|
|
4014
|
-
await new Promise((
|
|
4015
|
-
server.on("close", () =>
|
|
4411
|
+
await new Promise((resolve8) => {
|
|
4412
|
+
server.on("close", () => resolve8());
|
|
4016
4413
|
});
|
|
4017
4414
|
}
|
|
4018
4415
|
function createTemplatesCommand() {
|
|
@@ -4810,7 +5207,7 @@ import { mkdir as mkdir7, writeFile as writeFile7, access as access10, readdir a
|
|
|
4810
5207
|
import { existsSync as existsSync2 } from "fs";
|
|
4811
5208
|
import { execSync, spawnSync } from "child_process";
|
|
4812
5209
|
import { createInterface } from "readline";
|
|
4813
|
-
import { basename as basename7, dirname as
|
|
5210
|
+
import { basename as basename7, dirname as dirname7, join as join16, resolve as resolve6, sep } from "path";
|
|
4814
5211
|
import { fileURLToPath } from "url";
|
|
4815
5212
|
import chalk6 from "chalk";
|
|
4816
5213
|
import ora3 from "ora";
|
|
@@ -4889,14 +5286,105 @@ var missingItemSchema = z8.object({
|
|
|
4889
5286
|
status: z8.string().optional(),
|
|
4890
5287
|
notes: z8.string().optional()
|
|
4891
5288
|
});
|
|
5289
|
+
var referenceItemSchema = z8.object({
|
|
5290
|
+
id: z8.string(),
|
|
5291
|
+
status: z8.enum(["pending", "added", "existing"]),
|
|
5292
|
+
slide: z8.number(),
|
|
5293
|
+
purpose: z8.string(),
|
|
5294
|
+
requirement: z8.enum(["required", "recommended"]).optional(),
|
|
5295
|
+
added_date: z8.string().optional(),
|
|
5296
|
+
suggested_search: z8.array(z8.string()).optional(),
|
|
5297
|
+
notes: z8.string().optional()
|
|
5298
|
+
});
|
|
5299
|
+
var referencesStatusSchema = z8.object({
|
|
5300
|
+
required: z8.number().default(0),
|
|
5301
|
+
found: z8.number().default(0),
|
|
5302
|
+
pending: z8.number().default(0)
|
|
5303
|
+
});
|
|
5304
|
+
var referencesSectionSchema = z8.object({
|
|
5305
|
+
status: referencesStatusSchema.optional(),
|
|
5306
|
+
items: z8.array(referenceItemSchema).default([])
|
|
5307
|
+
});
|
|
4892
5308
|
var sourcesYamlSchema = z8.object({
|
|
4893
5309
|
project: projectSchema,
|
|
4894
5310
|
context: contextSchema.optional(),
|
|
4895
5311
|
sources: z8.array(sourceEntrySchema).optional(),
|
|
4896
5312
|
dependencies: z8.record(dependencySchema).optional(),
|
|
4897
|
-
missing: z8.array(missingItemSchema).optional()
|
|
5313
|
+
missing: z8.array(missingItemSchema).optional(),
|
|
5314
|
+
references: referencesSectionSchema.optional()
|
|
4898
5315
|
});
|
|
4899
5316
|
|
|
5317
|
+
// src/sources/references-tracker.ts
|
|
5318
|
+
var ReferencesTracker = class {
|
|
5319
|
+
items = [];
|
|
5320
|
+
constructor(initial) {
|
|
5321
|
+
if (initial?.items) {
|
|
5322
|
+
this.items = [...initial.items];
|
|
5323
|
+
}
|
|
5324
|
+
}
|
|
5325
|
+
/**
|
|
5326
|
+
* Add a pending reference that needs to be found
|
|
5327
|
+
*/
|
|
5328
|
+
addPending(ref) {
|
|
5329
|
+
this.items.push({
|
|
5330
|
+
...ref,
|
|
5331
|
+
status: "pending"
|
|
5332
|
+
});
|
|
5333
|
+
}
|
|
5334
|
+
/**
|
|
5335
|
+
* Mark a pending reference as added with its actual citation key
|
|
5336
|
+
*/
|
|
5337
|
+
markAdded(pendingId, actualId) {
|
|
5338
|
+
const item = this.items.find((i) => i.id === pendingId);
|
|
5339
|
+
if (item) {
|
|
5340
|
+
item.id = actualId;
|
|
5341
|
+
item.status = "added";
|
|
5342
|
+
item.added_date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
/**
|
|
5346
|
+
* Mark a reference as existing in the bibliography
|
|
5347
|
+
*/
|
|
5348
|
+
markExisting(ref) {
|
|
5349
|
+
this.items.push({
|
|
5350
|
+
...ref,
|
|
5351
|
+
status: "existing"
|
|
5352
|
+
});
|
|
5353
|
+
}
|
|
5354
|
+
/**
|
|
5355
|
+
* Get all reference items
|
|
5356
|
+
*/
|
|
5357
|
+
getItems() {
|
|
5358
|
+
return [...this.items];
|
|
5359
|
+
}
|
|
5360
|
+
/**
|
|
5361
|
+
* Get only pending references
|
|
5362
|
+
*/
|
|
5363
|
+
getPending() {
|
|
5364
|
+
return this.items.filter((i) => i.status === "pending");
|
|
5365
|
+
}
|
|
5366
|
+
/**
|
|
5367
|
+
* Calculate status summary
|
|
5368
|
+
*/
|
|
5369
|
+
getStatus() {
|
|
5370
|
+
const required = this.items.filter(
|
|
5371
|
+
(i) => i.requirement === "required"
|
|
5372
|
+
).length;
|
|
5373
|
+
const pending = this.items.filter((i) => i.status === "pending").length;
|
|
5374
|
+
const found = this.items.filter((i) => i.status !== "pending").length;
|
|
5375
|
+
return { required, found, pending };
|
|
5376
|
+
}
|
|
5377
|
+
/**
|
|
5378
|
+
* Convert to YAML-compatible object
|
|
5379
|
+
*/
|
|
5380
|
+
toYaml() {
|
|
5381
|
+
return {
|
|
5382
|
+
status: this.getStatus(),
|
|
5383
|
+
items: this.items
|
|
5384
|
+
};
|
|
5385
|
+
}
|
|
5386
|
+
};
|
|
5387
|
+
|
|
4900
5388
|
// src/sources/manager.ts
|
|
4901
5389
|
var SourcesManager = class _SourcesManager {
|
|
4902
5390
|
sourcesDir;
|
|
@@ -5071,6 +5559,44 @@ var SourcesManager = class _SourcesManager {
|
|
|
5071
5559
|
const data = await this.load();
|
|
5072
5560
|
return data.sources?.filter((s) => s.type === type) ?? [];
|
|
5073
5561
|
}
|
|
5562
|
+
/**
|
|
5563
|
+
* Get references from sources.yaml
|
|
5564
|
+
*/
|
|
5565
|
+
async getReferences() {
|
|
5566
|
+
const data = await this.load();
|
|
5567
|
+
const tracker = new ReferencesTracker(data.references);
|
|
5568
|
+
return tracker.toYaml();
|
|
5569
|
+
}
|
|
5570
|
+
/**
|
|
5571
|
+
* Add a pending reference that needs to be found
|
|
5572
|
+
*/
|
|
5573
|
+
async addPendingReference(ref) {
|
|
5574
|
+
const data = await this.load();
|
|
5575
|
+
const tracker = new ReferencesTracker(data.references);
|
|
5576
|
+
tracker.addPending(ref);
|
|
5577
|
+
data.references = tracker.toYaml();
|
|
5578
|
+
await this.save(data);
|
|
5579
|
+
}
|
|
5580
|
+
/**
|
|
5581
|
+
* Mark a pending reference as added with its actual citation key
|
|
5582
|
+
*/
|
|
5583
|
+
async markReferenceAdded(pendingId, actualId) {
|
|
5584
|
+
const data = await this.load();
|
|
5585
|
+
const tracker = new ReferencesTracker(data.references);
|
|
5586
|
+
tracker.markAdded(pendingId, actualId);
|
|
5587
|
+
data.references = tracker.toYaml();
|
|
5588
|
+
await this.save(data);
|
|
5589
|
+
}
|
|
5590
|
+
/**
|
|
5591
|
+
* Mark a reference as existing in the bibliography
|
|
5592
|
+
*/
|
|
5593
|
+
async markReferenceExisting(ref) {
|
|
5594
|
+
const data = await this.load();
|
|
5595
|
+
const tracker = new ReferencesTracker(data.references);
|
|
5596
|
+
tracker.markExisting(ref);
|
|
5597
|
+
data.references = tracker.toYaml();
|
|
5598
|
+
await this.save(data);
|
|
5599
|
+
}
|
|
5074
5600
|
};
|
|
5075
5601
|
|
|
5076
5602
|
// src/sources/importer.ts
|
|
@@ -5467,6 +5993,51 @@ allowed-tools: Read Write Edit Bash Glob Grep
|
|
|
5467
5993
|
|
|
5468
5994
|
Helps create Marp slides using the slide-gen CLI tool.
|
|
5469
5995
|
|
|
5996
|
+
## First Question
|
|
5997
|
+
|
|
5998
|
+
**Always start by asking about the user's material situation:**
|
|
5999
|
+
|
|
6000
|
+
> "Let's create slides. What materials do you have?
|
|
6001
|
+
>
|
|
6002
|
+
> A) I have detailed materials organized in a directory
|
|
6003
|
+
> (scenarios, scripts, data files, images, etc.)
|
|
6004
|
+
>
|
|
6005
|
+
> B) I have partial materials like a scenario or script
|
|
6006
|
+
> (but need to supplement with additional info)
|
|
6007
|
+
>
|
|
6008
|
+
> C) I don't have materials yet
|
|
6009
|
+
> (starting from scratch, will collect info through dialogue)"
|
|
6010
|
+
|
|
6011
|
+
Then follow the appropriate pattern below.
|
|
6012
|
+
|
|
6013
|
+
## Workflow Patterns
|
|
6014
|
+
|
|
6015
|
+
### Pattern A: Explore Mode (Detailed Materials Exist)
|
|
6016
|
+
|
|
6017
|
+
When user has materials organized in a directory:
|
|
6018
|
+
1. Ask for directory path and scan with Glob
|
|
6019
|
+
2. Read and classify files (scenario, scripts, data, images)
|
|
6020
|
+
3. Summarize findings and confirm with user
|
|
6021
|
+
4. Configure \`sources/\` directory
|
|
6022
|
+
|
|
6023
|
+
### Pattern B: Supplement Mode (Partial Materials)
|
|
6024
|
+
|
|
6025
|
+
When user has only a scenario or partial materials:
|
|
6026
|
+
1. Read and analyze provided content
|
|
6027
|
+
2. Identify what's present vs. missing
|
|
6028
|
+
3. Ask targeted questions to fill gaps
|
|
6029
|
+
4. Configure \`sources/\` directory
|
|
6030
|
+
|
|
6031
|
+
### Pattern C: Interview Mode (Starting from Scratch)
|
|
6032
|
+
|
|
6033
|
+
When user has no materials:
|
|
6034
|
+
1. Ask basic questions (purpose, audience, duration)
|
|
6035
|
+
2. Collect data and examples
|
|
6036
|
+
3. Propose slide structure for approval
|
|
6037
|
+
4. Configure \`sources/\` directory
|
|
6038
|
+
|
|
6039
|
+
**See [references/workflows.md](references/workflows.md) for detailed steps.**
|
|
6040
|
+
|
|
5470
6041
|
## Capabilities
|
|
5471
6042
|
|
|
5472
6043
|
1. **Project initialization**: \`slide-gen init\`
|
|
@@ -5476,21 +6047,14 @@ Helps create Marp slides using the slide-gen CLI tool.
|
|
|
5476
6047
|
5. **Conversion**: \`slide-gen convert\`
|
|
5477
6048
|
6. **Screenshot**: \`slide-gen screenshot\` (for AI review)
|
|
5478
6049
|
|
|
5479
|
-
##
|
|
5480
|
-
|
|
5481
|
-
### New Project
|
|
5482
|
-
|
|
5483
|
-
1. Run \`slide-gen init <directory>\` to initialize
|
|
5484
|
-
2. Gather requirements from user
|
|
5485
|
-
3. Check templates with \`slide-gen templates list --format llm\`
|
|
5486
|
-
4. Create presentation.yaml
|
|
5487
|
-
5. Validate and convert
|
|
6050
|
+
## Slide Creation Flow
|
|
5488
6051
|
|
|
5489
|
-
|
|
6052
|
+
After material collection (Pattern A/B/C above):
|
|
5490
6053
|
|
|
5491
|
-
1.
|
|
5492
|
-
2.
|
|
5493
|
-
3. Validate
|
|
6054
|
+
1. Check templates with \`slide-gen templates list --format llm\`
|
|
6055
|
+
2. Create presentation.yaml
|
|
6056
|
+
3. Validate: \`slide-gen validate presentation.yaml\`
|
|
6057
|
+
4. Convert: \`slide-gen convert presentation.yaml\`
|
|
5494
6058
|
|
|
5495
6059
|
## YAML Source Format
|
|
5496
6060
|
|
|
@@ -5573,6 +6137,76 @@ The \`validate --format llm\` command provides:
|
|
|
5573
6137
|
- Error locations with line numbers
|
|
5574
6138
|
- Fix examples from template definitions
|
|
5575
6139
|
- Contextual hints for unknown templates/icons
|
|
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
|
+
|
|
6184
|
+
## Reference Management
|
|
6185
|
+
|
|
6186
|
+
For academic presentations, manage citations and references:
|
|
6187
|
+
|
|
6188
|
+
1. **Analyze** content for citation needs
|
|
6189
|
+
2. **Search** existing library: \`ref search\`
|
|
6190
|
+
3. **Add** new references: \`ref add pmid:XXX\`
|
|
6191
|
+
4. **Validate** citations: \`slide-gen validate\`
|
|
6192
|
+
|
|
6193
|
+
See \`.skills/slide-assistant/references/skill.md\` for detailed workflow.
|
|
6194
|
+
|
|
6195
|
+
### Quick Commands
|
|
6196
|
+
|
|
6197
|
+
\`\`\`bash
|
|
6198
|
+
# Search library
|
|
6199
|
+
ref search "keyword" --format json
|
|
6200
|
+
|
|
6201
|
+
# Add from PMID
|
|
6202
|
+
ref add pmid:38941256
|
|
6203
|
+
|
|
6204
|
+
# Add from DOI
|
|
6205
|
+
ref add "10.1038/xxxxx"
|
|
6206
|
+
|
|
6207
|
+
# Validate citations
|
|
6208
|
+
slide-gen validate presentation.yaml
|
|
6209
|
+
\`\`\`
|
|
5576
6210
|
`;
|
|
5577
6211
|
}
|
|
5578
6212
|
|
|
@@ -5633,7 +6267,24 @@ Read \`.skills/slide-assistant/SKILL.md\` for detailed instructions.
|
|
|
5633
6267
|
- \`/slide-validate\` - Validate slide source file
|
|
5634
6268
|
- \`/slide-preview\` - Preview slides in browser
|
|
5635
6269
|
- \`/slide-screenshot\` - Take screenshots for review
|
|
6270
|
+
- \`/slide-review\` - Visual review and iteration workflow
|
|
5636
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.
|
|
5637
6288
|
`;
|
|
5638
6289
|
}
|
|
5639
6290
|
|
|
@@ -5835,31 +6486,72 @@ Image with text side by side.
|
|
|
5835
6486
|
function generateWorkflowsRef() {
|
|
5836
6487
|
return `# Workflow Reference
|
|
5837
6488
|
|
|
6489
|
+
## Entry Point
|
|
6490
|
+
|
|
6491
|
+
**Always start by asking this question:**
|
|
6492
|
+
|
|
6493
|
+
> "Let's create slides. What materials do you have?
|
|
6494
|
+
>
|
|
6495
|
+
> A) I have detailed materials organized in a directory
|
|
6496
|
+
> (scenarios, scripts, data files, images, etc.)
|
|
6497
|
+
>
|
|
6498
|
+
> B) I have partial materials like a scenario or script
|
|
6499
|
+
> (but need to supplement with additional info)
|
|
6500
|
+
>
|
|
6501
|
+
> C) I don't have materials yet
|
|
6502
|
+
> (starting from scratch, will collect info through dialogue)"
|
|
6503
|
+
|
|
6504
|
+
Based on the answer, follow the appropriate pattern below.
|
|
6505
|
+
|
|
5838
6506
|
## Source Collection Flow
|
|
5839
6507
|
|
|
5840
|
-
### Pattern A:
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
6508
|
+
### Pattern A: Explore Mode (Detailed Materials Exist)
|
|
6509
|
+
|
|
6510
|
+
When user has materials organized in a directory:
|
|
6511
|
+
|
|
6512
|
+
1. **Ask for the directory path**
|
|
6513
|
+
2. **Scan directory structure** with Glob tool
|
|
6514
|
+
3. **Read and analyze each file**
|
|
6515
|
+
4. **Classify files** into categories:
|
|
6516
|
+
- Scenario/scripts
|
|
6517
|
+
- Data files (CSV, JSON, etc.)
|
|
6518
|
+
- Images and diagrams
|
|
6519
|
+
- Reference documents
|
|
6520
|
+
5. **Summarize findings** and confirm with user
|
|
6521
|
+
6. **Ask clarifying questions** about gaps
|
|
6522
|
+
7. **Configure \`sources/\` directory** with organized materials
|
|
6523
|
+
8. Proceed to slide creation
|
|
6524
|
+
|
|
6525
|
+
### Pattern B: Supplement Mode (Partial Materials)
|
|
6526
|
+
|
|
6527
|
+
When user has only a scenario or partial materials:
|
|
6528
|
+
|
|
6529
|
+
1. **Ask user** to provide the file path or paste content
|
|
6530
|
+
2. **Analyze the content** thoroughly
|
|
6531
|
+
3. **Identify what information is present** vs. missing
|
|
6532
|
+
4. **Ask targeted questions** to fill gaps:
|
|
6533
|
+
- Purpose and audience
|
|
6534
|
+
- Duration and format
|
|
6535
|
+
- Key messages
|
|
6536
|
+
- Available data/examples
|
|
6537
|
+
5. **Load any additional files** user mentions
|
|
6538
|
+
6. **Configure \`sources/\` directory**
|
|
6539
|
+
7. Proceed to slide creation
|
|
6540
|
+
|
|
6541
|
+
### Pattern C: Interview Mode (Starting from Scratch)
|
|
6542
|
+
|
|
5857
6543
|
When user has no materials:
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
6544
|
+
|
|
6545
|
+
1. **Ask basic questions**:
|
|
6546
|
+
- "What is this presentation about?"
|
|
6547
|
+
- "Who is the audience?"
|
|
6548
|
+
- "How long is the presentation?"
|
|
6549
|
+
2. **Ask purpose-specific questions** (proposal, report, introduction, etc.)
|
|
6550
|
+
3. **Collect data and examples** user can provide
|
|
6551
|
+
4. **Propose slide structure** for approval
|
|
6552
|
+
5. **Incorporate feedback**
|
|
6553
|
+
6. **Configure \`sources/\` directory** from conversation
|
|
6554
|
+
7. Proceed to slide creation
|
|
5863
6555
|
|
|
5864
6556
|
## Slide Creation Flow
|
|
5865
6557
|
|
|
@@ -5908,6 +6600,221 @@ After user provides images:
|
|
|
5908
6600
|
|
|
5909
6601
|
### Phase 4: Iteration
|
|
5910
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 |
|
|
6666
|
+
`;
|
|
6667
|
+
}
|
|
6668
|
+
|
|
6669
|
+
// src/cli/templates/ai/references/skill-references.ts
|
|
6670
|
+
function generateReferenceSkillMd() {
|
|
6671
|
+
return `## Reference Management Skill
|
|
6672
|
+
|
|
6673
|
+
### When to Invoke
|
|
6674
|
+
|
|
6675
|
+
Use this skill when:
|
|
6676
|
+
- Creating academic or research presentations
|
|
6677
|
+
- User mentions needing citations or references
|
|
6678
|
+
- Scenario contains statistical claims or research findings
|
|
6679
|
+
- User provides literature (URL, PDF, DOI, PMID)
|
|
6680
|
+
|
|
6681
|
+
### Citation Requirement Analysis
|
|
6682
|
+
|
|
6683
|
+
Analyze scenario/script for statements requiring citations:
|
|
6684
|
+
|
|
6685
|
+
| Statement Type | Requirement | Example |
|
|
6686
|
+
|---------------|-------------|---------|
|
|
6687
|
+
| Statistical claims | Required | "Accuracy exceeds 90%" |
|
|
6688
|
+
| Research findings | Required | "Studies show that..." |
|
|
6689
|
+
| Methodology references | Required | "Using the XYZ method" |
|
|
6690
|
+
| Comparative claims | Recommended | "Better than conventional" |
|
|
6691
|
+
| Historical facts | Recommended | "First introduced in 2020" |
|
|
6692
|
+
| General knowledge | Not required | "AI is widely used" |
|
|
6693
|
+
|
|
6694
|
+
### Workflow
|
|
6695
|
+
|
|
6696
|
+
#### Phase 1: Analyze Content
|
|
6697
|
+
1. Read scenario/script thoroughly
|
|
6698
|
+
2. Identify statements requiring citations
|
|
6699
|
+
3. Categorize as Required or Recommended
|
|
6700
|
+
4. Note the slide number and exact statement
|
|
6701
|
+
|
|
6702
|
+
#### Phase 2: Search Existing Library
|
|
6703
|
+
\`\`\`bash
|
|
6704
|
+
# List all references
|
|
6705
|
+
ref list --format json
|
|
6706
|
+
|
|
6707
|
+
# Search by keyword
|
|
6708
|
+
ref search "diagnostic accuracy" --format json
|
|
6709
|
+
\`\`\`
|
|
6710
|
+
|
|
6711
|
+
#### Phase 3: Match or Request
|
|
6712
|
+
|
|
6713
|
+
**If found in library:**
|
|
6714
|
+
- Confirm relevance with user
|
|
6715
|
+
- Insert \`[@id]\` citation in YAML
|
|
6716
|
+
|
|
6717
|
+
**If not found:**
|
|
6718
|
+
- Present clear request to user
|
|
6719
|
+
- Specify what type of source is needed
|
|
6720
|
+
- Provide suggested search terms
|
|
6721
|
+
|
|
6722
|
+
#### Phase 4: Add New References
|
|
6723
|
+
|
|
6724
|
+
From user-provided input:
|
|
6725
|
+
|
|
6726
|
+
\`\`\`bash
|
|
6727
|
+
# From PMID
|
|
6728
|
+
ref add pmid:38941256
|
|
6729
|
+
|
|
6730
|
+
# From DOI
|
|
6731
|
+
ref add "10.1038/s41591-024-xxxxx"
|
|
6732
|
+
|
|
6733
|
+
# From ISBN
|
|
6734
|
+
ref add "ISBN:978-4-00-000000-0"
|
|
6735
|
+
|
|
6736
|
+
# From BibTeX file
|
|
6737
|
+
ref add paper.bib
|
|
6738
|
+
\`\`\`
|
|
6739
|
+
|
|
6740
|
+
#### Phase 5: Insert Citations
|
|
6741
|
+
|
|
6742
|
+
Update presentation.yaml:
|
|
6743
|
+
\`\`\`yaml
|
|
6744
|
+
items:
|
|
6745
|
+
- "This claim is supported [@smith2024]"
|
|
6746
|
+
\`\`\`
|
|
6747
|
+
|
|
6748
|
+
### Extracting from Non-Standard Input
|
|
6749
|
+
|
|
6750
|
+
#### URL Patterns
|
|
6751
|
+
- PubMed: Extract PMID from \`pubmed.ncbi.nlm.nih.gov/XXXXXXXX\`
|
|
6752
|
+
- DOI: Extract from \`doi.org/10.XXXX/XXXXX\`
|
|
6753
|
+
- Publisher sites: Fetch page, extract DOI from metadata
|
|
6754
|
+
|
|
6755
|
+
#### PDF Files
|
|
6756
|
+
1. Read PDF file
|
|
6757
|
+
2. Extract DOI from first page or metadata
|
|
6758
|
+
3. If not found, extract title and search databases
|
|
6759
|
+
|
|
6760
|
+
#### Free Text
|
|
6761
|
+
1. Parse author, year, journal information
|
|
6762
|
+
2. Search PubMed/CrossRef
|
|
6763
|
+
3. Present candidates for user confirmation
|
|
6764
|
+
|
|
6765
|
+
### User Communication Templates
|
|
6766
|
+
|
|
6767
|
+
**Analyzing content:**
|
|
6768
|
+
\`\`\`
|
|
6769
|
+
I've analyzed your scenario and identified citation needs:
|
|
6770
|
+
|
|
6771
|
+
Required Citations (N)
|
|
6772
|
+
----------------------
|
|
6773
|
+
1. Slide X: '[statement]'
|
|
6774
|
+
-> Needs: [type of source]
|
|
6775
|
+
|
|
6776
|
+
Recommended Citations (M)
|
|
6777
|
+
-------------------------
|
|
6778
|
+
...
|
|
6779
|
+
|
|
6780
|
+
Let me check your reference library...
|
|
6781
|
+
\`\`\`
|
|
6782
|
+
|
|
6783
|
+
**Requesting references:**
|
|
6784
|
+
\`\`\`
|
|
6785
|
+
I need your help finding references:
|
|
6786
|
+
|
|
6787
|
+
[REQUIRED] Reference 1: [Topic]
|
|
6788
|
+
-------------------------------
|
|
6789
|
+
Purpose: Support claim '[statement]' on Slide X
|
|
6790
|
+
|
|
6791
|
+
Ideal source type:
|
|
6792
|
+
- [type1]
|
|
6793
|
+
- [type2]
|
|
6794
|
+
|
|
6795
|
+
Suggested search terms:
|
|
6796
|
+
- [term1]
|
|
6797
|
+
- [term2]
|
|
6798
|
+
|
|
6799
|
+
How to provide:
|
|
6800
|
+
A) DOI or PMID (e.g., 'PMID: 38941256')
|
|
6801
|
+
B) URL (PubMed, journal site, etc.)
|
|
6802
|
+
C) PDF file
|
|
6803
|
+
D) Manual citation details
|
|
6804
|
+
\`\`\`
|
|
6805
|
+
|
|
6806
|
+
**Confirming addition:**
|
|
6807
|
+
\`\`\`
|
|
6808
|
+
Reference added successfully:
|
|
6809
|
+
-----------------------------
|
|
6810
|
+
Citation key: [@id]
|
|
6811
|
+
Authors: ...
|
|
6812
|
+
Title: '...'
|
|
6813
|
+
Journal: ...
|
|
6814
|
+
Year: XXXX
|
|
6815
|
+
|
|
6816
|
+
I'll use this for Slide X.
|
|
6817
|
+
\`\`\`
|
|
5911
6818
|
`;
|
|
5912
6819
|
}
|
|
5913
6820
|
|
|
@@ -6032,18 +6939,128 @@ slide-gen screenshot $ARGUMENTS
|
|
|
6032
6939
|
|
|
6033
6940
|
If no argument provided:
|
|
6034
6941
|
\`\`\`bash
|
|
6035
|
-
slide-gen screenshot presentation.yaml
|
|
6942
|
+
slide-gen screenshot presentation.yaml --format ai
|
|
6036
6943
|
\`\`\`
|
|
6037
6944
|
|
|
6038
6945
|
## Options
|
|
6039
6946
|
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
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
|
|
6043
6958
|
|
|
6044
|
-
|
|
6959
|
+
Use \`--format ai\` for token-efficient screenshots:
|
|
6960
|
+
- 640px width (75% token reduction)
|
|
6961
|
+
- JPEG format
|
|
6962
|
+
- Shows estimated token consumption
|
|
6045
6963
|
|
|
6046
|
-
|
|
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
|
|
6976
|
+
|
|
6977
|
+
| Format | Width | Est. Tokens/slide |
|
|
6978
|
+
|--------|-------|-------------------|
|
|
6979
|
+
| png/jpeg | 1280 | ~1,229 |
|
|
6980
|
+
| ai | 640 | ~308 (~75% reduction) |
|
|
6981
|
+
|
|
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
|
+
\`\`\`
|
|
6047
7064
|
`;
|
|
6048
7065
|
}
|
|
6049
7066
|
|
|
@@ -6070,9 +7087,75 @@ function generateSlideThemeCommand() {
|
|
|
6070
7087
|
`;
|
|
6071
7088
|
}
|
|
6072
7089
|
|
|
7090
|
+
// src/cli/templates/ai/commands/slide-references.ts
|
|
7091
|
+
function generateSlideReferencesCommand() {
|
|
7092
|
+
return `Manage references and citations for the presentation.
|
|
7093
|
+
|
|
7094
|
+
## Available Actions
|
|
7095
|
+
|
|
7096
|
+
### 1. Analyze - Find citation needs
|
|
7097
|
+
Analyze the scenario/content for statements that need citations.
|
|
7098
|
+
|
|
7099
|
+
### 2. Search - Find in library
|
|
7100
|
+
Search existing reference-manager library for relevant papers.
|
|
7101
|
+
|
|
7102
|
+
### 3. Add - Add new reference
|
|
7103
|
+
Add a reference from PMID, DOI, URL, or file.
|
|
7104
|
+
|
|
7105
|
+
### 4. List - Show all references
|
|
7106
|
+
List all references currently in the library.
|
|
7107
|
+
|
|
7108
|
+
## Usage
|
|
7109
|
+
|
|
7110
|
+
### Analyze scenario for citation needs:
|
|
7111
|
+
\`\`\`bash
|
|
7112
|
+
# First, read the presentation
|
|
7113
|
+
cat presentation.yaml
|
|
7114
|
+
|
|
7115
|
+
# Then analyze content for citation requirements
|
|
7116
|
+
\`\`\`
|
|
7117
|
+
Report statements needing citations with slide numbers.
|
|
7118
|
+
|
|
7119
|
+
### Search library:
|
|
7120
|
+
\`\`\`bash
|
|
7121
|
+
ref search "keyword" --format json
|
|
7122
|
+
ref list --format json
|
|
7123
|
+
\`\`\`
|
|
7124
|
+
|
|
7125
|
+
### Add reference:
|
|
7126
|
+
\`\`\`bash
|
|
7127
|
+
# From PMID
|
|
7128
|
+
ref add pmid:38941256
|
|
7129
|
+
|
|
7130
|
+
# From DOI
|
|
7131
|
+
ref add "10.1038/s41591-024-xxxxx"
|
|
7132
|
+
|
|
7133
|
+
# From URL (extract identifier first)
|
|
7134
|
+
# PubMed URL -> extract PMID
|
|
7135
|
+
# DOI URL -> extract DOI
|
|
7136
|
+
|
|
7137
|
+
# From file
|
|
7138
|
+
ref add paper.bib
|
|
7139
|
+
ref add export.ris
|
|
7140
|
+
\`\`\`
|
|
7141
|
+
|
|
7142
|
+
### Validate citations:
|
|
7143
|
+
\`\`\`bash
|
|
7144
|
+
slide-gen validate presentation.yaml
|
|
7145
|
+
\`\`\`
|
|
7146
|
+
|
|
7147
|
+
## Notes
|
|
7148
|
+
|
|
7149
|
+
- Always check library before requesting new references
|
|
7150
|
+
- Extract PMID/DOI from URLs before adding
|
|
7151
|
+
- Report missing citations with suggested search terms
|
|
7152
|
+
- Update presentation.yaml with [@id] format
|
|
7153
|
+
`;
|
|
7154
|
+
}
|
|
7155
|
+
|
|
6073
7156
|
// src/cli/commands/init.ts
|
|
6074
7157
|
function getPackageRoot() {
|
|
6075
|
-
const __dirname =
|
|
7158
|
+
const __dirname = dirname7(fileURLToPath(import.meta.url));
|
|
6076
7159
|
if (__dirname.includes(`${sep}src${sep}`) || __dirname.includes("/src/")) {
|
|
6077
7160
|
return join16(__dirname, "..", "..", "..");
|
|
6078
7161
|
}
|
|
@@ -6085,7 +7168,7 @@ function createInitCommand() {
|
|
|
6085
7168
|
}
|
|
6086
7169
|
async function executeInit(directory, options) {
|
|
6087
7170
|
const spinner = ora3();
|
|
6088
|
-
const targetDir =
|
|
7171
|
+
const targetDir = resolve6(directory);
|
|
6089
7172
|
const includeExamples = options.examples !== false;
|
|
6090
7173
|
const includeAiConfig = options.aiConfig !== false;
|
|
6091
7174
|
const includeSources = options.sources !== false;
|
|
@@ -6134,11 +7217,11 @@ async function executeInit(directory, options) {
|
|
|
6134
7217
|
await sourcesManager.init({
|
|
6135
7218
|
name: "Untitled Project",
|
|
6136
7219
|
setup_pattern: options.fromDirectory ? "A" : void 0,
|
|
6137
|
-
original_source: options.fromDirectory ?
|
|
7220
|
+
original_source: options.fromDirectory ? resolve6(options.fromDirectory) : void 0
|
|
6138
7221
|
});
|
|
6139
7222
|
if (options.fromDirectory) {
|
|
6140
7223
|
const importer = new SourceImporter(targetDir, sourcesManager);
|
|
6141
|
-
const result = await importer.importDirectory(
|
|
7224
|
+
const result = await importer.importDirectory(resolve6(options.fromDirectory), {
|
|
6142
7225
|
recursive: true
|
|
6143
7226
|
});
|
|
6144
7227
|
sourcesImported = result.imported;
|
|
@@ -6159,6 +7242,7 @@ async function executeInit(directory, options) {
|
|
|
6159
7242
|
}
|
|
6160
7243
|
if (includeAiConfig) {
|
|
6161
7244
|
console.log(` ${chalk6.cyan(".skills/")} - AgentSkills configuration`);
|
|
7245
|
+
console.log(` ${chalk6.cyan(".claude/skills/")} - Claude Code skills`);
|
|
6162
7246
|
console.log(` ${chalk6.cyan("CLAUDE.md")} - Claude Code configuration`);
|
|
6163
7247
|
console.log(` ${chalk6.cyan("AGENTS.md")} - OpenCode configuration`);
|
|
6164
7248
|
console.log(` ${chalk6.cyan(".cursorrules")} - Cursor configuration`);
|
|
@@ -6243,16 +7327,16 @@ async function promptMarpInstallChoice() {
|
|
|
6243
7327
|
console.log(` ${chalk6.cyan("2)")} Local install ${chalk6.dim("(creates package.json)")}`);
|
|
6244
7328
|
console.log(` ${chalk6.cyan("3)")} Skip ${chalk6.dim("(I'll install it later)")}`);
|
|
6245
7329
|
console.log("");
|
|
6246
|
-
return new Promise((
|
|
7330
|
+
return new Promise((resolve8) => {
|
|
6247
7331
|
rl.question("Choice [1]: ", (answer) => {
|
|
6248
7332
|
rl.close();
|
|
6249
7333
|
const normalized = answer.trim();
|
|
6250
7334
|
if (normalized === "" || normalized === "1") {
|
|
6251
|
-
|
|
7335
|
+
resolve8("global");
|
|
6252
7336
|
} else if (normalized === "2") {
|
|
6253
|
-
|
|
7337
|
+
resolve8("local");
|
|
6254
7338
|
} else {
|
|
6255
|
-
|
|
7339
|
+
resolve8("skip");
|
|
6256
7340
|
}
|
|
6257
7341
|
});
|
|
6258
7342
|
});
|
|
@@ -6366,27 +7450,37 @@ async function showMarpCliInfo(targetDir) {
|
|
|
6366
7450
|
async function generateAiConfig(targetDir) {
|
|
6367
7451
|
await mkdir7(join16(targetDir, ".skills", "slide-assistant", "references"), { recursive: true });
|
|
6368
7452
|
await mkdir7(join16(targetDir, ".skills", "slide-assistant", "scripts"), { recursive: true });
|
|
7453
|
+
await mkdir7(join16(targetDir, ".claude", "skills", "slide-assistant", "references"), { recursive: true });
|
|
6369
7454
|
await mkdir7(join16(targetDir, ".claude", "commands"), { recursive: true });
|
|
6370
7455
|
await mkdir7(join16(targetDir, ".opencode", "agent"), { recursive: true });
|
|
6371
|
-
|
|
6372
|
-
join16(targetDir, ".skills", "slide-assistant"
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
join16(
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
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
|
+
}
|
|
6383
7475
|
await writeFileIfNotExists(join16(targetDir, "CLAUDE.md"), generateClaudeMd());
|
|
6384
7476
|
const commandGenerators = {
|
|
6385
7477
|
"slide-create": generateSlideCreateCommand,
|
|
6386
7478
|
"slide-validate": generateSlideValidateCommand,
|
|
6387
7479
|
"slide-preview": generateSlidePreviewCommand,
|
|
6388
7480
|
"slide-screenshot": generateSlideScreenshotCommand,
|
|
6389
|
-
"slide-
|
|
7481
|
+
"slide-review": generateSlideReviewCommand,
|
|
7482
|
+
"slide-theme": generateSlideThemeCommand,
|
|
7483
|
+
"slide-references": generateSlideReferencesCommand
|
|
6390
7484
|
};
|
|
6391
7485
|
for (const [name, generator] of Object.entries(commandGenerators)) {
|
|
6392
7486
|
await writeFileIfNotExists(
|
|
@@ -6497,7 +7591,7 @@ slides:
|
|
|
6497
7591
|
// src/cli/commands/watch.ts
|
|
6498
7592
|
import { Command as Command7 } from "commander";
|
|
6499
7593
|
import { access as access11 } from "fs/promises";
|
|
6500
|
-
import { basename as basename8, dirname as
|
|
7594
|
+
import { basename as basename8, dirname as dirname8, join as join17 } from "path";
|
|
6501
7595
|
import chalk7 from "chalk";
|
|
6502
7596
|
import { watch as chokidarWatch2 } from "chokidar";
|
|
6503
7597
|
var WatchState = class {
|
|
@@ -6530,7 +7624,7 @@ var WatchState = class {
|
|
|
6530
7624
|
}
|
|
6531
7625
|
};
|
|
6532
7626
|
function getDefaultOutputPath2(inputPath) {
|
|
6533
|
-
const dir =
|
|
7627
|
+
const dir = dirname8(inputPath);
|
|
6534
7628
|
const base = basename8(inputPath, ".yaml");
|
|
6535
7629
|
return join17(dir, `${base}.md`);
|
|
6536
7630
|
}
|
|
@@ -6592,7 +7686,7 @@ async function executeWatch(inputPath, options) {
|
|
|
6592
7686
|
const configLoader = new ConfigLoader();
|
|
6593
7687
|
let configPath = options.config;
|
|
6594
7688
|
if (!configPath) {
|
|
6595
|
-
configPath = await configLoader.findConfig(
|
|
7689
|
+
configPath = await configLoader.findConfig(dirname8(inputPath));
|
|
6596
7690
|
}
|
|
6597
7691
|
const config = await configLoader.load(configPath);
|
|
6598
7692
|
const pipeline = new Pipeline(config);
|
|
@@ -6661,9 +7755,9 @@ async function executeWatch(inputPath, options) {
|
|
|
6661
7755
|
};
|
|
6662
7756
|
process.on("SIGINT", signalHandler);
|
|
6663
7757
|
process.on("SIGTERM", signalHandler);
|
|
6664
|
-
await new Promise((
|
|
7758
|
+
await new Promise((resolve8) => {
|
|
6665
7759
|
if (options.signal) {
|
|
6666
|
-
options.signal.addEventListener("abort", () =>
|
|
7760
|
+
options.signal.addEventListener("abort", () => resolve8());
|
|
6667
7761
|
}
|
|
6668
7762
|
});
|
|
6669
7763
|
return {
|
|
@@ -6676,7 +7770,7 @@ async function executeWatch(inputPath, options) {
|
|
|
6676
7770
|
// src/cli/commands/images.ts
|
|
6677
7771
|
import { Command as Command8 } from "commander";
|
|
6678
7772
|
import { readFile as readFile15, stat as stat2, mkdir as mkdir8 } from "fs/promises";
|
|
6679
|
-
import { dirname as
|
|
7773
|
+
import { dirname as dirname9, basename as basename9, join as join18 } from "path";
|
|
6680
7774
|
import chalk8 from "chalk";
|
|
6681
7775
|
import { stringify as stringifyYaml3 } from "yaml";
|
|
6682
7776
|
function createImagesCommand() {
|
|
@@ -6699,7 +7793,7 @@ function createImagesRequestCommand() {
|
|
|
6699
7793
|
async function executeImagesStatus(inputPath) {
|
|
6700
7794
|
try {
|
|
6701
7795
|
const slides = await loadPresentation(inputPath);
|
|
6702
|
-
const baseDir =
|
|
7796
|
+
const baseDir = dirname9(inputPath);
|
|
6703
7797
|
const validator = new ImageValidator(baseDir);
|
|
6704
7798
|
const imageRefs = validator.extractImageReferences(slides);
|
|
6705
7799
|
if (imageRefs.length === 0) {
|
|
@@ -6799,7 +7893,7 @@ async function outputImageStatus(stats, imageRefs, _validator, baseDir) {
|
|
|
6799
7893
|
async function executeImagesRequest(inputPath, options) {
|
|
6800
7894
|
try {
|
|
6801
7895
|
const slides = await loadPresentation(inputPath);
|
|
6802
|
-
const baseDir =
|
|
7896
|
+
const baseDir = dirname9(inputPath);
|
|
6803
7897
|
const validator = new ImageValidator(baseDir);
|
|
6804
7898
|
const missingImages = await validator.getMissingImages(slides);
|
|
6805
7899
|
if (missingImages.length === 0) {
|
|
@@ -6999,7 +8093,7 @@ async function processSingleFile(filePath, options) {
|
|
|
6999
8093
|
return;
|
|
7000
8094
|
}
|
|
7001
8095
|
const processor = new ImageProcessor();
|
|
7002
|
-
const dir =
|
|
8096
|
+
const dir = dirname9(filePath);
|
|
7003
8097
|
const filename = basename9(filePath);
|
|
7004
8098
|
const outputDir = join18(dir, options.output);
|
|
7005
8099
|
await mkdir8(outputDir, { recursive: true });
|
|
@@ -7097,10 +8191,11 @@ function parseBlurSpec(spec) {
|
|
|
7097
8191
|
|
|
7098
8192
|
// src/cli/commands/screenshot.ts
|
|
7099
8193
|
import { Command as Command9 } from "commander";
|
|
7100
|
-
import { access as access12, mkdir as mkdir9, readdir as readdir7, unlink as
|
|
7101
|
-
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";
|
|
7102
8196
|
import chalk9 from "chalk";
|
|
7103
8197
|
import ora4 from "ora";
|
|
8198
|
+
import sharp2 from "sharp";
|
|
7104
8199
|
async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
7105
8200
|
const slideStr = slideNumber.toString().padStart(3, "0");
|
|
7106
8201
|
const targetFileName = `${baseName}.${slideStr}.${format}`;
|
|
@@ -7119,7 +8214,7 @@ async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
|
7119
8214
|
);
|
|
7120
8215
|
for (const file of slideFiles) {
|
|
7121
8216
|
if (file !== targetFileName) {
|
|
7122
|
-
await
|
|
8217
|
+
await unlink2(join19(outputDir, file));
|
|
7123
8218
|
}
|
|
7124
8219
|
}
|
|
7125
8220
|
return {
|
|
@@ -7130,19 +8225,120 @@ async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
|
7130
8225
|
function checkMarpCliAvailable2(projectDir) {
|
|
7131
8226
|
return isMarpAvailable(projectDir);
|
|
7132
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
|
+
}
|
|
7133
8323
|
function buildMarpCommandArgs(markdownPath, outputDir, options) {
|
|
7134
|
-
const
|
|
7135
|
-
const
|
|
7136
|
-
|
|
7137
|
-
|
|
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;
|
|
7138
8330
|
args.push("--image-scale", String(scale));
|
|
7139
8331
|
}
|
|
8332
|
+
if (imageFormat === "jpeg") {
|
|
8333
|
+
const quality = options.quality || 80;
|
|
8334
|
+
args.push("--jpeg-quality", String(quality));
|
|
8335
|
+
}
|
|
7140
8336
|
args.push("-o", outputDir);
|
|
7141
8337
|
args.push(markdownPath);
|
|
7142
8338
|
return args;
|
|
7143
8339
|
}
|
|
7144
8340
|
function createScreenshotCommand() {
|
|
7145
|
-
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) => {
|
|
7146
8342
|
await executeScreenshot(input, options);
|
|
7147
8343
|
});
|
|
7148
8344
|
}
|
|
@@ -7167,7 +8363,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
7167
8363
|
return { success: false, errors };
|
|
7168
8364
|
}
|
|
7169
8365
|
spinner?.start("Checking for Marp CLI...");
|
|
7170
|
-
if (!checkMarpCliAvailable2(
|
|
8366
|
+
if (!checkMarpCliAvailable2(dirname10(inputPath))) {
|
|
7171
8367
|
spinner?.fail("Marp CLI not found");
|
|
7172
8368
|
const message = "Marp CLI not found. Install it with: npm install -D @marp-team/marp-cli";
|
|
7173
8369
|
console.error(chalk9.red(`Error: ${message}`));
|
|
@@ -7189,7 +8385,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
7189
8385
|
const configLoader = new ConfigLoader();
|
|
7190
8386
|
let configPath = options.config;
|
|
7191
8387
|
if (!configPath) {
|
|
7192
|
-
configPath = await configLoader.findConfig(
|
|
8388
|
+
configPath = await configLoader.findConfig(dirname10(inputPath));
|
|
7193
8389
|
}
|
|
7194
8390
|
const config = await configLoader.load(configPath);
|
|
7195
8391
|
spinner?.succeed("Configuration loaded");
|
|
@@ -7210,7 +8406,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
7210
8406
|
const tempMdPath = inputPath.replace(/\.ya?ml$/i, ".md");
|
|
7211
8407
|
const cleanupTempFile = async () => {
|
|
7212
8408
|
try {
|
|
7213
|
-
await
|
|
8409
|
+
await unlink2(tempMdPath);
|
|
7214
8410
|
} catch {
|
|
7215
8411
|
}
|
|
7216
8412
|
};
|
|
@@ -7233,7 +8429,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
7233
8429
|
}
|
|
7234
8430
|
try {
|
|
7235
8431
|
runMarp(marpArgs, {
|
|
7236
|
-
projectDir:
|
|
8432
|
+
projectDir: dirname10(inputPath),
|
|
7237
8433
|
stdio: options.verbose ? "inherit" : "pipe"
|
|
7238
8434
|
});
|
|
7239
8435
|
spinner?.succeed(`Screenshots saved to ${outputDir}`);
|
|
@@ -7246,15 +8442,18 @@ async function executeScreenshot(inputPath, options) {
|
|
|
7246
8442
|
await cleanupTempFile();
|
|
7247
8443
|
return { success: false, errors };
|
|
7248
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);
|
|
7249
8449
|
if (options.slide !== void 0) {
|
|
7250
8450
|
spinner?.start(`Filtering to slide ${options.slide}...`);
|
|
7251
|
-
const
|
|
7252
|
-
const format = options.format || "png";
|
|
8451
|
+
const mdBaseName2 = basename10(tempMdPath, ".md");
|
|
7253
8452
|
const filterResult = await filterToSpecificSlide(
|
|
7254
8453
|
outputDir,
|
|
7255
|
-
|
|
8454
|
+
mdBaseName2,
|
|
7256
8455
|
options.slide,
|
|
7257
|
-
|
|
8456
|
+
actualFormat
|
|
7258
8457
|
);
|
|
7259
8458
|
if (!filterResult.success) {
|
|
7260
8459
|
spinner?.fail("Failed to filter slides");
|
|
@@ -7266,20 +8465,71 @@ async function executeScreenshot(inputPath, options) {
|
|
|
7266
8465
|
}
|
|
7267
8466
|
spinner?.succeed(`Kept slide ${options.slide}: ${filterResult.keptFile}`);
|
|
7268
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
|
+
}
|
|
7269
8502
|
await cleanupTempFile();
|
|
7270
8503
|
console.log("");
|
|
7271
|
-
|
|
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
|
+
}
|
|
7272
8521
|
return {
|
|
7273
8522
|
success: true,
|
|
7274
8523
|
errors,
|
|
7275
|
-
outputDir
|
|
8524
|
+
outputDir,
|
|
8525
|
+
files: generatedFiles
|
|
7276
8526
|
};
|
|
7277
8527
|
}
|
|
7278
8528
|
|
|
7279
8529
|
// src/cli/commands/sources.ts
|
|
7280
8530
|
import { Command as Command10 } from "commander";
|
|
7281
8531
|
import { access as access13, stat as stat3 } from "fs/promises";
|
|
7282
|
-
import { resolve as
|
|
8532
|
+
import { resolve as resolve7 } from "path";
|
|
7283
8533
|
import chalk10 from "chalk";
|
|
7284
8534
|
import ora5 from "ora";
|
|
7285
8535
|
function createSourcesCommand() {
|
|
@@ -7328,7 +8578,7 @@ function createSourcesSyncCommand() {
|
|
|
7328
8578
|
});
|
|
7329
8579
|
}
|
|
7330
8580
|
async function executeSourcesInit(projectDir, options) {
|
|
7331
|
-
const resolvedDir =
|
|
8581
|
+
const resolvedDir = resolve7(projectDir);
|
|
7332
8582
|
const spinner = ora5("Initializing sources...").start();
|
|
7333
8583
|
try {
|
|
7334
8584
|
const manager = new SourcesManager(resolvedDir);
|
|
@@ -7344,10 +8594,10 @@ async function executeSourcesInit(projectDir, options) {
|
|
|
7344
8594
|
let originalSource;
|
|
7345
8595
|
if (options.fromDirectory) {
|
|
7346
8596
|
setupPattern = "A";
|
|
7347
|
-
originalSource =
|
|
8597
|
+
originalSource = resolve7(options.fromDirectory);
|
|
7348
8598
|
} else if (options.fromFile) {
|
|
7349
8599
|
setupPattern = "B";
|
|
7350
|
-
originalSource =
|
|
8600
|
+
originalSource = resolve7(options.fromFile);
|
|
7351
8601
|
}
|
|
7352
8602
|
await manager.init({
|
|
7353
8603
|
name: projectName,
|
|
@@ -7358,14 +8608,14 @@ async function executeSourcesInit(projectDir, options) {
|
|
|
7358
8608
|
if (options.fromDirectory) {
|
|
7359
8609
|
const importer = new SourceImporter(resolvedDir, manager);
|
|
7360
8610
|
const result = await importer.importDirectory(
|
|
7361
|
-
|
|
8611
|
+
resolve7(options.fromDirectory),
|
|
7362
8612
|
{ recursive: true }
|
|
7363
8613
|
);
|
|
7364
8614
|
filesImported = result.imported;
|
|
7365
8615
|
}
|
|
7366
8616
|
if (options.fromFile) {
|
|
7367
8617
|
const importer = new SourceImporter(resolvedDir, manager);
|
|
7368
|
-
await importer.importFile(
|
|
8618
|
+
await importer.importFile(resolve7(options.fromFile), {
|
|
7369
8619
|
type: "scenario"
|
|
7370
8620
|
});
|
|
7371
8621
|
filesImported = 1;
|
|
@@ -7390,8 +8640,8 @@ async function executeSourcesInit(projectDir, options) {
|
|
|
7390
8640
|
}
|
|
7391
8641
|
}
|
|
7392
8642
|
async function executeSourcesImport(projectDir, sourcePath, options) {
|
|
7393
|
-
const resolvedDir =
|
|
7394
|
-
const resolvedSource =
|
|
8643
|
+
const resolvedDir = resolve7(projectDir);
|
|
8644
|
+
const resolvedSource = resolve7(sourcePath);
|
|
7395
8645
|
const spinner = ora5("Importing files...").start();
|
|
7396
8646
|
try {
|
|
7397
8647
|
const manager = new SourcesManager(resolvedDir);
|
|
@@ -7450,7 +8700,7 @@ async function executeSourcesImport(projectDir, sourcePath, options) {
|
|
|
7450
8700
|
}
|
|
7451
8701
|
}
|
|
7452
8702
|
async function executeSourcesStatus(projectDir, _options) {
|
|
7453
|
-
const resolvedDir =
|
|
8703
|
+
const resolvedDir = resolve7(projectDir);
|
|
7454
8704
|
try {
|
|
7455
8705
|
const manager = new SourcesManager(resolvedDir);
|
|
7456
8706
|
if (!await manager.exists()) {
|
|
@@ -7499,6 +8749,26 @@ async function executeSourcesStatus(projectDir, _options) {
|
|
|
7499
8749
|
output += "\n";
|
|
7500
8750
|
}
|
|
7501
8751
|
}
|
|
8752
|
+
const refs = await manager.getReferences();
|
|
8753
|
+
if (refs.items.length > 0) {
|
|
8754
|
+
output += "\n";
|
|
8755
|
+
output += chalk10.cyan("References:\n");
|
|
8756
|
+
output += ` Required: ${refs.status?.required ?? 0}
|
|
8757
|
+
`;
|
|
8758
|
+
output += ` Found: ${refs.status?.found ?? 0}
|
|
8759
|
+
`;
|
|
8760
|
+
output += ` Pending: ${refs.status?.pending ?? 0}
|
|
8761
|
+
`;
|
|
8762
|
+
const pendingRefs = refs.items.filter((i) => i.status === "pending");
|
|
8763
|
+
if (pendingRefs.length > 0) {
|
|
8764
|
+
output += "\n";
|
|
8765
|
+
output += chalk10.yellow(" \u26A0 Pending references:\n");
|
|
8766
|
+
for (const ref of pendingRefs) {
|
|
8767
|
+
output += ` - ${ref.id} (Slide ${ref.slide}): ${ref.purpose}
|
|
8768
|
+
`;
|
|
8769
|
+
}
|
|
8770
|
+
}
|
|
8771
|
+
}
|
|
7502
8772
|
if (data.project.updated) {
|
|
7503
8773
|
output += "\n";
|
|
7504
8774
|
output += chalk10.gray(`Last updated: ${data.project.updated}
|
|
@@ -7517,7 +8787,7 @@ async function executeSourcesStatus(projectDir, _options) {
|
|
|
7517
8787
|
}
|
|
7518
8788
|
}
|
|
7519
8789
|
async function executeSourcesSync(projectDir, options) {
|
|
7520
|
-
const resolvedDir =
|
|
8790
|
+
const resolvedDir = resolve7(projectDir);
|
|
7521
8791
|
try {
|
|
7522
8792
|
const manager = new SourcesManager(resolvedDir);
|
|
7523
8793
|
if (!await manager.exists()) {
|