@decencia/ch-cli 1.0.0 → 1.2.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/dist/commands/db-schema.js +3 -3
- package/dist/commands/db-schema.js.map +1 -1
- package/dist/commands/dev-status.d.ts.map +1 -1
- package/dist/commands/dev-status.js +4 -6
- package/dist/commands/dev-status.js.map +1 -1
- package/dist/commands/prd.js +3 -3
- package/dist/commands/prd.js.map +1 -1
- package/dist/commands/setup-skill.d.ts +3 -0
- package/dist/commands/setup-skill.d.ts.map +1 -0
- package/dist/commands/setup-skill.js +65 -0
- package/dist/commands/setup-skill.js.map +1 -0
- package/dist/commands/specs.js +4 -4
- package/dist/commands/specs.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/skill/SKILL.md +500 -0
- package/src/client.ts +0 -137
- package/src/commands/archives.ts +0 -334
- package/src/commands/auth.ts +0 -154
- package/src/commands/dashboard.ts +0 -112
- package/src/commands/db-schema.ts +0 -74
- package/src/commands/dev-status.ts +0 -166
- package/src/commands/init.ts +0 -92
- package/src/commands/members.ts +0 -92
- package/src/commands/notifications.ts +0 -169
- package/src/commands/prd.ts +0 -72
- package/src/commands/projects.ts +0 -207
- package/src/commands/qna.ts +0 -205
- package/src/commands/specs.ts +0 -257
- package/src/commands/sprints.ts +0 -255
- package/src/commands/sqa.ts +0 -422
- package/src/config.ts +0 -98
- package/src/index.ts +0 -69
- package/src/output.ts +0 -186
- package/src/utils.ts +0 -3
- package/tsconfig.json +0 -18
package/src/commands/archives.ts
DELETED
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import FormData from 'form-data';
|
|
5
|
-
import { createClient } from '../client';
|
|
6
|
-
import { formatOutput, printSuccess, printError, printInfo } from '../output';
|
|
7
|
-
|
|
8
|
-
export function registerArchivesCommand(program: Command): void {
|
|
9
|
-
const archives = program
|
|
10
|
-
.command('archives')
|
|
11
|
-
.description('아카이브 관리');
|
|
12
|
-
|
|
13
|
-
// ch archives list
|
|
14
|
-
archives
|
|
15
|
-
.command('list')
|
|
16
|
-
.description('아카이브 목록 조회')
|
|
17
|
-
.option('--category <category>', '카테고리 필터')
|
|
18
|
-
.option('--date-from <date>', '문서일자 시작')
|
|
19
|
-
.option('--date-to <date>', '문서일자 끝')
|
|
20
|
-
.option('--search <keyword>', '제목/메모 검색')
|
|
21
|
-
.option('--sort <field>', '정렬 필드')
|
|
22
|
-
.option('--desc', '내림차순 정렬')
|
|
23
|
-
.option('--page <number>', '페이지 번호', '1')
|
|
24
|
-
.action(async (options) => {
|
|
25
|
-
const globalOpts = program.opts();
|
|
26
|
-
try {
|
|
27
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
28
|
-
const params: any = {};
|
|
29
|
-
if (options.category) params.category = options.category;
|
|
30
|
-
if (options.dateFrom) params.dateFrom = options.dateFrom;
|
|
31
|
-
if (options.dateTo) params.dateTo = options.dateTo;
|
|
32
|
-
if (options.search) params.search = options.search;
|
|
33
|
-
if (options.sort) params.sortBy = options.sort;
|
|
34
|
-
if (options.desc) params.sortOrder = 'desc';
|
|
35
|
-
params.page = parseInt(options.page, 10);
|
|
36
|
-
|
|
37
|
-
const { data } = await client.get('/archives', { params });
|
|
38
|
-
formatOutput(data, globalOpts);
|
|
39
|
-
} catch (error) {
|
|
40
|
-
// Error already handled
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// ch archives get
|
|
45
|
-
archives
|
|
46
|
-
.command('get <archiveId>')
|
|
47
|
-
.description('아카이브 상세 조회')
|
|
48
|
-
.action(async (archiveId: string) => {
|
|
49
|
-
const globalOpts = program.opts();
|
|
50
|
-
try {
|
|
51
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
52
|
-
const { data } = await client.get(`/archives/${archiveId}`);
|
|
53
|
-
formatOutput(data, globalOpts);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
// Error already handled
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// ch archives create
|
|
60
|
-
archives
|
|
61
|
-
.command('create')
|
|
62
|
-
.description('아카이브 등록')
|
|
63
|
-
.requiredOption('--title <title>', '제목')
|
|
64
|
-
.requiredOption('--category <category>', '카테고리')
|
|
65
|
-
.requiredOption('--date <date>', '문서일자 (YYYY-MM-DD)')
|
|
66
|
-
.option('--files <filePaths>', '첨부파일 경로 (쉼표 구분)')
|
|
67
|
-
.option('--memo <memo>', '메모')
|
|
68
|
-
.action(async (options) => {
|
|
69
|
-
const globalOpts = program.opts();
|
|
70
|
-
try {
|
|
71
|
-
if (globalOpts.dryRun) {
|
|
72
|
-
printInfo(`[DRY-RUN] 아카이브 "${options.title}" 등록 요청`);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const form = new FormData();
|
|
77
|
-
form.append('title', options.title);
|
|
78
|
-
form.append('category', options.category);
|
|
79
|
-
form.append('documentDate', options.date);
|
|
80
|
-
if (options.memo) form.append('memo', options.memo);
|
|
81
|
-
|
|
82
|
-
if (options.files) {
|
|
83
|
-
const filePaths = options.files.split(',').map((f: string) => f.trim());
|
|
84
|
-
for (const filePath of filePaths) {
|
|
85
|
-
const resolvedPath = path.resolve(filePath);
|
|
86
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
87
|
-
printError(`파일을 찾을 수 없습니다: ${resolvedPath}`);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
form.append('files', fs.createReadStream(resolvedPath));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
95
|
-
const { data } = await client.post('/archives', form, {
|
|
96
|
-
headers: {
|
|
97
|
-
...form.getHeaders(),
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
formatOutput(data, globalOpts);
|
|
101
|
-
printSuccess('아카이브가 등록되었습니다.');
|
|
102
|
-
} catch (error) {
|
|
103
|
-
// Error already handled
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// ch archives update
|
|
108
|
-
archives
|
|
109
|
-
.command('update <archiveId>')
|
|
110
|
-
.description('아카이브 수정')
|
|
111
|
-
.option('--title <title>', '제목')
|
|
112
|
-
.option('--category <category>', '카테고리')
|
|
113
|
-
.option('--date <date>', '문서일자')
|
|
114
|
-
.option('--memo <memo>', '메모')
|
|
115
|
-
.action(async (archiveId: string, options) => {
|
|
116
|
-
const globalOpts = program.opts();
|
|
117
|
-
try {
|
|
118
|
-
if (globalOpts.dryRun) {
|
|
119
|
-
printInfo(`[DRY-RUN] 아카이브 "${archiveId}" 수정 요청`);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const body: any = {};
|
|
123
|
-
if (options.title) body.title = options.title;
|
|
124
|
-
if (options.category) body.category = options.category;
|
|
125
|
-
if (options.date) body.documentDate = options.date;
|
|
126
|
-
if (options.memo) body.memo = options.memo;
|
|
127
|
-
|
|
128
|
-
if (Object.keys(body).length === 0) {
|
|
129
|
-
printError('수정할 필드를 지정하세요.');
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
134
|
-
const { data } = await client.patch(`/archives/${archiveId}`, body);
|
|
135
|
-
formatOutput(data, globalOpts);
|
|
136
|
-
printSuccess('아카이브가 수정되었습니다.');
|
|
137
|
-
} catch (error) {
|
|
138
|
-
// Error already handled
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// ch archives add-files
|
|
143
|
-
archives
|
|
144
|
-
.command('add-files <archiveId>')
|
|
145
|
-
.description('아카이브에 파일 추가')
|
|
146
|
-
.requiredOption('--files <filePaths>', '추가할 파일 경로 (쉼표 구분)')
|
|
147
|
-
.action(async (archiveId: string, options) => {
|
|
148
|
-
const globalOpts = program.opts();
|
|
149
|
-
try {
|
|
150
|
-
if (globalOpts.dryRun) {
|
|
151
|
-
printInfo(`[DRY-RUN] 아카이브 "${archiveId}"에 파일 추가 요청`);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const form = new FormData();
|
|
156
|
-
const filePaths = options.files.split(',').map((f: string) => f.trim());
|
|
157
|
-
for (const filePath of filePaths) {
|
|
158
|
-
const resolvedPath = path.resolve(filePath);
|
|
159
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
160
|
-
printError(`파일을 찾을 수 없습니다: ${resolvedPath}`);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
form.append('files', fs.createReadStream(resolvedPath));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
167
|
-
const { data } = await client.post(`/archives/${archiveId}/files`, form, {
|
|
168
|
-
headers: {
|
|
169
|
-
...form.getHeaders(),
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
formatOutput(data, globalOpts);
|
|
173
|
-
printSuccess('파일이 추가되었습니다.');
|
|
174
|
-
} catch (error) {
|
|
175
|
-
// Error already handled
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// ch archives remove-file
|
|
180
|
-
archives
|
|
181
|
-
.command('remove-file <archiveId> <fileName>')
|
|
182
|
-
.description('아카이브 첨부파일 삭제')
|
|
183
|
-
.action(async (archiveId: string, fileName: string) => {
|
|
184
|
-
const globalOpts = program.opts();
|
|
185
|
-
try {
|
|
186
|
-
if (globalOpts.dryRun) {
|
|
187
|
-
printInfo(`[DRY-RUN] 아카이브 "${archiveId}" 파일 "${fileName}" 삭제 요청`);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
191
|
-
await client.delete(`/archives/${archiveId}/files/${encodeURIComponent(fileName)}`);
|
|
192
|
-
printSuccess(`파일 "${fileName}"이 삭제되었습니다.`);
|
|
193
|
-
} catch (error) {
|
|
194
|
-
// Error already handled
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// ch archives delete
|
|
199
|
-
archives
|
|
200
|
-
.command('delete <archiveId>')
|
|
201
|
-
.description('아카이브 삭제')
|
|
202
|
-
.action(async (archiveId: string) => {
|
|
203
|
-
const globalOpts = program.opts();
|
|
204
|
-
try {
|
|
205
|
-
if (globalOpts.dryRun) {
|
|
206
|
-
printInfo(`[DRY-RUN] 아카이브 "${archiveId}" 삭제 요청`);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
210
|
-
await client.delete(`/archives/${archiveId}`);
|
|
211
|
-
printSuccess('아카이브가 삭제되었습니다.');
|
|
212
|
-
} catch (error) {
|
|
213
|
-
// Error already handled
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// ch archives download
|
|
218
|
-
archives
|
|
219
|
-
.command('download <archiveId>')
|
|
220
|
-
.description('아카이브 파일 다운로드')
|
|
221
|
-
.option('--file <fileName>', '특정 파일명 (미지정 시 전체 zip)')
|
|
222
|
-
.requiredOption('--output <dir>', '저장 디렉토리')
|
|
223
|
-
.action(async (archiveId: string, options) => {
|
|
224
|
-
const globalOpts = program.opts();
|
|
225
|
-
try {
|
|
226
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
227
|
-
const params: any = {};
|
|
228
|
-
if (options.file) params.file = options.file;
|
|
229
|
-
|
|
230
|
-
const response = await client.get(`/archives/${archiveId}/download`, {
|
|
231
|
-
params,
|
|
232
|
-
responseType: 'arraybuffer',
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// Determine file name from Content-Disposition header or use default
|
|
236
|
-
const contentDisposition = response.headers['content-disposition'];
|
|
237
|
-
let fileName = options.file || `archive-${archiveId}.zip`;
|
|
238
|
-
if (contentDisposition) {
|
|
239
|
-
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
|
240
|
-
if (match && match[1]) {
|
|
241
|
-
fileName = match[1].replace(/['"]/g, '');
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const outputDir = path.resolve(options.output);
|
|
246
|
-
if (!fs.existsSync(outputDir)) {
|
|
247
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const outputPath = path.join(outputDir, fileName);
|
|
251
|
-
fs.writeFileSync(outputPath, Buffer.from(response.data));
|
|
252
|
-
printSuccess(`파일이 저장되었습니다: ${outputPath}`);
|
|
253
|
-
} catch (error) {
|
|
254
|
-
// Error already handled
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// ch archives navigate
|
|
259
|
-
archives
|
|
260
|
-
.command('navigate <archiveId>')
|
|
261
|
-
.description('이전/다음 아카이브 조회')
|
|
262
|
-
.requiredOption('--direction <direction>', '방향 (prev|next)')
|
|
263
|
-
.action(async (archiveId: string, options) => {
|
|
264
|
-
const globalOpts = program.opts();
|
|
265
|
-
try {
|
|
266
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
267
|
-
const { data } = await client.get(`/archives/${archiveId}/navigate`, {
|
|
268
|
-
params: { direction: options.direction },
|
|
269
|
-
});
|
|
270
|
-
formatOutput(data, globalOpts);
|
|
271
|
-
} catch (error) {
|
|
272
|
-
// Error already handled
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
// ch archives categories
|
|
277
|
-
const categories = archives
|
|
278
|
-
.command('categories')
|
|
279
|
-
.description('아카이브 카테고리 관리');
|
|
280
|
-
|
|
281
|
-
// ch archives categories list
|
|
282
|
-
categories
|
|
283
|
-
.command('list')
|
|
284
|
-
.description('카테고리 목록 조회')
|
|
285
|
-
.action(async () => {
|
|
286
|
-
const globalOpts = program.opts();
|
|
287
|
-
try {
|
|
288
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
289
|
-
const { data } = await client.get('/archives/categories');
|
|
290
|
-
formatOutput(data, globalOpts);
|
|
291
|
-
} catch (error) {
|
|
292
|
-
// Error already handled
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// ch archives categories add
|
|
297
|
-
categories
|
|
298
|
-
.command('add <name>')
|
|
299
|
-
.description('카테고리 추가')
|
|
300
|
-
.action(async (name: string) => {
|
|
301
|
-
const globalOpts = program.opts();
|
|
302
|
-
try {
|
|
303
|
-
if (globalOpts.dryRun) {
|
|
304
|
-
printInfo(`[DRY-RUN] 카테고리 "${name}" 추가 요청`);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
308
|
-
const { data } = await client.post('/archives/categories', { name });
|
|
309
|
-
formatOutput(data, globalOpts);
|
|
310
|
-
printSuccess(`카테고리 "${name}"이 추가되었습니다.`);
|
|
311
|
-
} catch (error) {
|
|
312
|
-
// Error already handled
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
// ch archives categories remove
|
|
317
|
-
categories
|
|
318
|
-
.command('remove <name>')
|
|
319
|
-
.description('카테고리 삭제')
|
|
320
|
-
.action(async (name: string) => {
|
|
321
|
-
const globalOpts = program.opts();
|
|
322
|
-
try {
|
|
323
|
-
if (globalOpts.dryRun) {
|
|
324
|
-
printInfo(`[DRY-RUN] 카테고리 "${name}" 삭제 요청`);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
328
|
-
await client.delete(`/archives/categories/${encodeURIComponent(name)}`);
|
|
329
|
-
printSuccess(`카테고리 "${name}"이 삭제되었습니다.`);
|
|
330
|
-
} catch (error) {
|
|
331
|
-
// Error already handled
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
}
|
package/src/commands/auth.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { loadConfig, saveConfig, clearConfig, getConfigPath } from '../config';
|
|
3
|
-
import { createClient } from '../client';
|
|
4
|
-
import { formatOutput, printSuccess, printError, printInfo } from '../output';
|
|
5
|
-
|
|
6
|
-
export function registerAuthCommand(program: Command): void {
|
|
7
|
-
const auth = program
|
|
8
|
-
.command('auth')
|
|
9
|
-
.description('인증 및 세션 관리');
|
|
10
|
-
|
|
11
|
-
// ch auth login
|
|
12
|
-
auth
|
|
13
|
-
.command('login')
|
|
14
|
-
.description('API Key와 프로젝트 ID로 로그인')
|
|
15
|
-
.requiredOption('--key <key>', 'API Key')
|
|
16
|
-
.requiredOption('--project-id <projectId>', '프로젝트 ID')
|
|
17
|
-
.option('--api-url <url>', 'API 서버 URL', 'https://ch-api-618529407342.asia-northeast3.run.app')
|
|
18
|
-
.action(async (options) => {
|
|
19
|
-
try {
|
|
20
|
-
const config = {
|
|
21
|
-
apiKey: options.key,
|
|
22
|
-
projectId: options.projectId,
|
|
23
|
-
apiUrl: options.apiUrl,
|
|
24
|
-
defaultFormat: 'table' as const,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Verify credentials by calling auth status
|
|
28
|
-
const client = createClient({
|
|
29
|
-
apiKey: config.apiKey,
|
|
30
|
-
projectId: config.projectId,
|
|
31
|
-
apiUrl: config.apiUrl,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
await client.get('/auth/status');
|
|
36
|
-
} catch {
|
|
37
|
-
printError('인증 실패: API Key 또는 프로젝트 ID를 확인하세요.');
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
saveConfig(config);
|
|
42
|
-
printSuccess(`로그인 성공! 설정이 ${getConfigPath()}에 저장되었습니다.`);
|
|
43
|
-
} catch (error) {
|
|
44
|
-
// Error already handled by interceptor
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// ch auth status
|
|
49
|
-
auth
|
|
50
|
-
.command('status')
|
|
51
|
-
.description('현재 인증 상태 확인')
|
|
52
|
-
.action(async () => {
|
|
53
|
-
const globalOpts = program.opts();
|
|
54
|
-
try {
|
|
55
|
-
const config = loadConfig();
|
|
56
|
-
if (!config) {
|
|
57
|
-
printError('로그인되어 있지 않습니다. "ch auth login" 명령어로 로그인하세요.');
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const client = createClient();
|
|
62
|
-
const { data } = await client.get('/auth/status');
|
|
63
|
-
formatOutput(data, globalOpts);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
// Error already handled by interceptor
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// ch auth logout
|
|
70
|
-
auth
|
|
71
|
-
.command('logout')
|
|
72
|
-
.description('로그아웃 (로컬 설정 삭제)')
|
|
73
|
-
.action(() => {
|
|
74
|
-
clearConfig();
|
|
75
|
-
printSuccess('로그아웃되었습니다.');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// ch auth keys
|
|
79
|
-
const keys = auth
|
|
80
|
-
.command('keys')
|
|
81
|
-
.description('API Key 관리');
|
|
82
|
-
|
|
83
|
-
// ch auth keys list
|
|
84
|
-
keys
|
|
85
|
-
.command('list')
|
|
86
|
-
.description('내 API Key 목록 조회')
|
|
87
|
-
.action(async () => {
|
|
88
|
-
const globalOpts = program.opts();
|
|
89
|
-
try {
|
|
90
|
-
const client = createClient();
|
|
91
|
-
const { data } = await client.get('/auth/keys');
|
|
92
|
-
formatOutput(data, globalOpts);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
// Error already handled by interceptor
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// ch auth keys create
|
|
99
|
-
keys
|
|
100
|
-
.command('create')
|
|
101
|
-
.description('새 API Key 발급')
|
|
102
|
-
.requiredOption('--name <name>', 'API Key 이름')
|
|
103
|
-
.action(async (options) => {
|
|
104
|
-
const globalOpts = program.opts();
|
|
105
|
-
try {
|
|
106
|
-
if (globalOpts.dryRun) {
|
|
107
|
-
printInfo(`[DRY-RUN] API Key "${options.name}" 생성 요청`);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const client = createClient();
|
|
111
|
-
const { data } = await client.post('/auth/keys', { name: options.name });
|
|
112
|
-
formatOutput(data, globalOpts);
|
|
113
|
-
printSuccess('API Key가 발급되었습니다. 이 키는 다시 표시되지 않으니 안전하게 보관하세요.');
|
|
114
|
-
} catch (error) {
|
|
115
|
-
// Error already handled by interceptor
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// ch auth keys revoke
|
|
120
|
-
keys
|
|
121
|
-
.command('revoke <keyId>')
|
|
122
|
-
.description('API Key 폐기')
|
|
123
|
-
.action(async (keyId: string) => {
|
|
124
|
-
const globalOpts = program.opts();
|
|
125
|
-
try {
|
|
126
|
-
if (globalOpts.dryRun) {
|
|
127
|
-
printInfo(`[DRY-RUN] API Key "${keyId}" 폐기 요청`);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const client = createClient();
|
|
131
|
-
await client.delete(`/auth/keys/${keyId}`);
|
|
132
|
-
printSuccess(`API Key "${keyId}"가 폐기되었습니다.`);
|
|
133
|
-
} catch (error) {
|
|
134
|
-
// Error already handled by interceptor
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// ch auth switch
|
|
139
|
-
auth
|
|
140
|
-
.command('switch')
|
|
141
|
-
.description('프로젝트 전환')
|
|
142
|
-
.requiredOption('--project-id <projectId>', '전환할 프로젝트 ID')
|
|
143
|
-
.action(async (options) => {
|
|
144
|
-
const config = loadConfig();
|
|
145
|
-
if (!config) {
|
|
146
|
-
printError('로그인되어 있지 않습니다. "ch auth login" 명령어로 로그인하세요.');
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
config.projectId = options.projectId;
|
|
151
|
-
saveConfig(config);
|
|
152
|
-
printSuccess(`프로젝트가 "${options.projectId}"로 전환되었습니다.`);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { createClient } from '../client';
|
|
3
|
-
import { formatOutput } from '../output';
|
|
4
|
-
|
|
5
|
-
export function registerDashboardCommand(program: Command): void {
|
|
6
|
-
const dashboard = program
|
|
7
|
-
.command('dashboard')
|
|
8
|
-
.description('대시보드')
|
|
9
|
-
.action(async () => {
|
|
10
|
-
const globalOpts = program.opts();
|
|
11
|
-
try {
|
|
12
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
13
|
-
const { data } = await client.get('/dashboard');
|
|
14
|
-
formatOutput(data, globalOpts);
|
|
15
|
-
} catch (error) {
|
|
16
|
-
// Error already handled
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// ch dashboard progress
|
|
21
|
-
dashboard
|
|
22
|
-
.command('progress')
|
|
23
|
-
.description('전체 진행률 조회')
|
|
24
|
-
.action(async () => {
|
|
25
|
-
const globalOpts = program.opts();
|
|
26
|
-
try {
|
|
27
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
28
|
-
const { data } = await client.get('/dashboard/progress');
|
|
29
|
-
formatOutput(data, globalOpts);
|
|
30
|
-
} catch (error) {
|
|
31
|
-
// Error already handled
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// ch dashboard sprint
|
|
36
|
-
dashboard
|
|
37
|
-
.command('sprint')
|
|
38
|
-
.description('현재 Sprint 요약')
|
|
39
|
-
.action(async () => {
|
|
40
|
-
const globalOpts = program.opts();
|
|
41
|
-
try {
|
|
42
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
43
|
-
const { data } = await client.get('/dashboard/sprint');
|
|
44
|
-
formatOutput(data, globalOpts);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
// Error already handled
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// ch dashboard qna-pending
|
|
51
|
-
dashboard
|
|
52
|
-
.command('qna-pending')
|
|
53
|
-
.description('답변대기 QnA 조회')
|
|
54
|
-
.action(async () => {
|
|
55
|
-
const globalOpts = program.opts();
|
|
56
|
-
try {
|
|
57
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
58
|
-
const { data } = await client.get('/dashboard/qna-pending');
|
|
59
|
-
formatOutput(data, globalOpts);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
// Error already handled
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// ch dashboard sqa-latest
|
|
66
|
-
dashboard
|
|
67
|
-
.command('sqa-latest')
|
|
68
|
-
.description('최근 SQA 결과 조회')
|
|
69
|
-
.action(async () => {
|
|
70
|
-
const globalOpts = program.opts();
|
|
71
|
-
try {
|
|
72
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
73
|
-
const { data } = await client.get('/dashboard/sqa-latest');
|
|
74
|
-
formatOutput(data, globalOpts);
|
|
75
|
-
} catch (error) {
|
|
76
|
-
// Error already handled
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// ch dashboard recent-changes
|
|
81
|
-
dashboard
|
|
82
|
-
.command('recent-changes')
|
|
83
|
-
.description('최근 변경 이력 조회')
|
|
84
|
-
.option('--limit <number>', '조회 건수', '10')
|
|
85
|
-
.action(async (options) => {
|
|
86
|
-
const globalOpts = program.opts();
|
|
87
|
-
try {
|
|
88
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
89
|
-
const { data } = await client.get('/dashboard/recent-changes', {
|
|
90
|
-
params: { limit: parseInt(options.limit, 10) },
|
|
91
|
-
});
|
|
92
|
-
formatOutput(data, globalOpts);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
// Error already handled
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// ch dashboard members-summary
|
|
99
|
-
dashboard
|
|
100
|
-
.command('members-summary')
|
|
101
|
-
.description('멤버 요약 조회')
|
|
102
|
-
.action(async () => {
|
|
103
|
-
const globalOpts = program.opts();
|
|
104
|
-
try {
|
|
105
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
106
|
-
const { data } = await client.get('/dashboard/members-summary');
|
|
107
|
-
formatOutput(data, globalOpts);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
// Error already handled
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { createClient } from '../client';
|
|
3
|
-
import { formatOutput, printSuccess, printError, printInfo } from '../output';
|
|
4
|
-
|
|
5
|
-
export function registerDbSchemaCommand(program: Command): void {
|
|
6
|
-
const dbSchema = program
|
|
7
|
-
.command('db-schema')
|
|
8
|
-
.description('DB 스키마 관리');
|
|
9
|
-
|
|
10
|
-
// ch db-schema get
|
|
11
|
-
dbSchema
|
|
12
|
-
.command('get')
|
|
13
|
-
.description('DB 스키마 조회')
|
|
14
|
-
.action(async () => {
|
|
15
|
-
const globalOpts = program.opts();
|
|
16
|
-
try {
|
|
17
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
18
|
-
const { data } = await client.get('/db-schema');
|
|
19
|
-
if (!data) {
|
|
20
|
-
printInfo('DB 스키마가 아직 작성되지 않았습니다.');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
formatOutput(data, globalOpts);
|
|
24
|
-
} catch (error) {
|
|
25
|
-
// Error already handled
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// ch db-schema set
|
|
30
|
-
dbSchema
|
|
31
|
-
.command('set')
|
|
32
|
-
.description('DB 스키마 생성/수정')
|
|
33
|
-
.requiredOption('--title <title>', 'DB 스키마 제목')
|
|
34
|
-
.requiredOption('--content <content>', 'DB 스키마 본문 (마크다운)')
|
|
35
|
-
.option('--db-type <dbType>', 'DB 유형 (firestore, supabase, mysql, generic)', 'generic')
|
|
36
|
-
.option('--new-version', '새 버전으로 기록')
|
|
37
|
-
.action(async (options) => {
|
|
38
|
-
const globalOpts = program.opts();
|
|
39
|
-
try {
|
|
40
|
-
if (globalOpts.dryRun) {
|
|
41
|
-
printInfo(`[DRY-RUN] DB 스키마 "${options.title}" 생성/수정 요청`);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
45
|
-
const params: any = {};
|
|
46
|
-
if (options.newVersion) params.newVersion = 'true';
|
|
47
|
-
|
|
48
|
-
const { data } = await client.put('/db-schema', {
|
|
49
|
-
title: options.title,
|
|
50
|
-
content: options.content,
|
|
51
|
-
dbType: options.dbType,
|
|
52
|
-
}, { params });
|
|
53
|
-
formatOutput(data, globalOpts);
|
|
54
|
-
printSuccess(data.message || 'DB 스키마가 저장되었습니다.');
|
|
55
|
-
} catch (error) {
|
|
56
|
-
// Error already handled
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// ch db-schema versions
|
|
61
|
-
dbSchema
|
|
62
|
-
.command('versions')
|
|
63
|
-
.description('DB 스키마 버전 이력 조회')
|
|
64
|
-
.action(async () => {
|
|
65
|
-
const globalOpts = program.opts();
|
|
66
|
-
try {
|
|
67
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
68
|
-
const { data } = await client.get('/db-schema/versions');
|
|
69
|
-
formatOutput(data, globalOpts);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
// Error already handled
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
}
|