@zhijiewang/openharness 2.5.0 → 2.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 +1 -1
- package/data/registry.json +262 -0
- package/data/skills/code-review.md +19 -0
- package/data/skills/commit.md +17 -0
- package/data/skills/debug.md +24 -0
- package/data/skills/diagnose.md +24 -0
- package/data/skills/plan.md +25 -0
- package/data/skills/simplify.md +24 -0
- package/data/skills/tdd.md +22 -0
- package/dist/agents/roles.d.ts +12 -2
- package/dist/agents/roles.js +65 -6
- package/dist/commands/ai.d.ts +6 -0
- package/dist/commands/ai.js +264 -0
- package/dist/commands/git.d.ts +6 -0
- package/dist/commands/git.js +167 -0
- package/dist/commands/index.d.ts +10 -31
- package/dist/commands/index.js +22 -1096
- package/dist/commands/info.d.ts +8 -0
- package/dist/commands/info.js +671 -0
- package/dist/commands/session.d.ts +6 -0
- package/dist/commands/session.js +214 -0
- package/dist/commands/settings.d.ts +6 -0
- package/dist/commands/settings.js +187 -0
- package/dist/commands/skills.d.ts +6 -0
- package/dist/commands/skills.js +162 -0
- package/dist/commands/types.d.ts +36 -0
- package/dist/commands/types.js +5 -0
- package/dist/components/App.js +7 -1
- package/dist/components/InitWizard.js +60 -62
- package/dist/harness/config.d.ts +11 -0
- package/dist/harness/hooks.d.ts +14 -0
- package/dist/harness/hooks.js +56 -10
- package/dist/harness/marketplace.d.ts +77 -2
- package/dist/harness/marketplace.js +260 -38
- package/dist/harness/memory.d.ts +34 -0
- package/dist/harness/memory.js +96 -0
- package/dist/harness/plugins.d.ts +13 -3
- package/dist/harness/plugins.js +98 -17
- package/dist/harness/session-db.d.ts +8 -1
- package/dist/harness/session-db.js +24 -3
- package/dist/harness/skill-registry.d.ts +26 -2
- package/dist/harness/skill-registry.js +42 -4
- package/dist/providers/anthropic.js +7 -8
- package/dist/providers/openai.js +3 -2
- package/dist/renderer/layout-sections.d.ts +56 -0
- package/dist/renderer/layout-sections.js +462 -0
- package/dist/renderer/layout.d.ts +4 -2
- package/dist/renderer/layout.js +25 -500
- package/dist/tools/AgentTool/index.d.ts +2 -2
- package/dist/tools/DiagnosticsTool/index.d.ts +1 -1
- package/dist/tools/GrepTool/index.d.ts +6 -6
- package/dist/tools/MemoryTool/index.d.ts +6 -6
- package/dist/tools/MonitorTool/index.js +5 -1
- package/dist/tools/TodoWriteTool/index.d.ts +37 -0
- package/dist/tools/TodoWriteTool/index.js +78 -0
- package/dist/tools.js +2 -0
- package/dist/types/permissions.js +104 -42
- package/dist/utils/bash-safety.d.ts +19 -0
- package/dist/utils/bash-safety.js +179 -1
- package/dist/utils/safe-env.d.ts +5 -1
- package/dist/utils/safe-env.js +19 -1
- package/package.json +3 -1
|
@@ -92,6 +92,65 @@ export default function InitWizard({ onDone }) {
|
|
|
92
92
|
const [selectedMcp, setSelectedMcp] = useState(new Set());
|
|
93
93
|
const [mcpIdx, setMcpIdx] = useState(0);
|
|
94
94
|
const provider = PROVIDERS[providerIdx];
|
|
95
|
+
// ── Connection test ──
|
|
96
|
+
const runTest = useCallback(async (prov, key, url) => {
|
|
97
|
+
setTestStatus("testing");
|
|
98
|
+
try {
|
|
99
|
+
const { createProviderInstance } = await import("../providers/index.js");
|
|
100
|
+
const p = createProviderInstance(prov.key, {
|
|
101
|
+
name: prov.key,
|
|
102
|
+
apiKey: key || process.env[`${prov.key.toUpperCase()}_API_KEY`],
|
|
103
|
+
baseUrl: url || prov.defaultBaseUrl,
|
|
104
|
+
defaultModel: prov.defaultModel,
|
|
105
|
+
});
|
|
106
|
+
const fetched = "fetchModels" in p && typeof p.fetchModels === "function"
|
|
107
|
+
? await p.fetchModels()
|
|
108
|
+
: p.listModels();
|
|
109
|
+
const modelNames = fetched.map((m) => m.id);
|
|
110
|
+
setAvailableModels(modelNames.length > 0 ? modelNames : [prov.defaultModel]);
|
|
111
|
+
setTestStatus("ok");
|
|
112
|
+
setTimeout(() => setStep("model"), 600);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
setTestStatus("fail");
|
|
116
|
+
setTestError(err instanceof Error ? err.message : String(err));
|
|
117
|
+
setAvailableModels([prov.defaultModel]);
|
|
118
|
+
setTimeout(() => setStep("model"), 800);
|
|
119
|
+
}
|
|
120
|
+
}, []);
|
|
121
|
+
// ── Write final config ──
|
|
122
|
+
const writeFinal = useCallback(() => {
|
|
123
|
+
const selectedModel = availableModels.length > 0 ? (availableModels[modelIdx] ?? model) : model;
|
|
124
|
+
// Build MCP server configs from selected registry entries
|
|
125
|
+
let mcpServers;
|
|
126
|
+
if (selectedMcp.size > 0) {
|
|
127
|
+
try {
|
|
128
|
+
const { MCP_REGISTRY } = require("../mcp/registry.js");
|
|
129
|
+
mcpServers = [...selectedMcp]
|
|
130
|
+
.map((name) => MCP_REGISTRY.find((e) => e.name === name))
|
|
131
|
+
.filter(Boolean)
|
|
132
|
+
.map((e) => ({
|
|
133
|
+
name: e.name,
|
|
134
|
+
command: "npx",
|
|
135
|
+
args: ["-y", e.package, ...(e.args ?? [])],
|
|
136
|
+
...(e.envVars?.length ? { env: Object.fromEntries(e.envVars.map((v) => [v, `YOUR_${v}`])) } : {}),
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
/* ignore */
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
writeOhConfig({
|
|
144
|
+
provider: provider.key,
|
|
145
|
+
model: selectedModel || provider.defaultModel,
|
|
146
|
+
permissionMode: PERMISSION_MODES[permIdx].key,
|
|
147
|
+
...(apiKey ? { apiKey } : {}),
|
|
148
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
149
|
+
...(mcpServers?.length ? { mcpServers } : {}),
|
|
150
|
+
});
|
|
151
|
+
setStep("done");
|
|
152
|
+
setTimeout(() => onDone?.(), 1500);
|
|
153
|
+
}, [provider, model, availableModels, modelIdx, permIdx, apiKey, baseUrl, selectedMcp, onDone]);
|
|
95
154
|
// ── Keyboard navigation ──
|
|
96
155
|
useInput(useCallback((input, key) => {
|
|
97
156
|
if (step === "provider") {
|
|
@@ -175,68 +234,7 @@ export default function InitWizard({ onDone }) {
|
|
|
175
234
|
if (input === "n" || input === "N")
|
|
176
235
|
writeFinal();
|
|
177
236
|
}
|
|
178
|
-
},
|
|
179
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: runTest/writeFinal declared below, providerIdx intentionally omitted
|
|
180
|
-
[step, providerIdx, provider, modelIdx, availableModels, model, suggestedMcp, mcpIdx]));
|
|
181
|
-
// ── Connection test ──
|
|
182
|
-
const runTest = async (prov, key, url) => {
|
|
183
|
-
setTestStatus("testing");
|
|
184
|
-
try {
|
|
185
|
-
const { createProviderInstance } = await import("../providers/index.js");
|
|
186
|
-
const p = createProviderInstance(prov.key, {
|
|
187
|
-
name: prov.key,
|
|
188
|
-
apiKey: key || process.env[`${prov.key.toUpperCase()}_API_KEY`],
|
|
189
|
-
baseUrl: url || prov.defaultBaseUrl,
|
|
190
|
-
defaultModel: prov.defaultModel,
|
|
191
|
-
});
|
|
192
|
-
const fetched = "fetchModels" in p && typeof p.fetchModels === "function"
|
|
193
|
-
? await p.fetchModels()
|
|
194
|
-
: p.listModels();
|
|
195
|
-
const modelNames = fetched.map((m) => m.id);
|
|
196
|
-
setAvailableModels(modelNames.length > 0 ? modelNames : [prov.defaultModel]);
|
|
197
|
-
setTestStatus("ok");
|
|
198
|
-
setTimeout(() => setStep("model"), 600);
|
|
199
|
-
}
|
|
200
|
-
catch (err) {
|
|
201
|
-
setTestStatus("fail");
|
|
202
|
-
setTestError(err instanceof Error ? err.message : String(err));
|
|
203
|
-
setAvailableModels([prov.defaultModel]);
|
|
204
|
-
setTimeout(() => setStep("model"), 800);
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
// ── Write final config ──
|
|
208
|
-
const writeFinal = useCallback(() => {
|
|
209
|
-
const selectedModel = availableModels.length > 0 ? (availableModels[modelIdx] ?? model) : model;
|
|
210
|
-
// Build MCP server configs from selected registry entries
|
|
211
|
-
let mcpServers;
|
|
212
|
-
if (selectedMcp.size > 0) {
|
|
213
|
-
try {
|
|
214
|
-
const { MCP_REGISTRY } = require("../mcp/registry.js");
|
|
215
|
-
mcpServers = [...selectedMcp]
|
|
216
|
-
.map((name) => MCP_REGISTRY.find((e) => e.name === name))
|
|
217
|
-
.filter(Boolean)
|
|
218
|
-
.map((e) => ({
|
|
219
|
-
name: e.name,
|
|
220
|
-
command: "npx",
|
|
221
|
-
args: ["-y", e.package, ...(e.args ?? [])],
|
|
222
|
-
...(e.envVars?.length ? { env: Object.fromEntries(e.envVars.map((v) => [v, `YOUR_${v}`])) } : {}),
|
|
223
|
-
}));
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
/* ignore */
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
writeOhConfig({
|
|
230
|
-
provider: provider.key,
|
|
231
|
-
model: selectedModel || provider.defaultModel,
|
|
232
|
-
permissionMode: PERMISSION_MODES[permIdx].key,
|
|
233
|
-
...(apiKey ? { apiKey } : {}),
|
|
234
|
-
...(baseUrl ? { baseUrl } : {}),
|
|
235
|
-
...(mcpServers?.length ? { mcpServers } : {}),
|
|
236
|
-
});
|
|
237
|
-
setStep("done");
|
|
238
|
-
setTimeout(() => onDone?.(), 1500);
|
|
239
|
-
}, [provider, model, availableModels, modelIdx, permIdx, apiKey, baseUrl, selectedMcp, onDone]);
|
|
237
|
+
}, [step, provider, modelIdx, availableModels, model, suggestedMcp, mcpIdx, runTest, writeFinal]));
|
|
240
238
|
// ── Render ──
|
|
241
239
|
if (showSetup) {
|
|
242
240
|
return (_jsx(CybergotchiSetup, { onComplete: () => {
|
package/dist/harness/config.d.ts
CHANGED
|
@@ -98,6 +98,17 @@ export type OhConfig = {
|
|
|
98
98
|
rateLimit?: number;
|
|
99
99
|
allowedTools?: string[];
|
|
100
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* Environment variables injected into child processes spawned by the harness —
|
|
103
|
+
* Bash/Monitor/PowerShell tool executions and MCP server subprocesses. Useful
|
|
104
|
+
* for passing API keys to MCP servers without embedding them in the server's
|
|
105
|
+
* `env` field (which is per-server) or requiring the user to export them in
|
|
106
|
+
* their shell. Claude Code convention: same shape as `settings.json.env`.
|
|
107
|
+
*
|
|
108
|
+
* Implementation: read by `safeEnv()` in `src/utils/safe-env.ts` — every
|
|
109
|
+
* call-site that already uses `safeEnv()` picks this up automatically.
|
|
110
|
+
*/
|
|
111
|
+
env?: Record<string, string>;
|
|
101
112
|
};
|
|
102
113
|
/** Clear cached config (call after writes or to force re-read) */
|
|
103
114
|
export declare function invalidateConfigCache(): void;
|
package/dist/harness/hooks.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - http: POST JSON to URL, expect { allowed: true/false }
|
|
10
10
|
* - prompt: LLM yes/no check via provider.complete()
|
|
11
11
|
*/
|
|
12
|
+
import type { HookDef } from "./config.js";
|
|
12
13
|
export type HookEvent = "sessionStart" | "sessionEnd" | "preToolUse" | "postToolUse" | "fileChanged" | "cwdChanged" | "subagentStart" | "subagentStop" | "preCompact" | "postCompact" | "configChange" | "notification";
|
|
13
14
|
export type HookContext = {
|
|
14
15
|
toolName?: string;
|
|
@@ -32,6 +33,19 @@ export type HookContext = {
|
|
|
32
33
|
};
|
|
33
34
|
/** Clear hook cache (call after config changes) */
|
|
34
35
|
export declare function invalidateHookCache(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Evaluate a hook matcher against the current tool name.
|
|
38
|
+
*
|
|
39
|
+
* Supported forms (Claude Code compatible):
|
|
40
|
+
* - No matcher → always matches.
|
|
41
|
+
* - `/pattern/flags` → treated as a regex. Flags optional.
|
|
42
|
+
* - `mcp__server__tool` → literal match is a substring check (works for the
|
|
43
|
+
* standard `mcp__<server>__<tool>` naming convention).
|
|
44
|
+
* - `prefix*` or glob-ish → simple wildcard translated to regex.
|
|
45
|
+
* - Anything else → case-sensitive substring (legacy behavior — back-compat).
|
|
46
|
+
*/
|
|
47
|
+
/** @internal Exposed for testing. */
|
|
48
|
+
export declare function matchesHook(def: HookDef, ctx: HookContext): boolean;
|
|
35
49
|
/**
|
|
36
50
|
* Emit a hook event. For preToolUse, returns false if any hook blocks the call.
|
|
37
51
|
*
|
package/dist/harness/hooks.js
CHANGED
|
@@ -58,11 +58,54 @@ function buildEnv(event, ctx) {
|
|
|
58
58
|
env.OH_MESSAGE = ctx.message;
|
|
59
59
|
return env;
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Evaluate a hook matcher against the current tool name.
|
|
63
|
+
*
|
|
64
|
+
* Supported forms (Claude Code compatible):
|
|
65
|
+
* - No matcher → always matches.
|
|
66
|
+
* - `/pattern/flags` → treated as a regex. Flags optional.
|
|
67
|
+
* - `mcp__server__tool` → literal match is a substring check (works for the
|
|
68
|
+
* standard `mcp__<server>__<tool>` naming convention).
|
|
69
|
+
* - `prefix*` or glob-ish → simple wildcard translated to regex.
|
|
70
|
+
* - Anything else → case-sensitive substring (legacy behavior — back-compat).
|
|
71
|
+
*/
|
|
72
|
+
/** @internal Exposed for testing. */
|
|
73
|
+
export function matchesHook(def, ctx) {
|
|
74
|
+
if (!def.match)
|
|
75
|
+
return true;
|
|
76
|
+
if (!ctx.toolName)
|
|
77
|
+
return true;
|
|
78
|
+
const match = def.match;
|
|
79
|
+
// /regex/flags form
|
|
80
|
+
if (match.length > 2 && match.startsWith("/")) {
|
|
81
|
+
const lastSlash = match.lastIndexOf("/");
|
|
82
|
+
if (lastSlash > 0) {
|
|
83
|
+
try {
|
|
84
|
+
const pattern = match.slice(1, lastSlash);
|
|
85
|
+
const flags = match.slice(lastSlash + 1);
|
|
86
|
+
return new RegExp(pattern, flags).test(ctx.toolName);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
64
92
|
}
|
|
65
|
-
|
|
93
|
+
// Simple glob: asterisks translated to `.*`, anchored. Only activates if the
|
|
94
|
+
// match contains an asterisk — otherwise treat as substring for back-compat.
|
|
95
|
+
if (match.includes("*")) {
|
|
96
|
+
const escaped = match
|
|
97
|
+
.split("*")
|
|
98
|
+
.map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&"))
|
|
99
|
+
.join(".*");
|
|
100
|
+
try {
|
|
101
|
+
return new RegExp(`^${escaped}$`).test(ctx.toolName);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Legacy substring match
|
|
108
|
+
return ctx.toolName.includes(match);
|
|
66
109
|
}
|
|
67
110
|
// ── Hook Executors ──
|
|
68
111
|
/** Run a command hook. Returns exit code (0 = success/allowed). */
|
|
@@ -117,13 +160,16 @@ async function runHttpHook(url, event, ctx, timeoutMs = 10_000) {
|
|
|
117
160
|
return false;
|
|
118
161
|
}
|
|
119
162
|
}
|
|
120
|
-
/**
|
|
163
|
+
/**
|
|
164
|
+
* Run a prompt hook. Uses LLM to make a yes/no decision.
|
|
165
|
+
*
|
|
166
|
+
* Currently a stub — prompt hooks always allow because the hook system
|
|
167
|
+
* runs outside the query loop and has no access to a Provider instance.
|
|
168
|
+
* Full implementation requires passing a Provider via HookContext so the
|
|
169
|
+
* hook can call provider.complete() with the prompt text.
|
|
170
|
+
*/
|
|
121
171
|
async function runPromptHook(_promptText, _ctx) {
|
|
122
|
-
|
|
123
|
-
// This is a lightweight check; full LLM call would need provider injection
|
|
124
|
-
// For now, prompt hooks evaluate the prompt text as a simple template
|
|
125
|
-
// TODO: inject provider for full LLM-based prompt hooks
|
|
126
|
-
return true; // Default allow if no LLM available
|
|
172
|
+
return true;
|
|
127
173
|
}
|
|
128
174
|
// ── Hook Execution ──
|
|
129
175
|
/** Execute a single hook definition. Returns true if allowed. */
|
|
@@ -36,8 +36,76 @@ export type InstalledPlugin = {
|
|
|
36
36
|
marketplace: string;
|
|
37
37
|
installedAt: number;
|
|
38
38
|
cachePath: string;
|
|
39
|
+
/** Optional fields populated from `.claude-plugin/plugin.json` if present */
|
|
40
|
+
description?: string;
|
|
41
|
+
author?: string;
|
|
42
|
+
license?: string;
|
|
43
|
+
homepage?: string;
|
|
44
|
+
keywords?: string[];
|
|
45
|
+
};
|
|
46
|
+
/** Claude Code plugin manifest (`.claude-plugin/plugin.json`).
|
|
47
|
+
* Required: name, description. All other fields optional.
|
|
48
|
+
*/
|
|
49
|
+
export type CcPluginManifest = {
|
|
50
|
+
name: string;
|
|
51
|
+
description: string;
|
|
52
|
+
version?: string;
|
|
53
|
+
author?: {
|
|
54
|
+
name?: string;
|
|
55
|
+
email?: string;
|
|
56
|
+
} | string;
|
|
57
|
+
homepage?: string;
|
|
58
|
+
repository?: string;
|
|
59
|
+
license?: string;
|
|
60
|
+
keywords?: string[];
|
|
61
|
+
};
|
|
62
|
+
/** Parse a `.claude-plugin/plugin.json` file at the given plugin root, or null if missing/invalid. */
|
|
63
|
+
export declare function parseCcPluginManifest(pluginRoot: string): CcPluginManifest | null;
|
|
64
|
+
/** Claude Code marketplace.json format — superset of OH's marketplace.json with source-typed entries. */
|
|
65
|
+
export type CcMarketplacePluginSource = string | {
|
|
66
|
+
source: "github";
|
|
67
|
+
repo: string;
|
|
68
|
+
ref?: string;
|
|
69
|
+
} | {
|
|
70
|
+
source: "url";
|
|
71
|
+
url: string;
|
|
72
|
+
} | {
|
|
73
|
+
source: "npm";
|
|
74
|
+
package: string;
|
|
75
|
+
version?: string;
|
|
76
|
+
};
|
|
77
|
+
export type CcMarketplaceEntry = {
|
|
78
|
+
name: string;
|
|
79
|
+
description?: string;
|
|
80
|
+
version?: string;
|
|
81
|
+
author?: {
|
|
82
|
+
name?: string;
|
|
83
|
+
email?: string;
|
|
84
|
+
} | string;
|
|
85
|
+
source: CcMarketplacePluginSource;
|
|
39
86
|
};
|
|
40
|
-
|
|
87
|
+
export type CcMarketplace = {
|
|
88
|
+
name: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
owner?: {
|
|
91
|
+
name?: string;
|
|
92
|
+
email?: string;
|
|
93
|
+
};
|
|
94
|
+
plugins: CcMarketplaceEntry[];
|
|
95
|
+
};
|
|
96
|
+
/** Convert a CcMarketplace to OH's internal Marketplace type (lossy: dropped fields like `owner` are not stored). */
|
|
97
|
+
export declare function ccMarketplaceToOh(cc: CcMarketplace): Marketplace;
|
|
98
|
+
/** Parse marketplace JSON text. Tries CC format (.claude-plugin/marketplace.json shape) first, falls back to OH-native. */
|
|
99
|
+
export declare function parseMarketplaceJson(text: string): Marketplace | null;
|
|
100
|
+
/** Discover plugin-shipped MCP servers from `cachePath/.mcp.json`. Returns raw object for the runtime to merge. */
|
|
101
|
+
export declare function getPluginMcpServers(cachePath: string): Record<string, unknown> | null;
|
|
102
|
+
/** Discover plugin-shipped hooks from `cachePath/hooks/hooks.json`. Returns raw config for the runtime to register. */
|
|
103
|
+
export declare function getPluginHooks(cachePath: string): Record<string, unknown> | null;
|
|
104
|
+
/** Discover plugin-shipped LSP servers from `cachePath/.lsp.json`. Returns raw config for the runtime to register. */
|
|
105
|
+
export declare function getPluginLspServers(cachePath: string): Record<string, unknown> | null;
|
|
106
|
+
/** Add a marketplace from a URL, GitHub repo, or local path.
|
|
107
|
+
* Probes for both OH-native `marketplace.json` and Claude Code `.claude-plugin/marketplace.json`.
|
|
108
|
+
*/
|
|
41
109
|
export declare function addMarketplace(nameOrUrl: string): Marketplace | null;
|
|
42
110
|
/** Remove a marketplace */
|
|
43
111
|
export declare function removeMarketplace(name: string): boolean;
|
|
@@ -51,7 +119,14 @@ export declare function searchMarketplace(query: string): Array<MarketplaceEntry
|
|
|
51
119
|
export declare function installPlugin(pluginName: string, marketplaceName?: string): InstalledPlugin | null;
|
|
52
120
|
/** Uninstall a plugin */
|
|
53
121
|
export declare function uninstallPlugin(name: string): boolean;
|
|
54
|
-
/** Get all installed plugins
|
|
122
|
+
/** Get all installed plugins.
|
|
123
|
+
* Sources merged in priority order:
|
|
124
|
+
* 1. installed.json (plugins installed via /plugin install or addMarketplace flow)
|
|
125
|
+
* 2. CC-style plugins discovered in PLUGIN_CACHE_DIR via .claude-plugin/plugin.json
|
|
126
|
+
* (covers plugins manually dropped in the cache, or installed by parallel tooling)
|
|
127
|
+
* Plugins from #1 are enriched with manifest data if their cachePath has one.
|
|
128
|
+
* De-duplication is by cachePath.
|
|
129
|
+
*/
|
|
55
130
|
export declare function getInstalledPlugins(): InstalledPlugin[];
|
|
56
131
|
/** Format marketplace entries for display */
|
|
57
132
|
export declare function formatMarketplaceSearch(results: Array<MarketplaceEntry & {
|