@witchpot/devlog-cli 0.1.0 → 0.1.1
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/build/auth/auth-flow.js +3 -1
- package/build/client.d.ts +10 -0
- package/build/client.js +14 -0
- package/build/commands/drafts.d.ts +2 -2
- package/build/commands/drafts.js +7 -3
- package/build/commands/games.js +4 -1
- package/build/commands/knowledge.d.ts +5 -0
- package/build/commands/knowledge.js +28 -0
- package/build/commands/media.d.ts +16 -0
- package/build/commands/media.js +102 -0
- package/build/config.js +1 -1
- package/build/index.js +18 -0
- package/build/types.d.ts +7 -0
- package/package.json +1 -1
package/build/auth/auth-flow.js
CHANGED
|
@@ -23,10 +23,12 @@ function escapeHtml(s) {
|
|
|
23
23
|
.replace(/>/g, ">")
|
|
24
24
|
.replace(/"/g, """);
|
|
25
25
|
}
|
|
26
|
+
const VALID_CSS_COLOR = /^#[0-9a-fA-F]{6}$/;
|
|
26
27
|
function htmlPage(title, color, message) {
|
|
28
|
+
const safeColor = VALID_CSS_COLOR.test(color) ? color : "#333";
|
|
27
29
|
return `<!DOCTYPE html><html><head><title>${escapeHtml(title)}</title>
|
|
28
30
|
<style>body{font-family:system-ui,sans-serif;max-width:600px;margin:100px auto;padding:20px;text-align:center}
|
|
29
|
-
h1{color:${
|
|
31
|
+
h1{color:${safeColor}}p{color:#666}</style></head>
|
|
30
32
|
<body><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p><p>You can close this window.</p>
|
|
31
33
|
<script>setTimeout(()=>{try{window.close()}catch(e){}},2000)</script></body></html>`;
|
|
32
34
|
}
|
package/build/client.d.ts
CHANGED
|
@@ -21,6 +21,16 @@ export declare class DevlogClient {
|
|
|
21
21
|
* DELETE request to a CLI API endpoint.
|
|
22
22
|
*/
|
|
23
23
|
delete<T>(path: string, body?: unknown): Promise<T>;
|
|
24
|
+
/**
|
|
25
|
+
* Build a full URL for a CLI API endpoint.
|
|
26
|
+
* Useful for non-JSON requests (e.g. multipart uploads).
|
|
27
|
+
*/
|
|
28
|
+
buildUrl(path: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Return the Authorization header value.
|
|
31
|
+
* Useful for non-JSON requests (e.g. multipart uploads).
|
|
32
|
+
*/
|
|
33
|
+
getAuthHeader(): string;
|
|
24
34
|
/**
|
|
25
35
|
* GET request to an absolute API path (without /api/cli prefix).
|
|
26
36
|
* Used for endpoints like /api/steam/search that aren't under the CLI namespace.
|
package/build/client.js
CHANGED
|
@@ -77,6 +77,20 @@ export class DevlogClient {
|
|
|
77
77
|
});
|
|
78
78
|
return this.handleResponse(response);
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Build a full URL for a CLI API endpoint.
|
|
82
|
+
* Useful for non-JSON requests (e.g. multipart uploads).
|
|
83
|
+
*/
|
|
84
|
+
buildUrl(path) {
|
|
85
|
+
return new URL(`${this.apiPrefix}${path}`, this.baseUrl).toString();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Return the Authorization header value.
|
|
89
|
+
* Useful for non-JSON requests (e.g. multipart uploads).
|
|
90
|
+
*/
|
|
91
|
+
getAuthHeader() {
|
|
92
|
+
return `Bearer ${this.token}`;
|
|
93
|
+
}
|
|
80
94
|
/**
|
|
81
95
|
* GET request to an absolute API path (without /api/cli prefix).
|
|
82
96
|
* Used for endpoints like /api/steam/search that aren't under the CLI namespace.
|
|
@@ -7,14 +7,14 @@ export declare const draftListCommand: CommandHandler;
|
|
|
7
7
|
* devlog draft create [projectId] --platform=<platform> --body=<body>
|
|
8
8
|
* [--hook=<hook>] [--cta=<cta>] [--hashtags=<h1,h2>]
|
|
9
9
|
* [--rationale=<text>] [--post-type=<type>] [--emotional-hook=<type>]
|
|
10
|
-
* [--cta-type=<type>] [--media-style=<style>] [--language=<code>]
|
|
10
|
+
* [--cta-type=<type>] [--media-style=<style>] [--media-suggestion=<text>] [--language=<code>]
|
|
11
11
|
*/
|
|
12
12
|
export declare const draftCreateCommand: CommandHandler;
|
|
13
13
|
/**
|
|
14
14
|
* devlog draft update <draftId> --project=<projectId>
|
|
15
15
|
* [--body=<body>] [--hook=<hook>] [--platform=<platform>] [--status=<status>]
|
|
16
16
|
* [--cta=<cta>] [--hashtags=<h1,h2>] [--rationale=<text>] [--post-type=<type>]
|
|
17
|
-
* [--emotional-hook=<type>] [--cta-type=<type>] [--media-style=<style>] [--language=<code>]
|
|
17
|
+
* [--emotional-hook=<type>] [--cta-type=<type>] [--media-style=<style>] [--media-suggestion=<text>] [--language=<code>]
|
|
18
18
|
*/
|
|
19
19
|
export declare const draftUpdateCommand: CommandHandler;
|
|
20
20
|
/**
|
package/build/commands/drafts.js
CHANGED
|
@@ -26,7 +26,7 @@ export const draftListCommand = async (client, args, _flags, format) => {
|
|
|
26
26
|
* devlog draft create [projectId] --platform=<platform> --body=<body>
|
|
27
27
|
* [--hook=<hook>] [--cta=<cta>] [--hashtags=<h1,h2>]
|
|
28
28
|
* [--rationale=<text>] [--post-type=<type>] [--emotional-hook=<type>]
|
|
29
|
-
* [--cta-type=<type>] [--media-style=<style>] [--language=<code>]
|
|
29
|
+
* [--cta-type=<type>] [--media-style=<style>] [--media-suggestion=<text>] [--language=<code>]
|
|
30
30
|
*/
|
|
31
31
|
export const draftCreateCommand = async (client, args, flags, _format) => {
|
|
32
32
|
const projectId = args[0] || getActiveProjectId();
|
|
@@ -62,6 +62,8 @@ export const draftCreateCommand = async (client, args, flags, _format) => {
|
|
|
62
62
|
reqBody.cta_type = flags["cta-type"];
|
|
63
63
|
if (flags["media-style"])
|
|
64
64
|
reqBody.media_style = flags["media-style"];
|
|
65
|
+
if (flags["media-suggestion"])
|
|
66
|
+
reqBody.media_suggestion = flags["media-suggestion"];
|
|
65
67
|
if (flags["language"])
|
|
66
68
|
reqBody.language = flags["language"];
|
|
67
69
|
const res = await client.post(`/projects/${projectId}/drafts`, reqBody);
|
|
@@ -71,7 +73,7 @@ export const draftCreateCommand = async (client, args, flags, _format) => {
|
|
|
71
73
|
* devlog draft update <draftId> --project=<projectId>
|
|
72
74
|
* [--body=<body>] [--hook=<hook>] [--platform=<platform>] [--status=<status>]
|
|
73
75
|
* [--cta=<cta>] [--hashtags=<h1,h2>] [--rationale=<text>] [--post-type=<type>]
|
|
74
|
-
* [--emotional-hook=<type>] [--cta-type=<type>] [--media-style=<style>] [--language=<code>]
|
|
76
|
+
* [--emotional-hook=<type>] [--cta-type=<type>] [--media-style=<style>] [--media-suggestion=<text>] [--language=<code>]
|
|
75
77
|
*/
|
|
76
78
|
export const draftUpdateCommand = async (client, args, flags, _format) => {
|
|
77
79
|
const draftId = args[0];
|
|
@@ -109,10 +111,12 @@ export const draftUpdateCommand = async (client, args, flags, _format) => {
|
|
|
109
111
|
body.cta_type = flags["cta-type"];
|
|
110
112
|
if (flags["media-style"] !== undefined)
|
|
111
113
|
body.media_style = flags["media-style"];
|
|
114
|
+
if (flags["media-suggestion"] !== undefined)
|
|
115
|
+
body.media_suggestion = flags["media-suggestion"];
|
|
112
116
|
if (flags["language"] !== undefined)
|
|
113
117
|
body.language = flags["language"];
|
|
114
118
|
if (Object.keys(body).length === 0) {
|
|
115
|
-
console.error("No fields to update. Use --body, --hook, --cta, --platform, --status, --hashtags, --rationale, --post-type, --emotional-hook, --cta-type, --media-style, or --language.");
|
|
119
|
+
console.error("No fields to update. Use --body, --hook, --cta, --platform, --status, --hashtags, --rationale, --post-type, --emotional-hook, --cta-type, --media-style, --media-suggestion, or --language.");
|
|
116
120
|
process.exit(1);
|
|
117
121
|
}
|
|
118
122
|
const res = await client.patch(`/projects/${projectId}/drafts/${draftId}`, body);
|
package/build/commands/games.js
CHANGED
|
@@ -15,7 +15,10 @@ export const gamesListCommand = async (client, args, _flags, format) => {
|
|
|
15
15
|
reviews: g.total_reviews ?? "-",
|
|
16
16
|
rating: g.rating_percent != null ? `${g.rating_percent}%` : "-",
|
|
17
17
|
developer: g.developer ?? "-",
|
|
18
|
-
|
|
18
|
+
tags: g.tags?.join(", ") || "-",
|
|
19
|
+
aspects: g.game_similarity_aspects
|
|
20
|
+
?.map((a) => `${a.aspect}: ${a.description}`)
|
|
21
|
+
.join(" | ") || "-",
|
|
19
22
|
})));
|
|
20
23
|
}
|
|
21
24
|
else {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { printJson } from "../output.js";
|
|
2
|
+
/**
|
|
3
|
+
* devlog knowledge get <slug> - Fetch a knowledge document
|
|
4
|
+
*/
|
|
5
|
+
export const knowledgeGetCommand = async (client, args, _flags, format) => {
|
|
6
|
+
const slug = args[0];
|
|
7
|
+
if (!slug) {
|
|
8
|
+
console.error("Usage: devlog knowledge get <slug>");
|
|
9
|
+
console.error("Example: devlog knowledge get sns-content-strategy");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const res = await client.get(`/knowledge/${slug}`);
|
|
13
|
+
if (format === "table") {
|
|
14
|
+
// For table format, show metadata only (content is too long)
|
|
15
|
+
console.log(`Title: ${res.data.title}`);
|
|
16
|
+
console.log(`Slug: ${res.data.slug}`);
|
|
17
|
+
console.log(`Category: ${res.data.category}`);
|
|
18
|
+
console.log(`Version: ${res.data.version}`);
|
|
19
|
+
console.log(`Updated: ${res.data.updated_at}`);
|
|
20
|
+
console.log(`Content: ${res.data.content.length} chars`);
|
|
21
|
+
console.log("---");
|
|
22
|
+
console.log(res.data.content);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
printJson(res.data);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=knowledge.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CommandHandler } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* devlog media list <draftId> [--project=<id>]
|
|
4
|
+
*/
|
|
5
|
+
export declare const mediaListCommand: CommandHandler;
|
|
6
|
+
/**
|
|
7
|
+
* devlog media upload <draftId> --file=<path> [--project=<id>]
|
|
8
|
+
*
|
|
9
|
+
* Uploads a local file as media to a draft. Uses multipart/form-data
|
|
10
|
+
* via fetch since the DevlogClient only supports JSON.
|
|
11
|
+
*/
|
|
12
|
+
export declare const mediaUploadCommand: CommandHandler;
|
|
13
|
+
/**
|
|
14
|
+
* devlog media remove <draftId> --url=<mediaUrl> [--project=<id>]
|
|
15
|
+
*/
|
|
16
|
+
export declare const mediaRemoveCommand: CommandHandler;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFileSync, statSync, existsSync } from "node:fs";
|
|
2
|
+
import { basename, extname } from "node:path";
|
|
3
|
+
import { printJson } from "../output.js";
|
|
4
|
+
import { getActiveProjectId } from "../config.js";
|
|
5
|
+
import { assertSafeId } from "../errors.js";
|
|
6
|
+
const ALLOWED_EXTENSIONS = {
|
|
7
|
+
".png": "image/png",
|
|
8
|
+
".jpg": "image/jpeg",
|
|
9
|
+
".jpeg": "image/jpeg",
|
|
10
|
+
".gif": "image/gif",
|
|
11
|
+
".webp": "image/webp",
|
|
12
|
+
".mp4": "video/mp4",
|
|
13
|
+
".webm": "video/webm",
|
|
14
|
+
};
|
|
15
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
|
|
16
|
+
/**
|
|
17
|
+
* devlog media list <draftId> [--project=<id>]
|
|
18
|
+
*/
|
|
19
|
+
export const mediaListCommand = async (client, args, flags, _format) => {
|
|
20
|
+
const draftId = args[0];
|
|
21
|
+
if (!draftId) {
|
|
22
|
+
console.error("Usage: devlog media list <draftId> [--project=<id>]");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const projectId = flags["project"] || getActiveProjectId();
|
|
26
|
+
assertSafeId(draftId, "draft ID");
|
|
27
|
+
assertSafeId(projectId, "project ID");
|
|
28
|
+
const res = await client.get(`/projects/${projectId}/drafts/${draftId}/media`);
|
|
29
|
+
printJson(res.data);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* devlog media upload <draftId> --file=<path> [--project=<id>]
|
|
33
|
+
*
|
|
34
|
+
* Uploads a local file as media to a draft. Uses multipart/form-data
|
|
35
|
+
* via fetch since the DevlogClient only supports JSON.
|
|
36
|
+
*/
|
|
37
|
+
export const mediaUploadCommand = async (client, args, flags, _format) => {
|
|
38
|
+
const draftId = args[0];
|
|
39
|
+
const filePath = flags["file"];
|
|
40
|
+
if (!draftId || !filePath) {
|
|
41
|
+
console.error("Usage: devlog media upload <draftId> --file=<path> [--project=<id>]");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const projectId = flags["project"] || getActiveProjectId();
|
|
45
|
+
assertSafeId(draftId, "draft ID");
|
|
46
|
+
assertSafeId(projectId, "project ID");
|
|
47
|
+
// Validate file exists
|
|
48
|
+
if (!existsSync(filePath)) {
|
|
49
|
+
console.error(`File not found: ${filePath}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const stat = statSync(filePath);
|
|
53
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
54
|
+
console.error(`File too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Max: 50MB`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const ext = extname(filePath).toLowerCase();
|
|
58
|
+
const mimeType = ALLOWED_EXTENSIONS[ext];
|
|
59
|
+
if (!mimeType) {
|
|
60
|
+
console.error(`Unsupported file type: ${ext}`);
|
|
61
|
+
console.error(`Allowed: ${Object.keys(ALLOWED_EXTENSIONS).join(", ")}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const fileBuffer = readFileSync(filePath);
|
|
65
|
+
const fileName = basename(filePath);
|
|
66
|
+
const blob = new Blob([fileBuffer], { type: mimeType });
|
|
67
|
+
const formData = new FormData();
|
|
68
|
+
formData.append("file", blob, fileName);
|
|
69
|
+
// We need to use the client's internal URL and token to make a multipart request.
|
|
70
|
+
const url = client.buildUrl(`/projects/${projectId}/drafts/${draftId}/media`);
|
|
71
|
+
const response = await fetch(url, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: {
|
|
74
|
+
Authorization: client.getAuthHeader(),
|
|
75
|
+
},
|
|
76
|
+
body: formData,
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const errBody = await response.json().catch(() => ({ error: `HTTP ${response.status}` }));
|
|
80
|
+
console.error(`Upload failed: ${errBody.error || response.statusText}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const result = await response.json();
|
|
84
|
+
printJson(result.data);
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* devlog media remove <draftId> --url=<mediaUrl> [--project=<id>]
|
|
88
|
+
*/
|
|
89
|
+
export const mediaRemoveCommand = async (client, args, flags, _format) => {
|
|
90
|
+
const draftId = args[0];
|
|
91
|
+
const mediaUrl = flags["url"];
|
|
92
|
+
if (!draftId || !mediaUrl) {
|
|
93
|
+
console.error("Usage: devlog media remove <draftId> --url=<mediaUrl> [--project=<id>]");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const projectId = flags["project"] || getActiveProjectId();
|
|
97
|
+
assertSafeId(draftId, "draft ID");
|
|
98
|
+
assertSafeId(projectId, "project ID");
|
|
99
|
+
const res = await client.delete(`/projects/${projectId}/drafts/${draftId}/media`, { mediaUrl });
|
|
100
|
+
printJson(res.data);
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=media.js.map
|
package/build/config.js
CHANGED
|
@@ -50,7 +50,7 @@ function validateUrl(url) {
|
|
|
50
50
|
process.exit(1);
|
|
51
51
|
}
|
|
52
52
|
if (parsed.protocol === "http:" && !isLoopback(parsed.hostname)) {
|
|
53
|
-
console.error(`
|
|
53
|
+
console.error(`Refusing unencrypted HTTP for non-local host "${parsed.hostname}". Use HTTPS instead.`);
|
|
54
54
|
process.exit(1);
|
|
55
55
|
}
|
|
56
56
|
}
|
package/build/index.js
CHANGED
|
@@ -14,6 +14,8 @@ import { benchListCommand, benchAddCommand, benchRemoveCommand, } from "./comman
|
|
|
14
14
|
import { eventsListCommand, eventsAddCommand, eventsRemoveCommand, } from "./commands/events.js";
|
|
15
15
|
import { contextCommand } from "./commands/context.js";
|
|
16
16
|
import { steamSearchCommand, steamDetailsCommand, } from "./commands/steam.js";
|
|
17
|
+
import { knowledgeGetCommand } from "./commands/knowledge.js";
|
|
18
|
+
import { mediaListCommand, mediaUploadCommand, mediaRemoveCommand, } from "./commands/media.js";
|
|
17
19
|
const HELP = `devlog - DevLog Social Copilot CLI
|
|
18
20
|
|
|
19
21
|
Usage: devlog <command> [subcommand] [options]
|
|
@@ -46,6 +48,11 @@ Drafts:
|
|
|
46
48
|
draft update <draftId> Update a draft
|
|
47
49
|
draft delete <draftId> Delete a draft
|
|
48
50
|
|
|
51
|
+
Media:
|
|
52
|
+
media list <draftId> List media URLs for a draft
|
|
53
|
+
media upload <draftId> Upload media file to a draft
|
|
54
|
+
media remove <draftId> Remove media from a draft
|
|
55
|
+
|
|
49
56
|
Benchmark:
|
|
50
57
|
bench list [projectId] List benchmark accounts
|
|
51
58
|
bench add [projectId] Add a benchmark account
|
|
@@ -63,6 +70,9 @@ Steam:
|
|
|
63
70
|
steam search Search Steam games
|
|
64
71
|
steam details <appid> Get game details
|
|
65
72
|
|
|
73
|
+
Knowledge:
|
|
74
|
+
knowledge get <slug> Fetch a knowledge document
|
|
75
|
+
|
|
66
76
|
Options:
|
|
67
77
|
--url=<url> DevLog URL (default: http://localhost:3000)
|
|
68
78
|
--format=json|table Output format (default: json)
|
|
@@ -103,6 +113,11 @@ const COMPOUND_COMMANDS = {
|
|
|
103
113
|
update: draftUpdateCommand,
|
|
104
114
|
delete: draftDeleteCommand,
|
|
105
115
|
},
|
|
116
|
+
media: {
|
|
117
|
+
list: mediaListCommand,
|
|
118
|
+
upload: mediaUploadCommand,
|
|
119
|
+
remove: mediaRemoveCommand,
|
|
120
|
+
},
|
|
106
121
|
bench: {
|
|
107
122
|
list: benchListCommand,
|
|
108
123
|
add: benchAddCommand,
|
|
@@ -117,6 +132,9 @@ const COMPOUND_COMMANDS = {
|
|
|
117
132
|
search: steamSearchCommand,
|
|
118
133
|
details: steamDetailsCommand,
|
|
119
134
|
},
|
|
135
|
+
knowledge: {
|
|
136
|
+
get: knowledgeGetCommand,
|
|
137
|
+
},
|
|
120
138
|
};
|
|
121
139
|
// Simple top-level commands (no subcommand)
|
|
122
140
|
const SIMPLE_COMMANDS = {
|
package/build/types.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export interface ProjectDetail extends Project {
|
|
|
21
21
|
project_similar_games: SimilarGame[];
|
|
22
22
|
project_repositories: ProjectRepository[];
|
|
23
23
|
}
|
|
24
|
+
export interface GameSimilarityAspect {
|
|
25
|
+
aspect: string;
|
|
26
|
+
description: string;
|
|
27
|
+
}
|
|
24
28
|
export interface SimilarGame {
|
|
25
29
|
id?: string;
|
|
26
30
|
game_name: string;
|
|
@@ -33,6 +37,8 @@ export interface SimilarGame {
|
|
|
33
37
|
website: string | null;
|
|
34
38
|
note: string | null;
|
|
35
39
|
category?: string | null;
|
|
40
|
+
source?: string | null;
|
|
41
|
+
game_similarity_aspects?: GameSimilarityAspect[];
|
|
36
42
|
}
|
|
37
43
|
export interface ProjectRepository {
|
|
38
44
|
repo_full_name: string;
|
|
@@ -65,6 +71,7 @@ export interface PostDraft {
|
|
|
65
71
|
emotional_hook: string | null;
|
|
66
72
|
cta_type: string | null;
|
|
67
73
|
media_style: string | null;
|
|
74
|
+
media_suggestion: string | null;
|
|
68
75
|
media_urls: string[] | null;
|
|
69
76
|
created_at: string;
|
|
70
77
|
updated_at: string;
|