my-patina 0.5.0 → 0.6.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 CHANGED
@@ -7,7 +7,7 @@ import { Command } from "commander";
7
7
  import * as p from "@clack/prompts";
8
8
  import chalk from "chalk";
9
9
  import { dirname as dirname4, join as join7, resolve } from "path";
10
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
10
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
11
11
  import yaml3 from "js-yaml";
12
12
 
13
13
  // src/detect.ts
@@ -35,7 +35,7 @@ function render(template, vars) {
35
35
  }
36
36
 
37
37
  // src/upgrade.ts
38
- import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
38
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
39
39
  import { join as join3, dirname as dirname2 } from "path";
40
40
 
41
41
  // src/checksums.ts
@@ -163,8 +163,122 @@ var CONTENT_SUBDIRS = ["notes", "skills", "posts"];
163
163
  var MODULE_MANAGED_FILES = Object.fromEntries(MODULES.map((m) => [m.id, m.managedPaths]));
164
164
  var MODULE_CONTENT_FILES = Object.fromEntries(MODULES.map((m) => [m.id, m.contentFileNames]));
165
165
 
166
+ // src/sections.ts
167
+ function hasFences(content) {
168
+ return parseSections(content).length > 0;
169
+ }
170
+ function parseSections(content) {
171
+ const sections = [];
172
+ const startRe = /<!-- patina:([a-z0-9-]+):start -->/g;
173
+ let match;
174
+ while ((match = startRe.exec(content)) !== null) {
175
+ const id = match[1];
176
+ const startIdx = match.index;
177
+ const afterStart = match.index + match[0].length;
178
+ const endMarker = `<!-- patina:${id}:end -->`;
179
+ const endIdx = content.indexOf(endMarker, afterStart);
180
+ if (endIdx === -1) {
181
+ continue;
182
+ }
183
+ let innerStart = afterStart;
184
+ if (content[innerStart] === "\r") innerStart++;
185
+ if (content[innerStart] === "\n") innerStart++;
186
+ let innerEnd = endIdx;
187
+ if (innerEnd > 0 && content[innerEnd - 1] === "\n") innerEnd--;
188
+ if (innerEnd > 0 && content[innerEnd - 1] === "\r") innerEnd--;
189
+ const inner = content.slice(innerStart, innerEnd).replace(/\r\n/g, "\n");
190
+ sections.push({
191
+ id,
192
+ inner,
193
+ start: startIdx,
194
+ end: endIdx + endMarker.length
195
+ });
196
+ }
197
+ return sections;
198
+ }
199
+ function renderSection(id, innerContent) {
200
+ return `<!-- patina:${id}:start -->
201
+ ${innerContent}
202
+ <!-- patina:${id}:end -->`;
203
+ }
204
+ function mergeSections(existing, newSections, storedChecksums, relativePath, overwrite) {
205
+ const existingSections = parseSections(existing);
206
+ const outcomes = [];
207
+ const existingMap = new Map(
208
+ existingSections.map((s) => [s.id, s])
209
+ );
210
+ let result = existing;
211
+ const replacements = [];
212
+ for (const section of existingSections) {
213
+ const { id, inner } = section;
214
+ if (id in newSections) {
215
+ const newInner = newSections[id];
216
+ const storedKey = `${relativePath}:${id}`;
217
+ const storedHash = storedChecksums[storedKey];
218
+ const currentHash = hashContent(inner);
219
+ if (overwrite.has(id)) {
220
+ const normalized = newInner.replace(/\r\n/g, "\n");
221
+ replacements.push({
222
+ start: section.start,
223
+ end: section.end,
224
+ replacement: renderSection(id, normalized)
225
+ });
226
+ outcomes.push({ id, outcome: "updated", newChecksum: hashContent(normalized) });
227
+ } else if (storedHash && currentHash !== storedHash) {
228
+ outcomes.push({ id, outcome: "skipped", newChecksum: storedHash });
229
+ } else {
230
+ const normalized = newInner.replace(/\r\n/g, "\n");
231
+ const normalizedHash = hashContent(normalized);
232
+ if (normalizedHash === currentHash) {
233
+ outcomes.push({ id, outcome: "unchanged", newChecksum: currentHash });
234
+ } else {
235
+ replacements.push({
236
+ start: section.start,
237
+ end: section.end,
238
+ replacement: renderSection(id, normalized)
239
+ });
240
+ outcomes.push({ id, outcome: "updated", newChecksum: normalizedHash });
241
+ }
242
+ }
243
+ } else {
244
+ outcomes.push({ id, outcome: "unchanged", newChecksum: hashContent(inner) });
245
+ }
246
+ }
247
+ replacements.sort((a, b) => b.start - a.start);
248
+ for (const { start, end, replacement } of replacements) {
249
+ result = result.slice(0, start) + replacement + result.slice(end);
250
+ }
251
+ for (const [id, innerContent] of Object.entries(newSections)) {
252
+ if (!existingMap.has(id)) {
253
+ const normalized = innerContent.replace(/\r\n/g, "\n");
254
+ const block = renderSection(id, normalized);
255
+ result = result.endsWith("\n") ? result + block + "\n" : result + "\n" + block + "\n";
256
+ outcomes.push({ id, outcome: "added", newChecksum: hashContent(normalized) });
257
+ }
258
+ }
259
+ return { content: result, sections: outcomes };
260
+ }
261
+ function inspectSections(relativePath, existing, storedChecksums) {
262
+ const sections = parseSections(existing);
263
+ const editedIds = [];
264
+ for (const { id, inner } of sections) {
265
+ const storedKey = `${relativePath}:${id}`;
266
+ const storedHash = storedChecksums[storedKey];
267
+ if (!storedHash) {
268
+ continue;
269
+ }
270
+ if (hashContent(inner) !== storedHash) {
271
+ editedIds.push(id);
272
+ }
273
+ }
274
+ return editedIds;
275
+ }
276
+
166
277
  // src/upgrade.ts
