my-patina 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +232 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -115,6 +115,13 @@ var linkedinModule = {
|
|
|
115
115
|
delete updated.linkedin;
|
|
116
116
|
return updated;
|
|
117
117
|
},
|
|
118
|
+
launchTasks: [
|
|
119
|
+
{
|
|
120
|
+
id: "open-drafts",
|
|
121
|
+
label: "Show open LinkedIn drafts",
|
|
122
|
+
template: "- Show any LinkedIn section drafts in `{{CONTENT_DIR}}/linkedin/` that have content to review."
|
|
123
|
+
}
|
|
124
|
+
],
|
|
118
125
|
readmeBlock(vars) {
|
|
119
126
|
return [
|
|
120
127
|
"## LinkedIn module",
|
|
@@ -180,6 +187,13 @@ var resumeModule = {
|
|
|
180
187
|
render(tpl(`modules/resume/graph/${file}`), vars)
|
|
181
188
|
]);
|
|
182
189
|
},
|
|
190
|
+
launchTasks: [
|
|
191
|
+
{
|
|
192
|
+
id: "resume-stale-check",
|
|
193
|
+
label: "Flag resume if it may be out of date",
|
|
194
|
+
template: "- Compare `{{CONTENT_DIR}}/resume/Resume Working Draft.md` against recent notes and flag if the resume looks out of date."
|
|
195
|
+
}
|
|
196
|
+
],
|
|
183
197
|
readmeBlock(vars) {
|
|
184
198
|
return [
|
|
185
199
|
"## Resume module",
|
|
@@ -470,6 +484,66 @@ function writeState(root, state) {
|
|
|
470
484
|
);
|
|
471
485
|
}
|
|
472
486
|
|
|
487
|
+
// src/launch-tasks.ts
|
|
488
|
+
var BASE_LAUNCH_TASKS = [
|
|
489
|
+
{
|
|
490
|
+
id: "today-focus",
|
|
491
|
+
label: "Ask what to focus on today",
|
|
492
|
+
template: "- Ask the user what they want to focus on today and note it before proceeding."
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
id: "recent-notes",
|
|
496
|
+
label: "Summarise notes changed in the last 7 days",
|
|
497
|
+
template: "- Summarise any notes in `{{CONTENT_DIR}}/notes/` modified in the last 7 days, one line each."
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
id: "open-posts",
|
|
501
|
+
label: "List unfinished posts",
|
|
502
|
+
template: "- List any drafts in `{{CONTENT_DIR}}/posts/` that look unfinished and offer to continue one."
|
|
503
|
+
}
|
|
504
|
+
];
|
|
505
|
+
function availableLaunchTasks(modules) {
|
|
506
|
+
const tasks = BASE_LAUNCH_TASKS.map((t) => ({
|
|
507
|
+
nsId: `base/${t.id}`,
|
|
508
|
+
label: t.label,
|
|
509
|
+
template: t.template,
|
|
510
|
+
source: "base"
|
|
511
|
+
}));
|
|
512
|
+
for (const moduleId of modules) {
|
|
513
|
+
const def = getModule(moduleId);
|
|
514
|
+
if (def?.launchTasks) {
|
|
515
|
+
for (const t of def.launchTasks) {
|
|
516
|
+
tasks.push({
|
|
517
|
+
nsId: `${moduleId}/${t.id}`,
|
|
518
|
+
label: t.label,
|
|
519
|
+
template: t.template,
|
|
520
|
+
source: moduleId
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return tasks;
|
|
526
|
+
}
|
|
527
|
+
function renderLaunchSection(selectedNsIds, modules) {
|
|
528
|
+
if (!selectedNsIds || selectedNsIds.length === 0) return null;
|
|
529
|
+
const avail = availableLaunchTasks(modules);
|
|
530
|
+
const availMap = new Map(avail.map((t) => [t.nsId, t]));
|
|
531
|
+
const resolved = selectedNsIds.map((id) => availMap.get(id)).filter((t) => t !== void 0);
|
|
532
|
+
if (resolved.length === 0) return null;
|
|
533
|
+
return ["## Launch tasks", "", ...resolved.map((t) => t.template)].join("\n");
|
|
534
|
+
}
|
|
535
|
+
function pruneLaunchTasks(selectedNsIds, modules) {
|
|
536
|
+
if (!selectedNsIds || selectedNsIds.length === 0) return [];
|
|
537
|
+
const avail = new Set(availableLaunchTasks(modules).map((t) => t.nsId));
|
|
538
|
+
return selectedNsIds.filter((id) => avail.has(id));
|
|
539
|
+
}
|
|
540
|
+
function launchSelectionError(values) {
|
|
541
|
+
if (values.length > 5) {
|
|
542
|
+
return "You can select at most 5 launch tasks.";
|
|
543
|
+
}
|
|
544
|
+
return void 0;
|
|
545
|
+
}
|
|
546
|
+
|
|
473
547
|
// src/scaffold.ts
|
|
474
548
|
function writeRaw(targetDir, relativePath, content) {
|
|
475
549
|
const full = join5(targetDir, relativePath);
|
|
@@ -562,7 +636,8 @@ async function scaffold(opts) {
|
|
|
562
636
|
editor,
|
|
563
637
|
modules,
|
|
564
638
|
liProfileUrl,
|
|
565
|
-
contentDir
|
|
639
|
+
contentDir,
|
|
640
|
+
launchTasks = []
|
|
566
641
|
} = opts;
|
|
567
642
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
568
643
|
const tempProfile = {
|
|
@@ -576,6 +651,7 @@ async function scaffold(opts) {
|
|
|
576
651
|
modules,
|
|
577
652
|
content_dir: contentDir,
|
|
578
653
|
created: today,
|
|
654
|
+
...launchTasks.length ? { launch_tasks: launchTasks } : {},
|
|
579
655
|
...modules.includes("linkedin") && liProfileUrl ? { linkedin: { profile_url: liProfileUrl } } : {}
|
|
580
656
|
};
|
|
581
657
|
const vars = profileToVars(tempProfile, liProfileUrl);
|
|
@@ -620,6 +696,16 @@ async function scaffold(opts) {
|
|
|
620
696
|
}
|
|
621
697
|
}
|
|
622
698
|
}
|
|
699
|
+
const rawLaunch = renderLaunchSection(launchTasks, modules);
|
|
700
|
+
const expandedLaunch = rawLaunch ? render(rawLaunch, vars) : null;
|
|
701
|
+
if (expandedLaunch) {
|
|
702
|
+
const launchBlock = renderSection("launch", expandedLaunch);
|
|
703
|
+
const result = writeManagedFile(targetDir, "CLAUDE.md", launchBlock, checksums);
|
|
704
|
+
checksums["CLAUDE.md"] = result.checksum;
|
|
705
|
+
for (const s of result.sections ?? []) {
|
|
706
|
+
checksums[`CLAUDE.md:${s.id}`] = s.newChecksum;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
623
709
|
const baseDirs = ["notes", "skills", "posts"];
|
|
624
710
|
for (const dir of baseDirs) {
|
|
625
711
|
touch(targetDir, `${contentDir}/${dir}/.gitkeep`);
|
|
@@ -856,6 +942,58 @@ function label(text2) {
|
|
|
856
942
|
var MULTISELECT_HINT = `
|
|
857
943
|
${chalk.hex("#64748B")("\u2191\u2193 to move \xB7 space to select \xB7 enter to confirm")}`;
|
|
858
944
|
var OPTIONAL_HINT = ` ${chalk.dim.italic("optional, but helps a lot \u2014 hit enter to skip")}`;
|
|
945
|
+
function applyLaunchBlock(cwd, launchTasks, modules, vars, checksums, overwrite) {
|
|
946
|
+
const updated = [];
|
|
947
|
+
const skipped = [];
|
|
948
|
+
const keptSections = [];
|
|
949
|
+
const rawLaunch = renderLaunchSection(launchTasks, modules);
|
|
950
|
+
if (rawLaunch) {
|
|
951
|
+
const block = renderSection("launch", render(rawLaunch, vars));
|
|
952
|
+
const result = writeManagedFile(cwd, "CLAUDE.md", block, checksums, overwrite);
|
|
953
|
+
checksums["CLAUDE.md"] = result.checksum;
|
|
954
|
+
for (const s of result.sections ?? []) {
|
|
955
|
+
const sKey = `CLAUDE.md:${s.id}`;
|
|
956
|
+
if (s.outcome !== "skipped") checksums[sKey] = s.newChecksum;
|
|
957
|
+
else keptSections.push(sKey);
|
|
958
|
+
}
|
|
959
|
+
if (result.outcome === "skipped") skipped.push("CLAUDE.md");
|
|
960
|
+
else updated.push("CLAUDE.md");
|
|
961
|
+
} else {
|
|
962
|
+
const claudePath = join7(cwd, "CLAUDE.md");
|
|
963
|
+
if (existsSync7(claudePath)) {
|
|
964
|
+
const before = readFileSync8(claudePath, "utf8");
|
|
965
|
+
const editedIds = inspectSections("CLAUDE.md", before, checksums);
|
|
966
|
+
if (!editedIds.includes("launch") || overwrite?.has("launch")) {
|
|
967
|
+
const after = removeSection("launch", before);
|
|
968
|
+
if (after !== before) {
|
|
969
|
+
writeFileSync4(claudePath, after, "utf8");
|
|
970
|
+
checksums["CLAUDE.md"] = hashContent(after);
|
|
971
|
+
delete checksums["CLAUDE.md:launch"];
|
|
972
|
+
updated.push("CLAUDE.md");
|
|
973
|
+
}
|
|
974
|
+
} else {
|
|
975
|
+
keptSections.push("CLAUDE.md:launch");
|
|
976
|
+
skipped.push("CLAUDE.md");
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return { updated, skipped, keptSections };
|
|
981
|
+
}
|
|
982
|
+
async function promptLaunchTasks(avail, initial) {
|
|
983
|
+
const selected = await p.multiselect({
|
|
984
|
+
message: `Which tasks should run every time you launch Patina?${MULTISELECT_HINT}`,
|
|
985
|
+
options: avail.map((t) => ({
|
|
986
|
+
value: t.nsId,
|
|
987
|
+
label: t.label,
|
|
988
|
+
hint: chalk.hex("#64748B")(t.source)
|
|
989
|
+
})),
|
|
990
|
+
initialValues: initial,
|
|
991
|
+
required: false,
|
|
992
|
+
validate: launchSelectionError
|
|
993
|
+
});
|
|
994
|
+
if (p.isCancel(selected)) onCancel();
|
|
995
|
+
return Array.isArray(selected) ? selected : [];
|
|
996
|
+
}
|
|
859
997
|
async function main() {
|
|
860
998
|
printBanner();
|
|
861
999
|
const cwd = process.cwd();
|
|
@@ -962,6 +1100,18 @@ async function runInstall(cwd) {
|
|
|
962
1100
|
});
|
|
963
1101
|
liProfileUrl = typeof url === "string" ? url : "";
|
|
964
1102
|
}
|
|
1103
|
+
let launchTasks = [];
|
|
1104
|
+
const availTasks = availableLaunchTasks(modules);
|
|
1105
|
+
if (availTasks.length > 0) {
|
|
1106
|
+
const setupLaunch = await p.confirm({
|
|
1107
|
+
message: "Would you like to set up launch tasks?",
|
|
1108
|
+
initialValue: false
|
|
1109
|
+
});
|
|
1110
|
+
if (p.isCancel(setupLaunch)) onCancel();
|
|
1111
|
+
if (setupLaunch) {
|
|
1112
|
+
launchTasks = await promptLaunchTasks(availTasks, []);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
965
1115
|
const slug = slugify(identity.patinaName);
|
|
966
1116
|
const targetDir = resolve(cwd, slug);
|
|
967
1117
|
const s = p.spinner();
|
|
@@ -984,7 +1134,8 @@ async function runInstall(cwd) {
|
|
|
984
1134
|
editor: setup.editor,
|
|
985
1135
|
modules,
|
|
986
1136
|
liProfileUrl,
|
|
987
|
-
contentDir: "graph"
|
|
1137
|
+
contentDir: "graph",
|
|
1138
|
+
launchTasks
|
|
988
1139
|
});
|
|
989
1140
|
s.stop(chalk.green("Done."));
|
|
990
1141
|
} catch (err) {
|
|
@@ -1030,10 +1181,11 @@ async function runUpdate(cwd) {
|
|
|
1030
1181
|
p.intro(chalk.hex("#94A3B8")(`Found: ${chalk.bold.white(profile.patina_name || "patina")}`));
|
|
1031
1182
|
p.note(
|
|
1032
1183
|
[
|
|
1033
|
-
`${chalk.hex("#64748B")("Name:")}
|
|
1034
|
-
`${chalk.hex("#64748B")("Title:")}
|
|
1035
|
-
`${chalk.hex("#64748B")("Company:")}
|
|
1036
|
-
`${chalk.hex("#64748B")("Modules:")}
|
|
1184
|
+
`${chalk.hex("#64748B")("Name:")} ${profile.name}`,
|
|
1185
|
+
`${chalk.hex("#64748B")("Title:")} ${profile.title || "\u2014"}`,
|
|
1186
|
+
`${chalk.hex("#64748B")("Company:")} ${profile.work?.company_name || "\u2014"}`,
|
|
1187
|
+
`${chalk.hex("#64748B")("Modules:")} ${profile.modules?.join(", ") || "none"}`,
|
|
1188
|
+
`${chalk.hex("#64748B")("Launch tasks:")} ${profile.launch_tasks?.length ?? 0}`
|
|
1037
1189
|
].join("\n"),
|
|
1038
1190
|
label("Current profile")
|
|
1039
1191
|
);
|
|
@@ -1042,6 +1194,7 @@ async function runUpdate(cwd) {
|
|
|
1042
1194
|
options: [
|
|
1043
1195
|
{ value: "profile", label: "Update personal info" },
|
|
1044
1196
|
{ value: "modules", label: "Add or remove modules" },
|
|
1197
|
+
{ value: "launch-tasks", label: "Set up launch tasks", hint: chalk.hex("#64748B")("tasks Claude runs every session") },
|
|
1045
1198
|
{ value: "validate", label: "Run health check", hint: chalk.hex("#64748B")("check for broken links and excluded items") },
|
|
1046
1199
|
{ value: "nothing", label: "Nothing \u2014 just checking" }
|
|
1047
1200
|
]
|
|
@@ -1054,6 +1207,8 @@ async function runUpdate(cwd) {
|
|
|
1054
1207
|
await runUpdateProfile(cwd, profile);
|
|
1055
1208
|
} else if (action === "modules") {
|
|
1056
1209
|
await runUpdateModules(cwd, profile);
|
|
1210
|
+
} else if (action === "launch-tasks") {
|
|
1211
|
+
await runUpdateLaunchTasks(cwd, profile);
|
|
1057
1212
|
} else if (action === "validate") {
|
|
1058
1213
|
await runValidate(cwd, profile);
|
|
1059
1214
|
}
|
|
@@ -1125,6 +1280,19 @@ function applyProfileUpdate(cwd, profile, fields, overwrite) {
|
|
|
1125
1280
|
}
|
|
1126
1281
|
}
|
|
1127
1282
|
}
|
|
1283
|
+
if (updatedProfile.launch_tasks?.length) {
|
|
1284
|
+
const launchResult = applyLaunchBlock(
|
|
1285
|
+
cwd,
|
|
1286
|
+
updatedProfile.launch_tasks,
|
|
1287
|
+
updatedProfile.modules,
|
|
1288
|
+
vars,
|
|
1289
|
+
newChecksums,
|
|
1290
|
+
overwrite
|
|
1291
|
+
);
|
|
1292
|
+
updated.push(...launchResult.updated);
|
|
1293
|
+
skipped.push(...launchResult.skipped);
|
|
1294
|
+
keptSections.push(...launchResult.keptSections);
|
|
1295
|
+
}
|
|
1128
1296
|
for (const [rel, hash] of Object.entries(stored)) {
|
|
1129
1297
|
if (!(rel in newChecksums)) {
|
|
1130
1298
|
newChecksums[rel] = hash;
|
|
@@ -1345,6 +1513,8 @@ function applyModuleChanges(cwd, profile, toAdd, toRemove, moduleInputs) {
|
|
|
1345
1513
|
updatedProfile = def.onRemove(updatedProfile);
|
|
1346
1514
|
}
|
|
1347
1515
|
}
|
|
1516
|
+
const prunedTasks = pruneLaunchTasks(updatedProfile.launch_tasks, updatedProfile.modules);
|
|
1517
|
+
updatedProfile = { ...updatedProfile, launch_tasks: prunedTasks.length ? prunedTasks : void 0 };
|
|
1348
1518
|
const finalVars = profileToVars(updatedProfile);
|
|
1349
1519
|
for (const [rel, content] of baseManagedFiles(finalVars, updatedProfile.editor, cwd)) {
|
|
1350
1520
|
const result = writeManagedFile(cwd, rel, content, newChecksums);
|
|
@@ -1358,11 +1528,67 @@ function applyModuleChanges(cwd, profile, toAdd, toRemove, moduleInputs) {
|
|
|
1358
1528
|
}
|
|
1359
1529
|
}
|
|
1360
1530
|
}
|
|
1531
|
+
const launchResult = applyLaunchBlock(cwd, updatedProfile.launch_tasks ?? [], updatedProfile.modules, finalVars, newChecksums);
|
|
1532
|
+
keptSections.push(...launchResult.keptSections);
|
|
1361
1533
|
writeState(cwd, { checksums: newChecksums });
|
|
1362
1534
|
const finalProfile = stripLegacyChecksums(updatedProfile);
|
|
1363
1535
|
writeProfile(cwd, finalProfile);
|
|
1364
1536
|
return { profile: finalProfile, added, skipped: skippedFiles, deleted, kept, keptSections };
|
|
1365
1537
|
}
|
|
1538
|
+
function applyLaunchTaskUpdate(cwd, profile, launchTasks, overwrite) {
|
|
1539
|
+
const updatedProfile = {
|
|
1540
|
+
...profile,
|
|
1541
|
+
launch_tasks: launchTasks.length ? launchTasks : void 0
|
|
1542
|
+
};
|
|
1543
|
+
const vars = profileToVars(updatedProfile);
|
|
1544
|
+
const stored = readState(cwd, profile).checksums;
|
|
1545
|
+
const newChecksums = { ...stored };
|
|
1546
|
+
const { updated, skipped, keptSections } = applyLaunchBlock(
|
|
1547
|
+
cwd,
|
|
1548
|
+
launchTasks,
|
|
1549
|
+
profile.modules ?? [],
|
|
1550
|
+
vars,
|
|
1551
|
+
newChecksums,
|
|
1552
|
+
overwrite
|
|
1553
|
+
);
|
|
1554
|
+
writeState(cwd, { checksums: newChecksums });
|
|
1555
|
+
const profileToWrite = stripLegacyChecksums(updatedProfile);
|
|
1556
|
+
writeProfile(cwd, profileToWrite);
|
|
1557
|
+
return { profile: profileToWrite, updated, skipped, keptSections };
|
|
1558
|
+
}
|
|
1559
|
+
async function runUpdateLaunchTasks(cwd, profile) {
|
|
1560
|
+
const avail = availableLaunchTasks(profile.modules ?? []);
|
|
1561
|
+
const initial = pruneLaunchTasks(profile.launch_tasks, profile.modules ?? []);
|
|
1562
|
+
const overwriteSet = /* @__PURE__ */ new Set();
|
|
1563
|
+
const claudePath = join7(cwd, "CLAUDE.md");
|
|
1564
|
+
if (existsSync7(claudePath)) {
|
|
1565
|
+
const storedChecksums = readState(cwd, profile).checksums;
|
|
1566
|
+
const existingContent = readFileSync8(claudePath, "utf8");
|
|
1567
|
+
const editedIds = inspectSections("CLAUDE.md", existingContent, storedChecksums);
|
|
1568
|
+
if (editedIds.includes("launch")) {
|
|
1569
|
+
const confirmed = await p.confirm({
|
|
1570
|
+
message: `Section 'launch' in CLAUDE.md has been manually edited. Overwrite?`,
|
|
1571
|
+
initialValue: false
|
|
1572
|
+
});
|
|
1573
|
+
if (p.isCancel(confirmed)) onCancel();
|
|
1574
|
+
if (confirmed) overwriteSet.add("launch");
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
const selected = await promptLaunchTasks(avail, initial);
|
|
1578
|
+
const { updated, skipped, keptSections } = applyLaunchTaskUpdate(cwd, profile, selected, overwriteSet);
|
|
1579
|
+
const summaryLines = [];
|
|
1580
|
+
if (updated.length > 0) {
|
|
1581
|
+
summaryLines.push(chalk.hex("#94A3B8")(`Updated: ${updated.join(", ")}`));
|
|
1582
|
+
}
|
|
1583
|
+
if (keptSections.length > 0) {
|
|
1584
|
+
summaryLines.push(chalk.hex("#FFAB2E")(`Kept your edits: ${keptSections.join(", ")}`));
|
|
1585
|
+
}
|
|
1586
|
+
if (skipped.length > 0) {
|
|
1587
|
+
summaryLines.push(chalk.hex("#FFAB2E")(`Kept your edits: ${skipped.join(", ")}`));
|
|
1588
|
+
}
|
|
1589
|
+
p.note(summaryLines.join("\n") || "No changes.", label("Done"));
|
|
1590
|
+
p.outro(chalk.hex("#94A3B8")("Launch tasks updated."));
|
|
1591
|
+
}
|
|
1366
1592
|
async function runUpdateModules(cwd, profile) {
|
|
1367
1593
|
const currentModules = profile.modules ?? [];
|
|
1368
1594
|
const selected = await p.multiselect({
|