@mearl/setup 3.0.0-beta.8 → 3.0.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.en.md +276 -0
- package/README.md +65 -45
- package/dist/browserExtension.d.ts +3 -0
- package/dist/browserExtension.js +18 -2
- package/dist/browserPreset.d.ts +0 -3
- package/dist/browserPreset.js +12 -22
- package/dist/cli.d.ts +8 -2
- package/dist/cli.js +59 -4
- package/dist/lifecycle.d.ts +4 -0
- package/dist/lifecycle.js +45 -4
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +19 -5
- package/dist/preset.d.ts +4 -1
- package/dist/preset.js +23 -13
- package/dist/presetStore.d.ts +2 -46
- package/dist/presetStore.js +1 -248
- package/dist/setupPreset.js +1 -1
- package/package.json +7 -6
package/dist/browserPreset.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { invokeLocalHost, listLiveBrowsers } from '@mearl/client/local';
|
|
2
2
|
import { installationOriginCovered, loadInstallationConfig, matchesInstallationOrigin, trustedInstallationPreset } from '@mearl/preset/installation';
|
|
3
|
-
import { createNodePresetLoader, PresetInstaller, resolveInstallationConfigUrl } from './preset.js';
|
|
3
|
+
import { connectedPresetExtensions, createNodePresetLoader, PresetInstaller, resolveInstallationConfigUrl } from './preset.js';
|
|
4
4
|
/** Explicit setup may complete a trusted browser selection before installing any Skills. */
|
|
5
5
|
export async function prepareBrowserSetupPreset(options = {}, dependencies = {}) {
|
|
6
6
|
const env = options.env ?? process.env;
|
|
@@ -8,27 +8,29 @@ export async function prepareBrowserSetupPreset(options = {}, dependencies = {})
|
|
|
8
8
|
const current = await new PresetInstaller({ directory }).store.current();
|
|
9
9
|
if (current.activation || current.receipt)
|
|
10
10
|
return;
|
|
11
|
-
const now = dependencies.now ?? Date.now;
|
|
12
|
-
const deadline = now() + (options.waitMs ?? 0);
|
|
13
11
|
const browsers = dependencies.browsers ?? listLiveBrowsers;
|
|
14
12
|
const invoke = dependencies.invoke ?? invokeLocalHost;
|
|
15
|
-
const sleep = dependencies.sleep ?? (ms => new Promise(resolve => setTimeout(resolve, ms)));
|
|
16
13
|
const configuration = dependencies.configuration ?? (() => {
|
|
17
14
|
const url = resolveInstallationConfigUrl(env.MEARL_INSTALLATION_CONFIG_URL);
|
|
18
15
|
return loadInstallationConfig(url, url.origin, AbortSignal.timeout(5_000));
|
|
19
16
|
});
|
|
20
|
-
const
|
|
21
|
-
|
|
17
|
+
const connected = () => connectedPresetExtensions(browsers(), env.MEARL_SOCKET_PATH?.trim());
|
|
18
|
+
const read = async () => {
|
|
19
|
+
const targets = connected();
|
|
22
20
|
if (!targets.length)
|
|
23
21
|
return;
|
|
24
22
|
return (await Promise.all(targets.map(async (browser) => {
|
|
25
23
|
let value;
|
|
26
24
|
try {
|
|
27
|
-
value = await invoke('preset_browser_status', {}, { socketPath: browser.socketPath, timeoutSec });
|
|
25
|
+
value = await invoke('preset_browser_status', {}, { socketPath: browser.socketPath, timeoutSec: 5 });
|
|
28
26
|
}
|
|
29
27
|
catch {
|
|
28
|
+
if (!connected().some(target => target.browserId === browser.browserId && target.socketPath === browser.socketPath))
|
|
29
|
+
return [];
|
|
30
30
|
throw new Error(`Cannot read preset from ${browser.browserId}. Reconnect the extension and retry setup, or use --preset <source>.`);
|
|
31
31
|
}
|
|
32
|
+
if (value?.error === 'EXTENSION_NOT_CONNECTED')
|
|
33
|
+
return [];
|
|
32
34
|
if (value?.error || typeof value?.browserReady !== 'boolean' || !Object.hasOwn(value, 'browserRevision') || (value.presetId !== undefined && (typeof value.presetId !== 'string' || !value.presetId || typeof value.presetSource !== 'string' || typeof value.browserRevision !== 'string' || value.browserReady !== true)))
|
|
33
35
|
throw new Error(`Browser preset is unavailable in ${browser.browserId}. Reconnect the extension and retry setup, or use --preset <source>.`);
|
|
34
36
|
if (!value.presetId)
|
|
@@ -36,21 +38,9 @@ export async function prepareBrowserSetupPreset(options = {}, dependencies = {})
|
|
|
36
38
|
return [{ browserId: browser.browserId, presetId: value.presetId, presetSource: value.presetSource, browserRevision: value.browserRevision }];
|
|
37
39
|
}))).flat();
|
|
38
40
|
};
|
|
39
|
-
|
|
40
|
-
if (!
|
|
41
|
-
options.writeLine?.('Waiting for the Mearl extension to connect and checking its installed preset…');
|
|
42
|
-
while (!selected && now() < deadline) {
|
|
43
|
-
await sleep(Math.min(1000, deadline - now()));
|
|
44
|
-
if (now() < deadline)
|
|
45
|
-
selected = await read(Math.max(0.1, Math.min(5, (deadline - now()) / 1000)));
|
|
46
|
-
}
|
|
47
|
-
if (!selected) {
|
|
48
|
-
options.writeLine?.('No connected Mearl extension was found. Continuing without a browser preset; connect and rerun setup to adopt it, or use --preset <source> to select a preset.');
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (!selected.length)
|
|
41
|
+
const accepted = await read();
|
|
42
|
+
if (!accepted?.length)
|
|
52
43
|
return;
|
|
53
|
-
const accepted = selected;
|
|
54
44
|
const first = accepted[0];
|
|
55
45
|
if (accepted.some(value => value.presetId !== first.presetId || value.presetSource !== first.presetSource))
|
|
56
46
|
throw new Error('Connected browsers use different presets. Select one explicitly with --preset <source>.');
|
|
@@ -58,7 +48,7 @@ export async function prepareBrowserSetupPreset(options = {}, dependencies = {})
|
|
|
58
48
|
if (!trusted)
|
|
59
49
|
throw new Error('The browser preset is not in the trusted installation catalog. Install it explicitly with --preset <source>.');
|
|
60
50
|
const load = dependencies.load ?? createNodePresetLoader();
|
|
61
|
-
const installer = new PresetInstaller({ directory, load: (source, resourceOptions) => {
|
|
51
|
+
const installer = new PresetInstaller({ directory, connectedExtensionsOnly: true, load: (source, resourceOptions) => {
|
|
62
52
|
const url = new URL(source);
|
|
63
53
|
if (url.protocol !== 'https:' || url.username || url.password || !trusted.origins.some(origin => matchesInstallationOrigin(origin, url)))
|
|
64
54
|
throw new Error('Browser preset resource is outside the trusted installation catalog');
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { installMearlBrowserExtension } from './browserExtension.js';
|
|
2
|
+
import { downloadMearlBrowserExtension, installMearlBrowserExtension } from './browserExtension.js';
|
|
3
3
|
import { runMearlLifecycle, type MearlLifecycleOperation } from './lifecycle.js';
|
|
4
4
|
import type { MearlProfile } from './profiles.js';
|
|
5
5
|
import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
|
|
@@ -9,10 +9,13 @@ import { prepareSetupPreset } from './setupPreset.js';
|
|
|
9
9
|
export { SETUP_VERSION } from './setupVersion.js';
|
|
10
10
|
interface ParsedArgs {
|
|
11
11
|
cloudMode?: string;
|
|
12
|
+
extract?: boolean;
|
|
12
13
|
force?: boolean;
|
|
13
14
|
full?: boolean;
|
|
14
15
|
help: boolean;
|
|
15
|
-
operation?: MearlLifecycleOperation | 'extension' | 'plugin' | 'provider' | 'preset';
|
|
16
|
+
operation?: MearlLifecycleOperation | 'extension' | 'plugin' | 'provider' | 'preset' | 'profile';
|
|
17
|
+
profileSource?: string;
|
|
18
|
+
profileBrowser?: string;
|
|
16
19
|
presetCommand?: 'apply' | 'update' | 'status' | 'repair';
|
|
17
20
|
presetSource?: string;
|
|
18
21
|
presetReplaceSkills?: string[];
|
|
@@ -30,12 +33,14 @@ interface ParsedArgs {
|
|
|
30
33
|
yes: boolean;
|
|
31
34
|
}
|
|
32
35
|
export interface SetupCliDependencies {
|
|
36
|
+
installProfile?: typeof installProfile;
|
|
33
37
|
preparePreset?: typeof prepareSetupPreset;
|
|
34
38
|
runPreset?: typeof runPresetCommand;
|
|
35
39
|
registerPlugin?: typeof setupPlugin;
|
|
36
40
|
ensureOfficialPlugin?: typeof ensureOfficialPlugin;
|
|
37
41
|
installPlugins?: typeof installPlugins;
|
|
38
42
|
uninstallPlugin?: typeof uninstallPlugin;
|
|
43
|
+
downloadBrowserExtension?: typeof downloadMearlBrowserExtension;
|
|
39
44
|
installBrowserExtension?: typeof installMearlBrowserExtension;
|
|
40
45
|
installProvider?: typeof installProviderPackage;
|
|
41
46
|
runLifecycle?: typeof runMearlLifecycle;
|
|
@@ -43,4 +48,5 @@ export interface SetupCliDependencies {
|
|
|
43
48
|
updateProvider?: typeof updateProviderPackage;
|
|
44
49
|
}
|
|
45
50
|
export declare function parseSetupArgs(argv: string[]): ParsedArgs;
|
|
51
|
+
declare function installProfile(source: string, browser?: string): Promise<unknown>;
|
|
46
52
|
export declare function main(argv?: string[], interactiveTerminal?: boolean, dependencies?: SetupCliDependencies): Promise<number>;
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, CommanderError } from 'commander';
|
|
3
|
-
import {
|
|
3
|
+
import { invoke, listLiveBrowsers } from '@mearl/client/local';
|
|
4
|
+
import { parseProfileSource } from '@mearl/preset/bundles';
|
|
5
|
+
import { downloadMearlBrowserExtension, installMearlBrowserExtension, isUnsupportedCdpMethodError } from './browserExtension.js';
|
|
4
6
|
import { runMearlLifecycle } from './lifecycle.js';
|
|
5
7
|
import { isMearlReleaseLine } from './version.js';
|
|
6
8
|
import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
|
|
@@ -25,6 +27,15 @@ function commanderError(error) {
|
|
|
25
27
|
return new Error(message);
|
|
26
28
|
}
|
|
27
29
|
export function parseSetupArgs(argv) {
|
|
30
|
+
if (argv[0] === 'profile') {
|
|
31
|
+
if (argv.length === 2 && (argv[1] === '--help' || argv[1] === '-h'))
|
|
32
|
+
return { ...baseParsedArgs(), help: true };
|
|
33
|
+
if (argv[1] !== 'install' || !argv[2] || (argv.length !== 3 && !(argv.length === 5 && argv[3] === '--browser' && argv[4]))) {
|
|
34
|
+
throw new Error('profile requires install <GitHub owner/repo[@ref] or HTTPS bundle URL> [--browser <id-or-name>]');
|
|
35
|
+
}
|
|
36
|
+
parseProfileSource(argv[2]);
|
|
37
|
+
return { ...baseParsedArgs(), operation: 'profile', profileSource: argv[2], profileBrowser: argv[4] };
|
|
38
|
+
}
|
|
28
39
|
if (argv[0] === 'preset') {
|
|
29
40
|
if (argv[1] === 'repair') {
|
|
30
41
|
const command = new Command('preset repair')
|
|
@@ -163,12 +174,17 @@ export function parseSetupArgs(argv) {
|
|
|
163
174
|
.configureOutput({ writeErr: () => { }, writeOut: () => { } })
|
|
164
175
|
.option('--full')
|
|
165
176
|
.option('--force')
|
|
177
|
+
.option('--extract')
|
|
166
178
|
.option('-y, --yes')
|
|
167
179
|
.option('--non-interactive')
|
|
168
180
|
.action(() => {
|
|
169
181
|
const options = extension.opts();
|
|
182
|
+
if (options.extract && (options.full || options.force)) {
|
|
183
|
+
throw new Error('--extract cannot be combined with --full or --force');
|
|
184
|
+
}
|
|
170
185
|
parsed = {
|
|
171
186
|
...baseParsedArgs(),
|
|
187
|
+
...(options.extract ? { extract: true } : {}),
|
|
172
188
|
...(options.force ? { force: true } : {}),
|
|
173
189
|
...(options.full ? { full: true } : {}),
|
|
174
190
|
operation: 'extension',
|
|
@@ -206,6 +222,7 @@ Environment commands:
|
|
|
206
222
|
uninstall [options] Uninstall detected or selected environments
|
|
207
223
|
|
|
208
224
|
Integration commands:
|
|
225
|
+
profile install <source> Install one App profile bundle in a connected extension
|
|
209
226
|
preset apply <source> Apply a preset, synchronize hosts and install declared components
|
|
210
227
|
preset update Update the selected preset, preserving user overrides
|
|
211
228
|
preset repair Retry the installed preset using its accepted resources
|
|
@@ -219,6 +236,7 @@ Integration commands:
|
|
|
219
236
|
|
|
220
237
|
Browser extension:
|
|
221
238
|
extension [--full] [--force] Install or update the browser extension
|
|
239
|
+
extension --extract Download and unpack the extension for manual loading
|
|
222
240
|
|
|
223
241
|
Environment options:
|
|
224
242
|
--local Manage @mearl/native-host and @mearl/client
|
|
@@ -251,8 +269,8 @@ Examples:
|
|
|
251
269
|
npx @mearl/setup --local --cloud
|
|
252
270
|
npx @mearl/setup update
|
|
253
271
|
npx @mearl/setup uninstall --full
|
|
254
|
-
npx @mearl/setup plugin uninstall
|
|
255
|
-
npx @mearl/setup provider install @example/mearl-provider-
|
|
272
|
+
npx @mearl/setup plugin uninstall tabs
|
|
273
|
+
npx @mearl/setup provider install @example/mearl-provider-device
|
|
256
274
|
npx @mearl/setup preset repair --replace-skill example-skill
|
|
257
275
|
npx @mearl/setup extension --full`;
|
|
258
276
|
console.log(output.replaceAll('npx @mearl/setup', SETUP_COMMAND));
|
|
@@ -274,9 +292,22 @@ async function runSetupLifecycle(options, dependencies) {
|
|
|
274
292
|
const actual = await (dependencies.runPreset ?? runPresetCommand)('status');
|
|
275
293
|
if (actual.revision !== preset.revision)
|
|
276
294
|
throw new Error('The selected preset changed while setup was running');
|
|
277
|
-
|
|
295
|
+
const warnings = [...new Set([...(preset.warnings ?? []), ...(actual.warnings ?? [])])];
|
|
296
|
+
console.log(JSON.stringify({ ...actual, warnings }, null, 2));
|
|
278
297
|
return actual.ready ? 0 : 3;
|
|
279
298
|
}
|
|
299
|
+
async function installProfile(source, browser) {
|
|
300
|
+
if (!browser) {
|
|
301
|
+
const extensions = listLiveBrowsers().filter(target => target.transport === 'extension' && (target.status === undefined || target.status === 'connected'));
|
|
302
|
+
if (extensions.length !== 1)
|
|
303
|
+
throw new Error(extensions.length
|
|
304
|
+
? 'Multiple extensions are connected. Select one with --browser <id-or-name>.'
|
|
305
|
+
: 'Connect a Mearl extension before installing App profiles.');
|
|
306
|
+
browser = extensions[0].browserId;
|
|
307
|
+
}
|
|
308
|
+
console.error('If the bundle source needs permission, confirm it in the browser.');
|
|
309
|
+
return invoke('profile_install', { source }, { browser, timeoutSec: 150 });
|
|
310
|
+
}
|
|
280
311
|
export async function main(argv = process.argv.slice(2), interactiveTerminal = process.stdin.isTTY === true && process.stdout.isTTY === true, dependencies = {}) {
|
|
281
312
|
let parsed;
|
|
282
313
|
try {
|
|
@@ -295,6 +326,17 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
|
|
|
295
326
|
console.log(SETUP_VERSION);
|
|
296
327
|
return 0;
|
|
297
328
|
}
|
|
329
|
+
if (parsed.operation === 'profile') {
|
|
330
|
+
try {
|
|
331
|
+
const result = await (dependencies.installProfile ?? installProfile)(parsed.profileSource, parsed.profileBrowser);
|
|
332
|
+
console.log(JSON.stringify(result, null, 2));
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
337
|
+
return 1;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
298
340
|
if (parsed.operation === 'preset') {
|
|
299
341
|
try {
|
|
300
342
|
const run = dependencies.runPreset ?? runPresetCommand;
|
|
@@ -376,6 +418,18 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
|
|
|
376
418
|
}
|
|
377
419
|
}
|
|
378
420
|
if (parsed.operation === 'extension') {
|
|
421
|
+
if (parsed.extract) {
|
|
422
|
+
try {
|
|
423
|
+
const result = await (dependencies.downloadBrowserExtension ?? downloadMearlBrowserExtension)();
|
|
424
|
+
console.log(`Mearl v${result.version} is ready to load from ${result.path}`);
|
|
425
|
+
console.log('Open chrome://extensions/, enable Developer mode, click Load unpacked, and select that directory.');
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
430
|
+
return 1;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
379
433
|
if (!interactiveTerminal && !parsed.yes) {
|
|
380
434
|
console.error('Non-interactive input detected. Re-run with --yes or --non-interactive.');
|
|
381
435
|
return 2;
|
|
@@ -411,6 +465,7 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
|
|
|
411
465
|
: `Browser extension v${result.version} was not installed.`);
|
|
412
466
|
if (result.cdpError) {
|
|
413
467
|
console.error(`CDP installation failed: ${result.cdpError}`);
|
|
468
|
+
console.error(`To load the extension manually, run: ${SETUP_COMMAND} extension --extract`);
|
|
414
469
|
if (isUnsupportedCdpMethodError(result.cdpError)) {
|
|
415
470
|
console.error(`This Chrome version does not support installing extensions through CDP. Update Chrome to version 145 or later, then rerun: ${SETUP_COMMAND} extension`);
|
|
416
471
|
extensionFailureIsTerminal = true;
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { detectGlobalPackage, type PackageManagerInstallation, type PackageDetec
|
|
|
5
5
|
import { type DetectedMearlProfiles, type MearlProfile } from './profiles.js';
|
|
6
6
|
import { uninstallNativeHostArtifacts } from './nativeHost.js';
|
|
7
7
|
import { type CommandRunner } from '@mearl/daemon-core';
|
|
8
|
+
import { readInstallationManagement } from '@mearl/daemon-core/installation';
|
|
9
|
+
import { claimPresetInstallation } from './presetInstallationLease.js';
|
|
8
10
|
import { installManagedPlugins, uninstallManagedPlugins } from './managedPlugins.js';
|
|
9
11
|
import { uninstallPlugin } from './plugin.js';
|
|
10
12
|
import { uninstallProviderPackage } from './provider.js';
|
|
@@ -30,6 +32,8 @@ export interface MearlLifecycleOptions {
|
|
|
30
32
|
env?: NodeJS.ProcessEnv;
|
|
31
33
|
}
|
|
32
34
|
export interface MearlLifecycleDependencies {
|
|
35
|
+
readManagement?: typeof readInstallationManagement;
|
|
36
|
+
claimLifecycle?: () => ReturnType<typeof claimPresetInstallation>;
|
|
33
37
|
prepareBrowserPreset?: typeof prepareBrowserSetupPreset;
|
|
34
38
|
preparePreset?: typeof prepareSetupPreset;
|
|
35
39
|
onPresetStatus?: (status: PresetInstallStatus) => void;
|
package/dist/lifecycle.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SETUP_VERSION } from './setupVersion.js';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
4
5
|
import { createInterface } from 'node:readline/promises';
|
|
5
6
|
import { stdin as input, stdout as output } from 'node:process';
|
|
6
7
|
import { listPluginNames } from '@mearl/client/plugin';
|
|
@@ -9,7 +10,9 @@ import { runGlobalMearlSkillOperation } from './skills.js';
|
|
|
9
10
|
import { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js';
|
|
10
11
|
import { MEARL_PROFILES, detectMearlProfiles, } from './profiles.js';
|
|
11
12
|
import { uninstallNativeHostArtifacts } from './nativeHost.js';
|
|
12
|
-
import { executableNames, prependCommandPath, runCommand, } from '@mearl/daemon-core';
|
|
13
|
+
import { executableNames, prependCommandPath, runCommand, withOwnedCommands, } from '@mearl/daemon-core';
|
|
14
|
+
import { readInstallationManagement, setupManagementCommand, setupRuntimeCompatible } from '@mearl/daemon-core/installation';
|
|
15
|
+
import { claimPresetInstallation } from './presetInstallationLease.js';
|
|
13
16
|
import { isMearlReleaseLine, mearlPackageSelector } from './version.js';
|
|
14
17
|
import { installManagedPlugins, uninstallManagedPlugins, } from './managedPlugins.js';
|
|
15
18
|
import { uninstallPlugin } from './plugin.js';
|
|
@@ -176,6 +179,38 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
176
179
|
if (options.targetLine && !isMearlReleaseLine(options.targetLine)) {
|
|
177
180
|
throw new Error('targetLine must be a valid Mearl release line such as 2.3.x');
|
|
178
181
|
}
|
|
182
|
+
const writeLine = dependencies.writeLine ?? (line => console.error(line));
|
|
183
|
+
let lease;
|
|
184
|
+
try {
|
|
185
|
+
lease = (dependencies.claimLifecycle ?? (() => claimPresetInstallation(path.join(homedir(), '.agents', '.mearl-setup-lifecycle.json'))))();
|
|
186
|
+
const readManagement = () => (dependencies.readManagement ?? readInstallationManagement)({ directory: (options.env ?? process.env).MEARL_CAPABILITY_DIR });
|
|
187
|
+
let acceptedRevision = (await readManagement()).revision;
|
|
188
|
+
let selector = mearlPackageSelector(SETUP_VERSION, options.targetLine);
|
|
189
|
+
const assertManagement = async (expected = acceptedRevision) => {
|
|
190
|
+
const { setup, revision } = await readManagement();
|
|
191
|
+
if (revision !== expected)
|
|
192
|
+
throw new Error('PRESET_SELECTION_CHANGED: The selected preset changed during setup.');
|
|
193
|
+
if (setup && options.operation !== 'uninstall' && !setupRuntimeCompatible(setup, SETUP_VERSION, options.targetLine)) {
|
|
194
|
+
throw new Error(`SETUP_RUNTIME_CONFLICT: The selected preset requires ${setup.runtime}. Use ${setupManagementCommand(setup)} ${options.operation}.`);
|
|
195
|
+
}
|
|
196
|
+
selector = setup?.runtime ? options.targetLine ?? SETUP_VERSION : mearlPackageSelector(SETUP_VERSION, options.targetLine);
|
|
197
|
+
};
|
|
198
|
+
await assertManagement();
|
|
199
|
+
const runner = dependencies.runCommand ?? runCommand;
|
|
200
|
+
return await withOwnedCommands(lease.lifecycle, () => executeLifecycle(options, {
|
|
201
|
+
...dependencies,
|
|
202
|
+
runCommand: async (...args) => { await assertManagement(); return runner(...args); },
|
|
203
|
+
}, async (revision) => { await assertManagement(revision); acceptedRevision = revision; }, () => selector));
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
writeLine(`Setup: ${error instanceof Error ? error.message : String(error)}`);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
lease?.release();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function executeLifecycle(options, dependencies, acceptPreset, packageSelector) {
|
|
179
214
|
const env = options.env ?? process.env;
|
|
180
215
|
const writeLine = dependencies.writeLine ?? (line => console.error(line));
|
|
181
216
|
const existsSync = dependencies.existsSync ?? fs.existsSync;
|
|
@@ -290,6 +325,8 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
290
325
|
stagedPreset = await (dependencies.preparePreset ?? prepareSetupPreset)({
|
|
291
326
|
source: options.presetSource, env,
|
|
292
327
|
});
|
|
328
|
+
if (stagedPreset)
|
|
329
|
+
await acceptPreset(stagedPreset.revision);
|
|
293
330
|
}
|
|
294
331
|
catch (error) {
|
|
295
332
|
writeLine(`Preset preparation: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -322,7 +359,7 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
322
359
|
detected.get('local') ??
|
|
323
360
|
detected.get('cloud') ??
|
|
324
361
|
resolveInvokingManager(env);
|
|
325
|
-
const sharedClient = await runPackageOperation(clientInstallation, options.operation, [UNIFIED_CLIENT],
|
|
362
|
+
const sharedClient = await runPackageOperation(clientInstallation, options.operation, [UNIFIED_CLIENT], packageSelector(), runner, env, 'Packages (shared client)', writeLine);
|
|
326
363
|
if (!sharedClient.ok) {
|
|
327
364
|
writeLine('Mearl setup incomplete. Review the messages above.');
|
|
328
365
|
return false;
|
|
@@ -362,7 +399,7 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
362
399
|
if (packages.length === 0) {
|
|
363
400
|
continue;
|
|
364
401
|
}
|
|
365
|
-
const packageResult = await runPackageOperation(installation, packageOperation, packages,
|
|
402
|
+
const packageResult = await runPackageOperation(installation, packageOperation, packages, packageSelector(), runner, env, `Packages (${profile})`, writeLine);
|
|
366
403
|
if (!packageResult.ok) {
|
|
367
404
|
packagesOk = false;
|
|
368
405
|
continue;
|
|
@@ -422,7 +459,9 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
422
459
|
}
|
|
423
460
|
if (packagesOk && selectsInitialPreset && !stagedPreset && !options.presetSource && lifecycleSelected.some(profile => profile === 'local' || profile === 'mcp')) {
|
|
424
461
|
try {
|
|
425
|
-
stagedPreset = await (dependencies.prepareBrowserPreset ?? prepareBrowserSetupPreset)({ env,
|
|
462
|
+
stagedPreset = await (dependencies.prepareBrowserPreset ?? prepareBrowserSetupPreset)({ env, writeLine });
|
|
463
|
+
if (stagedPreset)
|
|
464
|
+
await acceptPreset(stagedPreset.revision);
|
|
426
465
|
}
|
|
427
466
|
catch (error) {
|
|
428
467
|
writeLine(`Browser preset: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -432,6 +471,8 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
432
471
|
if (packagesOk && selectsInitialPreset && !stagedPreset && options.defaultPresetSource) {
|
|
433
472
|
try {
|
|
434
473
|
stagedPreset = await (dependencies.preparePreset ?? prepareSetupPreset)({ fallbackSource: options.defaultPresetSource, env });
|
|
474
|
+
if (stagedPreset)
|
|
475
|
+
await acceptPreset(stagedPreset.revision);
|
|
435
476
|
}
|
|
436
477
|
catch (error) {
|
|
437
478
|
writeLine(`Default preset: ${error instanceof Error ? error.message : String(error)}`);
|
package/dist/plugin.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface PluginSetupResult {
|
|
|
5
5
|
entry: string;
|
|
6
6
|
name: string;
|
|
7
7
|
}
|
|
8
|
-
export declare function setupPlugin(target: string, requestedName?: string): Promise<PluginSetupResult>;
|
|
8
|
+
export declare function setupPlugin(target: string, requestedName?: string, runner?: CommandRunner): Promise<PluginSetupResult>;
|
|
9
9
|
export declare function ensureOfficialPlugin(name: string, runner?: CommandRunner): Promise<'existing' | 'installed'>;
|
|
10
10
|
export interface PluginUninstallResult {
|
|
11
11
|
entryMissing?: boolean;
|
package/dist/plugin.js
CHANGED
|
@@ -30,14 +30,15 @@ function defaultPluginName(target, entry) {
|
|
|
30
30
|
? basename(dirname(entryDirectory))
|
|
31
31
|
: basename(entryDirectory);
|
|
32
32
|
}
|
|
33
|
-
export async function setupPlugin(target, requestedName) {
|
|
33
|
+
export async function setupPlugin(target, requestedName, runner = runCommand) {
|
|
34
34
|
if (requestedName)
|
|
35
35
|
assertPluginName(requestedName);
|
|
36
36
|
const entry = resolvePluginEntry(target);
|
|
37
37
|
const plugin = await loadPlugin(entry, registrationContext);
|
|
38
38
|
const name = requestedName || plugin.name || defaultPluginName(target, entry);
|
|
39
39
|
assertPluginName(name);
|
|
40
|
-
await ensurePluginRuntime();
|
|
40
|
+
await ensurePluginRuntime(runner);
|
|
41
|
+
await plugin.setup?.({ runCommand: runner });
|
|
41
42
|
return {
|
|
42
43
|
entry: registerPlugin(name, entry, plugin.description, {
|
|
43
44
|
hasPublicCommands: Boolean(plugin.commandHelp(name)),
|
|
@@ -345,6 +346,7 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
|
|
|
345
346
|
throw new Error(`多个 skill 使用插件名称 ${name},请调整入口的 name metadata。`);
|
|
346
347
|
plugins.set(name, {
|
|
347
348
|
entry,
|
|
349
|
+
setup: plugin.setup,
|
|
348
350
|
hasPublicCommands: Boolean(plugin.commandHelp(name)),
|
|
349
351
|
description: plugin.description,
|
|
350
352
|
...(plugin.toolbox ? { toolbox: plugin.toolbox } : {}),
|
|
@@ -357,14 +359,26 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
|
|
|
357
359
|
return 0;
|
|
358
360
|
}
|
|
359
361
|
await ensurePluginRuntime(runner);
|
|
360
|
-
for (const [name, { entry, description, toolbox, appProfiles, profileSources, hasPublicCommands },] of plugins) {
|
|
362
|
+
for (const [name, { entry, description, toolbox, appProfiles, profileSources, hasPublicCommands, setup },] of plugins) {
|
|
361
363
|
await options.confirm?.();
|
|
362
364
|
verifySkill();
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
+
const hasUserRegistration = () => {
|
|
366
|
+
const existing = findPluginRegistration(name);
|
|
367
|
+
return options.registrationManagedBy && existing && existing.managedBy !== options.registrationManagedBy;
|
|
368
|
+
};
|
|
369
|
+
if (hasUserRegistration()) {
|
|
365
370
|
console.error(`Plugin ${name}: existing user registration kept.`);
|
|
366
371
|
continue;
|
|
367
372
|
}
|
|
373
|
+
if (setup) {
|
|
374
|
+
await setup({ runCommand: runner });
|
|
375
|
+
await options.confirm?.();
|
|
376
|
+
verifySkill();
|
|
377
|
+
if (hasUserRegistration()) {
|
|
378
|
+
console.error(`Plugin ${name}: existing user registration kept.`);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
368
382
|
registerPlugin(name, entry, description, {
|
|
369
383
|
hasPublicCommands,
|
|
370
384
|
managedBy: options.registrationManagedBy ?? (cli.id === 'skills' ? undefined : cli.id),
|
package/dist/preset.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { invoke } from '@mearl/client/local';
|
|
1
|
+
import { invoke, listLiveBrowsers } from '@mearl/client/local';
|
|
2
2
|
import { type JsonObject } from '@mearl/preset';
|
|
3
3
|
import { type BundleLoader, type PreparedPreset } from '@mearl/preset/bundles';
|
|
4
4
|
import { PresetInstallationStore } from './presetStore.js';
|
|
@@ -60,6 +60,8 @@ export interface PresetInstallerOptions {
|
|
|
60
60
|
load?: BundleLoader;
|
|
61
61
|
hosts?: () => PresetHostTarget[];
|
|
62
62
|
invoke?: typeof invoke;
|
|
63
|
+
/** CLI setup synchronizes connected extensions without requiring their acknowledgement. */
|
|
64
|
+
connectedExtensionsOnly?: boolean;
|
|
63
65
|
}
|
|
64
66
|
export interface PresetApplyOptions {
|
|
65
67
|
validatePrepared?: (prepared: PreparedPreset) => Promise<void>;
|
|
@@ -76,6 +78,7 @@ export interface StagedPresetInstallation {
|
|
|
76
78
|
}
|
|
77
79
|
/** The caller chooses the source; this loader adds no identity or credential discovery. */
|
|
78
80
|
export declare function createNodePresetLoader(): BundleLoader;
|
|
81
|
+
export declare function connectedPresetExtensions(browsers: ReturnType<typeof listLiveBrowsers>, socketPath?: string): import("@mearl/client/local").LiveBrowser[];
|
|
79
82
|
/** Installation and live acknowledgements are distinct; browser/external installs remain pending. */
|
|
80
83
|
export declare class PresetInstaller {
|
|
81
84
|
readonly store: PresetInstallationStore;
|
package/dist/preset.js
CHANGED
|
@@ -71,6 +71,10 @@ function localHosts(allowStart = false) {
|
|
|
71
71
|
const hosts = configured ? [{ id: 'local', socketPath: configured }] : listLiveBrowsers().filter(browser => !browser.provider).map(browser => ({ id: browser.browserId, socketPath: browser.socketPath }));
|
|
72
72
|
return hosts.length ? Array.from(new Map(hosts.map(host => [host.socketPath, host])).values()) : [{ id: 'local', ...(allowStart ? {} : { socketPath: SOCKET_PATH }) }];
|
|
73
73
|
}
|
|
74
|
+
export function connectedPresetExtensions(browsers, socketPath) {
|
|
75
|
+
return browsers.filter(browser => !browser.provider && browser.transport === 'extension' &&
|
|
76
|
+
(browser.status === undefined || browser.status === 'connected') && (!socketPath || browser.socketPath === socketPath));
|
|
77
|
+
}
|
|
74
78
|
/** Installation and live acknowledgements are distinct; browser/external installs remain pending. */
|
|
75
79
|
export class PresetInstaller {
|
|
76
80
|
store;
|
|
@@ -99,13 +103,18 @@ export class PresetInstaller {
|
|
|
99
103
|
}));
|
|
100
104
|
}
|
|
101
105
|
async browserStatus(reload, browserRevision, installationRevision) {
|
|
102
|
-
const targets = this.options.
|
|
106
|
+
const targets = this.options.connectedExtensionsOnly
|
|
107
|
+
? connectedPresetExtensions(listLiveBrowsers(), process.env.MEARL_SOCKET_PATH?.trim()).map(browser => ({ id: browser.browserId, socketPath: browser.socketPath }))
|
|
108
|
+
: this.options.hosts?.() ?? localHosts(reload);
|
|
103
109
|
return Promise.all(targets.map(async (target) => {
|
|
104
110
|
try {
|
|
105
111
|
const result = await this.invokeHost(reload ? 'preset_browser_sync' : 'preset_browser_status', reload ? { installationRevision } : {}, target, reload ? 130 : 15, reload);
|
|
106
112
|
if (result?.accepted === false)
|
|
107
113
|
return { id: target.id, state: 'rejected', settingsReady: false, error: typeof result.error === 'string' ? result.error : 'Browser preset activation rejected' };
|
|
108
|
-
|
|
114
|
+
const error = Array.isArray(result?.permissionPending) && result.permissionPending.length
|
|
115
|
+
? 'PRESET_DOMAIN_PERMISSION_REQUIRED: Open extension settings and authorize preset domains'
|
|
116
|
+
: typeof result?.error === 'string' ? result.error : undefined;
|
|
117
|
+
return { id: target.id, state: result?.ready === true && result?.browserRevision === browserRevision && result?.installationRevision === installationRevision ? 'active' : 'pending', settingsReady: result?.settingsReady === true, ...(error ? { error } : {}) };
|
|
109
118
|
}
|
|
110
119
|
catch {
|
|
111
120
|
return { id: target.id, state: 'pending', settingsReady: false, error: 'BROWSER_UNAVAILABLE' };
|
|
@@ -150,7 +159,8 @@ export class PresetInstaller {
|
|
|
150
159
|
result.settings = mergePresetDefaults(preset.defaults, receipt.overrides);
|
|
151
160
|
result.nodeRevision = receipt.nodeRevision;
|
|
152
161
|
result.browserRevision = receipt.browserRevision;
|
|
153
|
-
|
|
162
|
+
const browserSyncRequired = receipt.browserSyncRequired === true && !this.options.connectedExtensionsOnly;
|
|
163
|
+
result.browserSyncRequired = browserSyncRequired;
|
|
154
164
|
result.localComponentCount = Object.values(preset.packages).filter(entry => entry.enabled !== false && entry.targets?.includes('node')).length
|
|
155
165
|
+ [...Object.values(preset.skills), ...Object.values(preset.providers)].filter(entry => entry.enabled !== false).length;
|
|
156
166
|
result.hosts = await this.hostStatus(false, receipt.nodeRevision, receipt.revision);
|
|
@@ -163,7 +173,7 @@ export class PresetInstaller {
|
|
|
163
173
|
continue;
|
|
164
174
|
for (const runtime of entry.targets ?? []) {
|
|
165
175
|
if (runtime === 'browser') {
|
|
166
|
-
if (!browserReady)
|
|
176
|
+
if (browserSyncRequired && !browserReady)
|
|
167
177
|
result.pending.push({ kind: 'packages', id, runtime, reason: 'BROWSER_SYNC_REQUIRED' });
|
|
168
178
|
}
|
|
169
179
|
else if (!receipt.managedModules.includes(id) && Object.keys(current.prepared.bundles.find(bundle => bundle.manifest.id === id)?.manifest.contributions?.nodeRuntimes ?? {}).length)
|
|
@@ -189,9 +199,9 @@ export class PresetInstaller {
|
|
|
189
199
|
}
|
|
190
200
|
if (receipt.nodeSyncRequired && !nodeReady && !result.pending.some(entry => entry.reason === 'HOST_SYNC_REQUIRED'))
|
|
191
201
|
result.pending.push({ kind: 'preset', id: preset.id, runtime: 'node', reason: 'HOST_SYNC_REQUIRED' });
|
|
192
|
-
if (
|
|
202
|
+
if (browserSyncRequired && !browserReady && !result.pending.some(entry => entry.reason === 'BROWSER_SYNC_REQUIRED'))
|
|
193
203
|
result.pending.push({ kind: 'preset', id: preset.id, runtime: 'browser', reason: 'BROWSER_SYNC_REQUIRED' });
|
|
194
|
-
if (
|
|
204
|
+
if (browserSyncRequired && browserReady && Object.keys(result.settings ?? {}).length && !result.browsers.every(host => host.settingsReady))
|
|
195
205
|
result.pending.push({ kind: 'preset', id: preset.id, runtime: 'browser', reason: 'SETTINGS_APPLY_REQUIRED' });
|
|
196
206
|
result.ready = result.pending.length === 0;
|
|
197
207
|
return result;
|
|
@@ -207,18 +217,18 @@ export class PresetInstaller {
|
|
|
207
217
|
await this.hostStatus(true, previous.activation?.revision ?? '', previous.activation?.installationRevision ?? '');
|
|
208
218
|
throw new Error(`Node activation rejected; previous selection restored: ${rejection.error}`);
|
|
209
219
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
throw new Error(`Browser activation rejected; installed selection retained for retry: ${rejected.error}`);
|
|
215
|
-
}
|
|
220
|
+
const browsers = receipt.browserSyncRequired ? await this.browserStatus(true, receipt.browserRevision, receipt.revision) : [];
|
|
221
|
+
const rejected = browsers.find(host => host.state === 'rejected');
|
|
222
|
+
if (rejected && !this.options.connectedExtensionsOnly)
|
|
223
|
+
throw new Error(`Browser activation rejected; installed selection retained for retry: ${rejected.error}`);
|
|
216
224
|
const installed = await this.store.current({ verifyResources: false });
|
|
217
225
|
if (installed.receipt?.revision !== receipt.revision || !installed.preset)
|
|
218
226
|
throw new BundleError('BUNDLE_CONFLICT', 'The selected preset changed before component installation');
|
|
219
227
|
const hasExternal = [...Object.values(installed.preset.skills), ...Object.values(installed.preset.providers)].some(entry => entry.enabled !== false);
|
|
220
228
|
const components = hasExternal || installed.activation?.bindings['plugins.catalog'] ? await this.componentStatus(true, receipt, replaceSkills) : {};
|
|
221
229
|
const status = await this.status();
|
|
230
|
+
if (rejected)
|
|
231
|
+
status.warnings.push(`Browser preset activation rejected by ${rejected.id}: ${rejected.error}`);
|
|
222
232
|
if (components.error)
|
|
223
233
|
status.warnings.push(`Component installation failed; completed assets retained for retry: ${components.error}`);
|
|
224
234
|
if (status.revision !== receipt.revision)
|
|
@@ -305,7 +315,7 @@ export class PresetInstaller {
|
|
|
305
315
|
export async function runPresetCommand(command, source, options = {}) {
|
|
306
316
|
if (command !== 'repair' && options.replaceSkills !== undefined)
|
|
307
317
|
throw new Error('Skill replacements require preset repair');
|
|
308
|
-
const installer = new PresetInstaller();
|
|
318
|
+
const installer = new PresetInstaller({ connectedExtensionsOnly: true });
|
|
309
319
|
if (command === 'repair')
|
|
310
320
|
return installer.repair(options);
|
|
311
321
|
return command === 'status' ? installer.status() : command === 'update' ? installer.update() : installer.apply(source);
|
package/dist/presetStore.d.ts
CHANGED
|
@@ -1,46 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export interface PresetInstallReceipt {
|
|
4
|
-
schemaVersion: 1;
|
|
5
|
-
snapshot: PresetSnapshot;
|
|
6
|
-
bundles: Array<Omit<PreparedBundle, 'resources'>>;
|
|
7
|
-
browserRevision: string;
|
|
8
|
-
nodeRevision: string;
|
|
9
|
-
managedModules: string[];
|
|
10
|
-
nodeSyncRequired: boolean;
|
|
11
|
-
browserSyncRequired?: boolean;
|
|
12
|
-
overrides: JsonObject;
|
|
13
|
-
revision: string;
|
|
14
|
-
}
|
|
15
|
-
interface SelectionState {
|
|
16
|
-
rawHash: string | null;
|
|
17
|
-
activation?: CapabilityActivation;
|
|
18
|
-
receipt?: PresetInstallReceipt;
|
|
19
|
-
prepared?: PreparedPreset;
|
|
20
|
-
preset?: ResolvedPreset;
|
|
21
|
-
}
|
|
22
|
-
/** One pointer binds the desired preset, user overrides and exact Node selection. */
|
|
23
|
-
export declare class PresetInstallationStore {
|
|
24
|
-
readonly directory: string;
|
|
25
|
-
readonly mearlApiVersion: string;
|
|
26
|
-
private readonly pointer;
|
|
27
|
-
constructor(options?: {
|
|
28
|
-
directory?: string;
|
|
29
|
-
mearlApiVersion?: string;
|
|
30
|
-
});
|
|
31
|
-
private assertCurrent;
|
|
32
|
-
current(options?: {
|
|
33
|
-
verifyResources?: boolean;
|
|
34
|
-
}): Promise<SelectionState>;
|
|
35
|
-
apply(input: Pick<PreparedPreset, 'snapshot' | 'bundles'>, options: {
|
|
36
|
-
expectedRevision: string | null;
|
|
37
|
-
overrides?: JsonObject;
|
|
38
|
-
signal?: AbortSignal;
|
|
39
|
-
confirm?: () => Promise<void>;
|
|
40
|
-
}): Promise<PresetInstallReceipt>;
|
|
41
|
-
rollback(expectedRevision: string): Promise<void>;
|
|
42
|
-
collectGarbage(): Promise<void>;
|
|
43
|
-
private collectLocked;
|
|
44
|
-
}
|
|
45
|
-
export declare function presetSelectionRevision(state: SelectionState): string | null;
|
|
46
|
-
export {};
|
|
1
|
+
export { PresetInstallationStore, presetSelectionRevision } from '@mearl/daemon-core/installation';
|
|
2
|
+
export type { PresetInstallReceipt } from '@mearl/daemon-core/installation';
|