careervivid 1.6.0 → 1.8.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/api.d.ts CHANGED
@@ -20,6 +20,7 @@ export interface PublishResult {
20
20
  postId: string;
21
21
  url: string;
22
22
  message: string;
23
+ title?: string;
23
24
  }
24
25
  export interface VerifyResult {
25
26
  userId: string;
@@ -39,6 +40,8 @@ export interface ApiError {
39
40
  }[];
40
41
  }
41
42
  export declare function publishPost(payload: PublishPayload, dryRun?: boolean): Promise<PublishResult | ApiError>;
43
+ export declare function getPost(postId: string): Promise<PublishResult | ApiError>;
44
+ export declare function updatePost(postId: string, payload: Partial<PublishPayload>): Promise<PublishResult | ApiError>;
42
45
  /**
43
46
  * Verify an API key against the /verifyAuth endpoint.
44
47
  * Optionally accepts a specific key (for set-key validation).
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC;AAChD,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,UAAU,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACrB,OAAO,EAAE,IAAI,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAmED,wBAAsB,WAAW,CAC7B,OAAO,EAAE,cAAc,EACvB,MAAM,UAAQ,GACf,OAAO,CAAC,aAAa,GAAG,QAAQ,CAAC,CAgBnC;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,QAAQ,CAAC,CAwC9E;AAED;;GAEG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAMzE;AAED,wBAAsB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,CAEnJ;AAED,wBAAsB,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,CAEnK;AAED,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,CAEpK;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,CAEvJ;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,QAAQ,CAEpD"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { getApiKey, getApiUrl } from "./config.js";
8
8
  // ── Helpers ───────────────────────────────────────────────────────────────────
9
- const CLI_VERSION = "1.6.0";
9
+ const CLI_VERSION = "1.8.0";
10
10
  function requireApiKey() {
11
11
  const key = getApiKey();
12
12
  if (!key) {
@@ -73,6 +73,12 @@ export async function publishPost(payload, dryRun = false) {
73
73
  }
74
74
  return apiRequest("POST", "publish", payload);
75
75
  }
76
+ export async function getPost(postId) {
77
+ return apiRequest("GET", `publish/${postId}`);
78
+ }
79
+ export async function updatePost(postId, payload) {
80
+ return apiRequest("PATCH", "publish", { ...payload, postId });
81
+ }
76
82
  /**
77
83
  * Verify an API key against the /verifyAuth endpoint.
78
84
  * Optionally accepts a specific key (for set-key validation).
@@ -1 +1 @@
1
- {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/commands/publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkIpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8F7D"}
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/commands/publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4MpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6J7D"}
@@ -53,12 +53,34 @@ function getFiles(dir, recursive) {
53
53
  }
54
54
  async function publishSingleFile(filePath, content, opts, jsonMode) {
55
55
  const dryRun = !!opts.dryRun;
56
+ // ── Parse Frontmatter ───────────────────────────────────────────
57
+ let postId = undefined;
58
+ let cleanContent = content;
59
+ // Robust frontmatter extraction (multiline, flexible spacing)
60
+ // Matches --- start, any non-greedy content, and --- end with optional whitespace/newlines
61
+ const frontmatterMatch = content.match(/^---\s*[\r\n]+([\s\S]*?)[\r\n]+---/);
62
+ if (frontmatterMatch) {
63
+ const fm = frontmatterMatch[1];
64
+ // Match postId with potential leading/trailing whitespace or quotes
65
+ const idMatch = fm.match(/postId:\s*["']?([a-zA-Z0-9_-]+)["']?/i);
66
+ postId = idMatch?.[1];
67
+ // Remove the frontmatter block AND any trailing whitespace/newlines
68
+ const afterFm = content.slice(frontmatterMatch[0].length);
69
+ cleanContent = afterFm.replace(/^\s+/, "");
70
+ }
71
+ // Fail if onlyUpdate is set but no postId is found
72
+ if (opts.onlyUpdate && !postId) {
73
+ if (!jsonMode) {
74
+ console.error(chalk.red(`\n ✖ Missing postId frontmatter in ${filePath}. Use 'cv publish' to create a new post.`));
75
+ }
76
+ return { success: false };
77
+ }
56
78
  const format = opts.format ||
57
79
  (filePath !== "stdin" ? inferFormat(filePath) : "markdown");
58
80
  const type = opts.type || inferType(format);
59
81
  let title = opts.title || "";
60
82
  if (!title && format === "markdown") {
61
- const firstHeading = content.match(/^#\s+(.+)$/m);
83
+ const firstHeading = cleanContent.match(/^[#]{1,2}\s+(.+)$/m);
62
84
  if (firstHeading) {
63
85
  title = firstHeading[1].trim();
64
86
  }
@@ -86,21 +108,68 @@ async function publishSingleFile(filePath, content, opts, jsonMode) {
86
108
  type,
87
109
  dataFormat: format,
88
110
  title,
89
- content,
111
+ content: cleanContent,
90
112
  tags,
91
113
  ...(opts.cover ? { coverImage: opts.cover } : {}),
92
114
  ...(opts.official ? { isOfficialPost: true } : {}),
93
115
  };
94
- const result = await publishPost(payload, dryRun);
116
+ let result;
117
+ if (postId && !dryRun) {
118
+ const { updatePost, getPost } = await import("../api.js");
119
+ // Best Practice: Fetch remote title to verify identity before overwrite
120
+ const remotePost = await getPost(postId);
121
+ if (!isApiError(remotePost)) {
122
+ const remoteTitle = remotePost.title;
123
+ // Case-insensitive title comparison to catch potential mixups
124
+ if (title && remoteTitle && title.toLowerCase() !== remoteTitle.toLowerCase()) {
125
+ if (jsonMode) {
126
+ console.error(chalk.red(`\n ✖ Title mismatch for ${postId}. Local: "${title}", Remote: "${remoteTitle}". Aborting.`));
127
+ return { success: false };
128
+ }
129
+ else {
130
+ const enquirer = (await import("enquirer"));
131
+ const prompt = enquirer.default?.prompt || enquirer.prompt;
132
+ const answers = await prompt({
133
+ type: "confirm",
134
+ name: "confirm",
135
+ message: `Title Mismatch! Local: "${chalk.cyan(title)}", Remote: "${chalk.yellow(remoteTitle)}". Continue?`,
136
+ initial: false
137
+ });
138
+ if (!answers.confirm) {
139
+ return { success: false };
140
+ }
141
+ }
142
+ }
143
+ }
144
+ result = await updatePost(postId, payload);
145
+ }
146
+ else {
147
+ result = await publishPost(payload, dryRun);
148
+ }
95
149
  if (isApiError(result)) {
96
150
  if (jsonMode) {
97
151
  handleApiError(result, true);
98
152
  }
99
153
  else {
100
- console.error(chalk.red(`\n ✖ Failed to publish ${filePath}: ${result.message}`));
154
+ console.error(chalk.red(`\n ✖ Failed to ${postId ? "update" : "publish"} ${filePath}: ${result.message}`));
101
155
  }
102
156
  return { success: false };
103
157
  }
158
+ // ── Write back postId if it's new ────────────────────────────────
159
+ if (!postId && !dryRun && filePath !== "stdin" && result.postId) {
160
+ const newFm = `---\npostId: ${result.postId}\n---\n\n`;
161
+ const updatedFileContent = newFm + content;
162
+ try {
163
+ const { writeFileSync } = await import("fs");
164
+ writeFileSync(filePath, updatedFileContent, "utf-8");
165
+ }
166
+ catch (err) {
167
+ // Non-fatal error
168
+ if (!jsonMode) {
169
+ console.warn(chalk.yellow(` ⚠ Could not write postId to ${filePath}`));
170
+ }
171
+ }
172
+ }
104
173
  return {
105
174
  success: true,
106
175
  url: result.url,
@@ -198,4 +267,62 @@ export function registerPublishCommand(program) {
198
267
  console.log(`\n ${chalk.yellow("Dry run complete.")} Validated ${chalk.bold(fileList.length)} files.\n`);
199
268
  }
200
269
  });
270
+ program
271
+ .command("update [files...]")
272
+ .description("Update existing content on CareerVivid (requires postId in frontmatter)")
273
+ .option("-t, --title <title>", "Override post title")
274
+ .option("--tags <tags>", "Comma-separated tags")
275
+ .option("--dry-run", "Validate without updating")
276
+ .option("--json", "Machine-readable output")
277
+ .option("-r, --recursive", "Recursively scan directories")
278
+ .action(async (files, opts) => {
279
+ const extendedOpts = { ...opts, onlyUpdate: true };
280
+ const jsonMode = !!opts.json;
281
+ const dryRun = !!opts.dryRun;
282
+ let fileList = [];
283
+ if (files.length === 0) {
284
+ printError("No files specified to update.", undefined, jsonMode);
285
+ process.exit(1);
286
+ }
287
+ for (const arg of files) {
288
+ try {
289
+ const stat = lstatSync(arg);
290
+ if (stat.isDirectory()) {
291
+ fileList = fileList.concat(getFiles(arg, !!opts.recursive));
292
+ }
293
+ else {
294
+ fileList.push(arg);
295
+ }
296
+ }
297
+ catch (err) {
298
+ printError(`Cannot find path: ${arg}`, undefined, jsonMode);
299
+ process.exit(1);
300
+ }
301
+ }
302
+ if (!jsonMode && !dryRun) {
303
+ console.log(`\n ${chalk.bold("Preparing to update")} ${chalk.cyan(fileList.length)} ${fileList.length === 1 ? "file" : "files"}...`);
304
+ }
305
+ const results = [];
306
+ let successCount = 0;
307
+ for (const filePath of fileList) {
308
+ const content = readFileSync(filePath, "utf-8");
309
+ const spinner = (!jsonMode && !dryRun) ? ora(`Updating ${chalk.cyan(filePath)}...`).start() : null;
310
+ const res = await publishSingleFile(filePath, content, extendedOpts, jsonMode);
311
+ spinner?.stop();
312
+ if (res.success) {
313
+ successCount++;
314
+ results.push({ file: filePath, ...res });
315
+ if (!jsonMode && !dryRun) {
316
+ console.log(` ${chalk.green("✔")} Updated: ${chalk.bold(res.title)}`);
317
+ console.log(` ${chalk.dim(res.url)}`);
318
+ }
319
+ }
320
+ }
321
+ if (jsonMode) {
322
+ console.log(JSON.stringify(results, null, 2));
323
+ }
324
+ else if (!dryRun) {
325
+ console.log(`\n ${chalk.green("Done!")} Successfully updated ${chalk.bold(successCount)} of ${chalk.bold(fileList.length)} files.\n`);
326
+ }
327
+ });
201
328
  }
@@ -10,7 +10,7 @@ import { spawn } from "child_process";
10
10
  import { getHelpHeader } from "../branding.js";
11
11
  export function registerUpdateCommand(program) {
12
12
  program
13
- .command("update")
13
+ .command("upgrade")
14
14
  .description("Upgrade the CareerVivid CLI to the latest version")
15
15
  .action(async () => {
16
16
  console.log(getHelpHeader());
package/dist/index.d.ts CHANGED
@@ -15,7 +15,8 @@
15
15
  * cv config show Print current configuration
16
16
  * cv config set <key> <value> Update a config value
17
17
  * cv config get <key> Print a config value
18
- * cv update Upgrade the CLI to the latest version
18
+ * cv upgrade Upgrade the CLI to the latest version
19
+ * cv update [files...] Update existing content on CareerVivid
19
20
  * cv --help / cv --version
20
21
  */
21
22
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;GAkBG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;GAmBG"}
package/dist/index.js CHANGED
@@ -15,7 +15,8 @@
15
15
  * cv config show Print current configuration
16
16
  * cv config set <key> <value> Update a config value
17
17
  * cv config get <key> Print a config value
18
- * cv update Upgrade the CLI to the latest version
18
+ * cv upgrade Upgrade the CLI to the latest version
19
+ * cv update [files...] Update existing content on CareerVivid
19
20
  * cv --help / cv --version
20
21
  */
21
22
  import { Command } from "commander";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "careervivid",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Official CLI for CareerVivid — publish articles, diagrams, and portfolio updates from your terminal or AI agent",
5
5
  "type": "module",
6
6
  "bin": {