@pagepocket/cli 0.8.5 → 0.9.0

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 CHANGED
@@ -39,6 +39,7 @@ Manage configured plugins:
39
39
  ```bash
40
40
  pp plugin ls
41
41
  pp plugin add <plugin-name>
42
+ pp plugin update [plugin-name]
42
43
  pp plugin remove <plugin-name>
43
44
  ```
44
45
 
@@ -58,6 +59,8 @@ pp plugin add @scope/plugin --option 1 --option 2
58
59
  Notes:
59
60
 
60
61
  - `pp plugin remove` only removes the entry from `config.json` (it does not uninstall the package).
62
+ - `pp plugin update <plugin-name>` reinstalls that plugin to `latest`.
63
+ - `pp plugin update` reinstalls all configured plugins to `latest`.
61
64
  - During `pp archive ...`, the CLI reads `config.json` and tries to load all configured plugins.
62
65
  If a plugin fails to load, it will be skipped and the archive will continue.
63
66
 
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const core_1 = require("@oclif/core");
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_service_1 = require("../../services/config-service");
9
+ const plugin_installer_1 = require("../../services/plugin-installer");
10
+ const plugin_store_1 = require("../../services/plugin-store");
11
+ const parse_plugin_spec_1 = require("../../utils/parse-plugin-spec");
12
+ /**
13
+ * Read configured plugins and return unique package names.
14
+ *
15
+ * Usage:
16
+ * const names = getUniqueConfiguredPluginNames(store);
17
+ */
18
+ const getUniqueConfiguredPluginNames = (store) => {
19
+ const config = store.readConfig();
20
+ return [
21
+ ...new Set(config.plugins
22
+ .map((entry) => (0, plugin_store_1.normalizePluginConfigEntry)(entry).name)
23
+ .filter((name) => name.trim().length > 0))
24
+ ];
25
+ };
26
+ class PluginUpdateCommand extends core_1.Command {
27
+ async run() {
28
+ const { args } = await this.parse(PluginUpdateCommand);
29
+ const configService = new config_service_1.ConfigService();
30
+ const store = new plugin_store_1.PluginStore(configService);
31
+ configService.ensureConfigFileExists();
32
+ const targetName = args.name ? (0, parse_plugin_spec_1.parsePluginSpec)(args.name).name : undefined;
33
+ const configuredNames = getUniqueConfiguredPluginNames(store);
34
+ if (configuredNames.length === 0) {
35
+ this.log(chalk_1.default.gray("No plugins configured."));
36
+ return;
37
+ }
38
+ if (targetName && !configuredNames.includes(targetName)) {
39
+ this.log(chalk_1.default.gray(`Plugin not found in config: ${targetName}`));
40
+ return;
41
+ }
42
+ const namesToUpdate = targetName ? [targetName] : configuredNames;
43
+ namesToUpdate.forEach((name) => {
44
+ (0, plugin_installer_1.updatePluginPackageToLatest)(store, { packageName: name });
45
+ this.log(chalk_1.default.green(`Updated plugin: ${name}`));
46
+ });
47
+ }
48
+ }
49
+ PluginUpdateCommand.description = "Update one plugin (or all configured plugins) to latest.";
50
+ PluginUpdateCommand.args = {
51
+ name: core_1.Args.string({
52
+ description: "npm package name (optional; if omitted updates all configured plugins)",
53
+ required: false
54
+ })
55
+ };
56
+ exports.default = PluginUpdateCommand;
@@ -14,11 +14,11 @@ class ViewCommand extends core_1.Command {
14
14
  const { args, flags } = await this.parse(ViewCommand);
15
15
  const rootDir = node_path_1.default.resolve(node_process_1.default.cwd(), args.directory);
16
16
  const indexPath = node_path_1.default.join(rootDir, "index.html");
17
- const stat = await node_fs_1.default.promises.stat(rootDir).catch(() => null);
17
+ const stat = await node_fs_1.default.promises.stat(rootDir).catch(() => undefined);
18
18
  if (!stat || !stat.isDirectory()) {
19
19
  throw new Error(`view: directory not found: ${rootDir}`);
20
20
  }
21
- const indexStat = await node_fs_1.default.promises.stat(indexPath).catch(() => null);
21
+ const indexStat = await node_fs_1.default.promises.stat(indexPath).catch(() => undefined);
22
22
  if (!indexStat || !indexStat.isFile()) {
23
23
  throw new Error(`view: index.html not found under: ${rootDir}`);
24
24
  }
@@ -8,6 +8,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_os_1 = __importDefault(require("node:os"));
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const env_paths_1 = __importDefault(require("env-paths"));
11
+ const parse_json_1 = require("../utils/parse-json");
11
12
  const defaultConfig = {
12
13
  plugins: []
13
14
  };
@@ -45,7 +46,7 @@ class ConfigService {
45
46
  const dir = node_path_1.default.dirname(filePath);
46
47
  this.ensureDirExists(dir);
47
48
  const tmpPath = `${filePath}.tmp`;
48
- node_fs_1.default.writeFileSync(tmpPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
49
+ node_fs_1.default.writeFileSync(tmpPath, `${JSON.stringify(value, undefined, 2)}\n`, "utf8");
49
50
  node_fs_1.default.renameSync(tmpPath, filePath);
50
51
  }
51
52
  isObject(value) {
@@ -73,33 +74,28 @@ class ConfigService {
73
74
  ...(typeof options === "undefined" ? {} : { options })
74
75
  };
75
76
  }
76
- return null;
77
+ return undefined;
77
78
  })
