opensoma 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/package.json +5 -1
  2. package/dist/src/agent-browser-launcher.d.ts +43 -0
  3. package/dist/src/agent-browser-launcher.d.ts.map +1 -0
  4. package/dist/src/agent-browser-launcher.js +97 -0
  5. package/dist/src/agent-browser-launcher.js.map +1 -0
  6. package/dist/src/cli.d.ts.map +1 -1
  7. package/dist/src/cli.js +3 -2
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/client.d.ts +30 -7
  10. package/dist/src/client.d.ts.map +1 -1
  11. package/dist/src/client.js +218 -52
  12. package/dist/src/client.js.map +1 -1
  13. package/dist/src/commands/agent-browser.d.ts +3 -0
  14. package/dist/src/commands/agent-browser.d.ts.map +1 -0
  15. package/dist/src/commands/agent-browser.js +27 -0
  16. package/dist/src/commands/agent-browser.js.map +1 -0
  17. package/dist/src/commands/auth.d.ts +1 -1
  18. package/dist/src/commands/auth.d.ts.map +1 -1
  19. package/dist/src/commands/auth.js +4 -2
  20. package/dist/src/commands/auth.js.map +1 -1
  21. package/dist/src/commands/dashboard.d.ts +13 -0
  22. package/dist/src/commands/dashboard.d.ts.map +1 -1
  23. package/dist/src/commands/dashboard.js +10 -18
  24. package/dist/src/commands/dashboard.js.map +1 -1
  25. package/dist/src/commands/helpers.d.ts +1 -1
  26. package/dist/src/commands/helpers.d.ts.map +1 -1
  27. package/dist/src/commands/helpers.js +2 -2
  28. package/dist/src/commands/helpers.js.map +1 -1
  29. package/dist/src/commands/index.d.ts +2 -1
  30. package/dist/src/commands/index.d.ts.map +1 -1
  31. package/dist/src/commands/index.js +2 -1
  32. package/dist/src/commands/index.js.map +1 -1
  33. package/dist/src/commands/mentoring.d.ts.map +1 -1
  34. package/dist/src/commands/mentoring.js +54 -29
  35. package/dist/src/commands/mentoring.js.map +1 -1
  36. package/dist/src/commands/notice.d.ts.map +1 -1
  37. package/dist/src/commands/notice.js +2 -1
  38. package/dist/src/commands/notice.js.map +1 -1
  39. package/dist/src/commands/report.d.ts.map +1 -1
  40. package/dist/src/commands/report.js +4 -2
  41. package/dist/src/commands/report.js.map +1 -1
  42. package/dist/src/commands/room.d.ts.map +1 -1
  43. package/dist/src/commands/room.js +125 -2
  44. package/dist/src/commands/room.js.map +1 -1
  45. package/dist/src/commands/schedule.d.ts +3 -0
  46. package/dist/src/commands/schedule.d.ts.map +1 -0
  47. package/dist/src/commands/schedule.js +27 -0
  48. package/dist/src/commands/schedule.js.map +1 -0
  49. package/dist/src/commands/team.d.ts.map +1 -1
  50. package/dist/src/commands/team.js +55 -4
  51. package/dist/src/commands/team.js.map +1 -1
  52. package/dist/src/constants.d.ts +5 -5
  53. package/dist/src/constants.d.ts.map +1 -1
  54. package/dist/src/constants.js +20 -8
  55. package/dist/src/constants.js.map +1 -1
  56. package/dist/src/credential-manager.d.ts +9 -0
  57. package/dist/src/credential-manager.d.ts.map +1 -1
  58. package/dist/src/credential-manager.js +24 -0
  59. package/dist/src/credential-manager.js.map +1 -1
  60. package/dist/src/formatters.d.ts +11 -3
  61. package/dist/src/formatters.d.ts.map +1 -1
  62. package/dist/src/formatters.js +281 -52
  63. package/dist/src/formatters.js.map +1 -1
  64. package/dist/src/http.d.ts +8 -0
  65. package/dist/src/http.d.ts.map +1 -1
  66. package/dist/src/http.js +29 -1
  67. package/dist/src/http.js.map +1 -1
  68. package/dist/src/index.d.ts +4 -1
  69. package/dist/src/index.d.ts.map +1 -1
  70. package/dist/src/index.js +2 -1
  71. package/dist/src/index.js.map +1 -1
  72. package/dist/src/shared/utils/swmaestro.d.ts +34 -1
  73. package/dist/src/shared/utils/swmaestro.d.ts.map +1 -1
  74. package/dist/src/shared/utils/swmaestro.js +102 -39
  75. package/dist/src/shared/utils/swmaestro.js.map +1 -1
  76. package/dist/src/shared/utils/team-action-params.d.ts +3 -0
  77. package/dist/src/shared/utils/team-action-params.d.ts.map +1 -0
  78. package/dist/src/shared/utils/team-action-params.js +10 -0
  79. package/dist/src/shared/utils/team-action-params.js.map +1 -0
  80. package/dist/src/shared/utils/team-params.d.ts +12 -0
  81. package/dist/src/shared/utils/team-params.d.ts.map +1 -0
  82. package/dist/src/shared/utils/team-params.js +38 -0
  83. package/dist/src/shared/utils/team-params.js.map +1 -0
  84. package/dist/src/types.d.ts +147 -10
  85. package/dist/src/types.d.ts.map +1 -1
  86. package/dist/src/types.js +74 -6
  87. package/dist/src/types.js.map +1 -1
  88. package/package.json +5 -1
  89. package/src/agent-browser-launcher.test.ts +263 -0
  90. package/src/agent-browser-launcher.ts +159 -0
  91. package/src/cli.ts +4 -2
  92. package/src/client.test.ts +673 -30
  93. package/src/client.ts +277 -67
  94. package/src/commands/agent-browser.ts +33 -0
  95. package/src/commands/auth.test.ts +77 -26
  96. package/src/commands/auth.ts +5 -3
  97. package/src/commands/dashboard.test.ts +57 -0
  98. package/src/commands/dashboard.ts +22 -19
  99. package/src/commands/helpers.test.ts +72 -25
  100. package/src/commands/helpers.ts +3 -3
  101. package/src/commands/index.ts +2 -1
  102. package/src/commands/mentoring.ts +60 -29
  103. package/src/commands/notice.ts +2 -1
  104. package/src/commands/report.ts +4 -2
  105. package/src/commands/room.ts +160 -1
  106. package/src/commands/schedule.ts +32 -0
  107. package/src/commands/team.ts +73 -5
  108. package/src/constants.ts +20 -8
  109. package/src/credential-manager.test.ts +44 -0
  110. package/src/credential-manager.ts +27 -0
  111. package/src/formatters.test.ts +528 -33
  112. package/src/formatters.ts +309 -55
  113. package/src/http.test.ts +71 -2
  114. package/src/http.ts +41 -2
  115. package/src/index.ts +10 -1
  116. package/src/shared/utils/swmaestro.test.ts +245 -9
  117. package/src/shared/utils/swmaestro.ts +150 -47
  118. package/src/shared/utils/team-action-params.test.ts +32 -0
  119. package/src/shared/utils/team-action-params.ts +10 -0
  120. package/src/shared/utils/team-params.test.ts +141 -0
  121. package/src/shared/utils/team-params.ts +53 -0
  122. package/src/types.test.ts +26 -13
  123. package/src/types.ts +87 -7
  124. package/dist/src/commands/event.d.ts +0 -3
  125. package/dist/src/commands/event.d.ts.map +0 -1
  126. package/dist/src/commands/event.js +0 -58
  127. package/dist/src/commands/event.js.map +0 -1
  128. package/src/commands/event.ts +0 -73
