@jackwener/opencli 1.7.15 → 1.7.17

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 (172) hide show
  1. package/README.md +15 -13
  2. package/README.zh-CN.md +15 -12
  3. package/cli-manifest.json +165 -209
  4. package/clis/chatgpt/ask.js +3 -2
  5. package/clis/chatgpt/commands.test.js +2 -2
  6. package/clis/chatgpt/detail.js +7 -2
  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 +3 -2
  12. package/clis/chatgpt/send.js +3 -2
  13. package/clis/chatgpt/status.js +1 -1
  14. package/clis/chatgpt/utils.js +259 -25
  15. package/clis/chatgpt/utils.test.js +166 -2
  16. package/clis/claude/ask.js +23 -8
  17. package/clis/claude/detail.js +10 -3
  18. package/clis/claude/history.js +1 -1
  19. package/clis/claude/new.js +9 -3
  20. package/clis/claude/read.js +3 -2
  21. package/clis/claude/send.js +9 -4
  22. package/clis/claude/status.js +1 -1
  23. package/clis/claude/utils.js +27 -4
  24. package/clis/deepseek/ask.js +22 -9
  25. package/clis/deepseek/detail.js +10 -2
  26. package/clis/deepseek/history.js +1 -1
  27. package/clis/deepseek/new.js +14 -3
  28. package/clis/deepseek/read.js +3 -2
  29. package/clis/deepseek/send.js +1 -1
  30. package/clis/deepseek/status.js +1 -1
  31. package/clis/deepseek/utils.js +8 -1
  32. package/clis/doubao/ask.js +1 -1
  33. package/clis/doubao/detail.js +1 -1
  34. package/clis/doubao/history.js +1 -1
  35. package/clis/doubao/meeting-summary.js +1 -1
  36. package/clis/doubao/meeting-transcript.js +1 -1
  37. package/clis/doubao/new.js +1 -1
  38. package/clis/doubao/read.js +1 -1
  39. package/clis/doubao/send.js +1 -1
  40. package/clis/doubao/status.js +1 -1
  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/linkedin/search.js +8 -11
  55. package/clis/maimai/search-talents.js +10 -6
  56. package/clis/notebooklm/current.js +1 -1
  57. package/clis/notebooklm/get.js +1 -1
  58. package/clis/notebooklm/history.js +1 -1
  59. package/clis/notebooklm/note-list.js +1 -1
  60. package/clis/notebooklm/notes-get.js +1 -1
  61. package/clis/notebooklm/open.js +2 -2
  62. package/clis/notebooklm/open.test.js +1 -1
  63. package/clis/notebooklm/source-fulltext.js +1 -1
  64. package/clis/notebooklm/source-get.js +1 -1
  65. package/clis/notebooklm/source-guide.js +1 -1
  66. package/clis/notebooklm/source-list.js +1 -1
  67. package/clis/notebooklm/summary.js +1 -1
  68. package/clis/openreview/author.js +58 -0
  69. package/clis/openreview/openreview.test.js +83 -1
  70. package/clis/openreview/utils.js +14 -0
  71. package/clis/qwen/ask.js +1 -1
  72. package/clis/qwen/detail.js +1 -1
  73. package/clis/qwen/history.js +1 -1
  74. package/clis/qwen/image.js +1 -1
  75. package/clis/qwen/new.js +1 -1
  76. package/clis/qwen/read.js +1 -1
  77. package/clis/qwen/send.js +1 -1
  78. package/clis/qwen/status.js +1 -1
  79. package/clis/reddit/comment.js +1 -0
  80. package/clis/reddit/frontpage.js +1 -0
  81. package/clis/reddit/popular.js +1 -0
  82. package/clis/reddit/read.js +2 -0
  83. package/clis/reddit/read.test.js +4 -0
  84. package/clis/reddit/save.js +1 -0
  85. package/clis/reddit/saved.js +1 -0
  86. package/clis/reddit/search.js +1 -0
  87. package/clis/reddit/subreddit.js +1 -0
  88. package/clis/reddit/subscribe.js +1 -0
  89. package/clis/reddit/upvote.js +1 -0
  90. package/clis/reddit/upvoted.js +1 -0
  91. package/clis/reddit/user-comments.js +1 -0
  92. package/clis/reddit/user-posts.js +1 -0
  93. package/clis/reddit/user.js +1 -0
  94. package/clis/twitter/article.js +7 -4
  95. package/clis/twitter/bookmark-folder.js +3 -5
  96. package/clis/twitter/bookmark-folder.test.js +5 -2
  97. package/clis/twitter/bookmark-folders.js +3 -5
  98. package/clis/twitter/bookmark-folders.test.js +3 -1
  99. package/clis/twitter/bookmarks.js +3 -5
  100. package/clis/twitter/download.js +1 -0
  101. package/clis/twitter/followers.js +1 -0
  102. package/clis/twitter/following.js +3 -6
  103. package/clis/twitter/following.test.js +2 -1
  104. package/clis/twitter/likes.js +3 -5
  105. package/clis/twitter/list-add.js +4 -3
  106. package/clis/twitter/list-add.test.js +23 -1
  107. package/clis/twitter/list-remove.js +4 -3
  108. package/clis/twitter/list-remove.test.js +23 -1
  109. package/clis/twitter/list-tweets.js +3 -5
  110. package/clis/twitter/lists.js +3 -5
  111. package/clis/twitter/notifications.js +1 -0
  112. package/clis/twitter/profile.js +7 -4
  113. package/clis/twitter/search.js +1 -0
  114. package/clis/twitter/thread.js +5 -7
  115. package/clis/twitter/timeline.js +5 -7
  116. package/clis/twitter/trending.js +4 -4
  117. package/clis/twitter/tweets.js +3 -6
  118. package/clis/youtube/like.js +6 -2
  119. package/clis/youtube/subscribe.js +6 -2
  120. package/clis/youtube/unlike.js +6 -2
  121. package/clis/youtube/unsubscribe.js +6 -2
  122. package/clis/youtube/utils.js +19 -13
  123. package/clis/youtube/utils.test.js +17 -1
  124. package/clis/yuanbao/ask.js +1 -1
  125. package/clis/yuanbao/detail.js +1 -1
  126. package/clis/yuanbao/history.js +1 -1
  127. package/clis/yuanbao/new.js +1 -1
  128. package/clis/yuanbao/read.js +1 -1
  129. package/clis/yuanbao/send.js +1 -1
  130. package/clis/yuanbao/status.js +1 -1
  131. package/dist/src/browser/bridge.d.ts +4 -1
  132. package/dist/src/browser/bridge.js +3 -1
  133. package/dist/src/browser/cdp.d.ts +4 -1
  134. package/dist/src/browser/daemon-client.d.ts +9 -16
  135. package/dist/src/browser/daemon-client.js +8 -9
  136. package/dist/src/browser/daemon-client.test.js +10 -0
  137. package/dist/src/browser/network-cache.d.ts +5 -5
  138. package/dist/src/browser/network-cache.js +8 -8
  139. package/dist/src/browser/network-cache.test.js +4 -4
  140. package/dist/src/browser/page.d.ts +9 -7
  141. package/dist/src/browser/page.js +27 -16
  142. package/dist/src/browser/page.test.js +60 -30
  143. package/dist/src/build-manifest.js +1 -1
  144. package/dist/src/cli.js +91 -125
  145. package/dist/src/cli.test.js +293 -180
  146. package/dist/src/commanderAdapter.js +9 -0
  147. package/dist/src/discovery.js +1 -1
  148. package/dist/src/doctor.d.ts +0 -4
  149. package/dist/src/doctor.js +8 -72
  150. package/dist/src/doctor.test.js +26 -97
  151. package/dist/src/execution.d.ts +3 -0
  152. package/dist/src/execution.js +47 -23
  153. package/dist/src/execution.test.js +68 -45
  154. package/dist/src/external-clis.yaml +24 -0
  155. package/dist/src/help.d.ts +1 -0
  156. package/dist/src/help.js +36 -1
  157. package/dist/src/main.js +0 -29
  158. package/dist/src/manifest-types.d.ts +2 -4
  159. package/dist/src/observation/artifact.js +1 -1
  160. package/dist/src/observation/artifact.test.js +3 -3
  161. package/dist/src/observation/events.d.ts +1 -1
  162. package/dist/src/observation/manager.js +1 -1
  163. package/dist/src/observation/manager.test.js +3 -3
  164. package/dist/src/registry-api.d.ts +1 -1
  165. package/dist/src/registry.d.ts +3 -12
  166. package/dist/src/registry.js +6 -10
  167. package/dist/src/runtime.d.ts +10 -2
  168. package/dist/src/runtime.js +4 -1
  169. package/dist/src/serialization.d.ts +1 -1
  170. package/dist/src/serialization.js +1 -1
  171. package/dist/src/types.d.ts +0 -15
  172. 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 });