78
- .filter((x) => x !== null);
79
+ .filter((x) => typeof x !== "undefined");
79
80
  return { plugins };
80
81
  }
81
82
  readConfig() {
82
83
  const configPath = this.getConfigPath();
83
84
  const rawText = node_fs_1.default.readFileSync(configPath, "utf8");
84
- const parsed = JSON.parse(rawText);
85
- return this.normalizeConfig(parsed);
85
+ const parsed = (0, parse_json_1.parseJson)(rawText);
86
+ return this.normalizeConfig(parsed.ok ? parsed.value : undefined);
86
87
  }
87
88
  isConfigFilePresent() {
88
89
  return node_fs_1.default.existsSync(this.getConfigPath());
89
90
  }
90
91
  readConfigOrDefault() {
91
92
  const configPath = this.getConfigPath();
92
- const text = node_fs_1.default.existsSync(configPath) ? node_fs_1.default.readFileSync(configPath, "utf8") : null;
93
+ const text = node_fs_1.default.existsSync(configPath) ? node_fs_1.default.readFileSync(configPath, "utf8") : undefined;
93
94
  if (!text) {
94
95
  return defaultConfig;
95
96
  }
96
- try {
97
- const parsed = JSON.parse(text);
98
- return this.normalizeConfig(parsed);
99
- }
100
- catch {
101
- return defaultConfig;
102
- }
97
+ const parsed = (0, parse_json_1.parseJson)(text);
98
+ return parsed.ok ? this.normalizeConfig(parsed.value) : defaultConfig;
103
99
  }
104
100
  writeConfig(config) {
105
101
  this.writeJsonAtomic(this.getConfigPath(), this.normalizeConfig(config));
@@ -5,6 +5,18 @@ const validate_plugin_default_export_1 = require("../utils/validate-plugin-defau
5
5
  const config_service_1 = require("./config-service");
6
6
  const plugin_store_1 = require("./plugin-store");
7
7
  const loadConfiguredPlugins = async () => {
8
+ const getErrorMessage = (error) => {
9
+ if (error instanceof Error) {
10
+ return error.message;
11
+ }
12
+ if (error && typeof error === "object" && "message" in error) {
13
+ const msg = error.message;
14
+ if (typeof msg === "string") {
15
+ return msg;
16
+ }
17
+ }
18
+ return String(error);
19
+ };
8
20
  const configService = new config_service_1.ConfigService();
9
21
  const store = new plugin_store_1.PluginStore(configService);
10
22
  configService.ensureConfigFileExists();
@@ -20,18 +32,16 @@ const loadConfiguredPlugins = async () => {
20
32
  });
21
33
  if (!validation.ok) {
22
34
  console.error(`Skipping plugin ${spec.name}: ${validation.error.message}`);
23
- return null;
35
+ return undefined;
24
36
  }
25
37
  return validation.plugin;
26
38
  }
27
39
  catch (e) {
28
- const msg = e && typeof e.message === "string"
29
- ? e.message
30
- : String(e);
40
+ const msg = getErrorMessage(e);
31
41
  console.error(`Failed to load plugin ${spec.name}: ${msg}`);
32
- return null;
42
+ return undefined;
33
43
  }
34
44
  }));
35
- return loaded.filter((x) => x !== null);
45
+ return loaded.filter((x) => typeof x !== "undefined");
36
46
  };
