my-patina 0.1.1 → 0.1.2
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 +266 -227
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/wizard.ts
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
-
import { dirname as
|
|
6
|
+
import { dirname as dirname4, join as join6, resolve } from "path";
|
|
7
7
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
8
8
|
import yaml3 from "js-yaml";
|
|
9
9
|
|
|
@@ -20,9 +20,8 @@ function loadProfile(root) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// src/scaffold.ts
|
|
23
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2
|
|
24
|
-
import { join as
|
|
25
|
-
import { fileURLToPath } from "url";
|
|
23
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
24
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
26
25
|
import yaml2 from "js-yaml";
|
|
27
26
|
|
|
28
27
|
// src/template.ts
|
|
@@ -34,65 +33,139 @@ function render(template, vars) {
|
|
|
34
33
|
|
|
35
34
|
// src/upgrade.ts
|
|
36
35
|
import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
|
|
37
|
-
import { join as
|
|
36
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
38
37
|
|
|
39
38
|
// src/checksums.ts
|
|
40
39
|
import { createHash } from "crypto";
|
|
41
|
-
import { readFileSync as
|
|
40
|
+
import { readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
|
|
41
|
+
|
|
42
|
+
// src/template-loader.ts
|
|
43
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
44
|
+
import { join as join2, dirname } from "path";
|
|
45
|
+
import { fileURLToPath } from "url";
|
|
46
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
47
|
+
var TEMPLATES_DIR = join2(__dirname, "templates");
|
|
48
|
+
function tpl(relativePath) {
|
|
49
|
+
return readFileSync2(join2(TEMPLATES_DIR, relativePath), "utf8");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/modules/linkedin/index.ts
|
|
53
|
+
var LI_COMMANDS = [
|
|
54
|
+
"li-all.md",
|
|
55
|
+
"li-about.md",
|
|
56
|
+
"li-headline.md",
|
|
57
|
+
"li-experience.md",
|
|
58
|
+
"li-skills.md",
|
|
59
|
+
"li-featured.md",
|
|
60
|
+
"li-activity.md"
|
|
61
|
+
];
|
|
62
|
+
var LI_MANAGED_PATHS = [
|
|
63
|
+
...LI_COMMANDS.map((c) => `.claude/commands/${c}`),
|
|
64
|
+
".claude/modules/linkedin/manifest.md"
|
|
65
|
+
];
|
|
66
|
+
var CONTENT_FILE_NAMES = [
|
|
67
|
+
"INSTRUCTIONS.md",
|
|
68
|
+
"LinkedIn Current State.md",
|
|
69
|
+
"LinkedIn About.md",
|
|
70
|
+
"LinkedIn Headline.md",
|
|
71
|
+
"LinkedIn Experience.md",
|
|
72
|
+
"LinkedIn Skills.md",
|
|
73
|
+
"LinkedIn Featured.md",
|
|
74
|
+
"LinkedIn Activity.md"
|
|
75
|
+
];
|
|
76
|
+
var linkedinModule = {
|
|
77
|
+
id: "linkedin",
|
|
78
|
+
label: "LinkedIn",
|
|
79
|
+
hint: "draft and refine your LinkedIn profile",
|
|
80
|
+
managedPaths: LI_MANAGED_PATHS,
|
|
81
|
+
contentFileNames: CONTENT_FILE_NAMES,
|
|
82
|
+
managedFiles(vars) {
|
|
83
|
+
const files = LI_COMMANDS.map((cmd2) => [
|
|
84
|
+
`.claude/commands/${cmd2}`,
|
|
85
|
+
render(tpl(`modules/linkedin/commands/${cmd2}`), vars)
|
|
86
|
+
]);
|
|
87
|
+
files.push([
|
|
88
|
+
".claude/modules/linkedin/manifest.md",
|
|
89
|
+
render(tpl("modules/linkedin/manifest.md"), vars)
|
|
90
|
+
]);
|
|
91
|
+
return files;
|
|
92
|
+
},
|
|
93
|
+
contentFiles(vars, contentDir) {
|
|
94
|
+
return CONTENT_FILE_NAMES.map((file) => [
|
|
95
|
+
`${contentDir}/linkedin/${file}`,
|
|
96
|
+
render(tpl(`modules/linkedin/graph/${file}`), vars)
|
|
97
|
+
]);
|
|
98
|
+
},
|
|
99
|
+
onAdd(profile, inputs) {
|
|
100
|
+
if (!profile.linkedin?.profile_url && inputs.liProfileUrl?.trim()) {
|
|
101
|
+
return { ...profile, linkedin: { profile_url: inputs.liProfileUrl.trim() } };
|
|
102
|
+
}
|
|
103
|
+
return profile;
|
|
104
|
+
},
|
|
105
|
+
onRemove(profile) {
|
|
106
|
+
const updated = { ...profile };
|
|
107
|
+
delete updated.linkedin;
|
|
108
|
+
return updated;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/modules/resume/index.ts
|
|
113
|
+
var RESUME_MANAGED_PATHS = [
|
|
114
|
+
".claude/commands/resume-refresh.md",
|
|
115
|
+
".claude/modules/resume/manifest.md"
|
|
116
|
+
];
|
|
117
|
+
var CONTENT_FILE_NAMES2 = [
|
|
118
|
+
"INSTRUCTIONS.md",
|
|
119
|
+
"Resume Working Draft.md",
|
|
120
|
+
"Resume Last Submitted.md"
|
|
121
|
+
];
|
|
122
|
+
var resumeModule = {
|
|
123
|
+
id: "resume",
|
|
124
|
+
label: "Resume",
|
|
125
|
+
hint: "keep your resume current from your graph",
|
|
126
|
+
managedPaths: RESUME_MANAGED_PATHS,
|
|
127
|
+
contentFileNames: CONTENT_FILE_NAMES2,
|
|
128
|
+
managedFiles(vars) {
|
|
129
|
+
const [commandPath, manifestPath] = RESUME_MANAGED_PATHS;
|
|
130
|
+
return [
|
|
131
|
+
[commandPath, render(tpl("modules/resume/commands/resume-refresh.md"), vars)],
|
|
132
|
+
[manifestPath, render(tpl("modules/resume/manifest.md"), vars)]
|
|
133
|
+
];
|
|
134
|
+
},
|
|
135
|
+
contentFiles(vars, contentDir) {
|
|
136
|
+
return CONTENT_FILE_NAMES2.map((file) => [
|
|
137
|
+
`${contentDir}/resume/${file}`,
|
|
138
|
+
render(tpl(`modules/resume/graph/${file}`), vars)
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
// Resume has no module-specific profile fields — no onAdd/onRemove needed.
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// src/modules/registry.ts
|
|
145
|
+
var MODULES = [linkedinModule, resumeModule];
|
|
146
|
+
var BY_ID = new Map(MODULES.map((m) => [m.id, m]));
|
|
147
|
+
function getModule(id) {
|
|
148
|
+
return BY_ID.get(id);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/checksums.ts
|
|
42
152
|
function hashContent(content) {
|
|
43
153
|
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
44
154
|
}
|
|
45
155
|
function hashFile(filePath) {
|
|
46
156
|
if (!existsSync2(filePath)) return null;
|
|
47
|
-
return hashContent(
|
|
157
|
+
return hashContent(readFileSync3(filePath, "utf8"));
|
|
48
158
|
}
|
|
49
159
|
var CONTENT_SUBDIRS = ["notes", "skills", "posts"];
|
|
50
|
-
var
|
|
51
|
-
|
|
52
|
-
".claude/commands/li-about.md",
|
|
53
|
-
".claude/commands/li-headline.md",
|
|
54
|
-
".claude/commands/li-experience.md",
|
|
55
|
-
".claude/commands/li-skills.md",
|
|
56
|
-
".claude/commands/li-featured.md",
|
|
57
|
-
".claude/commands/li-activity.md"
|
|
58
|
-
];
|
|
59
|
-
var RESUME_MANAGED_FILES = [
|
|
60
|
-
".claude/commands/resume-refresh.md"
|
|
61
|
-
];
|
|
62
|
-
var MODULE_MANAGED_FILES = {
|
|
63
|
-
linkedin: [
|
|
64
|
-
...LINKEDIN_MANAGED_FILES,
|
|
65
|
-
".claude/modules/linkedin/manifest.md"
|
|
66
|
-
],
|
|
67
|
-
resume: [
|
|
68
|
-
...RESUME_MANAGED_FILES,
|
|
69
|
-
".claude/modules/resume/manifest.md"
|
|
70
|
-
]
|
|
71
|
-
};
|
|
72
|
-
var MODULE_CONTENT_FILES = {
|
|
73
|
-
linkedin: [
|
|
74
|
-
"INSTRUCTIONS.md",
|
|
75
|
-
"LinkedIn Current State.md",
|
|
76
|
-
"LinkedIn About.md",
|
|
77
|
-
"LinkedIn Headline.md",
|
|
78
|
-
"LinkedIn Experience.md",
|
|
79
|
-
"LinkedIn Skills.md",
|
|
80
|
-
"LinkedIn Featured.md",
|
|
81
|
-
"LinkedIn Activity.md"
|
|
82
|
-
],
|
|
83
|
-
resume: [
|
|
84
|
-
"INSTRUCTIONS.md",
|
|
85
|
-
"Resume Working Draft.md",
|
|
86
|
-
"Resume Last Submitted.md"
|
|
87
|
-
]
|
|
88
|
-
};
|
|
160
|
+
var MODULE_MANAGED_FILES = Object.fromEntries(MODULES.map((m) => [m.id, m.managedPaths]));
|
|
161
|
+
var MODULE_CONTENT_FILES = Object.fromEntries(MODULES.map((m) => [m.id, m.contentFileNames]));
|
|
89
162
|
|
|
90
163
|
// src/upgrade.ts
|
|
91
164
|
function writeManagedFile(targetDir, relativePath, newContent, storedChecksums) {
|
|
92
|
-
const fullPath =
|
|
165
|
+
const fullPath = join3(targetDir, relativePath);
|
|
93
166
|
const newChecksum = hashContent(newContent);
|
|
94
167
|
if (!existsSync3(fullPath)) {
|
|
95
|
-
mkdirSync(
|
|
168
|
+
mkdirSync(dirname2(fullPath), { recursive: true });
|
|
96
169
|
writeFileSync(fullPath, newContent, "utf8");
|
|
97
170
|
return { outcome: "added", checksum: newChecksum };
|
|
98
171
|
}
|
|
@@ -106,19 +179,14 @@ function writeManagedFile(targetDir, relativePath, newContent, storedChecksums)
|
|
|
106
179
|
}
|
|
107
180
|
|
|
108
181
|
// src/scaffold.ts
|
|
109
|
-
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
110
|
-
var TEMPLATES_DIR = join3(__dirname, "templates");
|
|
111
|
-
function tpl(relativePath) {
|
|
112
|
-
return readFileSync3(join3(TEMPLATES_DIR, relativePath), "utf8");
|
|
113
|
-
}
|
|
114
182
|
function writeRaw(targetDir, relativePath, content) {
|
|
115
|
-
const full =
|
|
116
|
-
mkdirSync2(
|
|
183
|
+
const full = join4(targetDir, relativePath);
|
|
184
|
+
mkdirSync2(dirname3(full), { recursive: true });
|
|
117
185
|
writeFileSync2(full, content, "utf8");
|
|
118
186
|
}
|
|
119
187
|
function touch(targetDir, relativePath) {
|
|
120
|
-
const full =
|
|
121
|
-
mkdirSync2(
|
|
188
|
+
const full = join4(targetDir, relativePath);
|
|
189
|
+
mkdirSync2(dirname3(full), { recursive: true });
|
|
122
190
|
writeFileSync2(full, "", "utf8");
|
|
123
191
|
}
|
|
124
192
|
function profileToVars(profile, liProfileUrl) {
|
|
@@ -148,7 +216,7 @@ function baseManagedFiles(vars, editor, targetDir) {
|
|
|
148
216
|
mcpServers: {
|
|
149
217
|
obsidian: {
|
|
150
218
|
command: "npx",
|
|
151
|
-
args: ["-y", "mcp-obsidian@latest",
|
|
219
|
+
args: ["-y", "mcp-obsidian@latest", join4(targetDir, vars.CONTENT_DIR).replace(/\\/g, "/")]
|
|
152
220
|
}
|
|
153
221
|
}
|
|
154
222
|
};
|
|
@@ -157,50 +225,10 @@ function baseManagedFiles(vars, editor, targetDir) {
|
|
|
157
225
|
return files;
|
|
158
226
|
}
|
|
159
227
|
function moduleManagedFiles(module, vars) {
|
|
160
|
-
|
|
161
|
-
const liCmds = [
|
|
162
|
-
"li-all.md",
|
|
163
|
-
"li-about.md",
|
|
164
|
-
"li-headline.md",
|
|
165
|
-
"li-experience.md",
|
|
166
|
-
"li-skills.md",
|
|
167
|
-
"li-featured.md",
|
|
168
|
-
"li-activity.md"
|
|
169
|
-
];
|
|
170
|
-
const files = liCmds.map((cmd2) => [
|
|
171
|
-
`.claude/commands/${cmd2}`,
|
|
172
|
-
render(tpl(`modules/linkedin/commands/${cmd2}`), vars)
|
|
173
|
-
]);
|
|
174
|
-
files.push([
|
|
175
|
-
".claude/modules/linkedin/manifest.md",
|
|
176
|
-
render(tpl("modules/linkedin/manifest.md"), vars)
|
|
177
|
-
]);
|
|
178
|
-
return files;
|
|
179
|
-
}
|
|
180
|
-
if (module === "resume") {
|
|
181
|
-
return [
|
|
182
|
-
[".claude/commands/resume-refresh.md", render(tpl("modules/resume/commands/resume-refresh.md"), vars)],
|
|
183
|
-
[".claude/modules/resume/manifest.md", render(tpl("modules/resume/manifest.md"), vars)]
|
|
184
|
-
];
|
|
185
|
-
}
|
|
186
|
-
return [];
|
|
228
|
+
return getModule(module)?.managedFiles(vars) ?? [];
|
|
187
229
|
}
|
|
188
230
|
function moduleContentFiles(module, vars, contentDir) {
|
|
189
|
-
|
|
190
|
-
const files = MODULE_CONTENT_FILES["linkedin"] ?? [];
|
|
191
|
-
return files.map((file) => [
|
|
192
|
-
`${contentDir}/linkedin/${file}`,
|
|
193
|
-
render(tpl(`modules/linkedin/graph/${file}`), vars)
|
|
194
|
-
]);
|
|
195
|
-
}
|
|
196
|
-
if (module === "resume") {
|
|
197
|
-
const files = MODULE_CONTENT_FILES["resume"] ?? [];
|
|
198
|
-
return files.map((file) => [
|
|
199
|
-
`${contentDir}/resume/${file}`,
|
|
200
|
-
render(tpl(`modules/resume/graph/${file}`), vars)
|
|
201
|
-
]);
|
|
202
|
-
}
|
|
203
|
-
return [];
|
|
231
|
+
return getModule(module)?.contentFiles(vars, contentDir) ?? [];
|
|
204
232
|
}
|
|
205
233
|
async function scaffold(opts) {
|
|
206
234
|
const {
|
|
@@ -273,18 +301,18 @@ async function scaffold(opts) {
|
|
|
273
301
|
|
|
274
302
|
// src/validate.ts
|
|
275
303
|
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
276
|
-
import { join as
|
|
304
|
+
import { join as join5, relative, sep, basename } from "path";
|
|
277
305
|
var NOTES = CONTENT_SUBDIRS[0];
|
|
278
306
|
var SKILLS = CONTENT_SUBDIRS[1];
|
|
279
307
|
var POSTS = CONTENT_SUBDIRS[2];
|
|
280
308
|
function findPatinaRoot(cwd) {
|
|
281
|
-
return existsSync4(
|
|
309
|
+
return existsSync4(join5(cwd, "profile.yaml")) ? cwd : null;
|
|
282
310
|
}
|
|
283
311
|
function listMarkdownFiles(dir) {
|
|
284
312
|
if (!existsSync4(dir)) return [];
|
|
285
313
|
const results = [];
|
|
286
314
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
287
|
-
const fullPath =
|
|
315
|
+
const fullPath = join5(dir, entry.name);
|
|
288
316
|
if (entry.isDirectory()) {
|
|
289
317
|
results.push(...listMarkdownFiles(fullPath));
|
|
290
318
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -337,9 +365,9 @@ function parseExclusions(markdown) {
|
|
|
337
365
|
return [...new Set(items)];
|
|
338
366
|
}
|
|
339
367
|
function checkSkillNotes(root, profile) {
|
|
340
|
-
const contentDir =
|
|
341
|
-
const notesDir =
|
|
342
|
-
const skillsDir =
|
|
368
|
+
const contentDir = join5(root, profile.content_dir ?? "graph");
|
|
369
|
+
const notesDir = join5(contentDir, NOTES);
|
|
370
|
+
const skillsDir = join5(contentDir, SKILLS);
|
|
343
371
|
const noteFiles = listMarkdownFiles(notesDir);
|
|
344
372
|
const noteSlugs = new Set(noteFiles.map((f) => basename(f, ".md")));
|
|
345
373
|
const issues = [];
|
|
@@ -360,9 +388,9 @@ function checkSkillNotes(root, profile) {
|
|
|
360
388
|
return issues;
|
|
361
389
|
}
|
|
362
390
|
function checkWikiLinks(root, profile) {
|
|
363
|
-
const contentDir =
|
|
364
|
-
const notesDir =
|
|
365
|
-
const postsDir =
|
|
391
|
+
const contentDir = join5(root, profile.content_dir ?? "graph");
|
|
392
|
+
const notesDir = join5(contentDir, NOTES);
|
|
393
|
+
const postsDir = join5(contentDir, POSTS);
|
|
366
394
|
const noteFiles = listMarkdownFiles(notesDir);
|
|
367
395
|
const noteSlugs = new Set(noteFiles.map((f) => basename(f, ".md")));
|
|
368
396
|
const issues = [];
|
|
@@ -387,15 +415,15 @@ function checkWikiLinks(root, profile) {
|
|
|
387
415
|
return issues;
|
|
388
416
|
}
|
|
389
417
|
function checkExclusions(root, profile) {
|
|
390
|
-
const contentDir =
|
|
391
|
-
const notesDir =
|
|
392
|
-
const exclusionsPath =
|
|
418
|
+
const contentDir = join5(root, profile.content_dir ?? "graph");
|
|
419
|
+
const notesDir = join5(contentDir, NOTES);
|
|
420
|
+
const exclusionsPath = join5(notesDir, "exclusions.md");
|
|
393
421
|
if (!existsSync4(exclusionsPath)) return [];
|
|
394
422
|
const content = readFileSync4(exclusionsPath, "utf8");
|
|
395
423
|
const items = parseExclusions(content);
|
|
396
424
|
if (items.length === 0) return [];
|
|
397
|
-
const skillsDir =
|
|
398
|
-
const postsDir =
|
|
425
|
+
const skillsDir = join5(contentDir, SKILLS);
|
|
426
|
+
const postsDir = join5(contentDir, POSTS);
|
|
399
427
|
const filesToScan = [
|
|
400
428
|
...listMarkdownFiles(skillsDir),
|
|
401
429
|
...listMarkdownFiles(postsDir)
|
|
@@ -436,11 +464,11 @@ function validate(root, profile) {
|
|
|
436
464
|
if (a.file > b.file) return 1;
|
|
437
465
|
return (a.line ?? 0) - (b.line ?? 0);
|
|
438
466
|
});
|
|
439
|
-
const contentDir =
|
|
467
|
+
const contentDir = join5(root, profile.content_dir ?? "graph");
|
|
440
468
|
const scannedFiles = /* @__PURE__ */ new Set([
|
|
441
|
-
...listMarkdownFiles(
|
|
442
|
-
...listMarkdownFiles(
|
|
443
|
-
...listMarkdownFiles(
|
|
469
|
+
...listMarkdownFiles(join5(contentDir, NOTES)),
|
|
470
|
+
...listMarkdownFiles(join5(contentDir, SKILLS)),
|
|
471
|
+
...listMarkdownFiles(join5(contentDir, POSTS))
|
|
444
472
|
]);
|
|
445
473
|
return {
|
|
446
474
|
ok: allIssues.length === 0,
|
|
@@ -574,18 +602,11 @@ async function runInstall(cwd) {
|
|
|
574
602
|
}),
|
|
575
603
|
modules: () => p.multiselect({
|
|
576
604
|
message: `Which modules do you want to add?${MULTISELECT_HINT}`,
|
|
577
|
-
options:
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
value: "resume",
|
|
585
|
-
label: "Resume",
|
|
586
|
-
hint: chalk.hex("#64748B")("keep your resume current from your graph")
|
|
587
|
-
}
|
|
588
|
-
],
|
|
605
|
+
options: MODULES.map((m) => ({
|
|
606
|
+
value: m.id,
|
|
607
|
+
label: m.label,
|
|
608
|
+
hint: chalk.hex("#64748B")(m.hint)
|
|
609
|
+
})),
|
|
589
610
|
required: false
|
|
590
611
|
})
|
|
591
612
|
},
|
|
@@ -641,11 +662,11 @@ async function runInstall(cwd) {
|
|
|
641
662
|
p.outro(chalk.hex("#94A3B8")("Run claude from inside your patina to get started."));
|
|
642
663
|
}
|
|
643
664
|
function writeProfile(cwd, profile) {
|
|
644
|
-
const full =
|
|
665
|
+
const full = join6(cwd, "profile.yaml");
|
|
645
666
|
writeFileSync3(full, yaml3.dump(profile), "utf8");
|
|
646
667
|
}
|
|
647
668
|
function removeManagedFileIfUnmodified(targetDir, rel, stored) {
|
|
648
|
-
const fullPath =
|
|
669
|
+
const fullPath = join6(targetDir, rel);
|
|
649
670
|
if (!existsSync5(fullPath)) return "deleted";
|
|
650
671
|
const currentHash = hashFile(fullPath);
|
|
651
672
|
const storedHash = stored[rel];
|
|
@@ -688,6 +709,47 @@ async function runUpdate(cwd) {
|
|
|
688
709
|
await runValidate(cwd, profile);
|
|
689
710
|
}
|
|
690
711
|
}
|
|
712
|
+
function applyProfileUpdate(cwd, profile, fields) {
|
|
713
|
+
const updatedProfile = {
|
|
714
|
+
...profile,
|
|
715
|
+
name: fields.name.trim(),
|
|
716
|
+
title: fields.title.trim(),
|
|
717
|
+
role_description: fields.roleDescription.trim() || void 0,
|
|
718
|
+
job_description_url: fields.jobDescriptionUrl.trim() || void 0,
|
|
719
|
+
work: {
|
|
720
|
+
self_employed: fields.selfEmployed,
|
|
721
|
+
company_name: fields.companyName.trim() || (fields.selfEmployed ? "Freelance" : ""),
|
|
722
|
+
website: fields.website.trim() || void 0,
|
|
723
|
+
company_description: fields.companyDescription.trim() || void 0
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
const vars = profileToVars(updatedProfile);
|
|
727
|
+
const stored = profile._checksums ?? {};
|
|
728
|
+
const newChecksums = {};
|
|
729
|
+
const files = [
|
|
730
|
+
...baseManagedFiles(vars, updatedProfile.editor, cwd),
|
|
731
|
+
...updatedProfile.modules.flatMap((m) => moduleManagedFiles(m, vars))
|
|
732
|
+
];
|
|
733
|
+
const updated = [];
|
|
734
|
+
const skipped = [];
|
|
735
|
+
for (const [rel, content] of files) {
|
|
736
|
+
const { outcome, checksum } = writeManagedFile(cwd, rel, content, stored);
|
|
737
|
+
newChecksums[rel] = checksum;
|
|
738
|
+
if (outcome === "skipped") {
|
|
739
|
+
skipped.push(rel);
|
|
740
|
+
} else {
|
|
741
|
+
updated.push(rel);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
for (const [rel, hash] of Object.entries(stored)) {
|
|
745
|
+
if (!(rel in newChecksums)) {
|
|
746
|
+
newChecksums[rel] = hash;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
updatedProfile._checksums = newChecksums;
|
|
750
|
+
writeProfile(cwd, updatedProfile);
|
|
751
|
+
return { profile: updatedProfile, updated, skipped };
|
|
752
|
+
}
|
|
691
753
|
async function runUpdateProfile(cwd, profile) {
|
|
692
754
|
console.log("");
|
|
693
755
|
console.log(` ${label("Update personal info")}`);
|
|
@@ -739,44 +801,16 @@ async function runUpdateProfile(cwd, profile) {
|
|
|
739
801
|
},
|
|
740
802
|
{ onCancel }
|
|
741
803
|
);
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
company_description: work.companyDescription?.trim() || void 0
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
const vars = profileToVars(updatedProfile);
|
|
756
|
-
const stored = profile._checksums ?? {};
|
|
757
|
-
const newChecksums = {};
|
|
758
|
-
const files = [
|
|
759
|
-
...baseManagedFiles(vars, updatedProfile.editor, cwd),
|
|
760
|
-
...updatedProfile.modules.flatMap((m) => moduleManagedFiles(m, vars))
|
|
761
|
-
];
|
|
762
|
-
const updated = [];
|
|
763
|
-
const skipped = [];
|
|
764
|
-
for (const [rel, content] of files) {
|
|
765
|
-
const { outcome, checksum } = writeManagedFile(cwd, rel, content, stored);
|
|
766
|
-
newChecksums[rel] = checksum;
|
|
767
|
-
if (outcome === "skipped") {
|
|
768
|
-
skipped.push(rel);
|
|
769
|
-
} else {
|
|
770
|
-
updated.push(rel);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
for (const [rel, hash] of Object.entries(stored)) {
|
|
774
|
-
if (!(rel in newChecksums)) {
|
|
775
|
-
newChecksums[rel] = hash;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
updatedProfile._checksums = newChecksums;
|
|
779
|
-
writeProfile(cwd, updatedProfile);
|
|
804
|
+
const { updated, skipped } = applyProfileUpdate(cwd, profile, {
|
|
805
|
+
name: identity.name,
|
|
806
|
+
title: identity.title ?? "",
|
|
807
|
+
roleDescription: identity.roleDescription ?? "",
|
|
808
|
+
jobDescriptionUrl: identity.jobDescriptionUrl ?? "",
|
|
809
|
+
selfEmployed,
|
|
810
|
+
companyName: work.companyName ?? "",
|
|
811
|
+
website: work.website ?? "",
|
|
812
|
+
companyDescription: work.companyDescription ?? ""
|
|
813
|
+
});
|
|
780
814
|
const summaryLines = [];
|
|
781
815
|
if (updated.length > 0) {
|
|
782
816
|
summaryLines.push(chalk.hex("#94A3B8")(`Updated: ${updated.join(", ")}`));
|
|
@@ -787,96 +821,101 @@ async function runUpdateProfile(cwd, profile) {
|
|
|
787
821
|
p.note(summaryLines.join("\n"), label("Done"));
|
|
788
822
|
p.outro(chalk.hex("#94A3B8")("Profile updated."));
|
|
789
823
|
}
|
|
790
|
-
|
|
791
|
-
const currentModules = profile.modules ?? [];
|
|
792
|
-
const selected = await p.multiselect({
|
|
793
|
-
message: `Which modules do you want active?${MULTISELECT_HINT}`,
|
|
794
|
-
options: [
|
|
795
|
-
{
|
|
796
|
-
value: "linkedin",
|
|
797
|
-
label: "LinkedIn",
|
|
798
|
-
hint: chalk.hex("#64748B")("draft and refine your LinkedIn profile")
|
|
799
|
-
},
|
|
800
|
-
{
|
|
801
|
-
value: "resume",
|
|
802
|
-
label: "Resume",
|
|
803
|
-
hint: chalk.hex("#64748B")("keep your resume current from your graph")
|
|
804
|
-
}
|
|
805
|
-
],
|
|
806
|
-
initialValues: currentModules,
|
|
807
|
-
required: false
|
|
808
|
-
});
|
|
809
|
-
if (p.isCancel(selected)) {
|
|
810
|
-
p.cancel(chalk.hex("#94A3B8")("No changes made."));
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
const selectedModules = Array.isArray(selected) ? selected : [];
|
|
814
|
-
const toAdd = selectedModules.filter((m) => !currentModules.includes(m));
|
|
815
|
-
const toRemove = currentModules.filter((m) => !selectedModules.includes(m));
|
|
816
|
-
if (toAdd.length === 0 && toRemove.length === 0) {
|
|
817
|
-
p.outro(chalk.hex("#94A3B8")("No changes \u2014 modules unchanged."));
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
824
|
+
function applyModuleChanges(cwd, profile, toAdd, toRemove, moduleInputs) {
|
|
820
825
|
const stored = profile._checksums ?? {};
|
|
821
826
|
const newChecksums = { ...stored };
|
|
822
|
-
|
|
823
|
-
const
|
|
827
|
+
let updatedProfile = { ...profile, modules: [...profile.modules ?? []] };
|
|
828
|
+
const added = [];
|
|
824
829
|
const skippedFiles = [];
|
|
825
|
-
const
|
|
826
|
-
const
|
|
830
|
+
const deleted = [];
|
|
831
|
+
const kept = [];
|
|
827
832
|
for (const module of toAdd) {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
placeholder: "https://linkedin.com/in/yourname (optional)"
|
|
832
|
-
});
|
|
833
|
-
if (p.isCancel(url)) {
|
|
834
|
-
p.cancel(chalk.hex("#94A3B8")("No changes made."));
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
if (typeof url === "string" && url.trim()) {
|
|
838
|
-
updatedProfile.linkedin = { profile_url: url.trim() };
|
|
839
|
-
}
|
|
833
|
+
const def = getModule(module);
|
|
834
|
+
if (def?.onAdd) {
|
|
835
|
+
updatedProfile = def.onAdd(updatedProfile, moduleInputs?.[module] ?? {});
|
|
840
836
|
}
|
|
841
837
|
const vars = profileToVars(updatedProfile);
|
|
842
838
|
const contentDir = updatedProfile.content_dir;
|
|
843
839
|
for (const [rel, content] of moduleManagedFiles(module, vars)) {
|
|
844
|
-
const { outcome, checksum } = writeManagedFile(cwd, rel, content,
|
|
840
|
+
const { outcome, checksum } = writeManagedFile(cwd, rel, content, newChecksums);
|
|
845
841
|
newChecksums[rel] = checksum;
|
|
846
842
|
if (outcome === "skipped") {
|
|
847
843
|
skippedFiles.push(rel);
|
|
848
844
|
} else {
|
|
849
|
-
|
|
845
|
+
added.push(rel);
|
|
850
846
|
}
|
|
851
847
|
}
|
|
852
848
|
for (const [relativePath, content] of moduleContentFiles(module, vars, contentDir)) {
|
|
853
|
-
const fullPath =
|
|
849
|
+
const fullPath = join6(cwd, relativePath);
|
|
854
850
|
if (!existsSync5(fullPath)) {
|
|
855
|
-
mkdirSync3(
|
|
851
|
+
mkdirSync3(dirname4(fullPath), { recursive: true });
|
|
856
852
|
writeFileSync3(fullPath, content, "utf8");
|
|
857
|
-
|
|
853
|
+
added.push(relativePath);
|
|
858
854
|
}
|
|
859
855
|
}
|
|
860
|
-
updatedProfile.modules
|
|
856
|
+
if (!updatedProfile.modules.includes(module)) {
|
|
857
|
+
updatedProfile.modules = [...updatedProfile.modules, module];
|
|
858
|
+
}
|
|
861
859
|
}
|
|
862
860
|
for (const module of toRemove) {
|
|
863
|
-
const
|
|
861
|
+
const def = getModule(module);
|
|
862
|
+
const managedRels = def?.managedPaths ?? [];
|
|
864
863
|
for (const rel of managedRels) {
|
|
865
864
|
const result = removeManagedFileIfUnmodified(cwd, rel, stored);
|
|
866
865
|
if (result === "deleted") {
|
|
867
|
-
|
|
866
|
+
deleted.push(rel);
|
|
868
867
|
delete newChecksums[rel];
|
|
869
868
|
} else {
|
|
870
|
-
|
|
869
|
+
kept.push(rel);
|
|
871
870
|
}
|
|
872
871
|
}
|
|
873
872
|
updatedProfile.modules = updatedProfile.modules.filter((m) => m !== module);
|
|
874
|
-
if (
|
|
875
|
-
|
|
873
|
+
if (def?.onRemove) {
|
|
874
|
+
updatedProfile = def.onRemove(updatedProfile);
|
|
876
875
|
}
|
|
877
876
|
}
|
|
878
877
|
updatedProfile._checksums = newChecksums;
|
|
879
878
|
writeProfile(cwd, updatedProfile);
|
|
879
|
+
return { profile: updatedProfile, added, skipped: skippedFiles, deleted, kept };
|
|
880
|
+
}
|
|
881
|
+
async function runUpdateModules(cwd, profile) {
|
|
882
|
+
const currentModules = profile.modules ?? [];
|
|
883
|
+
const selected = await p.multiselect({
|
|
884
|
+
message: `Which modules do you want active?${MULTISELECT_HINT}`,
|
|
885
|
+
options: MODULES.map((m) => ({
|
|
886
|
+
value: m.id,
|
|
887
|
+
label: m.label,
|
|
888
|
+
hint: chalk.hex("#64748B")(m.hint)
|
|
889
|
+
})),
|
|
890
|
+
initialValues: currentModules,
|
|
891
|
+
required: false
|
|
892
|
+
});
|
|
893
|
+
if (p.isCancel(selected)) {
|
|
894
|
+
p.cancel(chalk.hex("#94A3B8")("No changes made."));
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const selectedModules = Array.isArray(selected) ? selected : [];
|
|
898
|
+
const toAdd = selectedModules.filter((m) => !currentModules.includes(m));
|
|
899
|
+
const toRemove = currentModules.filter((m) => !selectedModules.includes(m));
|
|
900
|
+
if (toAdd.length === 0 && toRemove.length === 0) {
|
|
901
|
+
p.outro(chalk.hex("#94A3B8")("No changes \u2014 modules unchanged."));
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
let liProfileUrl;
|
|
905
|
+
if (toAdd.includes("linkedin") && !profile.linkedin?.profile_url) {
|
|
906
|
+
const url = await p.text({
|
|
907
|
+
message: "What's your LinkedIn profile URL?",
|
|
908
|
+
placeholder: "https://linkedin.com/in/yourname (optional)"
|
|
909
|
+
});
|
|
910
|
+
if (p.isCancel(url)) {
|
|
911
|
+
p.cancel(chalk.hex("#94A3B8")("No changes made."));
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (typeof url === "string" && url.trim()) {
|
|
915
|
+
liProfileUrl = url.trim();
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
const { added: addedFiles, skipped: skippedFiles, deleted: deletedFiles, kept: keptFiles } = applyModuleChanges(cwd, profile, toAdd, toRemove, { linkedin: { liProfileUrl } });
|
|
880
919
|
const summaryLines = [];
|
|
881
920
|
if (addedFiles.length > 0) summaryLines.push(chalk.hex("#94A3B8")(`Added: ${addedFiles.join(", ")}`));
|
|
882
921
|
if (skippedFiles.length > 0) summaryLines.push(chalk.hex("#FFAB2E")(`Kept your edits: ${skippedFiles.join(", ")}`));
|