167
- function writeManagedFile(targetDir, relativePath, newContent, storedChecksums) {
278
+ function writeManagedFile(targetDir, relativePath, newContent, storedChecksums, overwrite) {
279
+ if (hasFences(newContent)) {
280
+ return writeSectionedFile(targetDir, relativePath, newContent, storedChecksums, overwrite ?? /* @__PURE__ */ new Set());
281
+ }
168
282
  const fullPath = join3(targetDir, relativePath);
169
283
  const newChecksum = hashContent(newContent);
170
284
  if (!existsSync3(fullPath)) {
@@ -180,9 +294,51 @@ function writeManagedFile(targetDir, relativePath, newContent, storedChecksums)
180
294
  }
181
295
  return { outcome: "skipped", checksum: storedHash };
182
296
  }
297
+ function writeSectionedFile(targetDir, relativePath, newContent, storedChecksums, overwrite) {
298
+ const fullPath = join3(targetDir, relativePath);
299
+ if (!existsSync3(fullPath)) {
300
+ mkdirSync(dirname2(fullPath), { recursive: true });
301
+ writeFileSync(fullPath, newContent, "utf8");
302
+ const sections2 = parseSections(newContent).map((s) => ({
303
+ id: s.id,
304
+ outcome: "added",
305
+ newChecksum: hashContent(s.inner)
306
+ }));
307
+ return { outcome: "added", checksum: hashContent(newContent), sections: sections2 };
308
+ }
309
+ const existingContent = readFileSync4(fullPath, "utf8");
310
+ if (!hasFences(existingContent)) {
311
+ const currentHash = hashFile(fullPath);
312
+ const storedHash = storedChecksums[relativePath];
313
+ if (!storedHash || currentHash === storedHash) {
314
+ writeFileSync(fullPath, newContent, "utf8");
315
+ const sections2 = parseSections(newContent).map((s) => ({
316
+ id: s.id,
317
+ outcome: "added",
318
+ newChecksum: hashContent(s.inner)
319
+ }));
320
+ return { outcome: "updated", checksum: hashContent(newContent), sections: sections2 };
321
+ } else {
322
+ return { outcome: "skipped", checksum: storedHash ?? currentHash, sections: void 0 };
323
+ }
324
+ }
325
+ const newSectionMap = {};
326
+ for (const s of parseSections(newContent)) {
327
+ newSectionMap[s.id] = s.inner;
328
+ }
329
+ const { content: mergedContent, sections } = mergeSections(
330
+ existingContent,
331
+ newSectionMap,
332
+ storedChecksums,
333
+ relativePath,
334
+ overwrite
335
+ );
336
+ writeFileSync(fullPath, mergedContent, "utf8");
337
+ return { outcome: "updated", checksum: hashContent(mergedContent), sections };
338
+ }
183
339
 
184
340
  // src/state.ts
185
- import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
341
+ import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
186
342
  import { join as join4 } from "path";
187
343
  var STATE_FILENAME = ".patina-state.json";
188
344
  function normalizeChecksums(checksums) {
@@ -195,7 +351,7 @@ function normalizeChecksums(checksums) {
195
351
  function readState(root, profile) {
196
352
  const statePath = join4(root, STATE_FILENAME);
197
353
  if (existsSync4(statePath)) {
198
- const raw = readFileSync4(statePath, "utf8");
354
+ const raw = readFileSync5(statePath, "utf8");
199
355
  let parsed;
200
356
  try {
201
357
  parsed = JSON.parse(raw);
@@ -351,8 +507,11 @@ async function scaffold(opts) {
351
507
  if (manifestEntry) validateManifestFrontmatter(module, manifestEntry[1]);
352
508
  }
353
509
  for (const [relativePath, content] of managedFiles) {
354
- const { checksum } = writeManagedFile(targetDir, relativePath, content, {});
355
- checksums[relativePath] = checksum;
510
+ const result = writeManagedFile(targetDir, relativePath, content, {});
511
+ checksums[relativePath] = result.checksum;
512
+ for (const s of result.sections ?? []) {
513
+ checksums[`${relativePath}:${s.id}`] = s.newChecksum;
514
+ }
356
515
  }
357
516
  const baseDirs = ["notes", "skills", "posts"];
358
517
  for (const dir of baseDirs) {
@@ -374,7 +533,7 @@ ${STATE_FILENAME}
374
533
  }
375
534
 
376
535
  // src/validate.ts
377
- import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync } from "fs";
536
+ import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync } from "fs";
378
537
  import { join as join6, relative, sep, basename } from "path";
379
538
  var NOTES = CONTENT_SUBDIRS[0];
380
539
  var SKILLS = CONTENT_SUBDIRS[1];
@@ -446,7 +605,7 @@ function checkSkillNotes(root, profile) {
446
605
  const noteSlugs = new Set(noteFiles.map((f) => basename(f, ".md")));
447
606
  const issues = [];
448
607
  for (const skillFile of listMarkdownFiles(skillsDir)) {
449
- const content = readFileSync5(skillFile, "utf8");
608
+ const content = readFileSync6(skillFile, "utf8");
450
609
  const links = extractWikiLinks(content);
451
610
  for (const { target, line } of links) {
452
611
  if (!noteSlugs.has(target)) {
@@ -473,7 +632,7 @@ function checkWikiLinks(root, profile) {
473
632
  ...listMarkdownFiles(postsDir)
474
633
  ];
475
634
  for (const file of filesToScan) {
476
- const content = readFileSync5(file, "utf8");
635
+ const content = readFileSync6(file, "utf8");
477
636
  const links = extractWikiLinks(content);
478
637
  for (const { target, line } of links) {
479
638
  if (!noteSlugs.has(target)) {
@@ -493,7 +652,7 @@ function checkExclusions(root, profile) {
493
652
  const notesDir = join6(contentDir, NOTES);
494
653
  const exclusionsPath = join6(notesDir, "exclusions.md");
495
654
  if (!existsSync5(exclusionsPath)) return [];
496
- const content = readFileSync5(exclusionsPath, "utf8");
655
+ const content = readFileSync6(exclusionsPath, "utf8");
497
656
  const items = parseExclusions(content);
498
657
  if (items.length === 0) return [];
499
658
  const skillsDir = join6(contentDir, SKILLS);
@@ -505,7 +664,7 @@ function checkExclusions(root, profile) {
505
664
  const issues = [];
506
665
  const seen = /* @__PURE__ */ new Set();
507
666
  for (const file of filesToScan) {
508
- const fileContent = readFileSync5(file, "utf8");
667
+ const fileContent = readFileSync6(file, "utf8");
509
668
  const lines = fileContent.split("\n");
510
669
  for (let i = 0; i < lines.length; i++) {
511
670
  const lineText = lines[i];
@@ -742,6 +901,15 @@ function writeProfile(cwd, profile) {
742
901
  function removeManagedFileIfUnmodified(targetDir, rel, stored) {
743
902
  const fullPath = join7(targetDir, rel);
744
903
  if (!existsSync6(fullPath)) return "deleted";
904
+ const fileContent = readFileSync7(fullPath, "utf8");
905
+ if (hasFences(fileContent)) {
906
+ const editedIds = inspectSections(rel, fileContent, stored);
907
+ if (editedIds.length > 0) {
908
+ return "kept";
909
+ }
910
+ unlinkSync(fullPath);
911
+ return "deleted";
912
+ }
745
913
  const currentHash = hashFile(fullPath);
746
914
  const storedHash = stored[rel];
747
915
  if (storedHash && currentHash !== storedHash) {
@@ -783,7 +951,7 @@ async function runUpdate(cwd) {
783
951
  await runValidate(cwd, profile);
784
952
  }
785
953
  }
786
- function applyProfileUpdate(cwd, profile, fields) {
954
+ function applyProfileUpdate(cwd, profile, fields, overwrite) {
787
955
  const updatedProfile = {
788
956
  ...profile,
789
957
  name: fields.name.trim(),
@@ -806,10 +974,19 @@ function applyProfileUpdate(cwd, profile, fields) {
806
974
  ];
807
975
  const updated = [];
808
976
  const skipped = [];
977
+ const keptSections = [];
809
978
  for (const [rel, content] of files) {
810
- const { outcome, checksum } = writeManagedFile(cwd, rel, content, stored);
811
- newChecksums[rel] = checksum;
812
- if (outcome === "skipped") {
979
+ const result = writeManagedFile(cwd, rel, content, stored, overwrite);
980
+ newChecksums[rel] = result.checksum;
981
+ for (const s of result.sections ?? []) {
982
+ const sKey = `${rel}:${s.id}`;
983
+ if (s.outcome !== "skipped") newChecksums[sKey] = s.newChecksum;
984
+ else {
985
+ newChecksums[sKey] = stored[sKey] ?? "";
986
+ keptSections.push(sKey);
987
+ }
988
+ }
989
+ if (result.outcome === "skipped") {
813
990
  skipped.push(rel);
814
991
  } else {
815
992
  updated.push(rel);
@@ -823,7 +1000,7 @@ function applyProfileUpdate(cwd, profile, fields) {
823
1000
  writeState(cwd, { checksums: newChecksums });
824
1001
  const profileToWrite = stripLegacyChecksums(updatedProfile);
825
1002
  writeProfile(cwd, profileToWrite);
826
- return { profile: profileToWrite, updated, skipped };
1003
+ return { profile: profileToWrite, updated, skipped, keptSections };
827
1004
  }
828
1005
  async function runUpdateProfile(cwd, profile) {
829
1006
  console.log("");
@@ -876,7 +1053,7 @@ async function runUpdateProfile(cwd, profile) {
876
1053
  },
877
1054
  { onCancel }
878
1055
  );
879
- const { updated, skipped } = applyProfileUpdate(cwd, profile, {
1056
+ const fields = {
880
1057
  name: identity.name,
881
1058
  title: identity.title ?? "",
882
1059
  roleDescription: identity.roleDescription ?? "",
@@ -885,11 +1062,52 @@ async function runUpdateProfile(cwd, profile) {
885
1062
  companyName: work.companyName ?? "",
886
1063
  website: work.website ?? "",
887
1064
  companyDescription: work.companyDescription ?? ""
888
- });
1065
+ };
1066
+ const overwriteSet = /* @__PURE__ */ new Set();
1067
+ const previewProfile = {
1068
+ ...profile,
1069
+ name: fields.name.trim(),
1070
+ title: fields.title.trim(),
1071
+ role_description: fields.roleDescription.trim() || void 0,
1072
+ job_description_url: fields.jobDescriptionUrl.trim() || void 0,
1073
+ work: {
1074
+ self_employed: fields.selfEmployed,
1075
+ company_name: fields.companyName.trim() || (fields.selfEmployed ? "Freelance" : ""),
1076
+ website: fields.website.trim() || void 0,
1077
+ company_description: fields.companyDescription.trim() || void 0
1078
+ }
1079
+ };
1080
+ const previewVars = profileToVars(previewProfile);
1081
+ const storedChecksums = readState(cwd, profile).checksums;
1082
+ const previewFiles = [
1083
+ ...baseManagedFiles(previewVars, previewProfile.editor, cwd),
1084
+ ...previewProfile.modules.flatMap((m) => moduleManagedFiles(m, previewVars))
1085
+ ];
1086
+ for (const [rel, content] of previewFiles) {
1087
+ if (hasFences(content)) {
1088
+ const fullPath = join7(cwd, rel);
1089
+ if (existsSync6(fullPath)) {
1090
+ const existingContent = readFileSync7(fullPath, "utf8");
1091
+ const editedIds = inspectSections(rel, existingContent, storedChecksums);
1092
+ for (const sectionId of editedIds) {
1093
+ const confirmed = await p.confirm({
1094
+ message: `Section '${sectionId}' in ${rel} has been manually edited. Overwrite?`,
1095
+ initialValue: false
1096
+ });
1097
+ if (p.isCancel(confirmed)) onCancel();
1098
+ if (confirmed) overwriteSet.add(sectionId);
1099
+ }
1100
+ }
1101
+ }
1102
+ }
1103
+ const { updated, skipped, keptSections } = applyProfileUpdate(cwd, profile, fields, overwriteSet);
889
1104
  const summaryLines = [];
890
1105
  if (updated.length > 0) {
891
1106
  summaryLines.push(chalk.hex("#94A3B8")(`Updated: ${updated.join(", ")}`));
892
1107
  }
1108
+ if (keptSections.length > 0) {
1109
+ summaryLines.push(chalk.hex("#FFAB2E")(`Kept your edits: ${keptSections.join(", ")}`));
1110
+ }
893
1111
  if (skipped.length > 0) {
894
1112
  summaryLines.push(chalk.hex("#FFAB2E")(`Kept your edits: ${skipped.join(", ")}`));
895
1113
  }
@@ -904,6 +1122,7 @@ function applyModuleChanges(cwd, profile, toAdd, toRemove, moduleInputs) {
904
1122
  const skippedFiles = [];
905
1123
  const deleted = [];
906
1124
  const kept = [];
1125
+ const keptSections = [];
907
1126
  for (const module of toAdd) {
908
1127
  const def = getModule(module);
909
1128
  if (def?.onAdd) {
@@ -912,9 +1131,17 @@ function applyModuleChanges(cwd, profile, toAdd, toRemove, moduleInputs) {
912
1131
  const vars = profileToVars(updatedProfile);
913
1132
  const contentDir = updatedProfile.content_dir;
914
1133
  for (const [rel, content] of moduleManagedFiles(module, vars)) {
915
- const { outcome, checksum } = writeManagedFile(cwd, rel, content, newChecksums);
916
- newChecksums[rel] = checksum;
917
- if (outcome === "skipped") {
1134
+ const result = writeManagedFile(cwd, rel, content, newChecksums);
1135
+ newChecksums[rel] = result.checksum;
1136
+ for (const s of result.sections ?? []) {
1137
+ const sKey = `${rel}:${s.id}`;
1138
+ if (s.outcome !== "skipped") newChecksums[sKey] = s.newChecksum;
1139
+ else {
1140
+ newChecksums[sKey] = newChecksums[sKey] ?? "";
1141
+ keptSections.push(sKey);
1142
+ }
1143
+ }
1144
+ if (result.outcome === "skipped") {
918
1145
  skippedFiles.push(rel);
919
1146
  } else {
920
1147
  added.push(rel);
@@ -940,6 +1167,10 @@ function applyModuleChanges(cwd, profile, toAdd, toRemove, moduleInputs) {
940
1167
  if (result === "deleted") {
941
1168
  deleted.push(rel);
942
1169
  delete newChecksums[rel];
1170
+ const prefix = rel + ":";
1171
+ for (const key of Object.keys(newChecksums)) {
1172
+ if (key.startsWith(prefix)) delete newChecksums[key];
1173
+ }
943
1174
  } else {
944
1175
  kept.push(rel);
945
1176
  }
@@ -952,7 +1183,7 @@ function applyModuleChanges(cwd, profile, toAdd, toRemove, moduleInputs) {
952
1183
  writeState(cwd, { checksums: newChecksums });
953
1184
  const finalProfile = stripLegacyChecksums(updatedProfile);
954
1185
  writeProfile(cwd, finalProfile);
955
- return { profile: finalProfile, added, skipped: skippedFiles, deleted, kept };
1186
+ return { profile: finalProfile, added, skipped: skippedFiles, deleted, kept, keptSections };
956
1187
  }
957
1188
  async function runUpdateModules(cwd, profile) {
958
1189
  const currentModules = profile.modules ?? [];
@@ -991,9 +1222,10 @@ async function runUpdateModules(cwd, profile) {
991
1222
  liProfileUrl = url.trim();
992
1223
  }
993
1224
  }
994
- const { added: addedFiles, skipped: skippedFiles, deleted: deletedFiles, kept: keptFiles } = applyModuleChanges(cwd, profile, toAdd, toRemove, { linkedin: { liProfileUrl } });
1225
+ const { added: addedFiles, skipped: skippedFiles, deleted: deletedFiles, kept: keptFiles, keptSections: keptSectionKeys } = applyModuleChanges(cwd, profile, toAdd, toRemove, { linkedin: { liProfileUrl } });
995
1226
  const summaryLines = [];
996
1227
  if (addedFiles.length > 0) summaryLines.push(chalk.hex("#94A3B8")(`Added: ${addedFiles.join(", ")}`));
1228
+ if (keptSectionKeys.length > 0) summaryLines.push(chalk.hex("#FFAB2E")(`Kept your edits: ${keptSectionKeys.join(", ")}`));
997
1229
  if (skippedFiles.length > 0) summaryLines.push(chalk.hex("#FFAB2E")(`Kept your edits: ${skippedFiles.join(", ")}`));
998
1230
  if (deletedFiles.length > 0) summaryLines.push(chalk.hex("#94A3B8")(`Removed: ${deletedFiles.join(", ")}`));
999
1231
  if (keptFiles.length > 0) summaryLines.push(chalk.hex("#FFAB2E")(`Kept your edited files: ${keptFiles.join(", ")}`));
@@ -1022,7 +1254,7 @@ function registerCommands(_program) {
1022
1254
  // src/cli.ts
1023
1255
  import chalk2 from "chalk";
1024
1256
  var program = new Command();
1025
- program.name("patina").description("Personal professional knowledge graph \u2014 setup and management");
1257
+ program.name("patina").description("Personal professional knowledge graph \u2014 setup and management").allowExcessArguments(true);
1026
1258
  program.command("validate").description("Check your patina for broken links and excluded items").action(() => {
1027
1259
  try {
1028
1260
  const cwd = process.cwd();
@@ -2,6 +2,7 @@
2
2
 
3
3
  This file is loaded automatically each session to give you context about who you're working with and how this patina is organised.
4
4
 
5
+ <!-- patina:profile:start -->
5
6
  ## Who you're working with
6
7
 
7
8
  **Name:** {{USER_NAME}}
@@ -11,6 +12,7 @@ This file is loaded automatically each session to give you context about who you
11
12
  {{ROLE_DESCRIPTION}}
12
13
 
13
14
  {{COMPANY_DESCRIPTION}}
15
+ <!-- patina:profile:end -->
14
16
 
15
17
  ## What patina is
16
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-patina",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Personal professional knowledge graph — setup and management",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "@clack/prompts": "^0.9.0",
22
22
  "chalk": "^5.3.0",
23
- "commander": "^12.1.0",
23
+ "commander": "^15.0.0",
24
24
  "js-yaml": "^4.1.0"
25
25
  },
26
26
  "devDependencies": {
@@ -28,8 +28,8 @@
28
28
  "@types/node": "^20.0.0",
29
29
  "tsup": "^8.0.0",
30
30
  "tsx": "^4.0.0",
31
- "typescript": "^5.0.0",
32
- "vitest": "^1.0.0"
31
+ "typescript": "^6.0.3",
32
+ "vitest": "^4.1.7"
33
33
  },
34
34
  "tsup": {
35
35
  "entry": [