@jackwener/opencli 1.5.8 → 1.5.9
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/CHANGELOG.md +21 -0
- package/README.md +17 -1
- package/README.zh-CN.md +17 -1
- package/dist/browser/base-page.d.ts +48 -0
- package/dist/browser/base-page.js +160 -0
- package/dist/browser/cdp.js +4 -106
- package/dist/browser/daemon-client.d.ts +1 -7
- package/dist/browser/daemon-client.js +2 -9
- package/dist/browser/discover.d.ts +1 -4
- package/dist/browser/discover.js +1 -4
- package/dist/browser/errors.d.ts +4 -0
- package/dist/browser/errors.js +20 -0
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/page.d.ts +6 -35
- package/dist/browser/page.js +10 -189
- package/dist/browser/tabs.js +5 -5
- package/dist/browser.test.js +15 -15
- package/dist/cli-manifest.json +294 -22
- package/dist/clis/amazon/bestsellers.d.ts +21 -0
- package/dist/clis/amazon/bestsellers.js +130 -0
- package/dist/clis/amazon/bestsellers.test.js +20 -0
- package/dist/clis/amazon/discussion.d.ts +20 -0
- package/dist/clis/amazon/discussion.js +91 -0
- package/dist/clis/amazon/discussion.test.js +36 -0
- package/dist/clis/amazon/offer.d.ts +23 -0
- package/dist/clis/amazon/offer.js +140 -0
- package/dist/clis/amazon/offer.test.d.ts +1 -0
- package/dist/clis/amazon/offer.test.js +29 -0
- package/dist/clis/amazon/product.d.ts +18 -0
- package/dist/clis/amazon/product.js +92 -0
- package/dist/clis/amazon/product.test.d.ts +1 -0
- package/dist/clis/amazon/product.test.js +24 -0
- package/dist/clis/amazon/search.d.ts +18 -0
- package/dist/clis/amazon/search.js +87 -0
- package/dist/clis/amazon/search.test.d.ts +1 -0
- package/dist/clis/amazon/search.test.js +22 -0
- package/dist/clis/amazon/shared.d.ts +64 -0
- package/dist/clis/amazon/shared.js +255 -0
- package/dist/clis/amazon/shared.test.d.ts +1 -0
- package/dist/clis/amazon/shared.test.js +33 -0
- package/dist/clis/gemini/ask.d.ts +1 -0
- package/dist/clis/gemini/ask.js +40 -0
- package/dist/clis/gemini/image.d.ts +1 -0
- package/dist/clis/gemini/image.js +105 -0
- package/dist/clis/gemini/new.d.ts +1 -0
- package/dist/clis/gemini/new.js +20 -0
- package/dist/clis/gemini/utils.d.ts +34 -0
- package/dist/clis/gemini/utils.js +463 -0
- package/dist/clis/gemini/utils.test.d.ts +1 -0
- package/dist/clis/gemini/utils.test.js +31 -0
- package/dist/clis/notebooklm/compat.test.d.ts +1 -1
- package/dist/clis/notebooklm/compat.test.js +3 -3
- package/dist/clis/notebooklm/current.js +2 -3
- package/dist/clis/notebooklm/get.js +2 -3
- package/dist/clis/notebooklm/history.js +2 -3
- package/dist/clis/notebooklm/note-list.js +2 -3
- package/dist/clis/notebooklm/notes-get.js +2 -3
- package/dist/clis/notebooklm/open.d.ts +1 -0
- package/dist/clis/notebooklm/open.js +41 -0
- package/dist/clis/notebooklm/open.test.d.ts +1 -0
- package/dist/clis/notebooklm/open.test.js +63 -0
- package/dist/clis/notebooklm/source-fulltext.js +2 -3
- package/dist/clis/notebooklm/source-get.js +2 -3
- package/dist/clis/notebooklm/source-guide.js +2 -3
- package/dist/clis/notebooklm/source-list.js +2 -3
- package/dist/clis/notebooklm/status.js +1 -2
- package/dist/clis/notebooklm/summary.js +2 -3
- package/dist/clis/notebooklm/utils.d.ts +2 -1
- package/dist/clis/notebooklm/utils.js +20 -21
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +11 -11
- package/dist/clis/xiaohongshu/creator-notes-summary.test.js +6 -6
- package/dist/clis/xiaohongshu/creator-notes.test.js +22 -22
- package/dist/commanderAdapter.js +6 -3
- package/dist/commanderAdapter.test.js +33 -0
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/daemon.test.js +1 -1
- package/dist/doctor.d.ts +1 -2
- package/dist/doctor.js +7 -8
- package/dist/explore.js +1 -1
- package/dist/output.js +28 -0
- package/dist/output.test.js +15 -0
- package/dist/pipeline/executor.js +2 -7
- package/dist/pipeline/steps/browser.js +1 -1
- package/dist/pipeline/template.js +25 -3
- package/dist/record.d.ts +50 -0
- package/dist/record.js +298 -57
- package/dist/record.test.d.ts +1 -0
- package/dist/record.test.js +293 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +1 -0
- package/dist/registry.test.js +10 -0
- package/dist/runtime.js +3 -3
- package/dist/snapshotFormatter.d.ts +1 -1
- package/dist/snapshotFormatter.js +4 -4
- package/dist/snapshotFormatter.test.d.ts +1 -1
- package/dist/snapshotFormatter.test.js +2 -2
- package/dist/types.d.ts +3 -1
- package/dist/types.js +1 -1
- package/docs/.vitepress/config.mts +2 -0
- package/docs/adapters/browser/amazon.md +53 -0
- package/docs/adapters/browser/gemini.md +72 -0
- package/docs/adapters/browser/notebooklm.md +5 -5
- package/docs/adapters/index.md +3 -1
- package/extension/dist/background.js +5 -143
- package/extension/src/background.test.ts +7 -163
- package/extension/src/background.ts +7 -157
- package/extension/src/protocol.ts +1 -5
- package/package.json +1 -1
- package/skills/opencli-explorer/SKILL.md +847 -0
- package/skills/opencli-oneshot/SKILL.md +216 -0
- package/skills/opencli-usage/SKILL.md +71 -0
- package/skills/opencli-usage/browser.md +429 -0
- package/skills/opencli-usage/desktop.md +118 -0
- package/skills/opencli-usage/plugins.md +82 -0
- package/skills/opencli-usage/public-api.md +149 -0
- package/src/browser/base-page.ts +197 -0
- package/src/browser/cdp.ts +7 -131
- package/src/browser/daemon-client.ts +3 -14
- package/src/browser/discover.ts +1 -4
- package/src/browser/errors.ts +22 -0
- package/src/browser/index.ts +1 -1
- package/src/browser/page.ts +13 -212
- package/src/browser/tabs.ts +5 -5
- package/src/browser.test.ts +15 -15
- package/src/clis/amazon/bestsellers.test.ts +22 -0
- package/src/clis/amazon/bestsellers.ts +180 -0
- package/src/clis/amazon/discussion.test.ts +38 -0
- package/src/clis/amazon/discussion.ts +131 -0
- package/src/clis/amazon/offer.test.ts +35 -0
- package/src/clis/amazon/offer.ts +185 -0
- package/src/clis/amazon/product.test.ts +26 -0
- package/src/clis/amazon/product.ts +131 -0
- package/src/clis/amazon/search.test.ts +24 -0
- package/src/clis/amazon/search.ts +128 -0
- package/src/clis/amazon/shared.test.ts +37 -0
- package/src/clis/amazon/shared.ts +316 -0
- package/src/clis/gemini/ask.ts +46 -0
- package/src/clis/gemini/image.ts +115 -0
- package/src/clis/gemini/new.ts +22 -0
- package/src/clis/gemini/utils.test.ts +36 -0
- package/src/clis/gemini/utils.ts +523 -0
- package/src/clis/notebooklm/compat.test.ts +3 -3
- package/src/clis/notebooklm/current.ts +2 -3
- package/src/clis/notebooklm/get.ts +1 -3
- package/src/clis/notebooklm/history.ts +1 -3
- package/src/clis/notebooklm/note-list.ts +1 -3
- package/src/clis/notebooklm/notes-get.ts +1 -3
- package/src/clis/notebooklm/open.test.ts +78 -0
- package/src/clis/notebooklm/open.ts +61 -0
- package/src/clis/notebooklm/source-fulltext.ts +1 -3
- package/src/clis/notebooklm/source-get.ts +1 -3
- package/src/clis/notebooklm/source-guide.ts +1 -3
- package/src/clis/notebooklm/source-list.ts +1 -3
- package/src/clis/notebooklm/status.ts +1 -2
- package/src/clis/notebooklm/summary.ts +1 -3
- package/src/clis/notebooklm/utils.ts +29 -20
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +11 -11
- package/src/clis/xiaohongshu/creator-notes-summary.test.ts +6 -6
- package/src/clis/xiaohongshu/creator-notes.test.ts +22 -22
- package/src/commanderAdapter.test.ts +47 -0
- package/src/commanderAdapter.ts +7 -3
- package/src/commands/daemon.test.ts +1 -1
- package/src/commands/daemon.ts +1 -1
- package/src/doctor.ts +7 -8
- package/src/explore.ts +1 -1
- package/src/output.test.ts +17 -0
- package/src/output.ts +27 -0
- package/src/pipeline/executor.ts +2 -7
- package/src/pipeline/steps/browser.ts +1 -1
- package/src/pipeline/template.ts +27 -4
- package/src/record.test.ts +362 -0
- package/src/record.ts +341 -62
- package/src/registry.test.ts +12 -0
- package/src/registry.ts +3 -0
- package/src/runtime.ts +3 -3
- package/src/snapshotFormatter.test.ts +2 -2
- package/src/snapshotFormatter.ts +4 -4
- package/src/types.ts +3 -1
- package/.agents/skills/cross-project-adapter-migration/SKILL.md +0 -249
- package/.agents/workflows/cross-project-adapter-migration.md +0 -54
- package/SKILL.md +0 -879
- package/dist/clis/notebooklm/bind-current.js +0 -29
- package/dist/clis/notebooklm/bind-current.test.d.ts +0 -1
- package/dist/clis/notebooklm/bind-current.test.js +0 -35
- package/dist/clis/notebooklm/binding.test.js +0 -44
- package/src/clis/notebooklm/bind-current.test.ts +0 -43
- package/src/clis/notebooklm/bind-current.ts +0 -36
- package/src/clis/notebooklm/binding.test.ts +0 -53
- /package/dist/browser/{mcp.d.ts → bridge.d.ts} +0 -0
- /package/dist/browser/{mcp.js → bridge.js} +0 -0
- /package/dist/clis/{notebooklm/bind-current.d.ts → amazon/bestsellers.test.d.ts} +0 -0
- /package/dist/clis/{notebooklm/binding.test.d.ts → amazon/discussion.test.d.ts} +0 -0
- /package/src/browser/{mcp.ts → bridge.ts} +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import { CliError, EmptyResultError } from '../../errors.js';
|
|
3
|
+
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
4
|
+
import { buildNotebooklmNotebookUrl, getNotebooklmPageState, parseNotebooklmNotebookTarget, readCurrentNotebooklm, requireNotebooklmSession, } from './utils.js';
|
|
5
|
+
cli({
|
|
6
|
+
site: NOTEBOOKLM_SITE,
|
|
7
|
+
name: 'open',
|
|
8
|
+
aliases: ['select'],
|
|
9
|
+
description: 'Open one NotebookLM notebook in the automation workspace by id or URL',
|
|
10
|
+
domain: NOTEBOOKLM_DOMAIN,
|
|
11
|
+
strategy: Strategy.COOKIE,
|
|
12
|
+
browser: true,
|
|
13
|
+
navigateBefore: false,
|
|
14
|
+
args: [
|
|
15
|
+
{
|
|
16
|
+
name: 'notebook',
|
|
17
|
+
positional: true,
|
|
18
|
+
required: true,
|
|
19
|
+
help: 'Notebook id from list output, or a full NotebookLM notebook URL',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
columns: ['id', 'title', 'url', 'source'],
|
|
23
|
+
func: async (page, kwargs) => {
|
|
24
|
+
const notebookId = parseNotebooklmNotebookTarget(String(kwargs.notebook ?? ''));
|
|
25
|
+
await page.goto(buildNotebooklmNotebookUrl(notebookId));
|
|
26
|
+
await page.wait(2);
|
|
27
|
+
await requireNotebooklmSession(page);
|
|
28
|
+
const state = await getNotebooklmPageState(page);
|
|
29
|
+
if (state.kind !== 'notebook') {
|
|
30
|
+
throw new CliError('NOTEBOOKLM_OPEN_FAILED', `NotebookLM notebook "${notebookId}" did not open in the automation workspace`, 'Run `opencli notebooklm list -f json` first and pass a valid notebook id.');
|
|
31
|
+
}
|
|
32
|
+
if (state.notebookId !== notebookId) {
|
|
33
|
+
console.warn(`[notebooklm open] expected notebook "${notebookId}" but page reports "${state.notebookId}"; continuing`);
|
|
34
|
+
}
|
|
35
|
+
const current = await readCurrentNotebooklm(page);
|
|
36
|
+
if (!current) {
|
|
37
|
+
throw new EmptyResultError('opencli notebooklm open', 'NotebookLM notebook metadata was not found after navigation.');
|
|
38
|
+
}
|
|
39
|
+
return [current];
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './open.js';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
const { mockGetNotebooklmPageState, mockReadCurrentNotebooklm, mockRequireNotebooklmSession, } = vi.hoisted(() => ({
|
|
3
|
+
mockGetNotebooklmPageState: vi.fn(),
|
|
4
|
+
mockReadCurrentNotebooklm: vi.fn(),
|
|
5
|
+
mockRequireNotebooklmSession: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
vi.mock('./utils.js', async () => {
|
|
8
|
+
const actual = await vi.importActual('./utils.js');
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
getNotebooklmPageState: mockGetNotebooklmPageState,
|
|
12
|
+
readCurrentNotebooklm: mockReadCurrentNotebooklm,
|
|
13
|
+
requireNotebooklmSession: mockRequireNotebooklmSession,
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
import { getRegistry } from '../../registry.js';
|
|
17
|
+
import './open.js';
|
|
18
|
+
describe('notebooklm open', () => {
|
|
19
|
+
const command = getRegistry().get('notebooklm/open');
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockGetNotebooklmPageState.mockReset();
|
|
22
|
+
mockReadCurrentNotebooklm.mockReset();
|
|
23
|
+
mockRequireNotebooklmSession.mockReset();
|
|
24
|
+
mockRequireNotebooklmSession.mockResolvedValue(undefined);
|
|
25
|
+
mockGetNotebooklmPageState.mockResolvedValue({
|
|
26
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
27
|
+
title: 'Browser Automation',
|
|
28
|
+
hostname: 'notebooklm.google.com',
|
|
29
|
+
kind: 'notebook',
|
|
30
|
+
notebookId: 'nb-demo',
|
|
31
|
+
loginRequired: false,
|
|
32
|
+
notebookCount: 1,
|
|
33
|
+
});
|
|
34
|
+
mockReadCurrentNotebooklm.mockResolvedValue({
|
|
35
|
+
id: 'nb-demo',
|
|
36
|
+
title: 'Browser Automation',
|
|
37
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
38
|
+
source: 'current-page',
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it('opens a notebook by id in the automation workspace', async () => {
|
|
42
|
+
const page = {
|
|
43
|
+
goto: vi.fn(async () => { }),
|
|
44
|
+
wait: vi.fn(async () => { }),
|
|
45
|
+
};
|
|
46
|
+
const result = await command.func(page, { notebook: 'nb-demo' });
|
|
47
|
+
expect(page.goto).toHaveBeenCalledWith('https://notebooklm.google.com/notebook/nb-demo');
|
|
48
|
+
expect(result).toEqual([{
|
|
49
|
+
id: 'nb-demo',
|
|
50
|
+
title: 'Browser Automation',
|
|
51
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
52
|
+
source: 'current-page',
|
|
53
|
+
}]);
|
|
54
|
+
});
|
|
55
|
+
it('accepts a full notebook url', async () => {
|
|
56
|
+
const page = {
|
|
57
|
+
goto: vi.fn(async () => { }),
|
|
58
|
+
wait: vi.fn(async () => { }),
|
|
59
|
+
};
|
|
60
|
+
await command.func(page, { notebook: 'https://notebooklm.google.com/notebook/nb-demo?pli=1' });
|
|
61
|
+
expect(page.goto).toHaveBeenCalledWith('https://notebooklm.google.com/notebook/nb-demo');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { EmptyResultError } from '../../errors.js';
|
|
3
3
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
4
|
-
import {
|
|
4
|
+
import { findNotebooklmSourceRow, getNotebooklmPageState, getNotebooklmSourceFulltextViaRpc, listNotebooklmSourcesFromPage, listNotebooklmSourcesViaRpc, requireNotebooklmSession, } from './utils.js';
|
|
5
5
|
cli({
|
|
6
6
|
site: NOTEBOOKLM_SITE,
|
|
7
7
|
name: 'source-fulltext',
|
|
@@ -20,11 +20,10 @@ cli({
|
|
|
20
20
|
],
|
|
21
21
|
columns: ['title', 'kind', 'char_count', 'url', 'source'],
|
|
22
22
|
func: async (page, kwargs) => {
|
|
23
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
24
23
|
await requireNotebooklmSession(page);
|
|
25
24
|
const state = await getNotebooklmPageState(page);
|
|
26
25
|
if (state.kind !== 'notebook') {
|
|
27
|
-
throw new EmptyResultError('opencli notebooklm source-fulltext', '
|
|
26
|
+
throw new EmptyResultError('opencli notebooklm source-fulltext', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
|
|
28
27
|
}
|
|
29
28
|
const rpcRows = await listNotebooklmSourcesViaRpc(page).catch(() => []);
|
|
30
29
|
const rows = rpcRows.length > 0 ? rpcRows : await listNotebooklmSourcesFromPage(page);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { EmptyResultError } from '../../errors.js';
|
|
3
3
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
4
|
-
import {
|
|
4
|
+
import { findNotebooklmSourceRow, getNotebooklmPageState, listNotebooklmSourcesFromPage, listNotebooklmSourcesViaRpc, requireNotebooklmSession, } from './utils.js';
|
|
5
5
|
cli({
|
|
6
6
|
site: NOTEBOOKLM_SITE,
|
|
7
7
|
name: 'source-get',
|
|
@@ -20,11 +20,10 @@ cli({
|
|
|
20
20
|
],
|
|
21
21
|
columns: ['title', 'id', 'type', 'size', 'created_at', 'updated_at', 'url', 'source'],
|
|
22
22
|
func: async (page, kwargs) => {
|
|
23
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
24
23
|
await requireNotebooklmSession(page);
|
|
25
24
|
const state = await getNotebooklmPageState(page);
|
|
26
25
|
if (state.kind !== 'notebook') {
|
|
27
|
-
throw new EmptyResultError('opencli notebooklm source-get', '
|
|
26
|
+
throw new EmptyResultError('opencli notebooklm source-get', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
|
|
28
27
|
}
|
|
29
28
|
const rpcRows = await listNotebooklmSourcesViaRpc(page).catch(() => []);
|
|
30
29
|
const rows = rpcRows.length > 0 ? rpcRows : await listNotebooklmSourcesFromPage(page);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { EmptyResultError } from '../../errors.js';
|
|
3
3
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
4
|
-
import {
|
|
4
|
+
import { findNotebooklmSourceRow, getNotebooklmPageState, getNotebooklmSourceGuideViaRpc, listNotebooklmSourcesFromPage, listNotebooklmSourcesViaRpc, requireNotebooklmSession, } from './utils.js';
|
|
5
5
|
cli({
|
|
6
6
|
site: NOTEBOOKLM_SITE,
|
|
7
7
|
name: 'source-guide',
|
|
@@ -20,11 +20,10 @@ cli({
|
|
|
20
20
|
],
|
|
21
21
|
columns: ['source_id', 'notebook_id', 'title', 'type', 'summary', 'keywords', 'source'],
|
|
22
22
|
func: async (page, kwargs) => {
|
|
23
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
24
23
|
await requireNotebooklmSession(page);
|
|
25
24
|
const state = await getNotebooklmPageState(page);
|
|
26
25
|
if (state.kind !== 'notebook') {
|
|
27
|
-
throw new EmptyResultError('opencli notebooklm source-guide', '
|
|
26
|
+
throw new EmptyResultError('opencli notebooklm source-guide', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
|
|
28
27
|
}
|
|
29
28
|
const rpcRows = await listNotebooklmSourcesViaRpc(page).catch(() => []);
|
|
30
29
|
const rows = rpcRows.length > 0 ? rpcRows : await listNotebooklmSourcesFromPage(page);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { EmptyResultError } from '../../errors.js';
|
|
3
3
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
4
|
-
import {
|
|
4
|
+
import { getNotebooklmPageState, listNotebooklmSourcesFromPage, listNotebooklmSourcesViaRpc, requireNotebooklmSession, } from './utils.js';
|
|
5
5
|
cli({
|
|
6
6
|
site: NOTEBOOKLM_SITE,
|
|
7
7
|
name: 'source-list',
|
|
@@ -13,11 +13,10 @@ cli({
|
|
|
13
13
|
args: [],
|
|
14
14
|
columns: ['title', 'id', 'type', 'size', 'created_at', 'updated_at', 'url', 'source'],
|
|
15
15
|
func: async (page) => {
|
|
16
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
17
16
|
await requireNotebooklmSession(page);
|
|
18
17
|
const state = await getNotebooklmPageState(page);
|
|
19
18
|
if (state.kind !== 'notebook') {
|
|
20
|
-
throw new EmptyResultError('opencli notebooklm source-list', '
|
|
19
|
+
throw new EmptyResultError('opencli notebooklm source-list', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
|
|
21
20
|
}
|
|
22
21
|
const rpcRows = await listNotebooklmSourcesViaRpc(page).catch(() => []);
|
|
23
22
|
if (rpcRows.length > 0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_HOME_URL, NOTEBOOKLM_SITE } from './shared.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getNotebooklmPageState } from './utils.js';
|
|
4
4
|
cli({
|
|
5
5
|
site: NOTEBOOKLM_SITE,
|
|
6
6
|
name: 'status',
|
|
@@ -12,7 +12,6 @@ cli({
|
|
|
12
12
|
args: [],
|
|
13
13
|
columns: ['status', 'login', 'page', 'url', 'title', 'notebooks'],
|
|
14
14
|
func: async (page) => {
|
|
15
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
16
15
|
const currentUrl = await page.getCurrentUrl?.().catch(() => null);
|
|
17
16
|
if (!currentUrl || !currentUrl.includes(NOTEBOOKLM_DOMAIN)) {
|
|
18
17
|
await page.goto(NOTEBOOKLM_HOME_URL);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import { EmptyResultError } from '../../errors.js';
|
|
3
3
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
4
|
-
import {
|
|
4
|
+
import { getNotebooklmPageState, getNotebooklmSummaryViaRpc, readNotebooklmSummaryFromPage, requireNotebooklmSession, } from './utils.js';
|
|
5
5
|
cli({
|
|
6
6
|
site: NOTEBOOKLM_SITE,
|
|
7
7
|
name: 'summary',
|
|
@@ -13,11 +13,10 @@ cli({
|
|
|
13
13
|
args: [],
|
|
14
14
|
columns: ['title', 'summary', 'source', 'url'],
|
|
15
15
|
func: async (page) => {
|
|
16
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
17
16
|
await requireNotebooklmSession(page);
|
|
18
17
|
const state = await getNotebooklmPageState(page);
|
|
19
18
|
if (state.kind !== 'notebook') {
|
|
20
|
-
throw new EmptyResultError('opencli notebooklm summary', '
|
|
19
|
+
throw new EmptyResultError('opencli notebooklm summary', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
|
|
21
20
|
}
|
|
22
21
|
const domSummary = await readNotebooklmSummaryFromPage(page);
|
|
23
22
|
if (domSummary)
|
|
@@ -2,6 +2,8 @@ import type { IPage } from '../../types.js';
|
|
|
2
2
|
import { type NotebooklmHistoryRow, type NotebooklmNotebookDetailRow, type NotebooklmNoteDetailRow, type NotebooklmNoteRow, type NotebooklmPageKind, type NotebooklmPageState, type NotebooklmRow, type NotebooklmSourceFulltextRow, type NotebooklmSourceGuideRow, type NotebooklmSourceRow, type NotebooklmSummaryRow } from './shared.js';
|
|
3
3
|
export { buildNotebooklmRpcBody, extractNotebooklmRpcResult, fetchNotebooklmInPage, getNotebooklmPageAuth, parseNotebooklmChunkedResponse, stripNotebooklmAntiXssi, } from './rpc.js';
|
|
4
4
|
export declare function parseNotebooklmIdFromUrl(url: string): string;
|
|
5
|
+
export declare function parseNotebooklmNotebookTarget(value: string): string;
|
|
6
|
+
export declare function buildNotebooklmNotebookUrl(notebookId: string): string;
|
|
5
7
|
export declare function classifyNotebooklmPage(url: string): NotebooklmPageKind;
|
|
6
8
|
export declare function normalizeNotebooklmTitle(value: unknown, fallback?: string): string;
|
|
7
9
|
type NotebooklmRawNoteRow = {
|
|
@@ -29,7 +31,6 @@ export declare function getNotebooklmSourceFulltextViaRpc(page: IPage, sourceId:
|
|
|
29
31
|
export declare function getNotebooklmSourceGuideViaRpc(page: IPage, source: Pick<NotebooklmSourceRow, 'id' | 'notebook_id' | 'title' | 'type'>): Promise<NotebooklmSourceGuideRow | null>;
|
|
30
32
|
export declare function readNotebooklmVisibleNoteFromPage(page: IPage): Promise<NotebooklmNoteDetailRow | null>;
|
|
31
33
|
export declare function ensureNotebooklmHome(page: IPage): Promise<void>;
|
|
32
|
-
export declare function ensureNotebooklmNotebookBinding(page: IPage): Promise<boolean>;
|
|
33
34
|
export declare function getNotebooklmPageState(page: IPage): Promise<NotebooklmPageState>;
|
|
34
35
|
export declare function readCurrentNotebooklm(page: IPage): Promise<NotebooklmRow | null>;
|
|
35
36
|
export declare function listNotebooklmLinks(page: IPage): Promise<NotebooklmRow[]>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { AuthRequiredError, CliError } from '../../errors.js';
|
|
2
|
-
import {
|
|
3
|
-
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_HOME_URL, NOTEBOOKLM_SITE, } from './shared.js';
|
|
2
|
+
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_HOME_URL, } from './shared.js';
|
|
4
3
|
import { callNotebooklmRpc, getNotebooklmPageAuth, } from './rpc.js';
|
|
5
4
|
export { buildNotebooklmRpcBody, extractNotebooklmRpcResult, fetchNotebooklmInPage, getNotebooklmPageAuth, parseNotebooklmChunkedResponse, stripNotebooklmAntiXssi, } from './rpc.js';
|
|
6
5
|
const NOTEBOOKLM_LIST_RPC_ID = 'wXbhsf';
|
|
@@ -18,6 +17,25 @@ export function parseNotebooklmIdFromUrl(url) {
|
|
|
18
17
|
const match = url.match(/\/notebook\/([^/?#]+)/);
|
|
19
18
|
return match?.[1] ?? '';
|
|
20
19
|
}
|
|
20
|
+
export function parseNotebooklmNotebookTarget(value) {
|
|
21
|
+
const normalized = value.trim();
|
|
22
|
+
if (!normalized) {
|
|
23
|
+
throw new CliError('NOTEBOOKLM_INVALID_NOTEBOOK', 'NotebookLM notebook id is required', 'Pass a notebook id from `opencli notebooklm list` or a full notebook URL.');
|
|
24
|
+
}
|
|
25
|
+
if (/^https?:\/\//i.test(normalized)) {
|
|
26
|
+
const notebookId = parseNotebooklmIdFromUrl(normalized);
|
|
27
|
+
if (notebookId)
|
|
28
|
+
return notebookId;
|
|
29
|
+
throw new CliError('NOTEBOOKLM_INVALID_NOTEBOOK', 'NotebookLM notebook URL is invalid', 'Pass a full NotebookLM notebook URL like https://notebooklm.google.com/notebook/<id>.');
|
|
30
|
+
}
|
|
31
|
+
const pathMatch = normalized.match(/(?:^|\/)notebook\/([^/?#]+)/);
|
|
32
|
+
if (pathMatch?.[1])
|
|
33
|
+
return pathMatch[1];
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
export function buildNotebooklmNotebookUrl(notebookId) {
|
|
37
|
+
return new URL(`/notebook/${encodeURIComponent(notebookId)}`, NOTEBOOKLM_HOME_URL).toString();
|
|
38
|
+
}
|
|
21
39
|
export function classifyNotebooklmPage(url) {
|
|
22
40
|
try {
|
|
23
41
|
const parsed = new URL(url);
|
|
@@ -511,25 +529,6 @@ export async function ensureNotebooklmHome(page) {
|
|
|
511
529
|
await page.goto(NOTEBOOKLM_HOME_URL);
|
|
512
530
|
await page.wait(2);
|
|
513
531
|
}
|
|
514
|
-
export async function ensureNotebooklmNotebookBinding(page) {
|
|
515
|
-
if (!page.getCurrentUrl)
|
|
516
|
-
return false;
|
|
517
|
-
if (process.env.OPENCLI_CDP_ENDPOINT)
|
|
518
|
-
return false;
|
|
519
|
-
const currentUrl = await page.getCurrentUrl().catch(() => null);
|
|
520
|
-
if (currentUrl && classifyNotebooklmPage(currentUrl) === 'notebook')
|
|
521
|
-
return false;
|
|
522
|
-
try {
|
|
523
|
-
await bindCurrentTab(`site:${NOTEBOOKLM_SITE}`, {
|
|
524
|
-
matchDomain: NOTEBOOKLM_DOMAIN,
|
|
525
|
-
matchPathPrefix: '/notebook/',
|
|
526
|
-
});
|
|
527
|
-
return true;
|
|
528
|
-
}
|
|
529
|
-
catch {
|
|
530
|
-
return false;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
532
|
export async function getNotebooklmPageState(page) {
|
|
534
533
|
const raw = await page.evaluate(`(() => {
|
|
535
534
|
const url = window.location.href;
|
|
@@ -36,9 +36,9 @@ function createPageMock(evaluateResult) {
|
|
|
36
36
|
describe('xiaohongshu creator-note-detail', () => {
|
|
37
37
|
it('parses note detail page text into info and metric rows', () => {
|
|
38
38
|
const bodyText = `笔记数据详情
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
示例内容复盘
|
|
40
|
+
#测试标签
|
|
41
|
+
#内容分析
|
|
42
42
|
2026-03-18 20:01
|
|
43
43
|
切换笔记
|
|
44
44
|
笔记诊断
|
|
@@ -81,9 +81,9 @@ describe('xiaohongshu creator-note-detail', () => {
|
|
|
81
81
|
分享数
|
|
82
82
|
6
|
|
83
83
|
粉丝占比 0%`;
|
|
84
|
-
expect(parseCreatorNoteDetailText(bodyText, '
|
|
85
|
-
{ section: '笔记信息', metric: 'note_id', value: '
|
|
86
|
-
{ section: '笔记信息', metric: 'title', value: '
|
|
84
|
+
expect(parseCreatorNoteDetailText(bodyText, 'cccccccccccccccccccccccc')).toEqual([
|
|
85
|
+
{ section: '笔记信息', metric: 'note_id', value: 'cccccccccccccccccccccccc', extra: '' },
|
|
86
|
+
{ section: '笔记信息', metric: 'title', value: '示例内容复盘', extra: '' },
|
|
87
87
|
{ section: '笔记信息', metric: 'published_at', value: '2026-03-18 20:01', extra: '' },
|
|
88
88
|
{ section: '基础数据', metric: '曝光数', value: '1733', extra: '粉丝占比 6.6%' },
|
|
89
89
|
{ section: '基础数据', metric: '观看数', value: '544', extra: '粉丝占比 7.2%' },
|
|
@@ -98,8 +98,8 @@ describe('xiaohongshu creator-note-detail', () => {
|
|
|
98
98
|
});
|
|
99
99
|
it('parses structured note detail dom data into rows', () => {
|
|
100
100
|
expect(parseCreatorNoteDetailDomData({
|
|
101
|
-
title: '
|
|
102
|
-
infoText: '
|
|
101
|
+
title: '测试笔记一',
|
|
102
|
+
infoText: '测试笔记一\n#测试标签\n2025-12-04 19:45\n切换笔记',
|
|
103
103
|
sections: [
|
|
104
104
|
{
|
|
105
105
|
title: '基础数据',
|
|
@@ -121,9 +121,9 @@ describe('xiaohongshu creator-note-detail', () => {
|
|
|
121
121
|
],
|
|
122
122
|
},
|
|
123
123
|
],
|
|
124
|
-
}, '
|
|
125
|
-
{ section: '笔记信息', metric: 'note_id', value: '
|
|
126
|
-
{ section: '笔记信息', metric: 'title', value: '
|
|
124
|
+
}, 'bbbbbbbbbbbbbbbbbbbbbbbb')).toEqual([
|
|
125
|
+
{ section: '笔记信息', metric: 'note_id', value: 'bbbbbbbbbbbbbbbbbbbbbbbb', extra: '' },
|
|
126
|
+
{ section: '笔记信息', metric: 'title', value: '测试笔记一', extra: '' },
|
|
127
127
|
{ section: '笔记信息', metric: 'published_at', value: '2025-12-04 19:45', extra: '' },
|
|
128
128
|
{ section: '基础数据', metric: '曝光数', value: '898204', extra: '粉丝占比 0.5%' },
|
|
129
129
|
{ section: '基础数据', metric: '观看数', value: '148284', extra: '粉丝占比 0.6%' },
|
|
@@ -4,14 +4,14 @@ import './creator-notes-summary.js';
|
|
|
4
4
|
describe('xiaohongshu creator-notes-summary', () => {
|
|
5
5
|
it('summarizes note list row and detail rows into one compact row', () => {
|
|
6
6
|
const note = {
|
|
7
|
-
id: '
|
|
8
|
-
title: '
|
|
7
|
+
id: 'cccccccccccccccccccccccc',
|
|
8
|
+
title: '示例内容复盘',
|
|
9
9
|
date: '2026年03月18日 20:01',
|
|
10
10
|
views: 549,
|
|
11
11
|
likes: 19,
|
|
12
12
|
collects: 10,
|
|
13
13
|
comments: 7,
|
|
14
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
14
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=cccccccccccccccccccccccc',
|
|
15
15
|
};
|
|
16
16
|
const rows = [
|
|
17
17
|
{ section: '笔记信息', metric: 'published_at', value: '2026-03-18 20:01', extra: '' },
|
|
@@ -29,8 +29,8 @@ describe('xiaohongshu creator-notes-summary', () => {
|
|
|
29
29
|
];
|
|
30
30
|
expect(summarizeCreatorNote(note, rows, 1)).toEqual({
|
|
31
31
|
rank: 1,
|
|
32
|
-
id: '
|
|
33
|
-
title: '
|
|
32
|
+
id: 'cccccccccccccccccccccccc',
|
|
33
|
+
title: '示例内容复盘',
|
|
34
34
|
published_at: '2026-03-18 20:01',
|
|
35
35
|
views: '549',
|
|
36
36
|
likes: '19',
|
|
@@ -43,7 +43,7 @@ describe('xiaohongshu creator-notes-summary', () => {
|
|
|
43
43
|
top_source_pct: '89.9%',
|
|
44
44
|
top_interest: '二次元',
|
|
45
45
|
top_interest_pct: '13%',
|
|
46
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
46
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=cccccccccccccccccccccccc',
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
});
|
|
@@ -41,7 +41,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
41
41
|
const bodyText = `笔记管理
|
|
42
42
|
全部笔记(366)
|
|
43
43
|
已发布
|
|
44
|
-
|
|
44
|
+
测试笔记一
|
|
45
45
|
发布于 2025年12月04日 19:45
|
|
46
46
|
148208
|
|
47
47
|
324
|
|
@@ -53,7 +53,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
53
53
|
编辑
|
|
54
54
|
删除
|
|
55
55
|
仅自己可见
|
|
56
|
-
|
|
56
|
+
测试笔记二
|
|
57
57
|
发布于 2026年03月18日 12:39
|
|
58
58
|
10
|
|
59
59
|
0
|
|
@@ -64,7 +64,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
64
64
|
expect(parseCreatorNotesText(bodyText)).toEqual([
|
|
65
65
|
{
|
|
66
66
|
id: '',
|
|
67
|
-
title: '
|
|
67
|
+
title: '测试笔记一',
|
|
68
68
|
date: '2025年12月04日 19:45',
|
|
69
69
|
views: 148208,
|
|
70
70
|
likes: 2279,
|
|
@@ -74,7 +74,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
id: '',
|
|
77
|
-
title: '
|
|
77
|
+
title: '测试笔记二',
|
|
78
78
|
date: '2026年03月18日 12:39',
|
|
79
79
|
views: 10,
|
|
80
80
|
likes: 0,
|
|
@@ -98,7 +98,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
98
98
|
4
|
|
99
99
|
5
|
|
100
100
|
权限设置`,
|
|
101
|
-
html: '"noteId":"
|
|
101
|
+
html: '"noteId":"aaaaaaaaaaaaaaaaaaaaaaaa"',
|
|
102
102
|
},
|
|
103
103
|
]);
|
|
104
104
|
const result = await cmd.func(page, { limit: 1 });
|
|
@@ -106,14 +106,14 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
106
106
|
expect(result).toEqual([
|
|
107
107
|
{
|
|
108
108
|
rank: 1,
|
|
109
|
-
id: '
|
|
109
|
+
id: 'aaaaaaaaaaaaaaaaaaaaaaaa',
|
|
110
110
|
title: '示例笔记',
|
|
111
111
|
date: '2026年03月19日 12:00',
|
|
112
112
|
views: 10,
|
|
113
113
|
likes: 3,
|
|
114
114
|
collects: 4,
|
|
115
115
|
comments: 2,
|
|
116
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
116
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=aaaaaaaaaaaaaaaaaaaaaaaa',
|
|
117
117
|
},
|
|
118
118
|
]);
|
|
119
119
|
});
|
|
@@ -124,8 +124,8 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
124
124
|
undefined,
|
|
125
125
|
[
|
|
126
126
|
{
|
|
127
|
-
id: '
|
|
128
|
-
title: '
|
|
127
|
+
id: 'bbbbbbbbbbbbbbbbbbbbbbbb',
|
|
128
|
+
title: '测试笔记一',
|
|
129
129
|
date: '2025年12月04日 19:45',
|
|
130
130
|
metrics: [148284, 319, 2280, 466, 33],
|
|
131
131
|
},
|
|
@@ -135,14 +135,14 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
135
135
|
expect(result).toEqual([
|
|
136
136
|
{
|
|
137
137
|
rank: 1,
|
|
138
|
-
id: '
|
|
139
|
-
title: '
|
|
138
|
+
id: 'bbbbbbbbbbbbbbbbbbbbbbbb',
|
|
139
|
+
title: '测试笔记一',
|
|
140
140
|
date: '2025年12月04日 19:45',
|
|
141
141
|
views: 148284,
|
|
142
142
|
likes: 2280,
|
|
143
143
|
collects: 466,
|
|
144
144
|
comments: 319,
|
|
145
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
145
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=bbbbbbbbbbbbbbbbbbbbbbbb',
|
|
146
146
|
},
|
|
147
147
|
]);
|
|
148
148
|
});
|
|
@@ -153,8 +153,8 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
153
153
|
data: {
|
|
154
154
|
note_infos: [
|
|
155
155
|
{
|
|
156
|
-
id: '
|
|
157
|
-
title: '
|
|
156
|
+
id: 'cccccccccccccccccccccccc',
|
|
157
|
+
title: '示例内容复盘',
|
|
158
158
|
post_time: new Date('2026-03-18T20:01:00+08:00').getTime(),
|
|
159
159
|
read_count: 521,
|
|
160
160
|
like_count: 18,
|
|
@@ -169,26 +169,26 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
169
169
|
expect(result).toEqual([
|
|
170
170
|
{
|
|
171
171
|
rank: 1,
|
|
172
|
-
id: '
|
|
173
|
-
title: '
|
|
172
|
+
id: 'cccccccccccccccccccccccc',
|
|
173
|
+
title: '示例内容复盘',
|
|
174
174
|
date: '2026年03月18日 20:01',
|
|
175
175
|
views: 521,
|
|
176
176
|
likes: 18,
|
|
177
177
|
collects: 10,
|
|
178
178
|
comments: 7,
|
|
179
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
179
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=cccccccccccccccccccccccc',
|
|
180
180
|
},
|
|
181
181
|
]);
|
|
182
182
|
});
|
|
183
183
|
it('extracts note ids from creator note-manager html', () => {
|
|
184
184
|
const html = `
|
|
185
|
-
<div>"noteId":"
|
|
186
|
-
<div>"noteId":"
|
|
187
|
-
<div>"noteId":"
|
|
185
|
+
<div>"noteId":"aaaaaaaaaaaaaaaaaaaaaaaa"</div>
|
|
186
|
+
<div>"noteId":"dddddddddddddddddddddddd"</div>
|
|
187
|
+
<div>"noteId":"aaaaaaaaaaaaaaaaaaaaaaaa"</div>
|
|
188
188
|
`;
|
|
189
189
|
expect(parseCreatorNoteIdsFromHtml(html)).toEqual([
|
|
190
|
-
'
|
|
191
|
-
'
|
|
190
|
+
'aaaaaaaaaaaaaaaaaaaaaaaa',
|
|
191
|
+
'dddddddddddddddddddddddd',
|
|
192
192
|
]);
|
|
193
193
|
});
|
|
194
194
|
});
|
package/dist/commanderAdapter.js
CHANGED
|
@@ -59,7 +59,7 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
subCmd
|
|
62
|
-
.option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table')
|
|
62
|
+
.option('-f, --format <fmt>', 'Output format: table, plain, json, yaml, md, csv', 'table')
|
|
63
63
|
.option('-v, --verbose', 'Debug output', false);
|
|
64
64
|
subCmd.addHelpText('after', formatRegistryHelpText(cmd));
|
|
65
65
|
subCmd.action(async (...actionArgs) => {
|
|
@@ -84,7 +84,7 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
84
84
|
kwargs[arg.name] = normalizeArgValue(arg.type, v, arg.name);
|
|
85
85
|
}
|
|
86
86
|
const verbose = optionsRecord.verbose === true;
|
|
87
|
-
|
|
87
|
+
let format = typeof optionsRecord.format === 'string' ? optionsRecord.format : 'table';
|
|
88
88
|
if (verbose)
|
|
89
89
|
process.env.OPENCLI_VERBOSE = '1';
|
|
90
90
|
if (cmd.deprecated) {
|
|
@@ -96,10 +96,13 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
96
96
|
if (result === null || result === undefined) {
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
|
+
const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
|
|
100
|
+
if (format === 'table' && resolved.defaultFormat) {
|
|
101
|
+
format = resolved.defaultFormat;
|
|
102
|
+
}
|
|
99
103
|
if (verbose && (!result || (Array.isArray(result) && result.length === 0))) {
|
|
100
104
|
console.error(chalk.yellow('[Verbose] Warning: Command returned an empty result.'));
|
|
101
105
|
}
|
|
102
|
-
const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
|
|
103
106
|
renderOutput(result, {
|
|
104
107
|
fmt: format,
|
|
105
108
|
columns: resolved.columns,
|
|
@@ -124,3 +124,36 @@ describe('commanderAdapter command aliases', () => {
|
|
|
124
124
|
expect(mockExecuteCommand).toHaveBeenCalledWith(cmd, {}, false);
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
|
+
describe('commanderAdapter default formats', () => {
|
|
128
|
+
const cmd = {
|
|
129
|
+
site: 'gemini',
|
|
130
|
+
name: 'ask',
|
|
131
|
+
description: 'Ask Gemini',
|
|
132
|
+
browser: false,
|
|
133
|
+
args: [],
|
|
134
|
+
columns: ['response'],
|
|
135
|
+
defaultFormat: 'plain',
|
|
136
|
+
func: vi.fn(),
|
|
137
|
+
};
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
mockExecuteCommand.mockReset();
|
|
140
|
+
mockExecuteCommand.mockResolvedValue([{ response: 'hello' }]);
|
|
141
|
+
mockRenderOutput.mockReset();
|
|
142
|
+
delete process.env.OPENCLI_VERBOSE;
|
|
143
|
+
process.exitCode = undefined;
|
|
144
|
+
});
|
|
145
|
+
it('uses the command defaultFormat when the user keeps the default table format', async () => {
|
|
146
|
+
const program = new Command();
|
|
147
|
+
const siteCmd = program.command('gemini');
|
|
148
|
+
registerCommandToProgram(siteCmd, cmd);
|
|
149
|
+
await program.parseAsync(['node', 'opencli', 'gemini', 'ask']);
|
|
150
|
+
expect(mockRenderOutput).toHaveBeenCalledWith([{ response: 'hello' }], expect.objectContaining({ fmt: 'plain' }));
|
|
151
|
+
});
|
|
152
|
+
it('respects an explicit user format over the command defaultFormat', async () => {
|
|
153
|
+
const program = new Command();
|
|
154
|
+
const siteCmd = program.command('gemini');
|
|
155
|
+
registerCommandToProgram(siteCmd, cmd);
|
|
156
|
+
await program.parseAsync(['node', 'opencli', 'gemini', 'ask', '--format', 'json']);
|
|
157
|
+
expect(mockRenderOutput).toHaveBeenCalledWith([{ response: 'hello' }], expect.objectContaining({ fmt: 'json' }));
|
|
158
|
+
});
|
|
159
|
+
});
|
package/dist/commands/daemon.js
CHANGED
|
@@ -110,7 +110,7 @@ export async function daemonRestart() {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
// Import BrowserBridge to spawn a new daemon
|
|
113
|
-
const { BrowserBridge } = await import('../browser/
|
|
113
|
+
const { BrowserBridge } = await import('../browser/bridge.js');
|
|
114
114
|
const bridge = new BrowserBridge();
|
|
115
115
|
try {
|
|
116
116
|
console.log('Starting daemon...');
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* opencli doctor — diagnose browser connectivity.
|
|
3
3
|
*
|
|
4
|
-
* Simplified for the daemon-based architecture.
|
|
5
|
-
* MCP path discovery, or config file scanning.
|
|
4
|
+
* Simplified for the daemon-based architecture.
|
|
6
5
|
*/
|
|
7
6
|
export type DoctorOptions = {
|
|
8
7
|
yes?: boolean;
|