@decencia/ch-cli 1.0.0 → 1.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/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/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/skill/SKILL.md +490 -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
|
@@ -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
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { createClient } from '../client';
|
|
3
|
-
import { formatOutput, printSuccess, printError, printInfo } from '../output';
|
|
4
|
-
import { parseCommaSeparated } from '../utils';
|
|
5
|
-
|
|
6
|
-
export function registerDevStatusCommand(program: Command): void {
|
|
7
|
-
const devStatus = program
|
|
8
|
-
.command('dev-status')
|
|
9
|
-
.description('개발현황 문서 관리');
|
|
10
|
-
|
|
11
|
-
// ch dev-status list
|
|
12
|
-
devStatus
|
|
13
|
-
.command('list')
|
|
14
|
-
.description('개발현황 문서 목록 조회')
|
|
15
|
-
.option('--sprint <sprintId>', '관련 스프린트 ID 필터')
|
|
16
|
-
.option('--spec <specId>', '관련 기능명세 ID 필터')
|
|
17
|
-
.option('--search <keyword>', '제목 검색')
|
|
18
|
-
.option('--sort <field>', '정렬 필드')
|
|
19
|
-
.option('--desc', '내림차순 정렬')
|
|
20
|
-
.option('--page <number>', '페이지 번호', '1')
|
|
21
|
-
.option('--per-page <number>', '페이지당 건수', '20')
|
|
22
|
-
.action(async (options) => {
|
|
23
|
-
const globalOpts = program.opts();
|
|
24
|
-
try {
|
|
25
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
26
|
-
const params: any = {};
|
|
27
|
-
if (options.sprint) params.relatedSprintId = options.sprint;
|
|
28
|
-
if (options.spec) params.relatedSpecId = options.spec;
|
|
29
|
-
if (options.search) params.search = options.search;
|
|
30
|
-
if (options.sort) params.sortBy = options.sort;
|
|
31
|
-
if (options.desc) params.sortOrder = 'desc';
|
|
32
|
-
params.page = parseInt(options.page, 10);
|
|
33
|
-
params.perPage = parseInt(options.perPage, 10);
|
|
34
|
-
|
|
35
|
-
const { data } = await client.get('/dev-status', { params });
|
|
36
|
-
formatOutput(data, globalOpts);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
// Error already handled
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// ch dev-status get
|
|
43
|
-
devStatus
|
|
44
|
-
.command('get <docId>')
|
|
45
|
-
.description('개발현황 문서 상세 조회')
|
|
46
|
-
.action(async (docId: string) => {
|
|
47
|
-
const globalOpts = program.opts();
|
|
48
|
-
try {
|
|
49
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
50
|
-
const { data } = await client.get(`/dev-status/${docId}`);
|
|
51
|
-
formatOutput(data, globalOpts);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
// Error already handled
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// ch dev-status create
|
|
58
|
-
devStatus
|
|
59
|
-
.command('create')
|
|
60
|
-
.description('개발현황 문서 생성')
|
|
61
|
-
.requiredOption('--title <title>', '문서 제목')
|
|
62
|
-
.option('--content <content>', '본문 (HTML)')
|
|
63
|
-
.option('--sprints <sprintIds>', '관련 스프린트 ID (쉼표 구분)')
|
|
64
|
-
.option('--specs <specIds>', '관련 기능명세 ID (쉼표 구분)')
|
|
65
|
-
.action(async (options) => {
|
|
66
|
-
const globalOpts = program.opts();
|
|
67
|
-
try {
|
|
68
|
-
if (globalOpts.dryRun) {
|
|
69
|
-
printInfo(`[DRY-RUN] 개발현황 문서 "${options.title}" 생성 요청`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
73
|
-
const body: any = {
|
|
74
|
-
title: options.title,
|
|
75
|
-
};
|
|
76
|
-
if (options.content) body.content = options.content;
|
|
77
|
-
if (options.sprints) body.relatedSprintIds = parseCommaSeparated(options.sprints);
|
|
78
|
-
if (options.specs) body.relatedSpecIds = parseCommaSeparated(options.specs);
|
|
79
|
-
|
|
80
|
-
const { data } = await client.post('/dev-status', body);
|
|
81
|
-
formatOutput(data, globalOpts);
|
|
82
|
-
printSuccess('개발현황 문서가 생성되었습니다.');
|
|
83
|
-
} catch (error) {
|
|
84
|
-
// Error already handled
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// ch dev-status update
|
|
89
|
-
devStatus
|
|
90
|
-
.command('update <docId>')
|
|
91
|
-
.description('개발현황 문서 수정')
|
|
92
|
-
.option('--title <title>', '문서 제목')
|
|
93
|
-
.option('--content <content>', '본문 (HTML)')
|
|
94
|
-
.option('--sprints <sprintIds>', '관련 스프린트 ID (쉼표 구분)')
|
|
95
|
-
.option('--specs <specIds>', '관련 기능명세 ID (쉼표 구분)')
|
|
96
|
-
.option('--change-note <note>', '변경 사유')
|
|
97
|
-
.option('--new-version', '새 버전으로 수정')
|
|
98
|
-
.action(async (docId: string, options) => {
|
|
99
|
-
const globalOpts = program.opts();
|
|
100
|
-
try {
|
|
101
|
-
if (globalOpts.dryRun) {
|
|
102
|
-
printInfo(`[DRY-RUN] 개발현황 문서 "${docId}" 수정 요청`);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
106
|
-
const body: any = {};
|
|
107
|
-
if (options.title) body.title = options.title;
|
|
108
|
-
if (options.content) body.content = options.content;
|
|
109
|
-
if (options.sprints) body.relatedSprintIds = parseCommaSeparated(options.sprints);
|
|
110
|
-
if (options.specs) body.relatedSpecIds = parseCommaSeparated(options.specs);
|
|
111
|
-
if (options.changeNote) body.changeNote = options.changeNote;
|
|
112
|
-
|
|
113
|
-
if (Object.keys(body).length === 0) {
|
|
114
|
-
printError('수정할 필드를 지정하세요.');
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const params: any = {};
|
|
119
|
-
if (options.newVersion) params.newVersion = 'true';
|
|
120
|
-
|
|
121
|
-
const { data } = await client.patch(`/dev-status/${docId}`, body, { params });
|
|
122
|
-
formatOutput(data, globalOpts);
|
|
123
|
-
printSuccess(
|
|
124
|
-
options.newVersion
|
|
125
|
-
? '개발현황 문서가 새 버전으로 수정되었습니다.'
|
|
126
|
-
: '개발현황 문서가 수정되었습니다.',
|
|
127
|
-
);
|
|
128
|
-
} catch (error) {
|
|
129
|
-
// Error already handled
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// ch dev-status delete
|
|
134
|
-
devStatus
|
|
135
|
-
.command('delete <docId>')
|
|
136
|
-
.description('개발현황 문서 삭제')
|
|
137
|
-
.action(async (docId: string) => {
|
|
138
|
-
const globalOpts = program.opts();
|
|
139
|
-
try {
|
|
140
|
-
if (globalOpts.dryRun) {
|
|
141
|
-
printInfo(`[DRY-RUN] 개발현황 문서 "${docId}" 삭제 요청`);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
145
|
-
await client.delete(`/dev-status/${docId}`);
|
|
146
|
-
printSuccess('개발현황 문서가 삭제되었습니다.');
|
|
147
|
-
} catch (error) {
|
|
148
|
-
// Error already handled
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// ch dev-status versions
|
|
153
|
-
devStatus
|
|
154
|
-
.command('versions <docId>')
|
|
155
|
-
.description('개발현황 문서 버전 이력 조회')
|
|
156
|
-
.action(async (docId: string) => {
|
|
157
|
-
const globalOpts = program.opts();
|
|
158
|
-
try {
|
|
159
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
160
|
-
const { data } = await client.get(`/dev-status/${docId}/versions`);
|
|
161
|
-
formatOutput(data, globalOpts);
|
|
162
|
-
} catch (error) {
|
|
163
|
-
// Error already handled
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
}
|
package/src/commands/init.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { createClient } from '../client';
|
|
5
|
-
import { printSuccess, printError, printInfo } from '../output';
|
|
6
|
-
import { loadConfig, findLocalProject } from '../config';
|
|
7
|
-
|
|
8
|
-
export function registerInitCommand(program: Command): void {
|
|
9
|
-
program
|
|
10
|
-
.command('init')
|
|
11
|
-
.description('현재 디렉토리에 .ch-project 파일 생성 (로컬 프로젝트 설정)')
|
|
12
|
-
.requiredOption('--project-id <projectId>', '프로젝트 ID')
|
|
13
|
-
.option('--project-name <projectName>', '프로젝트명 (표시용)')
|
|
14
|
-
.action(async (options) => {
|
|
15
|
-
const targetPath = path.join(process.cwd(), '.ch-project');
|
|
16
|
-
|
|
17
|
-
if (fs.existsSync(targetPath)) {
|
|
18
|
-
try {
|
|
19
|
-
const existing = JSON.parse(fs.readFileSync(targetPath, 'utf-8'));
|
|
20
|
-
printError(
|
|
21
|
-
`.ch-project 파일이 이미 존재합니다 (projectId: ${existing.projectId}). 덮어쓰려면 파일을 삭제 후 다시 실행하세요.`
|
|
22
|
-
);
|
|
23
|
-
} catch {
|
|
24
|
-
printError('.ch-project 파일이 이미 존재합니다. 덮어쓰려면 파일을 삭제 후 다시 실행하세요.');
|
|
25
|
-
}
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// If projectName not given, try to fetch from API (only if API key is available)
|
|
30
|
-
let projectName = options.projectName;
|
|
31
|
-
if (!projectName) {
|
|
32
|
-
const config = loadConfig();
|
|
33
|
-
if (config?.apiKey) {
|
|
34
|
-
try {
|
|
35
|
-
const client = createClient({ projectId: options.projectId });
|
|
36
|
-
const { data } = await client.get('/projects/current');
|
|
37
|
-
projectName = data.name;
|
|
38
|
-
} catch {
|
|
39
|
-
// API call failed, proceed without name
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const content: any = { projectId: options.projectId };
|
|
45
|
-
if (projectName) content.projectName = projectName;
|
|
46
|
-
|
|
47
|
-
fs.writeFileSync(targetPath, JSON.stringify(content, null, 2) + '\n', 'utf-8');
|
|
48
|
-
printSuccess(`✅ .ch-project 파일이 생성되었습니다.`);
|
|
49
|
-
printInfo(` projectId: ${options.projectId}`);
|
|
50
|
-
if (projectName) {
|
|
51
|
-
printInfo(` projectName: ${projectName}`);
|
|
52
|
-
}
|
|
53
|
-
printInfo(` 경로: ${targetPath}`);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// ch status - show current project context
|
|
57
|
-
program
|
|
58
|
-
.command('status')
|
|
59
|
-
.description('현재 프로젝트 설정 상태 확인')
|
|
60
|
-
.action(() => {
|
|
61
|
-
const config = loadConfig();
|
|
62
|
-
const local = findLocalProject();
|
|
63
|
-
|
|
64
|
-
console.log('');
|
|
65
|
-
if (local) {
|
|
66
|
-
printSuccess(`로컬 설정 (.ch-project):`);
|
|
67
|
-
printInfo(` projectId: ${local.projectId}`);
|
|
68
|
-
if (local.projectName) printInfo(` projectName: ${local.projectName}`);
|
|
69
|
-
} else {
|
|
70
|
-
printInfo('로컬 설정 (.ch-project): 없음');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
console.log('');
|
|
74
|
-
if (config) {
|
|
75
|
-
printSuccess('글로벌 설정 (~/.ch/config.json):');
|
|
76
|
-
printInfo(` apiUrl: ${config.apiUrl}`);
|
|
77
|
-
printInfo(` projectId: ${config.projectId || '(없음)'}`);
|
|
78
|
-
printInfo(` apiKey: ${config.apiKey ? '***설정됨***' : '(없음)'}`);
|
|
79
|
-
} else {
|
|
80
|
-
printInfo('글로벌 설정 (~/.ch/config.json): 없음');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
console.log('');
|
|
84
|
-
const effectiveProjectId = local?.projectId || config?.projectId;
|
|
85
|
-
const source = local ? '.ch-project (로컬)' : config?.projectId ? '~/.ch/config.json (글로벌)' : '없음';
|
|
86
|
-
if (effectiveProjectId) {
|
|
87
|
-
printSuccess(`→ 현재 사용할 projectId: ${effectiveProjectId} (출처: ${source})`);
|
|
88
|
-
} else {
|
|
89
|
-
printError('프로젝트가 설정되지 않았습니다. "ch init --project-id <id>" 또는 "ch auth login"을 실행하세요.');
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
package/src/commands/members.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { createClient } from '../client';
|
|
3
|
-
import { formatOutput, printSuccess, printError, printInfo } from '../output';
|
|
4
|
-
|
|
5
|
-
export function registerMembersCommand(program: Command): void {
|
|
6
|
-
const members = program
|
|
7
|
-
.command('members')
|
|
8
|
-
.description('멤버 관리');
|
|
9
|
-
|
|
10
|
-
// ch members list
|
|
11
|
-
members
|
|
12
|
-
.command('list')
|
|
13
|
-
.description('멤버 목록 조회')
|
|
14
|
-
.action(async () => {
|
|
15
|
-
const globalOpts = program.opts();
|
|
16
|
-
try {
|
|
17
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
18
|
-
const { data } = await client.get('/members');
|
|
19
|
-
formatOutput(data, globalOpts);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
// Error already handled
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// ch members invite
|
|
26
|
-
members
|
|
27
|
-
.command('invite')
|
|
28
|
-
.description('멤버 추가 (이메일로 사용자를 찾아 바로 권한 부여)')
|
|
29
|
-
.requiredOption('--email <email>', '추가할 사용자 이메일')
|
|
30
|
-
.requiredOption('--role <role>', '역할 (developer|client)')
|
|
31
|
-
.action(async (options) => {
|
|
32
|
-
const globalOpts = program.opts();
|
|
33
|
-
try {
|
|
34
|
-
if (globalOpts.dryRun) {
|
|
35
|
-
printInfo(`[DRY-RUN] "${options.email}" 멤버 추가 요청`);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
39
|
-
const { data } = await client.post('/members/invite', {
|
|
40
|
-
email: options.email,
|
|
41
|
-
role: options.role,
|
|
42
|
-
});
|
|
43
|
-
formatOutput(data, globalOpts);
|
|
44
|
-
printSuccess(data.message || '멤버가 추가되었습니다.');
|
|
45
|
-
} catch (error) {
|
|
46
|
-
// Error already handled
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// ch members set-role
|
|
51
|
-
members
|
|
52
|
-
.command('set-role <userId>')
|
|
53
|
-
.description('멤버 역할 변경')
|
|
54
|
-
.requiredOption('--role <role>', '새 역할 (admin|developer|client)')
|
|
55
|
-
.action(async (userId: string, options) => {
|
|
56
|
-
const globalOpts = program.opts();
|
|
57
|
-
try {
|
|
58
|
-
if (globalOpts.dryRun) {
|
|
59
|
-
printInfo(`[DRY-RUN] 멤버 "${userId}" 역할을 "${options.role}"로 변경 요청`);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
63
|
-
const { data } = await client.patch(`/members/${userId}/role`, {
|
|
64
|
-
role: options.role,
|
|
65
|
-
});
|
|
66
|
-
formatOutput(data, globalOpts);
|
|
67
|
-
printSuccess(`멤버 역할이 "${options.role}"로 변경되었습니다.`);
|
|
68
|
-
} catch (error) {
|
|
69
|
-
// Error already handled
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// ch members remove
|
|
74
|
-
members
|
|
75
|
-
.command('remove <userId>')
|
|
76
|
-
.description('멤버 제거')
|
|
77
|
-
.action(async (userId: string) => {
|
|
78
|
-
const globalOpts = program.opts();
|
|
79
|
-
try {
|
|
80
|
-
if (globalOpts.dryRun) {
|
|
81
|
-
printInfo(`[DRY-RUN] 멤버 "${userId}" 제거 요청`);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const client = createClient({ projectId: globalOpts.project });
|
|
85
|
-
await client.delete(`/members/${userId}`);
|
|
86
|
-
printSuccess('멤버가 제거되었습니다.');
|
|
87
|
-
} catch (error) {
|
|
88
|
-
// Error already handled
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
}
|