@reeledge/agent-tools 0.1.6 → 0.1.7
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/apps/codex-plugin.js +152 -0
- package/dist/apps/codex.js +24 -1
- package/package.json +1 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeSkillTree } from '../skills.js';
|
|
4
|
+
import { CONNECTOR_NAME } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Self-contained local Codex **app-UI plugin** built from the fetched skills and
|
|
7
|
+
* registered in the user's personal marketplace, so the Reel Edge skills surface
|
|
8
|
+
* in the Codex / ChatGPT app plugins panel (in addition to the loose
|
|
9
|
+
* `~/.codex/skills/` CLI skills + the `[mcp_servers.reel-edge]` connector the
|
|
10
|
+
* codex adapter already writes).
|
|
11
|
+
*
|
|
12
|
+
* The plugin is fully self-contained — no remote source. The exact shapes here
|
|
13
|
+
* were validated against Codex's own plugin validator and installed successfully
|
|
14
|
+
* in the app:
|
|
15
|
+
* - `<homeDir>/plugins/reel-edge/.codex-plugin/plugin.json` (manifest + app-UI `interface`)
|
|
16
|
+
* - `<homeDir>/plugins/reel-edge/skills/<slug>/…` (the fetched skill trees)
|
|
17
|
+
* - `<homeDir>/.agents/plugins/marketplace.json` (a personal marketplace whose
|
|
18
|
+
* `source.path` is the home-relative `./plugins/reel-edge`).
|
|
19
|
+
*
|
|
20
|
+
* The marketplace `source` stays the LITERAL home-relative `./plugins/reel-edge`
|
|
21
|
+
* — Codex resolves it relative to the agents home, so it must NOT be absolutised.
|
|
22
|
+
*/
|
|
23
|
+
/** Plugin `name` (also the marketplace entry name + skills namespace). */
|
|
24
|
+
export const CODEX_PLUGIN_NAME = CONNECTOR_NAME;
|
|
25
|
+
/** Plugin `version` — bump to push skill-content updates to installed users. */
|
|
26
|
+
export const CODEX_PLUGIN_VERSION = '0.1.0';
|
|
27
|
+
/** Absolute plugin directory: `<homeDir>/plugins/reel-edge`. */
|
|
28
|
+
export function codexPluginDir(env) {
|
|
29
|
+
return path.join(env.homeDir, 'plugins', CODEX_PLUGIN_NAME);
|
|
30
|
+
}
|
|
31
|
+
/** Absolute personal-marketplace path: `<homeDir>/.agents/plugins/marketplace.json`. */
|
|
32
|
+
export function codexMarketplacePath(env) {
|
|
33
|
+
return path.join(env.homeDir, '.agents', 'plugins', 'marketplace.json');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `.codex-plugin/plugin.json` body — the exact shape Codex's plugin validator
|
|
37
|
+
* accepts. `skills: "./skills/"` points at the skill trees; `interface` drives
|
|
38
|
+
* the app plugins-panel card. Pretty-printed with a trailing newline.
|
|
39
|
+
*/
|
|
40
|
+
export function codexPluginManifest() {
|
|
41
|
+
const body = {
|
|
42
|
+
name: CODEX_PLUGIN_NAME,
|
|
43
|
+
version: CODEX_PLUGIN_VERSION,
|
|
44
|
+
description: 'Reel Edge read-only data skills: Reel Explorer (Xano database), Inbox Scout (player emails), and HubSpot CRM.',
|
|
45
|
+
author: { name: 'Reel Edge' },
|
|
46
|
+
skills: './skills/',
|
|
47
|
+
interface: {
|
|
48
|
+
displayName: 'Reel Edge',
|
|
49
|
+
shortDescription: 'Reel Edge read-only data skills for Codex.',
|
|
50
|
+
longDescription: 'Adds the Reel Edge domain skills — Reel Explorer (read-only Xano SQL), Inbox Scout (player emails), and HubSpot CRM — used alongside the reel-edge MCP connector.',
|
|
51
|
+
developerName: 'Reel Edge',
|
|
52
|
+
category: 'Productivity',
|
|
53
|
+
capabilities: [],
|
|
54
|
+
defaultPrompt: 'Query the Reel Edge database.',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
return `${JSON.stringify(body, null, 2)}\n`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The reel-edge plugin entry for the personal marketplace. `source.path` is the
|
|
61
|
+
* literal home-relative `./plugins/reel-edge` (Codex resolves it against the
|
|
62
|
+
* agents home); `INSTALLED_BY_DEFAULT` makes it show up installed in the panel.
|
|
63
|
+
*/
|
|
64
|
+
export function codexMarketplaceEntry() {
|
|
65
|
+
return {
|
|
66
|
+
name: CODEX_PLUGIN_NAME,
|
|
67
|
+
source: { source: 'local', path: `./plugins/${CODEX_PLUGIN_NAME}` },
|
|
68
|
+
policy: { installation: 'INSTALLED_BY_DEFAULT', authentication: 'ON_INSTALL' },
|
|
69
|
+
category: 'Productivity',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** The fresh personal-marketplace root wrapping a single plugin entry. */
|
|
73
|
+
function freshMarketplace(entry) {
|
|
74
|
+
return {
|
|
75
|
+
name: 'personal',
|
|
76
|
+
interface: { displayName: 'Personal' },
|
|
77
|
+
plugins: [entry],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Merge the reel-edge plugin entry into an existing `marketplace.json` body (or
|
|
82
|
+
* `undefined`/malformed → a fresh personal-marketplace root). Mirrors
|
|
83
|
+
* `mergeDroidConfig`/`mergeClaudeDesktopConfig`: PRESERVE the user's top-level
|
|
84
|
+
* keys (incl. their marketplace `name`/`interface`) and any OTHER plugins;
|
|
85
|
+
* REPLACE an existing reel-edge entry in `plugins[]` (matched by `name`) or
|
|
86
|
+
* APPEND it. Returns pretty-printed JSON with a trailing newline.
|
|
87
|
+
*/
|
|
88
|
+
export function mergeCodexMarketplace(existing, entry) {
|
|
89
|
+
let root = null;
|
|
90
|
+
if (existing && existing.trim() !== '') {
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(existing);
|
|
93
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
94
|
+
root = parsed;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
root = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (root === null) {
|
|
102
|
+
return `${JSON.stringify(freshMarketplace(entry), null, 2)}\n`;
|
|
103
|
+
}
|
|
104
|
+
const plugins = Array.isArray(root.plugins) ? [...root.plugins] : [];
|
|
105
|
+
const idx = plugins.findIndex((p) => p !== null &&
|
|
106
|
+
typeof p === 'object' &&
|
|
107
|
+
p.name === entry.name);
|
|
108
|
+
if (idx >= 0)
|
|
109
|
+
plugins[idx] = entry;
|
|
110
|
+
else
|
|
111
|
+
plugins.push(entry);
|
|
112
|
+
root.plugins = plugins;
|
|
113
|
+
// Don't clobber a user's marketplace name/interface; only default them when
|
|
114
|
+
// absent so the merged file is still a valid personal marketplace.
|
|
115
|
+
if (root.name === undefined)
|
|
116
|
+
root.name = 'personal';
|
|
117
|
+
if (root.interface === undefined)
|
|
118
|
+
root.interface = { displayName: 'Personal' };
|
|
119
|
+
return `${JSON.stringify(root, null, 2)}\n`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Build the Codex plugin at `dir`:
|
|
123
|
+
* dir/.codex-plugin/plugin.json
|
|
124
|
+
* dir/skills/<slug>/SKILL.md (+ references/**)
|
|
125
|
+
* Copies the fetched `SkillFile[]` verbatim (frontmatter carried through) via
|
|
126
|
+
* {@link writeSkillTree}, so the same path-traversal guards apply. Returns the
|
|
127
|
+
* written absolute file paths (the manifest first, then the skill files).
|
|
128
|
+
*/
|
|
129
|
+
export function buildCodexPlugin(dir, skills) {
|
|
130
|
+
const metaDir = path.join(dir, '.codex-plugin');
|
|
131
|
+
fs.mkdirSync(metaDir, { recursive: true });
|
|
132
|
+
const pluginPath = path.join(metaDir, 'plugin.json');
|
|
133
|
+
fs.writeFileSync(pluginPath, codexPluginManifest(), 'utf8');
|
|
134
|
+
const written = [pluginPath];
|
|
135
|
+
const skillsRoot = path.join(dir, 'skills');
|
|
136
|
+
for (const s of skills) {
|
|
137
|
+
written.push(...writeSkillTree(skillsRoot, s.slug, s.files));
|
|
138
|
+
}
|
|
139
|
+
return written;
|
|
140
|
+
}
|
|
141
|
+
/** `--dry-run` lines describing the plugin build + the marketplace merge. */
|
|
142
|
+
export function codexPluginDryRunLines(dir, marketplacePath, skills) {
|
|
143
|
+
const lines = [`would write ${path.join(dir, '.codex-plugin', 'plugin.json')}`];
|
|
144
|
+
const skillsRoot = path.join(dir, 'skills');
|
|
145
|
+
for (const s of skills) {
|
|
146
|
+
for (const file of s.files) {
|
|
147
|
+
lines.push(`would write ${path.join(skillsRoot, s.slug, file.path)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
lines.push(`would merge ${marketplacePath} with plugin entry "${CODEX_PLUGIN_NAME}" (INSTALLED_BY_DEFAULT)`);
|
|
151
|
+
return lines;
|
|
152
|
+
}
|
package/dist/apps/codex.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { mcpEndpoint } from '../mcp.js';
|
|
4
4
|
import { writeSkillTree } from '../skills.js';
|
|
5
|
+
import { buildCodexPlugin, codexMarketplaceEntry, codexMarketplacePath, codexPluginDir, codexPluginDryRunLines, mergeCodexMarketplace, } from './codex-plugin.js';
|
|
5
6
|
import { APP_LABELS, CONNECTOR_NAME, } from './types.js';
|
|
6
7
|
/**
|
|
7
8
|
* OpenAI Codex CLI adapter. Codex reads `~/.codex/config.toml` and registers
|
|
@@ -24,6 +25,12 @@ import { APP_LABELS, CONNECTOR_NAME, } from './types.js';
|
|
|
24
25
|
*
|
|
25
26
|
* MCP config verified against https://developers.openai.com/codex/mcp and
|
|
26
27
|
* https://developers.openai.com/codex/config-reference (Jun 2026).
|
|
28
|
+
*
|
|
29
|
+
* On top of the loose CLI skills + the MCP connector, this adapter also builds a
|
|
30
|
+
* Codex app-UI **plugin** (see `./codex-plugin.ts`) — a self-contained plugin
|
|
31
|
+
* under `<home>/plugins/reel-edge/` registered in the personal marketplace
|
|
32
|
+
* (`<home>/.agents/plugins/marketplace.json`) so the skills show in the
|
|
33
|
+
* Codex/ChatGPT app plugins panel.
|
|
27
34
|
*/
|
|
28
35
|
/** Absolute path to the Codex skills tree (`~/.codex/skills`). */
|
|
29
36
|
function codexSkillsDir(env) {
|
|
@@ -106,15 +113,29 @@ export function makeCodexAdapter(env) {
|
|
|
106
113
|
label: APP_LABELS.codex,
|
|
107
114
|
skillsDir,
|
|
108
115
|
writeSkills(skills) {
|
|
109
|
-
|
|
116
|
+
// 1. Loose CLI skills under ~/.codex/skills/<slug>/ (existing behaviour).
|
|
117
|
+
const cliSkills = skills.flatMap((s) => writeSkillTree(skillsDir(), s.slug, s.files));
|
|
118
|
+
// 2. The Codex app-UI plugin under <home>/plugins/reel-edge/ so the skills
|
|
119
|
+
// also surface in the Codex/ChatGPT app plugins panel.
|
|
120
|
+
const plugin = buildCodexPlugin(codexPluginDir(env), skills);
|
|
121
|
+
return [...cliSkills, ...plugin];
|
|
110
122
|
},
|
|
111
123
|
async configureMcp(serverUrl, token) {
|
|
124
|
+
// 1. MCP connector: ~/.codex/config.toml [mcp_servers.reel-edge] (existing).
|
|
112
125
|
const block = codexConfigBlock(CONNECTOR_NAME, mcpEndpoint(serverUrl), token);
|
|
113
126
|
const cfgPath = codexConfigPath(env);
|
|
114
127
|
const existing = fs.existsSync(cfgPath) ? fs.readFileSync(cfgPath, 'utf8') : '';
|
|
115
128
|
const merged = mergeCodexToml(existing, CONNECTOR_NAME, block);
|
|
116
129
|
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
117
130
|
fs.writeFileSync(cfgPath, merged, 'utf8');
|
|
131
|
+
// 2. Register the app-UI plugin in the personal marketplace so it installs
|
|
132
|
+
// in the Codex app plugins panel.
|
|
133
|
+
const mpPath = codexMarketplacePath(env);
|
|
134
|
+
const existingMp = fs.existsSync(mpPath) ? fs.readFileSync(mpPath, 'utf8') : undefined;
|
|
135
|
+
const mergedMp = mergeCodexMarketplace(existingMp, codexMarketplaceEntry());
|
|
136
|
+
fs.mkdirSync(path.dirname(mpPath), { recursive: true });
|
|
137
|
+
fs.writeFileSync(mpPath, mergedMp, 'utf8');
|
|
138
|
+
env.log?.(`Registered Codex plugin (${CONNECTOR_NAME}) — installs in the Codex app plugins panel`);
|
|
118
139
|
},
|
|
119
140
|
dryRunPlan(serverUrl, token, skills) {
|
|
120
141
|
const lines = [];
|
|
@@ -127,6 +148,8 @@ export function makeCodexAdapter(env) {
|
|
|
127
148
|
for (const l of codexConfigBlock(CONNECTOR_NAME, mcpEndpoint(serverUrl), token).trimEnd().split('\n')) {
|
|
128
149
|
lines.push(` ${l}`);
|
|
129
150
|
}
|
|
151
|
+
// The Codex app-UI plugin build + the personal-marketplace registration.
|
|
152
|
+
lines.push(...codexPluginDryRunLines(codexPluginDir(env), codexMarketplacePath(env), skills));
|
|
130
153
|
return lines;
|
|
131
154
|
},
|
|
132
155
|
};
|
package/package.json
CHANGED