forge-cw 0.1.3 → 0.1.4
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/dist/index.js
CHANGED
|
@@ -2441,14 +2441,17 @@ function manifestRepo(manifest) {
|
|
|
2441
2441
|
return repo["url"];
|
|
2442
2442
|
return void 0;
|
|
2443
2443
|
}
|
|
2444
|
-
function
|
|
2445
|
-
const entry = marketplaces[marketplace];
|
|
2444
|
+
function marketplaceSource(entry) {
|
|
2446
2445
|
const source = isRecord(entry) ? entry["source"] : void 0;
|
|
2447
2446
|
if (!isRecord(source))
|
|
2448
2447
|
return void 0;
|
|
2449
|
-
const value = source["repo"] ?? source["url"];
|
|
2448
|
+
const value = source["repo"] ?? source["url"] ?? source["path"];
|
|
2450
2449
|
return typeof value === "string" ? value : void 0;
|
|
2451
2450
|
}
|
|
2451
|
+
function splitPluginId(id) {
|
|
2452
|
+
const at = id.lastIndexOf("@");
|
|
2453
|
+
return { name: id.slice(0, at), marketplace: id.slice(at + 1) };
|
|
2454
|
+
}
|
|
2452
2455
|
function projectSkillNames(projectPath) {
|
|
2453
2456
|
const dir = join8(projectPath, ".claude", "skills");
|
|
2454
2457
|
if (!existsSync6(dir))
|
|
@@ -2456,17 +2459,13 @@ function projectSkillNames(projectPath) {
|
|
|
2456
2459
|
return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync6(join8(dir, e.name, "SKILL.md"))).map((e) => e.name).sort();
|
|
2457
2460
|
}
|
|
2458
2461
|
async function listPlugins(reader, remoteOf) {
|
|
2459
|
-
const scopes = [
|
|
2460
|
-
{ scope: "global", scopeRef: "global" },
|
|
2461
|
-
...reader.getAccounts().map((a) => ({ scope: "account", scopeRef: a }))
|
|
2462
|
-
];
|
|
2463
2462
|
const found = /* @__PURE__ */ new Map();
|
|
2464
|
-
for (const { scope, scopeRef } of
|
|
2463
|
+
for (const { scope, scopeRef } of pluginScopes(reader)) {
|
|
2465
2464
|
const configDir = reader.getSkillConfigDir(scope, scopeRef);
|
|
2466
2465
|
const installed = readJson(join8(configDir, "plugins", "installed_plugins.json"))["plugins"];
|
|
2467
2466
|
if (!isRecord(installed))
|
|
2468
2467
|
continue;
|
|
2469
|
-
const marketplaces =
|
|
2468
|
+
const marketplaces = knownMarketplaces(configDir);
|
|
2470
2469
|
const enabledPlugins = readJson(join8(configDir, "settings.json"))["enabledPlugins"];
|
|
2471
2470
|
for (const [id, entries] of Object.entries(installed)) {
|
|
2472
2471
|
if (!Array.isArray(entries))
|
|
@@ -2474,7 +2473,6 @@ async function listPlugins(reader, remoteOf) {
|
|
|
2474
2473
|
const user = entries.find((e) => isRecord(e) && e["scope"] === "user" && typeof e["installPath"] === "string");
|
|
2475
2474
|
if (!user)
|
|
2476
2475
|
continue;
|
|
2477
|
-
const marketplace = id.slice(id.lastIndexOf("@") + 1);
|
|
2478
2476
|
const install = {
|
|
2479
2477
|
scope,
|
|
2480
2478
|
scopeRef,
|
|
@@ -2484,7 +2482,7 @@ async function listPlugins(reader, remoteOf) {
|
|
|
2484
2482
|
lastUpdated: typeof user["lastUpdated"] === "string" ? user["lastUpdated"] : void 0
|
|
2485
2483
|
};
|
|
2486
2484
|
const list = found.get(id) ?? [];
|
|
2487
|
-
list.push({ install, repo:
|
|
2485
|
+
list.push({ install, repo: marketplaceSource(marketplaces[splitPluginId(id).marketplace]) });
|
|
2488
2486
|
found.set(id, list);
|
|
2489
2487
|
}
|
|
2490
2488
|
}
|
|
@@ -2498,8 +2496,7 @@ async function listPlugins(reader, remoteOf) {
|
|
|
2498
2496
|
const match = repo ? remotes.find((r) => r.repo === repo) : void 0;
|
|
2499
2497
|
plugins.push({
|
|
2500
2498
|
id,
|
|
2501
|
-
|
|
2502
|
-
marketplace: id.slice(id.lastIndexOf("@") + 1),
|
|
2499
|
+
...splitPluginId(id),
|
|
2503
2500
|
description: String(manifest["description"] ?? ""),
|
|
2504
2501
|
repo,
|
|
2505
2502
|
installs: list.map((f) => f.install),
|
|
@@ -2521,7 +2518,41 @@ function readPluginSkill(plugin, name) {
|
|
|
2521
2518
|
return null;
|
|
2522
2519
|
}
|
|
2523
2520
|
}
|
|
2524
|
-
|
|
2521
|
+
function readCatalog(location, marketplace) {
|
|
2522
|
+
const listed = readJson(join8(location, ".claude-plugin", "marketplace.json"))["plugins"];
|
|
2523
|
+
return (Array.isArray(listed) ? listed : []).filter((p) => isRecord(p) && typeof p["name"] === "string").map((p) => ({
|
|
2524
|
+
id: `${String(p["name"])}@${marketplace}`,
|
|
2525
|
+
name: String(p["name"]),
|
|
2526
|
+
description: String(p["description"] ?? ""),
|
|
2527
|
+
category: typeof p["category"] === "string" ? p["category"] : void 0
|
|
2528
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
2529
|
+
}
|
|
2530
|
+
function listMarketplaces(reader) {
|
|
2531
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2532
|
+
for (const target of pluginScopes(reader)) {
|
|
2533
|
+
for (const [name, entry] of Object.entries(knownMarketplaces(reader.getSkillConfigDir(target.scope, target.scopeRef)))) {
|
|
2534
|
+
const source = marketplaceSource(entry);
|
|
2535
|
+
if (!source)
|
|
2536
|
+
continue;
|
|
2537
|
+
const known = byName.get(name) ?? { name, source, scopes: [], plugins: [] };
|
|
2538
|
+
known.scopes.push(target);
|
|
2539
|
+
const location = isRecord(entry) ? entry["installLocation"] : void 0;
|
|
2540
|
+
if (known.plugins.length === 0 && typeof location === "string")
|
|
2541
|
+
known.plugins = readCatalog(location, name);
|
|
2542
|
+
byName.set(name, known);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
2546
|
+
}
|
|
2547
|
+
function marketplaceSourceOf(reader, marketplace) {
|
|
2548
|
+
for (const { scope, scopeRef } of pluginScopes(reader)) {
|
|
2549
|
+
const source = marketplaceSource(knownMarketplaces(reader.getSkillConfigDir(scope, scopeRef))[marketplace]);
|
|
2550
|
+
if (source)
|
|
2551
|
+
return source;
|
|
2552
|
+
}
|
|
2553
|
+
return void 0;
|
|
2554
|
+
}
|
|
2555
|
+
var isRecord, inside, pluginScopes, knownMarketplaces, knowsMarketplace;
|
|
2525
2556
|
var init_plugins = __esm({
|
|
2526
2557
|
"../core/dist/plugins.js"() {
|
|
2527
2558
|
"use strict";
|
|
@@ -2531,6 +2562,12 @@ var init_plugins = __esm({
|
|
|
2531
2562
|
const p = resolve2(path);
|
|
2532
2563
|
return p === r || p.startsWith(r + sep);
|
|
2533
2564
|
};
|
|
2565
|
+
pluginScopes = (reader) => [
|
|
2566
|
+
{ scope: "global", scopeRef: "global" },
|
|
2567
|
+
...reader.getAccounts().map((a) => ({ scope: "account", scopeRef: a }))
|
|
2568
|
+
];
|
|
2569
|
+
knownMarketplaces = (configDir) => readJson(join8(configDir, "plugins", "known_marketplaces.json"));
|
|
2570
|
+
knowsMarketplace = (configDir, marketplace) => isRecord(knownMarketplaces(configDir)[marketplace]);
|
|
2534
2571
|
}
|
|
2535
2572
|
});
|
|
2536
2573
|
|
|
@@ -2559,7 +2596,26 @@ function skillRoutes(reader, options = {}) {
|
|
|
2559
2596
|
const runnerFor = options.runnerFor ?? ((env) => createRunner(env, INSTALL_TIMEOUT_MS));
|
|
2560
2597
|
const remoteOf = options.remoteOf ?? createRemoteLookup(runCommand);
|
|
2561
2598
|
const app = new Hono2();
|
|
2562
|
-
const
|
|
2599
|
+
const busy = /* @__PURE__ */ new Set();
|
|
2600
|
+
const runClaude = async (scope, ref, commands, failure) => {
|
|
2601
|
+
const configDir = reader.getSkillConfigDir(scope, ref);
|
|
2602
|
+
if (busy.has(configDir))
|
|
2603
|
+
return { error: `A plugin command is already running for ${ref}`, status: 409 };
|
|
2604
|
+
busy.add(configDir);
|
|
2605
|
+
try {
|
|
2606
|
+
const { CLAUDE_CONFIG_DIR: _ignored, ...base } = envWithoutHarness();
|
|
2607
|
+
const run = runnerFor(scope === "global" ? base : { ...base, CLAUDE_CONFIG_DIR: configDir });
|
|
2608
|
+
for (const args of commands) {
|
|
2609
|
+
const result2 = await run("claude", args, configDir);
|
|
2610
|
+
if (result2.code !== 0)
|
|
2611
|
+
return { error: `${failure}: ${commandFailure(result2)}`, status: 500 };
|
|
2612
|
+
}
|
|
2613
|
+
return null;
|
|
2614
|
+
} finally {
|
|
2615
|
+
busy.delete(configDir);
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
const knownAccount = (account) => account && reader.getAccounts().includes(account) ? account : null;
|
|
2563
2619
|
const refOf = (scope, scopeRef) => scope === "global" ? "global" : scopeRef ?? "";
|
|
2564
2620
|
const knownScope = (scope, ref) => scope === "global" || (scope === "account" ? reader.getAccounts().includes(ref) : scope === "project" && ref in reader.getProjects());
|
|
2565
2621
|
app.get("/", (c) => c.json(reader.getSkills()));
|
|
@@ -2763,27 +2819,50 @@ function skillRoutes(reader, options = {}) {
|
|
|
2763
2819
|
const install = plugin?.installs.find((i) => i.scope === scope && i.scopeRef === ref);
|
|
2764
2820
|
if (!plugin || !install)
|
|
2765
2821
|
return c.json({ error: `${id} is not installed in ${ref || "that scope"}` }, 404);
|
|
2766
|
-
const
|
|
2767
|
-
if (
|
|
2768
|
-
return c.json({ error:
|
|
2769
|
-
updating.add(configDir);
|
|
2770
|
-
try {
|
|
2771
|
-
const { CLAUDE_CONFIG_DIR: _ignored, ...base } = envWithoutHarness();
|
|
2772
|
-
const run = runnerFor(install.scope === "global" ? base : { ...base, CLAUDE_CONFIG_DIR: configDir });
|
|
2773
|
-
for (const args of [["plugin", "marketplace", "update", plugin.marketplace], ["plugin", "update", plugin.id]]) {
|
|
2774
|
-
const result2 = await run("claude", args, configDir);
|
|
2775
|
-
if (result2.code !== 0)
|
|
2776
|
-
return c.json({ error: `Failed to update ${plugin.name}: ${commandFailure(result2)}` }, 500);
|
|
2777
|
-
}
|
|
2778
|
-
} finally {
|
|
2779
|
-
updating.delete(configDir);
|
|
2780
|
-
}
|
|
2822
|
+
const failed = await runClaude(install.scope, ref, [["plugin", "marketplace", "update", plugin.marketplace], ["plugin", "update", plugin.id]], `Failed to update ${plugin.name}`);
|
|
2823
|
+
if (failed)
|
|
2824
|
+
return c.json({ error: failed.error }, failed.status);
|
|
2781
2825
|
const version = (await find())?.installs.find((i) => i.scope === install.scope && i.scopeRef === ref)?.version ?? install.version;
|
|
2782
2826
|
return c.json({ ok: true, version });
|
|
2783
2827
|
});
|
|
2828
|
+
app.get("/marketplaces", (c) => c.json(listMarketplaces(reader)));
|
|
2829
|
+
app.post("/marketplaces", async (c) => {
|
|
2830
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2831
|
+
const source = body.source?.trim() ?? "";
|
|
2832
|
+
if (!source || source.startsWith("-"))
|
|
2833
|
+
return c.json({ error: "A GitHub repo, URL or path is required" }, 400);
|
|
2834
|
+
const account = knownAccount(body.account);
|
|
2835
|
+
if (!account)
|
|
2836
|
+
return c.json({ error: "Unknown account" }, 404);
|
|
2837
|
+
const failed = await runClaude("account", account, [["plugin", "marketplace", "add", source]], `Failed to add ${source}`);
|
|
2838
|
+
if (failed)
|
|
2839
|
+
return c.json({ error: failed.error }, failed.status);
|
|
2840
|
+
return c.json({ ok: true });
|
|
2841
|
+
});
|
|
2842
|
+
app.post("/plugins/install", async (c) => {
|
|
2843
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2844
|
+
const id = body.id ?? "";
|
|
2845
|
+
if (!PLUGIN_ID_RE.test(id))
|
|
2846
|
+
return c.json({ error: "Invalid plugin id" }, 400);
|
|
2847
|
+
const account = knownAccount(body.account);
|
|
2848
|
+
if (!account)
|
|
2849
|
+
return c.json({ error: "Unknown account" }, 404);
|
|
2850
|
+
const { name, marketplace } = splitPluginId(id);
|
|
2851
|
+
const commands = [["plugin", "install", id]];
|
|
2852
|
+
if (!knowsMarketplace(reader.getSkillConfigDir("account", account), marketplace)) {
|
|
2853
|
+
const source = marketplaceSourceOf(reader, marketplace);
|
|
2854
|
+
if (!source)
|
|
2855
|
+
return c.json({ error: `Unknown marketplace ${marketplace}` }, 404);
|
|
2856
|
+
commands.unshift(["plugin", "marketplace", "add", source]);
|
|
2857
|
+
}
|
|
2858
|
+
const failed = await runClaude("account", account, commands, `Failed to install ${name}`);
|
|
2859
|
+
if (failed)
|
|
2860
|
+
return c.json({ error: failed.error }, failed.status);
|
|
2861
|
+
return c.json({ ok: true });
|
|
2862
|
+
});
|
|
2784
2863
|
return app;
|
|
2785
2864
|
}
|
|
2786
|
-
var INSTALL_TIMEOUT_MS, SKILL_NAME_RE;
|
|
2865
|
+
var INSTALL_TIMEOUT_MS, SKILL_NAME_RE, PLUGIN_ID_RE;
|
|
2787
2866
|
var init_skill_routes = __esm({
|
|
2788
2867
|
"../core/dist/skill-routes.js"() {
|
|
2789
2868
|
"use strict";
|
|
@@ -2792,6 +2871,7 @@ var init_skill_routes = __esm({
|
|
|
2792
2871
|
init_plugins();
|
|
2793
2872
|
INSTALL_TIMEOUT_MS = 12e4;
|
|
2794
2873
|
SKILL_NAME_RE = /^\w[\w.-]*$/;
|
|
2874
|
+
PLUGIN_ID_RE = /^\w[\w.-]*@\w[\w.-]*$/;
|
|
2795
2875
|
}
|
|
2796
2876
|
});
|
|
2797
2877
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-cw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A visual dashboard for your coding agents on Claude Code, Codex, Pi or OpenCode. Installs CW for you.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"@types/node": "^22.0.0",
|
|
40
40
|
"esbuild": "^0.25.0",
|
|
41
41
|
"typescript": "^5.8.0",
|
|
42
|
-
"@forge-dev/
|
|
43
|
-
"@forge-dev/
|
|
42
|
+
"@forge-dev/console": "0.1.0",
|
|
43
|
+
"@forge-dev/core": "0.1.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc",
|