foco-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/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # FOCO CLI
2
+
3
+ FOCO 备考平台的命令行工具,供团队成员通过 Cursor、Codex 等 AI 编码工具调用,实现题库管理和工单管理。
4
+
5
+ ## 安装
6
+
7
+ ### 方式一:从 npm 安装(推荐)
8
+
9
+ ```bash
10
+ npm install -g foco-cli
11
+ ```
12
+
13
+ 安装后即可使用:
14
+
15
+ ```bash
16
+ foco login
17
+ ```
18
+
19
+ ### 方式二:npx 临时使用(无需安装)
20
+
21
+ ```bash
22
+ npx foco-cli
23
+ ```
24
+
25
+ ### 方式三:从本地构建安装(开发者)
26
+
27
+ ```bash
28
+ # 构建并打包
29
+ cd cli && npm run dist
30
+
31
+ # 全局安装
32
+ npm install -g ../app/cli-dist/foco-cli-*.tgz
33
+
34
+ # 或使用 npm link(开发模式)
35
+ cd cli && npm link
36
+ ```
37
+
38
+ ### 方式四:从自建服务器安装(无 npm 环境时备用)
39
+
40
+ ```bash
41
+ npm install -g https://你的-foco-服务器地址/api/cli/download
42
+ ```
43
+
44
+ > 如果服务器地址是 `https://foco.example.com`,则命令为:
45
+ > `npm install -g https://foco.example.com/api/cli/download`
46
+
47
+ ## 更新
48
+
49
+ ```bash
50
+ # npm 安装的版本
51
+ npm update -g foco-cli
52
+
53
+ # 或直接重新安装
54
+ npm install -g foco-cli@latest
55
+ ```
56
+
57
+ ## 配置
58
+
59
+ ### 快速开始
60
+
61
+ ```bash
62
+ # 登录到 FOCO 服务器(默认 http://localhost:3001)
63
+ foco login
64
+
65
+ # 指定服务器地址
66
+ foco login https://your-foco-server.com
67
+
68
+ # 非交互式登录(适合 CI/AI 工具)
69
+ foco login https://example.com admin mypassword
70
+
71
+ # 也可以通过环境变量
72
+ FOCO_API_URL=https://example.com FOCO_USERNAME=admin FOCO_PASSWORD=xxx foco login
73
+ ```
74
+
75
+ 凭证存储在 `~/.foco/config.json`。
76
+
77
+ ## 命令
78
+
79
+ ### 认证管理
80
+
81
+ ```bash
82
+ foco login [apiUrl] [username] [password]
83
+ foco auth login [apiUrl] [username] [password]
84
+ foco auth status # 查看登录状态
85
+ foco auth logout # 清除凭证
86
+ ```
87
+
88
+ ### 题库管理
89
+
90
+ ```bash
91
+ foco banks list # 查看题库列表
92
+ foco banks list --status published # 按状态筛选
93
+ foco banks list --keyword 数学 # 按名称搜索
94
+ foco banks show <bankId> # 查看题库详情
95
+ ```
96
+
97
+ ### 题目管理
98
+
99
+ ```bash
100
+ foco questions list # 查看题目列表
101
+ foco questions list --bank-id <id> # 按题库筛选
102
+ foco questions list --type single_choice # 按题型筛选
103
+ foco questions list --difficulty 3 # 按难度筛选
104
+ foco questions show <questionId> # 查看题目详情
105
+ ```
106
+
107
+ ### 工单管理
108
+
109
+ ```bash
110
+ # 列表
111
+ foco issues list
112
+ foco issues list --status in_progress
113
+ foco issues list --assignee-id me
114
+ foco issues list --priority urgent
115
+ foco issues list --search "bug"
116
+
117
+ # 详情
118
+ foco issues show <id>
119
+
120
+ # 创建
121
+ foco issues create --title "修复登录问题" --priority high --labels "bug,frontend"
122
+
123
+ # 更新
124
+ foco issues update <id> --status done
125
+ foco issues update <id> --assignee-id "" # 取消指派
126
+ foco issues update <id> --priority urgent --title "新标题"
127
+ ```
128
+
129
+ ## 输出格式
130
+
131
+ 所有命令输出结构化 JSON,方便 AI 工具解析:
132
+
133
+ ```json
134
+ {
135
+ "status": "ok",
136
+ "data": { ... }
137
+ }
138
+ ```
139
+
140
+ 错误时输出到 stderr,返回非零退出码。
141
+
142
+ ## 开发
143
+
144
+ ```bash
145
+ cd cli
146
+ npm run dev # tsx 热重载开发
147
+ npm run build # TypeScript 编译
148
+ npm run dist # 构建 + 打包为 .tgz(输出到 app/cli-dist/)
149
+ npm link # 链接到全局 foco 命令
150
+ ```
151
+
152
+ ## 发布流程
153
+
154
+ ### 发布到 npm(推荐)
155
+
156
+ ```bash
157
+ # 确保 npm 已登录
158
+ npm login
159
+
160
+ # 发布新版本(自动运行 npm version 和 npm publish)
161
+ npm version patch # 0.1.0 → 0.1.1(小修小改)
162
+ npm version minor # 0.1.0 → 0.2.0(新增功能)
163
+ npm version major # 0.1.0 → 1.0.0(不兼容变更)
164
+
165
+ # 或手动发布
166
+ npm run build
167
+ npm publish
168
+ ```
169
+
170
+ ### 自建服务器部署(备用)
171
+
172
+ ```bash
173
+ # 1. 构建并打包
174
+ cd cli && npm run dist
175
+
176
+ # 2. 将 .tgz 部署到生产服务器的 app/cli-dist/ 目录
177
+ scp ../app/cli-dist/foco-cli-*.tgz user@server:/path/to/app/cli-dist/
178
+
179
+ # 3. 团队成员即可通过以下命令安装/更新
180
+ npm install -g https://你的-foco-服务器地址/api/cli/download
181
+ ```
@@ -0,0 +1,9 @@
1
+ export declare function handleBanksList(options: {
2
+ status?: string;
3
+ examId?: string;
4
+ keyword?: string;
5
+ page?: string;
6
+ pageSize?: string;
7
+ }): Promise<void>;
8
+ export declare function handleBankShow(bankId: string): Promise<void>;
9
+ //# sourceMappingURL=banks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banks.d.ts","sourceRoot":"","sources":["../../src/commands/banks.ts"],"names":[],"mappings":"AAGA,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlE"}
@@ -0,0 +1,18 @@
1
+ import { apiCall, outputJSON } from "../lib/api.js";
2
+ export async function handleBanksList(options) {
3
+ const { data } = await apiCall("/api/admin/v1/banks", {
4
+ params: {
5
+ status: options.status,
6
+ examId: options.examId,
7
+ keyword: options.keyword,
8
+ page: options.page,
9
+ pageSize: options.pageSize,
10
+ },
11
+ });
12
+ outputJSON({ status: "ok", data });
13
+ }
14
+ export async function handleBankShow(bankId) {
15
+ const { data } = await apiCall(`/api/admin/v1/banks/${bankId}`);
16
+ outputJSON({ status: "ok", data });
17
+ }
18
+ //# sourceMappingURL=banks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banks.js","sourceRoot":"","sources":["../../src/commands/banks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGpD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAMrC;IACC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAsB,qBAAqB,EAAE;QACzE,MAAM,EAAE;YACN,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B;KACF,CAAC,CAAC;IAEH,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc;IACjD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAO,uBAAuB,MAAM,EAAE,CAAC,CAAC;IACtE,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,35 @@
1
+ export declare function handleIssuesList(options: {
2
+ status?: string;
3
+ priority?: string;
4
+ assigneeId?: string;
5
+ projectId?: string;
6
+ labels?: string;
7
+ source?: string;
8
+ search?: string;
9
+ page?: string;
10
+ pageSize?: string;
11
+ }): Promise<void>;
12
+ export declare function handleIssueShow(id: string): Promise<void>;
13
+ export declare function handleIssueCreate(options: {
14
+ title: string;
15
+ description?: string;
16
+ priority?: string;
17
+ labels?: string;
18
+ projectId?: string;
19
+ assigneeId?: string;
20
+ parentId?: string;
21
+ source?: string;
22
+ dueDate?: string;
23
+ }): Promise<void>;
24
+ export declare function handleIssueUpdate(id: string, options: {
25
+ status?: string;
26
+ priority?: string;
27
+ assigneeId?: string;
28
+ projectId?: string;
29
+ labels?: string;
30
+ title?: string;
31
+ description?: string;
32
+ dueDate?: string;
33
+ sortOrder?: string;
34
+ }): Promise<void>;
35
+ //# sourceMappingURL=issues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../src/commands/issues.ts"],"names":[],"mappings":"AAGA,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBhB;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG/D;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBhB;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,IAAI,CAAC,CAmBf"}
@@ -0,0 +1,74 @@
1
+ import { apiCall, outputJSON } from "../lib/api.js";
2
+ export async function handleIssuesList(options) {
3
+ const { data } = await apiCall("/api/admin/v1/issues", {
4
+ params: {
5
+ status: options.status,
6
+ priority: options.priority,
7
+ assigneeId: options.assigneeId,
8
+ projectId: options.projectId,
9
+ labels: options.labels,
10
+ source: options.source,
11
+ search: options.search,
12
+ page: options.page,
13
+ pageSize: options.pageSize,
14
+ },
15
+ });
16
+ outputJSON({ status: "ok", data });
17
+ }
18
+ export async function handleIssueShow(id) {
19
+ const { data } = await apiCall(`/api/admin/v1/issues/${id}`);
20
+ outputJSON({ status: "ok", data });
21
+ }
22
+ export async function handleIssueCreate(options) {
23
+ const body = {
24
+ title: options.title,
25
+ };
26
+ if (options.description)
27
+ body.description = options.description;
28
+ if (options.priority)
29
+ body.priority = options.priority;
30
+ if (options.labels)
31
+ body.labels = options.labels.split(",").map((s) => s.trim()).filter(Boolean);
32
+ if (options.projectId)
33
+ body.projectId = options.projectId;
34
+ if (options.assigneeId)
35
+ body.assigneeId = options.assigneeId;
36
+ if (options.parentId)
37
+ body.parentId = options.parentId;
38
+ if (options.source)
39
+ body.source = options.source;
40
+ if (options.dueDate)
41
+ body.dueDate = options.dueDate;
42
+ const { data } = await apiCall("/api/admin/v1/issues", {
43
+ method: "POST",
44
+ body,
45
+ });
46
+ outputJSON({ status: "ok", data });
47
+ }
48
+ export async function handleIssueUpdate(id, options) {
49
+ const body = {};
50
+ if (options.status)
51
+ body.status = options.status;
52
+ if (options.priority)
53
+ body.priority = options.priority;
54
+ if (options.assigneeId !== undefined)
55
+ body.assigneeId = options.assigneeId || null;
56
+ if (options.projectId !== undefined)
57
+ body.projectId = options.projectId || null;
58
+ if (options.labels)
59
+ body.labels = options.labels.split(",").map((s) => s.trim()).filter(Boolean);
60
+ if (options.title)
61
+ body.title = options.title;
62
+ if (options.description)
63
+ body.description = options.description;
64
+ if (options.dueDate !== undefined)
65
+ body.dueDate = options.dueDate || null;
66
+ if (options.sortOrder)
67
+ body.sortOrder = Number(options.sortOrder);
68
+ const { data } = await apiCall(`/api/admin/v1/issues/${id}`, {
69
+ method: "PATCH",
70
+ body,
71
+ });
72
+ outputJSON({ status: "ok", data });
73
+ }
74
+ //# sourceMappingURL=issues.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issues.js","sourceRoot":"","sources":["../../src/commands/issues.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function handleLogin(apiUrl: string | undefined, username: string | undefined, password: string | undefined): Promise<void>;
2
+ export declare function handleStatus(): Promise<void>;
3
+ export declare function handleLogout(): Promise<void>;
4
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAUA,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAElD;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAQlD"}
@@ -0,0 +1,39 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { loginAndSave } from "../lib/auth.js";
4
+ import { outputJSON, printError, getConfigInfo } from "../lib/api.js";
5
+ function prompt(question) {
6
+ const rl = createInterface({ input, output });
7
+ return rl.question(question).finally(() => rl.close());
8
+ }
9
+ export async function handleLogin(apiUrl, username, password) {
10
+ // Read from args, env, or prompt
11
+ const url = apiUrl || process.env.FOCO_API_URL || await prompt("API 地址 (默认 http://localhost:3001): ") || "http://localhost:3001";
12
+ const user = username || process.env.FOCO_USERNAME || await prompt("用户名: ");
13
+ const pass = password || process.env.FOCO_PASSWORD || await prompt("密码: ");
14
+ if (!user || !pass) {
15
+ printError("用户名和密码不能为空");
16
+ process.exit(1);
17
+ }
18
+ const result = await loginAndSave(url, user, pass);
19
+ if (result.ok) {
20
+ outputJSON({ status: "ok", message: result.message, apiUrl: url, username: user });
21
+ }
22
+ else {
23
+ printError(result.message);
24
+ process.exit(1);
25
+ }
26
+ }
27
+ export async function handleStatus() {
28
+ outputJSON({ status: "ok", data: getConfigInfo() });
29
+ }
30
+ export async function handleLogout() {
31
+ // Just clear the cookie from config
32
+ const { loadConfig, saveConfig } = await import("../lib/config.js");
33
+ const config = loadConfig();
34
+ config.cookie = undefined;
35
+ config.username = undefined;
36
+ saveConfig(config);
37
+ outputJSON({ status: "ok", message: "已登出" });
38
+ }
39
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEtE,SAAS,MAAM,CAAC,QAAgB;IAC9B,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9C,OAAO,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAA0B,EAC1B,QAA4B,EAC5B,QAA4B;IAE5B,iCAAiC;IACjC,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,MAAM,MAAM,CAAC,qCAAqC,CAAC,IAAI,uBAAuB,CAAC;IACjI,MAAM,IAAI,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;IAE3E,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,UAAU,CAAC,YAAY,CAAC,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACnD,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACrF,CAAC;SAAM,CAAC;QACN,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,oCAAoC;IACpC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;IAC5B,UAAU,CAAC,MAAM,CAAC,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,10 @@
1
+ export declare function handleQuestionsList(options: {
2
+ bankId?: string;
3
+ questionType?: string;
4
+ difficulty?: string;
5
+ keyword?: string;
6
+ page?: string;
7
+ pageSize?: string;
8
+ }): Promise<void>;
9
+ export declare function handleQuestionShow(questionId: string): Promise<void>;
10
+ //# sourceMappingURL=questions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questions.d.ts","sourceRoot":"","sources":["../../src/commands/questions.ts"],"names":[],"mappings":"AAGA,wBAAsB,mBAAmB,CAAC,OAAO,EAAE;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1E"}
@@ -0,0 +1,19 @@
1
+ import { apiCall, outputJSON } from "../lib/api.js";
2
+ export async function handleQuestionsList(options) {
3
+ const { data } = await apiCall("/api/admin/v1/questions", {
4
+ params: {
5
+ bankId: options.bankId,
6
+ questionType: options.questionType,
7
+ difficulty: options.difficulty,
8
+ keyword: options.keyword,
9
+ page: options.page,
10
+ pageSize: options.pageSize,
11
+ },
12
+ });
13
+ outputJSON({ status: "ok", data });
14
+ }
15
+ export async function handleQuestionShow(questionId) {
16
+ const { data } = await apiCall(`/api/admin/v1/questions/${questionId}`);
17
+ outputJSON({ status: "ok", data });
18
+ }
19
+ //# sourceMappingURL=questions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questions.js","sourceRoot":"","sources":["../../src/commands/questions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGpD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAOzC;IACC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAA0B,yBAAyB,EAAE;QACjF,MAAM,EAAE;YACN,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B;KACF,CAAC,CAAC;IAEH,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,UAAkB;IACzD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAW,2BAA2B,UAAU,EAAE,CAAC,CAAC;IAClF,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { handleLogin, handleStatus, handleLogout } from "./commands/login.js";
4
+ import { handleBanksList, handleBankShow } from "./commands/banks.js";
5
+ import { handleQuestionsList, handleQuestionShow } from "./commands/questions.js";
6
+ import { handleIssuesList, handleIssueShow, handleIssueCreate, handleIssueUpdate } from "./commands/issues.js";
7
+ const pkg = { version: "0.1.0", description: "FOCO CLI - 题库管理与工单管理命令行工具" };
8
+ const program = new Command();
9
+ program
10
+ .name("foco")
11
+ .version(pkg.version)
12
+ .description(pkg.description);
13
+ // ── Auth ──────────────────────────────────────────────────────
14
+ const auth = program.command("auth").description("认证管理");
15
+ auth
16
+ .command("login")
17
+ .description("登录到 FOCO 服务器")
18
+ .argument("[apiUrl]", "API 服务器地址")
19
+ .argument("[username]", "用户名")
20
+ .argument("[password]", "密码")
21
+ .action(async (apiUrl, username, password) => {
22
+ await handleLogin(apiUrl, username, password);
23
+ });
24
+ auth
25
+ .command("status")
26
+ .description("查看当前登录状态")
27
+ .action(async () => {
28
+ await handleStatus();
29
+ });
30
+ auth
31
+ .command("logout")
32
+ .description("登出并清除凭证")
33
+ .action(async () => {
34
+ await handleLogout();
35
+ });
36
+ // Alias: foco login = foco auth login
37
+ program
38
+ .command("login")
39
+ .description("登录到 FOCO 服务器")
40
+ .argument("[apiUrl]", "API 服务器地址")
41
+ .argument("[username]", "用户名")
42
+ .argument("[password]", "密码")
43
+ .action(async (apiUrl, username, password) => {
44
+ await handleLogin(apiUrl, username, password);
45
+ });
46
+ // ── Banks (题库) ──────────────────────────────────────────────
47
+ const banks = program.command("banks").description("题库管理");
48
+ banks
49
+ .command("list")
50
+ .description("查看题库列表")
51
+ .option("--status <status>", "按状态筛选 (draft|published|archived)")
52
+ .option("--exam-id <id>", "按考试筛选")
53
+ .option("--keyword <keyword>", "按名称关键词搜索")
54
+ .option("--page <page>", "页码", "1")
55
+ .option("--page-size <size>", "每页数量", "20")
56
+ .action(async (options) => {
57
+ await handleBanksList(options);
58
+ });
59
+ banks
60
+ .command("show <bankId>")
61
+ .description("查看题库详情")
62
+ .action(async (bankId) => {
63
+ await handleBankShow(bankId);
64
+ });
65
+ // ── Questions (题目) ──────────────────────────────────────────
66
+ const questions = program.command("questions").description("题目管理");
67
+ questions
68
+ .command("list")
69
+ .description("查看题目列表")
70
+ .option("--bank-id <id>", "按题库筛选")
71
+ .option("--type <type>", "按题型筛选")
72
+ .option("--difficulty <level>", "按难度筛选 (1-5)")
73
+ .option("--keyword <keyword>", "按题干关键词搜索")
74
+ .option("--page <page>", "页码", "1")
75
+ .option("--page-size <size>", "每页数量", "20")
76
+ .action(async (options) => {
77
+ await handleQuestionsList(options);
78
+ });
79
+ questions
80
+ .command("show <questionId>")
81
+ .description("查看题目详情")
82
+ .action(async (questionId) => {
83
+ await handleQuestionShow(questionId);
84
+ });
85
+ // ── Issues (工单) ─────────────────────────────────────────────
86
+ const issues = program.command("issues").description("工单管理");
87
+ issues
88
+ .command("list")
89
+ .description("查看工单列表")
90
+ .option("--status <status>", "按状态筛选 (backlog|todo|in_progress|done|cancelled)")
91
+ .option("--priority <priority>", "按优先级筛选 (none|urgent|high|medium|low)")
92
+ .option("--assignee-id <id>", "按指派人筛选 (使用 me 表示当前用户)")
93
+ .option("--project-id <id>", "按项目筛选")
94
+ .option("--labels <labels>", "按标签筛选(逗号分隔)")
95
+ .option("--source <source>", "按来源筛选")
96
+ .option("--search <keyword>", "搜索标题和描述")
97
+ .option("--page <page>", "页码", "1")
98
+ .option("--page-size <size>", "每页数量", "20")
99
+ .action(async (options) => {
100
+ await handleIssuesList(options);
101
+ });
102
+ issues
103
+ .command("show <id>")
104
+ .description("查看工单详情")
105
+ .action(async (id) => {
106
+ await handleIssueShow(id);
107
+ });
108
+ issues
109
+ .command("create")
110
+ .description("创建工单")
111
+ .requiredOption("--title <title>", "工单标题")
112
+ .option("--description <desc>", "工单描述")
113
+ .option("--priority <priority>", "优先级 (none|urgent|high|medium|low)")
114
+ .option("--labels <labels>", "标签(逗号分隔)")
115
+ .option("--project-id <id>", "所属项目 ID")
116
+ .option("--assignee-id <id>", "指派人 ID")
117
+ .option("--parent-id <id>", "父工单 ID")
118
+ .option("--due-date <date>", "截止日期 (ISO 8601)")
119
+ .action(async (options) => {
120
+ await handleIssueCreate(options);
121
+ });
122
+ issues
123
+ .command("update <id>")
124
+ .description("更新工单")
125
+ .option("--status <status>", "状态 (backlog|todo|in_progress|done|cancelled)")
126
+ .option("--priority <priority>", "优先级 (none|urgent|high|medium|low)")
127
+ .option("--assignee-id <id>", "指派人 ID(空字符串取消指派)")
128
+ .option("--project-id <id>", "所属项目 ID(空字符串取消关联)")
129
+ .option("--labels <labels>", "标签(逗号分隔)")
130
+ .option("--title <title>", "标题")
131
+ .option("--description <desc>", "描述")
132
+ .option("--due-date <date>", "截止日期(空字符串清除)")
133
+ .option("--sort-order <n>", "排序序号")
134
+ .action(async (id, options) => {
135
+ await handleIssueUpdate(id, options);
136
+ });
137
+ // ── Parse ─────────────────────────────────────────────────────
138
+ program.parse(process.argv);
139
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { ApiResponse } from "../types.js";
2
+ /**
3
+ * Call the FOCO admin API.
4
+ * Automatically attaches the stored session cookie.
5
+ * Returns parsed JSON response or exits on error.
6
+ */
7
+ export declare function apiCall<T>(path: string, options?: {
8
+ method?: string;
9
+ body?: unknown;
10
+ params?: Record<string, string | undefined>;
11
+ }): Promise<{
12
+ data: T;
13
+ raw: ApiResponse<T>;
14
+ }>;
15
+ /**
16
+ * Print error message to stderr.
17
+ */
18
+ export declare function printError(message: string): void;
19
+ /**
20
+ * Output JSON result to stdout (for AI tools to parse).
21
+ */
22
+ export declare function outputJSON(data: unknown): void;
23
+ /**
24
+ * Get config diagnostics.
25
+ */
26
+ export declare function getConfigInfo(): Record<string, string>;
27
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CACxC,GACL,OAAO,CAAC;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC,CAuD3C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEhD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE9C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQtD"}
@@ -0,0 +1,78 @@
1
+ import { loadConfig, getConfigPath } from "./config.js";
2
+ /**
3
+ * Call the FOCO admin API.
4
+ * Automatically attaches the stored session cookie.
5
+ * Returns parsed JSON response or exits on error.
6
+ */
7
+ export async function apiCall(path, options = {}) {
8
+ const config = loadConfig();
9
+ if (!config.cookie) {
10
+ printError("未登录。请先运行 foco login");
11
+ process.exit(1);
12
+ }
13
+ const baseUrl = config.apiUrl.replace(/\/+$/, "");
14
+ const url = new URL(`${baseUrl}${path.startsWith("/") ? path : `/${path}`}`);
15
+ // Append query params (skip undefined values)
16
+ if (options.params) {
17
+ for (const [key, value] of Object.entries(options.params)) {
18
+ if (value !== undefined && value !== "") {
19
+ url.searchParams.set(key, value);
20
+ }
21
+ }
22
+ }
23
+ const headers = {
24
+ Cookie: config.cookie,
25
+ Accept: "application/json",
26
+ };
27
+ if (options.body !== undefined) {
28
+ headers["Content-Type"] = "application/json";
29
+ }
30
+ const res = await fetch(url.toString(), {
31
+ method: options.method ?? "GET",
32
+ headers,
33
+ body: options.body ? JSON.stringify(options.body) : undefined,
34
+ });
35
+ // If we get a redirect (e.g., to login page), auth is expired
36
+ if (res.status >= 300 && res.status < 400) {
37
+ printError("会话已过期,请重新运行 foco login");
38
+ process.exit(1);
39
+ }
40
+ const text = await res.text();
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(text);
44
+ }
45
+ catch {
46
+ parsed = { code: -1, message: text.slice(0, 500), data: null };
47
+ }
48
+ if (parsed.code !== 0) {
49
+ printError(`API 错误: ${parsed.message}`);
50
+ process.exit(1);
51
+ }
52
+ return { data: parsed.data, raw: parsed };
53
+ }
54
+ /**
55
+ * Print error message to stderr.
56
+ */
57
+ export function printError(message) {
58
+ process.stderr.write(`Error: ${message}\n`);
59
+ }
60
+ /**
61
+ * Output JSON result to stdout (for AI tools to parse).
62
+ */
63
+ export function outputJSON(data) {
64
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
65
+ }
66
+ /**
67
+ * Get config diagnostics.
68
+ */
69
+ export function getConfigInfo() {
70
+ const config = loadConfig();
71
+ return {
72
+ apiUrl: config.apiUrl,
73
+ username: config.username ?? "(未设置)",
74
+ configPath: getConfigPath(),
75
+ hasCookie: config.cookie ? "是" : "否",
76
+ };
77
+ }
78
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGxD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAY,EACZ,UAII,EAAE;IAEN,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAE5B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,UAAU,CAAC,qBAAqB,CAAC,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IAE7E,8CAA8C;IAC9C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;gBACxC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAA2B;QACtC,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,kBAAkB;KAC3B,CAAC;IAEF,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;IAC/C,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;QACtC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAC/B,OAAO;QACP,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC9D,CAAC,CAAC;IAEH,8DAA8D;IAC9D,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QAC1C,UAAU,CAAC,wBAAwB,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,MAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACjE,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,UAAU,CAAC,WAAW,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAS,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AACjD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAC7D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,OAAO;QACpC,UAAU,EAAE,aAAa,EAAE;QAC3B,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ interface LoginResult {
2
+ success: boolean;
3
+ cookie?: string;
4
+ error?: string;
5
+ }
6
+ /**
7
+ * Authenticate via NextAuth's credentials callback.
8
+ * POSTs username/password, extracts the session cookie from Set-Cookie.
9
+ */
10
+ export declare function login(apiUrl: string, username: string, password: string): Promise<LoginResult>;
11
+ /**
12
+ * Verify the current configuration works by hitting the admin API.
13
+ */
14
+ export declare function verifyAuth(apiUrl: string, cookie: string): Promise<boolean>;
15
+ /**
16
+ * Login and save credentials to config.
17
+ */
18
+ export declare function loginAndSave(apiUrl: string, username: string, password: string): Promise<{
19
+ ok: boolean;
20
+ message: string;
21
+ }>;
22
+ export {};
23
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAEA,UAAU,WAAW;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,KAAK,CACzB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CAwCtB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CASlB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAa3C"}
@@ -0,0 +1,70 @@
1
+ import { loadConfig, saveConfig } from "./config.js";
2
+ /**
3
+ * Authenticate via NextAuth's credentials callback.
4
+ * POSTs username/password, extracts the session cookie from Set-Cookie.
5
+ */
6
+ export async function login(apiUrl, username, password) {
7
+ const loginUrl = `${apiUrl.replace(/\/+$/, "")}/api/auth/callback/credentials`;
8
+ try {
9
+ const res = await fetch(loginUrl, {
10
+ method: "POST",
11
+ headers: {
12
+ "Content-Type": "application/x-www-form-urlencoded",
13
+ },
14
+ body: new URLSearchParams({ username, password }),
15
+ redirect: "manual", // don't follow redirect — we want the Set-Cookie
16
+ });
17
+ // The session cookie is in Set-Cookie header(s)
18
+ const setCookie = res.headers.getSetCookie?.() ?? [];
19
+ // getSetCookie() returns an array; fallback to get() for older Node
20
+ const cookieHeaders = setCookie.length > 0
21
+ ? setCookie
22
+ : [res.headers.get("set-cookie") ?? ""].filter(Boolean);
23
+ if (cookieHeaders.length === 0) {
24
+ return { success: false, error: "认证失败:用户名或密码错误" };
25
+ }
26
+ // Extract the session token cookie(s)
27
+ const relevantCookies = cookieHeaders
28
+ .map((c) => c.split(";")[0]) // name=value part only
29
+ .filter((c) => c.startsWith("next-auth.") || c.startsWith("__Secure-next-auth."))
30
+ .join("; ");
31
+ if (!relevantCookies) {
32
+ return { success: false, error: "认证失败:未获取到会话令牌" };
33
+ }
34
+ return { success: true, cookie: relevantCookies };
35
+ }
36
+ catch (err) {
37
+ const message = err instanceof Error ? err.message : String(err);
38
+ return { success: false, error: `连接失败: ${message}` };
39
+ }
40
+ }
41
+ /**
42
+ * Verify the current configuration works by hitting the admin API.
43
+ */
44
+ export async function verifyAuth(apiUrl, cookie) {
45
+ try {
46
+ const res = await fetch(`${apiUrl.replace(/\/+$/, "")}/api/admin/v1/admins`, {
47
+ headers: { Cookie: cookie },
48
+ });
49
+ return res.status === 200;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
55
+ /**
56
+ * Login and save credentials to config.
57
+ */
58
+ export async function loginAndSave(apiUrl, username, password) {
59
+ const result = await login(apiUrl, username, password);
60
+ if (!result.success) {
61
+ return { ok: false, message: result.error ?? "认证失败" };
62
+ }
63
+ const config = loadConfig();
64
+ config.apiUrl = apiUrl;
65
+ config.cookie = result.cookie;
66
+ config.username = username;
67
+ saveConfig(config);
68
+ return { ok: true, message: "登录成功" };
69
+ }
70
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { FocoConfig } from "../types.js";
2
+ export declare function loadConfig(): FocoConfig;
3
+ export declare function saveConfig(config: FocoConfig): void;
4
+ export declare function getConfigPath(): string;
5
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAW9C,wBAAgB,UAAU,IAAI,UAAU,CAOvC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAGnD;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
@@ -0,0 +1,27 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const FOCO_DIR = join(homedir(), ".foco");
5
+ const CONFIG_PATH = join(FOCO_DIR, "config.json");
6
+ function ensureDir() {
7
+ if (!existsSync(FOCO_DIR)) {
8
+ mkdirSync(FOCO_DIR, { recursive: true });
9
+ }
10
+ }
11
+ export function loadConfig() {
12
+ try {
13
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
14
+ return JSON.parse(raw);
15
+ }
16
+ catch {
17
+ return { apiUrl: "http://localhost:3001" };
18
+ }
19
+ }
20
+ export function saveConfig(config) {
21
+ ensureDir();
22
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
23
+ }
24
+ export function getConfigPath() {
25
+ return CONFIG_PATH;
26
+ }
27
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;AAElD,SAAS,SAAS;IAChB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,SAAS,EAAE,CAAC;IACZ,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,105 @@
1
+ /** Common API response format from FOCO backend */
2
+ export interface ApiResponse<T = unknown> {
3
+ code: number;
4
+ message: string;
5
+ data: T | null;
6
+ }
7
+ export interface PaginatedData<T> {
8
+ items: T[];
9
+ page: number;
10
+ pageSize: number;
11
+ total: number;
12
+ }
13
+ export interface FocoConfig {
14
+ apiUrl: string;
15
+ /** Raw session cookie string, e.g. "next-auth.session-token=xxx" */
16
+ cookie?: string;
17
+ /** The username used when logging in (informational) */
18
+ username?: string;
19
+ }
20
+ /** Question bank item from /api/admin/v1/banks */
21
+ export interface Bank {
22
+ id: string;
23
+ examId: string;
24
+ code: string;
25
+ name: string;
26
+ subtitle: string | null;
27
+ coverUrl: string | null;
28
+ status: string;
29
+ saleType: string;
30
+ sortOrder: number;
31
+ isRecommended: boolean;
32
+ createdAt: string;
33
+ examName: string | null;
34
+ }
35
+ /** Question item from /api/admin/v1/questions */
36
+ export interface Question {
37
+ id: string;
38
+ bankId: string;
39
+ parentId: string | null;
40
+ questionType: string;
41
+ stem: string;
42
+ difficulty: number;
43
+ sortOrder: number;
44
+ status: string;
45
+ createdAt: string;
46
+ bankName: string | null;
47
+ sectionTitle: string | null;
48
+ }
49
+ /** Issue item from /api/admin/v1/issues */
50
+ export interface Issue {
51
+ id: string;
52
+ number: number;
53
+ title: string;
54
+ status: string;
55
+ priority: string;
56
+ labels: string[];
57
+ source: string;
58
+ projectId: string | null;
59
+ projectName: string | null;
60
+ assigneeId: string | null;
61
+ assigneeName: string | null;
62
+ parentId: string | null;
63
+ dueDate: string | null;
64
+ sortOrder: number;
65
+ createdAt: string;
66
+ updatedAt: string;
67
+ slug?: string;
68
+ }
69
+ /** Full issue detail from /api/admin/v1/issues/:id */
70
+ export interface IssueDetail extends Issue {
71
+ description: string;
72
+ creatorId: string;
73
+ creatorName: string;
74
+ githubIssueNumber: number | null;
75
+ githubSyncedAt: string | null;
76
+ parentIssue: {
77
+ id: string;
78
+ number: number;
79
+ title: string;
80
+ status: string;
81
+ slug: string;
82
+ } | null;
83
+ subIssues: Array<{
84
+ id: string;
85
+ number: number;
86
+ title: string;
87
+ status: string;
88
+ priority: string;
89
+ assigneeName: string | null;
90
+ slug: string;
91
+ }>;
92
+ links: Array<{
93
+ id: string;
94
+ linkType: string;
95
+ issueId: string;
96
+ issueNumber: number;
97
+ issueTitle: string;
98
+ issueStatus: string;
99
+ slug: string;
100
+ direction: string;
101
+ createdAt: string;
102
+ }>;
103
+ commentCount: number;
104
+ }
105
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "foco-cli",
3
+ "version": "0.1.0",
4
+ "description": "FOCO CLI - 题库管理与工单管理命令行工具",
5
+ "type": "module",
6
+ "bin": {
7
+ "foco": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsx src/index.ts",
16
+ "pack": "npm pack --pack-destination ../app/cli-dist",
17
+ "dist": "npm run build && npm run pack",
18
+ "prepublishOnly": "npm run build",
19
+ "preversion": "npm run build",
20
+ "version": "git add -A",
21
+ "postversion": "git push && git push --tags"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "dependencies": {
27
+ "commander": "^12.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^20.0.0",
31
+ "tsx": "^4.19.0",
32
+ "typescript": "^5.7.0"
33
+ },
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ }
37
+ }