claude-flow 3.50.0 → 3.51.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/.claude/proven-config.json +1 -1
- package/.claude/settings.json +11 -2
- package/.claude-plugin/marketplace.json +5 -0
- package/package.json +1 -1
- package/v3/@claude-flow/cli/README.md +0 -2
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/catalog.d.ts +18 -0
- package/v3/@claude-flow/cli/dist/src/commands/catalog.js +89 -0
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +8 -3
- package/v3/@claude-flow/cli/dist/src/commands/index.js +3 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +50 -11
- package/v3/@claude-flow/cli/dist/src/commands/mission.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/commands/mission.js +116 -0
- package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/mods.js +67 -23
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +3 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/mission-tools.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/mission-tools.js +75 -0
- package/v3/@claude-flow/cli/dist/src/missions/fold.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/missions/fold.js +238 -0
- package/v3/@claude-flow/cli/dist/src/missions/index.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/missions/index.js +10 -0
- package/v3/@claude-flow/cli/dist/src/missions/observation.d.ts +70 -0
- package/v3/@claude-flow/cli/dist/src/missions/observation.js +77 -0
- package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +735 -0
- package/v3/@claude-flow/cli/dist/src/missions/schemas.js +150 -0
- package/v3/@claude-flow/cli/dist/src/missions/service.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/missions/service.js +278 -0
- package/v3/@claude-flow/cli/dist/src/missions/store.d.ts +68 -0
- package/v3/@claude-flow/cli/dist/src/missions/store.js +209 -0
- package/v3/@claude-flow/cli/dist/src/missions/transitions.d.ts +81 -0
- package/v3/@claude-flow/cli/dist/src/missions/transitions.js +178 -0
- package/v3/@claude-flow/cli/dist/src/mods/apply.d.ts +50 -0
- package/v3/@claude-flow/cli/dist/src/mods/apply.js +186 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.d.ts +45 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.js +234 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.d.ts +66 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.js +119 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.js +166 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.js +9 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.d.ts +63 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.js +230 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +836 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.js +137 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.d.ts +60 -22
- package/v3/@claude-flow/cli/dist/src/mods/install.js +91 -41
- package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.d.ts +81 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.js +120 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.d.ts +63 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.js +182 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.js +28 -3
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +5 -1
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-406 §6 — the canonical command registry contract.
|
|
3
|
+
*
|
|
4
|
+
* `CommandDefinition` mirrors the ADR's proposed TypeScript contract field for
|
|
5
|
+
* field. Every object is `.strict()` (unknown fields are rejected), every
|
|
6
|
+
* string and array is capped, and `argumentsSchema` is capped by its
|
|
7
|
+
* serialized size. A definition carries no executable path, shell template,
|
|
8
|
+
* network grant or approval override: `bindingId` is only a key into a
|
|
9
|
+
* reviewed allowlist owned by runtime code.
|
|
10
|
+
*
|
|
11
|
+
* Inventory facts (where a command was found, its digest, how the engine
|
|
12
|
+
* spells it) live beside the definition in a catalog entry, never inside it,
|
|
13
|
+
* so the §6 shape stays exact.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
export const COMMAND_CATALOG_CONTRACT = 'ruflo.command-catalog/1';
|
|
17
|
+
export const LIMITS = Object.freeze({
|
|
18
|
+
id: 160,
|
|
19
|
+
name: 128,
|
|
20
|
+
names: 16,
|
|
21
|
+
path: 512,
|
|
22
|
+
ownerPlugin: 96,
|
|
23
|
+
capabilities: 64,
|
|
24
|
+
capability: 128,
|
|
25
|
+
argumentsSchemaBytes: 4_096,
|
|
26
|
+
entries: 4_096,
|
|
27
|
+
note: 512,
|
|
28
|
+
});
|
|
29
|
+
const SHA256 = /^[a-f0-9]{64}$/;
|
|
30
|
+
const SAFE_ID = /^[a-z0-9][a-z0-9._:/-]*$/i;
|
|
31
|
+
// A command spelling as the engine accepts it: letters, digits, `-`, `_`,
|
|
32
|
+
// `.` and the `:` namespace separator. No whitespace, no shell metacharacters.
|
|
33
|
+
const COMMAND_NAME = /^[a-z0-9][a-z0-9._:-]*$/i;
|
|
34
|
+
// Repository-relative, forward slashes, no `..` segment, no leading slash.
|
|
35
|
+
const REL_PATH = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[^\0\\]+$/;
|
|
36
|
+
export const commandKindSchema = z.enum(['prompt', 'query', 'mutation', 'workflow', 'help']);
|
|
37
|
+
export const modDispositionSchema = z.enum(['view', 'dispatch', 'delegate', 'legacy']);
|
|
38
|
+
const nameSchema = z.string().min(1).max(LIMITS.name).regex(COMMAND_NAME);
|
|
39
|
+
const relPathSchema = z.string().min(1).max(LIMITS.path).regex(REL_PATH);
|
|
40
|
+
const argumentsSchemaSchema = z
|
|
41
|
+
.record(z.unknown())
|
|
42
|
+
.refine((value) => JSON.stringify(value).length <= LIMITS.argumentsSchemaBytes, {
|
|
43
|
+
message: `argumentsSchema exceeds ${LIMITS.argumentsSchemaBytes} serialized bytes`,
|
|
44
|
+
});
|
|
45
|
+
export const commandDefinitionSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
schemaVersion: z.literal(1),
|
|
48
|
+
id: z.string().min(1).max(LIMITS.id).regex(SAFE_ID),
|
|
49
|
+
revision: z.number().int().min(1).max(1_000_000),
|
|
50
|
+
ownerPlugin: z.string().min(1).max(LIMITS.ownerPlugin).regex(SAFE_ID),
|
|
51
|
+
legacyNames: z.array(nameSchema).max(LIMITS.names),
|
|
52
|
+
modNames: z.array(nameSchema).max(LIMITS.names),
|
|
53
|
+
kind: commandKindSchema,
|
|
54
|
+
argumentsSchema: argumentsSchemaSchema,
|
|
55
|
+
resultSchemaId: z.string().min(1).max(LIMITS.id).regex(SAFE_ID).optional(),
|
|
56
|
+
prompt: z
|
|
57
|
+
.object({
|
|
58
|
+
path: relPathSchema,
|
|
59
|
+
sha256: z.string().regex(SHA256),
|
|
60
|
+
substitution: z.enum(['$ARGUMENTS', 'positional', 'none']),
|
|
61
|
+
})
|
|
62
|
+
.strict()
|
|
63
|
+
.optional(),
|
|
64
|
+
bindingId: z.string().min(1).max(LIMITS.id).regex(SAFE_ID).optional(),
|
|
65
|
+
requiredCapabilities: z.array(z.string().min(1).max(LIMITS.capability)).max(LIMITS.capabilities),
|
|
66
|
+
requiredHostFeatures: z.array(z.string().min(1).max(LIMITS.capability)).max(LIMITS.capabilities),
|
|
67
|
+
disposition: modDispositionSchema,
|
|
68
|
+
viewId: z.string().min(1).max(LIMITS.id).regex(SAFE_ID).optional(),
|
|
69
|
+
modelVisibility: z.enum(['same-as-legacy', 'explicit-summary']),
|
|
70
|
+
source: z
|
|
71
|
+
.object({
|
|
72
|
+
repository: z.string().min(1).max(LIMITS.id),
|
|
73
|
+
commit: z.string().regex(/^(?:[a-f0-9]{40}|uncommitted)$/),
|
|
74
|
+
path: relPathSchema,
|
|
75
|
+
})
|
|
76
|
+
.strict(),
|
|
77
|
+
})
|
|
78
|
+
.strict()
|
|
79
|
+
.superRefine((def, ctx) => {
|
|
80
|
+
if (def.legacyNames.length + def.modNames.length === 0) {
|
|
81
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'a command needs at least one name' });
|
|
82
|
+
}
|
|
83
|
+
if (def.kind === 'prompt' && !def.prompt) {
|
|
84
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'a prompt command must record its prompt provenance' });
|
|
85
|
+
}
|
|
86
|
+
// §7: prompt dispatch is not verified on any engine yet, so a prompt
|
|
87
|
+
// workflow can be delegated or kept legacy but never claim `dispatch`.
|
|
88
|
+
if (def.kind === 'prompt' && def.disposition === 'dispatch') {
|
|
89
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'prompt workflows cannot use dispatch (ADR-406 §7)' });
|
|
90
|
+
}
|
|
91
|
+
if (def.disposition === 'dispatch' && !def.bindingId) {
|
|
92
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'dispatch requires a reviewed bindingId' });
|
|
93
|
+
}
|
|
94
|
+
if (def.disposition === 'view' && !def.viewId) {
|
|
95
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'view disposition requires a viewId' });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
/** Where an inventoried command came from. */
|
|
99
|
+
export const deliveryChannelSchema = z.enum([
|
|
100
|
+
'repo-project-commands', // `.claude/commands/**` at the repository root
|
|
101
|
+
'cli-init-template', // packaged `.claude/commands/**` that `init` copies
|
|
102
|
+
'marketplace-plugin', // `plugins/<p>/commands/*.md` listed in the marketplace
|
|
103
|
+
'source-plugin', // a plugin command folder not listed in any marketplace
|
|
104
|
+
'mod-registration', // `$.command.register` inside a function-hook mod
|
|
105
|
+
]);
|
|
106
|
+
export const inventorySourceSchema = z
|
|
107
|
+
.object({
|
|
108
|
+
channel: deliveryChannelSchema,
|
|
109
|
+
package: z.string().min(1).max(LIMITS.id),
|
|
110
|
+
sourcePath: relPathSchema,
|
|
111
|
+
digest: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
112
|
+
observedInvocation: z.string().min(1).max(LIMITS.name + 1),
|
|
113
|
+
// How the engine reaches it: a Markdown file load, a mod `command.register`,
|
|
114
|
+
// or mod middleware attached to a Markdown command (`command.run` match).
|
|
115
|
+
binding: z.enum(['markdown-loader', 'mod-register', 'mod-middleware-on-markdown']),
|
|
116
|
+
initCategory: z.string().max(LIMITS.ownerPlugin).optional(),
|
|
117
|
+
shippedIn: z.array(z.string().max(LIMITS.ownerPlugin)).max(8),
|
|
118
|
+
})
|
|
119
|
+
.strict();
|
|
120
|
+
export const catalogEntrySchema = z
|
|
121
|
+
.object({
|
|
122
|
+
definition: commandDefinitionSchema,
|
|
123
|
+
status: z.enum(['shipped', 'source-only', 'documentation']),
|
|
124
|
+
divergent: z.boolean(),
|
|
125
|
+
sources: z.array(inventorySourceSchema).min(1).max(8),
|
|
126
|
+
notes: z.array(z.string().max(LIMITS.note)).max(8),
|
|
127
|
+
})
|
|
128
|
+
.strict();
|
|
129
|
+
export const commandCatalogSchema = z
|
|
130
|
+
.object({
|
|
131
|
+
contractVersion: z.literal(COMMAND_CATALOG_CONTRACT),
|
|
132
|
+
sourceCommit: z.string().regex(/^(?:[a-f0-9]{40}|uncommitted)$/),
|
|
133
|
+
sourceDigest: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
134
|
+
entries: z.array(catalogEntrySchema).max(LIMITS.entries),
|
|
135
|
+
})
|
|
136
|
+
.strict();
|
|
137
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `ruflo mods install|uninstall` (ADR-404): enable the ruflo
|
|
3
|
-
*
|
|
2
|
+
* `ruflo mods install|uninstall` (ADR-404): enable the ruflo mods for one
|
|
3
|
+
* project, and take back exactly what was added.
|
|
4
4
|
*
|
|
5
|
-
* Writes three
|
|
6
|
-
*
|
|
7
|
-
* repository's): `enabledPlugins["ruflo-mods@ruflo"]`, the `ruflo` entry of
|
|
5
|
+
* Writes three kinds of key in a Claude Code settings file: one
|
|
6
|
+
* `enabledPlugins` entry per plugin in MOD_PLUGINS, the `ruflo` entry of
|
|
8
7
|
* `extraKnownMarketplaces` when absent, and `env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS`.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* `ruflo init` writes them to the committed `.claude/settings.json` (project
|
|
9
|
+
* scope); a standalone `ruflo mods install` defaults to
|
|
10
|
+
* `.claude/settings.local.json`. What was added is recorded per settings file
|
|
11
|
+
* in `.claude-flow/mods/install.json`, so uninstall removes those and nothing
|
|
12
|
+
* a person set themselves. Classic hooks are never touched: they stay the
|
|
13
|
+
* fallback (a mod takes an event over at runtime only).
|
|
12
14
|
*/
|
|
13
15
|
export declare const MOD_PLUGIN_ID = "ruflo-mods@ruflo";
|
|
14
16
|
export declare const MARKETPLACE_NAME = "ruflo";
|
|
@@ -18,18 +20,48 @@ export declare const MARKETPLACE_SOURCE: {
|
|
|
18
20
|
readonly repo: "ruvnet/ruflo";
|
|
19
21
|
};
|
|
20
22
|
};
|
|
23
|
+
/** An `extraKnownMarketplaces` entry: github ruvnet/ruflo, or a local ruflo checkout (dogfooding). */
|
|
24
|
+
export type MarketplaceEntry = {
|
|
25
|
+
source: {
|
|
26
|
+
source: 'github';
|
|
27
|
+
repo: string;
|
|
28
|
+
} | {
|
|
29
|
+
source: 'directory';
|
|
30
|
+
path: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export declare function directoryMarketplace(path: string): MarketplaceEntry;
|
|
21
34
|
export declare const ENABLE_ENV = "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS";
|
|
22
35
|
export declare const INSTALL_RECORD: string;
|
|
36
|
+
export interface ModPlugin {
|
|
37
|
+
id: string;
|
|
38
|
+
/**
|
|
39
|
+
* Missing from a ruflo marketplace clone means the clone is stale (a
|
|
40
|
+
* failure). A plugin not yet released on main is `required: false`: missing
|
|
41
|
+
* is "pending", never a failure. Flip it once the plugin lands.
|
|
42
|
+
*/
|
|
43
|
+
required: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** Every plugin `ruflo mods install` and `ruflo init` enable. The one list to edit. */
|
|
46
|
+
export declare const MOD_PLUGINS: readonly ModPlugin[];
|
|
47
|
+
export declare const MOD_PLUGIN_IDS: readonly string[];
|
|
23
48
|
export type Scope = 'local' | 'project';
|
|
49
|
+
export interface Added {
|
|
50
|
+
plugins: string[];
|
|
51
|
+
marketplace: boolean;
|
|
52
|
+
env: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Plugins ruflo itself installed with `claude plugin install` (absent
|
|
55
|
+
* before, enabled by ruflo); uninstall runs `claude plugin uninstall` for
|
|
56
|
+
* exactly these.
|
|
57
|
+
*/
|
|
58
|
+
claudeInstalled?: string[];
|
|
59
|
+
}
|
|
24
60
|
export interface InstallRecord {
|
|
25
|
-
version:
|
|
26
|
-
settingsFile: string;
|
|
61
|
+
version: 2;
|
|
27
62
|
installedAt: string;
|
|
28
|
-
added
|
|
29
|
-
|
|
30
|
-
marketplace: boolean;
|
|
31
|
-
env: boolean;
|
|
32
|
-
};
|
|
63
|
+
/** What install added, per settings file it wrote. */
|
|
64
|
+
files: Record<string, Added>;
|
|
33
65
|
}
|
|
34
66
|
type Settings = Record<string, unknown> & {
|
|
35
67
|
enabledPlugins?: Record<string, unknown>;
|
|
@@ -39,24 +71,30 @@ type Settings = Record<string, unknown> & {
|
|
|
39
71
|
export declare function settingsFileFor(projectRoot: string, scope: Scope): string;
|
|
40
72
|
/** Reads a settings file; absent is `{}`, anything unparseable throws (never overwritten). */
|
|
41
73
|
export declare function readSettingsFile(path: string): Settings;
|
|
42
|
-
/** The settings after install, and what install added (pure). */
|
|
43
|
-
export declare function withModEnabled(settings: Settings): {
|
|
74
|
+
/** The settings after install, and what install added (pure). Existing values are kept. */
|
|
75
|
+
export declare function withModEnabled(settings: Settings, plugins?: readonly string[], marketplace?: MarketplaceEntry): {
|
|
44
76
|
next: Settings;
|
|
45
|
-
added:
|
|
77
|
+
added: Added;
|
|
46
78
|
};
|
|
47
79
|
/** The settings after uninstall: only what the record says install added (pure). */
|
|
48
|
-
export declare function withModRemoved(settings: Settings, added:
|
|
80
|
+
export declare function withModRemoved(settings: Settings, added: Added): Settings;
|
|
49
81
|
export interface InstallResult {
|
|
50
82
|
settingsFile: string;
|
|
51
83
|
backup?: string;
|
|
52
|
-
added
|
|
84
|
+
/** What this run added (empty on a re-run). */
|
|
85
|
+
added: Added;
|
|
53
86
|
dryRun: boolean;
|
|
54
87
|
next: Settings;
|
|
55
88
|
}
|
|
56
|
-
export declare function installMod(projectRoot: string, scope: Scope, dryRun?: boolean): InstallResult;
|
|
89
|
+
export declare function installMod(projectRoot: string, scope: Scope, dryRun?: boolean, plugins?: readonly string[], marketplace?: MarketplaceEntry): InstallResult;
|
|
90
|
+
/** The install record, v1 (3.50.0: one file, ruflo-mods only) read as v2. */
|
|
57
91
|
export declare function readRecord(projectRoot: string): InstallRecord | null;
|
|
92
|
+
/** Note plugins ruflo installed with `claude plugin install` for one settings file. */
|
|
93
|
+
export declare function recordClaudeInstalled(projectRoot: string, settingsFile: string, ids: readonly string[]): void;
|
|
94
|
+
/** The scope a recorded settings file was written at. */
|
|
95
|
+
export declare function scopeOfSettingsFile(file: string): Scope;
|
|
58
96
|
export declare function uninstallMod(projectRoot: string, dryRun?: boolean): {
|
|
59
|
-
|
|
97
|
+
settingsFiles: string[];
|
|
60
98
|
removed: boolean;
|
|
61
99
|
dryRun: boolean;
|
|
62
100
|
};
|
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `ruflo mods install|uninstall` (ADR-404): enable the ruflo
|
|
3
|
-
*
|
|
2
|
+
* `ruflo mods install|uninstall` (ADR-404): enable the ruflo mods for one
|
|
3
|
+
* project, and take back exactly what was added.
|
|
4
4
|
*
|
|
5
|
-
* Writes three
|
|
6
|
-
*
|
|
7
|
-
* repository's): `enabledPlugins["ruflo-mods@ruflo"]`, the `ruflo` entry of
|
|
5
|
+
* Writes three kinds of key in a Claude Code settings file: one
|
|
6
|
+
* `enabledPlugins` entry per plugin in MOD_PLUGINS, the `ruflo` entry of
|
|
8
7
|
* `extraKnownMarketplaces` when absent, and `env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS`.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* `ruflo init` writes them to the committed `.claude/settings.json` (project
|
|
9
|
+
* scope); a standalone `ruflo mods install` defaults to
|
|
10
|
+
* `.claude/settings.local.json`. What was added is recorded per settings file
|
|
11
|
+
* in `.claude-flow/mods/install.json`, so uninstall removes those and nothing
|
|
12
|
+
* a person set themselves. Classic hooks are never touched: they stay the
|
|
13
|
+
* fallback (a mod takes an event over at runtime only).
|
|
12
14
|
*/
|
|
13
15
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
14
16
|
import { dirname, join, resolve, sep } from 'node:path';
|
|
15
17
|
export const MOD_PLUGIN_ID = 'ruflo-mods@ruflo';
|
|
16
18
|
export const MARKETPLACE_NAME = 'ruflo';
|
|
17
19
|
export const MARKETPLACE_SOURCE = { source: { source: 'github', repo: 'ruvnet/ruflo' } };
|
|
20
|
+
export function directoryMarketplace(path) {
|
|
21
|
+
return { source: { source: 'directory', path: resolve(path) } };
|
|
22
|
+
}
|
|
18
23
|
export const ENABLE_ENV = 'CLAUDE_CODE_ENABLE_FUNCTION_HOOKS';
|
|
19
24
|
export const INSTALL_RECORD = join('.claude-flow', 'mods', 'install.json');
|
|
25
|
+
/** Every plugin `ruflo mods install` and `ruflo init` enable. The one list to edit. */
|
|
26
|
+
export const MOD_PLUGINS = [
|
|
27
|
+
{ id: MOD_PLUGIN_ID, required: true },
|
|
28
|
+
{ id: 'ruflo-swarm@ruflo', required: true },
|
|
29
|
+
{ id: 'ruflo-console@ruflo', required: true },
|
|
30
|
+
];
|
|
31
|
+
export const MOD_PLUGIN_IDS = MOD_PLUGINS.map((p) => p.id);
|
|
20
32
|
export function settingsFileFor(projectRoot, scope) {
|
|
21
33
|
return join(resolve(projectRoot), '.claude', scope === 'local' ? 'settings.local.json' : 'settings.json');
|
|
22
34
|
}
|
|
@@ -38,22 +50,26 @@ function writeJson(path, value) {
|
|
|
38
50
|
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
39
51
|
renameSync(tmp, path);
|
|
40
52
|
}
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
const nothingAdded = (a) => a.plugins.length === 0 && !a.marketplace && !a.env;
|
|
54
|
+
/** The settings after install, and what install added (pure). Existing values are kept. */
|
|
55
|
+
export function withModEnabled(settings, plugins = MOD_PLUGIN_IDS, marketplace = MARKETPLACE_SOURCE) {
|
|
43
56
|
const next = { ...settings };
|
|
44
|
-
const
|
|
57
|
+
const enabled = isRecord(settings.enabledPlugins) ? { ...settings.enabledPlugins } : {};
|
|
45
58
|
const markets = isRecord(settings.extraKnownMarketplaces) ? { ...settings.extraKnownMarketplaces } : {};
|
|
46
59
|
const env = isRecord(settings.env) ? { ...settings.env } : {};
|
|
60
|
+
// A plugin a person set to false is their decision: never flipped.
|
|
47
61
|
const added = {
|
|
48
|
-
|
|
62
|
+
plugins: plugins.filter((id) => !(id in enabled)),
|
|
49
63
|
marketplace: !(MARKETPLACE_NAME in markets),
|
|
50
|
-
env:
|
|
64
|
+
env: !(ENABLE_ENV in env),
|
|
51
65
|
};
|
|
52
|
-
|
|
66
|
+
for (const id of added.plugins)
|
|
67
|
+
enabled[id] = true;
|
|
53
68
|
if (added.marketplace)
|
|
54
|
-
markets[MARKETPLACE_NAME] =
|
|
55
|
-
env
|
|
56
|
-
|
|
69
|
+
markets[MARKETPLACE_NAME] = marketplace;
|
|
70
|
+
if (added.env)
|
|
71
|
+
env[ENABLE_ENV] = '1';
|
|
72
|
+
next.enabledPlugins = enabled;
|
|
57
73
|
next.extraKnownMarketplaces = markets;
|
|
58
74
|
next.env = env;
|
|
59
75
|
return { next, added };
|
|
@@ -72,19 +88,19 @@ export function withModRemoved(settings, added) {
|
|
|
72
88
|
else
|
|
73
89
|
next[key] = copy;
|
|
74
90
|
};
|
|
75
|
-
|
|
76
|
-
drop('enabledPlugins',
|
|
91
|
+
for (const id of added.plugins)
|
|
92
|
+
drop('enabledPlugins', id);
|
|
77
93
|
if (added.marketplace)
|
|
78
94
|
drop('extraKnownMarketplaces', MARKETPLACE_NAME);
|
|
79
95
|
if (added.env)
|
|
80
96
|
drop('env', ENABLE_ENV);
|
|
81
97
|
return next;
|
|
82
98
|
}
|
|
83
|
-
export function installMod(projectRoot, scope, dryRun = false) {
|
|
99
|
+
export function installMod(projectRoot, scope, dryRun = false, plugins = MOD_PLUGIN_IDS, marketplace = MARKETPLACE_SOURCE) {
|
|
84
100
|
const settingsFile = settingsFileFor(projectRoot, scope);
|
|
85
101
|
const current = readSettingsFile(settingsFile);
|
|
86
|
-
const { next, added } = withModEnabled(current);
|
|
87
|
-
if (dryRun)
|
|
102
|
+
const { next, added } = withModEnabled(current, plugins, marketplace);
|
|
103
|
+
if (dryRun || nothingAdded(added))
|
|
88
104
|
return { settingsFile, added, dryRun, next };
|
|
89
105
|
let backup;
|
|
90
106
|
if (existsSync(settingsFile)) {
|
|
@@ -92,44 +108,78 @@ export function installMod(projectRoot, scope, dryRun = false) {
|
|
|
92
108
|
copyFileSync(settingsFile, backup);
|
|
93
109
|
}
|
|
94
110
|
writeJson(settingsFile, next);
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
: added
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
// What ruflo added once is still ruflo's to remove: merge into the record.
|
|
112
|
+
const record = readRecord(projectRoot) ?? { version: 2, installedAt: '', files: {} };
|
|
113
|
+
const before = record.files[settingsFile] ?? { plugins: [], marketplace: false, env: false };
|
|
114
|
+
record.files[settingsFile] = {
|
|
115
|
+
plugins: [...new Set([...before.plugins, ...added.plugins])],
|
|
116
|
+
marketplace: before.marketplace || added.marketplace,
|
|
117
|
+
env: before.env || added.env,
|
|
118
|
+
claudeInstalled: before.claudeInstalled ?? [],
|
|
119
|
+
};
|
|
120
|
+
record.installedAt = new Date().toISOString();
|
|
121
|
+
writeJson(join(resolve(projectRoot), INSTALL_RECORD), record);
|
|
122
|
+
return { settingsFile, backup, added, dryRun, next };
|
|
105
123
|
}
|
|
124
|
+
/** The install record, v1 (3.50.0: one file, ruflo-mods only) read as v2. */
|
|
106
125
|
export function readRecord(projectRoot) {
|
|
107
126
|
const path = join(resolve(projectRoot), INSTALL_RECORD);
|
|
108
127
|
if (!existsSync(path))
|
|
109
128
|
return null;
|
|
110
129
|
try {
|
|
111
130
|
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
112
|
-
|
|
131
|
+
if (parsed.version === 2 && isRecord(parsed.files)) {
|
|
132
|
+
const files = {};
|
|
133
|
+
for (const [file, a] of Object.entries(parsed.files)) {
|
|
134
|
+
if (!isRecord(a))
|
|
135
|
+
continue;
|
|
136
|
+
const strings = (v) => (Array.isArray(v) ? v.filter((p) => typeof p === 'string') : []);
|
|
137
|
+
files[file] = { plugins: strings(a.plugins), marketplace: a.marketplace === true, env: a.env === true, claudeInstalled: strings(a.claudeInstalled) };
|
|
138
|
+
}
|
|
139
|
+
return { version: 2, installedAt: String(parsed.installedAt ?? ''), files };
|
|
140
|
+
}
|
|
141
|
+
if (parsed.version === 1 && typeof parsed.settingsFile === 'string' && isRecord(parsed.added)) {
|
|
142
|
+
const a = parsed.added;
|
|
143
|
+
return { version: 2, installedAt: String(parsed.installedAt ?? ''), files: { [parsed.settingsFile]: { plugins: a.plugin === true ? [MOD_PLUGIN_ID] : [], marketplace: a.marketplace === true, env: a.env === true } } };
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
113
146
|
}
|
|
114
147
|
catch {
|
|
115
148
|
return null;
|
|
116
149
|
}
|
|
117
150
|
}
|
|
151
|
+
/** Note plugins ruflo installed with `claude plugin install` for one settings file. */
|
|
152
|
+
export function recordClaudeInstalled(projectRoot, settingsFile, ids) {
|
|
153
|
+
const record = readRecord(projectRoot);
|
|
154
|
+
const entry = record?.files[settingsFile];
|
|
155
|
+
if (!record || !entry || ids.length === 0)
|
|
156
|
+
return;
|
|
157
|
+
entry.claudeInstalled = [...new Set([...(entry.claudeInstalled ?? []), ...ids])];
|
|
158
|
+
writeJson(join(resolve(projectRoot), INSTALL_RECORD), record);
|
|
159
|
+
}
|
|
160
|
+
/** The scope a recorded settings file was written at. */
|
|
161
|
+
export function scopeOfSettingsFile(file) {
|
|
162
|
+
return file.endsWith('settings.local.json') ? 'local' : 'project';
|
|
163
|
+
}
|
|
118
164
|
export function uninstallMod(projectRoot, dryRun = false) {
|
|
119
165
|
const record = readRecord(projectRoot);
|
|
120
166
|
if (!record)
|
|
121
|
-
return { removed: false, dryRun };
|
|
122
|
-
// The record names the
|
|
167
|
+
return { settingsFiles: [], removed: false, dryRun };
|
|
168
|
+
// The record names the files; never follow one outside this project.
|
|
123
169
|
const root = resolve(projectRoot);
|
|
124
|
-
|
|
125
|
-
|
|
170
|
+
const settingsFiles = Object.keys(record.files);
|
|
171
|
+
for (const file of settingsFiles) {
|
|
172
|
+
if (!resolve(file).startsWith(join(root, '.claude') + sep)) {
|
|
173
|
+
throw new Error(`install record names a settings file outside ${root}/.claude: ${file}`);
|
|
174
|
+
}
|
|
126
175
|
}
|
|
127
176
|
if (dryRun)
|
|
128
|
-
return {
|
|
129
|
-
|
|
130
|
-
|
|
177
|
+
return { settingsFiles, removed: true, dryRun };
|
|
178
|
+
for (const file of settingsFiles) {
|
|
179
|
+
if (existsSync(file))
|
|
180
|
+
writeJson(file, withModRemoved(readSettingsFile(file), record.files[file]));
|
|
131
181
|
}
|
|
132
182
|
unlinkSync(join(root, INSTALL_RECORD));
|
|
133
|
-
return {
|
|
183
|
+
return { settingsFiles, removed: true, dryRun };
|
|
134
184
|
}
|
|
135
185
|
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* After settings are written, make the mod plugins loadable the way a person
|
|
3
|
+
* would (ADR-404): refresh (or add) the `ruflo` marketplace clone, then
|
|
4
|
+
* `claude plugin install <id> --scope <scope>` for each plugin the clone
|
|
5
|
+
* carries. The refreshed clone is what makes a plugin load; the install adds
|
|
6
|
+
* a cached copy (and the `claude plugin list` entry) that survives a later
|
|
7
|
+
* stale clone.
|
|
8
|
+
*
|
|
9
|
+
* Fixed argv through execFile, never a shell; each step bounded. The `add`
|
|
10
|
+
* step passes `--scope` so the marketplace is declared in the same settings
|
|
11
|
+
* file ruflo already wrote, not in user settings. `~/.claude.json` is never
|
|
12
|
+
* touched by ruflo. No `-y`: a marketplace-declared command is never accepted
|
|
13
|
+
* on a person's behalf.
|
|
14
|
+
*/
|
|
15
|
+
import { type MarketplaceEntry, type ModPlugin, type Scope } from './install.js';
|
|
16
|
+
import { type ReadFs } from './plugin-resolve.js';
|
|
17
|
+
export declare const MARKETPLACE_TIMEOUT_MS = 150000;
|
|
18
|
+
export declare const INSTALL_TIMEOUT_MS = 60000;
|
|
19
|
+
export interface ExecResult {
|
|
20
|
+
code: number;
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
}
|
|
24
|
+
export type Exec = (file: string, args: readonly string[], opts: {
|
|
25
|
+
cwd: string;
|
|
26
|
+
timeout: number;
|
|
27
|
+
env: NodeJS.ProcessEnv;
|
|
28
|
+
}) => Promise<ExecResult>;
|
|
29
|
+
export declare const nodeExec: Exec;
|
|
30
|
+
/** The `claude` a shell would run, when it can be exec'd without a shell. */
|
|
31
|
+
export declare function findClaudeBinary(env: NodeJS.ProcessEnv, home: string): string | null;
|
|
32
|
+
export type StepState = 'ok' | 'failed' | 'pending';
|
|
33
|
+
export interface RepairStep {
|
|
34
|
+
argv: string[];
|
|
35
|
+
state: StepState;
|
|
36
|
+
output: string;
|
|
37
|
+
}
|
|
38
|
+
export interface RepairOptions {
|
|
39
|
+
projectRoot: string;
|
|
40
|
+
scope: Scope;
|
|
41
|
+
/** Claude Code's config directory, read after the marketplace step to see what the clone carries. */
|
|
42
|
+
configDir: string;
|
|
43
|
+
claude: string;
|
|
44
|
+
plugins?: readonly ModPlugin[];
|
|
45
|
+
/** The marketplace the settings declare (default github ruvnet/ruflo). */
|
|
46
|
+
marketplace?: MarketplaceEntry;
|
|
47
|
+
exec?: Exec;
|
|
48
|
+
env?: NodeJS.ProcessEnv;
|
|
49
|
+
fs?: ReadFs;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `update` when Claude Code already knows ruflo by the wanted source; else
|
|
53
|
+
* `add` it at the scope, which also switches a ruflo marketplace known by
|
|
54
|
+
* another source (Claude Code keeps one per config dir).
|
|
55
|
+
*/
|
|
56
|
+
export declare function marketplaceArgv(scope: Scope, known: boolean, wanted?: MarketplaceEntry): string[];
|
|
57
|
+
export declare function uninstallArgv(id: string, scope: Scope): string[];
|
|
58
|
+
export declare function installArgv(id: string, scope: Scope): string[];
|
|
59
|
+
/**
|
|
60
|
+
* Marketplace first (a failure there stops everything: nothing below can
|
|
61
|
+
* work); then one install per plugin, every failure collected. A plugin the
|
|
62
|
+
* refreshed clone does not carry is pending when not required, a failure when
|
|
63
|
+
* required.
|
|
64
|
+
*/
|
|
65
|
+
export declare function repairPluginInstall(opts: RepairOptions): Promise<{
|
|
66
|
+
ok: boolean;
|
|
67
|
+
steps: RepairStep[];
|
|
68
|
+
}>;
|
|
69
|
+
/** `claude plugin uninstall` for each id, every failure collected. */
|
|
70
|
+
export declare function uninstallPlugins(opts: {
|
|
71
|
+
projectRoot: string;
|
|
72
|
+
scope: Scope;
|
|
73
|
+
ids: readonly string[];
|
|
74
|
+
claude: string;
|
|
75
|
+
exec?: Exec;
|
|
76
|
+
env?: NodeJS.ProcessEnv;
|
|
77
|
+
}): Promise<{
|
|
78
|
+
ok: boolean;
|
|
79
|
+
steps: RepairStep[];
|
|
80
|
+
}>;
|
|
81
|
+
//# sourceMappingURL=plugin-repair.d.ts.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* After settings are written, make the mod plugins loadable the way a person
|
|
3
|
+
* would (ADR-404): refresh (or add) the `ruflo` marketplace clone, then
|
|
4
|
+
* `claude plugin install <id> --scope <scope>` for each plugin the clone
|
|
5
|
+
* carries. The refreshed clone is what makes a plugin load; the install adds
|
|
6
|
+
* a cached copy (and the `claude plugin list` entry) that survives a later
|
|
7
|
+
* stale clone.
|
|
8
|
+
*
|
|
9
|
+
* Fixed argv through execFile, never a shell; each step bounded. The `add`
|
|
10
|
+
* step passes `--scope` so the marketplace is declared in the same settings
|
|
11
|
+
* file ruflo already wrote, not in user settings. `~/.claude.json` is never
|
|
12
|
+
* touched by ruflo. No `-y`: a marketplace-declared command is never accepted
|
|
13
|
+
* on a person's behalf.
|
|
14
|
+
*/
|
|
15
|
+
import { execFile } from 'node:child_process';
|
|
16
|
+
import { findClaudeInstalls } from './claude-installs.js';
|
|
17
|
+
import { MARKETPLACE_NAME, MARKETPLACE_SOURCE, MOD_PLUGINS } from './install.js';
|
|
18
|
+
import { cloneHas, marketplaceState, nodeReadFs, sameSource } from './plugin-resolve.js';
|
|
19
|
+
export const MARKETPLACE_TIMEOUT_MS = 150_000; // Claude Code's own clone timeout is 120s
|
|
20
|
+
export const INSTALL_TIMEOUT_MS = 60_000;
|
|
21
|
+
export const nodeExec = (file, args, opts) => new Promise((done) => {
|
|
22
|
+
execFile(file, [...args], { ...opts, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, windowsHide: true }, (error, stdout, stderr) => {
|
|
23
|
+
// error.code is the exit status, or a string (ENOENT, ...) when it never ran; a timeout kills it.
|
|
24
|
+
const status = error?.code;
|
|
25
|
+
const code = !error ? 0 : typeof status === 'number' && status !== 0 ? status : 1;
|
|
26
|
+
done({ code, stdout: String(stdout), stderr: String(stderr) || (error ? error.message : '') });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
/** The `claude` a shell would run, when it can be exec'd without a shell. */
|
|
30
|
+
export function findClaudeBinary(env, home) {
|
|
31
|
+
const first = findClaudeInstalls(env, home, () => null)[0];
|
|
32
|
+
if (!first)
|
|
33
|
+
return null;
|
|
34
|
+
// A .cmd shim needs a shell to run; ruflo never uses one.
|
|
35
|
+
return process.platform === 'win32' && !first.path.toLowerCase().endsWith('.exe') ? null : first.path;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `update` when Claude Code already knows ruflo by the wanted source; else
|
|
39
|
+
* `add` it at the scope, which also switches a ruflo marketplace known by
|
|
40
|
+
* another source (Claude Code keeps one per config dir).
|
|
41
|
+
*/
|
|
42
|
+
export function marketplaceArgv(scope, known, wanted = MARKETPLACE_SOURCE) {
|
|
43
|
+
if (known)
|
|
44
|
+
return ['plugin', 'marketplace', 'update', MARKETPLACE_NAME];
|
|
45
|
+
const from = wanted.source.source === 'directory' ? wanted.source.path : wanted.source.repo;
|
|
46
|
+
return ['plugin', 'marketplace', 'add', from, '--scope', scope];
|
|
47
|
+
}
|
|
48
|
+
export function uninstallArgv(id, scope) {
|
|
49
|
+
return ['plugin', 'uninstall', id, '--scope', scope];
|
|
50
|
+
}
|
|
51
|
+
export function installArgv(id, scope) {
|
|
52
|
+
return ['plugin', 'install', id, '--scope', scope];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Marketplace first (a failure there stops everything: nothing below can
|
|
56
|
+
* work); then one install per plugin, every failure collected. A plugin the
|
|
57
|
+
* refreshed clone does not carry is pending when not required, a failure when
|
|
58
|
+
* required.
|
|
59
|
+
*/
|
|
60
|
+
export async function repairPluginInstall(opts) {
|
|
61
|
+
const exec = opts.exec ?? nodeExec;
|
|
62
|
+
const env = opts.env ?? process.env;
|
|
63
|
+
const fs = opts.fs ?? nodeReadFs;
|
|
64
|
+
const plugins = opts.plugins ?? MOD_PLUGINS;
|
|
65
|
+
const steps = [];
|
|
66
|
+
const run = async (argv, timeout) => {
|
|
67
|
+
let result;
|
|
68
|
+
try {
|
|
69
|
+
result = await exec(opts.claude, argv, { cwd: opts.projectRoot, timeout, env });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
result = { code: 1, stdout: '', stderr: error.message };
|
|
73
|
+
}
|
|
74
|
+
steps.push({ argv, state: result.code === 0 ? 'ok' : 'failed', output: `${result.stdout}${result.stderr}`.trim() });
|
|
75
|
+
return result.code === 0;
|
|
76
|
+
};
|
|
77
|
+
const wanted = opts.marketplace ?? MARKETPLACE_SOURCE;
|
|
78
|
+
const before = marketplaceState(opts.configDir, fs);
|
|
79
|
+
if (!(await run(marketplaceArgv(opts.scope, before.known && sameSource(before.source, wanted.source), wanted), MARKETPLACE_TIMEOUT_MS))) {
|
|
80
|
+
return { ok: false, steps };
|
|
81
|
+
}
|
|
82
|
+
const market = marketplaceState(opts.configDir, fs);
|
|
83
|
+
// A directory marketplace loads each plugin live from the working tree;
|
|
84
|
+
// an install would pin a cached snapshot of it instead.
|
|
85
|
+
if (wanted.source.source === 'directory') {
|
|
86
|
+
const missing = plugins.filter((p) => p.required && !cloneHas(market, p.id, fs));
|
|
87
|
+
for (const p of missing)
|
|
88
|
+
steps.push({ argv: installArgv(p.id, opts.scope), state: 'failed', output: `${p.id} is not in ${wanted.source.path}` });
|
|
89
|
+
return { ok: missing.length === 0, steps };
|
|
90
|
+
}
|
|
91
|
+
let ok = true;
|
|
92
|
+
for (const plugin of plugins) {
|
|
93
|
+
const argv = installArgv(plugin.id, opts.scope);
|
|
94
|
+
if (!cloneHas(market, plugin.id, fs)) {
|
|
95
|
+
steps.push({ argv, state: plugin.required ? 'failed' : 'pending', output: `${plugin.id} is not in the ruflo marketplace${plugin.required ? ' even after the update' : ' yet (pending)'}` });
|
|
96
|
+
ok &&= !plugin.required;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
ok = (await run(argv, INSTALL_TIMEOUT_MS)) && ok;
|
|
100
|
+
}
|
|
101
|
+
return { ok, steps };
|
|
102
|
+
}
|
|
103
|
+
/** `claude plugin uninstall` for each id, every failure collected. */
|
|
104
|
+
export async function uninstallPlugins(opts) {
|
|
105
|
+
const exec = opts.exec ?? nodeExec;
|
|
106
|
+
const steps = [];
|
|
107
|
+
for (const id of opts.ids) {
|
|
108
|
+
const argv = uninstallArgv(id, opts.scope);
|
|
109
|
+
let result;
|
|
110
|
+
try {
|
|
111
|
+
result = await exec(opts.claude, argv, { cwd: opts.projectRoot, timeout: INSTALL_TIMEOUT_MS, env: opts.env ?? process.env });
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
result = { code: 1, stdout: '', stderr: error.message };
|
|
115
|
+
}
|
|
116
|
+
steps.push({ argv, state: result.code === 0 ? 'ok' : 'failed', output: `${result.stdout}${result.stderr}`.trim() });
|
|
117
|
+
}
|
|
118
|
+
return { ok: steps.every((st) => st.state === 'ok'), steps };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=plugin-repair.js.map
|