37
47
  exports.loadConfiguredPlugins = loadConfiguredPlugins;
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.installPluginPackage = void 0;
3
+ exports.updatePluginPackageToLatest = exports.installPluginPackage = void 0;
4
4
  const node_child_process_1 = require("node:child_process");
5
- const installPluginPackage = (store, input) => {
6
- store.ensurePluginPackageJson();
7
- const installDir = store.getInstallDir();
5
+ /**
6
+ * Install a package spec with pnpm first, then fallback to npm.
7
+ *
8
+ * Usage:
9
+ * tryInstallWithFallback("/path/to/plugins", "@scope/plugin@latest");
10
+ */
11
+ const tryInstallWithFallback = (installDir, packageSpec) => {
8
12
  const tryRun = (cmd, args) => {
9
13
  const r = (0, node_child_process_1.spawnSync)(cmd, args, {
10
14
  cwd: installDir,
@@ -19,14 +23,26 @@ const installPluginPackage = (store, input) => {
19
23
  }
20
24
  return { ok: true };
21
25
  };
22
- const pnpm = tryRun("pnpm", ["add", "--silent", input.packageName]);
26
+ const pnpm = tryRun("pnpm", ["add", "--silent", packageSpec]);
23
27
  if (pnpm.ok) {
24
28
  return;
25
29
  }
26
- const npm = tryRun("npm", ["install", "--silent", input.packageName]);
30
+ const npm = tryRun("npm", ["install", "--silent", packageSpec]);
27
31
  if (npm.ok) {
28
32
  return;
29
33
  }
30
- throw new Error(`Failed to install ${input.packageName}. Ensure pnpm or npm is available.`);
34
+ throw new Error(`Failed to install ${packageSpec}. Ensure pnpm or npm is available.`);
35
+ };
36
+ const installPluginPackage = (store, input) => {
37
+ store.ensurePluginPackageJson();
38
+ const installDir = store.getInstallDir();
39
+ tryInstallWithFallback(installDir, input.packageName);
31
40
  };
32
41
  exports.installPluginPackage = installPluginPackage;
42
+ const updatePluginPackageToLatest = (store, input) => {
43
+ store.ensurePluginPackageJson();
44
+ const installDir = store.getInstallDir();
45
+ const packageSpec = `${input.packageName}@latest`;
46
+ tryInstallWithFallback(installDir, packageSpec);
47
+ };
48
+ exports.updatePluginPackageToLatest = updatePluginPackageToLatest;
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -8,6 +41,8 @@ const node_fs_1 = __importDefault(require("node:fs"));
8
41
  const node_module_1 = require("node:module");
9
42
  const node_path_1 = __importDefault(require("node:path"));
10
43
  const node_url_1 = require("node:url");
44
+ const parse_json_1 = require("../utils/parse-json");
45
+ const validate_plugin_default_export_1 = require("../utils/validate-plugin-default-export");
11
46
  const normalizePluginConfigEntry = (entry) => {
12
47
  if (typeof entry === "string") {
13
48
  return { name: entry };
@@ -21,6 +56,12 @@ const normalizePluginConfigEntry = (entry) => {
21
56
  exports.normalizePluginConfigEntry = normalizePluginConfigEntry;
22
57
  const getPluginNameFromSpec = (spec) => spec.name;
23
58
  exports.getPluginNameFromSpec = getPluginNameFromSpec;
59
+ const isRecord = (value) => {
60
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
61
+ };
62
+ const isCallable = (value) => {
63
+ return typeof value === "function";
64
+ };
24
65
  class PluginStore {
25
66
  constructor(configService) {
26
67
  this.configService = configService;
@@ -42,7 +83,7 @@ class PluginStore {
42
83
  private: true,
43
84
  version: "0.0.0"
44
85
  };
45
- node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`, "utf8");
86
+ node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, undefined, 2)}\n`, "utf8");
46
87
  return pkgJsonPath;
47
88
  }
48
89
  createPluginsRequireFromDir(dirPath) {
@@ -53,7 +94,7 @@ class PluginStore {
53
94
  private: true,
54
95
  version: "0.0.0"
55
96
  };
56
- node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`, "utf8");
97
+ node_fs_1.default.writeFileSync(pkgJsonPath, `${JSON.stringify(pkgJson, undefined, 2)}\n`, "utf8");
57
98
  }
58
99
  return (0, node_module_1.createRequire)(pkgJsonPath);
59
100
  }
@@ -69,11 +110,11 @@ class PluginStore {
69
110
  try {
70
111
  const pkgJsonPath = this.resolveInstalledPackageJsonPath(pluginName);
71
112
  const text = node_fs_1.default.readFileSync(pkgJsonPath, "utf8");
72
- const json = JSON.parse(text);
73
- if (!json || typeof json !== "object") {
113
+ const json = (0, parse_json_1.parseJson)(text);
114
+ if (!json.ok || !json.value || typeof json.value !== "object") {
74
115
  return { name: pluginName };
75
116
  }
76
- const o = json;
117
+ const o = json.value;
77
118
  return {
78
119
  name: typeof o.name === "string" ? o.name : pluginName,
79
120
  description: typeof o.description === "string" ? o.description : undefined,
@@ -81,7 +122,7 @@ class PluginStore {
81
122
  };
82
123
  }
83
124
  catch {
84
- return null;
125
+ return undefined;
85
126
  }
86
127
  }
87
128
  async importPluginModule(pluginName) {
@@ -90,30 +131,26 @@ class PluginStore {
90
131
  return this.importResolvedPath(resolved);
91
132
  }
92
133
  async importResolvedPath(resolved) {
93
- const dynamicImport = new Function("specifier", "return import(specifier)");
94
- const mod = await dynamicImport((0, node_url_1.pathToFileURL)(resolved).href);
95
- return (mod ?? {});
134
+ const mod = await Promise.resolve(`${(0, node_url_1.pathToFileURL)(resolved).href}`).then(s => __importStar(require(s)));
135
+ return isRecord(mod) ? mod : {};
96
136
  }
97
137
  async instantiatePluginFromSpec(spec) {
98
138
  const mod = await this.importPluginModule(spec.name);
99
- const defaultExport = mod.default;
100
- if (typeof defaultExport !== "function") {
101
- throw new Error(`Plugin ${spec.name} has no default export (class/function).`);
102
- }
103
- const ctor = defaultExport;
104
- if (typeof spec.options === "undefined") {
105
- return new ctor();
106
- }
107
- if (Array.isArray(spec.options)) {
108
- return new ctor(...spec.options);
139
+ const result = (0, validate_plugin_default_export_1.validatePluginDefaultExport)({
140
+ pluginName: spec.name,
141
+ moduleExports: mod,
142
+ options: spec.options
143
+ });
144
+ if (!result.ok) {
145
+ throw result.error;
109
146
  }
110
- return new ctor(spec.options);
147
+ return result.plugin;
111
148
  }
112
149
  async runOptionalSetup(pluginName) {
113
150
  const mod = await this.importPluginModule(pluginName);
114
151
  const setup = mod.setup;
115
- if (typeof setup === "function") {
116
- await setup();
152
+ if (isCallable(setup)) {
153
+ await Promise.resolve(setup());
117
154
  }
118
155
  }
119
156
  readConfig() {
@@ -21,12 +21,6 @@ const normalizeArgv = (argv) => {
21
21
  return argv;
22
22
  }
23
23
  const first = argv[0];
24
- if (first === "plugin") {
25
- const sub = argv[1];
26
- if (sub === "ls" || sub === "add" || sub === "remove") {
27
- return [`plugin:${sub}`, ...argv.slice(2)];
28
- }
29
- }
30
24
  // If the first token is a URL, treat it as implicit `archive`.
31
25
  if (first && (0, exports.isUrlLike)(first)) {
32
26
  return ["archive", ...argv];
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseJson = void 0;
4
+ const toErrorMessage = (value) => {
5
+ if (value instanceof Error) {
6
+ return value.message;
7
+ }
8
+ if (typeof value === "string") {
9
+ return value;
10
+ }
11
+ if (value && typeof value === "object" && "message" in value) {
12
+ return String(value.message);
13
+ }
14
+ return JSON.stringify(value);
15
+ };
16
+ const parseJson = (text) => {
17
+ try {
18
+ return { ok: true, value: JSON.parse(text) };
19
+ }
20
+ catch (e) {
21
+ return { ok: false, error: new Error(toErrorMessage(e)) };
22
+ }
23
+ };
24
+ exports.parseJson = parseJson;
@@ -2,6 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validatePluginDefaultExport = void 0;
4
4
  const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
5
+ const isPagePocketPlugin = (value) => {
6
+ if (!isRecord(value)) {
7
+ return false;
8
+ }
9
+ if (typeof value.name !== "string" || value.name.trim().length === 0) {
10
+ return false;
11
+ }
12
+ if (typeof value.setup !== "function") {
13
+ return false;
14
+ }
15
+ if (typeof value.contribute !== "undefined" && typeof value.contribute !== "function") {
16
+ return false;
17
+ }
18
+ return true;
19
+ };
5
20
  const toError = (value) => {
6
21
  if (value instanceof Error) {
7
22
  return value;
@@ -24,33 +39,12 @@ const validatePluginDefaultExport = (input) => {
24
39
  : Array.isArray(options)
25
40
  ? new ctor(...options)
26
41
  : new ctor(options);
27
- if (!isRecord(instance)) {
42
+ if (!isPagePocketPlugin(instance)) {
28
43
  return {
29
44
  ok: false,
30
45
  error: new Error(`Plugin ${pluginName} default export did not construct an object.`)
31
46
  };
32
47
  }
33
- const name = instance.name;
34
- if (typeof name !== "string" || name.trim().length === 0) {
35
- return {
36
- ok: false,
37
- error: new Error(`Plugin ${pluginName} instance must have a non-empty string 'name'.`)
38
- };
39
- }
40
- const setup = instance.setup;
41
- if (typeof setup !== "function") {
42
- return {
43
- ok: false,
44
- error: new Error(`Plugin ${pluginName} instance must implement setup(host).`)
45
- };
46
- }
47
- const contribute = instance.contribute;
48
- if (typeof contribute !== "undefined" && typeof contribute !== "function") {
49
- return {
50
- ok: false,
51
- error: new Error(`Plugin ${pluginName} contribute must be a function if provided.`)
52
- };
53
- }
54
48
  return { ok: true, plugin: instance };
55
49
  }
56
50
  catch (e) {
package/dist/view.js CHANGED
@@ -11,11 +11,11 @@ const koa_send_1 = __importDefault(require("koa-send"));
11
11
  const koa_static_1 = __importDefault(require("koa-static"));
12
12
  const createViewServer = async (args) => {
13
13
  const indexPath = node_path_1.default.join(args.rootDir, "index.html");
14
- const stat = await node_fs_1.default.promises.stat(args.rootDir).catch(() => null);
14
+ const stat = await node_fs_1.default.promises.stat(args.rootDir).catch(() => undefined);
15
15
  if (!stat || !stat.isDirectory()) {
16
16
  throw new Error(`view: directory not found: ${args.rootDir}`);
17
17
  }
18
- const indexStat = await node_fs_1.default.promises.stat(indexPath).catch(() => null);
18
+ const indexStat = await node_fs_1.default.promises.stat(indexPath).catch(() => undefined);
19
19
  if (!indexStat || !indexStat.isFile()) {
20
20
  throw new Error(`view: index.html not found under: ${args.rootDir}`);
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagepocket/cli",
3
- "version": "0.8.5",
3
+ "version": "0.9.0",
4
4
  "description": "CLI for capturing offline snapshots of web pages.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -21,15 +21,15 @@
21
21
  "koa-static": "^5.0.0",
22
22
  "npm-package-arg": "^13.0.2",
23
23
  "ora": "^9.0.0",
24
- "@pagepocket/capture-http-cdp-unit": "0.8.5",
25
- "@pagepocket/build-snapshot-unit": "0.8.5",
26
- "@pagepocket/capture-http-lighterceptor-unit": "0.8.5",
27
- "@pagepocket/capture-http-puppeteer-unit": "0.8.5",
28
- "@pagepocket/contracts": "0.8.5",
29
- "@pagepocket/plugin-yt-dlp": "0.8.5",
30
- "@pagepocket/lib": "0.8.5",
31
- "@pagepocket/single-file-unit": "0.8.5",
32
- "@pagepocket/write-down-unit": "0.8.5"
24
+ "@pagepocket/build-snapshot-unit": "0.9.0",
25
+ "@pagepocket/contracts": "0.9.0",
26
+ "@pagepocket/capture-http-lighterceptor-unit": "0.9.0",
27
+ "@pagepocket/capture-http-puppeteer-unit": "0.9.0",
28
+ "@pagepocket/lib": "0.9.0",
29
+ "@pagepocket/capture-http-cdp-unit": "0.9.0",
30
+ "@pagepocket/single-file-unit": "0.9.0",
31
+ "@pagepocket/plugin-yt-dlp": "0.9.0",
32
+ "@pagepocket/write-down-unit": "0.9.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/koa": "^2.15.0",
@@ -41,6 +41,7 @@
41
41
  },
42
42
  "oclif": {
43
43
  "bin": "pp",
44
+ "topicSeparator": " ",
44
45
  "commands": {
45
46
  "strategy": "pattern",
46
47
  "target": "dist/commands"