careervivid 1.2.0 → 1.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 +45 -1
- package/dist/api.js +6 -6
- package/dist/commands/jobs.d.ts +3 -0
- package/dist/commands/jobs.d.ts.map +1 -0
- package/dist/commands/jobs.js +91 -0
- package/dist/commands/profile.d.ts +3 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +101 -0
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +146 -88
- package/dist/commands/workspace.d.ts +3 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +39 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/index.js +8 -2
- package/dist/utils/gws-runner.d.ts +18 -0
- package/dist/utils/gws-runner.d.ts.map +1 -0
- package/dist/utils/gws-runner.js +80 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
- [Commands](#commands)
|
|
15
15
|
- [cv publish](#cv-publish)
|
|
16
16
|
- [cv whiteboard](#cv-whiteboard)
|
|
17
|
+
- [cv workspace](#cv-workspace)
|
|
18
|
+
- [cv profile](#cv-profile)
|
|
19
|
+
- [cv jobs](#cv-jobs)
|
|
17
20
|
- [cv auth](#cv-auth)
|
|
18
21
|
- [cv config](#cv-config)
|
|
19
22
|
- [Whiteboard Templates](#whiteboard-templates)
|
|
@@ -116,6 +119,46 @@ Print all available built-in Mermaid templates. (Also available as `cv whiteboar
|
|
|
116
119
|
cv list-templates
|
|
117
120
|
```
|
|
118
121
|
|
|
122
|
+
### `cv workspace` (or `cv gws`)
|
|
123
|
+
|
|
124
|
+
Manage your Google Workspace integration.
|
|
125
|
+
|
|
126
|
+
| Subcommand | Description |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `cv workspace check` | Verify GWS CLI installation and OAuth status |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### `cv profile`
|
|
133
|
+
|
|
134
|
+
Manage your CareerVivid profile.
|
|
135
|
+
|
|
136
|
+
| Subcommand | Description |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `cv profile export` | Export resume data to other formats (e.g. `gdoc`) |
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Export to Google Docs
|
|
142
|
+
cv profile export --format gdoc
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### `cv jobs`
|
|
148
|
+
|
|
149
|
+
Automate your job application tracking.
|
|
150
|
+
|
|
151
|
+
| Subcommand | Description |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `cv jobs sync-gmail` | Scan Gmail for applications and sync to a Google Sheet |
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Sync recent applications to Google Sheets
|
|
157
|
+
cv jobs sync-gmail
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
119
162
|
### `cv whiteboard`
|
|
120
163
|
|
|
121
164
|
Grouped commands for Mermaid architecture diagrams.
|
|
@@ -211,7 +254,8 @@ The CLI is designed to be **AI-agent-friendly**. Use `--json` for structured out
|
|
|
211
254
|
You are an expert technical writer and senior software architect.
|
|
212
255
|
Your secondary directive — after writing and fixing code — is to
|
|
213
256
|
proactively help the user document their work and build their
|
|
214
|
-
CareerVivid portfolio using the local `cv` CLI.
|
|
257
|
+
CareerVivid portfolio using the local `cv` CLI. The platform supports
|
|
258
|
+
the latest Gemini 2.5 and 3.0 models for high-quality content generation.
|
|
215
259
|
|
|
216
260
|
## 1. Triggers (When to Propose a Post)
|
|
217
261
|
Do not wait for the user to ask. Propose a CareerVivid post when:
|
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.
|
|
9
|
+
const CLI_VERSION = "1.4.0";
|
|
10
10
|
function requireApiKey() {
|
|
11
11
|
const key = getApiKey();
|
|
12
12
|
if (!key) {
|
|
@@ -71,7 +71,7 @@ export async function publishPost(payload, dryRun = false) {
|
|
|
71
71
|
message: "Dry run passed. No post was created.",
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
return apiRequest("POST", "", payload);
|
|
74
|
+
return apiRequest("POST", "publish", payload);
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Verify an API key against the /verifyAuth endpoint.
|
|
@@ -131,16 +131,16 @@ export async function pingAuth() {
|
|
|
131
131
|
return { ok: true };
|
|
132
132
|
}
|
|
133
133
|
export async function initPortfolio(title, templateId) {
|
|
134
|
-
return apiRequest("POST", "
|
|
134
|
+
return apiRequest("POST", "portfolio/init", { title, templateId });
|
|
135
135
|
}
|
|
136
136
|
export async function updatePortfolioProjects(portfolioId, projects, techStack) {
|
|
137
|
-
return apiRequest("PATCH", "
|
|
137
|
+
return apiRequest("PATCH", "portfolio/projects", { portfolioId, projects, techStack });
|
|
138
138
|
}
|
|
139
139
|
export async function updatePortfolioHero(portfolioId, hero, theme, seoMetadata) {
|
|
140
|
-
return apiRequest("PATCH", "
|
|
140
|
+
return apiRequest("PATCH", "portfolio/hero", { portfolioId, hero, theme, seoMetadata });
|
|
141
141
|
}
|
|
142
142
|
export async function uploadPortfolioAsset(image, path, mimeType) {
|
|
143
|
-
return apiRequest("POST", "
|
|
143
|
+
return apiRequest("POST", "portfolio/assets", { image, path, mimeType });
|
|
144
144
|
}
|
|
145
145
|
export function isApiError(v) {
|
|
146
146
|
return typeof v === "object" && v !== null && v.isError === true;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../src/commands/jobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QA6GnD"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import boxen from "boxen";
|
|
4
|
+
import { checkGwsReady, runGwsCommand } from "../utils/gws-runner.js";
|
|
5
|
+
import { printError } from "../output.js";
|
|
6
|
+
export function registerJobsCommand(program) {
|
|
7
|
+
const jobsCmd = program
|
|
8
|
+
.command("jobs")
|
|
9
|
+
.description("Automate and track your job applications");
|
|
10
|
+
jobsCmd
|
|
11
|
+
.command("sync-gmail")
|
|
12
|
+
.description("Scan your Gmail for job applications and generate a tracking Sheet")
|
|
13
|
+
.action(async () => {
|
|
14
|
+
const isJson = process.argv.includes("--json");
|
|
15
|
+
if (!isJson) {
|
|
16
|
+
console.log(`\n ${chalk.bold("Job Tracker: Syncing Gmail to Google Sheets")}\n`);
|
|
17
|
+
}
|
|
18
|
+
// 1. Verify GWS CLI is available
|
|
19
|
+
const isReady = await checkGwsReady();
|
|
20
|
+
if (!isReady) {
|
|
21
|
+
printError("Google Workspace CLI is not configured. Run 'cv workspace check'.", undefined, isJson);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
// 2. Search Gmail for Application Emails
|
|
25
|
+
const gmailSpinner = ora("Scanning Gmail for recent applications...").start();
|
|
26
|
+
// Note: In a real app we would paginate, but for the demo we'll fetch the top 5
|
|
27
|
+
const query = encodeURIComponent("subject:application OR subject:applied OR subject:\"thank you for applying\"");
|
|
28
|
+
const listRes = await runGwsCommand(`gmail users messages list --params '{"userId": "me", "maxResults": 5, "q": "${query}"}'`);
|
|
29
|
+
if (!listRes.success) {
|
|
30
|
+
gmailSpinner.fail("Failed to read Gmail.");
|
|
31
|
+
printError(listRes.error || "Unknown error", undefined, isJson);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const messages = listRes.data?.messages || [];
|
|
35
|
+
if (messages.length === 0) {
|
|
36
|
+
gmailSpinner.info("No recent application emails found.");
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
// Fetch snippets for these messages (mocking AI extraction)
|
|
40
|
+
const extractedJobs = [];
|
|
41
|
+
for (const msg of messages) {
|
|
42
|
+
const msgRes = await runGwsCommand(`gmail users messages get --params '{"userId": "me", "id": "${msg.id}", "format": "metadata"}'`);
|
|
43
|
+
if (msgRes.success && msgRes.data) {
|
|
44
|
+
const snippet = msgRes.data.snippet || "";
|
|
45
|
+
// Mocking AI parse
|
|
46
|
+
extractedJobs.push({
|
|
47
|
+
company: snippet.split(" ")[0] || "Unknown Corp",
|
|
48
|
+
role: "Software Engineer",
|
|
49
|
+
date: new Date().toISOString().split('T')[0]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
gmailSpinner.succeed(`Found ${extractedJobs.length} applications via Gmail.`);
|
|
54
|
+
// 3. Create the Google Sheet Tracker
|
|
55
|
+
const sheetSpinner = ora("Creating Tracker inside Google Sheets...").start();
|
|
56
|
+
const createRes = await runGwsCommand(`sheets spreadsheets create --json '{"properties": {"title": "CareerVivid Job Tracker ${new Date().getFullYear()}"}}'`);
|
|
57
|
+
if (!createRes.success || !createRes.data?.spreadsheetId) {
|
|
58
|
+
sheetSpinner.fail("Failed to create Google Sheet.");
|
|
59
|
+
printError(createRes.error || "Unknown error", undefined, isJson);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const sheetId = createRes.data.spreadsheetId;
|
|
63
|
+
sheetSpinner.succeed(`Created new spreadsheet: ${sheetId}`);
|
|
64
|
+
// 4. Append Data to the Sheet
|
|
65
|
+
const appendSpinner = ora("Writing data to Sheet...").start();
|
|
66
|
+
const values = [
|
|
67
|
+
["Company", "Role", "Date Applied", "Status"], // Header
|
|
68
|
+
...extractedJobs.map(job => [job.company, job.role, job.date, "Applied"]) // Rows
|
|
69
|
+
];
|
|
70
|
+
const payload = JSON.stringify({ values }).replace(/'/g, "");
|
|
71
|
+
const updateRes = await runGwsCommand(`sheets spreadsheets values append \
|
|
72
|
+
--params '{"spreadsheetId": "${sheetId}", "range": "Sheet1!A1", "valueInputOption": "USER_ENTERED"}' \
|
|
73
|
+
--json '${payload}'`);
|
|
74
|
+
if (!updateRes.success) {
|
|
75
|
+
appendSpinner.fail("Failed to write to Google Sheet.");
|
|
76
|
+
printError(updateRes.error || "Unknown validation issue with Sheets API payload.", undefined, isJson);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
appendSpinner.succeed("Tracker updated successfully!");
|
|
80
|
+
// 5. Output Result
|
|
81
|
+
const sheetUrl = `https://docs.google.com/spreadsheets/d/${sheetId}/edit`;
|
|
82
|
+
if (isJson) {
|
|
83
|
+
console.log(JSON.stringify({ success: true, url: sheetUrl, spreadsheetId: sheetId, jobsFound: extractedJobs.length }));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(boxen(`${chalk.bold.green("✔ Sync Complete!")}\n\n` +
|
|
87
|
+
`Your applications have been synced to Google Sheets:\n` +
|
|
88
|
+
`${chalk.cyan.underline(sheetUrl)}`, { padding: 1, borderStyle: "round" }));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/commands/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,QAiHtD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import boxen from "boxen";
|
|
5
|
+
import { checkGwsReady, runGwsCommand } from "../utils/gws-runner.js";
|
|
6
|
+
import { printError } from "../output.js";
|
|
7
|
+
export function registerProfileCommand(program) {
|
|
8
|
+
const profileCmd = program
|
|
9
|
+
.command("profile")
|
|
10
|
+
.description("Manage your CareerVivid developer profile and resume");
|
|
11
|
+
profileCmd
|
|
12
|
+
.command("export")
|
|
13
|
+
.description("Export your resume data to external formats")
|
|
14
|
+
.argument("[file]", "Path to local resume.json (uses mock data if omitted)")
|
|
15
|
+
.requiredOption("--format <fmt>", "Format to export (e.g., 'gdoc')")
|
|
16
|
+
.action(async (file, options) => {
|
|
17
|
+
const isJson = process.argv.includes("--json");
|
|
18
|
+
if (options.format.toLowerCase() !== "gdoc") {
|
|
19
|
+
printError("Currently only '--format gdoc' is supported by this integration.", undefined, isJson);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
if (!isJson) {
|
|
23
|
+
console.log(`\n ${chalk.bold("Exporting CareerVivid Resume to Google Docs")}\n`);
|
|
24
|
+
}
|
|
25
|
+
// 1. Verify GWS CLI is available
|
|
26
|
+
const isReady = await checkGwsReady();
|
|
27
|
+
if (!isReady) {
|
|
28
|
+
printError("Google Workspace CLI is not configured. Run 'cv workspace check'.", undefined, isJson);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// 2. Load Resume Data
|
|
32
|
+
let resumeData = {
|
|
33
|
+
name: "Alex Dev",
|
|
34
|
+
title: "Senior Full Stack Engineer",
|
|
35
|
+
summary: "Passionate developer building AI-first tools.",
|
|
36
|
+
experience: [
|
|
37
|
+
{ role: "Software Engineer", company: "Tech Corp", years: "2022 - Present" }
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
if (file && fs.existsSync(file)) {
|
|
41
|
+
try {
|
|
42
|
+
resumeData = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
printError(`Failed to parse resume file: ${err.message}`, undefined, isJson);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (file) {
|
|
50
|
+
printError(`File not found: ${file}`, undefined, isJson);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const title = `${resumeData.name.replace(/ /g, "_")}_Resume_Export`;
|
|
54
|
+
// 3. Create the Google Doc
|
|
55
|
+
const createSpinner = ora("Creating new Google Doc...").start();
|
|
56
|
+
const createRes = await runGwsCommand(`docs documents create --json '{"title": "${title}"}'`);
|
|
57
|
+
if (!createRes.success || !createRes.data?.documentId) {
|
|
58
|
+
createSpinner.fail("Failed to create Google Doc.");
|
|
59
|
+
printError(createRes.error || "Unknown error", undefined, isJson);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
createSpinner.succeed("Created base document.");
|
|
63
|
+
const docId = createRes.data.documentId;
|
|
64
|
+
// 4. Construct Content (Batch Update)
|
|
65
|
+
const contentSpinner = ora("Formatting resume content...").start();
|
|
66
|
+
// Text to insert (plain text for simplicity in this demo)
|
|
67
|
+
const textToInsert = `${resumeData.name}\n${resumeData.title}\n\nSUMMARY:\n${resumeData.summary}\n\nEXPERIENCE:\n` +
|
|
68
|
+
resumeData.experience.map(e => `- ${e.role} at ${e.company} (${e.years})`).join("\n");
|
|
69
|
+
// Google Docs API uses a specific JSON structure for insertions
|
|
70
|
+
const batchPayload = {
|
|
71
|
+
requests: [
|
|
72
|
+
{
|
|
73
|
+
insertText: {
|
|
74
|
+
location: { index: 1 },
|
|
75
|
+
text: textToInsert
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
};
|
|
80
|
+
// Needs precise escaping for bash if passing directly, or we save to tmp file and pass path.
|
|
81
|
+
// For simplicity, we'll strip single quotes from the payload if any exist.
|
|
82
|
+
const cleanPayload = JSON.stringify(batchPayload).replace(/'/g, "");
|
|
83
|
+
const updateRes = await runGwsCommand(`docs documents batchUpdate --params '{"documentId": "${docId}"}' --json '${cleanPayload}'`);
|
|
84
|
+
if (!updateRes.success) {
|
|
85
|
+
contentSpinner.fail("Failed to insert content.");
|
|
86
|
+
printError(updateRes.error || "Unknown error", undefined, isJson);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
contentSpinner.succeed("Resume content injected.");
|
|
90
|
+
// 5. Output Result
|
|
91
|
+
const docUrl = `https://docs.google.com/document/d/${docId}/edit`;
|
|
92
|
+
if (isJson) {
|
|
93
|
+
console.log(JSON.stringify({ success: true, url: docUrl, documentId: docId }));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(boxen(`${chalk.bold.green("✔ Export Complete!")}\n\n` +
|
|
97
|
+
`Your resume has been successfully generated in Google Docs:\n` +
|
|
98
|
+
`${chalk.cyan.underline(docUrl)}`, { padding: 1, borderStyle: "round" }));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -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;
|
|
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"}
|
package/dist/commands/publish.js
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
* cv publish - --dry-run < article.md Validate without publishing
|
|
10
10
|
* cv publish - --json < article.md Agent-friendly JSON output
|
|
11
11
|
*/
|
|
12
|
-
import { readFileSync } from "fs";
|
|
13
|
-
import { extname } from "path";
|
|
12
|
+
import { readFileSync, lstatSync, readdirSync } from "fs";
|
|
13
|
+
import { extname, join } from "path";
|
|
14
14
|
import chalk from "chalk";
|
|
15
15
|
import ora from "ora";
|
|
16
16
|
import { publishPost, isApiError } from "../api.js";
|
|
17
|
-
import { printError,
|
|
17
|
+
import { printError, handleApiError } from "../output.js";
|
|
18
18
|
function inferFormat(filePath) {
|
|
19
19
|
const ext = extname(filePath).toLowerCase();
|
|
20
20
|
if ([".mmd", ".mermaid"].includes(ext))
|
|
@@ -31,113 +31,171 @@ async function readStdin() {
|
|
|
31
31
|
}
|
|
32
32
|
return Buffer.concat(chunks).toString("utf-8");
|
|
33
33
|
}
|
|
34
|
+
function getFiles(dir, recursive) {
|
|
35
|
+
let results = [];
|
|
36
|
+
const list = readdirSync(dir);
|
|
37
|
+
for (const file of list) {
|
|
38
|
+
const path = join(dir, file);
|
|
39
|
+
const stat = lstatSync(path);
|
|
40
|
+
if (stat && stat.isDirectory()) {
|
|
41
|
+
if (recursive) {
|
|
42
|
+
results = results.concat(getFiles(path, recursive));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const ext = extname(path).toLowerCase();
|
|
47
|
+
if ([".md", ".mmd", ".mermaid"].includes(ext)) {
|
|
48
|
+
results.push(path);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
async function publishSingleFile(filePath, content, opts, jsonMode) {
|
|
55
|
+
const dryRun = !!opts.dryRun;
|
|
56
|
+
const format = opts.format ||
|
|
57
|
+
(filePath !== "stdin" ? inferFormat(filePath) : "markdown");
|
|
58
|
+
const type = opts.type || inferType(format);
|
|
59
|
+
let title = opts.title || "";
|
|
60
|
+
if (!title && format === "markdown") {
|
|
61
|
+
const firstHeading = content.match(/^#\s+(.+)$/m);
|
|
62
|
+
if (firstHeading) {
|
|
63
|
+
title = firstHeading[1].trim();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!title) {
|
|
67
|
+
if (jsonMode || filePath === "stdin") {
|
|
68
|
+
// No interactive prompt for stdin or JSON mode
|
|
69
|
+
title = filePath === "stdin" ? "Untitled Post" : filePath;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const enquirer = (await import("enquirer"));
|
|
73
|
+
const prompt = enquirer.default?.prompt || enquirer.prompt;
|
|
74
|
+
const answers = await prompt({
|
|
75
|
+
type: "input",
|
|
76
|
+
name: "title",
|
|
77
|
+
message: `Post title for ${chalk.cyan(filePath)}`,
|
|
78
|
+
});
|
|
79
|
+
title = answers.title.trim();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const tags = opts.tags
|
|
83
|
+
? opts.tags.split(",").map((t) => t.trim()).filter(Boolean)
|
|
84
|
+
: [];
|
|
85
|
+
const payload = {
|
|
86
|
+
type,
|
|
87
|
+
dataFormat: format,
|
|
88
|
+
title,
|
|
89
|
+
content,
|
|
90
|
+
tags,
|
|
91
|
+
...(opts.cover ? { coverImage: opts.cover } : {}),
|
|
92
|
+
...(opts.official ? { isOfficialPost: true } : {}),
|
|
93
|
+
};
|
|
94
|
+
const result = await publishPost(payload, dryRun);
|
|
95
|
+
if (isApiError(result)) {
|
|
96
|
+
if (jsonMode) {
|
|
97
|
+
handleApiError(result, true);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.error(chalk.red(`\n ✖ Failed to publish ${filePath}: ${result.message}`));
|
|
101
|
+
}
|
|
102
|
+
return { success: false };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
url: result.url,
|
|
107
|
+
postId: result.postId,
|
|
108
|
+
title: title
|
|
109
|
+
};
|
|
110
|
+
}
|
|
34
111
|
export function registerPublishCommand(program) {
|
|
35
112
|
program
|
|
36
|
-
.command("publish [
|
|
113
|
+
.command("publish [files...]")
|
|
37
114
|
.description([
|
|
38
|
-
"Publish
|
|
115
|
+
"Publish files or directories to CareerVivid",
|
|
39
116
|
"",
|
|
40
117
|
" cv publish article.md",
|
|
41
|
-
" cv publish
|
|
42
|
-
"
|
|
118
|
+
" cv publish docs/ --recursive",
|
|
119
|
+
" cv publish part1.md part2.md --tags series",
|
|
120
|
+
" cat article.md | cv publish - --title \"My Article\"",
|
|
43
121
|
].join("\n"))
|
|
44
|
-
.option("-t, --title <title>", "Post title (
|
|
45
|
-
.option("--type <type>", "Post type: article | whiteboard
|
|
46
|
-
.option("--format <format>", "Content format: markdown | mermaid
|
|
47
|
-
.option("--tags <tags>", "Comma-separated tags
|
|
122
|
+
.option("-t, --title <title>", "Post title (per file, inferred if omitted)")
|
|
123
|
+
.option("--type <type>", "Post type: article | whiteboard")
|
|
124
|
+
.option("--format <format>", "Content format: markdown | mermaid")
|
|
125
|
+
.option("--tags <tags>", "Comma-separated tags (max 5)")
|
|
48
126
|
.option("--cover <url>", "URL to a cover image")
|
|
49
127
|
.option("--official", "Publish as CareerVivid Community (admin only)")
|
|
50
|
-
.option("--
|
|
128
|
+
.option("-r, --recursive", "Recursively scan directories")
|
|
129
|
+
.option("--dry-run", "Validate without publishing")
|
|
51
130
|
.option("--json", "Machine-readable JSON output")
|
|
52
|
-
.action(async (
|
|
131
|
+
.action(async (files, opts) => {
|
|
53
132
|
const jsonMode = !!opts.json;
|
|
54
133
|
const dryRun = !!opts.dryRun;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!fileArg || fileArg === "-") {
|
|
59
|
-
if (!jsonMode) {
|
|
60
|
-
console.log(` ${chalk.dim("Reading from stdin... (Ctrl+D to finish)")}`);
|
|
61
|
-
}
|
|
62
|
-
content = await readStdin();
|
|
63
|
-
filePath = "stdin";
|
|
134
|
+
let fileList = [];
|
|
135
|
+
if (files.length === 0 || files.includes("-")) {
|
|
136
|
+
fileList = ["stdin"];
|
|
64
137
|
}
|
|
65
138
|
else {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
139
|
+
for (const arg of files) {
|
|
140
|
+
try {
|
|
141
|
+
const stat = lstatSync(arg);
|
|
142
|
+
if (stat.isDirectory()) {
|
|
143
|
+
fileList = fileList.concat(getFiles(arg, !!opts.recursive));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
fileList.push(arg);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
printError(`Cannot find path: ${arg}`, undefined, jsonMode);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
73
153
|
}
|
|
74
154
|
}
|
|
75
|
-
if (
|
|
76
|
-
printError("
|
|
155
|
+
if (fileList.length === 0) {
|
|
156
|
+
printError("No files found to publish.", undefined, jsonMode);
|
|
77
157
|
process.exit(1);
|
|
78
158
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
(filePath !== "stdin" ? inferFormat(filePath) : "markdown");
|
|
82
|
-
const type = opts.type || inferType(format);
|
|
83
|
-
// ── Infer title from first heading if not provided ─────────────────────
|
|
84
|
-
let title = opts.title || "";
|
|
85
|
-
if (!title && format === "markdown") {
|
|
86
|
-
const firstHeading = content.match(/^#\s+(.+)$/m);
|
|
87
|
-
if (firstHeading) {
|
|
88
|
-
title = firstHeading[1].trim();
|
|
89
|
-
}
|
|
159
|
+
if (!jsonMode && !dryRun) {
|
|
160
|
+
console.log(`\n ${chalk.bold("Preparing to publish")} ${chalk.cyan(fileList.length)} ${fileList.length === 1 ? "file" : "files"}...`);
|
|
90
161
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
162
|
+
const results = [];
|
|
163
|
+
let successCount = 0;
|
|
164
|
+
for (const filePath of fileList) {
|
|
165
|
+
let content;
|
|
166
|
+
if (filePath === "stdin") {
|
|
167
|
+
if (!jsonMode)
|
|
168
|
+
console.log(` ${chalk.dim("Reading from stdin... (Ctrl+D to finish)")}`);
|
|
169
|
+
content = await readStdin();
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
content = readFileSync(filePath, "utf-8");
|
|
173
|
+
}
|
|
174
|
+
if (!content.trim()) {
|
|
175
|
+
if (!jsonMode)
|
|
176
|
+
console.log(chalk.yellow(` ⚠ Skipping empty file: ${filePath}`));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const spinner = (!jsonMode && !dryRun) ? ora(`Publishing ${chalk.cyan(filePath)}...`).start() : null;
|
|
180
|
+
const res = await publishSingleFile(filePath, content, opts, jsonMode);
|
|
181
|
+
spinner?.stop();
|
|
182
|
+
if (res.success) {
|
|
183
|
+
successCount++;
|
|
184
|
+
results.push({ file: filePath, ...res });
|
|
185
|
+
if (!jsonMode && !dryRun) {
|
|
186
|
+
console.log(` ${chalk.green("✔")} Published: ${chalk.bold(res.title)}`);
|
|
187
|
+
console.log(` ${chalk.dim(res.url)}`);
|
|
188
|
+
}
|
|
95
189
|
}
|
|
96
|
-
// Interactive prompt fallback
|
|
97
|
-
const enquirer = (await import("enquirer"));
|
|
98
|
-
const prompt = enquirer.default?.prompt || enquirer.prompt;
|
|
99
|
-
const answers = await prompt({
|
|
100
|
-
type: "input",
|
|
101
|
-
name: "title",
|
|
102
|
-
message: "Post title",
|
|
103
|
-
});
|
|
104
|
-
title = answers.title.trim();
|
|
105
190
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
? opts.tags.split(",").map((t) => t.trim()).filter(Boolean)
|
|
109
|
-
: [];
|
|
110
|
-
if (tags.length > 5) {
|
|
111
|
-
printError("Maximum 5 tags allowed.", undefined, jsonMode);
|
|
112
|
-
process.exit(1);
|
|
191
|
+
if (jsonMode) {
|
|
192
|
+
console.log(JSON.stringify(results, null, 2));
|
|
113
193
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
console.log(`\n ${chalk.yellow("★")} ${chalk.bold("Publishing as")} ${chalk.cyan("CareerVivid Community")} ${chalk.dim("(official post)")}`);
|
|
194
|
+
else if (!dryRun) {
|
|
195
|
+
console.log(`\n ${chalk.green("Done!")} Successfully published ${chalk.bold(successCount)} of ${chalk.bold(fileList.length)} files.\n`);
|
|
117
196
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
dataFormat: format,
|
|
121
|
-
title,
|
|
122
|
-
content,
|
|
123
|
-
tags,
|
|
124
|
-
...(opts.cover ? { coverImage: opts.cover } : {}),
|
|
125
|
-
...(isOfficialPost ? { isOfficialPost: true } : {}),
|
|
126
|
-
};
|
|
127
|
-
// ── Publish ────────────────────────────────────────────────────────────
|
|
128
|
-
const spinner = jsonMode || dryRun
|
|
129
|
-
? null
|
|
130
|
-
: ora(`${dryRun ? "Validating" : "Publishing"} ${type}...`).start();
|
|
131
|
-
const result = await publishPost(payload, dryRun);
|
|
132
|
-
spinner?.stop();
|
|
133
|
-
if (isApiError(result)) {
|
|
134
|
-
handleApiError(result, jsonMode);
|
|
197
|
+
else {
|
|
198
|
+
console.log(`\n ${chalk.yellow("Dry run complete.")} Validated ${chalk.bold(fileList.length)} files.\n`);
|
|
135
199
|
}
|
|
136
|
-
printSuccess({
|
|
137
|
-
Title: title,
|
|
138
|
-
URL: result.url,
|
|
139
|
-
"Post ID": result.postId,
|
|
140
|
-
...(dryRun ? { Note: "Dry run — not published" } : {}),
|
|
141
|
-
}, jsonMode);
|
|
142
200
|
});
|
|
143
201
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/commands/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8C/D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import boxen from "boxen";
|
|
3
|
+
import { checkGwsReady } from "../utils/gws-runner.js";
|
|
4
|
+
import { COLORS } from "../branding.js";
|
|
5
|
+
export function registerWorkspaceCommand(program) {
|
|
6
|
+
const workspace = program
|
|
7
|
+
.command("workspace")
|
|
8
|
+
.alias("gws")
|
|
9
|
+
.description("Manage Google Workspace integrations (Google Docs, Gmail, Sheets)");
|
|
10
|
+
workspace
|
|
11
|
+
.command("check")
|
|
12
|
+
.description("Verify that the Google Workspace CLI is installed and authenticated")
|
|
13
|
+
.action(async () => {
|
|
14
|
+
console.log(`\n ${chalk.bold("CareerVivid Google Workspace Integration")}\n`);
|
|
15
|
+
const isReady = await checkGwsReady();
|
|
16
|
+
if (isReady) {
|
|
17
|
+
console.log(boxen(`${chalk.green("✔ Google Workspace integration is fully configured.")}\n\n` +
|
|
18
|
+
`You can now use CareerVivid commands that export to Google Docs,\n` +
|
|
19
|
+
`sync with Gmail, and manage Calendar invites.`, {
|
|
20
|
+
padding: 1,
|
|
21
|
+
margin: { top: 1, bottom: 1 },
|
|
22
|
+
borderStyle: "round",
|
|
23
|
+
borderColor: COLORS.success,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log(boxen(`${chalk.red("✖ Google Workspace integration is not ready.")}\n\n` +
|
|
28
|
+
`Please ensure you have installed \`gws\` and authenticated:\n\n` +
|
|
29
|
+
`${chalk.cyan("npm install -g @googleworkspace/cli")}\n` +
|
|
30
|
+
`${chalk.cyan("gws auth setup")}`, {
|
|
31
|
+
padding: 1,
|
|
32
|
+
margin: { top: 1, bottom: 1 },
|
|
33
|
+
borderStyle: "round",
|
|
34
|
+
borderColor: COLORS.error,
|
|
35
|
+
}));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* apiUrl — override for the publish endpoint (default: prod)
|
|
7
7
|
*/
|
|
8
8
|
export declare const CONFIG_FILE: string;
|
|
9
|
-
export declare const DEFAULT_API_URL = "https://careervivid.app/api
|
|
9
|
+
export declare const DEFAULT_API_URL = "https://careervivid.app/api";
|
|
10
10
|
export interface CareerVividConfig {
|
|
11
11
|
apiKey?: string;
|
|
12
12
|
apiUrl?: string;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,eAAO,MAAM,WAAW,QAAyC,CAAC;AAElE,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,eAAO,MAAM,WAAW,QAAyC,CAAC;AAElE,eAAO,MAAM,eAAe,gCAAgC,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,UAAU,IAAI,iBAAiB,CAQ9C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAE1D;AAED,wBAAgB,SAAS,IAAI,MAAM,GAAG,SAAS,CAG9C;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIhF"}
|
package/dist/config.js
CHANGED
|
@@ -9,7 +9,7 @@ import { homedir } from "os";
|
|
|
9
9
|
import { join } from "path";
|
|
10
10
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
11
11
|
export const CONFIG_FILE = join(homedir(), ".careervividrc.json");
|
|
12
|
-
export const DEFAULT_API_URL = "https://careervivid.app/api
|
|
12
|
+
export const DEFAULT_API_URL = "https://careervivid.app/api";
|
|
13
13
|
export function loadConfig() {
|
|
14
14
|
if (!existsSync(CONFIG_FILE))
|
|
15
15
|
return {};
|
package/dist/index.js
CHANGED
|
@@ -29,13 +29,16 @@ import { registerPublishCommand } from "./commands/publish.js";
|
|
|
29
29
|
import { registerConfigCommand } from "./commands/config.js";
|
|
30
30
|
import { registerUpdateCommand } from "./commands/update.js";
|
|
31
31
|
import { checkForUpdates } from "./updates.js";
|
|
32
|
-
import {
|
|
32
|
+
import { registerListTemplatesCommand, registerNewCommand, registerWhiteboardCommand } from "./commands/whiteboard.js";
|
|
33
33
|
import { registerPortfolioCommand } from "./commands/portfolio.js";
|
|
34
|
+
import { registerWorkspaceCommand } from "./commands/workspace.js";
|
|
35
|
+
import { registerProfileCommand } from "./commands/profile.js";
|
|
36
|
+
import { registerJobsCommand } from "./commands/jobs.js";
|
|
34
37
|
const program = new Command();
|
|
35
38
|
program
|
|
36
39
|
.name("cv")
|
|
37
40
|
.description("CareerVivid CLI — publish articles, diagrams, and portfolio updates from your terminal or AI agent")
|
|
38
|
-
.version("1.
|
|
41
|
+
.version("1.3.0", "-v, --version", "Print CLI version")
|
|
39
42
|
.addHelpText("before", getHelpHeader())
|
|
40
43
|
.helpOption("-h, --help", "Show help");
|
|
41
44
|
registerAuthCommand(program);
|
|
@@ -45,6 +48,9 @@ registerConfigCommand(program);
|
|
|
45
48
|
registerUpdateCommand(program);
|
|
46
49
|
registerWhiteboardCommand(program);
|
|
47
50
|
registerPortfolioCommand(program);
|
|
51
|
+
registerWorkspaceCommand(program);
|
|
52
|
+
registerProfileCommand(program);
|
|
53
|
+
registerJobsCommand(program);
|
|
48
54
|
// Shortcuts for whiteboard creation
|
|
49
55
|
registerNewCommand(program);
|
|
50
56
|
registerListTemplatesCommand(program);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type GwsResponse<T = any> = {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: T;
|
|
4
|
+
error?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Executes a GWS CLI command, preferring a globally installed binary,
|
|
8
|
+
* falling back to npx if not found.
|
|
9
|
+
*
|
|
10
|
+
* @param command - The GWS command string (e.g., 'drive files list --params "..."')
|
|
11
|
+
* @returns Parsed JSON response from GWS
|
|
12
|
+
*/
|
|
13
|
+
export declare function runGwsCommand<T = any>(command: string): Promise<GwsResponse<T>>;
|
|
14
|
+
/**
|
|
15
|
+
* Interactive check to ensure GWS is installed and authenticated.
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkGwsReady(): Promise<boolean>;
|
|
18
|
+
//# sourceMappingURL=gws-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gws-runner.d.ts","sourceRoot":"","sources":["../../src/utils/gws-runner.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,GAAG,IAAI;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAqCrF;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CA2BtD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
/**
|
|
6
|
+
* Executes a GWS CLI command, preferring a globally installed binary,
|
|
7
|
+
* falling back to npx if not found.
|
|
8
|
+
*
|
|
9
|
+
* @param command - The GWS command string (e.g., 'drive files list --params "..."')
|
|
10
|
+
* @returns Parsed JSON response from GWS
|
|
11
|
+
*/
|
|
12
|
+
export async function runGwsCommand(command) {
|
|
13
|
+
let baseCmd = 'gws';
|
|
14
|
+
// Check if gws is available globally
|
|
15
|
+
try {
|
|
16
|
+
await execAsync('command -v gws');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Fallback to npx if gws is not in PATH
|
|
20
|
+
baseCmd = 'npx --yes @googleworkspace/cli';
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const fullCmd = `${baseCmd} ${command}`;
|
|
24
|
+
const { stdout, stderr } = await execAsync(fullCmd);
|
|
25
|
+
// GWS outputs JSON on stdout
|
|
26
|
+
try {
|
|
27
|
+
const data = JSON.parse(stdout.trim());
|
|
28
|
+
return { success: true, data };
|
|
29
|
+
}
|
|
30
|
+
catch (parseError) {
|
|
31
|
+
// Sometimes there's non-JSON output before the JSON
|
|
32
|
+
// Try to extract JSON if possible, or just return raw
|
|
33
|
+
return { success: true, data: stdout.trim() };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// GWS errors usually contain stderr
|
|
38
|
+
let errorMsg = error.stderr || error.message;
|
|
39
|
+
try {
|
|
40
|
+
// Attempt to parse structured error from output if present
|
|
41
|
+
const parsedErr = JSON.parse(error.stdout || "{}");
|
|
42
|
+
if (parsedErr.error && parsedErr.error.message) {
|
|
43
|
+
errorMsg = parsedErr.error.message;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (_) { }
|
|
47
|
+
return { success: false, error: errorMsg };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Interactive check to ensure GWS is installed and authenticated.
|
|
52
|
+
*/
|
|
53
|
+
export async function checkGwsReady() {
|
|
54
|
+
const spinner = ora("Checking Google Workspace CLI (gws) connection...").start();
|
|
55
|
+
// Check if installed
|
|
56
|
+
try {
|
|
57
|
+
await execAsync('command -v gws');
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// If not installed globally, check if we can run it via npx
|
|
61
|
+
try {
|
|
62
|
+
await execAsync('npx --yes @googleworkspace/cli --version');
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
spinner.fail("Google Workspace CLI not found.");
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Check auth by doing a simple safe call
|
|
70
|
+
// getting the user's gmail profile is a good token check
|
|
71
|
+
const result = await runGwsCommand("gmail users getProfile --params '{\"userId\": \"me\"}'");
|
|
72
|
+
if (result.success && result.data && result.data.emailAddress) {
|
|
73
|
+
spinner.succeed(`GWS CLI is ready! Authenticated as ${result.data.emailAddress}`);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
spinner.fail("GWS CLI is installed but not authenticated, or missing Gmail scopes.");
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "careervivid",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.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": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"commander": "^12.1.0",
|
|
27
27
|
"enquirer": "^2.4.1",
|
|
28
28
|
"gradient-string": "^3.0.0",
|
|
29
|
+
"mermaid": "^11.12.3",
|
|
29
30
|
"open": "^10.1.0",
|
|
30
31
|
"ora": "^8.1.0",
|
|
31
32
|
"semver": "^7.6.3",
|