@amsterdamdatalabs/enact-extensions 0.1.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/.agents/plugins/marketplace.json +20 -0
- package/README.md +88 -0
- package/catalog/enact-context.json +9 -0
- package/catalog/enact-factory.json +7 -0
- package/catalog/enact-operator.json +7 -0
- package/catalog/enact-wiki.json +7 -0
- package/catalog/net-revenue-management.json +8 -0
- package/dist/create/claude.d.ts +3 -0
- package/dist/create/claude.d.ts.map +1 -0
- package/dist/create/claude.js +12 -0
- package/dist/create/claude.js.map +1 -0
- package/dist/create/codex.d.ts +3 -0
- package/dist/create/codex.d.ts.map +1 -0
- package/dist/create/codex.js +14 -0
- package/dist/create/codex.js.map +1 -0
- package/dist/create/cursor.d.ts +3 -0
- package/dist/create/cursor.d.ts.map +1 -0
- package/dist/create/cursor.js +20 -0
- package/dist/create/cursor.js.map +1 -0
- package/dist/create/enact.d.ts +3 -0
- package/dist/create/enact.d.ts.map +1 -0
- package/dist/create/enact.js +32 -0
- package/dist/create/enact.js.map +1 -0
- package/dist/create/index.d.ts +18 -0
- package/dist/create/index.d.ts.map +1 -0
- package/dist/create/index.js +54 -0
- package/dist/create/index.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +60 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +250 -0
- package/dist/install.js.map +1 -0
- package/dist/internal/claude.d.ts +16 -0
- package/dist/internal/claude.d.ts.map +1 -0
- package/dist/internal/claude.js +35 -0
- package/dist/internal/claude.js.map +1 -0
- package/dist/internal/codex.d.ts +43 -0
- package/dist/internal/codex.d.ts.map +1 -0
- package/dist/internal/codex.js +205 -0
- package/dist/internal/codex.js.map +1 -0
- package/dist/internal/cursor.d.ts +2 -0
- package/dist/internal/cursor.d.ts.map +1 -0
- package/dist/internal/cursor.js +6 -0
- package/dist/internal/cursor.js.map +1 -0
- package/dist/internal/io.d.ts +3 -0
- package/dist/internal/io.d.ts.map +1 -0
- package/dist/internal/io.js +11 -0
- package/dist/internal/io.js.map +1 -0
- package/dist/internal/pick.d.ts +4 -0
- package/dist/internal/pick.d.ts.map +1 -0
- package/dist/internal/pick.js +17 -0
- package/dist/internal/pick.js.map +1 -0
- package/dist/internal/platform.d.ts +21 -0
- package/dist/internal/platform.d.ts.map +1 -0
- package/dist/internal/platform.js +120 -0
- package/dist/internal/platform.js.map +1 -0
- package/dist/internal/schema.d.ts +5 -0
- package/dist/internal/schema.d.ts.map +1 -0
- package/dist/internal/schema.js +37 -0
- package/dist/internal/schema.js.map +1 -0
- package/dist/internal/types.d.ts +49 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/validate/claude.d.ts +3 -0
- package/dist/validate/claude.d.ts.map +1 -0
- package/dist/validate/claude.js +11 -0
- package/dist/validate/claude.js.map +1 -0
- package/dist/validate/codex.d.ts +3 -0
- package/dist/validate/codex.d.ts.map +1 -0
- package/dist/validate/codex.js +11 -0
- package/dist/validate/codex.js.map +1 -0
- package/dist/validate/cursor.d.ts +3 -0
- package/dist/validate/cursor.d.ts.map +1 -0
- package/dist/validate/cursor.js +11 -0
- package/dist/validate/cursor.js.map +1 -0
- package/dist/validate/enact.d.ts +4 -0
- package/dist/validate/enact.d.ts.map +1 -0
- package/dist/validate/enact.js +18 -0
- package/dist/validate/enact.js.map +1 -0
- package/dist/validate/index.d.ts +17 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +99 -0
- package/dist/validate/index.js.map +1 -0
- package/package.json +58 -0
- package/plugins/net-revenue-management/.codex-plugin/plugin.json +35 -0
- package/plugins/net-revenue-management/.mcp.json +9 -0
- package/plugins/net-revenue-management/skills/net-revenue-risks/SKILL.md +30 -0
- package/plugins/net-revenue-management/skills/net-revenue-scenario/SKILL.md +31 -0
- package/scripts/enact-extensions.mjs +105 -0
- package/scripts/install.sh +70 -0
- package/scripts/lib/resolve-plugin-root.mjs +9 -0
- package/scripts/lib/run-install.mjs +66 -0
- package/scripts/lib/run-sync.mjs +24 -0
- package/scripts/lib/run-validate.mjs +36 -0
- package/scripts/rename-supervisor-to-operator.pl +66 -0
- package/scripts/setup-enact-context.sh +23 -0
- package/scripts/sync-manifests.mjs +23 -0
- package/scripts/validate-catalog.mjs +37 -0
- package/scripts/validate-plugin.mjs +10 -0
- package/spec/claude.json +99 -0
- package/spec/claude.md +485 -0
- package/spec/codex.json +154 -0
- package/spec/codex.md +145 -0
- package/spec/cursor.json +88 -0
- package/spec/cursor.md +175 -0
- package/spec/enact.json +211 -0
- package/spec/enact.md +204 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: net-revenue-risks
|
|
3
|
+
description: Surface retailer terms risks, Asda delisting exposure, and Asda non-compliance impact for a CPG pricing event. Grounded in Meridian Confections UK Premium Snacks January 2026 data.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Net Revenue Risk Assessment
|
|
7
|
+
|
|
8
|
+
Use this skill to identify and quantify risks associated with a pricing event before committing to a recommendation.
|
|
9
|
+
|
|
10
|
+
### Workflow
|
|
11
|
+
|
|
12
|
+
1. **Check retailer terms** — call `check_retailer_terms` (optionally filtered by `accounts`) to surface SKUs where the new RSP breaches the Asda 15p own-label price-gap policy. Galaxy Sharing 150g and 200g are flagged at Asda; combined exposure ~£643K.
|
|
13
|
+
|
|
14
|
+
2. **Model Asda non-compliance** — call `model_asda_non_compliance` with `hold_weeks` (1–13) to quantify Q1 net revenue lost if Asda delays compliance. At a full 13-week hold the loss ≈ −£210K vs full compliance.
|
|
15
|
+
|
|
16
|
+
3. **Get all identified risks** — call `get_risks` to return the complete risk register: Asda delisting (~£643K), Tesco MFC clause, and the Jan W1 timing upside (+£340K opportunity).
|
|
17
|
+
|
|
18
|
+
### Key thresholds
|
|
19
|
+
|
|
20
|
+
- **Asda price-gap policy**: 15p branded-above-own-label threshold. A +5% RSP increase on Galaxy Sharing bags breaches this, triggering a delisting review.
|
|
21
|
+
- **Asda hold breakeven**: Every additional week of hold costs ~£16K net revenue on the sharing segment.
|
|
22
|
+
- **Split mitigation**: Applying only +3% to sharing SKUs keeps Galaxy Sharing 150g/200g below the 15p gap threshold, protecting the Asda account while still delivering ~+£1.92M net revenue impact range-wide.
|
|
23
|
+
|
|
24
|
+
### Example prompts
|
|
25
|
+
|
|
26
|
+
- "What is the total Asda delisting risk for the +5% price event?"
|
|
27
|
+
- "Model 8 weeks of Asda non-compliance on sharing SKUs."
|
|
28
|
+
- "Show all risks for the January 2026 price event, ordered by severity."
|
|
29
|
+
|
|
30
|
+
> **Draft note**: skill body is a starter draft for review. Validate risk thresholds and non-compliance formula against future data refreshes.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: net-revenue-scenario
|
|
3
|
+
description: Model pricing scenarios for a CPG price event using Revenue Growth Management tools. Covers full-range and split price increases, P&L waterfall cascades, and volume response across SKU segments.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Net Revenue Scenario Modelling
|
|
7
|
+
|
|
8
|
+
Use this skill to model the financial impact of a pricing event on the Meridian Confections UK Premium Snacks range (January 2026, 14 SKUs).
|
|
9
|
+
|
|
10
|
+
### Workflow
|
|
11
|
+
|
|
12
|
+
1. **Load the elasticity model** — call `load_elasticity_model` first. This grounds all subsequent tools in the actual SKU economics (own-price elasticity coefficients, RSP, net price, gross margin %).
|
|
13
|
+
|
|
14
|
+
2. **Model volume response** — call `calculate_volume_response` with the target `price_delta_pct` (e.g. `5.0` for Full 5%) and optionally filter by `segments` (`["premium","sharing","standard"]`).
|
|
15
|
+
|
|
16
|
+
3. **Run the P&L cascade** — call `run_pl_cascade` with `scenario` set to one of:
|
|
17
|
+
- `"conservative"` — lower list-price step, more volume drag
|
|
18
|
+
- `"base"` — the reference scenario (net revenue impact +£2.1M)
|
|
19
|
+
- `"optimistic"` — tighter volume response, fuller recovery
|
|
20
|
+
|
|
21
|
+
4. **Model a split increase** — if Asda delisting risk is a concern, call `model_split_scenario` with a lower `sharing_pct` (e.g. `3.0`) to keep Galaxy Sharing bags below the 15p Asda price-gap policy threshold while applying the full increase to premium and standard SKUs.
|
|
22
|
+
|
|
23
|
+
5. **Compare all options** — call `get_scenario_comparison` to return the four strategic options (Full 5%, Split recommended, Phased Q1/Q2, No change) with net revenue, GM%, EBITDA, and risk level.
|
|
24
|
+
|
|
25
|
+
### Example prompts
|
|
26
|
+
|
|
27
|
+
- "Model a +5%/+3% split scenario: premium at 5%, sharing at 3%."
|
|
28
|
+
- "What is the net revenue impact of the optimistic base scenario?"
|
|
29
|
+
- "Compare all four pricing options for the January 2026 price event."
|
|
30
|
+
|
|
31
|
+
> **Draft note**: skill body is a starter draft for review. Refine tool call sequences and example prompts based on actual agent testing.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolvePluginRoot } from "./lib/resolve-plugin-root.mjs";
|
|
3
|
+
import { runValidate } from "./lib/run-validate.mjs";
|
|
4
|
+
import { runSync } from "./lib/run-sync.mjs";
|
|
5
|
+
import { runInstall } from "./lib/run-install.mjs";
|
|
6
|
+
|
|
7
|
+
const HELP = `enact-extensions — Enact multi-platform plugin manifests
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
enact-extensions validate [path] Validate manifests (default: cwd)
|
|
11
|
+
enact-extensions sync [path] Sync host manifests from .agents/plugin.json (default: cwd)
|
|
12
|
+
enact-extensions sync [path] --name <id> Create .agents/plugin.json then sync (new plugin)
|
|
13
|
+
enact-extensions install [path] Sync and install plugin into Codex-compatible homes (default: cwd)
|
|
14
|
+
enact-extensions install [path] --platform claude Install to Claude Code
|
|
15
|
+
enact-extensions install [path] --platform cursor Install to Cursor
|
|
16
|
+
enact-extensions install [path] --platform enact Install to Enact (Codex fork)
|
|
17
|
+
enact-extensions install [path] --platform codex Install to Codex (explicit)
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
path Plugin root (skills/, .agents/, etc.). Defaults to process.cwd().
|
|
21
|
+
--platform <name> Target platform: codex (default), claude, cursor, enact
|
|
22
|
+
--codex-home <path> Install only into the given Codex-compatible home
|
|
23
|
+
--enact-home <path> Target a specific local Enact home (default: ~/.enact)
|
|
24
|
+
--claude-home <path> Target a specific local Claude home (default: ~/.claude)
|
|
25
|
+
--cursor-home <path> Target a specific local Cursor home (default: ~/.cursor)
|
|
26
|
+
--marketplace <name> Marketplace name (default: enact-os-plugins)
|
|
27
|
+
--no-enable Install files without enabling the Codex/Enact plugin
|
|
28
|
+
--skip-sync Install current manifests without regenerating them
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
cd ../enact-operator/extensions && enact-extensions validate
|
|
32
|
+
enact-extensions sync . --name my-plugin
|
|
33
|
+
enact-extensions install ../enact-operator/extensions
|
|
34
|
+
enact-extensions install ../enact-operator/extensions --platform claude
|
|
35
|
+
enact-extensions install ../enact-operator/extensions --platform cursor
|
|
36
|
+
enact-extensions install ../enact-operator/extensions --platform enact
|
|
37
|
+
enact-extensions validate ../enact-operator/extensions
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
const positional = [];
|
|
42
|
+
const options = {};
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < argv.length; i++) {
|
|
45
|
+
const arg = argv[i];
|
|
46
|
+
if (arg === "--name" && argv[i + 1]) {
|
|
47
|
+
options.name = argv[++i];
|
|
48
|
+
} else if (arg === "--codex-home" && argv[i + 1]) {
|
|
49
|
+
options.codexHome = argv[++i];
|
|
50
|
+
} else if (arg === "--marketplace" && argv[i + 1]) {
|
|
51
|
+
options.marketplaceName = argv[++i];
|
|
52
|
+
} else if (arg === "--no-enable") {
|
|
53
|
+
options.enable = false;
|
|
54
|
+
} else if (arg === "--skip-sync") {
|
|
55
|
+
options.sync = false;
|
|
56
|
+
} else if (arg === "--platform" && argv[i + 1]) {
|
|
57
|
+
options.platform = argv[++i];
|
|
58
|
+
} else if (arg === "--claude-home" && argv[i + 1]) {
|
|
59
|
+
options.claudeHome = argv[++i];
|
|
60
|
+
} else if (arg === "--cursor-home" && argv[i + 1]) {
|
|
61
|
+
options.cursorHome = argv[++i];
|
|
62
|
+
} else if (arg === "--enact-home" && argv[i + 1]) {
|
|
63
|
+
options.enactHome = argv[++i];
|
|
64
|
+
} else if (arg === "-h" || arg === "--help") {
|
|
65
|
+
positional.push("help");
|
|
66
|
+
} else if (!arg.startsWith("-")) {
|
|
67
|
+
positional.push(arg);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { command: positional[0], path: positional[1], options };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { command, path, options } = parseArgs(process.argv.slice(2));
|
|
75
|
+
|
|
76
|
+
if (!command || command === "help") {
|
|
77
|
+
console.log(HELP);
|
|
78
|
+
process.exit(command ? 0 : 1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const pluginRoot = resolvePluginRoot(path);
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
if (command === "validate") {
|
|
85
|
+
const ok = runValidate(pluginRoot);
|
|
86
|
+
process.exit(ok ? 0 : 1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (command === "sync") {
|
|
90
|
+
runSync(pluginRoot, { name: options.name });
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (command === "install") {
|
|
95
|
+
runInstall(pluginRoot, options);
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.error(`Unknown command: ${command}\n`);
|
|
100
|
+
console.log(HELP);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
|
|
6
|
+
# ── Node.js version check ──────────────────────────────────────────────────────
|
|
7
|
+
NODE_MAJOR="$(node --version 2>/dev/null | sed 's/v//' | cut -d. -f1)"
|
|
8
|
+
if [ -z "$NODE_MAJOR" ] || [ "$NODE_MAJOR" -lt 22 ]; then
|
|
9
|
+
echo "error: Node.js >= 22 is required (found: $(node --version 2>/dev/null || echo 'none'))" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
cd "$REPO_ROOT"
|
|
14
|
+
|
|
15
|
+
# ── Install dependencies ───────────────────────────────────────────────────────
|
|
16
|
+
echo "→ npm install"
|
|
17
|
+
npm install --no-audit --no-fund
|
|
18
|
+
|
|
19
|
+
# ── Build TypeScript ───────────────────────────────────────────────────────────
|
|
20
|
+
echo "→ npm run build"
|
|
21
|
+
npm run build
|
|
22
|
+
|
|
23
|
+
# ── Bundle into a single file ─────────────────────────────────────────────────
|
|
24
|
+
echo "→ bundling"
|
|
25
|
+
# Bundle the CLI and all JS/TS dependencies into one ESM file.
|
|
26
|
+
# The bundle is installed into <support>/dist/internal/ so that import.meta.url
|
|
27
|
+
# resolves ../../ to <support>/ — matching the dist/internal/schema.js assumption
|
|
28
|
+
# about where spec/ lives relative to the compiled output.
|
|
29
|
+
SUPPORT_DIR="$HOME/.local/share/enact-extensions"
|
|
30
|
+
BUNDLE_DIR="$SUPPORT_DIR/dist/internal"
|
|
31
|
+
mkdir -p "$BUNDLE_DIR"
|
|
32
|
+
|
|
33
|
+
npx --yes esbuild scripts/enact-extensions.mjs \
|
|
34
|
+
--bundle \
|
|
35
|
+
--platform=node \
|
|
36
|
+
--format=cjs \
|
|
37
|
+
"--banner:js=const __import_meta_url=require('url').pathToFileURL(__filename).href;" \
|
|
38
|
+
"--define:import.meta.url=__import_meta_url" \
|
|
39
|
+
--outfile="$BUNDLE_DIR/enact-extensions.js"
|
|
40
|
+
|
|
41
|
+
chmod +x "$BUNDLE_DIR/enact-extensions.js"
|
|
42
|
+
|
|
43
|
+
# ── Copy spec/ alongside the bundle ───────────────────────────────────────────
|
|
44
|
+
# schema.ts resolves spec/ as ../../spec/ relative to dist/internal/ — keep that layout.
|
|
45
|
+
rm -rf "$SUPPORT_DIR/spec"
|
|
46
|
+
cp -r "$REPO_ROOT/spec" "$SUPPORT_DIR/spec"
|
|
47
|
+
|
|
48
|
+
# ── Install launcher ───────────────────────────────────────────────────────────
|
|
49
|
+
if [ "$(id -u)" = "0" ]; then
|
|
50
|
+
INSTALL_DIR="/usr/local/bin"
|
|
51
|
+
else
|
|
52
|
+
INSTALL_DIR="$HOME/.local/bin"
|
|
53
|
+
mkdir -p "$INSTALL_DIR"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
LAUNCHER="$INSTALL_DIR/enact-extensions"
|
|
57
|
+
{
|
|
58
|
+
echo '#!/usr/bin/env bash'
|
|
59
|
+
echo "exec node \"$BUNDLE_DIR/enact-extensions.js\" \"\$@\""
|
|
60
|
+
} > "$LAUNCHER"
|
|
61
|
+
chmod +x "$LAUNCHER"
|
|
62
|
+
|
|
63
|
+
echo ""
|
|
64
|
+
echo "installed: $LAUNCHER"
|
|
65
|
+
|
|
66
|
+
if [ "$(id -u)" != "0" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
|
67
|
+
echo ""
|
|
68
|
+
echo " Add to your shell profile to use the command:"
|
|
69
|
+
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
|
|
70
|
+
fi
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
installPluginBundle,
|
|
3
|
+
installClaudePluginBundle,
|
|
4
|
+
installCursorPluginBundle,
|
|
5
|
+
installCodexPluginBundle,
|
|
6
|
+
defaultEnactHome,
|
|
7
|
+
} from "../../dist/index.js";
|
|
8
|
+
|
|
9
|
+
export function runInstall(pluginRoot, options = {}) {
|
|
10
|
+
const platform = options.platform ?? "codex";
|
|
11
|
+
|
|
12
|
+
if (platform === "claude") {
|
|
13
|
+
const result = installClaudePluginBundle({
|
|
14
|
+
pluginRoot,
|
|
15
|
+
claudeHome: options.claudeHome,
|
|
16
|
+
marketplaceName: options.marketplaceName,
|
|
17
|
+
sync: options.sync,
|
|
18
|
+
});
|
|
19
|
+
console.log(`installed ${result.name} -> ${result.installedPluginPath}`);
|
|
20
|
+
console.log(`registered marketplace ${result.marketplaceName} -> ${result.marketplaceDir}`);
|
|
21
|
+
for (const path of result.refreshedPaths) {
|
|
22
|
+
console.log(`refreshed ${path}`);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (platform === "cursor") {
|
|
28
|
+
const result = installCursorPluginBundle({
|
|
29
|
+
pluginRoot,
|
|
30
|
+
cursorHome: options.cursorHome,
|
|
31
|
+
sync: options.sync,
|
|
32
|
+
});
|
|
33
|
+
console.log(`installed ${result.name} -> ${result.installedPluginPath}`);
|
|
34
|
+
for (const path of result.refreshedPaths) {
|
|
35
|
+
console.log(`refreshed ${path}`);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (platform === "enact") {
|
|
41
|
+
const result = installCodexPluginBundle({
|
|
42
|
+
pluginRoot,
|
|
43
|
+
codexHome: options.enactHome ?? defaultEnactHome(),
|
|
44
|
+
marketplaceName: options.marketplaceName,
|
|
45
|
+
enable: options.enable,
|
|
46
|
+
sync: options.sync,
|
|
47
|
+
});
|
|
48
|
+
console.log(`installed ${result.pluginConfigKey} -> ${result.configPath}`);
|
|
49
|
+
console.log(`wrote ${result.marketplacePath}`);
|
|
50
|
+
for (const path of result.refreshedPaths) {
|
|
51
|
+
console.log(`refreshed ${path}`);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// codex (default) or explicit --platform codex
|
|
57
|
+
const result = installPluginBundle({ pluginRoot, cwd: options.cwd ?? pluginRoot, ...options });
|
|
58
|
+
for (const install of result.results) {
|
|
59
|
+
console.log(`installed ${install.pluginConfigKey} -> ${install.configPath}`);
|
|
60
|
+
console.log(`wrote ${install.marketplacePath}`);
|
|
61
|
+
for (const path of install.refreshedPaths) {
|
|
62
|
+
console.log(`refreshed ${path}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { syncPlatformManifests, createEnactManifest } from "../../dist/index.js";
|
|
4
|
+
|
|
5
|
+
export function runSync(pluginRoot, { name } = {}) {
|
|
6
|
+
const enactPath = join(pluginRoot, ".agents/plugin.json");
|
|
7
|
+
let enact;
|
|
8
|
+
|
|
9
|
+
if (existsSync(enactPath)) {
|
|
10
|
+
enact = JSON.parse(readFileSync(enactPath, "utf8"));
|
|
11
|
+
} else if (!name) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`No .agents/plugin.json in ${pluginRoot}. Pass --name <kebab-name> to create one.`,
|
|
14
|
+
);
|
|
15
|
+
} else {
|
|
16
|
+
enact = createEnactManifest({ name });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const results = syncPlatformManifests(pluginRoot, enact);
|
|
20
|
+
for (const r of results) {
|
|
21
|
+
console.log(`${r.written ? "wrote" : "skip"} ${r.manifestPath}`);
|
|
22
|
+
}
|
|
23
|
+
return results;
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validatePluginBundle,
|
|
3
|
+
checkComponentPaths,
|
|
4
|
+
checkPluginBundleComponentPaths,
|
|
5
|
+
readManifestFile,
|
|
6
|
+
} from "../../dist/index.js";
|
|
7
|
+
|
|
8
|
+
export function runValidate(pluginRoot, platforms) {
|
|
9
|
+
const report = validatePluginBundle(pluginRoot, platforms);
|
|
10
|
+
let warnings = [];
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const enact = readManifestFile(pluginRoot, "enact");
|
|
14
|
+
warnings = checkComponentPaths(pluginRoot, enact);
|
|
15
|
+
} catch {
|
|
16
|
+
// enact manifest optional for partial bundles
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
for (const result of report.results) {
|
|
20
|
+
const status = result.ok ? "ok" : "FAIL";
|
|
21
|
+
console.log(`[${status}] ${result.platform} (${pluginRoot})`);
|
|
22
|
+
for (const err of result.errors) {
|
|
23
|
+
console.log(` - ${err}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const warn of warnings) {
|
|
28
|
+
console.log(`[warn] ${warn}`);
|
|
29
|
+
}
|
|
30
|
+
const componentIssues = checkPluginBundleComponentPaths(pluginRoot, platforms);
|
|
31
|
+
for (const issue of componentIssues) {
|
|
32
|
+
console.log(`[FAIL] ${issue}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return report.ok && componentIssues.length === 0;
|
|
36
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env perl
|
|
2
|
+
use strict;
|
|
3
|
+
use warnings;
|
|
4
|
+
|
|
5
|
+
my @files = @ARGV;
|
|
6
|
+
for my $file (@files) {
|
|
7
|
+
open my $fh, '<', $file or die "Cannot read $file: $!";
|
|
8
|
+
my $content = do { local $/; <$fh> };
|
|
9
|
+
close $fh;
|
|
10
|
+
|
|
11
|
+
my $orig = $content;
|
|
12
|
+
|
|
13
|
+
$content =~ s|\.\./enact-operator|../enact-operator|g;
|
|
14
|
+
$content =~ s|enact-operator|enact-operator|g;
|
|
15
|
+
$content =~ s|\@enact/operator|\@enact/operator|g;
|
|
16
|
+
$content =~ s|validate:operator|validate:operator|g;
|
|
17
|
+
$content =~ s|sync:operator|sync:operator|g;
|
|
18
|
+
$content =~ s|\$enact-operator:|\$enact-operator:|g;
|
|
19
|
+
$content =~ s|enact-operator-tasks-|enact-operator-tasks-|g;
|
|
20
|
+
$content =~ s|enact-operator-factory-|enact-operator-factory-|g;
|
|
21
|
+
$content =~ s|enact-operator-setup-|enact-operator-setup-|g;
|
|
22
|
+
$content =~ s|enact-operator-doctor-|enact-operator-doctor-|g;
|
|
23
|
+
$content =~ s|enact-operator-parent-|enact-operator-parent-|g;
|
|
24
|
+
$content =~ s|enact-operator-hook-cont-|enact-operator-hook-cont-|g;
|
|
25
|
+
$content =~ s|enact-operator-followup-|enact-operator-followup-|g;
|
|
26
|
+
$content =~ s|enact-operator-provider-boundary-|enact-operator-provider-boundary-|g;
|
|
27
|
+
$content =~ s|enact-operator-skill-act-|enact-operator-skill-act-|g;
|
|
28
|
+
$content =~ s|enact-operator-authority-|enact-operator-authority-|g;
|
|
29
|
+
$content =~ s|enact-operator-reconcile-integration-|enact-operator-reconcile-integration-|g;
|
|
30
|
+
$content =~ s|enact-operator-subagent-runtime-|enact-operator-subagent-runtime-|g;
|
|
31
|
+
$content =~ s|basename\(root\) === 'enact-operator'|basename(root) === 'enact-operator'|g;
|
|
32
|
+
$content =~ s|basename\(root\) !== 'enact-operator'|basename(root) !== 'enact-operator'|g;
|
|
33
|
+
$content =~ s|/some/enact-operator|/some/enact-operator|g;
|
|
34
|
+
$content =~ s|join\(base, 'enact-operator'\)|join(base, 'enact-operator')|g;
|
|
35
|
+
$content =~ s|the enact-operator repo|the enact-operator repo|g;
|
|
36
|
+
$content =~ s|is enact-operator|is enact-operator|g;
|
|
37
|
+
$content =~ s|actor: 'enact-operator'|actor: 'enact-operator'|g;
|
|
38
|
+
$content =~ s|'enact-operator',|'enact-operator',|g;
|
|
39
|
+
$content =~ s|"enact-operator"|"enact-operator"|g;
|
|
40
|
+
$content =~ s|enact-operator doctor|enact-operator doctor|g;
|
|
41
|
+
$content =~ s|enact-operator hud|enact-operator hud|g;
|
|
42
|
+
$content =~ s|enact-operator session|enact-operator session|g;
|
|
43
|
+
$content =~ s|enact-operator CLI|enact-operator CLI|g;
|
|
44
|
+
$content =~ s|enact-operator setup|enact-operator setup|g;
|
|
45
|
+
$content =~ s|enact-operator build|enact-operator build|g;
|
|
46
|
+
$content =~ s|enact-operator dir|enact-operator dir|g;
|
|
47
|
+
$content =~ s|enact-operator not found|enact-operator not found|g;
|
|
48
|
+
$content =~ s|enact-operator repo|enact-operator repo|g;
|
|
49
|
+
$content =~ s|enact-operator even|enact-operator even|g;
|
|
50
|
+
$content =~ s|/Users/tarunrana/Documents/enact-os/enact-operator/|/Users/tarunrana/Documents/enact-os/enact-operator/|g;
|
|
51
|
+
$content =~ s|enact-operator |enact-operator |g;
|
|
52
|
+
$content =~ s|enact-operator`|enact-operator`|g;
|
|
53
|
+
$content =~ s|enact-operator/|enact-operator/|g;
|
|
54
|
+
$content =~ s|enact-operator@|enact-operator@|g;
|
|
55
|
+
$content =~ s|enact-operator-|enact-operator-|g;
|
|
56
|
+
$content =~ s|_git/enact-operator|_git/enact-operator|g;
|
|
57
|
+
$content =~ s|enact-operator MCP|enact-operator MCP|g;
|
|
58
|
+
$content =~ s|the enact-operator plugin|the enact-operator plugin|g;
|
|
59
|
+
$content =~ s|enact-operator plugin|enact-operator plugin|g;
|
|
60
|
+
|
|
61
|
+
next if $content eq $orig;
|
|
62
|
+
|
|
63
|
+
open my $out, '>', $file or die "Cannot write $file: $!";
|
|
64
|
+
print {$out} $content;
|
|
65
|
+
close $out;
|
|
66
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Wire enact-context setup into the enact-extensions workspace.
|
|
3
|
+
# Validates the plugin bundle (catalog) then runs consolidated agent setup.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
|
+
REPO_ROOT="$(cd "$ROOT/.." && pwd)"
|
|
8
|
+
|
|
9
|
+
if ! command -v enact-context >/dev/null 2>&1; then
|
|
10
|
+
if [[ -x "$REPO_ROOT/enact-context/rust/target/release/enact-context" ]]; then
|
|
11
|
+
export PATH="$REPO_ROOT/enact-context/rust/target/release:$PATH"
|
|
12
|
+
else
|
|
13
|
+
echo "enact-context binary not found on PATH" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
cd "$ROOT"
|
|
19
|
+
npm run validate:catalog
|
|
20
|
+
|
|
21
|
+
echo ""
|
|
22
|
+
echo "Running enact-context setup (claude, codex, opencode, enact, zed)..."
|
|
23
|
+
enact-context setup "$@"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Prefer: enact-extensions sync [path]
|
|
4
|
+
* Defaults to cwd when path is omitted.
|
|
5
|
+
*/
|
|
6
|
+
import { resolvePluginRoot } from "./lib/resolve-plugin-root.mjs";
|
|
7
|
+
import { runSync } from "./lib/run-sync.mjs";
|
|
8
|
+
|
|
9
|
+
function parseName(argv) {
|
|
10
|
+
for (let i = 0; i < argv.length; i++) {
|
|
11
|
+
if (argv[i] === "--name" && argv[i + 1]) {
|
|
12
|
+
return argv[i + 1];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
16
|
+
return positional[1];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const rest = process.argv.slice(2);
|
|
20
|
+
const pathArg = rest[0]?.startsWith("-") ? undefined : rest[0];
|
|
21
|
+
const name = parseName(rest);
|
|
22
|
+
|
|
23
|
+
runSync(resolvePluginRoot(pathArg), { name });
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
3
|
+
import { join, dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const workspaceRoot = resolve(__dirname, '..');
|
|
9
|
+
const catalogDir = join(workspaceRoot, 'catalog');
|
|
10
|
+
const repoRoot = resolve(workspaceRoot, '..');
|
|
11
|
+
|
|
12
|
+
const entries = readdirSync(catalogDir)
|
|
13
|
+
.filter((f) => f.endsWith('.json'))
|
|
14
|
+
.map((f) => JSON.parse(readFileSync(join(catalogDir, f), 'utf8')));
|
|
15
|
+
|
|
16
|
+
let allPassed = true;
|
|
17
|
+
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
const bundleAbsPath = resolve(repoRoot, entry.repo, entry.bundlePath);
|
|
20
|
+
if (!existsSync(bundleAbsPath)) {
|
|
21
|
+
console.error(`[catalog] FAIL ${entry.id}: bundle path not found: ${bundleAbsPath}`);
|
|
22
|
+
allPassed = false;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
execFileSync('enact-extensions', ['validate', bundleAbsPath], { stdio: 'inherit' });
|
|
27
|
+
console.log(`[catalog] OK ${entry.id}: ${bundleAbsPath}`);
|
|
28
|
+
} catch {
|
|
29
|
+
console.error(`[catalog] FAIL ${entry.id}: enact-extensions validate failed`);
|
|
30
|
+
allPassed = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!allPassed) {
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
console.log(`\n[catalog] All ${entries.length} bundle(s) passed validation.`);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Prefer: enact-extensions validate [path]
|
|
4
|
+
* Defaults to cwd when path is omitted.
|
|
5
|
+
*/
|
|
6
|
+
import { resolvePluginRoot } from "./lib/resolve-plugin-root.mjs";
|
|
7
|
+
import { runValidate } from "./lib/run-validate.mjs";
|
|
8
|
+
|
|
9
|
+
const ok = runValidate(resolvePluginRoot(process.argv[2]));
|
|
10
|
+
process.exit(ok ? 0 : 1);
|
package/spec/claude.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "claude-plugin.json",
|
|
4
|
+
"title": "Claude Code Plugin Manifest",
|
|
5
|
+
"description": "Validates .claude-plugin/plugin.json for Claude Code plugins. Components (skills/, agents/, hooks/, .mcp.json, etc.) are auto-discovered from default directories; path overrides are optional.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["name"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"name": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Unique plugin identifier and skill namespace prefix (skills become /name:skill-name).",
|
|
13
|
+
"pattern": "^[a-z][a-z0-9-]*$"
|
|
14
|
+
},
|
|
15
|
+
"version": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Semantic version. If omitted, the git commit SHA is used for version tracking.",
|
|
18
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
19
|
+
},
|
|
20
|
+
"description": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Shown in the plugin manager when browsing or installing plugins."
|
|
23
|
+
},
|
|
24
|
+
"author": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": ["name"],
|
|
27
|
+
"additionalProperties": false,
|
|
28
|
+
"properties": {
|
|
29
|
+
"name": { "type": "string" },
|
|
30
|
+
"email": { "type": "string", "format": "email" },
|
|
31
|
+
"url": { "type": "string", "format": "uri" }
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"homepage": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"format": "uri",
|
|
37
|
+
"description": "Plugin or developer homepage URL."
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "URL to the source repository."
|
|
42
|
+
},
|
|
43
|
+
"license": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "SPDX license identifier."
|
|
46
|
+
},
|
|
47
|
+
"keywords": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": { "type": "string" },
|
|
50
|
+
"uniqueItems": true,
|
|
51
|
+
"description": "Discovery tags shown in the plugin manager."
|
|
52
|
+
},
|
|
53
|
+
"skills": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "Custom path to skills directory (default: ./skills/).",
|
|
56
|
+
"default": "./skills/"
|
|
57
|
+
},
|
|
58
|
+
"commands": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Custom path to commands directory (flat Markdown files; prefer skills/ for new plugins).",
|
|
61
|
+
"default": "./commands/"
|
|
62
|
+
},
|
|
63
|
+
"agents": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "Custom path to agents directory.",
|
|
66
|
+
"default": "./agents/"
|
|
67
|
+
},
|
|
68
|
+
"hooks": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "Custom path to hooks configuration file.",
|
|
71
|
+
"default": "./hooks/hooks.json"
|
|
72
|
+
},
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"description": "Custom path to .mcp.json MCP server configuration.",
|
|
76
|
+
"default": "./.mcp.json"
|
|
77
|
+
},
|
|
78
|
+
"lsp": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"description": "Custom path to .lsp.json LSP server configuration.",
|
|
81
|
+
"default": "./.lsp.json"
|
|
82
|
+
},
|
|
83
|
+
"monitors": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "Custom path to monitors configuration file.",
|
|
86
|
+
"default": "./monitors/monitors.json"
|
|
87
|
+
},
|
|
88
|
+
"settings": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"description": "Custom path to default settings file applied when the plugin is enabled.",
|
|
91
|
+
"default": "./settings.json"
|
|
92
|
+
},
|
|
93
|
+
"bin": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"description": "Custom path to executables directory added to Bash PATH while the plugin is active.",
|
|
96
|
+
"default": "./bin/"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|