@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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Browser-based Authentication Flow
3
+ *
4
+ * Opens the browser to the DevLog app, user signs in via Clerk,
5
+ * the app generates a DevLog API token and redirects back to a
6
+ * local callback server.
7
+ */
8
+ export interface LoginOptions {
9
+ apiUrl: string;
10
+ port?: number;
11
+ }
12
+ /**
13
+ * Perform the full browser-based login flow.
14
+ *
15
+ * 1. Start local HTTP server on callback port
16
+ * 2. Open browser to DevLog auth page
17
+ * 3. Wait for callback with token
18
+ * 4. Store token locally
19
+ */
20
+ export declare function login(options: LoginOptions): Promise<void>;
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Browser-based Authentication Flow
3
+ *
4
+ * Opens the browser to the DevLog app, user signs in via Clerk,
5
+ * the app generates a DevLog API token and redirects back to a
6
+ * local callback server.
7
+ */
8
+ import { createServer, } from "node:http";
9
+ import { randomBytes } from "node:crypto";
10
+ import { execFile } from "node:child_process";
11
+ import { URL } from "node:url";
12
+ import { storeToken, getTokenHash } from "./token-storage.js";
13
+ const DEFAULT_PORT = 9800;
14
+ const MAX_PORT_RETRIES = 10;
15
+ const AUTH_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
16
+ function generateState() {
17
+ return randomBytes(16).toString("base64url");
18
+ }
19
+ function escapeHtml(s) {
20
+ return s
21
+ .replace(/&/g, "&amp;")
22
+ .replace(/</g, "&lt;")
23
+ .replace(/>/g, "&gt;")
24
+ .replace(/"/g, "&quot;");
25
+ }
26
+ function htmlPage(title, color, message) {
27
+ return `<!DOCTYPE html><html><head><title>${escapeHtml(title)}</title>
28
+ <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>
30
+ <body><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p><p>You can close this window.</p>
31
+ <script>setTimeout(()=>{try{window.close()}catch(e){}},2000)</script></body></html>`;
32
+ }
33
+ function startCallbackServer(port, expectedState, retriesLeft = MAX_PORT_RETRIES) {
34
+ return new Promise((resolve, reject) => {
35
+ let resolveCallback;
36
+ let rejectCallback;
37
+ const callbackPromise = new Promise((res, rej) => {
38
+ resolveCallback = res;
39
+ rejectCallback = rej;
40
+ });
41
+ const server = createServer((req, res) => {
42
+ if (!req.url?.startsWith("/callback")) {
43
+ res.writeHead(404);
44
+ res.end("Not Found");
45
+ return;
46
+ }
47
+ try {
48
+ const url = new URL(req.url, `http://localhost:${port}`);
49
+ const token = url.searchParams.get("token");
50
+ const state = url.searchParams.get("state");
51
+ const error = url.searchParams.get("error");
52
+ const errorDesc = url.searchParams.get("error_description");
53
+ if (error) {
54
+ res.writeHead(400, {
55
+ "Content-Type": "text/html; charset=utf-8",
56
+ });
57
+ res.end(htmlPage("Authentication Failed", "#ef4444", errorDesc || error));
58
+ rejectCallback(new Error(errorDesc || error));
59
+ return;
60
+ }
61
+ if (state !== expectedState) {
62
+ res.writeHead(400, {
63
+ "Content-Type": "text/html; charset=utf-8",
64
+ });
65
+ res.end(htmlPage("Security Error", "#ef4444", "State mismatch - possible CSRF attack."));
66
+ rejectCallback(new Error("State mismatch"));
67
+ return;
68
+ }
69
+ if (!token) {
70
+ res.writeHead(400, {
71
+ "Content-Type": "text/html; charset=utf-8",
72
+ });
73
+ res.end(htmlPage("Authentication Error", "#ef4444", "Missing token in callback."));
74
+ rejectCallback(new Error("Missing token in callback"));
75
+ return;
76
+ }
77
+ res.writeHead(200, {
78
+ "Content-Type": "text/html; charset=utf-8",
79
+ });
80
+ res.end(htmlPage("Authentication Successful!", "#22c55e", "You can close this window and return to your terminal."));
81
+ server.close();
82
+ resolveCallback({ token, state });
83
+ }
84
+ catch (err) {
85
+ res.writeHead(500);
86
+ res.end("Internal Server Error");
87
+ rejectCallback(err instanceof Error ? err : new Error(String(err)));
88
+ }
89
+ });
90
+ server.listen(port, "127.0.0.1", () => {
91
+ resolve({ server, callbackPromise, actualPort: port });
92
+ });
93
+ server.on("error", (err) => {
94
+ if (err.code === "EADDRINUSE") {
95
+ if (retriesLeft <= 0) {
96
+ reject(new Error(`All ports ${DEFAULT_PORT}-${port} are in use.`));
97
+ return;
98
+ }
99
+ console.error(`Port ${port} in use, trying ${port + 1}...`);
100
+ server.close();
101
+ startCallbackServer(port + 1, expectedState, retriesLeft - 1)
102
+ .then(resolve)
103
+ .catch(reject);
104
+ }
105
+ else {
106
+ reject(err);
107
+ }
108
+ });
109
+ });
110
+ }
111
+ function openBrowser(url) {
112
+ const platform = process.platform;
113
+ let cmd;
114
+ let args;
115
+ if (platform === "darwin") {
116
+ cmd = "open";
117
+ args = [url];
118
+ }
119
+ else if (platform === "win32") {
120
+ cmd = "cmd";
121
+ args = ["/c", "start", "", url];
122
+ }
123
+ else {
124
+ cmd = "xdg-open";
125
+ args = [url];
126
+ }
127
+ const child = execFile(cmd, args);
128
+ child.unref();
129
+ child.stdout?.destroy();
130
+ child.stderr?.destroy();
131
+ }
132
+ /**
133
+ * Perform the full browser-based login flow.
134
+ *
135
+ * 1. Start local HTTP server on callback port
136
+ * 2. Open browser to DevLog auth page
137
+ * 3. Wait for callback with token
138
+ * 4. Store token locally
139
+ */
140
+ export async function login(options) {
141
+ const apiUrl = options.apiUrl.replace(/\/$/, "");
142
+ const port = options.port || DEFAULT_PORT;
143
+ const state = generateState();
144
+ console.error("Starting local authentication server...");
145
+ const { server, callbackPromise, actualPort } = await startCallbackServer(port, state);
146
+ const callbackUrl = `http://localhost:${actualPort}/callback`;
147
+ try {
148
+ const authUrl = new URL("/auth/cli-callback", apiUrl);
149
+ authUrl.searchParams.set("callback", callbackUrl);
150
+ authUrl.searchParams.set("state", state);
151
+ console.error("Opening browser for authentication...");
152
+ console.error(`URL: ${authUrl.toString()}`);
153
+ openBrowser(authUrl.toString());
154
+ console.error("Waiting for authentication (timeout: 5 minutes)...");
155
+ let timeoutId;
156
+ const timeoutPromise = new Promise((_, reject) => {
157
+ timeoutId = setTimeout(() => reject(new Error("Authentication timed out after 5 minutes")), AUTH_TIMEOUT_MS);
158
+ });
159
+ try {
160
+ const { token } = await Promise.race([
161
+ callbackPromise,
162
+ timeoutPromise,
163
+ ]);
164
+ clearTimeout(timeoutId);
165
+ storeToken(token, apiUrl);
166
+ console.error(`\nLogin successful! (token: ${getTokenHash(token)})`);
167
+ console.error(`API URL: ${apiUrl}`);
168
+ }
169
+ catch (err) {
170
+ clearTimeout(timeoutId);
171
+ throw err;
172
+ }
173
+ }
174
+ finally {
175
+ server.close();
176
+ }
177
+ }
178
+ //# sourceMappingURL=auth-flow.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Secure Token Storage
3
+ *
4
+ * Stores DevLog API tokens encrypted on disk using AES-256-GCM.
5
+ * Derives encryption key from machine-specific values.
6
+ */
7
+ export interface StoredToken {
8
+ token: string;
9
+ apiUrl: string;
10
+ createdAt: number;
11
+ }
12
+ /**
13
+ * Store a DevLog API token securely (AES-256-GCM encrypted).
14
+ */
15
+ export declare function storeToken(token: string, apiUrl: string): void;
16
+ /**
17
+ * Read a stored DevLog API token.
18
+ * Returns null if no token or invalid.
19
+ */
20
+ export declare function readStoredToken(): StoredToken | null;
21
+ /**
22
+ * Clear stored token (overwrite with zeros, then delete).
23
+ */
24
+ export declare function clearStoredToken(): void;
25
+ /**
26
+ * Get token status info for display.
27
+ */
28
+ export declare function getTokenInfo(): {
29
+ exists: boolean;
30
+ createdAt: Date | null;
31
+ apiUrl: string | null;
32
+ tokenPrefix: string | null;
33
+ };
34
+ /**
35
+ * Get a short hash of a token for display.
36
+ */
37
+ export declare function getTokenHash(token: string): string;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Secure Token Storage
3
+ *
4
+ * Stores DevLog API tokens encrypted on disk using AES-256-GCM.
5
+ * Derives encryption key from machine-specific values.
6
+ */
7
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync, createHash, } from "node:crypto";
8
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, statSync, } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { homedir } from "node:os";
11
+ const CONFIG_DIR = join(homedir(), ".devlog-cli");
12
+ const TOKEN_FILE = join(CONFIG_DIR, "token.enc");
13
+ function getEncryptionKey() {
14
+ const machineId = [
15
+ homedir(),
16
+ process.arch,
17
+ process.platform,
18
+ "devlog-cli-v1",
19
+ ].join(":");
20
+ return scryptSync(machineId, "devlog-cli-salt-v1", 32);
21
+ }
22
+ function ensureConfigDir() {
23
+ mkdirSync(CONFIG_DIR, { mode: 0o700, recursive: true });
24
+ }
25
+ /**
26
+ * Store a DevLog API token securely (AES-256-GCM encrypted).
27
+ */
28
+ export function storeToken(token, apiUrl) {
29
+ ensureConfigDir();
30
+ const data = {
31
+ token,
32
+ apiUrl,
33
+ createdAt: Math.floor(Date.now() / 1000),
34
+ };
35
+ const key = getEncryptionKey();
36
+ const iv = randomBytes(16);
37
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
38
+ const encrypted = Buffer.concat([
39
+ cipher.update(JSON.stringify(data), "utf8"),
40
+ cipher.final(),
41
+ ]);
42
+ const authTag = cipher.getAuthTag();
43
+ // Format: iv (16 bytes) + authTag (16 bytes) + encrypted data
44
+ const fileContent = Buffer.concat([iv, authTag, encrypted]);
45
+ writeFileSync(TOKEN_FILE, fileContent, { mode: 0o600 });
46
+ }
47
+ /**
48
+ * Read a stored DevLog API token.
49
+ * Returns null if no token or invalid.
50
+ */
51
+ export function readStoredToken() {
52
+ if (!existsSync(TOKEN_FILE)) {
53
+ return null;
54
+ }
55
+ try {
56
+ const fileContent = readFileSync(TOKEN_FILE);
57
+ if (fileContent.length < 33) {
58
+ clearStoredToken();
59
+ return null;
60
+ }
61
+ const iv = fileContent.subarray(0, 16);
62
+ const authTag = fileContent.subarray(16, 32);
63
+ const encrypted = fileContent.subarray(32);
64
+ const key = getEncryptionKey();
65
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
66
+ decipher.setAuthTag(authTag);
67
+ const decrypted = Buffer.concat([
68
+ decipher.update(encrypted),
69
+ decipher.final(),
70
+ ]);
71
+ const data = JSON.parse(decrypted.toString("utf8"));
72
+ return data;
73
+ }
74
+ catch {
75
+ clearStoredToken();
76
+ return null;
77
+ }
78
+ }
79
+ /**
80
+ * Clear stored token (overwrite with zeros, then delete).
81
+ */
82
+ export function clearStoredToken() {
83
+ if (existsSync(TOKEN_FILE)) {
84
+ try {
85
+ const size = statSync(TOKEN_FILE).size;
86
+ writeFileSync(TOKEN_FILE, Buffer.alloc(size));
87
+ unlinkSync(TOKEN_FILE);
88
+ }
89
+ catch {
90
+ // Ignore cleanup errors
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ * Get token status info for display.
96
+ */
97
+ export function getTokenInfo() {
98
+ const stored = readStoredToken();
99
+ if (!stored) {
100
+ return { exists: false, createdAt: null, apiUrl: null, tokenPrefix: null };
101
+ }
102
+ return {
103
+ exists: true,
104
+ createdAt: new Date(stored.createdAt * 1000),
105
+ apiUrl: stored.apiUrl,
106
+ tokenPrefix: stored.token.slice(0, 12) + "...",
107
+ };
108
+ }
109
+ /**
110
+ * Get a short hash of a token for display.
111
+ */
112
+ export function getTokenHash(token) {
113
+ return createHash("sha256").update(token).digest("hex").substring(0, 8);
114
+ }
115
+ //# sourceMappingURL=token-storage.js.map
@@ -0,0 +1,30 @@
1
+ import type { Config } from "./config.js";
2
+ export declare class DevlogClient {
3
+ private baseUrl;
4
+ private token;
5
+ private apiPrefix;
6
+ constructor(config: Config);
7
+ private getHeaders;
8
+ /**
9
+ * GET request to a CLI API endpoint (prefixed with /api/cli).
10
+ */
11
+ get<T>(path: string, params?: Record<string, unknown>): Promise<T>;
12
+ /**
13
+ * POST request to a CLI API endpoint.
14
+ */
15
+ post<T>(path: string, body: unknown): Promise<T>;
16
+ /**
17
+ * PATCH request to a CLI API endpoint.
18
+ */
19
+ patch<T>(path: string, body: unknown): Promise<T>;
20
+ /**
21
+ * DELETE request to a CLI API endpoint.
22
+ */
23
+ delete<T>(path: string, body?: unknown): Promise<T>;
24
+ /**
25
+ * GET request to an absolute API path (without /api/cli prefix).
26
+ * Used for endpoints like /api/steam/search that aren't under the CLI namespace.
27
+ */
28
+ getAbsolute<T>(path: string, params?: Record<string, unknown>): Promise<T>;
29
+ private handleResponse;
30
+ }
@@ -0,0 +1,130 @@
1
+ import { ApiError } from "./errors.js";
2
+ export class DevlogClient {
3
+ baseUrl;
4
+ token;
5
+ apiPrefix = "/api/cli";
6
+ constructor(config) {
7
+ this.baseUrl = config.apiUrl;
8
+ if (!config.token) {
9
+ console.error('Not authenticated. Run "devlog login" first.');
10
+ process.exit(2);
11
+ }
12
+ this.token = config.token;
13
+ }
14
+ getHeaders() {
15
+ return {
16
+ "Content-Type": "application/json",
17
+ Accept: "application/json",
18
+ Authorization: `Bearer ${this.token}`,
19
+ };
20
+ }
21
+ /**
22
+ * GET request to a CLI API endpoint (prefixed with /api/cli).
23
+ */
24
+ async get(path, params) {
25
+ const url = new URL(`${this.apiPrefix}${path}`, this.baseUrl);
26
+ if (params) {
27
+ for (const [key, value] of Object.entries(params)) {
28
+ if (value !== undefined && value !== null && value !== "") {
29
+ if (Array.isArray(value)) {
30
+ url.searchParams.set(key, value.join(","));
31
+ }
32
+ else {
33
+ url.searchParams.set(key, String(value));
34
+ }
35
+ }
36
+ }
37
+ }
38
+ const response = await fetch(url.toString(), {
39
+ method: "GET",
40
+ headers: this.getHeaders(),
41
+ });
42
+ return this.handleResponse(response);
43
+ }
44
+ /**
45
+ * POST request to a CLI API endpoint.
46
+ */
47
+ async post(path, body) {
48
+ const url = new URL(`${this.apiPrefix}${path}`, this.baseUrl);
49
+ const response = await fetch(url.toString(), {
50
+ method: "POST",
51
+ headers: this.getHeaders(),
52
+ body: JSON.stringify(body),
53
+ });
54
+ return this.handleResponse(response);
55
+ }
56
+ /**
57
+ * PATCH request to a CLI API endpoint.
58
+ */
59
+ async patch(path, body) {
60
+ const url = new URL(`${this.apiPrefix}${path}`, this.baseUrl);
61
+ const response = await fetch(url.toString(), {
62
+ method: "PATCH",
63
+ headers: this.getHeaders(),
64
+ body: JSON.stringify(body),
65
+ });
66
+ return this.handleResponse(response);
67
+ }
68
+ /**
69
+ * DELETE request to a CLI API endpoint.
70
+ */
71
+ async delete(path, body) {
72
+ const url = new URL(`${this.apiPrefix}${path}`, this.baseUrl);
73
+ const response = await fetch(url.toString(), {
74
+ method: "DELETE",
75
+ headers: this.getHeaders(),
76
+ body: body ? JSON.stringify(body) : undefined,
77
+ });
78
+ return this.handleResponse(response);
79
+ }
80
+ /**
81
+ * GET request to an absolute API path (without /api/cli prefix).
82
+ * Used for endpoints like /api/steam/search that aren't under the CLI namespace.
83
+ */
84
+ async getAbsolute(path, params) {
85
+ const url = new URL(path, this.baseUrl);
86
+ if (params) {
87
+ for (const [key, value] of Object.entries(params)) {
88
+ if (value !== undefined && value !== null && value !== "") {
89
+ if (Array.isArray(value)) {
90
+ url.searchParams.set(key, value.join(","));
91
+ }
92
+ else {
93
+ url.searchParams.set(key, String(value));
94
+ }
95
+ }
96
+ }
97
+ }
98
+ const response = await fetch(url.toString(), {
99
+ method: "GET",
100
+ headers: this.getHeaders(),
101
+ });
102
+ return this.handleResponse(response);
103
+ }
104
+ async handleResponse(response) {
105
+ const contentType = response.headers.get("content-type") || "";
106
+ if (!contentType.includes("application/json")) {
107
+ throw new ApiError(response.status, {
108
+ message: `Expected JSON but got ${contentType || "unknown content type"} (HTTP ${response.status})`,
109
+ hint: "Check that the DevLog app is running and the URL is correct",
110
+ });
111
+ }
112
+ if (!response.ok) {
113
+ let detail;
114
+ try {
115
+ const body = await response.json();
116
+ detail = {
117
+ message: body.error || `HTTP ${response.status}: ${response.statusText}`,
118
+ };
119
+ }
120
+ catch {
121
+ detail = {
122
+ message: `HTTP ${response.status}: ${response.statusText}`,
123
+ };
124
+ }
125
+ throw new ApiError(response.status, detail);
126
+ }
127
+ return response.json();
128
+ }
129
+ }
130
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * devlog login - Authenticate via browser
3
+ */
4
+ export declare function loginCommand(_args: string[], flags: Record<string, string>): Promise<void>;
5
+ /**
6
+ * devlog logout - Clear stored token
7
+ */
8
+ export declare function logoutCommand(): void;
9
+ /**
10
+ * devlog status - Show current authentication status
11
+ */
12
+ export declare function statusCommand(): void;
@@ -0,0 +1,36 @@
1
+ import { login } from "../auth/auth-flow.js";
2
+ import { clearStoredToken, getTokenInfo } from "../auth/token-storage.js";
3
+ const DEFAULT_API_URL = "https://devlog.witchpot.com";
4
+ /**
5
+ * devlog login - Authenticate via browser
6
+ */
7
+ export async function loginCommand(_args, flags) {
8
+ const url = flags["url"] || process.env.DEVLOG_URL || DEFAULT_API_URL;
9
+ const port = flags["port"] ? parseInt(flags["port"], 10) : undefined;
10
+ await login({ apiUrl: url, port });
11
+ // Force exit -- callback server and child processes may hold the event loop
12
+ process.exit(0);
13
+ }
14
+ /**
15
+ * devlog logout - Clear stored token
16
+ */
17
+ export function logoutCommand() {
18
+ clearStoredToken();
19
+ console.error("Logged out successfully. Token removed.");
20
+ }
21
+ /**
22
+ * devlog status - Show current authentication status
23
+ */
24
+ export function statusCommand() {
25
+ const info = getTokenInfo();
26
+ if (!info.exists) {
27
+ console.log("Not logged in.");
28
+ console.log('Run "devlog login" to authenticate.');
29
+ return;
30
+ }
31
+ console.log("Logged in");
32
+ console.log(` URL: ${info.apiUrl}`);
33
+ console.log(` Token: ${info.tokenPrefix}`);
34
+ console.log(` Created: ${info.createdAt.toISOString()}`);
35
+ }
36
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,13 @@
1
+ import type { CommandHandler } from "../types.js";
2
+ /**
3
+ * devlog bench list [projectId] - List benchmark accounts
4
+ */
5
+ export declare const benchListCommand: CommandHandler;
6
+ /**
7
+ * devlog bench add [projectId] --platform=<platform> --handle=<handle>
8
+ */
9
+ export declare const benchAddCommand: CommandHandler;
10
+ /**
11
+ * devlog bench remove <accountId> --project=<projectId>
12
+ */
13
+ export declare const benchRemoveCommand: CommandHandler;
@@ -0,0 +1,60 @@
1
+ import { printJson, printTable } from "../output.js";
2
+ import { getActiveProjectId } from "../config.js";
3
+ import { assertSafeId } from "../errors.js";
4
+ /**
5
+ * devlog bench list [projectId] - List benchmark accounts
6
+ */
7
+ export const benchListCommand = async (client, args, _flags, format) => {
8
+ const projectId = args[0] || getActiveProjectId();
9
+ assertSafeId(projectId, "project ID");
10
+ const res = await client.get(`/projects/${projectId}/bench`);
11
+ if (format === "table") {
12
+ printTable(res.data.map((a) => ({
13
+ id: a.id.slice(0, 8),
14
+ platform: a.platform,
15
+ handle: `@${a.handle}`,
16
+ source: a.source,
17
+ developer: a.developer_name ?? "-",
18
+ game: a.game_name ?? "-",
19
+ followers: a.follower_count ?? "-",
20
+ })));
21
+ }
22
+ else {
23
+ printJson(res.data);
24
+ }
25
+ };
26
+ /**
27
+ * devlog bench add [projectId] --platform=<platform> --handle=<handle>
28
+ */
29
+ export const benchAddCommand = async (client, args, flags, _format) => {
30
+ const projectId = args[0] || getActiveProjectId();
31
+ const platform = flags["platform"];
32
+ const handle = flags["handle"];
33
+ if (!platform) {
34
+ console.error("Usage: devlog bench add [projectId] --platform=<platform> --handle=<handle>");
35
+ process.exit(1);
36
+ }
37
+ if (!handle) {
38
+ console.error("--handle is required");
39
+ process.exit(1);
40
+ }
41
+ assertSafeId(projectId, "project ID");
42
+ const res = await client.post(`/projects/${projectId}/bench`, { platform, handle });
43
+ printJson(res.data);
44
+ };
45
+ /**
46
+ * devlog bench remove <accountId> --project=<projectId>
47
+ */
48
+ export const benchRemoveCommand = async (client, args, flags, _format) => {
49
+ const accountId = args[0];
50
+ if (!accountId) {
51
+ console.error("Usage: devlog bench remove <accountId>");
52
+ process.exit(1);
53
+ }
54
+ const projectId = flags["project"] || getActiveProjectId();
55
+ assertSafeId(accountId, "account ID");
56
+ assertSafeId(projectId, "project ID");
57
+ await client.delete(`/projects/${projectId}/bench`, { accountId });
58
+ console.error("Benchmark account removed.");
59
+ };
60
+ //# sourceMappingURL=bench.js.map
@@ -0,0 +1,6 @@
1
+ import type { CommandHandler } from "../types.js";
2
+ /**
3
+ * devlog context [projectId] [--section=<name>]
4
+ * Get full project context as a single JSON blob.
5
+ */
6
+ export declare const contextCommand: CommandHandler;
@@ -0,0 +1,33 @@
1
+ import { printJson } from "../output.js";
2
+ import { getActiveProjectId } from "../config.js";
3
+ import { assertSafeId } from "../errors.js";
4
+ const VALID_SECTIONS = [
5
+ "project",
6
+ "games",
7
+ "repository",
8
+ "recentHistory",
9
+ "recentDrafts",
10
+ "benchAccounts",
11
+ "upcomingEvents",
12
+ ];
13
+ /**
14
+ * devlog context [projectId] [--section=<name>]
15
+ * Get full project context as a single JSON blob.
16
+ */
17
+ export const contextCommand = async (client, args, flags, _format) => {
18
+ const projectId = args[0] || getActiveProjectId();
19
+ assertSafeId(projectId, "project ID");
20
+ const section = flags["section"];
21
+ const res = await client.get(`/projects/${projectId}/context`);
22
+ if (section) {
23
+ if (!VALID_SECTIONS.includes(section)) {
24
+ console.error(`Invalid section: ${section}\nValid sections: ${VALID_SECTIONS.join(", ")}`);
25
+ process.exit(1);
26
+ }
27
+ printJson(res.data[section]);
28
+ }
29
+ else {
30
+ printJson(res.data);
31
+ }
32
+ };
33
+ //# sourceMappingURL=context.js.map