@lwmxiaobei/xbcode 1.0.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/LICENSE +21 -0
- package/README.md +631 -0
- package/README.zh-CN.md +542 -0
- package/dist/agent.js +1450 -0
- package/dist/busy-status.js +29 -0
- package/dist/clipboard-image.js +97 -0
- package/dist/commands.js +109 -0
- package/dist/compact.js +262 -0
- package/dist/config.js +516 -0
- package/dist/error-log.js +80 -0
- package/dist/http.js +89 -0
- package/dist/idle-watchdog.js +88 -0
- package/dist/index.js +2031 -0
- package/dist/input-submit.js +41 -0
- package/dist/mcp/client.js +466 -0
- package/dist/mcp/manager.js +275 -0
- package/dist/mcp/runtime.js +420 -0
- package/dist/mcp/types.js +12 -0
- package/dist/message-bus.js +180 -0
- package/dist/oauth/openai.js +326 -0
- package/dist/prompt.js +156 -0
- package/dist/session-store.js +186 -0
- package/dist/skills/frontmatter.js +85 -0
- package/dist/skills/index.js +2 -0
- package/dist/skills/loader.js +88 -0
- package/dist/skills/render.js +35 -0
- package/dist/skills/types.js +1 -0
- package/dist/subagents.js +64 -0
- package/dist/supervisor.js +58 -0
- package/dist/task-manager.js +280 -0
- package/dist/team-types.js +1 -0
- package/dist/teammate-manager.js +266 -0
- package/dist/tools.js +1068 -0
- package/dist/trust-store.js +42 -0
- package/dist/types.js +1 -0
- package/dist/usage.js +226 -0
- package/dist/utils.js +21 -0
- package/package.json +67 -0
- package/scripts/postinstall.mjs +30 -0
- package/skills/code-review/SKILL.md +22 -0
- package/skills/pdf/SKILL.md +18 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const TRUST_DIR = path.join(os.homedir(), ".xbcode");
|
|
5
|
+
const TRUST_FILE = path.join(TRUST_DIR, "trusted.json");
|
|
6
|
+
function normalize(dir) {
|
|
7
|
+
try {
|
|
8
|
+
return fs.realpathSync(dir);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function readTrustedDirs() {
|
|
15
|
+
try {
|
|
16
|
+
const raw = fs.readFileSync(TRUST_FILE, "utf-8");
|
|
17
|
+
const parsed = JSON.parse(raw);
|
|
18
|
+
if (!Array.isArray(parsed))
|
|
19
|
+
return [];
|
|
20
|
+
return parsed.filter((x) => typeof x === "string");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function isTrusted(dir) {
|
|
27
|
+
return readTrustedDirs().includes(normalize(dir));
|
|
28
|
+
}
|
|
29
|
+
export function markTrusted(dir) {
|
|
30
|
+
const target = normalize(dir);
|
|
31
|
+
try {
|
|
32
|
+
fs.mkdirSync(TRUST_DIR, { recursive: true });
|
|
33
|
+
const dirs = readTrustedDirs();
|
|
34
|
+
if (dirs.includes(target))
|
|
35
|
+
return;
|
|
36
|
+
dirs.push(target);
|
|
37
|
+
fs.writeFileSync(TRUST_FILE, JSON.stringify(dirs, null, 2));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error("[trust-store] failed to persist trust:", err);
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { OPENAI_CODEX_ORIGINATOR, OPENAI_CODEX_USER_AGENT, OPENAI_CODEX_VERSION, createOpenAIOAuthFetch, getOpenAIOAuthDefaultHeaders, } from "./oauth/openai.js";
|
|
2
|
+
/**
|
|
3
|
+
* ChatGPT backend 用于查询 Codex 订阅用量的端点。
|
|
4
|
+
*
|
|
5
|
+
* 为什么写成常量而不是直接拼接:
|
|
6
|
+
* - 这个 URL 不属于公共 OpenAI API,而是 ChatGPT 内部的 codex 子域,
|
|
7
|
+
* 未来如果迁移到 `/backend-api/codex/...` 之类的路径,集中改一处即可。
|
|
8
|
+
* - 测试和调试时也方便直接 grep 出真实落点,避免重复字符串散落。
|
|
9
|
+
*/
|
|
10
|
+
export const OPENAI_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
11
|
+
/**
|
|
12
|
+
* `/usage` 命令查询失败时携带的结构化信息。
|
|
13
|
+
*
|
|
14
|
+
* 为什么单独建一个 Error 子类:
|
|
15
|
+
* - 上层既要友好地提示 "未登录 / token 失效",又要在调试场景下保留 HTTP 状态码,
|
|
16
|
+
* 普通 Error.message 拼字符串容易丢信息;
|
|
17
|
+
* - 子类可以让调用方用 `instanceof` 判断是否是已知错误,从而决定是否暴露 raw body。
|
|
18
|
+
*/
|
|
19
|
+
export class UsageRequestError extends Error {
|
|
20
|
+
status;
|
|
21
|
+
body;
|
|
22
|
+
constructor(message, options = {}) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "UsageRequestError";
|
|
25
|
+
this.status = options.status;
|
|
26
|
+
this.body = options.body;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 调用 ChatGPT backend `/wham/usage`,返回订阅与额度信息。
|
|
31
|
+
*
|
|
32
|
+
* 为什么这里要复用 oauth 模块的 fetch 与 headers:
|
|
33
|
+
* - `createOpenAIOAuthFetch()` 已经接管了 HTTP_PROXY/NO_PROXY 等环境变量,
|
|
34
|
+
* 保证用户在公司网络下也能复用同一套代理,无需另起一份逻辑;
|
|
35
|
+
* - `getOpenAIOAuthDefaultHeaders()` 包含 codex CLI 期望的 `originator`、`Version`、
|
|
36
|
+
* `chatgpt-account-id` 等头部,缺一不可——backend 会按这些头部来识别"是 codex 在请求"
|
|
37
|
+
* 并下发对应的限额视图。
|
|
38
|
+
*/
|
|
39
|
+
export async function fetchOpenAIUsage(credentials, options = {}) {
|
|
40
|
+
const accessToken = credentials.access_token?.trim();
|
|
41
|
+
if (!accessToken) {
|
|
42
|
+
throw new UsageRequestError("Missing OpenAI OAuth access token. Run /login first.");
|
|
43
|
+
}
|
|
44
|
+
const fetchImpl = createOpenAIOAuthFetch();
|
|
45
|
+
// 这里要把 oauth 默认头部合并进来:包含 chatgpt-account-id、originator、Version 等
|
|
46
|
+
// codex backend 需要的标识。Authorization 单独追加,避免被默认头部覆盖。
|
|
47
|
+
const headers = {
|
|
48
|
+
...getOpenAIOAuthDefaultHeaders(credentials),
|
|
49
|
+
Authorization: `Bearer ${accessToken}`,
|
|
50
|
+
Accept: "application/json",
|
|
51
|
+
};
|
|
52
|
+
const response = await fetchImpl(OPENAI_USAGE_URL, {
|
|
53
|
+
method: "GET",
|
|
54
|
+
headers,
|
|
55
|
+
signal: options.signal,
|
|
56
|
+
});
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
// 把 body 一起带回去,便于调试 401(token 过期)/403(账号未启用 codex)等情况。
|
|
59
|
+
const body = await safeReadText(response);
|
|
60
|
+
throw new UsageRequestError(`Usage request failed: HTTP ${response.status}`, { status: response.status, body });
|
|
61
|
+
}
|
|
62
|
+
const json = (await response.json());
|
|
63
|
+
return json;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 安全读取 Response 文本:失败时返回空字符串。
|
|
67
|
+
*
|
|
68
|
+
* 为什么需要这层包装:
|
|
69
|
+
* - 出错路径里如果再抛一个 "stream consumed twice" 之类的二次异常,
|
|
70
|
+
* 会盖住真正的 HTTP 状态码,让排障难度直线上升;
|
|
71
|
+
* - 即使 body 拿不到也不影响主错误的语义,所以这里吞掉异常是合理的。
|
|
72
|
+
*/
|
|
73
|
+
async function safeReadText(response) {
|
|
74
|
+
try {
|
|
75
|
+
return await response.text();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 把 `limit_window_seconds` 翻译成人类语义的窗口名。
|
|
83
|
+
*
|
|
84
|
+
* 为什么不直接用上游 primary/secondary 名称:
|
|
85
|
+
* - 不同 plan 下,"5h 短窗口" 可能落在 primary,也可能落在 secondary;
|
|
86
|
+
* - 用窗口长度判断更稳:≤ 6h 视为短窗口(5h 滚动限额),≥ 24h 视为长窗口(周滚动限额),
|
|
87
|
+
* 其它情况就老老实实叫 "window",避免误导。
|
|
88
|
+
*/
|
|
89
|
+
function inferWindowLabel(rawLabel, windowSeconds) {
|
|
90
|
+
if (typeof windowSeconds !== "number" || !Number.isFinite(windowSeconds)) {
|
|
91
|
+
return rawLabel;
|
|
92
|
+
}
|
|
93
|
+
const SIX_HOURS = 6 * 3600;
|
|
94
|
+
const ONE_DAY = 24 * 3600;
|
|
95
|
+
if (windowSeconds <= SIX_HOURS) {
|
|
96
|
+
return `${rawLabel} (5小时窗口)`;
|
|
97
|
+
}
|
|
98
|
+
if (windowSeconds >= ONE_DAY) {
|
|
99
|
+
const days = Math.round(windowSeconds / ONE_DAY);
|
|
100
|
+
return `${rawLabel} (${days}天窗口)`;
|
|
101
|
+
}
|
|
102
|
+
return rawLabel;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 把秒数格式成 "Xd Yh Zm" 的紧凑形式。
|
|
106
|
+
*
|
|
107
|
+
* 为什么不用 ISO duration 或 toLocale:
|
|
108
|
+
* - 终端环境下短字符串比 ISO 串可读性更高;
|
|
109
|
+
* - 不依赖 Intl.RelativeTimeFormat 也能跑在最低版本 Node 上。
|
|
110
|
+
*/
|
|
111
|
+
function formatDurationSeconds(totalSeconds) {
|
|
112
|
+
if (typeof totalSeconds !== "number" || !Number.isFinite(totalSeconds) || totalSeconds < 0) {
|
|
113
|
+
return "-";
|
|
114
|
+
}
|
|
115
|
+
const seconds = Math.round(totalSeconds);
|
|
116
|
+
const days = Math.floor(seconds / 86400);
|
|
117
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
118
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
119
|
+
const parts = [];
|
|
120
|
+
if (days > 0)
|
|
121
|
+
parts.push(`${days}d`);
|
|
122
|
+
if (hours > 0)
|
|
123
|
+
parts.push(`${hours}h`);
|
|
124
|
+
if (minutes > 0 && days === 0)
|
|
125
|
+
parts.push(`${minutes}m`);
|
|
126
|
+
if (parts.length === 0)
|
|
127
|
+
return "<1m";
|
|
128
|
+
return parts.join(" ");
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 把 unix 秒时间戳转成本地可读时间。
|
|
132
|
+
*
|
|
133
|
+
* 为什么用 ISO + 替换:
|
|
134
|
+
* - `toISOString()` 始终可用,不依赖 Intl 数据;
|
|
135
|
+
* - 把 `T` 换成空格、去掉毫秒和 `Z` 后,就是终端里最直观的 `YYYY-MM-DD HH:MM:SS`,
|
|
136
|
+
* 并且是 UTC,避免不同机器时区差异导致的歧义。
|
|
137
|
+
*/
|
|
138
|
+
function formatResetAt(resetAt) {
|
|
139
|
+
if (typeof resetAt !== "number" || !Number.isFinite(resetAt) || resetAt <= 0) {
|
|
140
|
+
return "-";
|
|
141
|
+
}
|
|
142
|
+
const date = new Date(resetAt * 1000);
|
|
143
|
+
if (Number.isNaN(date.getTime()))
|
|
144
|
+
return "-";
|
|
145
|
+
return `${date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC")}`;
|
|
146
|
+
}
|
|
147
|
+
function formatWindowLine(label, window) {
|
|
148
|
+
if (!window) {
|
|
149
|
+
return `${label} (n/a)`;
|
|
150
|
+
}
|
|
151
|
+
const friendlyLabel = inferWindowLabel(label, window.limit_window_seconds);
|
|
152
|
+
const used = typeof window.used_percent === "number" ? `${window.used_percent}% used` : "used -";
|
|
153
|
+
const resetIn = `resets in ${formatDurationSeconds(window.reset_after_seconds)}`;
|
|
154
|
+
const resetAt = `at ${formatResetAt(window.reset_at)}`;
|
|
155
|
+
return `${friendlyLabel} ${used} | ${resetIn} | ${resetAt}`;
|
|
156
|
+
}
|
|
157
|
+
function formatCreditsLine(credits) {
|
|
158
|
+
if (!credits) {
|
|
159
|
+
return "credits (n/a)";
|
|
160
|
+
}
|
|
161
|
+
if (credits.unlimited) {
|
|
162
|
+
return "credits unlimited";
|
|
163
|
+
}
|
|
164
|
+
if (!credits.has_credits) {
|
|
165
|
+
return "credits none";
|
|
166
|
+
}
|
|
167
|
+
const balance = typeof credits.balance === "number" ? credits.balance.toFixed(2) : "-";
|
|
168
|
+
const local = typeof credits.approx_local_messages === "number" ? `${credits.approx_local_messages} local msgs` : "";
|
|
169
|
+
const cloud = typeof credits.approx_cloud_messages === "number" ? `${credits.approx_cloud_messages} cloud msgs` : "";
|
|
170
|
+
const extras = [local, cloud].filter(Boolean).join(" / ");
|
|
171
|
+
const overage = credits.overage_limit_reached ? " | overage reached" : "";
|
|
172
|
+
return `credits balance ${balance}${extras ? ` | ${extras}` : ""}${overage}`;
|
|
173
|
+
}
|
|
174
|
+
function formatSpendLine(spend) {
|
|
175
|
+
if (!spend) {
|
|
176
|
+
return "spend (n/a)";
|
|
177
|
+
}
|
|
178
|
+
if (spend.reached) {
|
|
179
|
+
const cap = typeof spend.individual_limit === "number" ? ` (cap ${spend.individual_limit})` : "";
|
|
180
|
+
return `spend reached${cap}`;
|
|
181
|
+
}
|
|
182
|
+
return "spend ok";
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 把 `/wham/usage` 的响应格式成给终端展示的多行文本。
|
|
186
|
+
*
|
|
187
|
+
* 为什么把这一步抽成纯函数:
|
|
188
|
+
* - 写单测时不需要起 HTTP,只要喂结构化对象即可;
|
|
189
|
+
* - 终端展示不只 `/usage` 一处会用:未来想在 `/status` 里加一行用量摘要,
|
|
190
|
+
* 也能直接复用同一个 formatter,避免出现两套对不上的展示口径。
|
|
191
|
+
*/
|
|
192
|
+
export function formatUsageReport(usage) {
|
|
193
|
+
const rate = usage.rate_limit;
|
|
194
|
+
const lines = [];
|
|
195
|
+
lines.push(`account ${usage.email ?? usage.account_id ?? usage.user_id ?? "-"}`);
|
|
196
|
+
lines.push(`plan ${usage.plan_type ?? "-"}`);
|
|
197
|
+
lines.push("");
|
|
198
|
+
if (!rate) {
|
|
199
|
+
lines.push("rate-limit (no data)");
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
if (rate.limit_reached) {
|
|
203
|
+
lines.push("rate-limit ⚠ limit reached");
|
|
204
|
+
}
|
|
205
|
+
lines.push(formatWindowLine("primary ", rate.primary_window));
|
|
206
|
+
lines.push(formatWindowLine("secondary", rate.secondary_window));
|
|
207
|
+
}
|
|
208
|
+
lines.push(formatCreditsLine(usage.credits));
|
|
209
|
+
lines.push(formatSpendLine(usage.spend_control));
|
|
210
|
+
if (usage.rate_limit_reached_type) {
|
|
211
|
+
lines.push(`note reached type: ${usage.rate_limit_reached_type}`);
|
|
212
|
+
}
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 暴露给 oauth.ts 调用方的标识,便于未来调试时确认 codex 与 xbcode 共用同一组 UA。
|
|
217
|
+
*
|
|
218
|
+
* 为什么 re-export:
|
|
219
|
+
* - usage.ts 的调用方可能想在错误信息里附上当前 UA / Version;
|
|
220
|
+
* - 直接从 oauth.ts 拿会让外层多一层 import,影响代码的纵向阅读体验。
|
|
221
|
+
*/
|
|
222
|
+
export const USAGE_DEBUG_INFO = {
|
|
223
|
+
userAgent: OPENAI_CODEX_USER_AGENT,
|
|
224
|
+
originator: OPENAI_CODEX_ORIGINATOR,
|
|
225
|
+
version: OPENAI_CODEX_VERSION,
|
|
226
|
+
};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import util from "node:util";
|
|
2
|
+
export function isPlainRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
export function ellipsize(text, maxLength = 160) {
|
|
6
|
+
if (text.length <= maxLength) {
|
|
7
|
+
return text;
|
|
8
|
+
}
|
|
9
|
+
return `${text.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
10
|
+
}
|
|
11
|
+
function isDebugEnabled() {
|
|
12
|
+
const value = process.env.DEBUG?.trim().toLowerCase();
|
|
13
|
+
return value === "1" || value === "true" || value === "yes" || value === "on";
|
|
14
|
+
}
|
|
15
|
+
export function debugLog(...args) {
|
|
16
|
+
if (!isDebugEnabled()) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const message = args.length > 0 ? util.format(...args) : "";
|
|
20
|
+
process.stderr.write(`[debug] ${message}\n`);
|
|
21
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lwmxiaobei/xbcode",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "xbcode CLI agent",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/lwmxiaobei/agent-mini.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/lwmxiaobei/agent-mini#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/lwmxiaobei/agent-mini/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"cli",
|
|
21
|
+
"agent",
|
|
22
|
+
"ai",
|
|
23
|
+
"mcp"
|
|
24
|
+
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"xbcode": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"scripts",
|
|
34
|
+
"skills",
|
|
35
|
+
"README.md",
|
|
36
|
+
"README.zh-CN.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"dev": "tsx src/index.tsx",
|
|
41
|
+
"build": "tsc -p tsconfig.json",
|
|
42
|
+
"test": "node --import tsx --test test/**/*.test.ts",
|
|
43
|
+
"start": "node dist/index.js",
|
|
44
|
+
"prepublishOnly": "npm run build && npm test",
|
|
45
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
49
|
+
"@types/react": "^19.2.14",
|
|
50
|
+
"dotenv": "^16.6.1",
|
|
51
|
+
"ink": "^6.8.0",
|
|
52
|
+
"ink-text-input": "^6.0.0",
|
|
53
|
+
"marked": "^15.0.12",
|
|
54
|
+
"marked-terminal": "^7.3.0",
|
|
55
|
+
"openai": "^5.19.1",
|
|
56
|
+
"proper-lockfile": "^4.1.2",
|
|
57
|
+
"react": "^19.2.4",
|
|
58
|
+
"undici": "^7.25.0",
|
|
59
|
+
"zod": "^4.3.6"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^24.5.2",
|
|
63
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
64
|
+
"tsx": "^4.20.5",
|
|
65
|
+
"typescript": "^5.9.2"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
|
|
7
|
+
const CONFIG_DIR = path.join(os.homedir(), ".xbcode");
|
|
8
|
+
const SETTINGS_PATH = path.join(CONFIG_DIR, "settings.json");
|
|
9
|
+
|
|
10
|
+
if (fs.existsSync(SETTINGS_PATH)) {
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const defaultSettings = {
|
|
15
|
+
providers: {
|
|
16
|
+
openai: {
|
|
17
|
+
models: ["gpt-5.4", "gpt-5.3-codex"],
|
|
18
|
+
apiKey: "",
|
|
19
|
+
baseURL: "https://api.openai.com/v1",
|
|
20
|
+
apiMode: "responses",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultProvider: "openai",
|
|
24
|
+
showThinking: false,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
28
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(defaultSettings, null, 2) + "\n", "utf8");
|
|
29
|
+
|
|
30
|
+
console.log(`[xbcode] Created default config at ${SETTINGS_PATH}`);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
description: Review code for quality, bugs, and best practices
|
|
4
|
+
tags: development
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Code Review Guidelines
|
|
8
|
+
|
|
9
|
+
When reviewing code, follow these steps:
|
|
10
|
+
|
|
11
|
+
1. **Read the code thoroughly** — understand the intent before critiquing.
|
|
12
|
+
2. **Check for bugs** — null references, off-by-one errors, race conditions, resource leaks.
|
|
13
|
+
3. **Evaluate naming** — are variables, functions, and classes named clearly?
|
|
14
|
+
4. **Assess structure** — is the code modular? Are responsibilities well-separated?
|
|
15
|
+
5. **Security** — look for injection vulnerabilities, unsafe inputs, hardcoded secrets.
|
|
16
|
+
6. **Performance** — unnecessary allocations, N+1 queries, blocking calls in async code.
|
|
17
|
+
7. **Tests** — are edge cases covered? Are tests readable and maintainable?
|
|
18
|
+
|
|
19
|
+
Output format:
|
|
20
|
+
- Start with a one-line summary (good / needs work / has issues).
|
|
21
|
+
- List findings grouped by severity: 🔴 Critical, 🟡 Warning, 🟢 Suggestion.
|
|
22
|
+
- For each finding, include file, line, and a concrete fix.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pdf
|
|
3
|
+
description: Process and extract content from PDF files
|
|
4
|
+
tags: document-processing
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# PDF Processing
|
|
8
|
+
|
|
9
|
+
To work with PDF files:
|
|
10
|
+
|
|
11
|
+
1. Use `bash` to check if `pdftotext` is available: `which pdftotext`
|
|
12
|
+
2. If not installed, suggest: `brew install poppler` (macOS) or `apt install poppler-utils` (Linux)
|
|
13
|
+
3. Extract text: `pdftotext input.pdf -` (outputs to stdout)
|
|
14
|
+
4. For structured extraction: `pdftotext -layout input.pdf -`
|
|
15
|
+
5. For page-specific extraction: `pdftotext -f 1 -l 5 input.pdf -` (pages 1-5)
|
|
16
|
+
|
|
17
|
+
For PDF metadata: `pdfinfo input.pdf`
|
|
18
|
+
For PDF to images: `pdftoppm -png input.pdf output_prefix`
|