@rhseung/ps-cli 1.4.0 → 1.6.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,510 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ source_default
4
+ } from "../chunk-ASMT3CRD.js";
5
+ import {
6
+ searchProblems
7
+ } from "../chunk-AG6KWWHS.js";
8
+ import {
9
+ getProblem,
10
+ getTierColor,
11
+ getTierName
12
+ } from "../chunk-NH36IFWR.js";
13
+ import {
14
+ useOpenBrowser
15
+ } from "../chunk-GCOFFYJ3.js";
16
+ import "../chunk-QGMWUOJ3.js";
17
+ import {
18
+ Command,
19
+ CommandBuilder,
20
+ CommandDef,
21
+ getProblemDirPath
22
+ } from "../chunk-RVD22OUQ.js";
23
+ import {
24
+ __decorateClass
25
+ } from "../chunk-7MQMPJ3X.js";
26
+
27
+ // src/commands/search.tsx
28
+ import { existsSync } from "fs";
29
+ import { Alert, Spinner } from "@inkjs/ui";
30
+ import { Box as Box2, Text as Text2 } from "ink";
31
+ import { useEffect, useState } from "react";
32
+
33
+ // src/components/problem-selector.tsx
34
+ import { Select } from "@inkjs/ui";
35
+ import { Box, Text } from "ink";
36
+ import { jsx, jsxs } from "react/jsx-runtime";
37
+ function ProblemSelector({
38
+ problems,
39
+ currentPage,
40
+ totalPages,
41
+ showPagination = false,
42
+ onSelect,
43
+ onPageChange,
44
+ header
45
+ }) {
46
+ const options = [];
47
+ problems.forEach((problem) => {
48
+ const solvedText = problem.solvedCount ? ` (${problem.solvedCount.toLocaleString()}\uBA85` : "";
49
+ const triesText = problem.averageTries ? `, \uD3C9\uADE0 ${problem.averageTries}\uD68C` : "";
50
+ const suffix = solvedText + triesText + (solvedText ? ")" : "");
51
+ const solvedMark = problem.isSolved ? " \u2713" : "";
52
+ let tierText = "";
53
+ if (problem.level) {
54
+ const tierName = getTierName(problem.level);
55
+ const tierColor = getTierColor(problem.level);
56
+ if (typeof tierColor === "string") {
57
+ tierText = ` ${source_default.bold.hex(tierColor)(tierName)}`;
58
+ } else {
59
+ tierText = ` ${tierColor(source_default.bold(tierName))}`;
60
+ }
61
+ }
62
+ const problemText = `${problem.problemId} - ${problem.title}`;
63
+ options.push({
64
+ label: `${tierText} ${problemText}${solvedMark}${suffix}`,
65
+ value: `problem:${problem.problemId}`
66
+ });
67
+ });
68
+ if (showPagination && currentPage !== void 0 && totalPages !== void 0) {
69
+ if (currentPage < totalPages) {
70
+ options.push({
71
+ label: `\u2192 \uB2E4\uC74C \uD398\uC774\uC9C0 (${currentPage + 1}/${totalPages})`,
72
+ value: "next-page"
73
+ });
74
+ }
75
+ if (currentPage > 1) {
76
+ options.push({
77
+ label: `\u2190 \uC774\uC804 \uD398\uC774\uC9C0 (${currentPage - 1}/${totalPages})`,
78
+ value: "prev-page"
79
+ });
80
+ }
81
+ }
82
+ const handleSelect = (value) => {
83
+ if (value === "next-page" && onPageChange && currentPage !== void 0) {
84
+ onPageChange(currentPage + 1);
85
+ return;
86
+ }
87
+ if (value === "prev-page" && onPageChange && currentPage !== void 0) {
88
+ onPageChange(currentPage - 1);
89
+ return;
90
+ }
91
+ if (value.startsWith("problem:")) {
92
+ const problemId = parseInt(value.replace("problem:", ""), 10);
93
+ if (!isNaN(problemId)) {
94
+ onSelect(problemId);
95
+ }
96
+ }
97
+ };
98
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
99
+ header && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: header }),
100
+ showPagination && currentPage !== void 0 && totalPages !== void 0 && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
101
+ "\uD398\uC774\uC9C0 ",
102
+ currentPage,
103
+ "/",
104
+ totalPages
105
+ ] }) }),
106
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Select, { options, onChange: handleSelect }) })
107
+ ] });
108
+ }
109
+
110
+ // src/services/workbook-scraper.ts
111
+ import * as cheerio from "cheerio";
112
+ var BOJ_BASE_URL = "https://www.acmicpc.net";
113
+ async function scrapeWorkbook(workbookId) {
114
+ const url = `${BOJ_BASE_URL}/workbook/view/${workbookId}`;
115
+ const response = await fetch(url, {
116
+ headers: {
117
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
118
+ }
119
+ });
120
+ if (!response.ok) {
121
+ throw new Error(
122
+ `Failed to fetch workbook page: HTTP ${response.status}. \uBB38\uC81C\uC9D1\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uAC70\uB098 \uC811\uADFC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
123
+ );
124
+ }
125
+ const html = await response.text();
126
+ const $ = cheerio.load(html);
127
+ let title = "";
128
+ const titleElement = $("h1").first();
129
+ if (titleElement.length > 0) {
130
+ title = titleElement.text().trim();
131
+ } else {
132
+ const pageTitle = $("title").text().trim();
133
+ const match = pageTitle.match(/문제집[:\s]*(.+)/);
134
+ if (match) {
135
+ title = match[1].trim();
136
+ } else {
137
+ title = pageTitle;
138
+ }
139
+ }
140
+ const problems = [];
141
+ const table = $("table.table.table-striped.table-bordered");
142
+ if (table.length === 0) {
143
+ throw new Error(
144
+ `\uBB38\uC81C\uC9D1 ${workbookId}\uC758 \uBB38\uC81C \uBAA9\uB85D\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD398\uC774\uC9C0 \uAD6C\uC870\uAC00 \uBCC0\uACBD\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`
145
+ );
146
+ }
147
+ const rows = table.find("tbody tr");
148
+ rows.each((index, row) => {
149
+ const $row = $(row);
150
+ const cells = $row.find("td");
151
+ if (cells.length >= 2) {
152
+ const problemIdText = $(cells[0]).text().trim();
153
+ const problemId = parseInt(problemIdText, 10);
154
+ const title2 = $(cells[1]).text().trim();
155
+ if (!isNaN(problemId) && title2) {
156
+ problems.push({
157
+ problemId,
158
+ title: title2,
159
+ order: index + 1
160
+ // 1부터 시작하는 순서
161
+ });
162
+ }
163
+ }
164
+ });
165
+ if (problems.length === 0) {
166
+ throw new Error(
167
+ `\uBB38\uC81C\uC9D1 ${workbookId}\uC5D0 \uBB38\uC81C\uAC00 \uC5C6\uAC70\uB098 \uBB38\uC81C \uBAA9\uB85D\uC744 \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
168
+ );
169
+ }
170
+ if (!title) {
171
+ title = `\uBB38\uC81C\uC9D1 ${workbookId}`;
172
+ }
173
+ return {
174
+ id: workbookId,
175
+ title,
176
+ problems,
177
+ createdAt: /* @__PURE__ */ new Date()
178
+ };
179
+ }
180
+
181
+ // src/commands/search.tsx
182
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
183
+ async function enrichProblemsWithTiers(problems) {
184
+ const BATCH_SIZE = 10;
185
+ const DELAY_MS = 200;
186
+ const enriched = [];
187
+ for (let i = 0; i < problems.length; i += BATCH_SIZE) {
188
+ const batch = problems.slice(i, i + BATCH_SIZE);
189
+ const batchPromises = batch.map(async (problem) => {
190
+ try {
191
+ const solvedAcData = await getProblem(problem.problemId);
192
+ return {
193
+ ...problem,
194
+ level: solvedAcData.level
195
+ };
196
+ } catch (error) {
197
+ console.warn(
198
+ `\uBB38\uC81C ${problem.problemId}\uC758 \uD2F0\uC5B4 \uC815\uBCF4\uB97C \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${error instanceof Error ? error.message : String(error)}`
199
+ );
200
+ return problem;
201
+ }
202
+ });
203
+ const batchResults = await Promise.all(batchPromises);
204
+ enriched.push(...batchResults);
205
+ if (i + BATCH_SIZE < problems.length) {
206
+ await new Promise((resolve) => setTimeout(resolve, DELAY_MS));
207
+ }
208
+ }
209
+ return enriched;
210
+ }
211
+ function WorkbookSearchView({
212
+ workbookId,
213
+ onComplete
214
+ }) {
215
+ const [problems, setProblems] = useState([]);
216
+ const [workbookTitle, setWorkbookTitle] = useState("");
217
+ const [loading, setLoading] = useState(true);
218
+ const [error, setError] = useState(null);
219
+ const [selectedProblemId, setSelectedProblemId] = useState(
220
+ null
221
+ );
222
+ useEffect(() => {
223
+ async function loadWorkbook() {
224
+ try {
225
+ setLoading(true);
226
+ setError(null);
227
+ const workbook = await scrapeWorkbook(workbookId);
228
+ setWorkbookTitle(workbook.title);
229
+ const enriched = await enrichProblemsWithTiers(workbook.problems);
230
+ setProblems(enriched);
231
+ } catch (err) {
232
+ setError(err instanceof Error ? err.message : String(err));
233
+ } finally {
234
+ setLoading(false);
235
+ }
236
+ }
237
+ void loadWorkbook();
238
+ }, [workbookId]);
239
+ if (selectedProblemId) {
240
+ return /* @__PURE__ */ jsx2(OpenBrowserView, { problemId: selectedProblemId, onComplete });
241
+ }
242
+ if (loading) {
243
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
244
+ /* @__PURE__ */ jsx2(Spinner, { label: "\uBB38\uC81C\uC9D1\uC744 \uB85C\uB4DC\uD558\uB294 \uC911..." }),
245
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
246
+ "\uBB38\uC81C\uC9D1 ID: ",
247
+ workbookId
248
+ ] }) })
249
+ ] });
250
+ }
251
+ if (error) {
252
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
253
+ /* @__PURE__ */ jsxs2(Alert, { variant: "error", children: [
254
+ "\uC624\uB958: ",
255
+ error
256
+ ] }),
257
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
258
+ "\uBB38\uC81C\uC9D1 ID: ",
259
+ workbookId
260
+ ] }) })
261
+ ] });
262
+ }
263
+ if (problems.length === 0) {
264
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
265
+ /* @__PURE__ */ jsx2(Alert, { variant: "info", children: "\uBB38\uC81C\uC9D1\uC5D0 \uBB38\uC81C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }),
266
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
267
+ "\uBB38\uC81C\uC9D1 ID: ",
268
+ workbookId
269
+ ] }) })
270
+ ] });
271
+ }
272
+ const problemsWithSolvedStatus = problems.map((problem) => {
273
+ const problemDirPath = getProblemDirPath(problem.problemId);
274
+ const isSolved = existsSync(problemDirPath);
275
+ return {
276
+ problemId: problem.problemId,
277
+ title: problem.title,
278
+ level: problem.level,
279
+ isSolved
280
+ };
281
+ });
282
+ return /* @__PURE__ */ jsx2(
283
+ ProblemSelector,
284
+ {
285
+ problems: problemsWithSolvedStatus,
286
+ onSelect: (problemId) => {
287
+ setSelectedProblemId(problemId);
288
+ },
289
+ header: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
290
+ /* @__PURE__ */ jsx2(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "cyan", bold: true, children: [
291
+ "\u{1F4DA} \uBB38\uC81C\uC9D1: ",
292
+ workbookTitle,
293
+ " (ID: ",
294
+ workbookId,
295
+ ")"
296
+ ] }) }),
297
+ /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
298
+ "\uCD1D ",
299
+ problems.length,
300
+ "\uBB38\uC81C"
301
+ ] }) })
302
+ ] })
303
+ }
304
+ );
305
+ }
306
+ function OpenBrowserView({ problemId, onComplete }) {
307
+ const { status, error, url } = useOpenBrowser({
308
+ problemId,
309
+ onComplete
310
+ });
311
+ if (status === "loading") {
312
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
313
+ /* @__PURE__ */ jsx2(Spinner, { label: "\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5EC\uB294 \uC911..." }),
314
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
315
+ "\uBB38\uC81C #",
316
+ problemId
317
+ ] }) })
318
+ ] });
319
+ }
320
+ if (status === "error") {
321
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
322
+ /* @__PURE__ */ jsxs2(Alert, { variant: "error", children: [
323
+ "\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ",
324
+ error
325
+ ] }),
326
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
327
+ "URL: ",
328
+ url
329
+ ] }) })
330
+ ] });
331
+ }
332
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
333
+ /* @__PURE__ */ jsx2(Alert, { variant: "success", children: "\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uBB38\uC81C \uD398\uC774\uC9C0\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4!" }),
334
+ /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
335
+ /* @__PURE__ */ jsxs2(Text2, { children: [
336
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "\uBB38\uC81C \uBC88\uD638:" }),
337
+ " ",
338
+ problemId
339
+ ] }),
340
+ /* @__PURE__ */ jsxs2(Text2, { children: [
341
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "URL:" }),
342
+ " ",
343
+ /* @__PURE__ */ jsx2(Text2, { color: "blue", underline: true, children: url })
344
+ ] })
345
+ ] })
346
+ ] });
347
+ }
348
+ function SearchView({ query, onComplete }) {
349
+ const [results, setResults] = useState([]);
350
+ const [currentPage, setCurrentPage] = useState(1);
351
+ const [totalPages, setTotalPages] = useState(1);
352
+ const [loading, setLoading] = useState(true);
353
+ const [error, setError] = useState(null);
354
+ const [selectedProblemId, setSelectedProblemId] = useState(
355
+ null
356
+ );
357
+ useEffect(() => {
358
+ async function performSearch() {
359
+ try {
360
+ setLoading(true);
361
+ setError(null);
362
+ const searchResults = await searchProblems(query, currentPage);
363
+ const resultsWithSolvedStatus = searchResults.problems.map(
364
+ (problem) => {
365
+ const problemDirPath = getProblemDirPath(problem.problemId);
366
+ const isSolved = existsSync(problemDirPath);
367
+ return {
368
+ ...problem,
369
+ isSolved
370
+ };
371
+ }
372
+ );
373
+ setResults(resultsWithSolvedStatus);
374
+ setTotalPages(searchResults.totalPages);
375
+ } catch (err) {
376
+ setError(err instanceof Error ? err.message : String(err));
377
+ } finally {
378
+ setLoading(false);
379
+ }
380
+ }
381
+ void performSearch();
382
+ }, [query, currentPage]);
383
+ if (loading && !selectedProblemId) {
384
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
385
+ /* @__PURE__ */ jsx2(Spinner, { label: "\uAC80\uC0C9 \uC911..." }),
386
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
387
+ "\uCFFC\uB9AC: ",
388
+ query
389
+ ] }) })
390
+ ] });
391
+ }
392
+ if (error && !selectedProblemId) {
393
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
394
+ /* @__PURE__ */ jsxs2(Alert, { variant: "error", children: [
395
+ "\uAC80\uC0C9 \uC2E4\uD328: ",
396
+ error
397
+ ] }),
398
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
399
+ "\uCFFC\uB9AC: ",
400
+ query
401
+ ] }) })
402
+ ] });
403
+ }
404
+ if (selectedProblemId) {
405
+ return /* @__PURE__ */ jsx2(OpenBrowserView, { problemId: selectedProblemId, onComplete });
406
+ }
407
+ if (results.length === 0) {
408
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
409
+ /* @__PURE__ */ jsx2(Alert, { variant: "info", children: "\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }),
410
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
411
+ "\uCFFC\uB9AC: ",
412
+ query
413
+ ] }) })
414
+ ] });
415
+ }
416
+ return /* @__PURE__ */ jsx2(
417
+ ProblemSelector,
418
+ {
419
+ problems: results.map((problem) => ({
420
+ problemId: problem.problemId,
421
+ title: problem.title,
422
+ level: problem.level,
423
+ solvedCount: problem.solvedCount,
424
+ averageTries: problem.averageTries,
425
+ isSolved: problem.isSolved
426
+ })),
427
+ currentPage,
428
+ totalPages,
429
+ showPagination: true,
430
+ onSelect: (problemId) => {
431
+ setSelectedProblemId(problemId);
432
+ },
433
+ onPageChange: (page) => {
434
+ setCurrentPage(page);
435
+ },
436
+ header: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
437
+ /* @__PURE__ */ jsx2(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "\u{1F50D} \uAC80\uC0C9 \uACB0\uACFC" }) }),
438
+ /* @__PURE__ */ jsx2(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
439
+ "\uCFFC\uB9AC: ",
440
+ query
441
+ ] }) })
442
+ ] })
443
+ }
444
+ );
445
+ }
446
+ var SearchCommand = class extends Command {
447
+ async execute(args, flags) {
448
+ const workbookId = flags.workbook ? parseInt(String(flags.workbook), 10) : null;
449
+ if (workbookId) {
450
+ if (isNaN(workbookId) || workbookId <= 0) {
451
+ console.error("\uC624\uB958: \uC720\uD6A8\uD55C \uBB38\uC81C\uC9D1 ID\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
452
+ console.error(`\uC0AC\uC6A9\uBC95: ps search --workbook <\uBB38\uC81C\uC9D1ID>`);
453
+ console.error(`\uB3C4\uC6C0\uB9D0: ps search --help`);
454
+ process.exit(1);
455
+ return;
456
+ }
457
+ await this.renderView(WorkbookSearchView, {
458
+ workbookId
459
+ });
460
+ return;
461
+ }
462
+ const query = args.join(" ").trim();
463
+ if (!query) {
464
+ console.error("\uC624\uB958: \uAC80\uC0C9 \uCFFC\uB9AC \uB610\uB294 --workbook \uC635\uC158\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694.");
465
+ console.error(`\uC0AC\uC6A9\uBC95: ps search <\uCFFC\uB9AC>`);
466
+ console.error(` ps search --workbook <\uBB38\uC81C\uC9D1ID>`);
467
+ console.error(`\uB3C4\uC6C0\uB9D0: ps search --help`);
468
+ console.error(`\uC608\uC81C: ps search "*g1...g5"`);
469
+ console.error(` ps search --workbook 25052`);
470
+ process.exit(1);
471
+ return;
472
+ }
473
+ await this.renderView(SearchView, {
474
+ query
475
+ });
476
+ }
477
+ };
478
+ SearchCommand = __decorateClass([
479
+ CommandDef({
480
+ name: "search",
481
+ description: `solved.ac\uC5D0\uC11C \uBB38\uC81C\uB97C \uAC80\uC0C9\uD558\uAC70\uB098 \uBC31\uC900 \uBB38\uC81C\uC9D1\uC758 \uBB38\uC81C \uBAA9\uB85D\uC744 \uD45C\uC2DC\uD569\uB2C8\uB2E4.
482
+ - solved.ac \uAC80\uC0C9\uC5B4 \uBB38\uBC95\uC744 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.
483
+ - \uBB38\uC81C \uBAA9\uB85D\uC5D0\uC11C \uC120\uD0DD\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uBB38\uC81C \uD398\uC774\uC9C0\uB97C \uC5FD\uB2C8\uB2E4.
484
+ - \uD398\uC774\uC9C0\uB124\uC774\uC158\uC744 \uD1B5\uD574 \uC5EC\uB7EC \uD398\uC774\uC9C0\uC758 \uACB0\uACFC\uB97C \uD0D0\uC0C9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.
485
+ - --workbook \uC635\uC158\uC73C\uB85C \uBC31\uC900 \uBB38\uC81C\uC9D1\uC758 \uBB38\uC81C \uBAA9\uB85D\uC744 \uBCFC \uC218 \uC788\uC2B5\uB2C8\uB2E4.`,
486
+ flags: [
487
+ {
488
+ name: "workbook",
489
+ options: {
490
+ description: "\uBB38\uC81C\uC9D1 ID\uB97C \uC9C0\uC815\uD558\uC5EC \uD574\uB2F9 \uBB38\uC81C\uC9D1\uC758 \uBB38\uC81C \uBAA9\uB85D\uC744 \uD45C\uC2DC"
491
+ }
492
+ }
493
+ ],
494
+ autoDetectProblemId: false,
495
+ requireProblemId: false,
496
+ examples: [
497
+ 'search "*g1...g5" # Gold 1-5 \uBB38\uC81C \uAC80\uC0C9',
498
+ 'search "tier:g1...g5" # Gold 1-5 \uBB38\uC81C \uAC80\uC0C9 (tier: \uBB38\uBC95)',
499
+ 'search "#dp" # DP \uD0DC\uADF8 \uBB38\uC81C \uAC80\uC0C9',
500
+ 'search "tag:dp" # DP \uD0DC\uADF8 \uBB38\uC81C \uAC80\uC0C9 (tag: \uBB38\uBC95)',
501
+ 'search "*g1...g5 #dp" # Gold 1-5 \uD2F0\uC5B4\uC758 DP \uD0DC\uADF8 \uBB38\uC81C \uAC80\uC0C9',
502
+ "search --workbook 25052 # \uBB38\uC81C\uC9D1 25052\uC758 \uBB38\uC81C \uBAA9\uB85D \uD45C\uC2DC"
503
+ ]
504
+ })
505
+ ], SearchCommand);
506
+ var search_default = CommandBuilder.fromClass(SearchCommand);
507
+ export {
508
+ SearchCommand,
509
+ search_default as default
510
+ };
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ Command,
4
+ CommandBuilder,
5
+ CommandDef,
6
+ findProjectRoot,
7
+ getProblemDirPath,
8
+ getProblemId,
9
+ getSolvingDirPath
10
+ } from "../chunk-RVD22OUQ.js";
11
+ import {
12
+ __decorateClass
13
+ } from "../chunk-7MQMPJ3X.js";
14
+
15
+ // src/commands/solve.tsx
16
+ import { StatusMessage, Alert } from "@inkjs/ui";
17
+ import { Spinner } from "@inkjs/ui";
18
+ import { Box } from "ink";
19
+
20
+ // src/hooks/use-solve.ts
21
+ import { access, readFile, rename } from "fs/promises";
22
+ import { join } from "path";
23
+ import { execa } from "execa";
24
+ import { useEffect, useState } from "react";
25
+ function useSolve({
26
+ problemId,
27
+ onComplete
28
+ }) {
29
+ const [status, setStatus] = useState(
30
+ "loading"
31
+ );
32
+ const [message, setMessage] = useState("\uBB38\uC81C\uB97C \uC544\uCE74\uC774\uBE0C\uD558\uB294 \uC911...");
33
+ const [error, setError] = useState(null);
34
+ useEffect(() => {
35
+ async function solve() {
36
+ try {
37
+ const projectRoot = findProjectRoot();
38
+ if (!projectRoot) {
39
+ throw new Error("\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
40
+ }
41
+ const solvingDir = getSolvingDirPath(problemId, projectRoot);
42
+ setMessage("solving \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C\uB97C \uD655\uC778\uD558\uB294 \uC911...");
43
+ try {
44
+ await access(solvingDir);
45
+ } catch {
46
+ throw new Error(
47
+ `solving \uB514\uB809\uD1A0\uB9AC\uC5D0 \uBB38\uC81C ${problemId}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
48
+ );
49
+ }
50
+ setMessage("\uBB38\uC81C \uC815\uBCF4\uB97C \uC77D\uB294 \uC911...");
51
+ const metaPath = join(solvingDir, "meta.json");
52
+ let problemTitle = `\uBB38\uC81C ${problemId}`;
53
+ try {
54
+ const metaContent = await readFile(metaPath, "utf-8");
55
+ const meta = JSON.parse(metaContent);
56
+ if (meta.title) {
57
+ problemTitle = meta.title;
58
+ }
59
+ } catch {
60
+ }
61
+ const problemDir = getProblemDirPath(problemId, projectRoot);
62
+ try {
63
+ await access(problemDir);
64
+ throw new Error(
65
+ `problem \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC774\uBBF8 \uBB38\uC81C ${problemId}\uAC00 \uC874\uC7AC\uD569\uB2C8\uB2E4.`
66
+ );
67
+ } catch (err) {
68
+ if (err instanceof Error && err.message.includes("\uC774\uBBF8")) {
69
+ throw err;
70
+ }
71
+ }
72
+ setMessage("\uBB38\uC81C\uB97C problem \uB514\uB809\uD1A0\uB9AC\uB85C \uC774\uB3D9\uD558\uB294 \uC911...");
73
+ await rename(solvingDir, problemDir);
74
+ setMessage("Git \uCEE4\uBC0B\uC744 \uC2E4\uD589\uD558\uB294 \uC911...");
75
+ try {
76
+ await execa("git", ["add", problemDir], { cwd: projectRoot });
77
+ const commitMessage = `solve: ${problemId} - ${problemTitle}`;
78
+ await execa("git", ["commit", "-m", commitMessage], {
79
+ cwd: projectRoot
80
+ });
81
+ } catch (gitError) {
82
+ console.warn(
83
+ "Git \uCEE4\uBC0B \uC2E4\uD328:",
84
+ gitError instanceof Error ? gitError.message : String(gitError)
85
+ );
86
+ }
87
+ setStatus("success");
88
+ setMessage(`\uBB38\uC81C ${problemId}\uB97C \uC544\uCE74\uC774\uBE0C\uD588\uC2B5\uB2C8\uB2E4: ${problemDir}`);
89
+ setTimeout(() => {
90
+ onComplete?.();
91
+ }, 2e3);
92
+ } catch (err) {
93
+ setStatus("error");
94
+ setError(err instanceof Error ? err.message : String(err));
95
+ setTimeout(() => {
96
+ onComplete?.();
97
+ }, 2e3);
98
+ }
99
+ }
100
+ void solve();
101
+ }, [problemId, onComplete]);
102
+ return {
103
+ status,
104
+ message,
105
+ error
106
+ };
107
+ }
108
+
109
+ // src/commands/solve.tsx
110
+ import { jsx, jsxs } from "react/jsx-runtime";
111
+ function SolveView({ problemId, onComplete }) {
112
+ const { status, message, error } = useSolve({
113
+ problemId,
114
+ onComplete
115
+ });
116
+ if (status === "loading") {
117
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(Spinner, { label: message }) });
118
+ }
119
+ if (status === "error") {
120
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
121
+ "\uC624\uB958 \uBC1C\uC0DD: ",
122
+ error
123
+ ] }) });
124
+ }
125
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: message }) });
126
+ }
127
+ var SolveCommand = class extends Command {
128
+ async execute(args, _) {
129
+ const problemId = getProblemId(args);
130
+ if (problemId === null) {
131
+ console.error("\uC624\uB958: \uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
132
+ console.error(`\uC0AC\uC6A9\uBC95: ps solve <\uBB38\uC81C\uBC88\uD638>`);
133
+ console.error(`\uB3C4\uC6C0\uB9D0: ps solve --help`);
134
+ console.error(
135
+ `\uD78C\uD2B8: solving/{\uBB38\uC81C\uBC88\uD638} \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC2E4\uD589\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C \uBB38\uC81C \uBC88\uD638\uB97C \uCD94\uB860\uD569\uB2C8\uB2E4.`
136
+ );
137
+ process.exit(1);
138
+ return;
139
+ }
140
+ await this.renderView(SolveView, {
141
+ problemId
142
+ });
143
+ }
144
+ };
145
+ SolveCommand = __decorateClass([
146
+ CommandDef({
147
+ name: "solve",
148
+ description: `\uBB38\uC81C\uB97C \uC544\uCE74\uC774\uBE0C\uD558\uACE0 Git \uCEE4\uBC0B\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4.
149
+ - solving \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C\uB97C \uCC3E\uC544 problem \uB514\uB809\uD1A0\uB9AC\uB85C \uC774\uB3D9
150
+ - Git add \uBC0F commit \uC2E4\uD589 (\uCEE4\uBC0B \uBA54\uC2DC\uC9C0: "solve: {\uBB38\uC81C\uBC88\uD638} - {\uBB38\uC81C\uC774\uB984}")`,
151
+ autoDetectProblemId: true,
152
+ requireProblemId: false,
153
+ examples: [
154
+ "solve 1000",
155
+ "solve # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C \uBC88\uD638 \uC790\uB3D9 \uAC10\uC9C0"
156
+ ]
157
+ })
158
+ ], SolveCommand);
159
+ var solve_default = CommandBuilder.fromClass(SolveCommand);
160
+ export {
161
+ SolveCommand,
162
+ solve_default as default
163
+ };