opensoma 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/.claude-plugin/README.md +145 -0
  2. package/.claude-plugin/plugin.json +23 -0
  3. package/.github/workflows/release.yml +86 -0
  4. package/.oxfmtrc.json +9 -0
  5. package/.oxlintrc.json +4 -0
  6. package/AGENTS.md +78 -0
  7. package/README.md +249 -0
  8. package/bun.lock +297 -0
  9. package/bunfig.toml +2 -0
  10. package/dist/package.json +56 -0
  11. package/dist/src/cli.d.ts +5 -0
  12. package/dist/src/cli.d.ts.map +1 -0
  13. package/dist/src/cli.js +39 -0
  14. package/dist/src/cli.js.map +1 -0
  15. package/dist/src/client.d.ts +98 -0
  16. package/dist/src/client.d.ts.map +1 -0
  17. package/dist/src/client.js +141 -0
  18. package/dist/src/client.js.map +1 -0
  19. package/dist/src/commands/auth.d.ts +3 -0
  20. package/dist/src/commands/auth.d.ts.map +1 -0
  21. package/dist/src/commands/auth.js +125 -0
  22. package/dist/src/commands/auth.js.map +1 -0
  23. package/dist/src/commands/dashboard.d.ts +3 -0
  24. package/dist/src/commands/dashboard.d.ts.map +1 -0
  25. package/dist/src/commands/dashboard.js +33 -0
  26. package/dist/src/commands/dashboard.js.map +1 -0
  27. package/dist/src/commands/event.d.ts +3 -0
  28. package/dist/src/commands/event.d.ts.map +1 -0
  29. package/dist/src/commands/event.js +58 -0
  30. package/dist/src/commands/event.js.map +1 -0
  31. package/dist/src/commands/helpers.d.ts +3 -0
  32. package/dist/src/commands/helpers.d.ts.map +1 -0
  33. package/dist/src/commands/helpers.js +12 -0
  34. package/dist/src/commands/helpers.js.map +1 -0
  35. package/dist/src/commands/index.d.ts +9 -0
  36. package/dist/src/commands/index.d.ts.map +1 -0
  37. package/dist/src/commands/index.js +9 -0
  38. package/dist/src/commands/index.js.map +1 -0
  39. package/dist/src/commands/member.d.ts +3 -0
  40. package/dist/src/commands/member.d.ts.map +1 -0
  41. package/dist/src/commands/member.js +23 -0
  42. package/dist/src/commands/member.js.map +1 -0
  43. package/dist/src/commands/mentoring.d.ts +3 -0
  44. package/dist/src/commands/mentoring.d.ts.map +1 -0
  45. package/dist/src/commands/mentoring.js +154 -0
  46. package/dist/src/commands/mentoring.js.map +1 -0
  47. package/dist/src/commands/notice.d.ts +3 -0
  48. package/dist/src/commands/notice.d.ts.map +1 -0
  49. package/dist/src/commands/notice.js +42 -0
  50. package/dist/src/commands/notice.js.map +1 -0
  51. package/dist/src/commands/room.d.ts +3 -0
  52. package/dist/src/commands/room.d.ts.map +1 -0
  53. package/dist/src/commands/room.js +79 -0
  54. package/dist/src/commands/room.js.map +1 -0
  55. package/dist/src/commands/team.d.ts +3 -0
  56. package/dist/src/commands/team.d.ts.map +1 -0
  57. package/dist/src/commands/team.js +20 -0
  58. package/dist/src/commands/team.js.map +1 -0
  59. package/dist/src/constants.d.ts +43 -0
  60. package/dist/src/constants.d.ts.map +1 -0
  61. package/dist/src/constants.js +62 -0
  62. package/dist/src/constants.js.map +1 -0
  63. package/dist/src/credential-manager.d.ts +15 -0
  64. package/dist/src/credential-manager.d.ts.map +1 -0
  65. package/dist/src/credential-manager.js +40 -0
  66. package/dist/src/credential-manager.js.map +1 -0
  67. package/dist/src/formatters.d.ts +15 -0
  68. package/dist/src/formatters.d.ts.map +1 -0
  69. package/dist/src/formatters.js +382 -0
  70. package/dist/src/formatters.js.map +1 -0
  71. package/dist/src/http.d.ts +32 -0
  72. package/dist/src/http.d.ts.map +1 -0
  73. package/dist/src/http.js +143 -0
  74. package/dist/src/http.js.map +1 -0
  75. package/dist/src/index.d.ts +7 -0
  76. package/dist/src/index.d.ts.map +1 -0
  77. package/dist/src/index.js +6 -0
  78. package/dist/src/index.js.map +1 -0
  79. package/dist/src/shared/utils/error-handler.d.ts +2 -0
  80. package/dist/src/shared/utils/error-handler.d.ts.map +1 -0
  81. package/dist/src/shared/utils/error-handler.js +7 -0
  82. package/dist/src/shared/utils/error-handler.js.map +1 -0
  83. package/dist/src/shared/utils/mentoring-params.d.ts +15 -0
  84. package/dist/src/shared/utils/mentoring-params.d.ts.map +1 -0
  85. package/dist/src/shared/utils/mentoring-params.js +39 -0
  86. package/dist/src/shared/utils/mentoring-params.js.map +1 -0
  87. package/dist/src/shared/utils/output.d.ts +2 -0
  88. package/dist/src/shared/utils/output.d.ts.map +1 -0
  89. package/dist/src/shared/utils/output.js +4 -0
  90. package/dist/src/shared/utils/output.js.map +1 -0
  91. package/dist/src/shared/utils/stderr.d.ts +5 -0
  92. package/dist/src/shared/utils/stderr.d.ts.map +1 -0
  93. package/dist/src/shared/utils/stderr.js +19 -0
  94. package/dist/src/shared/utils/stderr.js.map +1 -0
  95. package/dist/src/shared/utils/swmaestro.d.ts +33 -0
  96. package/dist/src/shared/utils/swmaestro.d.ts.map +1 -0
  97. package/dist/src/shared/utils/swmaestro.js +164 -0
  98. package/dist/src/shared/utils/swmaestro.js.map +1 -0
  99. package/dist/src/token-extractor.d.ts +23 -0
  100. package/dist/src/token-extractor.d.ts.map +1 -0
  101. package/dist/src/token-extractor.js +163 -0
  102. package/dist/src/token-extractor.js.map +1 -0
  103. package/dist/src/types.d.ts +176 -0
  104. package/dist/src/types.d.ts.map +1 -0
  105. package/dist/src/types.js +110 -0
  106. package/dist/src/types.js.map +1 -0
  107. package/e2e/.gitkeep +0 -0
  108. package/package.json +56 -0
  109. package/scripts/postbuild.ts +11 -0
  110. package/scripts/prepublish.ts +9 -0
  111. package/scripts/test.ts +82 -0
  112. package/skills/opensoma/SKILL.md +345 -0
  113. package/skills/opensoma/references/common-patterns.md +182 -0
  114. package/skills/opensoma/references/output-format.md +130 -0
  115. package/src/cli.ts +57 -0
  116. package/src/client.test.ts +210 -0
  117. package/src/client.ts +264 -0
  118. package/src/commands/auth.ts +153 -0
  119. package/src/commands/dashboard.ts +39 -0
  120. package/src/commands/event.ts +74 -0
  121. package/src/commands/helpers.ts +12 -0
  122. package/src/commands/index.ts +8 -0
  123. package/src/commands/member.ts +29 -0
  124. package/src/commands/mentoring.ts +209 -0
  125. package/src/commands/notice.ts +56 -0
  126. package/src/commands/room.ts +102 -0
  127. package/src/commands/team.ts +26 -0
  128. package/src/constants.ts +70 -0
  129. package/src/credential-manager.test.ts +66 -0
  130. package/src/credential-manager.ts +52 -0
  131. package/src/formatters.test.ts +382 -0
  132. package/src/formatters.ts +489 -0
  133. package/src/http.test.ts +152 -0
  134. package/src/http.ts +196 -0
  135. package/src/index.ts +6 -0
  136. package/src/shared/utils/error-handler.ts +7 -0
  137. package/src/shared/utils/mentoring-params.test.ts +112 -0
  138. package/src/shared/utils/mentoring-params.ts +57 -0
  139. package/src/shared/utils/output.ts +3 -0
  140. package/src/shared/utils/stderr.ts +23 -0
  141. package/src/shared/utils/swmaestro.ts +218 -0
  142. package/src/token-extractor.test.ts +119 -0
  143. package/src/token-extractor.ts +205 -0
  144. package/src/types.test.ts +172 -0
  145. package/src/types.ts +134 -0
  146. package/tsconfig.json +38 -0
