@jackwener/opencli 1.5.7 → 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 +29 -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/extension-manifest-regression.test.js +1 -0
- 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 +614 -794
- package/extension/manifest.json +2 -1
- package/extension/src/background.test.ts +7 -163
- package/extension/src/background.ts +7 -156
- package/extension/src/cdp.test.ts +75 -0
- package/extension/src/cdp.ts +77 -3
- 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/extension-manifest-regression.test.ts +1 -0
- 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,78 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
mockGetNotebooklmPageState,
|
|
5
|
+
mockReadCurrentNotebooklm,
|
|
6
|
+
mockRequireNotebooklmSession,
|
|
7
|
+
} = vi.hoisted(() => ({
|
|
8
|
+
mockGetNotebooklmPageState: vi.fn(),
|
|
9
|
+
mockReadCurrentNotebooklm: vi.fn(),
|
|
10
|
+
mockRequireNotebooklmSession: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('./utils.js', async () => {
|
|
14
|
+
const actual = await vi.importActual<typeof import('./utils.js')>('./utils.js');
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
getNotebooklmPageState: mockGetNotebooklmPageState,
|
|
18
|
+
readCurrentNotebooklm: mockReadCurrentNotebooklm,
|
|
19
|
+
requireNotebooklmSession: mockRequireNotebooklmSession,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
import { getRegistry } from '../../registry.js';
|
|
24
|
+
import './open.js';
|
|
25
|
+
|
|
26
|
+
describe('notebooklm open', () => {
|
|
27
|
+
const command = getRegistry().get('notebooklm/open');
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
mockGetNotebooklmPageState.mockReset();
|
|
31
|
+
mockReadCurrentNotebooklm.mockReset();
|
|
32
|
+
mockRequireNotebooklmSession.mockReset();
|
|
33
|
+
mockRequireNotebooklmSession.mockResolvedValue(undefined);
|
|
34
|
+
mockGetNotebooklmPageState.mockResolvedValue({
|
|
35
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
36
|
+
title: 'Browser Automation',
|
|
37
|
+
hostname: 'notebooklm.google.com',
|
|
38
|
+
kind: 'notebook',
|
|
39
|
+
notebookId: 'nb-demo',
|
|
40
|
+
loginRequired: false,
|
|
41
|
+
notebookCount: 1,
|
|
42
|
+
});
|
|
43
|
+
mockReadCurrentNotebooklm.mockResolvedValue({
|
|
44
|
+
id: 'nb-demo',
|
|
45
|
+
title: 'Browser Automation',
|
|
46
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
47
|
+
source: 'current-page',
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('opens a notebook by id in the automation workspace', async () => {
|
|
52
|
+
const page = {
|
|
53
|
+
goto: vi.fn(async () => {}),
|
|
54
|
+
wait: vi.fn(async () => {}),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const result = await command!.func!(page as any, { notebook: 'nb-demo' });
|
|
58
|
+
|
|
59
|
+
expect(page.goto).toHaveBeenCalledWith('https://notebooklm.google.com/notebook/nb-demo');
|
|
60
|
+
expect(result).toEqual([{
|
|
61
|
+
id: 'nb-demo',
|
|
62
|
+
title: 'Browser Automation',
|
|
63
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
64
|
+
source: 'current-page',
|
|
65
|
+
}]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('accepts a full notebook url', async () => {
|
|
69
|
+
const page = {
|
|
70
|
+
goto: vi.fn(async () => {}),
|
|
71
|
+
wait: vi.fn(async () => {}),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
await command!.func!(page as any, { notebook: 'https://notebooklm.google.com/notebook/nb-demo?pli=1' });
|
|
75
|
+
|
|
76
|
+
expect(page.goto).toHaveBeenCalledWith('https://notebooklm.google.com/notebook/nb-demo');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import type { IPage } from '../../types.js';
|
|
3
|
+
import { CliError, EmptyResultError } from '../../errors.js';
|
|
4
|
+
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
5
|
+
import {
|
|
6
|
+
buildNotebooklmNotebookUrl,
|
|
7
|
+
getNotebooklmPageState,
|
|
8
|
+
parseNotebooklmNotebookTarget,
|
|
9
|
+
readCurrentNotebooklm,
|
|
10
|
+
requireNotebooklmSession,
|
|
11
|
+
} from './utils.js';
|
|
12
|
+
|
|
13
|
+
cli({
|
|
14
|
+
site: NOTEBOOKLM_SITE,
|
|
15
|
+
name: 'open',
|
|
16
|
+
aliases: ['select'],
|
|
17
|
+
description: 'Open one NotebookLM notebook in the automation workspace by id or URL',
|
|
18
|
+
domain: NOTEBOOKLM_DOMAIN,
|
|
19
|
+
strategy: Strategy.COOKIE,
|
|
20
|
+
browser: true,
|
|
21
|
+
navigateBefore: false,
|
|
22
|
+
args: [
|
|
23
|
+
{
|
|
24
|
+
name: 'notebook',
|
|
25
|
+
positional: true,
|
|
26
|
+
required: true,
|
|
27
|
+
help: 'Notebook id from list output, or a full NotebookLM notebook URL',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
columns: ['id', 'title', 'url', 'source'],
|
|
31
|
+
func: async (page: IPage, kwargs) => {
|
|
32
|
+
const notebookId = parseNotebooklmNotebookTarget(String(kwargs.notebook ?? ''));
|
|
33
|
+
await page.goto(buildNotebooklmNotebookUrl(notebookId));
|
|
34
|
+
await page.wait(2);
|
|
35
|
+
await requireNotebooklmSession(page);
|
|
36
|
+
|
|
37
|
+
const state = await getNotebooklmPageState(page);
|
|
38
|
+
if (state.kind !== 'notebook') {
|
|
39
|
+
throw new CliError(
|
|
40
|
+
'NOTEBOOKLM_OPEN_FAILED',
|
|
41
|
+
`NotebookLM notebook "${notebookId}" did not open in the automation workspace`,
|
|
42
|
+
'Run `opencli notebooklm list -f json` first and pass a valid notebook id.',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (state.notebookId !== notebookId) {
|
|
46
|
+
console.warn(
|
|
47
|
+
`[notebooklm open] expected notebook "${notebookId}" but page reports "${state.notebookId}"; continuing`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const current = await readCurrentNotebooklm(page);
|
|
52
|
+
if (!current) {
|
|
53
|
+
throw new EmptyResultError(
|
|
54
|
+
'opencli notebooklm open',
|
|
55
|
+
'NotebookLM notebook metadata was not found after navigation.',
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return [current];
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -3,7 +3,6 @@ import type { IPage } from '../../types.js';
|
|
|
3
3
|
import { EmptyResultError } from '../../errors.js';
|
|
4
4
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
5
5
|
import {
|
|
6
|
-
ensureNotebooklmNotebookBinding,
|
|
7
6
|
findNotebooklmSourceRow,
|
|
8
7
|
getNotebooklmPageState,
|
|
9
8
|
getNotebooklmSourceFulltextViaRpc,
|
|
@@ -30,13 +29,12 @@ cli({
|
|
|
30
29
|
],
|
|
31
30
|
columns: ['title', 'kind', 'char_count', 'url', 'source'],
|
|
32
31
|
func: async (page: IPage, kwargs) => {
|
|
33
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
34
32
|
await requireNotebooklmSession(page);
|
|
35
33
|
const state = await getNotebooklmPageState(page);
|
|
36
34
|
if (state.kind !== 'notebook') {
|
|
37
35
|
throw new EmptyResultError(
|
|
38
36
|
'opencli notebooklm source-fulltext',
|
|
39
|
-
'
|
|
37
|
+
'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.',
|
|
40
38
|
);
|
|
41
39
|
}
|
|
42
40
|
|
|
@@ -3,7 +3,6 @@ import type { IPage } from '../../types.js';
|
|
|
3
3
|
import { EmptyResultError } from '../../errors.js';
|
|
4
4
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
5
5
|
import {
|
|
6
|
-
ensureNotebooklmNotebookBinding,
|
|
7
6
|
findNotebooklmSourceRow,
|
|
8
7
|
getNotebooklmPageState,
|
|
9
8
|
listNotebooklmSourcesFromPage,
|
|
@@ -29,13 +28,12 @@ cli({
|
|
|
29
28
|
],
|
|
30
29
|
columns: ['title', 'id', 'type', 'size', 'created_at', 'updated_at', 'url', 'source'],
|
|
31
30
|
func: async (page: IPage, kwargs) => {
|
|
32
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
33
31
|
await requireNotebooklmSession(page);
|
|
34
32
|
const state = await getNotebooklmPageState(page);
|
|
35
33
|
if (state.kind !== 'notebook') {
|
|
36
34
|
throw new EmptyResultError(
|
|
37
35
|
'opencli notebooklm source-get',
|
|
38
|
-
'
|
|
36
|
+
'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.',
|
|
39
37
|
);
|
|
40
38
|
}
|
|
41
39
|
|
|
@@ -3,7 +3,6 @@ import type { IPage } from '../../types.js';
|
|
|
3
3
|
import { EmptyResultError } from '../../errors.js';
|
|
4
4
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
5
5
|
import {
|
|
6
|
-
ensureNotebooklmNotebookBinding,
|
|
7
6
|
findNotebooklmSourceRow,
|
|
8
7
|
getNotebooklmPageState,
|
|
9
8
|
getNotebooklmSourceGuideViaRpc,
|
|
@@ -30,13 +29,12 @@ cli({
|
|
|
30
29
|
],
|
|
31
30
|
columns: ['source_id', 'notebook_id', 'title', 'type', 'summary', 'keywords', 'source'],
|
|
32
31
|
func: async (page: IPage, kwargs) => {
|
|
33
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
34
32
|
await requireNotebooklmSession(page);
|
|
35
33
|
const state = await getNotebooklmPageState(page);
|
|
36
34
|
if (state.kind !== 'notebook') {
|
|
37
35
|
throw new EmptyResultError(
|
|
38
36
|
'opencli notebooklm source-guide',
|
|
39
|
-
'
|
|
37
|
+
'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.',
|
|
40
38
|
);
|
|
41
39
|
}
|
|
42
40
|
|
|
@@ -3,7 +3,6 @@ import type { IPage } from '../../types.js';
|
|
|
3
3
|
import { EmptyResultError } from '../../errors.js';
|
|
4
4
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
5
5
|
import {
|
|
6
|
-
ensureNotebooklmNotebookBinding,
|
|
7
6
|
getNotebooklmPageState,
|
|
8
7
|
listNotebooklmSourcesFromPage,
|
|
9
8
|
listNotebooklmSourcesViaRpc,
|
|
@@ -21,13 +20,12 @@ cli({
|
|
|
21
20
|
args: [],
|
|
22
21
|
columns: ['title', 'id', 'type', 'size', 'created_at', 'updated_at', 'url', 'source'],
|
|
23
22
|
func: async (page: IPage) => {
|
|
24
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
25
23
|
await requireNotebooklmSession(page);
|
|
26
24
|
const state = await getNotebooklmPageState(page);
|
|
27
25
|
if (state.kind !== 'notebook') {
|
|
28
26
|
throw new EmptyResultError(
|
|
29
27
|
'opencli notebooklm source-list',
|
|
30
|
-
'
|
|
28
|
+
'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.',
|
|
31
29
|
);
|
|
32
30
|
}
|
|
33
31
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cli, Strategy } from '../../registry.js';
|
|
2
2
|
import type { IPage } from '../../types.js';
|
|
3
3
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_HOME_URL, NOTEBOOKLM_SITE } from './shared.js';
|
|
4
|
-
import {
|
|
4
|
+
import { getNotebooklmPageState } from './utils.js';
|
|
5
5
|
|
|
6
6
|
cli({
|
|
7
7
|
site: NOTEBOOKLM_SITE,
|
|
@@ -14,7 +14,6 @@ cli({
|
|
|
14
14
|
args: [],
|
|
15
15
|
columns: ['status', 'login', 'page', 'url', 'title', 'notebooks'],
|
|
16
16
|
func: async (page: IPage) => {
|
|
17
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
18
17
|
const currentUrl = await page.getCurrentUrl?.().catch(() => null);
|
|
19
18
|
if (!currentUrl || !currentUrl.includes(NOTEBOOKLM_DOMAIN)) {
|
|
20
19
|
await page.goto(NOTEBOOKLM_HOME_URL);
|
|
@@ -3,7 +3,6 @@ import type { IPage } from '../../types.js';
|
|
|
3
3
|
import { EmptyResultError } from '../../errors.js';
|
|
4
4
|
import { NOTEBOOKLM_DOMAIN, NOTEBOOKLM_SITE } from './shared.js';
|
|
5
5
|
import {
|
|
6
|
-
ensureNotebooklmNotebookBinding,
|
|
7
6
|
getNotebooklmPageState,
|
|
8
7
|
getNotebooklmSummaryViaRpc,
|
|
9
8
|
readNotebooklmSummaryFromPage,
|
|
@@ -21,13 +20,12 @@ cli({
|
|
|
21
20
|
args: [],
|
|
22
21
|
columns: ['title', 'summary', 'source', 'url'],
|
|
23
22
|
func: async (page: IPage) => {
|
|
24
|
-
await ensureNotebooklmNotebookBinding(page);
|
|
25
23
|
await requireNotebooklmSession(page);
|
|
26
24
|
const state = await getNotebooklmPageState(page);
|
|
27
25
|
if (state.kind !== 'notebook') {
|
|
28
26
|
throw new EmptyResultError(
|
|
29
27
|
'opencli notebooklm summary',
|
|
30
|
-
'
|
|
28
|
+
'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.',
|
|
31
29
|
);
|
|
32
30
|
}
|
|
33
31
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { AuthRequiredError, CliError } from '../../errors.js';
|
|
2
2
|
import type { IPage } from '../../types.js';
|
|
3
|
-
import { bindCurrentTab } from '../../browser/daemon-client.js';
|
|
4
3
|
import {
|
|
5
4
|
NOTEBOOKLM_DOMAIN,
|
|
6
5
|
NOTEBOOKLM_HOME_URL,
|
|
7
|
-
NOTEBOOKLM_SITE,
|
|
8
6
|
type NotebooklmHistoryRow,
|
|
9
7
|
type NotebooklmNotebookDetailRow,
|
|
10
8
|
type NotebooklmNoteDetailRow,
|
|
@@ -54,6 +52,35 @@ export function parseNotebooklmIdFromUrl(url: string): string {
|
|
|
54
52
|
return match?.[1] ?? '';
|
|
55
53
|
}
|
|
56
54
|
|
|
55
|
+
export function parseNotebooklmNotebookTarget(value: string): string {
|
|
56
|
+
const normalized = value.trim();
|
|
57
|
+
if (!normalized) {
|
|
58
|
+
throw new CliError(
|
|
59
|
+
'NOTEBOOKLM_INVALID_NOTEBOOK',
|
|
60
|
+
'NotebookLM notebook id is required',
|
|
61
|
+
'Pass a notebook id from `opencli notebooklm list` or a full notebook URL.',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (/^https?:\/\//i.test(normalized)) {
|
|
66
|
+
const notebookId = parseNotebooklmIdFromUrl(normalized);
|
|
67
|
+
if (notebookId) return notebookId;
|
|
68
|
+
throw new CliError(
|
|
69
|
+
'NOTEBOOKLM_INVALID_NOTEBOOK',
|
|
70
|
+
'NotebookLM notebook URL is invalid',
|
|
71
|
+
'Pass a full NotebookLM notebook URL like https://notebooklm.google.com/notebook/<id>.',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const pathMatch = normalized.match(/(?:^|\/)notebook\/([^/?#]+)/);
|
|
76
|
+
if (pathMatch?.[1]) return pathMatch[1];
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function buildNotebooklmNotebookUrl(notebookId: string): string {
|
|
81
|
+
return new URL(`/notebook/${encodeURIComponent(notebookId)}`, NOTEBOOKLM_HOME_URL).toString();
|
|
82
|
+
}
|
|
83
|
+
|
|
57
84
|
export function classifyNotebooklmPage(url: string): NotebooklmPageKind {
|
|
58
85
|
try {
|
|
59
86
|
const parsed = new URL(url);
|
|
@@ -656,24 +683,6 @@ export async function ensureNotebooklmHome(page: IPage): Promise<void> {
|
|
|
656
683
|
await page.wait(2);
|
|
657
684
|
}
|
|
658
685
|
|
|
659
|
-
export async function ensureNotebooklmNotebookBinding(page: IPage): Promise<boolean> {
|
|
660
|
-
if (!page.getCurrentUrl) return false;
|
|
661
|
-
if (process.env.OPENCLI_CDP_ENDPOINT) return false;
|
|
662
|
-
|
|
663
|
-
const currentUrl = await page.getCurrentUrl().catch(() => null);
|
|
664
|
-
if (currentUrl && classifyNotebooklmPage(currentUrl) === 'notebook') return false;
|
|
665
|
-
|
|
666
|
-
try {
|
|
667
|
-
await bindCurrentTab(`site:${NOTEBOOKLM_SITE}`, {
|
|
668
|
-
matchDomain: NOTEBOOKLM_DOMAIN,
|
|
669
|
-
matchPathPrefix: '/notebook/',
|
|
670
|
-
});
|
|
671
|
-
return true;
|
|
672
|
-
} catch {
|
|
673
|
-
return false;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
686
|
export async function getNotebooklmPageState(page: IPage): Promise<NotebooklmPageState> {
|
|
678
687
|
const raw = await page.evaluate(`(() => {
|
|
679
688
|
const url = window.location.href;
|
|
@@ -40,9 +40,9 @@ function createPageMock(evaluateResult: any): IPage {
|
|
|
40
40
|
describe('xiaohongshu creator-note-detail', () => {
|
|
41
41
|
it('parses note detail page text into info and metric rows', () => {
|
|
42
42
|
const bodyText = `笔记数据详情
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
示例内容复盘
|
|
44
|
+
#测试标签
|
|
45
|
+
#内容分析
|
|
46
46
|
2026-03-18 20:01
|
|
47
47
|
切换笔记
|
|
48
48
|
笔记诊断
|
|
@@ -86,9 +86,9 @@ describe('xiaohongshu creator-note-detail', () => {
|
|
|
86
86
|
6
|
|
87
87
|
粉丝占比 0%`;
|
|
88
88
|
|
|
89
|
-
expect(parseCreatorNoteDetailText(bodyText, '
|
|
90
|
-
{ section: '笔记信息', metric: 'note_id', value: '
|
|
91
|
-
{ section: '笔记信息', metric: 'title', value: '
|
|
89
|
+
expect(parseCreatorNoteDetailText(bodyText, 'cccccccccccccccccccccccc')).toEqual([
|
|
90
|
+
{ section: '笔记信息', metric: 'note_id', value: 'cccccccccccccccccccccccc', extra: '' },
|
|
91
|
+
{ section: '笔记信息', metric: 'title', value: '示例内容复盘', extra: '' },
|
|
92
92
|
{ section: '笔记信息', metric: 'published_at', value: '2026-03-18 20:01', extra: '' },
|
|
93
93
|
{ section: '基础数据', metric: '曝光数', value: '1733', extra: '粉丝占比 6.6%' },
|
|
94
94
|
{ section: '基础数据', metric: '观看数', value: '544', extra: '粉丝占比 7.2%' },
|
|
@@ -104,8 +104,8 @@ describe('xiaohongshu creator-note-detail', () => {
|
|
|
104
104
|
|
|
105
105
|
it('parses structured note detail dom data into rows', () => {
|
|
106
106
|
expect(parseCreatorNoteDetailDomData({
|
|
107
|
-
title: '
|
|
108
|
-
infoText: '
|
|
107
|
+
title: '测试笔记一',
|
|
108
|
+
infoText: '测试笔记一\n#测试标签\n2025-12-04 19:45\n切换笔记',
|
|
109
109
|
sections: [
|
|
110
110
|
{
|
|
111
111
|
title: '基础数据',
|
|
@@ -127,9 +127,9 @@ describe('xiaohongshu creator-note-detail', () => {
|
|
|
127
127
|
],
|
|
128
128
|
},
|
|
129
129
|
],
|
|
130
|
-
}, '
|
|
131
|
-
{ section: '笔记信息', metric: 'note_id', value: '
|
|
132
|
-
{ section: '笔记信息', metric: 'title', value: '
|
|
130
|
+
}, 'bbbbbbbbbbbbbbbbbbbbbbbb')).toEqual([
|
|
131
|
+
{ section: '笔记信息', metric: 'note_id', value: 'bbbbbbbbbbbbbbbbbbbbbbbb', extra: '' },
|
|
132
|
+
{ section: '笔记信息', metric: 'title', value: '测试笔记一', extra: '' },
|
|
133
133
|
{ section: '笔记信息', metric: 'published_at', value: '2025-12-04 19:45', extra: '' },
|
|
134
134
|
{ section: '基础数据', metric: '曝光数', value: '898204', extra: '粉丝占比 0.5%' },
|
|
135
135
|
{ section: '基础数据', metric: '观看数', value: '148284', extra: '粉丝占比 0.6%' },
|
|
@@ -7,14 +7,14 @@ import './creator-notes-summary.js';
|
|
|
7
7
|
describe('xiaohongshu creator-notes-summary', () => {
|
|
8
8
|
it('summarizes note list row and detail rows into one compact row', () => {
|
|
9
9
|
const note: CreatorNoteRow = {
|
|
10
|
-
id: '
|
|
11
|
-
title: '
|
|
10
|
+
id: 'cccccccccccccccccccccccc',
|
|
11
|
+
title: '示例内容复盘',
|
|
12
12
|
date: '2026年03月18日 20:01',
|
|
13
13
|
views: 549,
|
|
14
14
|
likes: 19,
|
|
15
15
|
collects: 10,
|
|
16
16
|
comments: 7,
|
|
17
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
17
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=cccccccccccccccccccccccc',
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
const rows: CreatorNoteDetailRow[] = [
|
|
@@ -34,8 +34,8 @@ describe('xiaohongshu creator-notes-summary', () => {
|
|
|
34
34
|
|
|
35
35
|
expect(summarizeCreatorNote(note, rows, 1)).toEqual({
|
|
36
36
|
rank: 1,
|
|
37
|
-
id: '
|
|
38
|
-
title: '
|
|
37
|
+
id: 'cccccccccccccccccccccccc',
|
|
38
|
+
title: '示例内容复盘',
|
|
39
39
|
published_at: '2026-03-18 20:01',
|
|
40
40
|
views: '549',
|
|
41
41
|
likes: '19',
|
|
@@ -48,7 +48,7 @@ describe('xiaohongshu creator-notes-summary', () => {
|
|
|
48
48
|
top_source_pct: '89.9%',
|
|
49
49
|
top_interest: '二次元',
|
|
50
50
|
top_interest_pct: '13%',
|
|
51
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
51
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=cccccccccccccccccccccccc',
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
});
|
|
@@ -46,7 +46,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
46
46
|
const bodyText = `笔记管理
|
|
47
47
|
全部笔记(366)
|
|
48
48
|
已发布
|
|
49
|
-
|
|
49
|
+
测试笔记一
|
|
50
50
|
发布于 2025年12月04日 19:45
|
|
51
51
|
148208
|
|
52
52
|
324
|
|
@@ -58,7 +58,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
58
58
|
编辑
|
|
59
59
|
删除
|
|
60
60
|
仅自己可见
|
|
61
|
-
|
|
61
|
+
测试笔记二
|
|
62
62
|
发布于 2026年03月18日 12:39
|
|
63
63
|
10
|
|
64
64
|
0
|
|
@@ -70,7 +70,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
70
70
|
expect(parseCreatorNotesText(bodyText)).toEqual([
|
|
71
71
|
{
|
|
72
72
|
id: '',
|
|
73
|
-
title: '
|
|
73
|
+
title: '测试笔记一',
|
|
74
74
|
date: '2025年12月04日 19:45',
|
|
75
75
|
views: 148208,
|
|
76
76
|
likes: 2279,
|
|
@@ -80,7 +80,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
id: '',
|
|
83
|
-
title: '
|
|
83
|
+
title: '测试笔记二',
|
|
84
84
|
date: '2026年03月18日 12:39',
|
|
85
85
|
views: 10,
|
|
86
86
|
likes: 0,
|
|
@@ -106,7 +106,7 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
106
106
|
4
|
|
107
107
|
5
|
|
108
108
|
权限设置`,
|
|
109
|
-
html: '"noteId":"
|
|
109
|
+
html: '"noteId":"aaaaaaaaaaaaaaaaaaaaaaaa"',
|
|
110
110
|
},
|
|
111
111
|
]);
|
|
112
112
|
|
|
@@ -116,14 +116,14 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
116
116
|
expect(result).toEqual([
|
|
117
117
|
{
|
|
118
118
|
rank: 1,
|
|
119
|
-
id: '
|
|
119
|
+
id: 'aaaaaaaaaaaaaaaaaaaaaaaa',
|
|
120
120
|
title: '示例笔记',
|
|
121
121
|
date: '2026年03月19日 12:00',
|
|
122
122
|
views: 10,
|
|
123
123
|
likes: 3,
|
|
124
124
|
collects: 4,
|
|
125
125
|
comments: 2,
|
|
126
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
126
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=aaaaaaaaaaaaaaaaaaaaaaaa',
|
|
127
127
|
},
|
|
128
128
|
]);
|
|
129
129
|
});
|
|
@@ -136,8 +136,8 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
136
136
|
undefined,
|
|
137
137
|
[
|
|
138
138
|
{
|
|
139
|
-
id: '
|
|
140
|
-
title: '
|
|
139
|
+
id: 'bbbbbbbbbbbbbbbbbbbbbbbb',
|
|
140
|
+
title: '测试笔记一',
|
|
141
141
|
date: '2025年12月04日 19:45',
|
|
142
142
|
metrics: [148284, 319, 2280, 466, 33],
|
|
143
143
|
},
|
|
@@ -149,14 +149,14 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
149
149
|
expect(result).toEqual([
|
|
150
150
|
{
|
|
151
151
|
rank: 1,
|
|
152
|
-
id: '
|
|
153
|
-
title: '
|
|
152
|
+
id: 'bbbbbbbbbbbbbbbbbbbbbbbb',
|
|
153
|
+
title: '测试笔记一',
|
|
154
154
|
date: '2025年12月04日 19:45',
|
|
155
155
|
views: 148284,
|
|
156
156
|
likes: 2280,
|
|
157
157
|
collects: 466,
|
|
158
158
|
comments: 319,
|
|
159
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
159
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=bbbbbbbbbbbbbbbbbbbbbbbb',
|
|
160
160
|
},
|
|
161
161
|
]);
|
|
162
162
|
});
|
|
@@ -169,8 +169,8 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
169
169
|
data: {
|
|
170
170
|
note_infos: [
|
|
171
171
|
{
|
|
172
|
-
id: '
|
|
173
|
-
title: '
|
|
172
|
+
id: 'cccccccccccccccccccccccc',
|
|
173
|
+
title: '示例内容复盘',
|
|
174
174
|
post_time: new Date('2026-03-18T20:01:00+08:00').getTime(),
|
|
175
175
|
read_count: 521,
|
|
176
176
|
like_count: 18,
|
|
@@ -187,28 +187,28 @@ describe('xiaohongshu creator-notes', () => {
|
|
|
187
187
|
expect(result).toEqual([
|
|
188
188
|
{
|
|
189
189
|
rank: 1,
|
|
190
|
-
id: '
|
|
191
|
-
title: '
|
|
190
|
+
id: 'cccccccccccccccccccccccc',
|
|
191
|
+
title: '示例内容复盘',
|
|
192
192
|
date: '2026年03月18日 20:01',
|
|
193
193
|
views: 521,
|
|
194
194
|
likes: 18,
|
|
195
195
|
collects: 10,
|
|
196
196
|
comments: 7,
|
|
197
|
-
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=
|
|
197
|
+
url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=cccccccccccccccccccccccc',
|
|
198
198
|
},
|
|
199
199
|
]);
|
|
200
200
|
});
|
|
201
201
|
|
|
202
202
|
it('extracts note ids from creator note-manager html', () => {
|
|
203
203
|
const html = `
|
|
204
|
-
<div>"noteId":"
|
|
205
|
-
<div>"noteId":"
|
|
206
|
-
<div>"noteId":"
|
|
204
|
+
<div>"noteId":"aaaaaaaaaaaaaaaaaaaaaaaa"</div>
|
|
205
|
+
<div>"noteId":"dddddddddddddddddddddddd"</div>
|
|
206
|
+
<div>"noteId":"aaaaaaaaaaaaaaaaaaaaaaaa"</div>
|
|
207
207
|
`;
|
|
208
208
|
|
|
209
209
|
expect(parseCreatorNoteIdsFromHtml(html)).toEqual([
|
|
210
|
-
'
|
|
211
|
-
'
|
|
210
|
+
'aaaaaaaaaaaaaaaaaaaaaaaa',
|
|
211
|
+
'dddddddddddddddddddddddd',
|
|
212
212
|
]);
|
|
213
213
|
});
|
|
214
214
|
});
|
|
@@ -153,3 +153,50 @@ describe('commanderAdapter command aliases', () => {
|
|
|
153
153
|
expect(mockExecuteCommand).toHaveBeenCalledWith(cmd, {}, false);
|
|
154
154
|
});
|
|
155
155
|
});
|
|
156
|
+
|
|
157
|
+
describe('commanderAdapter default formats', () => {
|
|
158
|
+
const cmd: CliCommand = {
|
|
159
|
+
site: 'gemini',
|
|
160
|
+
name: 'ask',
|
|
161
|
+
description: 'Ask Gemini',
|
|
162
|
+
browser: false,
|
|
163
|
+
args: [],
|
|
164
|
+
columns: ['response'],
|
|
165
|
+
defaultFormat: 'plain',
|
|
166
|
+
func: vi.fn(),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
beforeEach(() => {
|
|
170
|
+
mockExecuteCommand.mockReset();
|
|
171
|
+
mockExecuteCommand.mockResolvedValue([{ response: 'hello' }]);
|
|
172
|
+
mockRenderOutput.mockReset();
|
|
173
|
+
delete process.env.OPENCLI_VERBOSE;
|
|
174
|
+
process.exitCode = undefined;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('uses the command defaultFormat when the user keeps the default table format', async () => {
|
|
178
|
+
const program = new Command();
|
|
179
|
+
const siteCmd = program.command('gemini');
|
|
180
|
+
registerCommandToProgram(siteCmd, cmd);
|
|
181
|
+
|
|
182
|
+
await program.parseAsync(['node', 'opencli', 'gemini', 'ask']);
|
|
183
|
+
|
|
184
|
+
expect(mockRenderOutput).toHaveBeenCalledWith(
|
|
185
|
+
[{ response: 'hello' }],
|
|
186
|
+
expect.objectContaining({ fmt: 'plain' }),
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('respects an explicit user format over the command defaultFormat', async () => {
|
|
191
|
+
const program = new Command();
|
|
192
|
+
const siteCmd = program.command('gemini');
|
|
193
|
+
registerCommandToProgram(siteCmd, cmd);
|
|
194
|
+
|
|
195
|
+
await program.parseAsync(['node', 'opencli', 'gemini', 'ask', '--format', 'json']);
|
|
196
|
+
|
|
197
|
+
expect(mockRenderOutput).toHaveBeenCalledWith(
|
|
198
|
+
[{ response: 'hello' }],
|
|
199
|
+
expect.objectContaining({ fmt: 'json' }),
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
});
|
package/src/commanderAdapter.ts
CHANGED
|
@@ -69,7 +69,7 @@ export function registerCommandToProgram(siteCmd: Command, cmd: CliCommand): voi
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
subCmd
|
|
72
|
-
.option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table')
|
|
72
|
+
.option('-f, --format <fmt>', 'Output format: table, plain, json, yaml, md, csv', 'table')
|
|
73
73
|
.option('-v, --verbose', 'Debug output', false);
|
|
74
74
|
|
|
75
75
|
subCmd.addHelpText('after', formatRegistryHelpText(cmd));
|
|
@@ -95,7 +95,7 @@ export function registerCommandToProgram(siteCmd: Command, cmd: CliCommand): voi
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
const verbose = optionsRecord.verbose === true;
|
|
98
|
-
|
|
98
|
+
let format = typeof optionsRecord.format === 'string' ? optionsRecord.format : 'table';
|
|
99
99
|
if (verbose) process.env.OPENCLI_VERBOSE = '1';
|
|
100
100
|
if (cmd.deprecated) {
|
|
101
101
|
const message = typeof cmd.deprecated === 'string' ? cmd.deprecated : `${fullName(cmd)} is deprecated.`;
|
|
@@ -108,10 +108,14 @@ export function registerCommandToProgram(siteCmd: Command, cmd: CliCommand): voi
|
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
|
|
112
|
+
if (format === 'table' && resolved.defaultFormat) {
|
|
113
|
+
format = resolved.defaultFormat;
|
|
114
|
+
}
|
|
115
|
+
|
|
111
116
|
if (verbose && (!result || (Array.isArray(result) && result.length === 0))) {
|
|
112
117
|
console.error(chalk.yellow('[Verbose] Warning: Command returned an empty result.'));
|
|
113
118
|
}
|
|
114
|
-
const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
|
|
115
119
|
renderOutput(result, {
|
|
116
120
|
fmt: format,
|
|
117
121
|
columns: resolved.columns,
|