@witchpot/devlog-cli 0.1.0 → 0.1.2

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.
@@ -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:${color}}p{color:#666}</style></head>
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
  /**
@@ -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);
@@ -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
- note: g.note ?? "-",
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,5 @@
1
+ import type { CommandHandler } from "../types.js";
2
+ /**
3
+ * devlog knowledge get <slug> - Fetch a knowledge document
4
+ */
5
+ export declare const knowledgeGetCommand: CommandHandler;
@@ -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(`Warning: Using unencrypted HTTP with a non-local host (${parsed.hostname}). Use HTTPS for production.`);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@witchpot/devlog-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "DevLog Social Copilot CLI",
5
5
  "type": "module",
6
6
  "main": "./build/index.js",
@@ -19,11 +19,7 @@
19
19
  "marketing",
20
20
  "cli"
21
21
  ],
22
- "repository": {
23
- "type": "git",
24
- "url": "https://github.com/Witchpot/DevLogSystem.git",
25
- "directory": "packages/cli"
26
- },
22
+ "homepage": "https://devlog.witchpot.com",
27
23
  "license": "MIT",
28
24
  "publishConfig": {
29
25
  "access": "public"