@inblog/cli 0.2.2 → 0.2.4
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 +202 -0
- package/dist/bin/inblog.js +163 -53
- package/dist/bin/inblog.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# @inblog/cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for managing [inblog.ai](https://inblog.ai) blog content. Create, publish, and manage blog posts, tags, authors, images, and more from your terminal.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @inblog/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Authenticate with your API key
|
|
17
|
+
inblog auth login
|
|
18
|
+
|
|
19
|
+
# Check current blog
|
|
20
|
+
inblog auth status
|
|
21
|
+
|
|
22
|
+
# List posts
|
|
23
|
+
inblog posts list
|
|
24
|
+
|
|
25
|
+
# Create and publish a post
|
|
26
|
+
inblog posts create --title "My Post" --slug "my-post" --content-file ./content.html
|
|
27
|
+
inblog posts publish <id>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
### Authentication
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
inblog auth login # Login with API key
|
|
36
|
+
inblog auth whoami # Show current user/blog
|
|
37
|
+
inblog auth status # Check auth status
|
|
38
|
+
inblog auth logout # Remove saved credentials
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Blog Management
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
inblog blogs me # Current blog info
|
|
45
|
+
inblog blogs list # List accessible blogs (OAuth)
|
|
46
|
+
inblog blogs switch [id] # Switch active blog
|
|
47
|
+
inblog blogs update # Update blog settings
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Blog settings:**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
inblog blogs update --title "Blog Name" --description "About" --language ko
|
|
54
|
+
inblog blogs update --logo ./logo.png --favicon ./favicon.ico --og-image ./og.jpg
|
|
55
|
+
inblog blogs update --ga-id G-XXXXXXXXXX
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Custom domain:**
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
inblog blogs domain connect blog.example.com # Connect + DNS guide
|
|
62
|
+
inblog blogs domain status # Check verification
|
|
63
|
+
inblog blogs domain disconnect # Disconnect
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Banner:**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
inblog blogs banner get
|
|
70
|
+
inblog blogs banner set --image ./banner.png --title "Title" --subtext "Subtitle"
|
|
71
|
+
inblog blogs banner remove
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Posts
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
inblog posts list # List all posts
|
|
78
|
+
inblog posts list --published # Published only
|
|
79
|
+
inblog posts list --draft # Drafts only
|
|
80
|
+
inblog posts get <id> # Post details
|
|
81
|
+
|
|
82
|
+
inblog posts create \
|
|
83
|
+
--title "Title" \
|
|
84
|
+
--slug "url-slug" \
|
|
85
|
+
--image ./cover.jpg \
|
|
86
|
+
--content-file ./content.html
|
|
87
|
+
|
|
88
|
+
inblog posts update <id> --title "New Title" --image ./new-cover.jpg
|
|
89
|
+
inblog posts delete <id>
|
|
90
|
+
|
|
91
|
+
inblog posts publish <id> # Publish immediately
|
|
92
|
+
inblog posts unpublish <id> # Unpublish
|
|
93
|
+
inblog posts schedule <id> --at "2026-03-15T09:00:00+09:00"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Tags & Authors on posts:**
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
inblog posts add-tags <id> --tag-ids 1,2,3
|
|
100
|
+
inblog posts remove-tag <postId> <tagId>
|
|
101
|
+
inblog posts add-authors <id> --author-ids uuid1,uuid2
|
|
102
|
+
inblog posts remove-author <postId> <authorId>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Images
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Upload images to CDN
|
|
109
|
+
inblog images upload ./photo1.jpg ./photo2.png
|
|
110
|
+
inblog images upload ./cover.jpg -b featured_image --json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Local image files used with `--image` or `--content-file` are automatically uploaded to the CDN.
|
|
114
|
+
|
|
115
|
+
### Tags
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
inblog tags list
|
|
119
|
+
inblog tags create --name "Tag Name" --slug "tag-slug"
|
|
120
|
+
inblog tags update <id> --name "New Name"
|
|
121
|
+
inblog tags delete <id>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Authors
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
inblog authors list
|
|
128
|
+
inblog authors get <id>
|
|
129
|
+
inblog authors update <id> --name "Name" --avatar-url "https://..."
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Redirects
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
inblog redirects list
|
|
136
|
+
inblog redirects create --from "/old" --to "/new" --type 308
|
|
137
|
+
inblog redirects update <id> --to "/newer"
|
|
138
|
+
inblog redirects delete <id>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Forms
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
inblog forms list
|
|
145
|
+
inblog forms get <id>
|
|
146
|
+
inblog form-responses list --form-id <id>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Search Console
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
inblog search-console connect # OAuth connect
|
|
153
|
+
inblog search-console status # Connection status
|
|
154
|
+
inblog search-console keywords --sort clicks --limit 20
|
|
155
|
+
inblog search-console pages --sort clicks --limit 20
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Analytics
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
inblog analytics traffic --interval day # Blog traffic
|
|
162
|
+
inblog analytics posts --sort visits --limit 20 --include title
|
|
163
|
+
inblog analytics sources --limit 20 # Traffic sources
|
|
164
|
+
inblog analytics post <id> --interval day # Single post traffic
|
|
165
|
+
inblog analytics post <id> --sources # Single post sources
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Global Options
|
|
169
|
+
|
|
170
|
+
| Option | Description |
|
|
171
|
+
|--------|-------------|
|
|
172
|
+
| `--json` | Output as JSON |
|
|
173
|
+
| `--base-url <url>` | Custom API base URL |
|
|
174
|
+
| `--no-color` | Disable colored output |
|
|
175
|
+
| `--api-key <key>` | Use specific API key |
|
|
176
|
+
|
|
177
|
+
## Image Handling
|
|
178
|
+
|
|
179
|
+
The CLI automatically handles image uploads:
|
|
180
|
+
|
|
181
|
+
- `--image ./cover.jpg` on `posts create/update` uploads the file to CDN
|
|
182
|
+
- `--content-file` scans HTML for local file paths and base64 data URIs, uploads them, and replaces with CDN URLs
|
|
183
|
+
- `--logo`, `--favicon`, `--og-image` on `blogs update` accept local files
|
|
184
|
+
- `inblog images upload` for standalone CDN uploads
|
|
185
|
+
|
|
186
|
+
**Note:** Do not embed base64 images directly in `content_html` API calls (causes 413 errors). Use `--content-file` or upload first with `inblog images upload`.
|
|
187
|
+
|
|
188
|
+
## API Key
|
|
189
|
+
|
|
190
|
+
Get your API key from [inblog.ai dashboard](https://inblog.ai) > Settings > API Keys. Requires a Team plan or higher.
|
|
191
|
+
|
|
192
|
+
## AI Skills
|
|
193
|
+
|
|
194
|
+
For AI-assisted blog management (Claude Code, Cursor, GitHub Copilot), install the companion package:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npx @inblog/ai-skills
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
package/dist/bin/inblog.js
CHANGED
|
@@ -1107,7 +1107,119 @@ function registerAuthCommands(program2) {
|
|
|
1107
1107
|
}
|
|
1108
1108
|
|
|
1109
1109
|
// src/commands/posts.ts
|
|
1110
|
-
var
|
|
1110
|
+
var fs4 = __toESM(require("fs"));
|
|
1111
|
+
|
|
1112
|
+
// src/utils/upload.ts
|
|
1113
|
+
var import_node_fs = __toESM(require("fs"));
|
|
1114
|
+
var import_node_path = __toESM(require("path"));
|
|
1115
|
+
var import_node_crypto2 = require("crypto");
|
|
1116
|
+
var WORKER_URL = "https://upload.inblog.dev/";
|
|
1117
|
+
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
1118
|
+
var MIME_TYPES = {
|
|
1119
|
+
".png": "image/png",
|
|
1120
|
+
".jpg": "image/jpeg",
|
|
1121
|
+
".jpeg": "image/jpeg",
|
|
1122
|
+
".gif": "image/gif",
|
|
1123
|
+
".webp": "image/webp",
|
|
1124
|
+
".svg": "image/svg+xml",
|
|
1125
|
+
".ico": "image/x-icon"
|
|
1126
|
+
};
|
|
1127
|
+
function isLocalPath(value) {
|
|
1128
|
+
if (value.startsWith("http://") || value.startsWith("https://")) return false;
|
|
1129
|
+
return import_node_fs.default.existsSync(value);
|
|
1130
|
+
}
|
|
1131
|
+
async function uploadImage(filePath, bucket) {
|
|
1132
|
+
const resolved = import_node_path.default.resolve(filePath);
|
|
1133
|
+
if (!import_node_fs.default.existsSync(resolved)) {
|
|
1134
|
+
throw new Error(`File not found: ${resolved}`);
|
|
1135
|
+
}
|
|
1136
|
+
const stat = import_node_fs.default.statSync(resolved);
|
|
1137
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
1138
|
+
throw new Error(`File size exceeds 10MB limit: ${(stat.size / 1024 / 1024).toFixed(1)}MB`);
|
|
1139
|
+
}
|
|
1140
|
+
const ext = import_node_path.default.extname(resolved).toLowerCase();
|
|
1141
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
1142
|
+
const fileKey = `${bucket}/${(/* @__PURE__ */ new Date()).toISOString()}-${(0, import_node_crypto2.randomUUID)()}`;
|
|
1143
|
+
const body = import_node_fs.default.readFileSync(resolved);
|
|
1144
|
+
const response = await fetch(`${WORKER_URL}?fileKey=${fileKey}`, {
|
|
1145
|
+
method: "POST",
|
|
1146
|
+
body,
|
|
1147
|
+
headers: { "Content-Type": contentType }
|
|
1148
|
+
});
|
|
1149
|
+
if (!response.ok) {
|
|
1150
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
1151
|
+
}
|
|
1152
|
+
const data = await response.json();
|
|
1153
|
+
return data.publicUrl;
|
|
1154
|
+
}
|
|
1155
|
+
async function resolveImageUrl(value, bucket) {
|
|
1156
|
+
if (!isLocalPath(value)) return value;
|
|
1157
|
+
return uploadImage(value, bucket);
|
|
1158
|
+
}
|
|
1159
|
+
async function uploadBase64(dataUri, bucket) {
|
|
1160
|
+
const match = dataUri.match(/^data:([^;]+);base64,(.+)$/s);
|
|
1161
|
+
if (!match) throw new Error("Invalid data URI");
|
|
1162
|
+
const contentType = match[1];
|
|
1163
|
+
const body = Buffer.from(match[2], "base64");
|
|
1164
|
+
if (body.length > MAX_FILE_SIZE) {
|
|
1165
|
+
throw new Error(`Base64 image exceeds 10MB limit: ${(body.length / 1024 / 1024).toFixed(1)}MB`);
|
|
1166
|
+
}
|
|
1167
|
+
const fileKey = `${bucket}/${(/* @__PURE__ */ new Date()).toISOString()}-${(0, import_node_crypto2.randomUUID)()}`;
|
|
1168
|
+
const response = await fetch(`${WORKER_URL}?fileKey=${fileKey}`, {
|
|
1169
|
+
method: "POST",
|
|
1170
|
+
body,
|
|
1171
|
+
headers: { "Content-Type": contentType }
|
|
1172
|
+
});
|
|
1173
|
+
if (!response.ok) {
|
|
1174
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
1175
|
+
}
|
|
1176
|
+
const data = await response.json();
|
|
1177
|
+
return data.publicUrl;
|
|
1178
|
+
}
|
|
1179
|
+
async function processContentImages(html) {
|
|
1180
|
+
const imgSrcRegex = /(<img\s[^>]*\bsrc=["'])([^"']+)(["'][^>]*>)/gi;
|
|
1181
|
+
const matches = [];
|
|
1182
|
+
let m;
|
|
1183
|
+
while ((m = imgSrcRegex.exec(html)) !== null) {
|
|
1184
|
+
matches.push({ full: m[0], prefix: m[1], src: m[2], suffix: m[3], index: m.index });
|
|
1185
|
+
}
|
|
1186
|
+
if (matches.length === 0) return { html, uploadCount: 0 };
|
|
1187
|
+
let uploadCount = 0;
|
|
1188
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
1189
|
+
const concurrency = 5;
|
|
1190
|
+
for (let i = 0; i < matches.length; i += concurrency) {
|
|
1191
|
+
const batch = matches.slice(i, i + concurrency);
|
|
1192
|
+
const results = await Promise.allSettled(
|
|
1193
|
+
batch.map(async ({ src }) => {
|
|
1194
|
+
if (src.startsWith("https://source.inblog.dev/") || src.startsWith("https://image.inblog.dev/")) {
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
if (src.startsWith("data:image/")) {
|
|
1198
|
+
const url = await uploadBase64(src, "post_image");
|
|
1199
|
+
return { src, url };
|
|
1200
|
+
}
|
|
1201
|
+
if (isLocalPath(src)) {
|
|
1202
|
+
const url = await uploadImage(src, "post_image");
|
|
1203
|
+
return { src, url };
|
|
1204
|
+
}
|
|
1205
|
+
return null;
|
|
1206
|
+
})
|
|
1207
|
+
);
|
|
1208
|
+
for (const result of results) {
|
|
1209
|
+
if (result.status === "fulfilled" && result.value) {
|
|
1210
|
+
replacements.set(result.value.src, result.value.url);
|
|
1211
|
+
uploadCount++;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
let processed = html;
|
|
1216
|
+
for (const [src, url] of replacements) {
|
|
1217
|
+
processed = processed.split(src).join(url);
|
|
1218
|
+
}
|
|
1219
|
+
return { html: processed, uploadCount };
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/commands/posts.ts
|
|
1111
1223
|
function formatPost(post) {
|
|
1112
1224
|
return [
|
|
1113
1225
|
["ID", post.id],
|
|
@@ -1183,19 +1295,27 @@ Showing page ${meta.page ?? 1} (${data.length} of ${meta.total} posts)`);
|
|
|
1183
1295
|
handleError(error, json);
|
|
1184
1296
|
}
|
|
1185
1297
|
});
|
|
1186
|
-
posts.command("create").description("Create post (draft by default, use --published to publish)").requiredOption("-t, --title <title>", "Post title").option("-s, --slug <slug>", "Post slug").option("-d, --description <desc>", "Post description").option("--content <html>", "HTML content").option("--content-file <path>", "Read HTML content from file").option("--notion-url <url>", "Notion page URL").option("--published", "Publish immediately").option("--tag-ids <ids>", "Comma-separated tag IDs").option("--author-ids <ids>", "Comma-separated author IDs").option("--canonical-url <url>", "Canonical URL").option("--meta-title <title>", "Meta title").option("--meta-description <desc>", "Meta description").action(async function() {
|
|
1298
|
+
posts.command("create").description("Create post (draft by default, use --published to publish)").requiredOption("-t, --title <title>", "Post title").option("-s, --slug <slug>", "Post slug").option("-d, --description <desc>", "Post description").option("--content <html>", "HTML content").option("--content-file <path>", "Read HTML content from file").option("--image <path-or-url>", "Cover image (local file or URL)").option("--notion-url <url>", "Notion page URL").option("--published", "Publish immediately").option("--tag-ids <ids>", "Comma-separated tag IDs").option("--author-ids <ids>", "Comma-separated author IDs").option("--canonical-url <url>", "Canonical URL").option("--meta-title <title>", "Meta title").option("--meta-description <desc>", "Meta description").action(async function() {
|
|
1187
1299
|
const json = isJsonMode(this);
|
|
1188
1300
|
try {
|
|
1189
1301
|
const opts = this.opts();
|
|
1190
1302
|
const { posts: endpoint } = createClientFromCommand(this);
|
|
1191
1303
|
let contentHtml = opts.content;
|
|
1192
1304
|
if (opts.contentFile) {
|
|
1193
|
-
contentHtml =
|
|
1305
|
+
contentHtml = fs4.readFileSync(opts.contentFile, "utf-8");
|
|
1306
|
+
}
|
|
1307
|
+
if (contentHtml) {
|
|
1308
|
+
const { html, uploadCount } = await processContentImages(contentHtml);
|
|
1309
|
+
contentHtml = html;
|
|
1310
|
+
if (uploadCount > 0 && !json) {
|
|
1311
|
+
printWarning(`Uploaded ${uploadCount} image(s) to CDN.`);
|
|
1312
|
+
}
|
|
1194
1313
|
}
|
|
1195
1314
|
const input = { title: opts.title };
|
|
1196
1315
|
if (opts.slug) input.slug = opts.slug;
|
|
1197
1316
|
if (opts.description) input.description = opts.description;
|
|
1198
1317
|
if (contentHtml) input.content_html = contentHtml;
|
|
1318
|
+
if (opts.image) input.image = { url: await resolveImageUrl(opts.image, "featured_image") };
|
|
1199
1319
|
if (opts.notionUrl) input.notion_url = opts.notionUrl;
|
|
1200
1320
|
if (opts.published) input.published = true;
|
|
1201
1321
|
if (opts.canonicalUrl) input.canonical_url = opts.canonicalUrl;
|
|
@@ -1214,20 +1334,28 @@ Showing page ${meta.page ?? 1} (${data.length} of ${meta.total} posts)`);
|
|
|
1214
1334
|
handleError(error, json);
|
|
1215
1335
|
}
|
|
1216
1336
|
});
|
|
1217
|
-
posts.command("update <id>").description("Update post fields (title, slug, content, SEO metadata)").option("-t, --title <title>", "Post title").option("-s, --slug <slug>", "Post slug").option("-d, --description <desc>", "Post description").option("--content <html>", "HTML content").option("--content-file <path>", "Read HTML content from file").option("--canonical-url <url>", "Canonical URL").option("--meta-title <title>", "Meta title").option("--meta-description <desc>", "Meta description").action(async function(id) {
|
|
1337
|
+
posts.command("update <id>").description("Update post fields (title, slug, content, SEO metadata)").option("-t, --title <title>", "Post title").option("-s, --slug <slug>", "Post slug").option("-d, --description <desc>", "Post description").option("--content <html>", "HTML content").option("--content-file <path>", "Read HTML content from file").option("--image <path-or-url>", "Cover image (local file or URL)").option("--canonical-url <url>", "Canonical URL").option("--meta-title <title>", "Meta title").option("--meta-description <desc>", "Meta description").action(async function(id) {
|
|
1218
1338
|
const json = isJsonMode(this);
|
|
1219
1339
|
try {
|
|
1220
1340
|
const opts = this.opts();
|
|
1221
1341
|
const { posts: endpoint } = createClientFromCommand(this);
|
|
1222
1342
|
let contentHtml = opts.content;
|
|
1223
1343
|
if (opts.contentFile) {
|
|
1224
|
-
contentHtml =
|
|
1344
|
+
contentHtml = fs4.readFileSync(opts.contentFile, "utf-8");
|
|
1345
|
+
}
|
|
1346
|
+
if (contentHtml) {
|
|
1347
|
+
const { html, uploadCount } = await processContentImages(contentHtml);
|
|
1348
|
+
contentHtml = html;
|
|
1349
|
+
if (uploadCount > 0 && !json) {
|
|
1350
|
+
printWarning(`Uploaded ${uploadCount} image(s) to CDN.`);
|
|
1351
|
+
}
|
|
1225
1352
|
}
|
|
1226
1353
|
const input = {};
|
|
1227
1354
|
if (opts.title) input.title = opts.title;
|
|
1228
1355
|
if (opts.slug) input.slug = opts.slug;
|
|
1229
1356
|
if (opts.description) input.description = opts.description;
|
|
1230
1357
|
if (contentHtml) input.content_html = contentHtml;
|
|
1358
|
+
if (opts.image) input.image = { url: await resolveImageUrl(opts.image, "featured_image") };
|
|
1231
1359
|
if (opts.canonicalUrl !== void 0) input.canonical_url = opts.canonicalUrl || null;
|
|
1232
1360
|
if (opts.metaTitle !== void 0) input.meta_title = opts.metaTitle || null;
|
|
1233
1361
|
if (opts.metaDescription !== void 0) input.meta_description = opts.metaDescription || null;
|
|
@@ -1672,54 +1800,6 @@ function getDnsProviderGuide(provider) {
|
|
|
1672
1800
|
return DNS_PROVIDER_GUIDES[provider] || null;
|
|
1673
1801
|
}
|
|
1674
1802
|
|
|
1675
|
-
// src/utils/upload.ts
|
|
1676
|
-
var import_node_fs = __toESM(require("fs"));
|
|
1677
|
-
var import_node_path = __toESM(require("path"));
|
|
1678
|
-
var import_node_crypto2 = require("crypto");
|
|
1679
|
-
var WORKER_URL = "https://upload.inblog.dev/";
|
|
1680
|
-
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
1681
|
-
var MIME_TYPES = {
|
|
1682
|
-
".png": "image/png",
|
|
1683
|
-
".jpg": "image/jpeg",
|
|
1684
|
-
".jpeg": "image/jpeg",
|
|
1685
|
-
".gif": "image/gif",
|
|
1686
|
-
".webp": "image/webp",
|
|
1687
|
-
".svg": "image/svg+xml",
|
|
1688
|
-
".ico": "image/x-icon"
|
|
1689
|
-
};
|
|
1690
|
-
function isLocalPath(value) {
|
|
1691
|
-
if (value.startsWith("http://") || value.startsWith("https://")) return false;
|
|
1692
|
-
return import_node_fs.default.existsSync(value);
|
|
1693
|
-
}
|
|
1694
|
-
async function uploadImage(filePath, bucket) {
|
|
1695
|
-
const resolved = import_node_path.default.resolve(filePath);
|
|
1696
|
-
if (!import_node_fs.default.existsSync(resolved)) {
|
|
1697
|
-
throw new Error(`File not found: ${resolved}`);
|
|
1698
|
-
}
|
|
1699
|
-
const stat = import_node_fs.default.statSync(resolved);
|
|
1700
|
-
if (stat.size > MAX_FILE_SIZE) {
|
|
1701
|
-
throw new Error(`File size exceeds 10MB limit: ${(stat.size / 1024 / 1024).toFixed(1)}MB`);
|
|
1702
|
-
}
|
|
1703
|
-
const ext = import_node_path.default.extname(resolved).toLowerCase();
|
|
1704
|
-
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
1705
|
-
const fileKey = `${bucket}/${(/* @__PURE__ */ new Date()).toISOString()}-${(0, import_node_crypto2.randomUUID)()}`;
|
|
1706
|
-
const body = import_node_fs.default.readFileSync(resolved);
|
|
1707
|
-
const response = await fetch(`${WORKER_URL}?fileKey=${fileKey}`, {
|
|
1708
|
-
method: "POST",
|
|
1709
|
-
body,
|
|
1710
|
-
headers: { "Content-Type": contentType }
|
|
1711
|
-
});
|
|
1712
|
-
if (!response.ok) {
|
|
1713
|
-
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
1714
|
-
}
|
|
1715
|
-
const data = await response.json();
|
|
1716
|
-
return data.publicUrl;
|
|
1717
|
-
}
|
|
1718
|
-
async function resolveImageUrl(value, bucket) {
|
|
1719
|
-
if (!isLocalPath(value)) return value;
|
|
1720
|
-
return uploadImage(value, bucket);
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
1803
|
// src/commands/blogs.ts
|
|
1724
1804
|
function registerBlogsCommands(program2) {
|
|
1725
1805
|
const blogs = program2.command("blogs").description("Manage blogs \u2014 list, switch, view, and update blog settings");
|
|
@@ -2593,6 +2673,35 @@ function registerAnalyticsCommands(program2) {
|
|
|
2593
2673
|
});
|
|
2594
2674
|
}
|
|
2595
2675
|
|
|
2676
|
+
// src/commands/images.ts
|
|
2677
|
+
var VALID_BUCKETS = ["post_image", "featured_image", "logo", "favicon", "og_image", "banner", "avatar"];
|
|
2678
|
+
function registerImagesCommands(program2) {
|
|
2679
|
+
const images = program2.command("images").description("Upload images to inblog CDN");
|
|
2680
|
+
images.command("upload <file...>").description("Upload local image file(s) to inblog CDN").option("-b, --bucket <type>", "Image bucket (post_image, featured_image, logo, favicon, og_image, banner)", "post_image").action(async function(files) {
|
|
2681
|
+
const json = isJsonMode(this);
|
|
2682
|
+
try {
|
|
2683
|
+
const opts = this.opts();
|
|
2684
|
+
const bucket = opts.bucket;
|
|
2685
|
+
if (!VALID_BUCKETS.includes(bucket)) {
|
|
2686
|
+
throw new Error(`Invalid bucket: ${bucket}. Valid: ${VALID_BUCKETS.join(", ")}`);
|
|
2687
|
+
}
|
|
2688
|
+
const results = [];
|
|
2689
|
+
for (const file of files) {
|
|
2690
|
+
const url = await uploadImage(file, bucket);
|
|
2691
|
+
results.push({ file, url });
|
|
2692
|
+
if (!json) {
|
|
2693
|
+
printSuccess(`${file} \u2192 ${url}`);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
if (json) {
|
|
2697
|
+
printJson(results);
|
|
2698
|
+
}
|
|
2699
|
+
} catch (error) {
|
|
2700
|
+
handleError(error, json);
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2596
2705
|
// bin/inblog.ts
|
|
2597
2706
|
var program = new import_commander.Command();
|
|
2598
2707
|
program.name("inblog").description("CLI for managing inblog.ai blog content (posts, tags, authors, redirects, forms)").version("0.2.0").option("--json", "Output as JSON (for programmatic use)").option("--base-url <url>", "API base URL").option("--no-color", "Disable colored output");
|
|
@@ -2607,5 +2716,6 @@ registerFormResponsesCommands(program);
|
|
|
2607
2716
|
registerConfigCommands(program);
|
|
2608
2717
|
registerSearchConsoleCommands(program);
|
|
2609
2718
|
registerAnalyticsCommands(program);
|
|
2719
|
+
registerImagesCommands(program);
|
|
2610
2720
|
program.parse();
|
|
2611
2721
|
//# sourceMappingURL=inblog.js.map
|