atcoder-workspace 1.1.0-beta.1

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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/THIRD_PARTY_LICENSES +21 -0
  4. package/dist/atcoder/client.d.ts +2 -0
  5. package/dist/atcoder/client.js +23 -0
  6. package/dist/atcoder/new.d.ts +15 -0
  7. package/dist/atcoder/new.js +123 -0
  8. package/dist/atcoder/parser/contest-tasks.d.ts +6 -0
  9. package/dist/atcoder/parser/contest-tasks.js +86 -0
  10. package/dist/atcoder/parser/limits.d.ts +10 -0
  11. package/dist/atcoder/parser/limits.js +71 -0
  12. package/dist/atcoder/parser/problem-page.d.ts +12 -0
  13. package/dist/atcoder/parser/problem-page.js +136 -0
  14. package/dist/atcoder/parser/submission-status.d.ts +8 -0
  15. package/dist/atcoder/parser/submission-status.js +78 -0
  16. package/dist/atcoder/submit.d.ts +9 -0
  17. package/dist/atcoder/submit.js +182 -0
  18. package/dist/cli.d.ts +2 -0
  19. package/dist/cli.js +508 -0
  20. package/dist/config/config-store.d.ts +17 -0
  21. package/dist/config/config-store.js +92 -0
  22. package/dist/session/auth.d.ts +8 -0
  23. package/dist/session/auth.js +117 -0
  24. package/dist/session/store.d.ts +15 -0
  25. package/dist/session/store.js +75 -0
  26. package/dist/test-runner/diff.d.ts +7 -0
  27. package/dist/test-runner/diff.js +32 -0
  28. package/dist/test-runner/runner.d.ts +46 -0
  29. package/dist/test-runner/runner.js +274 -0
  30. package/dist/utils/errors.d.ts +15 -0
  31. package/dist/utils/errors.js +35 -0
  32. package/dist/utils/format.d.ts +9 -0
  33. package/dist/utils/format.js +89 -0
  34. package/dist/utils/i18n.d.ts +345 -0
  35. package/dist/utils/i18n.js +413 -0
  36. package/dist/utils/open.d.ts +8 -0
  37. package/dist/utils/open.js +50 -0
  38. package/dist/workspace/finder.d.ts +9 -0
  39. package/dist/workspace/finder.js +62 -0
  40. package/dist/workspace/initializer.d.ts +4 -0
  41. package/dist/workspace/initializer.js +109 -0
  42. package/package.json +38 -0
  43. package/src/atcoder/client.ts +21 -0
  44. package/src/atcoder/new.ts +107 -0
  45. package/src/atcoder/parser/contest-tasks.test.ts +37 -0
  46. package/src/atcoder/parser/contest-tasks.ts +61 -0
  47. package/src/atcoder/parser/limits.test.ts +52 -0
  48. package/src/atcoder/parser/limits.ts +75 -0
  49. package/src/atcoder/parser/problem-page.test.ts +68 -0
  50. package/src/atcoder/parser/problem-page.ts +126 -0
  51. package/src/atcoder/parser/submission-status.test.ts +36 -0
  52. package/src/atcoder/parser/submission-status.ts +54 -0
  53. package/src/atcoder/submit.ts +170 -0
  54. package/src/cli.ts +554 -0
  55. package/src/config/config-store.ts +72 -0
  56. package/src/session/auth.ts +87 -0
  57. package/src/session/store.ts +50 -0
  58. package/src/test-runner/diff.test.ts +26 -0
  59. package/src/test-runner/diff.ts +42 -0
  60. package/src/test-runner/runner.test.ts +70 -0
  61. package/src/test-runner/runner.ts +315 -0
  62. package/src/utils/errors.ts +31 -0
  63. package/src/utils/format.test.ts +69 -0
  64. package/src/utils/format.ts +95 -0
  65. package/src/utils/i18n.test.ts +74 -0
  66. package/src/utils/i18n.ts +418 -0
  67. package/src/utils/open.ts +47 -0
  68. package/src/workspace/finder.ts +29 -0
  69. package/src/workspace/initializer.ts +85 -0
  70. package/tsconfig.json +16 -0