161
- expect(sessionOpts[1]).toMatchObject({ workspace: 'site:test-execution', idleTimeout: 600 });
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,15 +184,17 @@ 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();
192
+ expect(sessionOpts[0]?.windowMode).toBe('background');
193
+ expect(sessionOpts[1]?.windowMode).toBe('background');
190
194
  expect(closeWindow).toHaveBeenCalledTimes(2);
191
195
  vi.restoreAllMocks();
192
196
  });
193
- it('lets user --reuse none override adapter reuse metadata', async () => {
197
+ it('lets user --site-session ephemeral override adapter persistent metadata', async () => {
194
198
  const closeWindow = vi.fn().mockResolvedValue(undefined);
195
199
  const mockPage = { closeWindow };
196
200
  const sessionOpts = [];
@@ -199,33 +203,27 @@ describe('executeCommand — non-browser timeout', () => {
199
203
  sessionOpts.push(opts ?? {});
200
204
  return fn(mockPage);
201
205
  });
202
- const prev = process.env.OPENCLI_BROWSER_REUSE;
203
- process.env.OPENCLI_BROWSER_REUSE = 'none';
204
206
  try {
205
207
  const cmd = cli({
206
208
  site: 'test-execution',
207
- name: 'browser-reuse-override-none', access: 'read',
208
- description: 'test user reuse override',
209
+ name: 'site-session-override-ephemeral', access: 'read',
210
+ description: 'test user site-session override',
209
211
  browser: true,
210
212
  strategy: Strategy.PUBLIC,
211
- browserSession: { reuse: 'site' },
213
+ siteSession: 'persistent',
212
214
  func: async () => [{ ok: true }],
213
215
  });
214
- await executeCommand(cmd, {});
216
+ await executeCommand(cmd, {}, false, { siteSession: 'ephemeral' });
215
217
  expect(sessionOpts).toHaveLength(1);
216
- expect(sessionOpts[0]?.workspace).toMatch(/^site:test-execution:/);
218
+ expect(sessionOpts[0]?.session).toMatch(/^site:test-execution:/);
217
219
  expect(sessionOpts[0]?.idleTimeout).toBeUndefined();
218
220
  expect(closeWindow).toHaveBeenCalledTimes(1);
219
221
  }
220
222
  finally {
221
- if (prev === undefined)
222
- delete process.env.OPENCLI_BROWSER_REUSE;
223
- else
224
- process.env.OPENCLI_BROWSER_REUSE = prev;
225
223
  vi.restoreAllMocks();
226
224
  }
227
225
  });
228
- it('skips repeated domain pre-navigation for site-reused browser sessions', async () => {
226
+ it('skips repeated domain pre-navigation for persistent site sessions', async () => {
229
227
  const closeWindow = vi.fn().mockResolvedValue(undefined);
230
228
  const goto = vi.fn().mockResolvedValue(undefined);
231
229
  const mockPage = {
@@ -237,12 +235,12 @@ describe('executeCommand — non-browser timeout', () => {
237
235
  vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
238
236
  const cmd = cli({
239
237
  site: 'test-execution',
240
- name: 'browser-reuse-skip-prenav', access: 'read',
238
+ name: 'site-session-skip-prenav', access: 'read',
241
239
  description: 'test reused same-domain tabs do not reset conversation state',
242
240
  browser: true,
243
241
  strategy: Strategy.COOKIE,
244
242
  domain: 'grok.com',
245
- browserSession: { reuse: 'site' },
243
+ siteSession: 'persistent',
246
244
  func: async () => [{ ok: true }],
247
245
  });
248
246
  await executeCommand(cmd, {});
@@ -250,7 +248,7 @@ describe('executeCommand — non-browser timeout', () => {
250
248
  expect(closeWindow).not.toHaveBeenCalled();
251
249
  vi.restoreAllMocks();
252
250
  });
253
- it('keeps explicit path pre-navigation for site-reused browser sessions', async () => {
251
+ it('keeps explicit path pre-navigation for persistent site sessions', async () => {
254
252
  const closeWindow = vi.fn().mockResolvedValue(undefined);
255
253
  const goto = vi.fn().mockResolvedValue(undefined);
256
254
  const mockPage = {
@@ -262,13 +260,13 @@ describe('executeCommand — non-browser timeout', () => {
262
260
  vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
263
261
  const cmd = cli({
264
262
  site: 'test-execution',
265
- name: 'browser-reuse-path-prenav', access: 'read',
263
+ name: 'site-session-path-prenav', access: 'read',
266
264
  description: 'test explicit path pre-navigation still runs',
267
265
  browser: true,
268
266
  strategy: Strategy.COOKIE,
269
267
  domain: 'example.com',
270
268
  navigateBefore: 'https://example.com/dashboard',
271
- browserSession: { reuse: 'site' },
269
+ siteSession: 'persistent',
272
270
  func: async () => [{ ok: true }],
273
271
  });
274
272
  await executeCommand(cmd, {});
@@ -372,18 +370,18 @@ describe('executeCommand — non-browser timeout', () => {
372
370
  expect(closeWindow).toHaveBeenCalledTimes(1);
373
371
  vi.restoreAllMocks();
374
372
  });
375
- it('skips closeWindow when OPENCLI_LIVE=1 (success path)', async () => {
373
+ it('skips closeWindow when OPENCLI_KEEP_TAB=true (success path)', async () => {
376
374
  const closeWindow = vi.fn().mockResolvedValue(undefined);
377
375
  const mockPage = { closeWindow };
378
376
  vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
379
377
  vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
380
- const prev = process.env.OPENCLI_LIVE;
381
- process.env.OPENCLI_LIVE = '1';
378
+ const prev = process.env.OPENCLI_KEEP_TAB;
379
+ process.env.OPENCLI_KEEP_TAB = 'true';
382
380
  try {
383
381
  const cmd = cli({
384
382
  site: 'test-execution',
385
- name: 'browser-live-success', access: 'read',
386
- description: 'test closeWindow skipped with --live on success',
383
+ name: 'browser-keep-tab-success', access: 'read',
384
+ description: 'test closeWindow skipped with --keep-tab on success',
387
385
  browser: true,
388
386
  strategy: Strategy.PUBLIC,
389
387
  func: async () => [{ ok: true }],
@@ -393,24 +391,24 @@ describe('executeCommand — non-browser timeout', () => {
393
391
  }
394
392
  finally {
395
393
  if (prev === undefined)
396
- delete process.env.OPENCLI_LIVE;
394
+ delete process.env.OPENCLI_KEEP_TAB;
397
395
  else
398
- process.env.OPENCLI_LIVE = prev;
396
+ process.env.OPENCLI_KEEP_TAB = prev;
399
397
  vi.restoreAllMocks();
400
398
  }
401
399
  });
402
- it('skips closeWindow when OPENCLI_LIVE=1 (failure path)', async () => {
400
+ it('skips closeWindow when OPENCLI_KEEP_TAB=true (failure path)', async () => {
403
401
  const closeWindow = vi.fn().mockResolvedValue(undefined);
404
402
  const mockPage = { closeWindow };
405
403
  vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
406
404
  vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
407
- const prev = process.env.OPENCLI_LIVE;
408
- process.env.OPENCLI_LIVE = '1';
405
+ const prev = process.env.OPENCLI_KEEP_TAB;
406
+ process.env.OPENCLI_KEEP_TAB = 'true';
409
407
  try {
410
408
  const cmd = cli({
411
409
  site: 'test-execution',
412
- name: 'browser-live-failure', access: 'read',
413
- description: 'test closeWindow skipped with --live on failure',
410
+ name: 'browser-keep-tab-failure', access: 'read',
411
+ description: 'test closeWindow skipped with --keep-tab on failure',
414
412
  browser: true,
415
413
  strategy: Strategy.PUBLIC,
416
414
  func: async () => { throw new Error('adapter failure'); },
@@ -420,12 +418,37 @@ describe('executeCommand — non-browser timeout', () => {
420
418
  }
421
419
  finally {
422
420
  if (prev === undefined)
423
- delete process.env.OPENCLI_LIVE;
421
+ delete process.env.OPENCLI_KEEP_TAB;
424
422
  else
425
- process.env.OPENCLI_LIVE = prev;
423
+ process.env.OPENCLI_KEEP_TAB = prev;
426
424
  vi.restoreAllMocks();
427
425
  }
428
426
  });
427
+ it('lets browser common options override adapter window and keep-tab defaults', async () => {
428
+ const closeWindow = vi.fn().mockResolvedValue(undefined);
429
+ const mockPage = { closeWindow };
430
+ const sessionOpts = [];
431
+ vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
432
+ vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn, opts) => {
433
+ sessionOpts.push(opts ?? {});
434
+ return fn(mockPage);
435
+ });
436
+ const cmd = cli({
437
+ site: 'test-execution',
438
+ name: 'browser-window-options', access: 'read',
439
+ description: 'test browser common options',
440
+ browser: true,
441
+ strategy: Strategy.PUBLIC,
442
+ func: async () => [{ ok: true }],
443
+ });
444
+ await executeCommand(cmd, {}, false, {
445
+ windowMode: 'foreground',
446
+ keepTab: 'true',
447
+ });
448
+ expect(sessionOpts[0]).toMatchObject({ windowMode: 'foreground' });
449
+ expect(closeWindow).not.toHaveBeenCalled();
450
+ vi.restoreAllMocks();
451
+ });
429
452
  it('does not re-run custom validation when args are already prepared', async () => {
430
453
  const validateArgs = vi.fn();
431
454
  const cmd = {
@@ -54,3 +54,27 @@
54
54
  tags: [vercel, deployment, serverless, frontend, devops]
55
55
  install:
56
56
  default: "npm install -g vercel"
57
+
58
+ - name: tg-cli
59
+ binary: tg
60
+ description: "Telegram CLI — local-first sync, search, export via MTProto for AI agents"
61
+ homepage: "https://github.com/jackwener/tg-cli"
62
+ tags: [telegram, messaging, search, export, ai-agent]
63
+ install:
64
+ default: "uv tool install kabi-tg-cli"
65
+
66
+ - name: discord-cli
67
+ binary: discord
68
+ description: "Discord CLI — local-first sync, search, export via SQLite for AI agents"
69
+ homepage: "https://github.com/jackwener/discord-cli"
70
+ tags: [discord, messaging, search, export, ai-agent]
71
+ install:
72
+ default: "uv tool install kabi-discord-cli"
73
+
74
+ - name: wx-cli
75
+ binary: wx
76
+ description: "WeChat local data CLI — sessions, messages, search, contacts, export for AI agents"
77
+ homepage: "https://github.com/jackwener/wx-cli"
78
+ tags: [wechat, messaging, search, export, ai-agent]
79
+ install:
80
+ default: "npm install -g @jackwener/wx-cli"
@@ -66,6 +66,7 @@ export declare function rootHelpData(program: Command, groups: RootAdapterGroups
66
66
  export declare function siteHelpData(site: string, commands: readonly CliCommand[]): Record<string, unknown>;
67
67
  export declare function commandHelpData(cmd: CliCommand): Record<string, unknown>;
68
68
  export declare function formatCommonOptionsHelpText(): string;
69
+ export declare function formatBrowserCommonOptionsHelpText(): string;
69
70
  export declare function formatSiteHelpText(site: string, commands: readonly CliCommand[]): string;
70
71
  export declare function formatCommandHelpText(cmd: CliCommand): string;
71
72
  export declare function installStructuredHelp(command: Command, data: () => unknown, textSuffix?: string | (() => string)): void;
package/dist/src/help.js CHANGED
@@ -28,6 +28,26 @@ const COMMON_OPTIONS = [
28
28
  help: 'display help for command',
29
29
  },
30
30
  ];
31
+ const BROWSER_COMMON_OPTIONS = [
32
+ {
33
+ flags: '--window <mode>',
34
+ name: 'window',
35
+ help: 'Browser window mode: foreground or background',
36
+ choices: ['foreground', 'background'],
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
+ },
44
+ {
45
+ flags: '--keep-tab <bool>',
46
+ name: 'keep-tab',
47
+ help: 'Keep the browser tab lease after the command finishes',
48
+ choices: ['true', 'false'],
49
+ },
50
+ ];
31
51
  function normalizeStructuredHelpFormat(value) {
32
52
  const normalized = value?.toLowerCase();
33
53
  if (normalized === 'yaml' || normalized === 'yml')
@@ -307,8 +327,9 @@ function compactCommand(cmd) {
307
327
  ...(cmd.aliases?.length ? { aliases: cmd.aliases } : {}),
308
328
  positionals: positionals(cmd).map(compactArg),
309
329
  command_options: commandOptions(cmd).map(compactArg),
330
+ ...(cmd.browser ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
310
331
  example: formatCommandExample(cmd),
311
- ...(cmd.browserSession ? { browserSession: cmd.browserSession } : {}),
332
+ ...(cmd.siteSession ? { siteSession: cmd.siteSession } : {}),
312
333
  ...(cmd.defaultFormat ? { defaultFormat: cmd.defaultFormat } : {}),
313
334
  ...(cmd.columns?.length ? { columns: cmd.columns } : {}),
314
335
  };
@@ -353,6 +374,7 @@ export function siteHelpData(site, commands) {
353
374
  command_count: unique.length,
354
375
  commands: unique.map(cmd => compactCommand(cmd)),
355
376
  common_options: COMMON_OPTIONS.map(compactCommonOption),
377
+ ...(unique.some(cmd => cmd.browser) ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
356
378
  next: [
357
379
  `opencli ${site} <command> --help -f yaml`,
358
380
  `opencli ${site} <command> -f yaml`,
@@ -364,6 +386,7 @@ export function commandHelpData(cmd) {
364
386
  site: cmd.site,
365
387
  ...compactCommand(cmd),
366
388
  common_options: COMMON_OPTIONS.map(compactCommonOption),
389
+ ...(cmd.browser ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
367
390
  output_formats: ['table', 'plain', 'yaml', 'json', 'md', 'csv'],
368
391
  };
369
392
  }
@@ -394,6 +417,15 @@ export function formatCommonOptionsHelpText() {
394
417
  });
395
418
  return ['Common options:', ...formatRows(rows)].join('\n');
396
419
  }
420
+ export function formatBrowserCommonOptionsHelpText() {
421
+ const rows = BROWSER_COMMON_OPTIONS.map(option => {
422
+ const details = [option.help];
423
+ if ('choices' in option)
424
+ details.push(`choices: ${option.choices.join(', ')}`);
425
+ return [option.flags, details.join(' ')];
426
+ });
427
+ return ['Browser common options:', ...formatRows(rows)].join('\n');
428
+ }
397
429
  export function formatSiteHelpText(site, commands) {
398
430
  const unique = [...new Map(commands.map(cmd => [fullName(cmd), cmd])).values()]
399
431
  .sort((a, b) => a.name.localeCompare(b.name));
@@ -406,6 +438,7 @@ export function formatSiteHelpText(site, commands) {
406
438
  ...formatRows(unique.map(cmd => [formatCommandListTerm(cmd), formatSiteCommandDescription(cmd)])),
407
439
  '',
408
440
  formatCommonOptionsHelpText(),
441
+ ...(unique.some(cmd => cmd.browser) ? ['', formatBrowserCommonOptionsHelpText()] : []),
409
442
  '',
410
443
  `Agent tip: use 'opencli ${site} --help -f yaml' to get all command args/options in one structured response.`,
411
444
  '',
@@ -434,6 +467,8 @@ export function formatCommandHelpText(cmd) {
434
467
  lines.push('Command options:', ...formatRows(optionRows), '');
435
468
  }
436
469
  lines.push(formatCommonOptionsHelpText(), '');
470
+ if (cmd.browser)
471
+ lines.push(formatBrowserCommonOptionsHelpText(), '');
437
472
  const meta = [];
438
473
  meta.push(`Access: ${cmd.access}`);
439
474
  meta.push(`Browser: ${cmd.browser ? 'yes' : 'no'}`);
package/dist/src/main.js CHANGED
@@ -27,35 +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
- // ── Session lifecycle flags ──────────────────────────────────────────────
31
- // `--live` / `--focus` / `--reuse` are top-level-ish toggles that tweak the automation
32
- // window's lifecycle. We strip them from argv before Commander runs so they
33
- // can be placed anywhere and work on any subcommand (adapter or browser).
34
- {
35
- const liveIdx = process.argv.indexOf('--live');
36
- if (liveIdx !== -1) {
37
- process.env.OPENCLI_LIVE = '1';
38
- process.argv.splice(liveIdx, 1);
39
- }
40
- const focusIdx = process.argv.indexOf('--focus');
41
- if (focusIdx !== -1) {
42
- process.env.OPENCLI_WINDOW_FOCUSED = '1';
43
- process.argv.splice(focusIdx, 1);
44
- }
45
- const reuseIdx = process.argv.findIndex(arg => arg === '--reuse' || arg.startsWith('--reuse='));
46
- if (reuseIdx !== -1) {
47
- const arg = process.argv[reuseIdx];
48
- const value = arg.startsWith('--reuse=')
49
- ? arg.slice('--reuse='.length)
50
- : process.argv[reuseIdx + 1];
51
- if (value !== 'none' && value !== 'site') {
52
- process.stderr.write(`--reuse must be one of: none, site. Received: "${value ?? ''}"\n`);
53
- process.exit(EXIT_CODES.USAGE_ERROR);
54
- }
55
- process.env.OPENCLI_BROWSER_REUSE = value;
56
- process.argv.splice(reuseIdx, arg.startsWith('--reuse=') ? 1 : 2);
57
- }
58
- }
59
30
  // ── Ultra-fast path: lightweight commands bypass full discovery ──────────
60
31
  // These are high-frequency or trivial paths that must not pay the startup tax.
61
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) {
@@ -6,6 +6,8 @@ import type { IPage } from './types.js';
6
6
  export declare function getBrowserFactory(site?: string): new () => IBrowserFactory;
7
7
  export declare const DEFAULT_BROWSER_CONNECT_TIMEOUT: number;
8
8
  export declare const DEFAULT_BROWSER_COMMAND_TIMEOUT: number;
9
+ export type BrowserWindowMode = 'foreground' | 'background';
10
+ export type BrowserSurface = 'browser' | 'adapter';
9
11
  /**
10
12
  * Timeout with seconds unit. Used for high-level command timeouts.
11
13
  */
@@ -24,16 +26,22 @@ export declare function withTimeoutMs<T>(promise: Promise<T>, timeoutMs: number,
24
26
  export interface IBrowserFactory {
25
27
  connect(opts?: {
26
28
  timeout?: number;
27
- workspace?: string;
29
+ session?: string;
28
30
  cdpEndpoint?: string;
29
31
  contextId?: string;
30
32
  idleTimeout?: number;
33
+ windowMode?: BrowserWindowMode;
34
+ surface?: BrowserSurface;
35
+ siteSession?: 'ephemeral' | 'persistent';
31
36
  }): Promise<IPage>;
32
37
  close(): Promise<void>;
33
38
  }
34
39
  export declare function browserSession<T>(BrowserFactory: new () => IBrowserFactory, fn: (page: IPage) => Promise<T>, opts?: {
35
- workspace?: string;
40
+ session?: string;
36
41
  cdpEndpoint?: string;
37
42
  contextId?: string;
38
43
  idleTimeout?: number;
44
+ windowMode?: BrowserWindowMode;
45
+ surface?: BrowserSurface;
46
+ siteSession?: 'ephemeral' | 'persistent';
39
47
  }): Promise<T>;
@@ -50,10 +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
+ windowMode: opts.windowMode,
58
+ surface: opts.surface,
59
+ siteSession: opts.siteSession,
57
60
  });
58
61
  return await fn(page);
59
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 ──────────────────────────────────────────────────────────────