@jackwener/opencli 1.7.16 → 1.7.18

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 (174) hide show
  1. package/README.md +11 -9
  2. package/README.zh-CN.md +10 -8
  3. package/cli-manifest.json +377 -271
  4. package/clis/chatgpt/ask.js +1 -1
  5. package/clis/chatgpt/commands.test.js +2 -2
  6. package/clis/chatgpt/detail.js +1 -1
  7. package/clis/chatgpt/history.js +1 -1
  8. package/clis/chatgpt/image.js +38 -4
  9. package/clis/chatgpt/image.test.js +68 -1
  10. package/clis/chatgpt/new.js +1 -1
  11. package/clis/chatgpt/read.js +1 -1
  12. package/clis/chatgpt/send.js +1 -1
  13. package/clis/chatgpt/status.js +1 -1
  14. package/clis/chatgpt/utils.js +208 -16
  15. package/clis/chatgpt/utils.test.js +131 -2
  16. package/clis/claude/ask.js +1 -1
  17. package/clis/claude/detail.js +1 -1
  18. package/clis/claude/history.js +1 -1
  19. package/clis/claude/new.js +1 -1
  20. package/clis/claude/read.js +1 -1
  21. package/clis/claude/send.js +1 -1
  22. package/clis/claude/status.js +1 -1
  23. package/clis/deepseek/ask.js +1 -1
  24. package/clis/deepseek/detail.js +1 -1
  25. package/clis/deepseek/history.js +1 -1
  26. package/clis/deepseek/new.js +1 -1
  27. package/clis/deepseek/read.js +1 -1
  28. package/clis/deepseek/send.js +1 -1
  29. package/clis/deepseek/status.js +1 -1
  30. package/clis/doubao/ask.js +1 -1
  31. package/clis/doubao/detail.js +1 -1
  32. package/clis/doubao/history.js +1 -1
  33. package/clis/doubao/meeting-summary.js +1 -1
  34. package/clis/doubao/meeting-transcript.js +1 -1
  35. package/clis/doubao/new.js +1 -1
  36. package/clis/doubao/read.js +1 -1
  37. package/clis/doubao/send.js +1 -1
  38. package/clis/doubao/status.js +1 -1
  39. package/clis/doubao/utils.js +17 -0
  40. package/clis/doubao/utils.test.js +61 -0
  41. package/clis/gemini/ask.js +1 -1
  42. package/clis/gemini/deep-research-result.js +1 -1
  43. package/clis/gemini/deep-research.js +1 -1
  44. package/clis/gemini/image.js +1 -1
  45. package/clis/gemini/new.js +1 -1
  46. package/clis/grok/ask.js +1 -1
  47. package/clis/grok/detail.js +1 -1
  48. package/clis/grok/history.js +1 -1
  49. package/clis/grok/image.js +1 -1
  50. package/clis/grok/new.js +1 -1
  51. package/clis/grok/read.js +1 -1
  52. package/clis/grok/send.js +1 -1
  53. package/clis/grok/status.js +1 -1
  54. package/clis/notebooklm/current.js +1 -1
  55. package/clis/notebooklm/get.js +1 -1
  56. package/clis/notebooklm/history.js +1 -1
  57. package/clis/notebooklm/note-list.js +1 -1
  58. package/clis/notebooklm/notes-get.js +1 -1
  59. package/clis/notebooklm/open.js +2 -2
  60. package/clis/notebooklm/open.test.js +1 -1
  61. package/clis/notebooklm/source-fulltext.js +1 -1
  62. package/clis/notebooklm/source-get.js +1 -1
  63. package/clis/notebooklm/source-guide.js +1 -1
  64. package/clis/notebooklm/source-list.js +1 -1
  65. package/clis/notebooklm/summary.js +1 -1
  66. package/clis/qwen/ask.js +1 -1
  67. package/clis/qwen/detail.js +1 -1
  68. package/clis/qwen/history.js +1 -1
  69. package/clis/qwen/image.js +1 -1
  70. package/clis/qwen/new.js +1 -1
  71. package/clis/qwen/read.js +1 -1
  72. package/clis/qwen/send.js +1 -1
  73. package/clis/qwen/status.js +1 -1
  74. package/clis/reddit/comment.js +1 -1
  75. package/clis/reddit/frontpage.js +1 -1
  76. package/clis/reddit/popular.js +1 -1
  77. package/clis/reddit/read.js +1 -1
  78. package/clis/reddit/read.test.js +2 -2
  79. package/clis/reddit/reply.js +182 -0
  80. package/clis/reddit/reply.test.js +89 -0
  81. package/clis/reddit/save.js +1 -1
  82. package/clis/reddit/saved.js +1 -1
  83. package/clis/reddit/search.js +1 -1
  84. package/clis/reddit/subreddit.js +1 -1
  85. package/clis/reddit/subscribe.js +1 -1
  86. package/clis/reddit/upvote.js +1 -1
  87. package/clis/reddit/upvoted.js +1 -1
  88. package/clis/reddit/user-comments.js +1 -1
  89. package/clis/reddit/user-posts.js +1 -1
  90. package/clis/reddit/user.js +1 -1
  91. package/clis/rednote/comments.js +76 -0
  92. package/clis/rednote/download.js +59 -0
  93. package/clis/rednote/feed.js +95 -0
  94. package/clis/rednote/navigation.test.js +26 -0
  95. package/clis/rednote/note.js +68 -0
  96. package/clis/rednote/notifications.js +139 -0
  97. package/clis/rednote/rednote.test.js +157 -0
  98. package/clis/rednote/search.js +97 -0
  99. package/clis/rednote/user.js +55 -0
  100. package/clis/twitter/article.js +1 -1
  101. package/clis/twitter/bookmark-folder.js +1 -1
  102. package/clis/twitter/bookmark-folders.js +1 -1
  103. package/clis/twitter/bookmarks.js +1 -1
  104. package/clis/twitter/download.js +1 -1
  105. package/clis/twitter/followers.js +1 -1
  106. package/clis/twitter/following.js +1 -1
  107. package/clis/twitter/likes.js +1 -1
  108. package/clis/twitter/list-tweets.js +1 -1
  109. package/clis/twitter/lists.js +1 -1
  110. package/clis/twitter/notifications.js +1 -1
  111. package/clis/twitter/profile.js +1 -1
  112. package/clis/twitter/search.js +1 -1
  113. package/clis/twitter/thread.js +1 -1
  114. package/clis/twitter/timeline.js +1 -1
  115. package/clis/twitter/trending.js +1 -1
  116. package/clis/twitter/tweets.js +1 -1
  117. package/clis/xiaohongshu/comments.js +34 -24
  118. package/clis/xiaohongshu/download.js +32 -23
  119. package/clis/xiaohongshu/feed.js +23 -15
  120. package/clis/xiaohongshu/note-helpers.js +16 -6
  121. package/clis/xiaohongshu/note.js +26 -20
  122. package/clis/xiaohongshu/notifications.js +26 -19
  123. package/clis/xiaohongshu/search.js +37 -28
  124. package/clis/xiaohongshu/user-helpers.js +13 -4
  125. package/clis/xiaohongshu/user-helpers.test.js +20 -0
  126. package/clis/xiaohongshu/user.js +9 -4
  127. package/clis/youtube/transcript.js +28 -3
  128. package/clis/youtube/transcript.test.js +90 -1
  129. package/clis/yuanbao/ask.js +1 -1
  130. package/clis/yuanbao/detail.js +1 -1
  131. package/clis/yuanbao/history.js +1 -1
  132. package/clis/yuanbao/new.js +1 -1
  133. package/clis/yuanbao/read.js +1 -1
  134. package/clis/yuanbao/send.js +1 -1
  135. package/clis/yuanbao/status.js +1 -1
  136. package/dist/src/browser/bridge.d.ts +3 -1
  137. package/dist/src/browser/bridge.js +3 -1
  138. package/dist/src/browser/cdp.d.ts +3 -1
  139. package/dist/src/browser/daemon-client.d.ts +7 -14
  140. package/dist/src/browser/daemon-client.js +2 -6
  141. package/dist/src/browser/network-cache.d.ts +5 -5
  142. package/dist/src/browser/network-cache.js +8 -8
  143. package/dist/src/browser/network-cache.test.js +4 -4
  144. package/dist/src/browser/page.d.ts +8 -7
  145. package/dist/src/browser/page.js +23 -16
  146. package/dist/src/browser/page.test.js +60 -30
  147. package/dist/src/build-manifest.js +1 -1
  148. package/dist/src/cli.js +60 -162
  149. package/dist/src/cli.test.js +184 -198
  150. package/dist/src/commanderAdapter.js +2 -0
  151. package/dist/src/discovery.js +1 -1
  152. package/dist/src/doctor.d.ts +0 -4
  153. package/dist/src/doctor.js +14 -73
  154. package/dist/src/doctor.test.js +28 -97
  155. package/dist/src/execution.d.ts +1 -0
  156. package/dist/src/execution.js +20 -21
  157. package/dist/src/execution.test.js +27 -31
  158. package/dist/src/help.js +7 -1
  159. package/dist/src/main.js +0 -19
  160. package/dist/src/manifest-types.d.ts +2 -4
  161. package/dist/src/observation/artifact.js +1 -1
  162. package/dist/src/observation/artifact.test.js +3 -3
  163. package/dist/src/observation/events.d.ts +1 -1
  164. package/dist/src/observation/manager.js +1 -1
  165. package/dist/src/observation/manager.test.js +3 -3
  166. package/dist/src/registry-api.d.ts +1 -1
  167. package/dist/src/registry.d.ts +3 -12
  168. package/dist/src/registry.js +6 -10
  169. package/dist/src/runtime.d.ts +7 -2
  170. package/dist/src/runtime.js +3 -1
  171. package/dist/src/serialization.d.ts +1 -1
  172. package/dist/src/serialization.js +1 -1
  173. package/dist/src/types.d.ts +0 -15
  174. package/package.json +1 -1
