@massa-ai/codex-plugin 1.7.1 → 1.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/.codex-plugin/plugin.json +1 -1
- package/hooks/massa-ai-hook +56 -1
- package/install.sh +83 -8
- package/package.json +1 -1
package/hooks/massa-ai-hook
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
import { spawnSync } from "child_process";
|
|
31
31
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, fstatSync } from "fs";
|
|
32
|
+
import { homedir } from "os";
|
|
32
33
|
import path from "path";
|
|
33
34
|
|
|
34
35
|
// ── Event type mapping ──────────────────────────────────────────────────────
|
|
@@ -132,6 +133,60 @@ export function readStdin(): string {
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
// ── API key resolution (SEC-06) ─────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Path to the runtime config file, resolved the same way
|
|
140
|
+
* `packages/shared/src/config/xdg.ts` resolves it: `XDG_CONFIG_HOME` when set
|
|
141
|
+
* and non-blank, else `~/.config`.
|
|
142
|
+
*
|
|
143
|
+
* Duplicated rather than imported on purpose. This binary's entire dependency
|
|
144
|
+
* surface is `child_process`/`fs`/`os`/`path`; pulling in `@massa-ai/shared`
|
|
145
|
+
* would drag the config loader, its Prisma-adjacent transitive graph, and its
|
|
146
|
+
* startup cost into a process that runs on every single tool call and is
|
|
147
|
+
* budgeted in milliseconds. The duplication is nine lines and is pinned by
|
|
148
|
+
* tests on both sides.
|
|
149
|
+
*/
|
|
150
|
+
export function getHookConfigPath(): string {
|
|
151
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
152
|
+
const base = xdg && xdg.trim() ? xdg : path.join(homedir(), ".config");
|
|
153
|
+
return path.join(base, "massa-ai", "config.json");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolve the Tools API key: `MASSA_AI_API_KEY` → `config.json`
|
|
158
|
+
* `security.apiKey` → none. Returns "" when no key is available.
|
|
159
|
+
*
|
|
160
|
+
* SEC-01 auto-provisions the key into config.json on first API start, and this
|
|
161
|
+
* binary never runs the `env.ts` seeding that puts it in the environment for
|
|
162
|
+
* everything else — so without reading the file it would send unauthenticated
|
|
163
|
+
* POSTs forever. Because the hook silent-degrades by contract, that failure
|
|
164
|
+
* has no symptom: observation capture just stops.
|
|
165
|
+
*
|
|
166
|
+
* Whitespace-only values count as unset, matching `usable()` in
|
|
167
|
+
* `packages/shared/src/config/api-key.ts`. Every failure mode (missing file,
|
|
168
|
+
* unreadable, malformed JSON, wrong shape) degrades to "" rather than throwing:
|
|
169
|
+
* a hook must never block the agent.
|
|
170
|
+
*
|
|
171
|
+
* Deliberately not memoised. A hook process handles exactly one event and
|
|
172
|
+
* exits, so there is no second call to amortise, and a module-level cache would
|
|
173
|
+
* make the resolution untestable without a reset seam.
|
|
174
|
+
*/
|
|
175
|
+
export function resolveHookApiKey(): string {
|
|
176
|
+
const fromEnv = process.env.MASSA_AI_API_KEY;
|
|
177
|
+
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(readFileSync(getHookConfigPath(), "utf8"));
|
|
181
|
+
const stored = parsed?.security?.apiKey;
|
|
182
|
+
if (typeof stored === "string" && stored.trim()) return stored.trim();
|
|
183
|
+
} catch {
|
|
184
|
+
// Missing, unreadable, or malformed config → no key (silent-degrade).
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return "";
|
|
188
|
+
}
|
|
189
|
+
|
|
135
190
|
// ── POST helper ─────────────────────────────────────────────────────────────
|
|
136
191
|
|
|
137
192
|
export function postObservation(
|
|
@@ -149,7 +204,7 @@ export function postObservation(
|
|
|
149
204
|
const headers: Record<string, string> = {
|
|
150
205
|
"Content-Type": "application/json",
|
|
151
206
|
};
|
|
152
|
-
const apiKey =
|
|
207
|
+
const apiKey = resolveHookApiKey();
|
|
153
208
|
if (apiKey) {
|
|
154
209
|
headers["x-api-key"] = apiKey;
|
|
155
210
|
}
|
package/install.sh
CHANGED
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
# MCP registration is delegated to scripts/install-agents.sh, the single writer
|
|
11
11
|
# of host MCP config. This installer no longer ships a plugin-local .mcp.json.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
# `codex plugin add massa-ai@massa-ai`
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
13
|
+
# This script now drives BOTH halves. `codex plugin marketplace add` +
|
|
14
|
+
# `codex plugin add massa-ai@massa-ai` supply the skills and the /plugins entry;
|
|
15
|
+
# they CANNOT supply hooks, because a Codex plugin manifest has no hooks key
|
|
16
|
+
# (none of the 203 manifests shipped by the bundled/curated/runtime marketplaces
|
|
17
|
+
# declares one). Hooks reach Codex only through ~/.codex/hooks.json, which this
|
|
18
|
+
# script writes. The two halves are therefore additive, not exclusive — unlike
|
|
19
|
+
# apps/claude-plugin/install.sh, whose plugin does ship hooks/hooks.json and so
|
|
20
|
+
# has to pick one route or double-fire.
|
|
21
|
+
#
|
|
22
|
+
# Skills are likewise not duplicated: the marketplace route populates Codex's
|
|
23
|
+
# own plugin skill cache, while the harness skills go to $CODEX_DIR/skills under
|
|
24
|
+
# the skillsOwner coordination described further down. Separate mechanisms.
|
|
20
25
|
#
|
|
21
26
|
# Idempotent: re-running is a no-op when owned entries already present.
|
|
22
27
|
# Uninstall removes only ownership-marked entries + the plugin directory.
|
|
@@ -33,6 +38,11 @@ set -euo pipefail
|
|
|
33
38
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
34
39
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
35
40
|
CLAUDE_PLUGIN_BIN="$REPO_ROOT/apps/claude-plugin/hooks/massa-ai-hook.ts"
|
|
41
|
+
# shellcheck source=scripts/lib/installer-shared.sh
|
|
42
|
+
source "$REPO_ROOT/scripts/lib/installer-shared.sh"
|
|
43
|
+
# Marketplace root to register; install-harness.sh resolves it once per run
|
|
44
|
+
# (--plugin-source local|copy|auto). Direct invocation uses this checkout.
|
|
45
|
+
PLUGIN_SOURCE_ROOT="${MASSA_AI_PLUGIN_SOURCE_ROOT:-$REPO_ROOT}"
|
|
36
46
|
|
|
37
47
|
SCOPE="user"
|
|
38
48
|
UNINSTALL=0
|
|
@@ -335,9 +345,59 @@ try {
|
|
|
335
345
|
NODE
|
|
336
346
|
}
|
|
337
347
|
|
|
348
|
+
# ── Plugin-registry registration (delegated to the codex CLI) ───────────────
|
|
349
|
+
# The CLI owns config.toml's [marketplaces.*] / [plugins."x@y"] tables and the
|
|
350
|
+
# plugins/cache layout, so it is the only supported way to reach /plugins.
|
|
351
|
+
# Failure is never fatal: without this the rest of the install (hooks, agents,
|
|
352
|
+
# MCP) is still fully functional, it just does not show in the plugins screen.
|
|
353
|
+
CODEX_MARKETPLACE="massa-ai"
|
|
354
|
+
CODEX_PLUGIN_ID="massa-ai@massa-ai"
|
|
355
|
+
|
|
356
|
+
# Where the CLI is allowed to write. Pinned explicitly, and not left to the
|
|
357
|
+
# CLI's own resolution, for two reasons:
|
|
358
|
+
#
|
|
359
|
+
# 1. The codex CLI resolves its config root from $CODEX_HOME before falling
|
|
360
|
+
# back to $HOME/.codex — and the Codex app exports CODEX_HOME into every
|
|
361
|
+
# shell it spawns. This script resolves CODEX_DIR from $HOME alone, so an
|
|
362
|
+
# install running against an overridden $HOME (tests, --target) would write
|
|
363
|
+
# its files into the sandbox while registering the marketplace into the
|
|
364
|
+
# developer's real ~/.codex. That is not hypothetical: it silently removed
|
|
365
|
+
# a live [marketplaces.massa-ai] table during development of this change.
|
|
366
|
+
# 2. Codex records plugin installs at user scope, so this stays $HOME-derived
|
|
367
|
+
# even for --project, mirroring PLUGIN_REGISTRY in apps/claude-plugin.
|
|
368
|
+
#
|
|
369
|
+
# Note the pre-existing divergence left untouched here: the file-copy half of
|
|
370
|
+
# this installer still ignores CODEX_HOME entirely. Making both halves honour
|
|
371
|
+
# it is a separate change with a much wider blast radius.
|
|
372
|
+
CODEX_CLI_HOME="$HOME/.codex"
|
|
373
|
+
|
|
374
|
+
register_codex_plugin() {
|
|
375
|
+
# Explicit opt-out; see the same knob in apps/claude-plugin/install.sh.
|
|
376
|
+
[[ "${MASSA_AI_SKIP_PLUGIN_REGISTRY:-0}" == "1" ]] && return 1
|
|
377
|
+
installer_host_cli_supports codex plugin marketplace || return 1
|
|
378
|
+
if [[ ! -f "$PLUGIN_SOURCE_ROOT/.agents/plugins/marketplace.json" ]]; then
|
|
379
|
+
vecho " – no marketplace manifest under $PLUGIN_SOURCE_ROOT — skipping /plugins registration"
|
|
380
|
+
return 1
|
|
381
|
+
fi
|
|
382
|
+
# Idempotent: a second run reports "already added" and leaves config.toml
|
|
383
|
+
# byte-identical.
|
|
384
|
+
CODEX_HOME="$CODEX_CLI_HOME" codex plugin marketplace add "$PLUGIN_SOURCE_ROOT" </dev/null >/dev/null 2>&1 || return 1
|
|
385
|
+
CODEX_HOME="$CODEX_CLI_HOME" codex plugin add "$CODEX_PLUGIN_ID" </dev/null >/dev/null 2>&1 || return 1
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
unregister_codex_plugin() {
|
|
389
|
+
installer_host_cli_supports codex plugin marketplace || return 0
|
|
390
|
+
CODEX_HOME="$CODEX_CLI_HOME" codex plugin remove "$CODEX_PLUGIN_ID" </dev/null >/dev/null 2>&1 || true
|
|
391
|
+
# Order matters: the marketplace entry must go last, because removing it
|
|
392
|
+
# first leaves `codex plugin list` unable to resolve the plugin it still has
|
|
393
|
+
# registered — the same cache-miss failure a deleted checkout produces.
|
|
394
|
+
CODEX_HOME="$CODEX_CLI_HOME" codex plugin marketplace remove "$CODEX_MARKETPLACE" </dev/null >/dev/null 2>&1 || true
|
|
395
|
+
}
|
|
396
|
+
|
|
338
397
|
# ── Uninstall ───────────────────────────────────────────────────────────────
|
|
339
398
|
if [[ "$UNINSTALL" -eq 1 ]]; then
|
|
340
399
|
echo "Uninstalling massa-ai Codex plugin (scope: $SCOPE)..."
|
|
400
|
+
unregister_codex_plugin
|
|
341
401
|
uninstall_bundled_skills
|
|
342
402
|
# Remove owned hook entries (preserves user hooks)
|
|
343
403
|
if [[ -f "$HOOKS_JSON" ]]; then
|
|
@@ -463,9 +523,24 @@ else
|
|
|
463
523
|
echo " ⚠ scripts/install-agents.sh not found — register MCP with: bash scripts/install-agents.sh --agent codex --yes" >&2
|
|
464
524
|
fi
|
|
465
525
|
|
|
526
|
+
# ── Plugin-registry registration ─────────────────────────────────────────────
|
|
527
|
+
# Runs after the file writes above because it is additive here: hooks and agent
|
|
528
|
+
# TOMLs stay owned by this script either way.
|
|
529
|
+
CODEX_PLUGIN_ROUTE=0
|
|
530
|
+
if register_codex_plugin; then
|
|
531
|
+
CODEX_PLUGIN_ROUTE=1
|
|
532
|
+
vecho " + registered ${CODEX_PLUGIN_ID} (marketplace root: ${PLUGIN_SOURCE_ROOT})"
|
|
533
|
+
fi
|
|
534
|
+
|
|
466
535
|
# Summary line in quiet mode
|
|
467
536
|
if [ "${MASSA_AI_VERBOSE:-0}" != "1" ]; then
|
|
468
|
-
|
|
537
|
+
if [[ "$CODEX_PLUGIN_ROUTE" -eq 1 ]]; then
|
|
538
|
+
ok "codex plugin installed (${skill_count} skills, ${specialist_count} specialists, 6 hooks) — shows in /plugins"
|
|
539
|
+
else
|
|
540
|
+
ok "codex plugin installed (${skill_count} skills, ${specialist_count} specialists, 6 hooks)"
|
|
541
|
+
warn "codex CLI unavailable — not registered in /plugins. Register it with:"
|
|
542
|
+
warn " codex plugin marketplace add \"$PLUGIN_SOURCE_ROOT\" && codex plugin add $CODEX_PLUGIN_ID"
|
|
543
|
+
fi
|
|
469
544
|
else
|
|
470
545
|
vecho ""
|
|
471
546
|
vecho "Done. Restart Codex to pick up the plugin."
|