@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.
- package/README.md +11 -9
- package/README.zh-CN.md +10 -8
- package/cli-manifest.json +377 -271
- package/clis/chatgpt/ask.js +1 -1
- package/clis/chatgpt/commands.test.js +2 -2
- package/clis/chatgpt/detail.js +1 -1
- package/clis/chatgpt/history.js +1 -1
- package/clis/chatgpt/image.js +38 -4
- package/clis/chatgpt/image.test.js +68 -1
- package/clis/chatgpt/new.js +1 -1
- package/clis/chatgpt/read.js +1 -1
- package/clis/chatgpt/send.js +1 -1
- package/clis/chatgpt/status.js +1 -1
- package/clis/chatgpt/utils.js +208 -16
- package/clis/chatgpt/utils.test.js +131 -2
- package/clis/claude/ask.js +1 -1
- package/clis/claude/detail.js +1 -1
- package/clis/claude/history.js +1 -1
- package/clis/claude/new.js +1 -1
- package/clis/claude/read.js +1 -1
- package/clis/claude/send.js +1 -1
- package/clis/claude/status.js +1 -1
- package/clis/deepseek/ask.js +1 -1
- package/clis/deepseek/detail.js +1 -1
- package/clis/deepseek/history.js +1 -1
- package/clis/deepseek/new.js +1 -1
- package/clis/deepseek/read.js +1 -1
- package/clis/deepseek/send.js +1 -1
- package/clis/deepseek/status.js +1 -1
- package/clis/doubao/ask.js +1 -1
- package/clis/doubao/detail.js +1 -1
- package/clis/doubao/history.js +1 -1
- package/clis/doubao/meeting-summary.js +1 -1
- package/clis/doubao/meeting-transcript.js +1 -1
- package/clis/doubao/new.js +1 -1
- package/clis/doubao/read.js +1 -1
- package/clis/doubao/send.js +1 -1
- package/clis/doubao/status.js +1 -1
- package/clis/doubao/utils.js +17 -0
- package/clis/doubao/utils.test.js +61 -0
- package/clis/gemini/ask.js +1 -1
- package/clis/gemini/deep-research-result.js +1 -1
- package/clis/gemini/deep-research.js +1 -1
- package/clis/gemini/image.js +1 -1
- package/clis/gemini/new.js +1 -1
- package/clis/grok/ask.js +1 -1
- package/clis/grok/detail.js +1 -1
- package/clis/grok/history.js +1 -1
- package/clis/grok/image.js +1 -1
- package/clis/grok/new.js +1 -1
- package/clis/grok/read.js +1 -1
- package/clis/grok/send.js +1 -1
- package/clis/grok/status.js +1 -1
- package/clis/notebooklm/current.js +1 -1
- package/clis/notebooklm/get.js +1 -1
- package/clis/notebooklm/history.js +1 -1
- package/clis/notebooklm/note-list.js +1 -1
- package/clis/notebooklm/notes-get.js +1 -1
- package/clis/notebooklm/open.js +2 -2
- package/clis/notebooklm/open.test.js +1 -1
- package/clis/notebooklm/source-fulltext.js +1 -1
- package/clis/notebooklm/source-get.js +1 -1
- package/clis/notebooklm/source-guide.js +1 -1
- package/clis/notebooklm/source-list.js +1 -1
- package/clis/notebooklm/summary.js +1 -1
- package/clis/qwen/ask.js +1 -1
- package/clis/qwen/detail.js +1 -1
- package/clis/qwen/history.js +1 -1
- package/clis/qwen/image.js +1 -1
- package/clis/qwen/new.js +1 -1
- package/clis/qwen/read.js +1 -1
- package/clis/qwen/send.js +1 -1
- package/clis/qwen/status.js +1 -1
- package/clis/reddit/comment.js +1 -1
- package/clis/reddit/frontpage.js +1 -1
- package/clis/reddit/popular.js +1 -1
- package/clis/reddit/read.js +1 -1
- package/clis/reddit/read.test.js +2 -2
- package/clis/reddit/reply.js +182 -0
- package/clis/reddit/reply.test.js +89 -0
- package/clis/reddit/save.js +1 -1
- package/clis/reddit/saved.js +1 -1
- package/clis/reddit/search.js +1 -1
- package/clis/reddit/subreddit.js +1 -1
- package/clis/reddit/subscribe.js +1 -1
- package/clis/reddit/upvote.js +1 -1
- package/clis/reddit/upvoted.js +1 -1
- package/clis/reddit/user-comments.js +1 -1
- package/clis/reddit/user-posts.js +1 -1
- package/clis/reddit/user.js +1 -1
- package/clis/rednote/comments.js +76 -0
- package/clis/rednote/download.js +59 -0
- package/clis/rednote/feed.js +95 -0
- package/clis/rednote/navigation.test.js +26 -0
- package/clis/rednote/note.js +68 -0
- package/clis/rednote/notifications.js +139 -0
- package/clis/rednote/rednote.test.js +157 -0
- package/clis/rednote/search.js +97 -0
- package/clis/rednote/user.js +55 -0
- package/clis/twitter/article.js +1 -1
- package/clis/twitter/bookmark-folder.js +1 -1
- package/clis/twitter/bookmark-folders.js +1 -1
- package/clis/twitter/bookmarks.js +1 -1
- package/clis/twitter/download.js +1 -1
- package/clis/twitter/followers.js +1 -1
- package/clis/twitter/following.js +1 -1
- package/clis/twitter/likes.js +1 -1
- package/clis/twitter/list-tweets.js +1 -1
- package/clis/twitter/lists.js +1 -1
- package/clis/twitter/notifications.js +1 -1
- package/clis/twitter/profile.js +1 -1
- package/clis/twitter/search.js +1 -1
- package/clis/twitter/thread.js +1 -1
- package/clis/twitter/timeline.js +1 -1
- package/clis/twitter/trending.js +1 -1
- package/clis/twitter/tweets.js +1 -1
- package/clis/xiaohongshu/comments.js +34 -24
- package/clis/xiaohongshu/download.js +32 -23
- package/clis/xiaohongshu/feed.js +23 -15
- package/clis/xiaohongshu/note-helpers.js +16 -6
- package/clis/xiaohongshu/note.js +26 -20
- package/clis/xiaohongshu/notifications.js +26 -19
- package/clis/xiaohongshu/search.js +37 -28
- package/clis/xiaohongshu/user-helpers.js +13 -4
- package/clis/xiaohongshu/user-helpers.test.js +20 -0
- package/clis/xiaohongshu/user.js +9 -4
- package/clis/youtube/transcript.js +28 -3
- package/clis/youtube/transcript.test.js +90 -1
- package/clis/yuanbao/ask.js +1 -1
- package/clis/yuanbao/detail.js +1 -1
- package/clis/yuanbao/history.js +1 -1
- package/clis/yuanbao/new.js +1 -1
- package/clis/yuanbao/read.js +1 -1
- package/clis/yuanbao/send.js +1 -1
- package/clis/yuanbao/status.js +1 -1
- package/dist/src/browser/bridge.d.ts +3 -1
- package/dist/src/browser/bridge.js +3 -1
- package/dist/src/browser/cdp.d.ts +3 -1
- package/dist/src/browser/daemon-client.d.ts +7 -14
- package/dist/src/browser/daemon-client.js +2 -6
- package/dist/src/browser/network-cache.d.ts +5 -5
- package/dist/src/browser/network-cache.js +8 -8
- package/dist/src/browser/network-cache.test.js +4 -4
- package/dist/src/browser/page.d.ts +8 -7
- package/dist/src/browser/page.js +23 -16
- package/dist/src/browser/page.test.js +60 -30
- package/dist/src/build-manifest.js +1 -1
- package/dist/src/cli.js +60 -162
- package/dist/src/cli.test.js +184 -198
- package/dist/src/commanderAdapter.js +2 -0
- package/dist/src/discovery.js +1 -1
- package/dist/src/doctor.d.ts +0 -4
- package/dist/src/doctor.js +14 -73
- package/dist/src/doctor.test.js +28 -97
- package/dist/src/execution.d.ts +1 -0
- package/dist/src/execution.js +20 -21
- package/dist/src/execution.test.js +27 -31
- package/dist/src/help.js +7 -1
- package/dist/src/main.js +0 -19
- package/dist/src/manifest-types.d.ts +2 -4
- package/dist/src/observation/artifact.js +1 -1
- package/dist/src/observation/artifact.test.js +3 -3
- package/dist/src/observation/events.d.ts +1 -1
- package/dist/src/observation/manager.js +1 -1
- package/dist/src/observation/manager.test.js +3 -3
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +3 -12
- package/dist/src/registry.js +6 -10
- package/dist/src/runtime.d.ts +7 -2
- package/dist/src/runtime.js +3 -1
- package/dist/src/serialization.d.ts +1 -1
- package/dist/src/serialization.js +1 -1
- package/dist/src/types.d.ts +0 -15
- 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
|
|
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: '
|
|
151
|
-
description: 'test site
|
|
150
|
+
name: 'site-session-persistent', access: 'read',
|
|
151
|
+
description: 'test persistent site session',
|
|
152
152
|
browser: true,
|
|
153
153
|
strategy: Strategy.PUBLIC,
|
|
154
|
-
|
|
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({
|
|
161
|
-
expect(sessionOpts[1]).toMatchObject({
|
|
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
|
|
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: '
|
|
177
|
-
description: 'test default one-shot browser
|
|
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]?.
|
|
186
|
-
expect(sessionOpts[1]?.
|
|
187
|
-
expect(sessionOpts[0]?.
|
|
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 --
|
|
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: '
|
|
210
|
-
description: 'test user
|
|
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
|
-
|
|
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]?.
|
|
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
|
|
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: '
|
|
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
|
-
|
|
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
|
|
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: '
|
|
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
|
-
|
|
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.
|
|
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
|
-
/**
|
|
40
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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' });
|
|
@@ -19,7 +19,7 @@ export class ObservationManager {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
function scopeMatches(actual, expected) {
|
|
22
|
-
return actual.
|
|
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',
|
|
7
|
-
manager.start({ id: 'personal-1', scope: { contextId: 'personal',
|
|
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',
|
|
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,
|
|
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';
|
package/dist/src/registry.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
70
|
-
|
|
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
|
}
|
package/dist/src/registry.js
CHANGED
|
@@ -27,7 +27,7 @@ export function cli(opts) {
|
|
|
27
27
|
pipeline: opts.pipeline,
|
|
28
28
|
footerExtra: opts.footerExtra,
|
|
29
29
|
navigateBefore: opts.navigateBefore,
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
86
|
-
if (cmd.
|
|
85
|
+
function assertSiteSession(cmd) {
|
|
86
|
+
if (cmd.siteSession === undefined)
|
|
87
87
|
return;
|
|
88
88
|
const key = `${cmd.site}/${cmd.name}`;
|
|
89
|
-
if (cmd.
|
|
90
|
-
throw new Error(`Command ${key}
|
|
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) {
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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>;
|
package/dist/src/runtime.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
+
siteSession: cmd.siteSession ?? null,
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
// ── Formatting ──────────────────────────────────────────────────────────────
|
package/dist/src/types.d.ts
CHANGED
|
@@ -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. */
|