@@ -0,0 +1,39 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { MENU_NO } from '@/constants'
4
+ import * as formatters from '@/formatters'
5
+ import { handleError } from '@/shared/utils/error-handler'
6
+ import { formatOutput } from '@/shared/utils/output'
7
+
8
+ import { getHttpOrExit } from './helpers'
9
+ import { buildMentoringListParams } from '@/shared/utils/mentoring-params'
10
+
11
+ type ShowOptions = { pretty?: boolean }
12
+
13
+ async function showAction(options: ShowOptions): Promise<void> {
14
+ try {
15
+ const http = await getHttpOrExit()
16
+ const user = (await http.checkLogin()) ?? undefined
17
+ const search = { field: 'author' as const, value: '@me', me: true }
18
+ const [dashboardHtml, mentoringHtml] = await Promise.all([
19
+ http.get('/mypage/myMain/dashboard.do', { menuNo: MENU_NO.DASHBOARD }),
20
+ http.get('/mypage/mentoLec/list.do', buildMentoringListParams({ search, user })),
21
+ ])
22
+ const dashboard = formatters.parseDashboard(dashboardHtml)
23
+ const myMentoring = formatters.parseMentoringList(mentoringHtml)
24
+ dashboard.mentoringSessions = myMentoring.map((item) => ({
25
+ title: item.title,
26
+ url: `/mypage/mentoLec/view.do?qustnrSn=${item.id}`,
27
+ status: item.status,
28
+ }))
29
+ console.log(formatOutput(dashboard, options.pretty))
30
+ } catch (error) {
31
+ handleError(error)
32
+ }
33
+ }
34
+
35
+ export const dashboardCommand = new Command('dashboard')
36
+ .description('Show dashboard information')
37
+ .addCommand(
38
+ new Command('show').description('Show dashboard').option('--pretty', 'Pretty print JSON output').action(showAction),
39
+ )
@@ -0,0 +1,74 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { MENU_NO } from '@/constants'
4
+ import * as formatters from '@/formatters'
5
+ import { handleError } from '@/shared/utils/error-handler'
6
+ import { formatOutput } from '@/shared/utils/output'
7
+ import { buildApplicationPayload, parseEventDetail } from '@/shared/utils/swmaestro'
8
+
9
+ import { getHttpOrExit } from './helpers'
10
+
11
+ type ListOptions = { page?: string; pretty?: boolean }
12
+ type GetOptions = { pretty?: boolean }
13
+
14
+ async function listAction(options: ListOptions): Promise<void> {
15
+ try {
16
+ const http = await getHttpOrExit()
17
+ const html = await http.get('/mypage/applicants/list.do', {
18
+ menuNo: MENU_NO.EVENT,
19
+ ...(options.page ? { pageIndex: options.page } : {}),
20
+ })
21
+ console.log(
22
+ formatOutput(
23
+ { items: formatters.parseEventList(html), pagination: formatters.parsePagination(html) },
24
+ options.pretty,
25
+ ),
26
+ )
27
+ } catch (error) {
28
+ handleError(error)
29
+ }
30
+ }
31
+
32
+ async function getAction(id: string, options: GetOptions): Promise<void> {
33
+ try {
34
+ const http = await getHttpOrExit()
35
+ const html = await http.get('/mypage/applicants/view.do', { menuNo: MENU_NO.EVENT, bbsId: id })
36
+ console.log(formatOutput(parseEventDetail(html), options.pretty))
37
+ } catch (error) {
38
+ handleError(error)
39
+ }
40
+ }
41
+
42
+ async function applyAction(id: string, options: GetOptions): Promise<void> {
43
+ try {
44
+ const http = await getHttpOrExit()
45
+ await http.post('/application/application/application.do', buildApplicationPayload(Number.parseInt(id, 10)))
46
+ console.log(formatOutput({ ok: true }, options.pretty))
47
+ } catch (error) {
48
+ handleError(error)
49
+ }
50
+ }
51
+
52
+ export const eventCommand = new Command('event')
53
+ .description('Browse and apply to events')
54
+ .addCommand(
55
+ new Command('list')
56
+ .description('List events')
57
+ .option('--page <n>', 'Page number')
58
+ .option('--pretty', 'Pretty print JSON output')
59
+ .action(listAction),
60
+ )
61
+ .addCommand(
62
+ new Command('get')
63
+ .description('Get event detail')
64
+ .argument('<id>')
65
+ .option('--pretty', 'Pretty print JSON output')
66
+ .action(getAction),
67
+ )
68
+ .addCommand(
69
+ new Command('apply')
70
+ .description('Apply to an event')
71
+ .argument('<id>')
72
+ .option('--pretty', 'Pretty print JSON output')
73
+ .action(applyAction),
74
+ )
@@ -0,0 +1,12 @@
1
+ import { CredentialManager } from '@/credential-manager'
2
+ import { SomaHttp } from '@/http'
3
+
4
+ export async function getHttpOrExit(): Promise<SomaHttp> {
5
+ const manager = new CredentialManager()
6
+ const creds = await manager.getCredentials()
7
+ if (!creds) {
8
+ console.error(JSON.stringify({ error: 'Not logged in. Run: opensoma auth login' }))
9
+ process.exit(1)
10
+ }
11
+ return new SomaHttp({ sessionCookie: creds.sessionCookie, csrfToken: creds.csrfToken })
12
+ }
@@ -0,0 +1,8 @@
1
+ export { authCommand } from './auth'
2
+ export { mentoringCommand } from './mentoring'
3
+ export { roomCommand } from './room'
4
+ export { dashboardCommand } from './dashboard'
5
+ export { noticeCommand } from './notice'
6
+ export { teamCommand } from './team'
7
+ export { memberCommand } from './member'
8
+ export { eventCommand } from './event'
@@ -0,0 +1,29 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { MENU_NO } from '@/constants'
4
+ import * as formatters from '@/formatters'
5
+ import { handleError } from '@/shared/utils/error-handler'
6
+ import { formatOutput } from '@/shared/utils/output'
7
+
8
+ import { getHttpOrExit } from './helpers'
9
+
10
+ type ShowOptions = { pretty?: boolean }
11
+
12
+ async function showAction(options: ShowOptions): Promise<void> {
13
+ try {
14
+ const http = await getHttpOrExit()
15
+ const html = await http.get('/mypage/myInfo/forUpdateMy.do', { menuNo: MENU_NO.MEMBER_INFO })
16
+ console.log(formatOutput(formatters.parseMemberInfo(html), options.pretty))
17
+ } catch (error) {
18
+ handleError(error)
19
+ }
20
+ }
21
+
22
+ export const memberCommand = new Command('member')
23
+ .description('Show member information')
24
+ .addCommand(
25
+ new Command('show')
26
+ .description('Show member profile')
27
+ .option('--pretty', 'Pretty print JSON output')
28
+ .action(showAction),
29
+ )
@@ -0,0 +1,209 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { MENU_NO } from '@/constants'
4
+ import * as formatters from '@/formatters'
5
+ import { handleError } from '@/shared/utils/error-handler'
6
+ import { formatOutput } from '@/shared/utils/output'
7
+ import {
8
+ buildApplicationPayload,
9
+ buildCancelApplicationPayload,
10
+ buildDeleteMentoringPayload,
11
+ buildMentoringPayload,
12
+ } from '@/shared/utils/swmaestro'
13
+
14
+ import { getHttpOrExit } from './helpers'
15
+ import { buildMentoringListParams, parseSearchQuery } from '@/shared/utils/mentoring-params'
16
+
17
+ type ListOptions = { status?: string; type?: string; search?: string; page?: string; pretty?: boolean }
18
+ type GetOptions = { pretty?: boolean }
19
+ type CreateOptions = {
20
+ title: string
21
+ type: 'public' | 'lecture'
22
+ date: string
23
+ start: string
24
+ end: string
25
+ venue: string
26
+ maxAttendees?: string
27
+ regStart?: string
28
+ regEnd?: string
29
+ content?: string
30
+ pretty?: boolean
31
+ }
32
+ type CancelOptions = { applySn: string; qustnrSn: string; pretty?: boolean }
33
+ type HistoryOptions = { page?: string; pretty?: boolean }
34
+
35
+ async function listAction(options: ListOptions): Promise<void> {
36
+ try {
37
+ const http = await getHttpOrExit()
38
+ const search = options.search ? parseSearchQuery(options.search) : undefined
39
+ const user = search?.me ? (await http.checkLogin()) ?? undefined : undefined
40
+ const html = await http.get('/mypage/mentoLec/list.do', buildMentoringListParams({
41
+ status: options.status,
42
+ type: options.type,
43
+ page: options.page,
44
+ search,
45
+ user,
46
+ }))
47
+ console.log(
48
+ formatOutput(
49
+ { items: formatters.parseMentoringList(html), pagination: formatters.parsePagination(html) },
50
+ options.pretty,
51
+ ),
52
+ )
53
+ } catch (error) {
54
+ handleError(error)
55
+ }
56
+ }
57
+
58
+ async function getAction(id: string, options: GetOptions): Promise<void> {
59
+ try {
60
+ const http = await getHttpOrExit()
61
+ const html = await http.get('/mypage/mentoLec/view.do', { menuNo: MENU_NO.MENTORING, qustnrSn: id })
62
+ console.log(formatOutput(formatters.parseMentoringDetail(html, Number.parseInt(id, 10)), options.pretty))
63
+ } catch (error) {
64
+ handleError(error)
65
+ }
66
+ }
67
+
68
+ async function createAction(options: CreateOptions): Promise<void> {
69
+ try {
70
+ const http = await getHttpOrExit()
71
+ await http.post(
72
+ '/mypage/mentoLec/insert.do',
73
+ buildMentoringPayload({
74
+ title: options.title,
75
+ type: options.type,
76
+ date: options.date,
77
+ startTime: options.start,
78
+ endTime: options.end,
79
+ venue: options.venue,
80
+ maxAttendees: options.maxAttendees ? Number.parseInt(options.maxAttendees, 10) : undefined,
81
+ regStart: options.regStart,
82
+ regEnd: options.regEnd,
83
+ content: options.content,
84
+ }),
85
+ )
86
+ console.log(formatOutput({ ok: true }, options.pretty))
87
+ } catch (error) {
88
+ handleError(error)
89
+ }
90
+ }
91
+
92
+ async function deleteAction(id: string, options: GetOptions): Promise<void> {
93
+ try {
94
+ const http = await getHttpOrExit()
95
+ await http.post('/mypage/mentoLec/delete.do', buildDeleteMentoringPayload(Number.parseInt(id, 10)))
96
+ console.log(formatOutput({ ok: true }, options.pretty))
97
+ } catch (error) {
98
+ handleError(error)
99
+ }
100
+ }
101
+
102
+ async function applyAction(id: string, options: GetOptions): Promise<void> {
103
+ try {
104
+ const http = await getHttpOrExit()
105
+ await http.post('/application/application/application.do', buildApplicationPayload(Number.parseInt(id, 10)))
106
+ console.log(formatOutput({ ok: true }, options.pretty))
107
+ } catch (error) {
108
+ handleError(error)
109
+ }
110
+ }
111
+
112
+ async function cancelAction(options: CancelOptions): Promise<void> {
113
+ try {
114
+ const http = await getHttpOrExit()
115
+ await http.post(
116
+ '/mypage/userAnswer/cancel.do',
117
+ buildCancelApplicationPayload({
118
+ applySn: Number.parseInt(options.applySn, 10),
119
+ qustnrSn: Number.parseInt(options.qustnrSn, 10),
120
+ }),
121
+ )
122
+ console.log(formatOutput({ ok: true }, options.pretty))
123
+ } catch (error) {
124
+ handleError(error)
125
+ }
126
+ }
127
+
128
+ async function historyAction(options: HistoryOptions): Promise<void> {
129
+ try {
130
+ const http = await getHttpOrExit()
131
+ const html = await http.get('/mypage/userAnswer/history.do', {
132
+ menuNo: MENU_NO.APPLICATION_HISTORY,
133
+ ...(options.page ? { pageIndex: options.page } : {}),
134
+ })
135
+ console.log(
136
+ formatOutput(
137
+ { items: formatters.parseApplicationHistory(html), pagination: formatters.parsePagination(html) },
138
+ options.pretty,
139
+ ),
140
+ )
141
+ } catch (error) {
142
+ handleError(error)
143
+ }
144
+ }
145
+
146
+ export const mentoringCommand = new Command('mentoring')
147
+ .description('Manage mentoring sessions')
148
+ .addCommand(
149
+ new Command('list')
150
+ .description('List mentoring sessions')
151
+ .option('--status <status>', 'Status filter (open|closed)')
152
+ .option('--type <type>', 'Type filter (public|lecture)')
153
+ .option('--search <query>', 'Search (e.g. "keyword", "author:@me", "content:text")')
154
+ .option('--page <n>', 'Page number')
155
+ .option('--pretty', 'Pretty print JSON output')
156
+ .action(listAction),
157
+ )
158
+ .addCommand(
159
+ new Command('get')
160
+ .description('Get mentoring detail')
161
+ .argument('<id>')
162
+ .option('--pretty', 'Pretty print JSON output')
163
+ .action(getAction),
164
+ )
165
+ .addCommand(
166
+ new Command('create')
167
+ .description('Create a mentoring session')
168
+ .requiredOption('--title <title>', 'Title')
169
+ .requiredOption('--type <type>', 'Mentoring type (public|lecture)')
170
+ .requiredOption('--date <date>', 'Session date')
171
+ .requiredOption('--start <time>', 'Start time')
172
+ .requiredOption('--end <time>', 'End time')
173
+ .requiredOption('--venue <venue>', 'Venue')
174
+ .option('--max-attendees <count>', 'Maximum attendees')
175
+ .option('--reg-start <date>', 'Registration start date')
176
+ .option('--reg-end <date>', 'Registration end date')
177
+ .option('--content <html>', 'HTML content')
178
+ .option('--pretty', 'Pretty print JSON output')
179
+ .action(createAction),
180
+ )
181
+ .addCommand(
182
+ new Command('delete')
183
+ .description('Delete a mentoring session')
184
+ .argument('<id>')
185
+ .option('--pretty', 'Pretty print JSON output')
186
+ .action(deleteAction),
187
+ )
188
+ .addCommand(
189
+ new Command('apply')
190
+ .description('Apply to a mentoring session')
191
+ .argument('<id>')
192
+ .option('--pretty', 'Pretty print JSON output')
193
+ .action(applyAction),
194
+ )
195
+ .addCommand(
196
+ new Command('cancel')
197
+ .description('Cancel a mentoring application')
198
+ .requiredOption('--apply-sn <id>', 'Application serial number')
199
+ .requiredOption('--qustnr-sn <id>', 'Mentoring serial number')
200
+ .option('--pretty', 'Pretty print JSON output')
201
+ .action(cancelAction),
202
+ )
203
+ .addCommand(
204
+ new Command('history')
205
+ .description('List mentoring application history')
206
+ .option('--page <n>', 'Page number')
207
+ .option('--pretty', 'Pretty print JSON output')
208
+ .action(historyAction),
209
+ )
@@ -0,0 +1,56 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { MENU_NO } from '@/constants'
4
+ import * as formatters from '@/formatters'
5
+ import { handleError } from '@/shared/utils/error-handler'
6
+ import { formatOutput } from '@/shared/utils/output'
7
+
8
+ import { getHttpOrExit } from './helpers'
9
+
10
+ type ListOptions = { page?: string; pretty?: boolean }
11
+ type GetOptions = { pretty?: boolean }
12
+
13
+ async function listAction(options: ListOptions): Promise<void> {
14
+ try {
15
+ const http = await getHttpOrExit()
16
+ const html = await http.get('/mypage/myNotice/list.do', {
17
+ menuNo: MENU_NO.NOTICE,
18
+ ...(options.page ? { pageIndex: options.page } : {}),
19
+ })
20
+ console.log(
21
+ formatOutput(
22
+ { items: formatters.parseNoticeList(html), pagination: formatters.parsePagination(html) },
23
+ options.pretty,
24
+ ),
25
+ )
26
+ } catch (error) {
27
+ handleError(error)
28
+ }
29
+ }
30
+
31
+ async function getAction(id: string, options: GetOptions): Promise<void> {
32
+ try {
33
+ const http = await getHttpOrExit()
34
+ const html = await http.get('/mypage/myNotice/view.do', { menuNo: MENU_NO.NOTICE, nttId: id })
35
+ console.log(formatOutput(formatters.parseNoticeDetail(html, Number.parseInt(id, 10)), options.pretty))
36
+ } catch (error) {
37
+ handleError(error)
38
+ }
39
+ }
40
+
41
+ export const noticeCommand = new Command('notice')
42
+ .description('Browse notices')
43
+ .addCommand(
44
+ new Command('list')
45
+ .description('List notices')
46
+ .option('--page <n>', 'Page number')
47
+ .option('--pretty', 'Pretty print JSON output')
48
+ .action(listAction),
49
+ )
50
+ .addCommand(
51
+ new Command('get')
52
+ .description('Get notice detail')
53
+ .argument('<id>')
54
+ .option('--pretty', 'Pretty print JSON output')
55
+ .action(getAction),
56
+ )
@@ -0,0 +1,102 @@
1
+ import { Command } from 'commander'
2
+
3
+ import * as formatters from '@/formatters'
4
+ import { handleError } from '@/shared/utils/error-handler'
5
+ import { formatOutput } from '@/shared/utils/output'
6
+ import { buildRoomReservationPayload, resolveRoomId } from '@/shared/utils/swmaestro'
7
+
8
+ import { getHttpOrExit } from './helpers'
9
+
10
+ type ListOptions = { date?: string; room?: string; pretty?: boolean }
11
+ type AvailableOptions = { date: string; pretty?: boolean }
12
+ type ReserveOptions = {
13
+ room: string
14
+ date: string
15
+ slots: string
16
+ title: string
17
+ attendees?: string
18
+ notes?: string
19
+ pretty?: boolean
20
+ }
21
+
22
+ async function listAction(options: ListOptions): Promise<void> {
23
+ try {
24
+ const http = await getHttpOrExit()
25
+ const html = await http.post('/mypage/officeMng/list.do', {
26
+ menuNo: '200058',
27
+ sdate: options.date ?? new Date().toISOString().slice(0, 10),
28
+ searchItemId: options.room ? String(resolveRoomId(options.room)) : '',
29
+ })
30
+ console.log(formatOutput(formatters.parseRoomList(html), options.pretty))
31
+ } catch (error) {
32
+ handleError(error)
33
+ }
34
+ }
35
+
36
+ async function availableAction(roomId: string, options: AvailableOptions): Promise<void> {
37
+ try {
38
+ const http = await getHttpOrExit()
39
+ const html = await http.post('/mypage/officeMng/rentTime.do', {
40
+ viewType: 'CONTBODY',
41
+ itemId: String(resolveRoomId(roomId)),
42
+ rentDt: options.date,
43
+ })
44
+ console.log(formatOutput(formatters.parseRoomSlots(html), options.pretty))
45
+ } catch (error) {
46
+ handleError(error)
47
+ }
48
+ }
49
+
50
+ async function reserveAction(options: ReserveOptions): Promise<void> {
51
+ try {
52
+ const http = await getHttpOrExit()
53
+ await http.post(
54
+ '/mypage/itemRent/insert.do',
55
+ buildRoomReservationPayload({
56
+ roomId: resolveRoomId(options.room),
57
+ date: options.date,
58
+ slots: options.slots
59
+ .split(',')
60
+ .map((slot) => slot.trim())
61
+ .filter(Boolean),
62
+ title: options.title,
63
+ attendees: options.attendees ? Number.parseInt(options.attendees, 10) : undefined,
64
+ notes: options.notes,
65
+ }),
66
+ )
67
+ console.log(formatOutput({ ok: true }, options.pretty))
68
+ } catch (error) {
69
+ handleError(error)
70
+ }
71
+ }
72
+
73
+ export const roomCommand = new Command('room')
74
+ .description('Manage room reservations')
75
+ .addCommand(
76
+ new Command('list')
77
+ .description('List rooms')
78
+ .option('--date <date>', 'Reservation date')
79
+ .option('--room <room>', 'Room filter')
80
+ .option('--pretty', 'Pretty print JSON output')
81
+ .action(listAction),
82
+ )
83
+ .addCommand(
84
+ new Command('available')
85
+ .description('Show available time slots')
86
+ .argument('<roomId>')
87
+ .requiredOption('--date <date>', 'Reservation date')
88
+ .option('--pretty', 'Pretty print JSON output')
89
+ .action(availableAction),
90
+ )
91
+ .addCommand(
92
+ new Command('reserve')
93
+ .description('Reserve a room')
94
+ .requiredOption('--room <room>', 'Room ID or short name')
95
+ .requiredOption('--date <date>', 'Reservation date')
96
+ .requiredOption('--slots <slots>', 'Comma-separated HH:MM values')
97
+ .requiredOption('--title <title>', 'Reservation title')
98
+ .option('--attendees <count>', 'Number of attendees')
99
+ .option('--notes <notes>', 'Reservation notes')
100
+ .option('--pretty', 'Pretty print JSON output')
101
+ .action(reserveAction),
102
+ )
@@ -0,0 +1,26 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { MENU_NO } from '@/constants'
4
+ import * as formatters from '@/formatters'
5
+ import { handleError } from '@/shared/utils/error-handler'
6
+ import { formatOutput } from '@/shared/utils/output'
7
+
8
+ import { getHttpOrExit } from './helpers'
9
+
10
+ type ShowOptions = { pretty?: boolean }
11
+
12
+ async function showAction(options: ShowOptions): Promise<void> {
13
+ try {
14
+ const http = await getHttpOrExit()
15
+ const html = await http.get('/mypage/myTeam/team.do', { menuNo: MENU_NO.TEAM })
16
+ console.log(formatOutput(formatters.parseTeamInfo(html), options.pretty))
17
+ } catch (error) {
18
+ handleError(error)
19
+ }
20
+ }
21
+
22
+ export const teamCommand = new Command('team')
23
+ .description('Show team information')
24
+ .addCommand(
25
+ new Command('show').description('Show team').option('--pretty', 'Pretty print JSON output').action(showAction),
26
+ )
@@ -0,0 +1,70 @@
1
+ export const BASE_URL = 'https://www.swmaestro.ai/sw'
2
+
3
+ export const MENU_NO = {
4
+ LOGIN: '200025',
5
+ DASHBOARD: '200026',
6
+ NOTICE: '200038',
7
+ TEAM: '200093',
8
+ MENTORING: '200046',
9
+ EVENT: '200045',
10
+ APPLICATION_HISTORY: '200047',
11
+ ROOM: '200058',
12
+ MEMBER_INFO: '200036',
13
+ } as const
14
+
15
+ export const ROOM_IDS: Record<string, number> = {
16
+ A1: 17,
17
+ A2: 18,
18
+ A3: 19,
19
+ A4: 20,
20
+ A5: 21,
21
+ A6: 22,
22
+ A7: 23,
23
+ A8: 24,
24
+ }
25
+
26
+ export const VENUES = {
27
+ TOZ_GWANGHWAMUN: '광화문점',
28
+ TOZ_YANGJAE: '양재점',
29
+ TOZ_GANGNAM_CONFERENCE_CENTER: '강남컨퍼런스센터점',
30
+ TOZ_KONKUK: '건대점',
31
+ TOZ_GANGNAM_TOWER: '강남역토즈타워점',
32
+ TOZ_SEOLLEUNG: '선릉점',
33
+ TOZ_YEOKSAM: '역삼점',
34
+ TOZ_HONGDAE: '홍대점',
35
+ TOZ_SINCHON_BUSINESS_CENTER: '신촌비즈니스센터점',
36
+ ONLINE_WEBEX: '온라인(Webex)',
37
+ SPACE_A1: '스페이스 A1',
38
+ SPACE_A2: '스페이스 A2',
39
+ SPACE_A3: '스페이스 A3',
40
+ SPACE_A4: '스페이스 A4',
41
+ SPACE_A5: '스페이스 A5',
42
+ SPACE_A6: '스페이스 A6',
43
+ SPACE_A7: '스페이스 A7',
44
+ SPACE_A8: '스페이스 A8',
45
+ SPACE_M1: '스페이스 M1',
46
+ SPACE_M2: '스페이스 M2',
47
+ SPACE_S: '스페이스 S',
48
+ } as const
49
+
50
+ export const REPORT_CD = {
51
+ PUBLIC_MENTORING: 'MRC010',
52
+ MENTOR_LECTURE: 'MRC020',
53
+ REGULAR_MENTORING: 'MRC990',
54
+ } as const
55
+
56
+ export const TIME_SLOTS = createTimeSlots()
57
+
58
+ function createTimeSlots(): string[] {
59
+ const slots: string[] = []
60
+
61
+ for (let hour = 9; hour <= 23; hour += 1) {
62
+ slots.push(formatTime(hour, 0), formatTime(hour, 30))
63
+ }
64
+
65
+ return slots
66
+ }
67
+
68
+ function formatTime(hour: number, minute: number): string {
69
+ return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`
70
+ }