@witchpot/devlog-cli 0.1.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/build/auth/auth-flow.d.ts +20 -0
- package/build/auth/auth-flow.js +178 -0
- package/build/auth/token-storage.d.ts +37 -0
- package/build/auth/token-storage.js +115 -0
- package/build/client.d.ts +30 -0
- package/build/client.js +130 -0
- package/build/commands/auth.d.ts +12 -0
- package/build/commands/auth.js +36 -0
- package/build/commands/bench.d.ts +13 -0
- package/build/commands/bench.js +60 -0
- package/build/commands/context.d.ts +6 -0
- package/build/commands/context.js +33 -0
- package/build/commands/drafts.d.ts +23 -0
- package/build/commands/drafts.js +136 -0
- package/build/commands/events.d.ts +13 -0
- package/build/commands/events.js +69 -0
- package/build/commands/games.d.ts +13 -0
- package/build/commands/games.js +64 -0
- package/build/commands/history.d.ts +9 -0
- package/build/commands/history.js +44 -0
- package/build/commands/projects.d.ts +26 -0
- package/build/commands/projects.js +120 -0
- package/build/commands/steam.d.ts +12 -0
- package/build/commands/steam.js +82 -0
- package/build/config.d.ts +36 -0
- package/build/config.js +149 -0
- package/build/errors.d.ts +23 -0
- package/build/errors.js +54 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +197 -0
- package/build/output.d.ts +13 -0
- package/build/output.js +53 -0
- package/build/types.d.ts +120 -0
- package/build/types.js +2 -0
- package/package.json +48 -0
package/build/config.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { readStoredToken } from "./auth/token-storage.js";
|
|
5
|
+
export const CONFIG_DIR = join(homedir(), ".devlog-cli");
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
+
function isValidConfigFile(obj) {
|
|
8
|
+
if (typeof obj !== "object" || obj === null)
|
|
9
|
+
return false;
|
|
10
|
+
const c = obj;
|
|
11
|
+
if (c.activeProjectId !== undefined && typeof c.activeProjectId !== "string")
|
|
12
|
+
return false;
|
|
13
|
+
if (c.apiUrl !== undefined && typeof c.apiUrl !== "string")
|
|
14
|
+
return false;
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
function ensureConfigDir() {
|
|
18
|
+
mkdirSync(CONFIG_DIR, { mode: 0o700, recursive: true });
|
|
19
|
+
}
|
|
20
|
+
function loadConfigFile() {
|
|
21
|
+
try {
|
|
22
|
+
const content = readFileSync(CONFIG_FILE, "utf-8");
|
|
23
|
+
const parsed = JSON.parse(content);
|
|
24
|
+
if (!isValidConfigFile(parsed))
|
|
25
|
+
return null;
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function saveConfigFile(config) {
|
|
33
|
+
ensureConfigDir();
|
|
34
|
+
// Merge with existing config
|
|
35
|
+
const existing = loadConfigFile() || {};
|
|
36
|
+
const merged = { ...existing, ...config };
|
|
37
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", {
|
|
38
|
+
mode: 0o600,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function isLoopback(hostname) {
|
|
42
|
+
return ["localhost", "127.0.0.1", "::1"].includes(hostname);
|
|
43
|
+
}
|
|
44
|
+
function validateUrl(url) {
|
|
45
|
+
const cleanUrl = url.replace(/\/$/, "");
|
|
46
|
+
try {
|
|
47
|
+
const parsed = new URL(cleanUrl);
|
|
48
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
49
|
+
console.error(`Invalid URL protocol: ${parsed.protocol} -- only http/https are allowed.`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
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.`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
console.error(`Invalid URL: ${cleanUrl}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
return cleanUrl;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve configuration from flags, env vars, config file, and stored token.
|
|
65
|
+
*
|
|
66
|
+
* Priority:
|
|
67
|
+
* 1. --token flag / DEVLOG_TOKEN env var (explicit token)
|
|
68
|
+
* 2. Stored token (from `devlog login`)
|
|
69
|
+
* 3. Neither -- error at request time
|
|
70
|
+
*/
|
|
71
|
+
export function resolveConfig(flags) {
|
|
72
|
+
const configFile = loadConfigFile();
|
|
73
|
+
// Resolve token
|
|
74
|
+
const explicitToken = flags["token"] || process.env.DEVLOG_TOKEN || undefined;
|
|
75
|
+
// Resolve API URL
|
|
76
|
+
const storedToken = readStoredToken();
|
|
77
|
+
const apiUrl = flags["url"] ||
|
|
78
|
+
process.env.DEVLOG_URL ||
|
|
79
|
+
configFile?.apiUrl ||
|
|
80
|
+
storedToken?.apiUrl ||
|
|
81
|
+
"https://devlog.witchpot.com";
|
|
82
|
+
const token = explicitToken || storedToken?.token || undefined;
|
|
83
|
+
return {
|
|
84
|
+
apiUrl: validateUrl(apiUrl),
|
|
85
|
+
token,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get the active project ID from config.
|
|
90
|
+
* Returns the ID or exits with a helpful error message.
|
|
91
|
+
*/
|
|
92
|
+
export function getActiveProjectId() {
|
|
93
|
+
const config = loadConfigFile();
|
|
94
|
+
if (!config?.activeProjectId) {
|
|
95
|
+
console.error('No project specified. Run "devlog project use <id>" first, or pass a project ID as an argument.');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
return config.activeProjectId;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Set the active project ID in config.
|
|
102
|
+
*/
|
|
103
|
+
export function setActiveProjectId(projectId) {
|
|
104
|
+
saveConfigFile({ activeProjectId: projectId });
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get the active project ID from config, or undefined if not set.
|
|
108
|
+
* Does not exit on missing -- use when the ID is optional.
|
|
109
|
+
*/
|
|
110
|
+
export function getActiveProjectIdOptional() {
|
|
111
|
+
const config = loadConfigFile();
|
|
112
|
+
return config?.activeProjectId;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Parse CLI arguments into command, positional args, and flags.
|
|
116
|
+
*/
|
|
117
|
+
export function parseArgs(argv) {
|
|
118
|
+
const rawArgs = argv.slice(2);
|
|
119
|
+
const command = rawArgs[0] || "help";
|
|
120
|
+
const rest = rawArgs.slice(1);
|
|
121
|
+
const flags = {};
|
|
122
|
+
const args = [];
|
|
123
|
+
for (let i = 0; i < rest.length; i++) {
|
|
124
|
+
const arg = rest[i];
|
|
125
|
+
if (arg.startsWith("--")) {
|
|
126
|
+
const eqIndex = arg.indexOf("=");
|
|
127
|
+
if (eqIndex !== -1) {
|
|
128
|
+
const key = arg.slice(2, eqIndex);
|
|
129
|
+
flags[key] = arg.slice(eqIndex + 1);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const key = arg.slice(2);
|
|
133
|
+
const next = rest[i + 1];
|
|
134
|
+
if (next && !next.startsWith("--")) {
|
|
135
|
+
flags[key] = next;
|
|
136
|
+
i++;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
flags[key] = "true";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
args.push(arg);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { command, args, flags };
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ApiErrorDetail {
|
|
2
|
+
code?: string;
|
|
3
|
+
message: string;
|
|
4
|
+
hint?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class ApiError extends Error {
|
|
7
|
+
status: number;
|
|
8
|
+
detail: ApiErrorDetail;
|
|
9
|
+
constructor(status: number, detail: ApiErrorDetail);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Map HTTP status codes to CLI exit codes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function exitCodeFromError(error: ApiError): number;
|
|
15
|
+
/**
|
|
16
|
+
* Validate that an ID is safe to interpolate into a URL path.
|
|
17
|
+
* Blocks path traversal (../, /) and other injection characters.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertSafeId(id: string, label: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Format an API error for CLI output.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatError(error: ApiError): string;
|
package/build/errors.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export class ApiError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
detail;
|
|
4
|
+
constructor(status, detail) {
|
|
5
|
+
super(detail.message);
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.detail = detail;
|
|
8
|
+
this.name = "ApiError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Map HTTP status codes to CLI exit codes.
|
|
13
|
+
*/
|
|
14
|
+
export function exitCodeFromError(error) {
|
|
15
|
+
if (error.status === 401 || error.status === 403)
|
|
16
|
+
return 2;
|
|
17
|
+
if (error.status === 404)
|
|
18
|
+
return 3;
|
|
19
|
+
if (error.status === 409)
|
|
20
|
+
return 4;
|
|
21
|
+
if (error.status === 429)
|
|
22
|
+
return 4;
|
|
23
|
+
if (error.status >= 500)
|
|
24
|
+
return 5;
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
const SAFE_ID_REGEX = /^[0-9a-zA-Z_-]+$/;
|
|
28
|
+
/**
|
|
29
|
+
* Validate that an ID is safe to interpolate into a URL path.
|
|
30
|
+
* Blocks path traversal (../, /) and other injection characters.
|
|
31
|
+
*/
|
|
32
|
+
export function assertSafeId(id, label) {
|
|
33
|
+
if (!id || !SAFE_ID_REGEX.test(id)) {
|
|
34
|
+
console.error(`Invalid ${label}: ${id}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format an API error for CLI output.
|
|
40
|
+
*/
|
|
41
|
+
export function formatError(error) {
|
|
42
|
+
const lines = [];
|
|
43
|
+
if (error.detail.code) {
|
|
44
|
+
lines.push(`Error [${error.detail.code}]: ${error.detail.message}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
lines.push(`Error: ${error.detail.message}`);
|
|
48
|
+
}
|
|
49
|
+
if (error.detail.hint) {
|
|
50
|
+
lines.push(` Hint: ${error.detail.hint}`);
|
|
51
|
+
}
|
|
52
|
+
return lines.join("\n");
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=errors.js.map
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs, resolveConfig } from "./config.js";
|
|
3
|
+
import { DevlogClient } from "./client.js";
|
|
4
|
+
import { ApiError, exitCodeFromError, formatError } from "./errors.js";
|
|
5
|
+
import { getFormat } from "./output.js";
|
|
6
|
+
// Auth commands
|
|
7
|
+
import { loginCommand, logoutCommand, statusCommand, } from "./commands/auth.js";
|
|
8
|
+
// API commands
|
|
9
|
+
import { projectListCommand, projectShowCommand, projectCreateCommand, projectUpdateCommand, projectDeleteCommand, projectUseCommand, } from "./commands/projects.js";
|
|
10
|
+
import { gamesListCommand, gamesAddCommand, gamesRemoveCommand, } from "./commands/games.js";
|
|
11
|
+
import { historyListCommand, historyAddCommand, } from "./commands/history.js";
|
|
12
|
+
import { draftListCommand, draftCreateCommand, draftUpdateCommand, draftDeleteCommand, } from "./commands/drafts.js";
|
|
13
|
+
import { benchListCommand, benchAddCommand, benchRemoveCommand, } from "./commands/bench.js";
|
|
14
|
+
import { eventsListCommand, eventsAddCommand, eventsRemoveCommand, } from "./commands/events.js";
|
|
15
|
+
import { contextCommand } from "./commands/context.js";
|
|
16
|
+
import { steamSearchCommand, steamDetailsCommand, } from "./commands/steam.js";
|
|
17
|
+
const HELP = `devlog - DevLog Social Copilot CLI
|
|
18
|
+
|
|
19
|
+
Usage: devlog <command> [subcommand] [options]
|
|
20
|
+
|
|
21
|
+
Authentication:
|
|
22
|
+
login Authenticate via browser
|
|
23
|
+
logout Clear stored credentials
|
|
24
|
+
status Show authentication status
|
|
25
|
+
|
|
26
|
+
Projects:
|
|
27
|
+
project list List all projects
|
|
28
|
+
project show [id] Show project details
|
|
29
|
+
project create Create a new project
|
|
30
|
+
project update [id] Update project
|
|
31
|
+
project delete <id> Delete project
|
|
32
|
+
project use <id> Set active project
|
|
33
|
+
|
|
34
|
+
Games (Similar Games):
|
|
35
|
+
games list [projectId] List similar games
|
|
36
|
+
games add [projectId] Add a similar game
|
|
37
|
+
games remove [projectId] Remove a similar game
|
|
38
|
+
|
|
39
|
+
History:
|
|
40
|
+
history list [projectId] List history cards
|
|
41
|
+
history add [projectId] Add a note or milestone
|
|
42
|
+
|
|
43
|
+
Drafts:
|
|
44
|
+
draft list [projectId] List post drafts
|
|
45
|
+
draft create [projectId] Create a draft
|
|
46
|
+
draft update <draftId> Update a draft
|
|
47
|
+
draft delete <draftId> Delete a draft
|
|
48
|
+
|
|
49
|
+
Benchmark:
|
|
50
|
+
bench list [projectId] List benchmark accounts
|
|
51
|
+
bench add [projectId] Add a benchmark account
|
|
52
|
+
bench remove <accountId> Remove a benchmark account
|
|
53
|
+
|
|
54
|
+
Events:
|
|
55
|
+
events list [projectId] List events
|
|
56
|
+
events add [projectId] Add an event
|
|
57
|
+
events remove <eventId> Remove an event
|
|
58
|
+
|
|
59
|
+
Context:
|
|
60
|
+
context [projectId] Get full project context (JSON)
|
|
61
|
+
|
|
62
|
+
Steam:
|
|
63
|
+
steam search Search Steam games
|
|
64
|
+
steam details <appid> Get game details
|
|
65
|
+
|
|
66
|
+
Options:
|
|
67
|
+
--url=<url> DevLog URL (default: http://localhost:3000)
|
|
68
|
+
--format=json|table Output format (default: json)
|
|
69
|
+
--token=<token> API token (or DEVLOG_TOKEN env var)
|
|
70
|
+
help Show this help
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
devlog login
|
|
74
|
+
devlog project list --format=table
|
|
75
|
+
devlog project use 3f8a2b1c-...
|
|
76
|
+
devlog games list --format=table
|
|
77
|
+
devlog draft list --format=table
|
|
78
|
+
devlog context --section=games
|
|
79
|
+
devlog steam search --q=roguelike --sort=reviews_desc --limit=10
|
|
80
|
+
`;
|
|
81
|
+
// Commands that have subcommands (e.g. "project list", "games add")
|
|
82
|
+
const COMPOUND_COMMANDS = {
|
|
83
|
+
project: {
|
|
84
|
+
list: projectListCommand,
|
|
85
|
+
show: projectShowCommand,
|
|
86
|
+
create: projectCreateCommand,
|
|
87
|
+
update: projectUpdateCommand,
|
|
88
|
+
delete: projectDeleteCommand,
|
|
89
|
+
use: projectUseCommand,
|
|
90
|
+
},
|
|
91
|
+
games: {
|
|
92
|
+
list: gamesListCommand,
|
|
93
|
+
add: gamesAddCommand,
|
|
94
|
+
remove: gamesRemoveCommand,
|
|
95
|
+
},
|
|
96
|
+
history: {
|
|
97
|
+
list: historyListCommand,
|
|
98
|
+
add: historyAddCommand,
|
|
99
|
+
},
|
|
100
|
+
draft: {
|
|
101
|
+
list: draftListCommand,
|
|
102
|
+
create: draftCreateCommand,
|
|
103
|
+
update: draftUpdateCommand,
|
|
104
|
+
delete: draftDeleteCommand,
|
|
105
|
+
},
|
|
106
|
+
bench: {
|
|
107
|
+
list: benchListCommand,
|
|
108
|
+
add: benchAddCommand,
|
|
109
|
+
remove: benchRemoveCommand,
|
|
110
|
+
},
|
|
111
|
+
events: {
|
|
112
|
+
list: eventsListCommand,
|
|
113
|
+
add: eventsAddCommand,
|
|
114
|
+
remove: eventsRemoveCommand,
|
|
115
|
+
},
|
|
116
|
+
steam: {
|
|
117
|
+
search: steamSearchCommand,
|
|
118
|
+
details: steamDetailsCommand,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
// Simple top-level commands (no subcommand)
|
|
122
|
+
const SIMPLE_COMMANDS = {
|
|
123
|
+
context: contextCommand,
|
|
124
|
+
};
|
|
125
|
+
function handleError(error, config) {
|
|
126
|
+
if (error instanceof ApiError) {
|
|
127
|
+
console.error(formatError(error));
|
|
128
|
+
process.exit(exitCodeFromError(error));
|
|
129
|
+
}
|
|
130
|
+
if (error instanceof TypeError && error.cause) {
|
|
131
|
+
const cause = error.cause;
|
|
132
|
+
if (cause.code === "ECONNREFUSED") {
|
|
133
|
+
console.error(`Connection refused: ${config.apiUrl}\nIs the DevLog app running?`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
async function main() {
|
|
140
|
+
const { command, args, flags } = parseArgs(process.argv);
|
|
141
|
+
// Help
|
|
142
|
+
if (command === "help" || flags["help"] === "true" || !command) {
|
|
143
|
+
console.log(HELP);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Auth commands (no API client needed)
|
|
147
|
+
if (command === "login") {
|
|
148
|
+
await loginCommand(args, flags);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (command === "logout") {
|
|
152
|
+
logoutCommand();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (command === "status") {
|
|
156
|
+
statusCommand();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// All other commands need authentication
|
|
160
|
+
const config = resolveConfig(flags);
|
|
161
|
+
const client = new DevlogClient(config);
|
|
162
|
+
const format = getFormat(flags);
|
|
163
|
+
// Compound commands (project list, games add, etc.)
|
|
164
|
+
const subCommands = COMPOUND_COMMANDS[command];
|
|
165
|
+
if (subCommands) {
|
|
166
|
+
const subCommand = args[0] || "list"; // default to "list"
|
|
167
|
+
const handler = subCommands[subCommand];
|
|
168
|
+
if (!handler) {
|
|
169
|
+
console.error(`Unknown subcommand: ${command} ${subCommand}`);
|
|
170
|
+
console.error('Run "devlog help" for usage.');
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
await handler(client, args.slice(1), flags, format);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
handleError(error, config);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Simple commands
|
|
182
|
+
const simpleHandler = SIMPLE_COMMANDS[command];
|
|
183
|
+
if (simpleHandler) {
|
|
184
|
+
try {
|
|
185
|
+
await simpleHandler(client, args, flags, format);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
handleError(error, config);
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
console.error(`Unknown command: ${command}`);
|
|
193
|
+
console.error('Run "devlog help" for usage.');
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
main();
|
|
197
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type OutputFormat = "json" | "table";
|
|
2
|
+
/**
|
|
3
|
+
* Determine output format from flags.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getFormat(flags: Record<string, string>): OutputFormat;
|
|
6
|
+
/**
|
|
7
|
+
* Print data as formatted JSON.
|
|
8
|
+
*/
|
|
9
|
+
export declare function printJson(data: unknown): void;
|
|
10
|
+
/**
|
|
11
|
+
* Print an array of objects as an aligned table.
|
|
12
|
+
*/
|
|
13
|
+
export declare function printTable(rows: Record<string, unknown>[], columns?: string[]): void;
|
package/build/output.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determine output format from flags.
|
|
3
|
+
*/
|
|
4
|
+
export function getFormat(flags) {
|
|
5
|
+
if (flags["format"] === "table")
|
|
6
|
+
return "table";
|
|
7
|
+
return "json";
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Print data as formatted JSON.
|
|
11
|
+
*/
|
|
12
|
+
export function printJson(data) {
|
|
13
|
+
console.log(JSON.stringify(data, null, 2));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Print an array of objects as an aligned table.
|
|
17
|
+
*/
|
|
18
|
+
export function printTable(rows, columns) {
|
|
19
|
+
if (rows.length === 0) {
|
|
20
|
+
console.log("No results found.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const cols = columns || Object.keys(rows[0]);
|
|
24
|
+
const widths = cols.map((c) => Math.max(c.length, ...rows.map((r) => {
|
|
25
|
+
const val = r[c];
|
|
26
|
+
return String(val ?? "-").length;
|
|
27
|
+
})));
|
|
28
|
+
// Cap column widths at 50 chars for readability
|
|
29
|
+
const maxWidth = 50;
|
|
30
|
+
const cappedWidths = widths.map((w) => Math.min(w, maxWidth));
|
|
31
|
+
// Header
|
|
32
|
+
console.log(cols
|
|
33
|
+
.map((c, i) => c.toUpperCase().padEnd(cappedWidths[i]))
|
|
34
|
+
.join(" "));
|
|
35
|
+
console.log(cols
|
|
36
|
+
.map((_, i) => "-".repeat(cappedWidths[i]))
|
|
37
|
+
.join(" "));
|
|
38
|
+
// Rows
|
|
39
|
+
for (const row of rows) {
|
|
40
|
+
console.log(cols
|
|
41
|
+
.map((c, i) => {
|
|
42
|
+
const val = String(row[c] ?? "-");
|
|
43
|
+
return truncate(val, cappedWidths[i]).padEnd(cappedWidths[i]);
|
|
44
|
+
})
|
|
45
|
+
.join(" "));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function truncate(str, max) {
|
|
49
|
+
if (str.length <= max)
|
|
50
|
+
return str;
|
|
51
|
+
return str.slice(0, max - 1) + "\u2026";
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=output.js.map
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/** Envelope returned by all /api/cli/* endpoints */
|
|
2
|
+
export interface ApiResponse<T> {
|
|
3
|
+
data: T;
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Project {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string | null;
|
|
9
|
+
summary: string | null;
|
|
10
|
+
status: string;
|
|
11
|
+
phase: string | null;
|
|
12
|
+
created_at: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ProjectDetail extends Project {
|
|
15
|
+
owner_type: string;
|
|
16
|
+
owner_id: string;
|
|
17
|
+
target_audience: string | null;
|
|
18
|
+
phase_other: string | null;
|
|
19
|
+
interview_context: Record<string, unknown> | null;
|
|
20
|
+
updated_at: string;
|
|
21
|
+
project_similar_games: SimilarGame[];
|
|
22
|
+
project_repositories: ProjectRepository[];
|
|
23
|
+
}
|
|
24
|
+
export interface SimilarGame {
|
|
25
|
+
id?: string;
|
|
26
|
+
game_name: string;
|
|
27
|
+
steam_appid: number | null;
|
|
28
|
+
tags: string[] | null;
|
|
29
|
+
developer: string | null;
|
|
30
|
+
publisher: string | null;
|
|
31
|
+
total_reviews: number | null;
|
|
32
|
+
rating_percent: number | null;
|
|
33
|
+
website: string | null;
|
|
34
|
+
note: string | null;
|
|
35
|
+
category?: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface ProjectRepository {
|
|
38
|
+
repo_full_name: string;
|
|
39
|
+
default_branch: string;
|
|
40
|
+
}
|
|
41
|
+
export interface HistoryCard {
|
|
42
|
+
id: string;
|
|
43
|
+
project_id: string;
|
|
44
|
+
source_type: string;
|
|
45
|
+
title: string;
|
|
46
|
+
summary: string | null;
|
|
47
|
+
metadata_json: Record<string, unknown> | null;
|
|
48
|
+
media_urls: string[] | null;
|
|
49
|
+
occurred_at: string;
|
|
50
|
+
created_at: string;
|
|
51
|
+
updated_at: string;
|
|
52
|
+
}
|
|
53
|
+
export interface PostDraft {
|
|
54
|
+
id: string;
|
|
55
|
+
project_id: string;
|
|
56
|
+
platform: string;
|
|
57
|
+
hook: string | null;
|
|
58
|
+
body: string;
|
|
59
|
+
hashtags: string[] | null;
|
|
60
|
+
cta: string | null;
|
|
61
|
+
status: string;
|
|
62
|
+
rationale: string | null;
|
|
63
|
+
language: string | null;
|
|
64
|
+
post_type: string | null;
|
|
65
|
+
emotional_hook: string | null;
|
|
66
|
+
cta_type: string | null;
|
|
67
|
+
media_style: string | null;
|
|
68
|
+
media_urls: string[] | null;
|
|
69
|
+
created_at: string;
|
|
70
|
+
updated_at: string;
|
|
71
|
+
}
|
|
72
|
+
export interface BenchAccount {
|
|
73
|
+
id: string;
|
|
74
|
+
project_id: string;
|
|
75
|
+
platform: string;
|
|
76
|
+
handle: string;
|
|
77
|
+
source: string;
|
|
78
|
+
reason: string | null;
|
|
79
|
+
developer_name: string | null;
|
|
80
|
+
game_name: string | null;
|
|
81
|
+
steam_appid: number | null;
|
|
82
|
+
follower_count: number | null;
|
|
83
|
+
created_at: string;
|
|
84
|
+
}
|
|
85
|
+
export interface EventSchedule {
|
|
86
|
+
id: string;
|
|
87
|
+
project_id: string;
|
|
88
|
+
event_name: string;
|
|
89
|
+
event_date: string;
|
|
90
|
+
importance: string | null;
|
|
91
|
+
}
|
|
92
|
+
export interface ProjectContext {
|
|
93
|
+
project: Project;
|
|
94
|
+
games: SimilarGame[];
|
|
95
|
+
repository: ProjectRepository | null;
|
|
96
|
+
recentHistory: HistoryCard[];
|
|
97
|
+
recentDrafts: PostDraft[];
|
|
98
|
+
benchAccounts: (BenchAccount & {
|
|
99
|
+
post_count: number;
|
|
100
|
+
})[];
|
|
101
|
+
upcomingEvents: EventSchedule[];
|
|
102
|
+
}
|
|
103
|
+
export interface SteamSearchGame {
|
|
104
|
+
appid: number;
|
|
105
|
+
name: string;
|
|
106
|
+
priceFinal: number | null;
|
|
107
|
+
totalReviews: number | null;
|
|
108
|
+
ratingPercent: number | null;
|
|
109
|
+
releaseDate: string | null;
|
|
110
|
+
tags: string[];
|
|
111
|
+
}
|
|
112
|
+
export interface SteamSearchResult {
|
|
113
|
+
games: SteamSearchGame[];
|
|
114
|
+
total: number;
|
|
115
|
+
hasMore: boolean;
|
|
116
|
+
nextCursor: string | null;
|
|
117
|
+
}
|
|
118
|
+
import type { DevlogClient } from "./client.js";
|
|
119
|
+
import type { OutputFormat } from "./output.js";
|
|
120
|
+
export type CommandHandler = (client: DevlogClient, args: string[], flags: Record<string, string>, format: OutputFormat) => Promise<void>;
|
package/build/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@witchpot/devlog-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DevLog Social Copilot CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./build/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"devlog": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build/**/*.js",
|
|
12
|
+
"build/**/*.d.ts",
|
|
13
|
+
"!build/__tests__"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"devlog",
|
|
17
|
+
"gamedev",
|
|
18
|
+
"indie",
|
|
19
|
+
"marketing",
|
|
20
|
+
"cli"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/Witchpot/DevLogSystem.git",
|
|
25
|
+
"directory": "packages/cli"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"dev": "tsc --watch",
|
|
34
|
+
"clean": "rm -rf build",
|
|
35
|
+
"start": "node build/index.js",
|
|
36
|
+
"prepublishOnly": "npm run build",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:watch": "vitest"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20",
|
|
42
|
+
"typescript": "^5",
|
|
43
|
+
"vitest": "^4.1.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
}
|
|
48
|
+
}
|