@@ -0,0 +1,413 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.t = exports.MESSAGES = void 0;
7
+ exports.getSystemLanguage = getSystemLanguage;
8
+ exports.getLanguage = getLanguage;
9
+ const picocolors_1 = __importDefault(require("picocolors"));
10
+ const config_store_1 = require("../config/config-store");
11
+ const finder_1 = require("../workspace/finder");
12
+ /**
13
+ * Detects the system language.
14
+ * Default is English. If Japanese is detected in environment variables, returns 'ja'.
15
+ */
16
+ function getSystemLanguage() {
17
+ const envs = [
18
+ process.env.LANG,
19
+ process.env.LANGUAGE,
20
+ process.env.LC_ALL,
21
+ process.env.LC_MESSAGES
22
+ ];
23
+ for (const env of envs) {
24
+ if (env && env.toLowerCase().includes('ja')) {
25
+ return 'ja';
26
+ }
27
+ }
28
+ return 'en';
29
+ }
30
+ /**
31
+ * Gets the current active language.
32
+ * Looks up the workspace config first, then falls back to system language.
33
+ */
34
+ function getLanguage(workspaceRoot) {
35
+ let root = workspaceRoot;
36
+ if (!root) {
37
+ try {
38
+ root = (0, finder_1.findWorkspaceRoot)();
39
+ }
40
+ catch {
41
+ // Not in workspace
42
+ }
43
+ }
44
+ if (root) {
45
+ try {
46
+ const config = (0, config_store_1.loadConfig)(root);
47
+ if (config.lang === 'ja' || config.lang === 'en') {
48
+ return config.lang;
49
+ }
50
+ }
51
+ catch {
52
+ // Ignore config loading errors
53
+ }
54
+ }
55
+ return getSystemLanguage();
56
+ }
57
+ // Translations dictionary
58
+ exports.MESSAGES = {
59
+ // Common / Errors
60
+ workspaceNotFound: {
61
+ en: '.atcoder-cli/ directory was not found. Please run "atc init" in your workspace root first.',
62
+ ja: '.atcoder-cli/ ディレクトリが見つかりませんでした。先にワークスペースのルートで "atc init" を実行してください。'
63
+ },
64
+ // CLI Command Descriptions
65
+ descInit: {
66
+ en: 'Initialize .atcoder-cli/ workspace and configure .gitignore',
67
+ ja: 'AtCoder ワークスペース (.atcoder-cli/) を初期化し、.gitignore を設定します'
68
+ },
69
+ descLogin: {
70
+ en: 'Log in to AtCoder by entering the REVEL_SESSION cookie',
71
+ ja: 'REVEL_SESSION クッキーを入力して AtCoder にログインします'
72
+ },
73
+ descLogout: {
74
+ en: 'Discard the local session Cookie',
75
+ ja: 'ローカルに保存されているセッションクッキーを破棄してログアウトします'
76
+ },
77
+ descWhoami: {
78
+ en: 'Verify and display current login status',
79
+ ja: '現在のログイン状態を確認・表示します'
80
+ },
81
+ descNew: {
82
+ en: 'Create task directories and download sample cases',
83
+ ja: '問題用フォルダを作成し、サンプルテストケースをダウンロードします'
84
+ },
85
+ descTest: {
86
+ en: 'Run local tests against downloaded sample cases',
87
+ ja: 'ダウンロードしたサンプルケースに対してローカルテストを実行します'
88
+ },
89
+ descSubmit: {
90
+ en: 'Submit code to AtCoder and poll status',
91
+ ja: 'コードを AtCoder に提出し、ジャッジステータスを監視します'
92
+ },
93
+ descLang: {
94
+ en: 'Change the display language (en or ja)',
95
+ ja: '表示言語の切り替え (en または ja)'
96
+ },
97
+ // init
98
+ initIntro: {
99
+ en: 'AtCoder Workspace - Workspace Initialization',
100
+ ja: 'AtCoder Workspace - ワークスペース初期化'
101
+ },
102
+ initSelectLang: {
103
+ en: 'Select your default programming language:',
104
+ ja: 'デフォルトのプログラミング言語を選択してください:'
105
+ },
106
+ initCancelled: {
107
+ en: 'Initialization cancelled.',
108
+ ja: '初期化がキャンセルされました。'
109
+ },
110
+ initSpinner: {
111
+ en: 'Initializing workspace...',
112
+ ja: 'ワークスペースを初期化中...'
113
+ },
114
+ initFilesSet: {
115
+ en: 'Workspace files set up.',
116
+ ja: 'ワークスペースファイルがセットアップされました。'
117
+ },
118
+ initAlreadyInitialized: {
119
+ en: '.atcoder-cli/ directory already exists. Preserved existing configurations.',
120
+ ja: '.atcoder-cli/ ディレクトリは既に存在します。既存の設定は保持されました。'
121
+ },
122
+ initCreatedConfig: {
123
+ en: (lang) => `Created .atcoder-cli/ configuration (default language: ${lang}) and template folders.`,
124
+ ja: (lang) => `.atcoder-cli/ 設定ファイル(デフォルト言語: ${lang})とテンプレートフォルダを作成しました。`
125
+ },
126
+ initGitignoreUpdated: {
127
+ en: 'Added .atcoder-cli/session.json to .gitignore to keep your credentials safe.',
128
+ ja: '認証情報を安全に保つため、.atcoder-cli/session.json を .gitignore に追加しました。'
129
+ },
130
+ initOutro: {
131
+ en: 'Initialization complete! You can now run "atc login".',
132
+ ja: '初期化が完了しました! "atc login" を実行できます。'
133
+ },
134
+ // login
135
+ loginIntro: {
136
+ en: 'AtCoder Workspace - Login',
137
+ ja: 'AtCoder Workspace - ログイン'
138
+ },
139
+ loginNote: {
140
+ en: `Please copy your REVEL_SESSION cookie from your browser and paste it here.\n` +
141
+ `To find it: Open developer tools (F12) on atcoder.jp -> Application / Storage -> Cookies -> REVEL_SESSION`,
142
+ ja: `ブラウザから REVEL_SESSION クッキーをコピーして、ここに貼り付けてください。\n` +
143
+ `確認方法: atcoder.jp で開発者ツール (F12) を開く -> アプリケーション/ストレージ -> クッキー -> REVEL_SESSION`
144
+ },
145
+ loginWelcome: {
146
+ en: (username) => `Welcome, ${picocolors_1.default.bold(username)}! Session successfully validated and saved.`,
147
+ ja: (username) => `ようこそ、${picocolors_1.default.bold(username)}さん!セッションが正常に検証され、保存されました。`
148
+ },
149
+ loginEnterCookie: {
150
+ en: 'Enter your AtCoder REVEL_SESSION cookie value:',
151
+ ja: 'AtCoder の REVEL_SESSION クッキーの値を入力してください:'
152
+ },
153
+ loginPlaceholder: {
154
+ en: 'REVEL_SESSION=...',
155
+ ja: 'REVEL_SESSION=...'
156
+ },
157
+ loginCookieNotEmpty: {
158
+ en: 'Cookie value cannot be empty.',
159
+ ja: 'クッキーの値を空にすることはできません。'
160
+ },
161
+ loginCancelled: {
162
+ en: 'Login cancelled.',
163
+ ja: 'ログインがキャンセルされました。'
164
+ },
165
+ loginVerifying: {
166
+ en: 'Verifying REVEL_SESSION cookie...',
167
+ ja: 'REVEL_SESSION クッキーを検証中...'
168
+ },
169
+ loginVerifySuccess: {
170
+ en: 'Cookie verified successfully!',
171
+ ja: 'クッキーの検証に成功しました!'
172
+ },
173
+ loginVerifyFailed: {
174
+ en: 'Verification failed.',
175
+ ja: '検証に失敗しました。'
176
+ },
177
+ loginRetryConfirm: {
178
+ en: 'Would you like to try entering the cookie again?',
179
+ ja: 'クッキーをもう一度入力しますか?'
180
+ },
181
+ loginAborted: {
182
+ en: 'Login aborted.',
183
+ ja: 'ログインが中断されました。'
184
+ },
185
+ // logout
186
+ logoutSuccess: {
187
+ en: 'Session cleared. You are logged out.',
188
+ ja: 'セッションがクリアされました。ログアウトしました。'
189
+ },
190
+ // whoami
191
+ whoamiVerifying: {
192
+ en: 'Verifying session...',
193
+ ja: 'セッションを検証中...'
194
+ },
195
+ whoamiLoggedIn: {
196
+ en: (username) => `Logged in as: ${picocolors_1.default.bold(picocolors_1.default.cyan(username))}`,
197
+ ja: (username) => `ログイン中: ${picocolors_1.default.bold(picocolors_1.default.cyan(username))}`
198
+ },
199
+ // new
200
+ newContestDirExists: {
201
+ en: (contestId) => `Contest directory "${contestId}" already exists. If you want to recreate it, please delete or rename it manually first.`,
202
+ ja: (contestId) => `コンテストディレクトリ "${contestId}" は既に存在します。再作成したい場合は、手動で削除またはリネームしてください。`
203
+ },
204
+ newIntro: {
205
+ en: (contestId) => `Scaffolding Contest: ${contestId}`,
206
+ ja: (contestId) => `コンテストのセットアップ開始: ${contestId}`
207
+ },
208
+ newFetchingTasks: {
209
+ en: (contestId) => `Fetching tasks for contest ${contestId}...`,
210
+ ja: (contestId) => `コンテスト ${contestId} の問題を読み込み中...`
211
+ },
212
+ newFoundTasks: {
213
+ en: (count) => `Found ${count} tasks.`,
214
+ ja: (count) => `${count} 個の問題が見つかりました。`
215
+ },
216
+ newNoTasksFound: {
217
+ en: (contestId) => `No tasks found for contest "${contestId}". Make sure the contest ID is correct.`,
218
+ ja: (contestId) => `コンテスト "${contestId}" の問題が見つかりませんでした。コンテストIDが正しいか確認してください。`
219
+ },
220
+ newLabelNotFound: {
221
+ en: (label, contestId, available) => `Task label "${label}" not found in contest "${contestId}". Available tasks: ${available}`,
222
+ ja: (label, contestId, available) => `問題ラベル "${label}" がコンテスト "${contestId}" に見つかりません。利用可能な問題: ${available}`
223
+ },
224
+ newMultiselectMessage: {
225
+ en: "Which tasks do you want to set up? (Space: select, 'a': toggle all, Enter: confirm)",
226
+ ja: "どの問題をセットアップしますか?(Space: 選択, 'a': 全選択/解除, Enter: 決定)"
227
+ },
228
+ newCancelled: {
229
+ en: 'Scaffolding cancelled.',
230
+ ja: 'セットアップがキャンセルされました。'
231
+ },
232
+ newSettingUpTask: {
233
+ en: (label, id) => `Setting up task ${label} (${id})...`,
234
+ ja: (label, id) => `問題 ${label} (${id}) をセットアップ中...`
235
+ },
236
+ newSetupSuccess: {
237
+ en: (label, count) => `Task ${label} set up with ${count} samples.`,
238
+ ja: (label, count) => `問題 ${label} を ${count} 個のサンプルでセットアップしました。`
239
+ },
240
+ newScaffoldingComplete: {
241
+ en: (count) => `Scaffolding complete for ${count} task(s).`,
242
+ ja: (count) => `${count} 個の問題のセットアップが完了しました。`
243
+ },
244
+ // test
245
+ testIntro: {
246
+ en: (contestId, label) => `Running tests for ${contestId}/${label}`,
247
+ ja: (contestId, label) => `テスト実行中: ${contestId}/${label}`
248
+ },
249
+ testRetrievingLimits: {
250
+ en: 'Retrieving problem time limits...',
251
+ ja: '問題の実行時間制限を取得中...'
252
+ },
253
+ testLoadedLimits: {
254
+ en: (limit) => `Loaded limits (Time Limit: ${limit} ms)`,
255
+ ja: (limit) => `時間制限をロードしました (制限時間: ${limit} ms)`
256
+ },
257
+ testDefaultLimits: {
258
+ en: 'Using default time limit of 2000 ms (task info not found).',
259
+ ja: 'デフォルトの時間制限 2000 ms を使用します(問題情報が見つかりません)。'
260
+ },
261
+ testDefaultLimitsError: {
262
+ en: 'Using default time limit of 2000 ms.',
263
+ ja: 'デフォルトの時間制限 2000 ms を使用します。'
264
+ },
265
+ testCompilingRunning: {
266
+ en: 'Compiling and running test cases...',
267
+ ja: 'コンパイルおよびテストケース実行中...'
268
+ },
269
+ testFinished: {
270
+ en: 'Test run finished.',
271
+ ja: 'テスト実行が完了しました。'
272
+ },
273
+ testCompilationFailed: {
274
+ en: 'Compilation Failed:',
275
+ ja: 'コンパイル失敗:'
276
+ },
277
+ testNoSamples: {
278
+ en: 'No sample test cases found in tests/ directory.',
279
+ ja: 'tests/ ディレクトリにサンプルテストケースが見つかりませんでした。'
280
+ },
281
+ testOutroPassed: {
282
+ en: 'All tests passed! 🎉',
283
+ ja: 'すべてのテストに合格しました! 🎉'
284
+ },
285
+ testOutroFailed: {
286
+ en: 'Some tests failed. 😢',
287
+ ja: '一部のテストが不合格でした。 😢'
288
+ },
289
+ // submit
290
+ submitPreparing: {
291
+ en: (contestId, label) => `Preparing Submission for ${contestId}/${label}`,
292
+ ja: (contestId, label) => `提出準備中: ${contestId}/${label}`
293
+ },
294
+ submitRetrievingLimits: {
295
+ en: 'Retrieving problem time limits for testing...',
296
+ ja: 'テスト用の実行時間制限を取得中...'
297
+ },
298
+ submitRunningTests: {
299
+ en: 'Running local tests...',
300
+ ja: 'ローカルテストを実行中...'
301
+ },
302
+ submitNoSamples: {
303
+ en: 'No sample test cases found. Skipping tests.',
304
+ ja: 'サンプルテストケースが見つかりませんでした。テストをスキップします。'
305
+ },
306
+ submitTestsFailed: {
307
+ en: 'Some local test cases failed!',
308
+ ja: '一部のローカルテストケースが不合格でした!'
309
+ },
310
+ submitConfirmMessage: {
311
+ en: 'Do you still want to submit to AtCoder?',
312
+ ja: 'AtCoder にコードを提出しますか?'
313
+ },
314
+ submitAborted: {
315
+ en: 'Submission aborted.',
316
+ ja: '提出が中止されました。'
317
+ },
318
+ submitTestsPassed: {
319
+ en: 'All local tests passed! Proceeding to submit...',
320
+ ja: 'すべてのローカルテストに合格しました!提出中...'
321
+ },
322
+ submitSubmitting: {
323
+ en: 'Submitting code...',
324
+ ja: 'コードを提出中...'
325
+ },
326
+ submitSuccess: {
327
+ en: (id) => `Submitted successfully! Submission ID: ${picocolors_1.default.bold(id)}`,
328
+ ja: (id) => `提出が成功しました! 提出ID: ${picocolors_1.default.bold(id)}`
329
+ },
330
+ submitWaitingJudge: {
331
+ en: 'Waiting for judge status...',
332
+ ja: 'ジャッジ状況を待機中...'
333
+ },
334
+ submitJudgeFinished: {
335
+ en: (status) => `Judge Finished: ${status}`,
336
+ ja: (status) => `ジャッジ完了: ${status}`
337
+ },
338
+ submitAccepted: {
339
+ en: 'Accepted! 🎉',
340
+ ja: '正解 (AC) です! 🎉'
341
+ },
342
+ submitFailed: {
343
+ en: 'Judge Failed.',
344
+ ja: 'ジャッジ失敗。'
345
+ },
346
+ submitTimeout: {
347
+ en: 'Polling timed out.',
348
+ ja: 'タイムアウトしました。'
349
+ },
350
+ submitTimeoutWarn: {
351
+ en: (url) => `The submission status was not determined in time. View details at: https://atcoder.jp${url}`,
352
+ ja: (url) => `時間内に提出ステータスを判定できませんでした。詳細は以下で確認してください: https://atcoder.jp${url}`
353
+ },
354
+ submitTurnstileDetected: {
355
+ en: 'Cloudflare Turnstile challenge detected. Automated CLI submissions are blocked by AtCoder\'s bot protection. Please submit via your browser.',
356
+ ja: 'Cloudflare Turnstile チャレンジが検出されました。AtCoderの保護機能によりCLIからの自動提出がブロックされています。ブラウザから提出してください。'
357
+ },
358
+ submitRejected: {
359
+ en: 'Submission rejected by AtCoder. Please verify if your session is valid or if you are rate-limited.',
360
+ ja: 'AtCoderにより提出が拒否されました。ログイン状態や短時間での連続提出でないか確認してください。'
361
+ },
362
+ submitFallbackMessage: {
363
+ en: 'We have opened the submission page in your default browser. Please submit your code manually.',
364
+ ja: 'デフォルトのブラウザで提出ページを開きました。手動でコードを提出してください。'
365
+ },
366
+ submitFallbackMessageWithClipboard: {
367
+ en: 'We have opened the submission page in your default browser and copied your code to the clipboard. Please paste (Cmd+V) and submit.',
368
+ ja: 'デフォルトのブラウザで提出ページを開き、コードをクリップボードにコピーしました。ペースト(Cmd+V)して手動で提出してください。'
369
+ },
370
+ submitManualSubmission: {
371
+ en: 'Manual Submission',
372
+ ja: '手動提出へ切り替え'
373
+ },
374
+ // lang command
375
+ langSuccess: {
376
+ en: (l) => `Display language changed to: ${l}`,
377
+ ja: (l) => `表示言語を変更しました: ${l}`
378
+ },
379
+ langInvalid: {
380
+ en: 'Invalid language. Please specify "en" or "ja".',
381
+ ja: '無効な言語です。"en" または "ja" を指定してください。'
382
+ },
383
+ langWorkspaceRequired: {
384
+ en: 'Display language can only be configured inside an AtCoder workspace. Please run "atc init" first.',
385
+ ja: '表示言語の設定は AtCoder ワークスペース内でのみ可能です。先に "atc init" を実行してください。'
386
+ },
387
+ langCommandUsage: {
388
+ en: 'Usage: atc lang <en|ja>',
389
+ ja: '使い方: atc lang <en|ja>'
390
+ },
391
+ submitSessionExpired: {
392
+ en: 'Session expired or invalid. Please log in again using "atc login".',
393
+ ja: 'セッションの期限が切れているか無効です。"atc login" を実行して再ログインしてください。'
394
+ },
395
+ submitLangSelectNotFound: {
396
+ en: 'Language selection element not found on submit page. Please make sure you are logged in and the contest has started.',
397
+ ja: '提出ページに言語選択要素が見つかりませんでした。ログイン状態であること、およびコンテストが開始されていることを確認してください。'
398
+ }
399
+ };
400
+ /**
401
+ * Translates a key into the active language.
402
+ */
403
+ const t = (key, lang, ...args) => {
404
+ const item = exports.MESSAGES[key];
405
+ if (!item)
406
+ return String(key);
407
+ const msg = item[lang] || item['en'];
408
+ if (typeof msg === 'function') {
409
+ return msg(...args);
410
+ }
411
+ return msg;
412
+ };
413
+ exports.t = t;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Opens the specified URL in the default system web browser.
3
+ */
4
+ export declare function openUrl(url: string): void;
5
+ /**
6
+ * Copies the specified text to the system clipboard.
7
+ */
8
+ export declare function copyToClipboard(text: string): Promise<boolean>;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.openUrl = openUrl;
4
+ exports.copyToClipboard = copyToClipboard;
5
+ const child_process_1 = require("child_process");
6
+ /**
7
+ * Opens the specified URL in the default system web browser.
8
+ */
9
+ function openUrl(url) {
10
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
11
+ // For Windows, 'start' needs to be run in a shell or with shell: true
12
+ // Let's spawn it safely
13
+ if (process.platform === 'win32') {
14
+ (0, child_process_1.exec)(`start "" "${url}"`);
15
+ }
16
+ else {
17
+ (0, child_process_1.exec)(`${cmd} "${url}"`);
18
+ }
19
+ }
20
+ /**
21
+ * Copies the specified text to the system clipboard.
22
+ */
23
+ function copyToClipboard(text) {
24
+ return new Promise((resolve) => {
25
+ try {
26
+ let proc;
27
+ if (process.platform === 'darwin') {
28
+ proc = (0, child_process_1.spawn)('pbcopy');
29
+ }
30
+ else if (process.platform === 'win32') {
31
+ proc = (0, child_process_1.spawn)('clip');
32
+ }
33
+ else {
34
+ // Linux fallback (xclip)
35
+ proc = (0, child_process_1.spawn)('xclip', ['-selection', 'clipboard']);
36
+ }
37
+ proc.stdin.write(text);
38
+ proc.stdin.end();
39
+ proc.on('close', (code) => {
40
+ resolve(code === 0);
41
+ });
42
+ proc.on('error', () => {
43
+ resolve(false);
44
+ });
45
+ }
46
+ catch {
47
+ resolve(false);
48
+ }
49
+ });
50
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Finds the absolute path to the `.atcoder-cli/` directory by traversing upwards
3
+ * from the starting directory (defaults to process.cwd()).
4
+ *
5
+ * @param startDir The directory to start searching from.
6
+ * @returns The absolute path to the `.atcoder-cli` directory.
7
+ * @throws WorkspaceNotFoundError if the `.atcoder-cli` directory is not found.
8
+ */
9
+ export declare function findWorkspaceRoot(startDir?: string): string;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.findWorkspaceRoot = findWorkspaceRoot;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const errors_1 = require("../utils/errors");
40
+ /**
41
+ * Finds the absolute path to the `.atcoder-cli/` directory by traversing upwards
42
+ * from the starting directory (defaults to process.cwd()).
43
+ *
44
+ * @param startDir The directory to start searching from.
45
+ * @returns The absolute path to the `.atcoder-cli` directory.
46
+ * @throws WorkspaceNotFoundError if the `.atcoder-cli` directory is not found.
47
+ */
48
+ function findWorkspaceRoot(startDir = process.cwd()) {
49
+ let currentDir = path.resolve(startDir);
50
+ while (true) {
51
+ const targetPath = path.join(currentDir, '.atcoder-cli');
52
+ if (fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory()) {
53
+ return currentDir;
54
+ }
55
+ const parentDir = path.dirname(currentDir);
56
+ if (parentDir === currentDir) {
57
+ // Reached the root directory
58
+ throw new errors_1.WorkspaceNotFoundError();
59
+ }
60
+ currentDir = parentDir;
61
+ }
62
+ }
@@ -0,0 +1,4 @@
1
+ export declare function initWorkspace(targetDir?: string, defaultLanguage?: string): {
2
+ alreadyInitialized: boolean;
3
+ gitignoreUpdated: boolean;
4
+ };
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.initWorkspace = initWorkspace;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const config_store_1 = require("../config/config-store");
40
+ const DEFAULT_CPP_TEMPLATE = `#include <bits/stdc++.h>
41
+
42
+ using namespace std;
43
+
44
+ int main() {
45
+ // Solve the problem here
46
+ return 0;
47
+ }
48
+ `;
49
+ const DEFAULT_PYTHON_TEMPLATE = `import sys
50
+
51
+ def main():
52
+ # Solve the problem here
53
+ pass
54
+
55
+ if __name__ == '__main__':
56
+ main()
57
+ `;
58
+ function initWorkspace(targetDir = process.cwd(), defaultLanguage = 'cpp') {
59
+ const atCoderCliDir = path.join(targetDir, '.atcoder-cli');
60
+ let alreadyInitialized = false;
61
+ if (fs.existsSync(atCoderCliDir)) {
62
+ alreadyInitialized = true;
63
+ }
64
+ else {
65
+ fs.mkdirSync(atCoderCliDir, { recursive: true });
66
+ }
67
+ // Create default config if it doesn't exist
68
+ const configPath = path.join(atCoderCliDir, 'config.json');
69
+ if (!fs.existsSync(configPath)) {
70
+ (0, config_store_1.saveConfig)(targetDir, {
71
+ ...config_store_1.DEFAULT_CONFIG,
72
+ defaultLanguage
73
+ });
74
+ }
75
+ // Create default templates
76
+ const templatesDir = path.join(atCoderCliDir, 'templates');
77
+ const cppTemplateDir = path.join(templatesDir, 'cpp');
78
+ const pythonTemplateDir = path.join(templatesDir, 'python');
79
+ if (!fs.existsSync(cppTemplateDir)) {
80
+ fs.mkdirSync(cppTemplateDir, { recursive: true });
81
+ }
82
+ const cppFile = path.join(cppTemplateDir, 'main.cpp');
83
+ if (!fs.existsSync(cppFile)) {
84
+ fs.writeFileSync(cppFile, DEFAULT_CPP_TEMPLATE, 'utf8');
85
+ }
86
+ if (!fs.existsSync(pythonTemplateDir)) {
87
+ fs.mkdirSync(pythonTemplateDir, { recursive: true });
88
+ }
89
+ const pythonFile = path.join(pythonTemplateDir, 'main.py');
90
+ if (!fs.existsSync(pythonFile)) {
91
+ fs.writeFileSync(pythonFile, DEFAULT_PYTHON_TEMPLATE, 'utf8');
92
+ }
93
+ // Update or create .gitignore
94
+ const gitignorePath = path.join(targetDir, '.gitignore');
95
+ const ignoreRule = '\n# AtCoder CLI Session\n.atcoder-cli/session.json\n';
96
+ let gitignoreUpdated = false;
97
+ if (fs.existsSync(gitignorePath)) {
98
+ const content = fs.readFileSync(gitignorePath, 'utf8');
99
+ if (!content.includes('.atcoder-cli/session.json')) {
100
+ fs.writeFileSync(gitignorePath, content + ignoreRule, 'utf8');
101
+ gitignoreUpdated = true;
102
+ }
103
+ }
104
+ else {
105
+ fs.writeFileSync(gitignorePath, ignoreRule.trim() + '\n', 'utf8');
106
+ gitignoreUpdated = true;
107
+ }
108
+ return { alreadyInitialized, gitignoreUpdated };
109
+ }