careervivid 1.0.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 +199 -0
- package/dist/api.d.ts +38 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +94 -0
- package/dist/commands/auth.d.ts +12 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +128 -0
- package/dist/commands/config.d.ts +11 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +97 -0
- package/dist/commands/publish.d.ts +14 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +136 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +38 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/output.d.ts +16 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +57 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# CareerVivid CLI
|
|
2
|
+
|
|
3
|
+
Official command-line interface for [CareerVivid](https://careervivid.app) — publish articles, architecture diagrams, and portfolio updates directly from your terminal or an AI coding agent.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 1. Clone and build (one-time setup)
|
|
11
|
+
git clone https://github.com/Jastalk/CareerVivid
|
|
12
|
+
cd CareerVivid/cli
|
|
13
|
+
npm install && npm run build
|
|
14
|
+
|
|
15
|
+
# 2. Install globally
|
|
16
|
+
npm install -g .
|
|
17
|
+
|
|
18
|
+
# 3. Authenticate and publish
|
|
19
|
+
cv auth set-key
|
|
20
|
+
cv publish article.md
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Build from source and install globally
|
|
29
|
+
git clone https://github.com/Jastalk/CareerVivid
|
|
30
|
+
cd CareerVivid/cli && npm install && npm run build
|
|
31
|
+
npm install -g .
|
|
32
|
+
|
|
33
|
+
# If you already have the repo:
|
|
34
|
+
cd path/to/careervivid/cli
|
|
35
|
+
npm install && npm run build && npm install -g .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Authentication
|
|
41
|
+
|
|
42
|
+
Get your API key at [careervivid.app/#/developer](https://careervivid.app/#/developer).
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Interactive setup
|
|
46
|
+
cv auth set-key
|
|
47
|
+
|
|
48
|
+
# Or pass it directly (no prompts)
|
|
49
|
+
cv auth set-key cv_live_your_key_here
|
|
50
|
+
|
|
51
|
+
# Verify it works
|
|
52
|
+
cv auth check
|
|
53
|
+
|
|
54
|
+
# See current config
|
|
55
|
+
cv auth whoami
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Your key is stored in `~/.careervividrc.json` with `chmod 600` permissions. It is never printed to stdout.
|
|
59
|
+
|
|
60
|
+
**Alternatively**, set the `CV_API_KEY` environment variable (takes priority over the config file):
|
|
61
|
+
```bash
|
|
62
|
+
export CV_API_KEY=cv_live_...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Publishing Content
|
|
68
|
+
|
|
69
|
+
### Publish a Markdown article
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cv publish article.md
|
|
73
|
+
cv publish article.md --title "My Custom Title" --tags "typescript,firebase,saas"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The title is automatically inferred from the first `# Heading` in your file.
|
|
77
|
+
|
|
78
|
+
### Publish a Mermaid diagram
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cv publish diagram.mmd --type whiteboard
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Files with `.mmd` or `.mermaid` extensions are automatically detected as Mermaid diagrams.
|
|
85
|
+
|
|
86
|
+
### Publish from stdin (pipe-friendly for AI agents)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cat article.md | cv publish - --title "Architecture Deep Dive"
|
|
90
|
+
echo "# My Post\n\nHello world!" | cv publish - --tags "ai,demo"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Dry run (validate without publishing)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
cv publish article.md --dry-run
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Machine-readable JSON output (for AI agents and scripts)
|
|
100
|
+
|
|
101
|
+
Every command supports `--json`:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cv publish article.md --json
|
|
105
|
+
# → {"success":true,"postId":"abc123","url":"https://careervivid.app/community/post/abc123"}
|
|
106
|
+
|
|
107
|
+
cat article.md | cv publish - --title "Post" --json
|
|
108
|
+
# → {"success":true,"postId":"abc123","url":"..."}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Command Reference
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
cv publish [file]
|
|
117
|
+
[file] Path to a .md / .mmd file, or "-" to read stdin
|
|
118
|
+
--title <title> Post title (required for stdin / if no # heading)
|
|
119
|
+
--type article | whiteboard (default: inferred)
|
|
120
|
+
--format markdown | mermaid (default: inferred from extension)
|
|
121
|
+
--tags <tags> Comma-separated tags, max 5
|
|
122
|
+
--cover <url> URL to a cover image
|
|
123
|
+
--dry-run Validate without publishing
|
|
124
|
+
--json Machine-readable output
|
|
125
|
+
|
|
126
|
+
cv auth set-key [apiKey]
|
|
127
|
+
cv auth check
|
|
128
|
+
cv auth whoami
|
|
129
|
+
cv auth revoke
|
|
130
|
+
|
|
131
|
+
cv config show
|
|
132
|
+
cv config get <key> (keys: apiKey, apiUrl)
|
|
133
|
+
cv config set <key> <val>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## AI Agent & MCP Integration
|
|
139
|
+
|
|
140
|
+
### With Cursor / Claude Desktop (MCP)
|
|
141
|
+
|
|
142
|
+
The [MCP server](../mcp-server/) is the recommended way to integrate with AI coding agents. See `mcp-server/README.md`.
|
|
143
|
+
|
|
144
|
+
### Direct CLI from an AI agent
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Agent publishes an article it just wrote:
|
|
148
|
+
echo "${ARTICLE_CONTENT}" | cv publish - \
|
|
149
|
+
--title "${TITLE}" \
|
|
150
|
+
--tags "${TAGS}" \
|
|
151
|
+
--json
|
|
152
|
+
|
|
153
|
+
# Agent parses the JSON response:
|
|
154
|
+
# {"success":true,"postId":"abc123","url":"https://careervivid.app/community/post/abc123"}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
For full agentic pipelines, prefer the MCP server — it integrates natively with AI tool calling.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Environment Variables
|
|
162
|
+
|
|
163
|
+
| Variable | Description |
|
|
164
|
+
|----------|-------------|
|
|
165
|
+
| `CV_API_KEY` | API key (overrides config file) |
|
|
166
|
+
| `CV_API_URL` | Override the publish endpoint (for self-hosting or staging) |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Configuration File
|
|
171
|
+
|
|
172
|
+
Location: `~/.careervividrc.json` (mode 600 — readable only by your user)
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"apiKey": "cv_live_...",
|
|
177
|
+
"apiUrl": "https://careervivid.app/api/publish"
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Building from Source
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
cd cli
|
|
187
|
+
npm install
|
|
188
|
+
npm run build # TypeScript → dist/
|
|
189
|
+
node dist/index.js --help
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Security
|
|
195
|
+
|
|
196
|
+
- API keys are **never** written to `stdout` — only `stderr` diagnostics and masked previews
|
|
197
|
+
- The config file is created with `chmod 600`
|
|
198
|
+
- Never commit `~/.careervividrc.json` or `CV_API_KEY` to version control
|
|
199
|
+
- Revoke a compromised key immediately at [careervivid.app/#/developer](https://careervivid.app/#/developer)
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client — thin fetch wrapper around the CareerVivid API.
|
|
3
|
+
*
|
|
4
|
+
* All functions throw on network errors; on HTTP errors they return
|
|
5
|
+
* a structured ApiError for clean CLI error messages.
|
|
6
|
+
*/
|
|
7
|
+
export type PostType = "article" | "whiteboard";
|
|
8
|
+
export type DataFormat = "markdown" | "mermaid";
|
|
9
|
+
export interface PublishPayload {
|
|
10
|
+
type: PostType;
|
|
11
|
+
dataFormat: DataFormat;
|
|
12
|
+
title: string;
|
|
13
|
+
content: string;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
coverImage?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface PublishResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
postId: string;
|
|
20
|
+
url: string;
|
|
21
|
+
message: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ApiError {
|
|
24
|
+
isError: true;
|
|
25
|
+
statusCode: number;
|
|
26
|
+
message: string;
|
|
27
|
+
fields?: {
|
|
28
|
+
field: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
export declare function publishPost(payload: PublishPayload, dryRun?: boolean): Promise<PublishResult | ApiError>;
|
|
33
|
+
export declare function pingAuth(): Promise<{
|
|
34
|
+
ok: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function isApiError(v: unknown): v is ApiError;
|
|
38
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +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;CACvB;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,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;AAsDD,wBAAsB,WAAW,CAC7B,OAAO,EAAE,cAAc,EACvB,MAAM,UAAQ,GACf,OAAO,CAAC,aAAa,GAAG,QAAQ,CAAC,CAiBnC;AAED,wBAAsB,QAAQ,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBzE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,QAAQ,CAEpD"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client — thin fetch wrapper around the CareerVivid API.
|
|
3
|
+
*
|
|
4
|
+
* All functions throw on network errors; on HTTP errors they return
|
|
5
|
+
* a structured ApiError for clean CLI error messages.
|
|
6
|
+
*/
|
|
7
|
+
import { getApiKey, getApiUrl } from "./config.js";
|
|
8
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
9
|
+
function requireApiKey() {
|
|
10
|
+
const key = getApiKey();
|
|
11
|
+
if (!key) {
|
|
12
|
+
throw new Error("No API key configured.\n\n" +
|
|
13
|
+
" Run: cv auth set-key\n" +
|
|
14
|
+
" Or: export CV_API_KEY=cv_live_...\n\n" +
|
|
15
|
+
" Get your key at: https://careervivid.app/#/developer");
|
|
16
|
+
}
|
|
17
|
+
return key;
|
|
18
|
+
}
|
|
19
|
+
async function apiRequest(method, path, body) {
|
|
20
|
+
const apiKey = requireApiKey();
|
|
21
|
+
const baseUrl = getApiUrl().replace(/\/+$/, "");
|
|
22
|
+
const url = `${baseUrl}${path === "" ? "" : "/" + path}`;
|
|
23
|
+
const response = await fetch(url, {
|
|
24
|
+
method,
|
|
25
|
+
headers: {
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
"x-api-key": apiKey,
|
|
28
|
+
"User-Agent": "careervivid-cli/1.0.0",
|
|
29
|
+
},
|
|
30
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
31
|
+
});
|
|
32
|
+
const text = await response.text();
|
|
33
|
+
let parsed = {};
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(text);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
parsed = { message: text };
|
|
39
|
+
}
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
return {
|
|
42
|
+
isError: true,
|
|
43
|
+
statusCode: response.status,
|
|
44
|
+
message: parsed.error || parsed.message || `HTTP ${response.status}`,
|
|
45
|
+
fields: parsed.fields,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|
|
50
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
51
|
+
export async function publishPost(payload, dryRun = false) {
|
|
52
|
+
if (dryRun) {
|
|
53
|
+
// Basic local validation only — don't call the network
|
|
54
|
+
if (!payload.title || payload.title.trim() === "") {
|
|
55
|
+
return { isError: true, statusCode: 0, message: "Title is required." };
|
|
56
|
+
}
|
|
57
|
+
if (!payload.content || payload.content.trim() === "") {
|
|
58
|
+
return { isError: true, statusCode: 0, message: "Content is required." };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
postId: "dry-run-no-id",
|
|
63
|
+
url: "https://careervivid.app/community (dry-run — not published)",
|
|
64
|
+
message: "Dry run passed. No post was created.",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return apiRequest("POST", "", payload);
|
|
68
|
+
}
|
|
69
|
+
export async function pingAuth() {
|
|
70
|
+
const apiKey = getApiKey();
|
|
71
|
+
if (!apiKey)
|
|
72
|
+
return { ok: false, error: "No API key configured." };
|
|
73
|
+
// We send a GET (HEAD-like) to the publish endpoint.
|
|
74
|
+
// The API returns 405 if auth succeeds and only POST is allowed — that's fine.
|
|
75
|
+
// It returns 401 if auth fails.
|
|
76
|
+
const apiUrl = getApiUrl().replace(/\/+$/, "");
|
|
77
|
+
try {
|
|
78
|
+
const res = await fetch(apiUrl, {
|
|
79
|
+
method: "GET",
|
|
80
|
+
headers: { "x-api-key": apiKey, "User-Agent": "careervivid-cli/1.0.0" },
|
|
81
|
+
});
|
|
82
|
+
// 405 = Method Not Allowed → the key is valid, but GET is not supported → auth OK
|
|
83
|
+
// 401 = Unauthorized → bad key
|
|
84
|
+
if (res.status === 401)
|
|
85
|
+
return { ok: false, error: "API key is invalid or has been revoked." };
|
|
86
|
+
return { ok: true };
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
return { ok: false, error: `Network error: ${err.message}` };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function isApiError(v) {
|
|
93
|
+
return typeof v === "object" && v !== null && v.isError === true;
|
|
94
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cv auth — API key management
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* cv auth set-key Interactively prompt for an API key and save it
|
|
6
|
+
* cv auth check Verify the saved key is accepted by the server
|
|
7
|
+
* cv auth whoami Print the identity associated with the key (if supported)
|
|
8
|
+
* cv auth revoke Remove saved API key from config
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
export declare function registerAuthCommand(program: Command): void;
|
|
12
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoJ1D"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cv auth — API key management
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* cv auth set-key Interactively prompt for an API key and save it
|
|
6
|
+
* cv auth check Verify the saved key is accepted by the server
|
|
7
|
+
* cv auth whoami Print the identity associated with the key (if supported)
|
|
8
|
+
* cv auth revoke Remove saved API key from config
|
|
9
|
+
*/
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import ora from "ora";
|
|
12
|
+
import { CONFIG_FILE, loadConfig, setConfigValue } from "../config.js";
|
|
13
|
+
import { pingAuth } from "../api.js";
|
|
14
|
+
import { getApiKey } from "../config.js";
|
|
15
|
+
import { printError, printSuccess, printInfo } from "../output.js";
|
|
16
|
+
export function registerAuthCommand(program) {
|
|
17
|
+
const auth = program
|
|
18
|
+
.command("auth")
|
|
19
|
+
.description("Manage API key authentication");
|
|
20
|
+
// ── set-key ──────────────────────────────────────────────────────────────────
|
|
21
|
+
auth
|
|
22
|
+
.command("set-key [apiKey]")
|
|
23
|
+
.description("Save your CareerVivid API key to ~/.careervividrc.json")
|
|
24
|
+
.option("--json", "Machine-readable output")
|
|
25
|
+
.action(async (apiKeyArg, opts) => {
|
|
26
|
+
const jsonMode = !!opts.json;
|
|
27
|
+
let apiKey = apiKeyArg;
|
|
28
|
+
if (!apiKey) {
|
|
29
|
+
// Interactive prompt
|
|
30
|
+
if (jsonMode) {
|
|
31
|
+
printError("In --json mode, provide the API key as an argument: cv auth set-key <key>", undefined, true);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
console.log();
|
|
35
|
+
console.log(` ${chalk.bold("Get your API key at:")} ${chalk.cyan("https://careervivid.app/#/developer")}`);
|
|
36
|
+
console.log();
|
|
37
|
+
const { prompt } = await import("enquirer");
|
|
38
|
+
const answers = await prompt({
|
|
39
|
+
type: "password",
|
|
40
|
+
name: "key",
|
|
41
|
+
message: "Paste your API key",
|
|
42
|
+
});
|
|
43
|
+
apiKey = answers.key.trim();
|
|
44
|
+
}
|
|
45
|
+
if (!apiKey || !apiKey.startsWith("cv_live_")) {
|
|
46
|
+
printError("Invalid key format. CareerVivid API keys start with cv_live_", undefined, jsonMode);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
setConfigValue("apiKey", apiKey);
|
|
50
|
+
if (!jsonMode) {
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(` ${chalk.green("✔")} Key saved to ${chalk.dim(CONFIG_FILE)}`);
|
|
53
|
+
console.log(` ${chalk.dim("Run")} ${chalk.cyan("cv auth check")} ${chalk.dim("to verify it works.")}`);
|
|
54
|
+
console.log();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(JSON.stringify({ success: true, configFile: CONFIG_FILE }));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
// ── check ─────────────────────────────────────────────────────────────────────
|
|
61
|
+
auth
|
|
62
|
+
.command("check")
|
|
63
|
+
.description("Verify your API key is valid")
|
|
64
|
+
.option("--json", "Machine-readable output")
|
|
65
|
+
.action(async (opts) => {
|
|
66
|
+
const jsonMode = !!opts.json;
|
|
67
|
+
const key = getApiKey();
|
|
68
|
+
if (!key) {
|
|
69
|
+
printError("No API key configured. Run: cv auth set-key", undefined, jsonMode);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const spinner = jsonMode ? null : ora("Checking API key...").start();
|
|
73
|
+
const result = await pingAuth();
|
|
74
|
+
spinner?.stop();
|
|
75
|
+
if (result.ok) {
|
|
76
|
+
printSuccess({ status: "Valid", key: `${key.slice(0, 16)}...` }, jsonMode);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
printError(result.error || "Key check failed", undefined, jsonMode);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// ── whoami ────────────────────────────────────────────────────────────────────
|
|
84
|
+
auth
|
|
85
|
+
.command("whoami")
|
|
86
|
+
.description("Print the current API key configuration")
|
|
87
|
+
.option("--json", "Machine-readable output")
|
|
88
|
+
.action(async (opts) => {
|
|
89
|
+
const jsonMode = !!opts.json;
|
|
90
|
+
const config = loadConfig();
|
|
91
|
+
const keyFromEnv = !!process.env.CV_API_KEY;
|
|
92
|
+
const key = getApiKey();
|
|
93
|
+
if (!key) {
|
|
94
|
+
printError("No API key configured. Run: cv auth set-key", undefined, jsonMode);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const masked = `${key.slice(0, 16)}${"•".repeat(Math.max(0, key.length - 16))}`;
|
|
98
|
+
printSuccess({
|
|
99
|
+
"API Key": masked,
|
|
100
|
+
"Key Source": keyFromEnv ? "Environment (CV_API_KEY)" : `Config file (${CONFIG_FILE})`,
|
|
101
|
+
"API URL": config.apiUrl || "https://careervivid.app/api/publish (default)",
|
|
102
|
+
}, jsonMode);
|
|
103
|
+
});
|
|
104
|
+
// ── revoke ────────────────────────────────────────────────────────────────────
|
|
105
|
+
auth
|
|
106
|
+
.command("revoke")
|
|
107
|
+
.description("Remove the saved API key from ~/.careervividrc.json")
|
|
108
|
+
.option("--json", "Machine-readable output")
|
|
109
|
+
.action(async (opts) => {
|
|
110
|
+
const jsonMode = !!opts.json;
|
|
111
|
+
const config = loadConfig();
|
|
112
|
+
if (!config.apiKey) {
|
|
113
|
+
printInfo("No API key in config file to remove.", jsonMode);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
delete config.apiKey;
|
|
117
|
+
const { saveConfig } = await import("../config.js");
|
|
118
|
+
saveConfig(config);
|
|
119
|
+
if (jsonMode) {
|
|
120
|
+
console.log(JSON.stringify({ success: true }));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log();
|
|
124
|
+
console.log(` ${chalk.green("✔")} API key removed from ${chalk.dim(CONFIG_FILE)}`);
|
|
125
|
+
console.log();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cv config — view and modify CLI configuration
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* cv config show Print entire configuration
|
|
6
|
+
* cv config get <key> Print single config value
|
|
7
|
+
* cv config set <key> <val> Set a config value
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
export declare function registerConfigCommand(program: Command): void;
|
|
11
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoG5D"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cv config — view and modify CLI configuration
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* cv config show Print entire configuration
|
|
6
|
+
* cv config get <key> Print single config value
|
|
7
|
+
* cv config set <key> <val> Set a config value
|
|
8
|
+
*/
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import { CONFIG_FILE, loadConfig, setConfigValue } from "../config.js";
|
|
11
|
+
import { printError, printSuccess } from "../output.js";
|
|
12
|
+
const VALID_KEYS = ["apiKey", "apiUrl"];
|
|
13
|
+
export function registerConfigCommand(program) {
|
|
14
|
+
const config = program
|
|
15
|
+
.command("config")
|
|
16
|
+
.description("View and modify CLI configuration (~/.careervividrc.json)");
|
|
17
|
+
// ── show ──────────────────────────────────────────────────────────────────────
|
|
18
|
+
config
|
|
19
|
+
.command("show")
|
|
20
|
+
.description("Print current configuration")
|
|
21
|
+
.option("--json", "Machine-readable output")
|
|
22
|
+
.action((opts) => {
|
|
23
|
+
const jsonMode = !!opts.json;
|
|
24
|
+
const cfg = loadConfig();
|
|
25
|
+
if (jsonMode) {
|
|
26
|
+
// Never leak the full key in JSON — mask it
|
|
27
|
+
const masked = { ...cfg };
|
|
28
|
+
if (masked.apiKey)
|
|
29
|
+
masked.apiKey = `${masked.apiKey.slice(0, 16)}...`;
|
|
30
|
+
console.log(JSON.stringify({ configFile: CONFIG_FILE, config: masked }));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log();
|
|
34
|
+
console.log(` ${chalk.bold("Config file:")} ${chalk.dim(CONFIG_FILE)}`);
|
|
35
|
+
console.log();
|
|
36
|
+
if (!cfg.apiKey && !cfg.apiUrl) {
|
|
37
|
+
console.log(` ${chalk.dim("No configuration set. Run: cv auth set-key")}`);
|
|
38
|
+
console.log();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (cfg.apiKey) {
|
|
42
|
+
const masked = `${cfg.apiKey.slice(0, 16)}${"•".repeat(cfg.apiKey.length - 16)}`;
|
|
43
|
+
console.log(` ${chalk.dim("apiKey".padEnd(10))} ${masked}`);
|
|
44
|
+
}
|
|
45
|
+
if (cfg.apiUrl) {
|
|
46
|
+
console.log(` ${chalk.dim("apiUrl".padEnd(10))} ${cfg.apiUrl}`);
|
|
47
|
+
}
|
|
48
|
+
console.log();
|
|
49
|
+
});
|
|
50
|
+
// ── get ───────────────────────────────────────────────────────────────────────
|
|
51
|
+
config
|
|
52
|
+
.command("get <key>")
|
|
53
|
+
.description(`Get a config value (keys: ${VALID_KEYS.join(", ")})`)
|
|
54
|
+
.option("--json", "Machine-readable output")
|
|
55
|
+
.action((key, opts) => {
|
|
56
|
+
const jsonMode = !!opts.json;
|
|
57
|
+
if (!VALID_KEYS.includes(key)) {
|
|
58
|
+
printError(`Unknown key "${key}". Valid keys: ${VALID_KEYS.join(", ")}`, undefined, jsonMode);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const cfg = loadConfig();
|
|
62
|
+
const value = cfg[key];
|
|
63
|
+
if (!value) {
|
|
64
|
+
if (jsonMode) {
|
|
65
|
+
console.log(JSON.stringify({ key, value: null }));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(` ${chalk.dim(key)} is not set`);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Mask apiKey
|
|
73
|
+
const displayValue = key === "apiKey"
|
|
74
|
+
? `${value.slice(0, 16)}${"•".repeat(value.length - 16)}`
|
|
75
|
+
: value;
|
|
76
|
+
if (jsonMode) {
|
|
77
|
+
console.log(JSON.stringify({ key, value: displayValue }));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(` ${chalk.dim(key.padEnd(10))} ${displayValue}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// ── set ───────────────────────────────────────────────────────────────────────
|
|
84
|
+
config
|
|
85
|
+
.command("set <key> <value>")
|
|
86
|
+
.description(`Set a config value (keys: ${VALID_KEYS.join(", ")})`)
|
|
87
|
+
.option("--json", "Machine-readable output")
|
|
88
|
+
.action((key, value, opts) => {
|
|
89
|
+
const jsonMode = !!opts.json;
|
|
90
|
+
if (!VALID_KEYS.includes(key)) {
|
|
91
|
+
printError(`Unknown key "${key}". Valid keys: ${VALID_KEYS.join(", ")}`, undefined, jsonMode);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
setConfigValue(key, value);
|
|
95
|
+
printSuccess({ key, saved: "true", configFile: CONFIG_FILE }, jsonMode);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cv publish — publish content to CareerVivid
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* cv publish <file> Publish a file
|
|
6
|
+
* cv publish - Read from stdin
|
|
7
|
+
* cv publish README.md --title "..." Override title
|
|
8
|
+
* cv publish arch.mmd --type whiteboard --format mermaid
|
|
9
|
+
* cv publish - --dry-run < article.md Validate without publishing
|
|
10
|
+
* cv publish - --json < article.md Agent-friendly JSON output
|
|
11
|
+
*/
|
|
12
|
+
import { Command } from "commander";
|
|
13
|
+
export declare function registerPublishCommand(program: Command): void;
|
|
14
|
+
//# sourceMappingURL=publish.d.ts.map
|
|
@@ -0,0 +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;AA2BpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoJ7D"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cv publish — publish content to CareerVivid
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* cv publish <file> Publish a file
|
|
6
|
+
* cv publish - Read from stdin
|
|
7
|
+
* cv publish README.md --title "..." Override title
|
|
8
|
+
* cv publish arch.mmd --type whiteboard --format mermaid
|
|
9
|
+
* cv publish - --dry-run < article.md Validate without publishing
|
|
10
|
+
* cv publish - --json < article.md Agent-friendly JSON output
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from "fs";
|
|
13
|
+
import { extname } from "path";
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
import ora from "ora";
|
|
16
|
+
import { publishPost, isApiError } from "../api.js";
|
|
17
|
+
import { printError, printSuccess, handleApiError } from "../output.js";
|
|
18
|
+
function inferFormat(filePath) {
|
|
19
|
+
const ext = extname(filePath).toLowerCase();
|
|
20
|
+
if ([".mmd", ".mermaid"].includes(ext))
|
|
21
|
+
return "mermaid";
|
|
22
|
+
return "markdown";
|
|
23
|
+
}
|
|
24
|
+
function inferType(format) {
|
|
25
|
+
return format === "mermaid" ? "whiteboard" : "article";
|
|
26
|
+
}
|
|
27
|
+
async function readStdin() {
|
|
28
|
+
const chunks = [];
|
|
29
|
+
for await (const chunk of process.stdin) {
|
|
30
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
31
|
+
}
|
|
32
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
33
|
+
}
|
|
34
|
+
export function registerPublishCommand(program) {
|
|
35
|
+
program
|
|
36
|
+
.command("publish [file]")
|
|
37
|
+
.description([
|
|
38
|
+
"Publish a markdown or mermaid file to CareerVivid",
|
|
39
|
+
"",
|
|
40
|
+
" cv publish article.md",
|
|
41
|
+
" cv publish diagram.mmd --type whiteboard",
|
|
42
|
+
" cat article.md | cv publish - --title \"My Article\" --json",
|
|
43
|
+
].join("\n"))
|
|
44
|
+
.option("-t, --title <title>", "Post title (inferred from first heading if omitted)")
|
|
45
|
+
.option("--type <type>", "Post type: article | whiteboard (default: inferred from format)")
|
|
46
|
+
.option("--format <format>", "Content format: markdown | mermaid (default: inferred from file extension)")
|
|
47
|
+
.option("--tags <tags>", "Comma-separated tags, e.g. typescript,firebase,react")
|
|
48
|
+
.option("--cover <url>", "URL to a cover image")
|
|
49
|
+
.option("--dry-run", "Validate payload without publishing")
|
|
50
|
+
.option("--json", "Machine-readable JSON output")
|
|
51
|
+
.action(async (fileArg, opts) => {
|
|
52
|
+
const jsonMode = !!opts.json;
|
|
53
|
+
const dryRun = !!opts.dryRun;
|
|
54
|
+
// ── Read content ────────────────────────────────────────────────────────
|
|
55
|
+
let content;
|
|
56
|
+
let filePath;
|
|
57
|
+
if (!fileArg || fileArg === "-") {
|
|
58
|
+
if (!jsonMode) {
|
|
59
|
+
console.log(` ${chalk.dim("Reading from stdin... (Ctrl+D to finish)")}`);
|
|
60
|
+
}
|
|
61
|
+
content = await readStdin();
|
|
62
|
+
filePath = "stdin";
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
try {
|
|
66
|
+
content = readFileSync(fileArg, "utf-8");
|
|
67
|
+
filePath = fileArg;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
printError(`Cannot read file: ${err.message}`, undefined, jsonMode);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!content.trim()) {
|
|
75
|
+
printError("Content is empty.", undefined, jsonMode);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
// ── Infer format and type ───────────────────────────────────────────────
|
|
79
|
+
const format = opts.format ||
|
|
80
|
+
(filePath !== "stdin" ? inferFormat(filePath) : "markdown");
|
|
81
|
+
const type = opts.type || inferType(format);
|
|
82
|
+
// ── Infer title from first heading if not provided ─────────────────────
|
|
83
|
+
let title = opts.title;
|
|
84
|
+
if (!title && format === "markdown") {
|
|
85
|
+
const firstHeading = content.match(/^#\s+(.+)$/m);
|
|
86
|
+
if (firstHeading) {
|
|
87
|
+
title = firstHeading[1].trim();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!title) {
|
|
91
|
+
if (jsonMode) {
|
|
92
|
+
printError("Title is required. Use --title <title> or add a # heading in your markdown.", undefined, true);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
// Interactive prompt fallback
|
|
96
|
+
const { prompt } = await import("enquirer");
|
|
97
|
+
const answers = await prompt({
|
|
98
|
+
type: "input",
|
|
99
|
+
name: "title",
|
|
100
|
+
message: "Post title",
|
|
101
|
+
});
|
|
102
|
+
title = answers.title.trim();
|
|
103
|
+
}
|
|
104
|
+
// ── Build payload ──────────────────────────────────────────────────────
|
|
105
|
+
const tags = opts.tags
|
|
106
|
+
? opts.tags.split(",").map((t) => t.trim()).filter(Boolean)
|
|
107
|
+
: [];
|
|
108
|
+
if (tags.length > 5) {
|
|
109
|
+
printError("Maximum 5 tags allowed.", undefined, jsonMode);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
const payload = {
|
|
113
|
+
type,
|
|
114
|
+
dataFormat: format,
|
|
115
|
+
title,
|
|
116
|
+
content,
|
|
117
|
+
tags,
|
|
118
|
+
...(opts.cover ? { coverImage: opts.cover } : {}),
|
|
119
|
+
};
|
|
120
|
+
// ── Publish ────────────────────────────────────────────────────────────
|
|
121
|
+
const spinner = jsonMode || dryRun
|
|
122
|
+
? null
|
|
123
|
+
: ora(`${dryRun ? "Validating" : "Publishing"} ${type}...`).start();
|
|
124
|
+
const result = await publishPost(payload, dryRun);
|
|
125
|
+
spinner?.stop();
|
|
126
|
+
if (isApiError(result)) {
|
|
127
|
+
handleApiError(result, jsonMode);
|
|
128
|
+
}
|
|
129
|
+
printSuccess({
|
|
130
|
+
Title: title,
|
|
131
|
+
URL: result.url,
|
|
132
|
+
"Post ID": result.postId,
|
|
133
|
+
...(dryRun ? { Note: "Dry run — not published" } : {}),
|
|
134
|
+
}, jsonMode);
|
|
135
|
+
});
|
|
136
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Manager — reads/writes ~/.careervividrc.json
|
|
3
|
+
*
|
|
4
|
+
* Stored fields:
|
|
5
|
+
* apiKey — cv_live_... key
|
|
6
|
+
* apiUrl — override for the publish endpoint (default: prod)
|
|
7
|
+
*/
|
|
8
|
+
export declare const CONFIG_FILE: string;
|
|
9
|
+
export declare const DEFAULT_API_URL = "https://careervivid.app/api/publish";
|
|
10
|
+
export interface CareerVividConfig {
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
apiUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function loadConfig(): CareerVividConfig;
|
|
15
|
+
export declare function saveConfig(config: CareerVividConfig): void;
|
|
16
|
+
export declare function getApiKey(): string | undefined;
|
|
17
|
+
export declare function getApiUrl(): string;
|
|
18
|
+
export declare function setConfigValue(key: keyof CareerVividConfig, value: string): void;
|
|
19
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +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,wCAAwC,CAAC;AAErE,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
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Manager — reads/writes ~/.careervividrc.json
|
|
3
|
+
*
|
|
4
|
+
* Stored fields:
|
|
5
|
+
* apiKey — cv_live_... key
|
|
6
|
+
* apiUrl — override for the publish endpoint (default: prod)
|
|
7
|
+
*/
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
11
|
+
export const CONFIG_FILE = join(homedir(), ".careervividrc.json");
|
|
12
|
+
export const DEFAULT_API_URL = "https://careervivid.app/api/publish";
|
|
13
|
+
export function loadConfig() {
|
|
14
|
+
if (!existsSync(CONFIG_FILE))
|
|
15
|
+
return {};
|
|
16
|
+
try {
|
|
17
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function saveConfig(config) {
|
|
25
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
|
|
26
|
+
}
|
|
27
|
+
export function getApiKey() {
|
|
28
|
+
// Priority: env var > config file
|
|
29
|
+
return process.env.CV_API_KEY || loadConfig().apiKey;
|
|
30
|
+
}
|
|
31
|
+
export function getApiUrl() {
|
|
32
|
+
return process.env.CV_API_URL || loadConfig().apiUrl || DEFAULT_API_URL;
|
|
33
|
+
}
|
|
34
|
+
export function setConfigValue(key, value) {
|
|
35
|
+
const current = loadConfig();
|
|
36
|
+
current[key] = value;
|
|
37
|
+
saveConfig(current);
|
|
38
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CareerVivid CLI — Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* cv publish <file> Publish a markdown/mermaid file
|
|
7
|
+
* cv publish - Read from stdin (pipe-friendly)
|
|
8
|
+
* cv auth set-key Save your API key
|
|
9
|
+
* cv auth check Test that your API key is valid
|
|
10
|
+
* cv config show Print current configuration
|
|
11
|
+
* cv config set <key> <value> Update a config value
|
|
12
|
+
* cv config get <key> Print a config value
|
|
13
|
+
* cv --help / cv --version
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CareerVivid CLI — Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* cv publish <file> Publish a markdown/mermaid file
|
|
7
|
+
* cv publish - Read from stdin (pipe-friendly)
|
|
8
|
+
* cv auth set-key Save your API key
|
|
9
|
+
* cv auth check Test that your API key is valid
|
|
10
|
+
* cv config show Print current configuration
|
|
11
|
+
* cv config set <key> <value> Update a config value
|
|
12
|
+
* cv config get <key> Print a config value
|
|
13
|
+
* cv --help / cv --version
|
|
14
|
+
*/
|
|
15
|
+
import { Command } from "commander";
|
|
16
|
+
import { registerAuthCommand } from "./commands/auth.js";
|
|
17
|
+
import { registerPublishCommand } from "./commands/publish.js";
|
|
18
|
+
import { registerConfigCommand } from "./commands/config.js";
|
|
19
|
+
const program = new Command();
|
|
20
|
+
program
|
|
21
|
+
.name("cv")
|
|
22
|
+
.description("CareerVivid CLI — publish articles, diagrams, and portfolio updates from your terminal or AI agent")
|
|
23
|
+
.version("1.0.0", "-v, --version", "Print CLI version")
|
|
24
|
+
.helpOption("-h, --help", "Show help");
|
|
25
|
+
registerAuthCommand(program);
|
|
26
|
+
registerPublishCommand(program);
|
|
27
|
+
registerConfigCommand(program);
|
|
28
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
29
|
+
console.error(`\nFatal error: ${err.message}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
package/dist/output.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output helpers — pretty-print (human) vs --json (machine) modes.
|
|
3
|
+
*
|
|
4
|
+
* Every command receives an `opts.json` flag. Pass it through to these helpers
|
|
5
|
+
* to ensure consistent behaviour across human and agent callers.
|
|
6
|
+
*/
|
|
7
|
+
import type { ApiError } from "./api.js";
|
|
8
|
+
export declare function printSuccess(fields: Record<string, string>, jsonMode: boolean): void;
|
|
9
|
+
export declare function printError(message: string, fields?: {
|
|
10
|
+
field: string;
|
|
11
|
+
message: string;
|
|
12
|
+
}[], jsonMode?: boolean): void;
|
|
13
|
+
export declare function printInfo(message: string, jsonMode: boolean): void;
|
|
14
|
+
export declare function printTable(rows: Record<string, string>[], jsonMode: boolean): void;
|
|
15
|
+
export declare function handleApiError(err: ApiError, jsonMode: boolean): never;
|
|
16
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIzC,wBAAgB,YAAY,CACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,QAAQ,EAAE,OAAO,GAClB,IAAI,CAaN;AAED,wBAAgB,UAAU,CACtB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,EAC7C,QAAQ,UAAQ,GACjB,IAAI,CAeN;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAIlE;AAED,wBAAgB,UAAU,CACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAC9B,QAAQ,EAAE,OAAO,GAClB,IAAI,CAWN;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAG,KAAK,CAGtE"}
|
package/dist/output.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output helpers — pretty-print (human) vs --json (machine) modes.
|
|
3
|
+
*
|
|
4
|
+
* Every command receives an `opts.json` flag. Pass it through to these helpers
|
|
5
|
+
* to ensure consistent behaviour across human and agent callers.
|
|
6
|
+
*/
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
// ── Output helpers ─────────────────────────────────────────────────────────────
|
|
9
|
+
export function printSuccess(fields, jsonMode) {
|
|
10
|
+
if (jsonMode) {
|
|
11
|
+
console.log(JSON.stringify({ success: true, ...fields }));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
console.log();
|
|
15
|
+
console.log(` ${chalk.green("✔")} ${chalk.bold("Success!")}`);
|
|
16
|
+
console.log();
|
|
17
|
+
for (const [label, value] of Object.entries(fields)) {
|
|
18
|
+
console.log(` ${chalk.dim(label.padEnd(10))} ${chalk.cyan(value)}`);
|
|
19
|
+
}
|
|
20
|
+
console.log();
|
|
21
|
+
}
|
|
22
|
+
export function printError(message, fields, jsonMode = false) {
|
|
23
|
+
if (jsonMode) {
|
|
24
|
+
console.error(JSON.stringify({ success: false, error: message, fields }));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.error();
|
|
28
|
+
console.error(` ${chalk.red("✖")} ${chalk.bold("Error:")} ${message}`);
|
|
29
|
+
if (fields && fields.length > 0) {
|
|
30
|
+
console.error();
|
|
31
|
+
for (const f of fields) {
|
|
32
|
+
console.error(` ${chalk.yellow("•")} ${chalk.bold(f.field)}: ${f.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
console.error();
|
|
36
|
+
}
|
|
37
|
+
export function printInfo(message, jsonMode) {
|
|
38
|
+
if (!jsonMode) {
|
|
39
|
+
console.log(` ${chalk.blue("ℹ")} ${message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function printTable(rows, jsonMode) {
|
|
43
|
+
if (jsonMode) {
|
|
44
|
+
console.log(JSON.stringify(rows));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
for (const [k, v] of Object.entries(row)) {
|
|
49
|
+
console.log(` ${chalk.dim(k.padEnd(12))} ${v}`);
|
|
50
|
+
}
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function handleApiError(err, jsonMode) {
|
|
55
|
+
printError(err.message, err.fields, jsonMode);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "careervivid",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official CLI for CareerVivid — publish articles, diagrams, and portfolio updates from your terminal or AI agent",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cv": "dist/index.js",
|
|
8
|
+
"careervivid": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "node --loader ts-node/esm src/index.ts",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chalk": "^5.3.0",
|
|
24
|
+
"commander": "^12.1.0",
|
|
25
|
+
"enquirer": "^2.4.1",
|
|
26
|
+
"ora": "^8.1.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"ts-node": "^10.9.2",
|
|
31
|
+
"typescript": "^5.4.0"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"careervivid",
|
|
38
|
+
"cli",
|
|
39
|
+
"developer-tools",
|
|
40
|
+
"publish",
|
|
41
|
+
"portfolio",
|
|
42
|
+
"ai-agent",
|
|
43
|
+
"mcp"
|
|
44
|
+
],
|
|
45
|
+
"author": "CareerVivid",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/Jastalk/CareerVivid.git",
|
|
50
|
+
"directory": "cli"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://careervivid.app",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/Jastalk/CareerVivid/issues"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
}
|
|
59
|
+
}
|