@@ -57,21 +57,72 @@ describe('resolveExtractedCredentials', () => {
57
57
  })
58
58
 
59
59
  describe('inspectStoredAuthStatus', () => {
60
- it('clears stale credentials when both recovery methods fail', async () => {
61
- let removed = false
60
+ it('clears session state but preserves saved id/password when both recovery methods fail', async () => {
61
+ let cleared = false
62
+ let postClearCredentials = {
63
+ sessionCookie: '',
64
+ csrfToken: '',
65
+ username: 'mentor@example.com',
66
+ password: 'secret-password',
67
+ }
62
68
 
63
69
  const status = await inspectStoredAuthStatus(
64
70
  {
65
- getCredentials: async () => ({
66
- sessionCookie: 'stale-session',
67
- csrfToken: 'csrf-token',
68
- username: 'neo@example.com',
69
- }),
71
+ getCredentials: async () => {
72
+ if (cleared) return postClearCredentials
73
+ return {
74
+ sessionCookie: 'stale-session',
75
+ csrfToken: 'csrf-token',
76
+ username: 'mentor@example.com',
77
+ password: 'secret-password',
78
+ }
79
+ },
80
+ setCredentials: async () => {
81
+ throw new Error('inspectStoredAuthStatus must not write credentials directly on the failure path')
82
+ },
83
+ clearSessionState: async () => {
84
+ cleared = true
85
+ },
86
+ },
87
+ () => ({
88
+ checkLogin: async () => null,
89
+ }),
90
+ () => ({
91
+ login: async () => {},
92
+ checkLogin: async () => null,
93
+ getSessionCookie: () => null,
94
+ getCsrfToken: () => null,
95
+ }),
96
+ noBrowserExtraction,
97
+ )
98
+
99
+ expect(status).toEqual({
100
+ authenticated: false,
101
+ credentials: null,
102
+ clearedStaleSession: true,
103
+ preservedRecoveryCredentials: true,
104
+ hint: 'Session expired. Run: opensoma auth login or opensoma auth extract',
105
+ })
106
+ expect(cleared).toBe(true)
107
+ })
108
+
109
+ it('reports preservedRecoveryCredentials=false when no recovery material was stored', async () => {
110
+ let cleared = false
111
+
112
+ const status = await inspectStoredAuthStatus(
113
+ {
114
+ getCredentials: async () => {
115
+ if (cleared) return null
116
+ return {
117
+ sessionCookie: 'stale-session',
118
+ csrfToken: 'csrf-token',
119
+ }
120
+ },
70
121
  setCredentials: async () => {
71
- throw new Error('should not save unrecoverable credentials')
122
+ throw new Error('should not write credentials')
72
123
  },
73
- remove: async () => {
74
- removed = true
124
+ clearSessionState: async () => {
125
+ cleared = true
75
126
  },
76
127
  },
77
128
  () => ({
@@ -84,28 +135,28 @@ describe('inspectStoredAuthStatus', () => {
84
135
  expect(status).toEqual({
85
136
  authenticated: false,
86
137
  credentials: null,
87
- clearedStaleCredentials: true,
138
+ clearedStaleSession: true,
139
+ preservedRecoveryCredentials: false,
88
140
  hint: 'Session expired. Run: opensoma auth login or opensoma auth extract',
89
141
  })
90
- expect(removed).toBe(true)
91
142
  })
92
143
 
93
144
  it('preserves credentials when session verification fails unexpectedly', async () => {
94
- let removed = false
145
+ let cleared = false
95
146
 
96
147
  const status = await inspectStoredAuthStatus(
97
148
  {
98
149
  getCredentials: async () => ({
99
150
  sessionCookie: 'maybe-valid-session',
100
151
  csrfToken: 'csrf-token',
101
- username: 'neo@example.com',
152
+ username: 'mentor@example.com',
102
153
  loggedInAt: '2026-04-13T00:00:00.000Z',
103
154
  }),
104
155
  setCredentials: async () => {
105
156
  throw new Error('should not rewrite credentials when verification fails')
106
157
  },
107
- remove: async () => {
108
- removed = true
158
+ clearSessionState: async () => {
159
+ cleared = true
109
160
  },
110
161
  },
111
162
  () => ({
@@ -118,11 +169,11 @@ describe('inspectStoredAuthStatus', () => {
118
169
  expect(status).toEqual({
119
170
  authenticated: true,
120
171
  valid: false,
121
- username: 'neo@example.com',
172
+ username: 'mentor@example.com',
122
173
  loggedInAt: '2026-04-13T00:00:00.000Z',
123
174
  hint: 'Could not verify session. Try again or run: opensoma auth login or opensoma auth extract',
124
175
  })
125
- expect(removed).toBe(false)
176
+ expect(cleared).toBe(false)
126
177
  })
127
178
 
128
179
  it('recovers via browser extraction when no stored password is available', async () => {
@@ -137,8 +188,8 @@ describe('inspectStoredAuthStatus', () => {
137
188
  setCredentials: async (credentials: Record<string, unknown>) => {
138
189
  savedCredentials = credentials
139
190
  },
140
- remove: async () => {
141
- throw new Error('should not remove when browser extraction succeeds')
191
+ clearSessionState: async () => {
192
+ throw new Error('should not clear session state when browser extraction succeeds')
142
193
  },
143
194
  },
144
195
  () => ({
@@ -168,15 +219,15 @@ describe('inspectStoredAuthStatus', () => {
168
219
  getCredentials: async () => ({
169
220
  sessionCookie: 'stale-session',
170
221
  csrfToken: 'stale-csrf',
171
- username: 'neo@example.com',
222
+ username: 'mentor@example.com',
172
223
  password: 'secret',
173
224
  loggedInAt: '2026-04-13T00:00:00.000Z',
174
225
  }),
175
226
  setCredentials: async (credentials: Record<string, string>) => {
176
227
  savedCredentials = credentials
177
228
  },
178
- remove: async () => {
179
- throw new Error('should not remove recoverable credentials')
229
+ clearSessionState: async () => {
230
+ throw new Error('should not clear session state when re-login succeeds')
180
231
  },
181
232
  },
182
233
  () => ({
@@ -184,7 +235,7 @@ describe('inspectStoredAuthStatus', () => {
184
235
  }),
185
236
  () => ({
186
237
  login: async () => {},
187
- checkLogin: async () => ({ userId: 'neo@example.com', userNm: 'Neo' }),
238
+ checkLogin: async () => ({ userId: 'mentor@example.com', userNm: 'Mentor One' }),
188
239
  getSessionCookie: () => 'fresh-session',
189
240
  getCsrfToken: () => 'fresh-csrf',
190
241
  }),
@@ -193,13 +244,13 @@ describe('inspectStoredAuthStatus', () => {
193
244
  expect(status).toEqual({
194
245
  authenticated: true,
195
246
  valid: true,
196
- username: 'neo@example.com',
247
+ username: 'mentor@example.com',
197
248
  loggedInAt: expect.any(String),
198
249
  })
199
250
  expect(savedCredentials).toMatchObject({
200
251
  sessionCookie: 'fresh-session',
201
252
  csrfToken: 'fresh-csrf',
202
- username: 'neo@example.com',
253
+ username: 'mentor@example.com',
203
254
  password: 'secret',
204
255
  })
205
256
  })
@@ -103,7 +103,7 @@ type LoginOptions = { username?: string; password?: string; pretty?: boolean }
103
103
  type StatusOptions = { pretty?: boolean }
104
104
  type ExtractOptions = { debug?: boolean; pretty?: boolean }
105
105
  type ExtractedSessionValidator = Pick<SomaHttp, 'checkLogin' | 'extractCsrfToken'>
106
- type CredentialStore = Pick<CredentialManager, 'getCredentials' | 'remove' | 'setCredentials'>
106
+ type CredentialStore = Pick<CredentialManager, 'clearSessionState' | 'getCredentials' | 'setCredentials'>
107
107
  type StatusValidator = Pick<SomaHttp, 'checkLogin'>
108
108
  type ReloginHttp = Pick<SomaHttp, 'checkLogin' | 'getCsrfToken' | 'getSessionCookie' | 'login'>
109
109
  type BrowserExtractor = () => Promise<{ csrfToken: string; sessionCookie: string } | null>
@@ -286,11 +286,13 @@ export async function inspectStoredAuthStatus(
286
286
  // Browser extraction failed — fall through to credential removal
287
287
  }
288
288
 
289
- await manager.remove()
289
+ await manager.clearSessionState()
290
+ const post = await manager.getCredentials()
290
291
  return {
291
292
  authenticated: false,
292
293
  credentials: null,
293
- clearedStaleCredentials: true,
294
+ clearedStaleSession: true,
295
+ preservedRecoveryCredentials: Boolean(post?.username || post?.password),
294
296
  hint: EXPIRED_SESSION_HINT,
295
297
  }
296
298
  }
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+
3
+ import type { SomaHttp } from '../http'
4
+ import type { Dashboard } from '../types'
5
+ import { showDashboard } from './dashboard'
6
+
7
+ describe('showDashboard', () => {
8
+ it('prints the SDK dashboard response', async () => {
9
+ const dashboard: Dashboard = {
10
+ name: '김연수',
11
+ role: '17기 연수생',
12
+ organization: 'OpenSoma',
13
+ position: '',
14
+ mentoringSessions: [
15
+ {
16
+ title: '팀 프로젝트 방향성 피드백',
17
+ url: '/sw/mypage/mentoLec/view.do?qustnrSn=44',
18
+ status: '접수완료',
19
+ date: '2026-04-28',
20
+ time: '22:00',
21
+ timeEnd: '24:00',
22
+ type: '자유 멘토링',
23
+ },
24
+ ],
25
+ roomReservations: [],
26
+ }
27
+ const fakeHttp = {} as SomaHttp
28
+ const calls: string[] = []
29
+ const outputs: string[] = []
30
+
31
+ await showDashboard(
32
+ { pretty: true },
33
+ {
34
+ getHttp: async () => {
35
+ calls.push('getHttp')
36
+ return fakeHttp
37
+ },
38
+ createClient: (http) => {
39
+ expect(http).toBe(fakeHttp)
40
+ calls.push('createClient')
41
+ return {
42
+ dashboard: {
43
+ get: async () => {
44
+ calls.push('dashboard.get')
45
+ return dashboard
46
+ },
47
+ },
48
+ }
49
+ },
50
+ write: (output) => outputs.push(output),
51
+ },
52
+ )
53
+
54
+ expect(calls).toEqual(['getHttp', 'createClient', 'dashboard.get'])
55
+ expect(outputs).toEqual([JSON.stringify(dashboard, null, 2)])
56
+ })
57
+ })
@@ -1,31 +1,34 @@
1
1
  import { Command } from 'commander'
2
2
 
3
- import { MENU_NO } from '../constants'
4
- import * as formatters from '../formatters'
3
+ import { SomaClient } from '../client'
4
+ import type { SomaHttp } from '../http'
5
5
  import { handleError } from '../shared/utils/error-handler'
6
- import { buildMentoringListParams } from '../shared/utils/mentoring-params'
7
6
  import { formatOutput } from '../shared/utils/output'
8
7
  import { getHttpOrExit } from './helpers'
9
8
 
10
- type ShowOptions = { pretty?: boolean }
9
+ export type ShowOptions = { pretty?: boolean }
10
+
11
+ type DashboardClient = Pick<SomaClient, 'dashboard'>
12
+
13
+ interface ShowDashboardDeps {
14
+ getHttp?: () => Promise<SomaHttp>
15
+ createClient?: (http: SomaHttp) => DashboardClient
16
+ write?: (output: string) => void
17
+ }
18
+
19
+ export async function showDashboard(options: ShowOptions, deps: ShowDashboardDeps = {}): Promise<void> {
20
+ const getHttp = deps.getHttp ?? getHttpOrExit
21
+ const createClient = deps.createClient ?? ((http) => new SomaClient({ http }))
22
+ const write = deps.write ?? ((output) => console.log(output))
23
+
24
+ const http = await getHttp()
25
+ const dashboard = await createClient(http).dashboard.get()
26
+ write(formatOutput(dashboard, options.pretty))
27
+ }
11
28
 
12
29
  async function showAction(options: ShowOptions): Promise<void> {
13
30
  try {
14
- const http = await getHttpOrExit()
15
- const user = (await http.checkLogin()) ?? undefined
16
- const search = { field: 'author' as const, value: '@me', me: true }
17
- const [dashboardHtml, mentoringHtml] = await Promise.all([
18
- http.get('/mypage/myMain/dashboard.do', { menuNo: MENU_NO.DASHBOARD }),
19
- http.get('/mypage/mentoLec/list.do', buildMentoringListParams({ search, user })),
20
- ])
21
- const dashboard = formatters.parseDashboard(dashboardHtml)
22
- const myMentoring = formatters.parseMentoringList(mentoringHtml)
23
- dashboard.mentoringSessions = myMentoring.map((item) => ({
24
- title: item.title,
25
- url: `/mypage/mentoLec/view.do?qustnrSn=${item.id}`,
26
- status: item.status,
27
- }))
28
- console.log(formatOutput(dashboard, options.pretty))
31
+ await showDashboard(options)
29
32
  } catch (error) {
30
33
  handleError(error)
31
34
  }
@@ -8,7 +8,7 @@ describe('createAuthenticatedHttp', () => {
8
8
  it('throws a login hint when no credentials are stored', async () => {
9
9
  const manager = {
10
10
  getCredentials: async () => null,
11
- remove: async () => {},
11
+ clearSessionState: async () => {},
12
12
  }
13
13
 
14
14
  await expect(createAuthenticatedHttp(manager)).rejects.toThrow(
@@ -16,32 +16,79 @@ describe('createAuthenticatedHttp', () => {
16
16
  )
17
17
  })
18
18
 
19
- it('clears stale credentials when both recovery methods fail', async () => {
20
- let removed = false
19
+ it('clears only session state (not username/password) when both recovery methods fail', async () => {
20
+ let cleared = false
21
21
  const manager = {
22
22
  getCredentials: async () => ({
23
23
  sessionCookie: 'stale-session',
24
24
  csrfToken: 'csrf-token',
25
+ username: 'mentor@example.com',
26
+ password: 'secret-password',
25
27
  }),
26
28
  setCredentials: async () => {
27
- throw new Error('should not save unrecoverable credentials')
29
+ throw new Error('createAuthenticatedHttp must not write credentials directly')
28
30
  },
29
- remove: async () => {
30
- removed = true
31
+ clearSessionState: async () => {
32
+ cleared = true
31
33
  },
32
34
  }
33
35
 
34
36
  await expect(
35
37
  createAuthenticatedHttp(manager, () => ({ checkLogin: async () => null }), undefined, noBrowserExtraction),
36
38
  ).rejects.toThrow(
37
- 'Session expired. Saved credentials were cleared. Run: opensoma auth login or opensoma auth extract',
39
+ 'Session expired. Run: opensoma auth login or opensoma auth extract (saved id/password were preserved)',
38
40
  )
39
- expect(removed).toBe(true)
41
+ expect(cleared).toBe(true)
42
+ })
43
+
44
+ it('preserves stored id/password on disk when session expires and recovery fails', async () => {
45
+ const { CredentialManager } = await import('../credential-manager')
46
+ const { mkdtemp, rm } = await import('node:fs/promises')
47
+ const { tmpdir } = await import('node:os')
48
+ const { join } = await import('node:path')
49
+
50
+ const dir = await mkdtemp(join(tmpdir(), 'opensoma-helpers-'))
51
+ try {
52
+ const manager = new CredentialManager(dir)
53
+ await manager.setCredentials({
54
+ sessionCookie: 'stale-session',
55
+ csrfToken: 'stale-csrf',
56
+ username: 'mentor@example.com',
57
+ password: 'secret-password',
58
+ loggedInAt: '2026-04-09T00:00:00.000Z',
59
+ })
60
+
61
+ await expect(
62
+ createAuthenticatedHttp(
63
+ manager,
64
+ () => ({ checkLogin: async () => null }),
65
+ () => ({
66
+ login: async () => {
67
+ throw new Error('upstream rejected re-login')
68
+ },
69
+ checkLogin: async () => null,
70
+ getSessionCookie: () => null,
71
+ getCsrfToken: () => null,
72
+ }),
73
+ noBrowserExtraction,
74
+ ),
75
+ ).rejects.toThrow('Session expired')
76
+
77
+ const after = await manager.getCredentials()
78
+ expect(after).toEqual({
79
+ sessionCookie: '',
80
+ csrfToken: '',
81
+ username: 'mentor@example.com',
82
+ password: 'secret-password',
83
+ })
84
+ } finally {
85
+ await rm(dir, { force: true, recursive: true })
86
+ }
40
87
  })
41
88
 
42
89
  it('returns the authenticated http client when the session is valid', async () => {
43
90
  const http = {
44
- checkLogin: async () => ({ userId: 'neo@example.com', userNm: '전수열' }),
91
+ checkLogin: async () => ({ userId: 'mentor@example.com', userNm: 'Mentor One' }),
45
92
  get: async () => '',
46
93
  }
47
94
  const manager = {
@@ -52,8 +99,8 @@ describe('createAuthenticatedHttp', () => {
52
99
  setCredentials: async () => {
53
100
  throw new Error('should not rewrite valid credentials')
54
101
  },
55
- remove: async () => {
56
- throw new Error('should not remove valid credentials')
102
+ clearSessionState: async () => {
103
+ throw new Error('should not clear session state for valid credentials')
57
104
  },
58
105
  }
59
106
 
@@ -66,18 +113,18 @@ describe('createAuthenticatedHttp', () => {
66
113
  getCredentials: async () => ({
67
114
  sessionCookie: 'stale-session',
68
115
  csrfToken: 'stale-csrf',
69
- username: 'neo@example.com',
116
+ username: 'mentor@example.com',
70
117
  password: 'secret',
71
118
  }),
72
119
  setCredentials: async (credentials: Record<string, string>) => {
73
120
  savedCredentials = credentials
74
121
  },
75
- remove: async () => {
76
- throw new Error('should not remove recoverable credentials')
122
+ clearSessionState: async () => {
123
+ throw new Error('should not clear session state when re-login succeeds')
77
124
  },
78
125
  }
79
126
  const recoveredHttp = {
80
- checkLogin: async () => ({ userId: 'neo@example.com', userNm: 'Neo' }),
127
+ checkLogin: async () => ({ userId: 'mentor@example.com', userNm: 'Mentor One' }),
81
128
  get: async () => '',
82
129
  }
83
130
 
@@ -95,7 +142,7 @@ describe('createAuthenticatedHttp', () => {
95
142
  },
96
143
  () => ({
97
144
  login: async () => {},
98
- checkLogin: async () => ({ userId: 'neo@example.com', userNm: 'Neo' }),
145
+ checkLogin: async () => ({ userId: 'mentor@example.com', userNm: 'Mentor One' }),
99
146
  getSessionCookie: () => 'fresh-session',
100
147
  getCsrfToken: () => 'fresh-csrf',
101
148
  }),
@@ -105,7 +152,7 @@ describe('createAuthenticatedHttp', () => {
105
152
  expect(savedCredentials).toMatchObject({
106
153
  sessionCookie: 'fresh-session',
107
154
  csrfToken: 'fresh-csrf',
108
- username: 'neo@example.com',
155
+ username: 'mentor@example.com',
109
156
  password: 'secret',
110
157
  })
111
158
  })
@@ -120,12 +167,12 @@ describe('createAuthenticatedHttp', () => {
120
167
  setCredentials: async (credentials: Record<string, unknown>) => {
121
168
  savedCredentials = credentials
122
169
  },
123
- remove: async () => {
124
- throw new Error('should not remove when browser extraction succeeds')
170
+ clearSessionState: async () => {
171
+ throw new Error('should not clear session state when browser extraction succeeds')
125
172
  },
126
173
  }
127
174
  const recoveredHttp = {
128
- checkLogin: async () => ({ userId: 'neo@example.com', userNm: 'Neo' }),
175
+ checkLogin: async () => ({ userId: 'mentor@example.com', userNm: 'Mentor One' }),
129
176
  }
130
177
 
131
178
  const result = await createAuthenticatedHttp(
@@ -152,18 +199,18 @@ describe('createAuthenticatedHttp', () => {
152
199
  getCredentials: async () => ({
153
200
  sessionCookie: 'stale-session',
154
201
  csrfToken: 'stale-csrf',
155
- username: 'neo@example.com',
202
+ username: 'mentor@example.com',
156
203
  password: 'wrong-password',
157
204
  }),
158
205
  setCredentials: async (credentials: Record<string, unknown>) => {
159
206
  savedCredentials = credentials
160
207
  },
161
- remove: async () => {
162
- throw new Error('should not remove when browser extraction succeeds')
208
+ clearSessionState: async () => {
209
+ throw new Error('should not clear session state when browser extraction succeeds')
163
210
  },
164
211
  }
165
212
  const recoveredHttp = {
166
- checkLogin: async () => ({ userId: 'neo@example.com', userNm: 'Neo' }),
213
+ checkLogin: async () => ({ userId: 'mentor@example.com', userNm: 'Mentor One' }),
167
214
  }
168
215
 
169
216
  const result = await createAuthenticatedHttp(
@@ -197,7 +244,7 @@ describe('createAuthenticatedHttp', () => {
197
244
  setCredentials: async () => {
198
245
  throw new Error('should not save')
199
246
  },
200
- remove: async () => {},
247
+ clearSessionState: async () => {},
201
248
  }
202
249
 
203
250
  await expect(
@@ -4,14 +4,14 @@ import { recoverSession } from '../session-recovery'
4
4
  import * as stderr from '../shared/utils/stderr'
5
5
  import type { Credentials } from '../types'
6
6
 
7
- type CredentialStore = Pick<CredentialManager, 'getCredentials' | 'remove' | 'setCredentials'>
7
+ type CredentialStore = Pick<CredentialManager, 'clearSessionState' | 'getCredentials' | 'setCredentials'>
8
8
  type AuthenticatedHttp = Pick<SomaHttp, 'checkLogin'>
9
9
  type ReloginHttp = Pick<SomaHttp, 'checkLogin' | 'getCsrfToken' | 'getSessionCookie' | 'login'>
10
10
  type BrowserExtractor = () => Promise<{ csrfToken: string; sessionCookie: string } | null>
11
11
 
12
12
  const NOT_LOGGED_IN_MESSAGE = 'Not logged in. Run: opensoma auth login or opensoma auth extract'
13
13
  const STALE_SESSION_MESSAGE =
14
- 'Session expired. Saved credentials were cleared. Run: opensoma auth login or opensoma auth extract'
14
+ 'Session expired. Run: opensoma auth login or opensoma auth extract (saved id/password were preserved)'
15
15
 
16
16
  function defaultCreateHttp(credentials: Credentials): SomaHttp {
17
17
  return new SomaHttp({ sessionCookie: credentials.sessionCookie, csrfToken: credentials.csrfToken })
@@ -73,7 +73,7 @@ export async function createAuthenticatedHttp<T extends AuthenticatedHttp>(
73
73
  // Browser extraction also failed
74
74
  }
75
75
 
76
- await manager.remove()
76
+ await manager.clearSessionState()
77
77
  throw new Error(STALE_SESSION_MESSAGE)
78
78
  }
79
79
 
@@ -1,3 +1,4 @@
1
+ export { agentBrowserCommand } from './agent-browser'
1
2
  export { authCommand } from './auth'
2
3
  export { mentoringCommand } from './mentoring'
3
4
  export { roomCommand } from './room'
@@ -5,5 +6,5 @@ export { dashboardCommand } from './dashboard'
5
6
  export { noticeCommand } from './notice'
6
7
  export { teamCommand } from './team'
7
8
  export { memberCommand } from './member'
8
- export { eventCommand } from './event'
9
+ export { scheduleCommand } from './schedule'
9
10
  export { reportCommand } from './report'