@@ -136,7 +136,7 @@ describe('executeCommand — non-browser timeout', () => {
136
136
  });
137
137
  vi.restoreAllMocks();
138
138
  });
139
- it('reuses a site-scoped browser workspace and keeps the tab lease open', async () => {
139
+ it('reuses a persistent site browser session and keeps the tab lease open', async () => {
140
140
  const closeWindow = vi.fn().mockResolvedValue(undefined);
141
141
  const mockPage = { closeWindow };
142
142
  const sessionOpts = [];
@@ -147,22 +147,24 @@ describe('executeCommand — non-browser timeout', () => {
147
147
  });
148
148
  const cmd = cli({
149
149
  site: 'test-execution',
150
- name: 'browser-reuse-site', access: 'read',
151
- description: 'test site-scoped browser reuse',
150
+ name: 'site-session-persistent', access: 'read',
151
+ description: 'test persistent site session',
152
152
  browser: true,
153
153
  strategy: Strategy.PUBLIC,
154
- browserSession: { reuse: 'site' },
154
+ siteSession: 'persistent',
155
155
  func: async () => [{ ok: true }],
156
156
  });
157
157
  await executeCommand(cmd, {});
158
- await executeCommand(cmd, {});
158
+ await executeCommand(cmd, {}, false, { keepTab: 'false' });
159
159
  expect(sessionOpts).toHaveLength(2);
160
- expect(sessionOpts[0]).toMatchObject({ workspace: 'site:test-execution', idleTimeout: 600, windowMode: 'background' });
161
- expect(sessionOpts[1]).toMatchObject({ workspace: 'site:test-execution', idleTimeout: 600, windowMode: 'background' });
160
+ expect(sessionOpts[0]).toMatchObject({ session: 'site:test-execution', windowMode: 'background', siteSession: 'persistent' });
161
+ expect(sessionOpts[1]).toMatchObject({ session: 'site:test-execution', windowMode: 'background', siteSession: 'persistent' });
162
+ expect(sessionOpts[0]?.idleTimeout).toBeUndefined();
163
+ expect(sessionOpts[1]?.idleTimeout).toBeUndefined();
162
164
  expect(closeWindow).not.toHaveBeenCalled();
163
165
  vi.restoreAllMocks();
164
166
  });
165
- it('keeps default browser commands on one-shot workspaces', async () => {
167
+ it('keeps default browser commands on one-shot adapter sessions', async () => {
166
168
  const closeWindow = vi.fn().mockResolvedValue(undefined);
167
169
  const mockPage = { closeWindow };
168
170
  const sessionOpts = [];
@@ -173,8 +175,8 @@ describe('executeCommand — non-browser timeout', () => {
173
175
  });
174
176
  const cmd = cli({
175
177
  site: 'test-execution',
176
- name: 'browser-reuse-default', access: 'read',
177
- description: 'test default one-shot browser workspace',
178
+ name: 'site-session-default', access: 'read',
179
+ description: 'test default one-shot browser session',
178
180
  browser: true,
179
181
  strategy: Strategy.PUBLIC,
180
182
  func: async () => [{ ok: true }],
@@ -182,9 +184,9 @@ describe('executeCommand — non-browser timeout', () => {
182
184
  await executeCommand(cmd, {});
183
185
  await executeCommand(cmd, {});
184
186
  expect(sessionOpts).toHaveLength(2);
185
- expect(sessionOpts[0]?.workspace).toMatch(/^site:test-execution:/);
186
- expect(sessionOpts[1]?.workspace).toMatch(/^site:test-execution:/);
187
- expect(sessionOpts[0]?.workspace).not.toBe(sessionOpts[1]?.workspace);
187
+ expect(sessionOpts[0]?.session).toMatch(/^site:test-execution:/);
188
+ expect(sessionOpts[1]?.session).toMatch(/^site:test-execution:/);
189
+ expect(sessionOpts[0]?.session).not.toBe(sessionOpts[1]?.session);
188
190
  expect(sessionOpts[0]?.idleTimeout).toBeUndefined();
189
191
  expect(sessionOpts[1]?.idleTimeout).toBeUndefined();
190
192
  expect(sessionOpts[0]?.windowMode).toBe('background');
@@ -192,7 +194,7 @@ describe('executeCommand — non-browser timeout', () => {
192
194
  expect(closeWindow).toHaveBeenCalledTimes(2);
193
195
  vi.restoreAllMocks();
194
196
  });
195
- it('lets user --reuse none override adapter reuse metadata', async () => {
197
+ it('lets user --site-session ephemeral override adapter persistent metadata', async () => {
196
198
  const closeWindow = vi.fn().mockResolvedValue(undefined);
197
199
  const mockPage = { closeWindow };
198
200
  const sessionOpts = [];
@@ -201,33 +203,27 @@ describe('executeCommand — non-browser timeout', () => {
201
203
  sessionOpts.push(opts ?? {});
202
204
  return fn(mockPage);
203
205
  });
204
- const prev = process.env.OPENCLI_BROWSER_REUSE;
205
- process.env.OPENCLI_BROWSER_REUSE = 'none';
206
206
  try {
207
207
  const cmd = cli({
208
208
  site: 'test-execution',
209
- name: 'browser-reuse-override-none', access: 'read',
210
- description: 'test user reuse override',
209
+ name: 'site-session-override-ephemeral', access: 'read',
210
+ description: 'test user site-session override',
211
211
  browser: true,
212
212
  strategy: Strategy.PUBLIC,
213
- browserSession: { reuse: 'site' },
213
+ siteSession: 'persistent',
214
214
  func: async () => [{ ok: true }],
215
215
  });
216
- await executeCommand(cmd, {});
216
+ await executeCommand(cmd, {}, false, { siteSession: 'ephemeral' });
217
217
  expect(sessionOpts).toHaveLength(1);
218
- expect(sessionOpts[0]?.workspace).toMatch(/^site:test-execution:/);
218
+ expect(sessionOpts[0]?.session).toMatch(/^site:test-execution:/);
219
219
  expect(sessionOpts[0]?.idleTimeout).toBeUndefined();
220
220
  expect(closeWindow).toHaveBeenCalledTimes(1);
221
221
  }
222
222
  finally {
223
- if (prev === undefined)
224
- delete process.env.OPENCLI_BROWSER_REUSE;
225
- else
226
- process.env.OPENCLI_BROWSER_REUSE = prev;
227
223
  vi.restoreAllMocks();
228
224
  }
229
225
  });
230
- it('skips repeated domain pre-navigation for site-reused browser sessions', async () => {
226
+ it('skips repeated domain pre-navigation for persistent site sessions', async () => {
231
227
  const closeWindow = vi.fn().mockResolvedValue(undefined);
232
228
  const goto = vi.fn().mockResolvedValue(undefined);
233
229
  const mockPage = {
@@ -239,12 +235,12 @@ describe('executeCommand — non-browser timeout', () => {
239
235
  vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
240
236
  const cmd = cli({
241
237
  site: 'test-execution',
242
- name: 'browser-reuse-skip-prenav', access: 'read',
238
+ name: 'site-session-skip-prenav', access: 'read',
243
239
  description: 'test reused same-domain tabs do not reset conversation state',
244
240
  browser: true,
245
241
  strategy: Strategy.COOKIE,
246
242
  domain: 'grok.com',
247
- browserSession: { reuse: 'site' },
243
+ siteSession: 'persistent',
248
244
  func: async () => [{ ok: true }],
249
245
  });
250
246
  await executeCommand(cmd, {});
@@ -252,7 +248,7 @@ describe('executeCommand — non-browser timeout', () => {
252
248
  expect(closeWindow).not.toHaveBeenCalled();
253
249
  vi.restoreAllMocks();
254
250
  });
255
- it('keeps explicit path pre-navigation for site-reused browser sessions', async () => {
251
+ it('keeps explicit path pre-navigation for persistent site sessions', async () => {
256
252
  const closeWindow = vi.fn().mockResolvedValue(undefined);
257
253
  const goto = vi.fn().mockResolvedValue(undefined);
258
254
  const mockPage = {
@@ -264,13 +260,13 @@ describe('executeCommand — non-browser timeout', () => {
264
260
  vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
265
261
  const cmd = cli({
266
262
  site: 'test-execution',
267
- name: 'browser-reuse-path-prenav', access: 'read',
263
+ name: 'site-session-path-prenav', access: 'read',
268
264
  description: 'test explicit path pre-navigation still runs',
269
265
  browser: true,
270
266
  strategy: Strategy.COOKIE,
271
267
  domain: 'example.com',
272
268
  navigateBefore: 'https://example.com/dashboard',
273
- browserSession: { reuse: 'site' },
269
+ siteSession: 'persistent',
274
270
  func: async () => [{ ok: true }],
275
271
  });
276
272
  await executeCommand(cmd, {});
package/dist/src/help.js CHANGED
@@ -35,6 +35,12 @@ const BROWSER_COMMON_OPTIONS = [
35
35
  help: 'Browser window mode: foreground or background',
36
36
  choices: ['foreground', 'background'],
37
37
  },
38
+ {
39
+ flags: '--site-session <mode>',
40
+ name: 'site-session',
41
+ help: 'Adapter site session lifecycle: ephemeral or persistent',
42
+ choices: ['ephemeral', 'persistent'],
43
+ },
38
44
  {
39
45
  flags: '--keep-tab <bool>',
40
46
  name: 'keep-tab',
@@ -323,7 +329,7 @@ function compactCommand(cmd) {
323
329
  command_options: commandOptions(cmd).map(compactArg),
324
330
  ...(cmd.browser ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
325
331
  example: formatCommandExample(cmd),
326
- ...(cmd.browserSession ? { browserSession: cmd.browserSession } : {}),
332
+ ...(cmd.siteSession ? { siteSession: cmd.siteSession } : {}),
327
333
  ...(cmd.defaultFormat ? { defaultFormat: cmd.defaultFormat } : {}),
328
334
  ...(cmd.columns?.length ? { columns: cmd.columns } : {}),
329
335
  };
package/dist/src/main.js CHANGED
@@ -27,25 +27,6 @@ const __dirname = path.dirname(__filename);
27
27
  // Use findPackageRoot so the path works both in dev (src/main.ts) and prod (dist/src/main.js).
28
28
  const BUILTIN_CLIS = path.join(findPackageRoot(__filename), 'clis');
29
29
  const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
30
- // ── Browser reuse flag ───────────────────────────────────────────────────
31
- // `--reuse` is a runtime-level browser session override rather than an adapter
32
- // arg. Strip it before Commander runs and expose it through an explicit env
33
- // name. Window/keep-tab are registered on browser-backed commands directly.
34
- {
35
- const reuseIdx = process.argv.findIndex(arg => arg === '--reuse' || arg.startsWith('--reuse='));
36
- if (reuseIdx !== -1) {
37
- const arg = process.argv[reuseIdx];
38
- const value = arg.startsWith('--reuse=')
39
- ? arg.slice('--reuse='.length)
40
- : process.argv[reuseIdx + 1];
41
- if (value !== 'none' && value !== 'site') {
42
- process.stderr.write(`--reuse must be one of: none, site. Received: "${value ?? ''}"\n`);
43
- process.exit(EXIT_CODES.USAGE_ERROR);
44
- }
45
- process.env.OPENCLI_BROWSER_REUSE = value;
46
- process.argv.splice(reuseIdx, arg.startsWith('--reuse=') ? 1 : 2);
47
- }
48
- }
49
30
  // ── Ultra-fast path: lightweight commands bypass full discovery ──────────
50
31
  // These are high-frequency or trivial paths that must not pay the startup tax.
51
32
  const argv = process.argv.slice(2);
@@ -36,8 +36,6 @@ export interface ManifestEntry {
36
36
  sourceFile?: string;
37
37
  /** Pre-navigation control — see CliCommand.navigateBefore */
38
38
  navigateBefore?: boolean | string;
39
- /** Browser session lifecycle defaults — see CliCommand.browserSession */
40
- browserSession?: {
41
- reuse?: 'none' | 'site';
42
- };
39
+ /** Site session lifecycle defaults — see CliCommand.siteSession */
40
+ siteSession?: 'ephemeral' | 'persistent';
43
41
  }
@@ -141,7 +141,7 @@ function renderSummary(session, events, opts) {
141
141
  `traceId: ${yamlScalar(session.id)}`,
142
142
  `status: ${opts.status}`,
143
143
  `contextId: ${yamlScalar(session.scope.contextId ?? 'default')}`,
144
- `workspace: ${yamlScalar(session.scope.workspace)}`,
144
+ `session: ${yamlScalar(session.scope.session)}`,
145
145
  ...(session.scope.target ? [`target: ${yamlScalar(session.scope.target)}`] : []),
146
146
  ...(session.scope.site ? [`site: ${yamlScalar(session.scope.site)}`] : []),
147
147
  ...(session.scope.command ? [`command: ${yamlScalar(session.scope.command)}`] : []),
@@ -17,7 +17,7 @@ describe('observation artifact', () => {
17
17
  id: 'trace-1',
18
18
  scope: {
19
19
  contextId: 'work',
20
- workspace: 'site:demo',
20
+ session: 'site:demo',
21
21
  site: 'demo',
22
22
  command: 'demo/run',
23
23
  adapterSourcePath: '/tmp/clis/demo/run.js',
@@ -69,7 +69,7 @@ describe('observation artifact', () => {
69
69
  expiresAt: expect.any(String),
70
70
  scope: {
71
71
  contextId: 'work',
72
- workspace: 'site:demo',
72
+ session: 'site:demo',
73
73
  site: 'demo',
74
74
  command: 'demo/run',
75
75
  adapterSourcePath: '/tmp/clis/demo/run.js',
@@ -107,7 +107,7 @@ describe('observation artifact', () => {
107
107
  fs.writeFileSync(path.join(oldTraceDir, 'trace.jsonl'), '{}\n', 'utf-8');
108
108
  const session = new ObservationSession({
109
109
  id: 'new-trace',
110
- scope: { contextId: 'work', workspace: 'site:demo' },
110
+ scope: { contextId: 'work', session: 'site:demo' },
111
111
  now: () => Date.parse('2026-05-03T00:00:00.000Z'),
112
112
  });
113
113
  session.record({ stream: 'action', name: 'command', phase: 'start' });
@@ -1,7 +1,7 @@
1
1
  export type ObservationStream = 'action' | 'network' | 'console' | 'screenshot' | 'state' | 'error';
2
2
  export interface ObservationScope {
3
3
  contextId?: string;
4
- workspace: string;
4
+ session: string;
5
5
  target?: string;
6
6
  site?: string;
7
7
  command?: string;
@@ -19,7 +19,7 @@ export class ObservationManager {
19
19
  }
20
20
  }
21
21
  function scopeMatches(actual, expected) {
22
- return actual.workspace === expected.workspace
22
+ return actual.session === expected.session
23
23
  && (expected.contextId === undefined || actual.contextId === expected.contextId)
24
24
  && (expected.target === undefined || actual.target === expected.target)
25
25
  && (expected.site === undefined || actual.site === expected.site)
@@ -3,10 +3,10 @@ import { ObservationManager } from './manager.js';
3
3
  describe('ObservationManager', () => {
4
4
  it('indexes sessions by id and scope', () => {
5
5
  const manager = new ObservationManager();
6
- const work = manager.start({ id: 'work-1', scope: { contextId: 'work', workspace: 'site:x', target: 'tab-1' } });
7
- manager.start({ id: 'personal-1', scope: { contextId: 'personal', workspace: 'site:x', target: 'tab-2' } });
6
+ const work = manager.start({ id: 'work-1', scope: { contextId: 'work', session: 'site:x', target: 'tab-1' } });
7
+ manager.start({ id: 'personal-1', scope: { contextId: 'personal', session: 'site:x', target: 'tab-2' } });
8
8
  expect(manager.get('work-1')).toBe(work);
9
- expect(manager.findByScope({ contextId: 'work', workspace: 'site:x' }).map((session) => session.id)).toEqual(['work-1']);
9
+ expect(manager.findByScope({ contextId: 'work', session: 'site:x' }).map((session) => session.id)).toEqual(['work-1']);
10
10
  expect(manager.stop('work-1')).toBe(work);
11
11
  expect(manager.get('work-1')).toBeUndefined();
12
12
  });
@@ -7,7 +7,7 @@
7
7
  * plugins are dynamically imported during discoverPlugins().
8
8
  */
9
9
  export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
10
- export type { CliCommand, Arg, CliOptions, CommandArgs, BrowserSessionOptions, BrowserSessionReuse } from './registry.js';
10
+ export type { CliCommand, Arg, CliOptions, CommandArgs, SiteSessionMode } from './registry.js';
11
11
  export type { IPage } from './types.js';
12
12
  export { onStartup, onBeforeExecute, onAfterExecute } from './hooks.js';
13
13
  export type { HookFn, HookContext, HookName } from './hooks.js';
@@ -23,16 +23,7 @@ export type CommandArgs = Record<string, any>;
23
23
  export type BrowserCommandFunc = (page: IPage, kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
24
24
  export type NonBrowserCommandFunc = (kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
25
25
  export type CommandAccess = 'read' | 'write';
26
- export type BrowserSessionReuse = 'none' | 'site';
27
- export interface BrowserSessionOptions {
28
- /**
29
- * Control whether browser-backed adapter commands reuse a stable tab lease.
30
- *
31
- * - `none`: one-shot workspace per command execution (default)
32
- * - `site`: all commands for this site share `site:<site>` until idle expiry
33
- */
34
- reuse?: BrowserSessionReuse;
35
- }
26
+ export type SiteSessionMode = 'ephemeral' | 'persistent';
36
27
  interface BaseCliCommand {
37
28
  site: string;
38
29
  name: string;
@@ -66,8 +57,8 @@ interface BaseCliCommand {
66
57
  * Adapter authors can set this explicitly to override the strategy-based default.
67
58
  */
68
59
  navigateBefore?: boolean | string;
69
- /** Browser session lifecycle defaults for adapter commands. */
70
- browserSession?: BrowserSessionOptions;
60
+ /** Site session lifecycle for adapter commands. */
61
+ siteSession?: SiteSessionMode;
71
62
  /** Override the default CLI output format when the user does not pass -f/--format. */
72
63
  defaultFormat?: 'table' | 'plain' | 'json' | 'yaml' | 'yml' | 'md' | 'markdown' | 'csv';
73
64
  }
@@ -27,7 +27,7 @@ export function cli(opts) {
27
27
  pipeline: opts.pipeline,
28
28
  footerExtra: opts.footerExtra,
29
29
  navigateBefore: opts.navigateBefore,
30
- browserSession: opts.browserSession,
30
+ siteSession: opts.siteSession,
31
31
  defaultFormat: opts.defaultFormat,
32
32
  };
33
33
  registerCommand(cmd);
@@ -57,7 +57,7 @@ export function strategyLabel(cmd) {
57
57
  */
58
58
  function normalizeCommand(cmd) {
59
59
  assertCommandAccess(cmd);
60
- assertBrowserSessionOptions(cmd);
60
+ assertSiteSession(cmd);
61
61
  const strategy = cmd.strategy ?? (cmd.browser === false ? Strategy.PUBLIC : Strategy.COOKIE);
62
62
  const browser = cmd.browser ?? (strategy !== Strategy.PUBLIC && strategy !== Strategy.LOCAL);
63
63
  let navigateBefore = cmd.navigateBefore;
@@ -82,16 +82,12 @@ function assertCommandAccess(cmd) {
82
82
  const key = `${cmd.site}/${cmd.name}`;
83
83
  throw new Error(`Command ${key} must declare access: 'read' | 'write'`);
84
84
  }
85
- function assertBrowserSessionOptions(cmd) {
86
- if (cmd.browserSession === undefined)
85
+ function assertSiteSession(cmd) {
86
+ if (cmd.siteSession === undefined)
87
87
  return;
88
88
  const key = `${cmd.site}/${cmd.name}`;
89
- if (cmd.browserSession === null || typeof cmd.browserSession !== 'object' || Array.isArray(cmd.browserSession)) {
90
- throw new Error(`Command ${key} browserSession must be an object`);
91
- }
92
- const reuse = cmd.browserSession.reuse;
93
- if (reuse !== undefined && reuse !== 'none' && reuse !== 'site') {
94
- throw new Error(`Command ${key} browserSession.reuse must be one of: none, site`);
89
+ if (cmd.siteSession !== 'ephemeral' && cmd.siteSession !== 'persistent') {
90
+ throw new Error(`Command ${key} siteSession must be one of: ephemeral, persistent`);
95
91
  }
96
92
  }
97
93
  export function registerCommand(cmd) {
@@ -7,6 +7,7 @@ export declare function getBrowserFactory(site?: string): new () => IBrowserFact
7
7
  export declare const DEFAULT_BROWSER_CONNECT_TIMEOUT: number;
8
8
  export declare const DEFAULT_BROWSER_COMMAND_TIMEOUT: number;
9
9
  export type BrowserWindowMode = 'foreground' | 'background';
10
+ export type BrowserSurface = 'browser' | 'adapter';
10
11
  /**
11
12
  * Timeout with seconds unit. Used for high-level command timeouts.
12
13
  */
@@ -25,18 +26,22 @@ export declare function withTimeoutMs<T>(promise: Promise<T>, timeoutMs: number,
25
26
  export interface IBrowserFactory {
26
27
  connect(opts?: {
27
28
  timeout?: number;
28
- workspace?: string;
29
+ session?: string;
29
30
  cdpEndpoint?: string;
30
31
  contextId?: string;
31
32
  idleTimeout?: number;
32
33
  windowMode?: BrowserWindowMode;
34
+ surface?: BrowserSurface;
35
+ siteSession?: 'ephemeral' | 'persistent';
33
36
  }): Promise<IPage>;
34
37
  close(): Promise<void>;
35
38
  }
36
39
  export declare function browserSession<T>(BrowserFactory: new () => IBrowserFactory, fn: (page: IPage) => Promise<T>, opts?: {
37
- workspace?: string;
40
+ session?: string;
38
41
  cdpEndpoint?: string;
39
42
  contextId?: string;
40
43
  idleTimeout?: number;
41
44
  windowMode?: BrowserWindowMode;
45
+ surface?: BrowserSurface;
46
+ siteSession?: 'ephemeral' | 'persistent';
42
47
  }): Promise<T>;
@@ -50,11 +50,13 @@ export async function browserSession(BrowserFactory, fn, opts = {}) {
50
50
  try {
51
51
  const page = await browser.connect({
52
52
  timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT,
53
- workspace: opts.workspace,
53
+ session: opts.session,
54
54
  cdpEndpoint: opts.cdpEndpoint,
55
55
  contextId: opts.contextId,
56
56
  idleTimeout: opts.idleTimeout,
57
57
  windowMode: opts.windowMode,
58
+ surface: opts.surface,
59
+ siteSession: opts.siteSession,
58
60
  });
59
61
  return await fn(page);
60
62
  }
@@ -32,7 +32,7 @@ export declare function serializeCommand(cmd: CliCommand): {
32
32
  domain: string | null;
33
33
  example: string;
34
34
  defaultFormat: "table" | "plain" | "json" | "yaml" | "yml" | "md" | "markdown" | "csv" | null;
35
- browserSession: import("./registry.js").BrowserSessionOptions | null;
35
+ siteSession: import("./registry.js").SiteSessionMode | null;
36
36
  };
37
37
  /** Human-readable arg summary: `<required> [optional]` style. */
38
38
  export declare function formatArgSummary(args: Arg[]): string;
@@ -34,7 +34,7 @@ export function serializeCommand(cmd) {
34
34
  domain: cmd.domain ?? null,
35
35
  example: formatCommandExample(cmd),
36
36
  defaultFormat: cmd.defaultFormat ?? null,
37
- browserSession: cmd.browserSession ?? null,
37
+ siteSession: cmd.siteSession ?? null,
38
38
  };
39
39
  }
40
40
  // ── Formatting ──────────────────────────────────────────────────────────────
@@ -60,25 +60,10 @@ export interface FetchJsonOptions {
60
60
  body?: unknown;
61
61
  timeoutMs?: number;
62
62
  }
63
- export interface BrowserSessionInfo {
64
- workspace?: string;
65
- connected?: boolean;
66
- windowId?: number;
67
- preferredTabId?: number | null;
68
- owned?: boolean;
69
- ownership?: 'owned' | 'borrowed';
70
- lifecycle?: 'ephemeral' | 'persistent' | 'pinned';
71
- windowRole?: 'interactive' | 'automation' | 'borrowed-user';
72
- contextId?: string;
73
- tabCount?: number;
74
- idleMsRemaining?: number | null;
75
- [key: string]: unknown;
76
- }
77
63
  export interface IPage {
78
64
  goto(url: string, options?: {
79
65
  waitUntil?: 'load' | 'none';
80
66
  settleMs?: number;
81
- allowBoundNavigation?: boolean;
82
67
  }): Promise<void>;
83
68
  evaluate(js: string): Promise<any>;
84
69
  /** Safely evaluate JS with pre-serialized arguments — prevents injection. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "1.7.16",
3
+ "version": "1.7.18",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },