Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .githooks/pre-commit.d/02-auto-bump-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ MANIFESTS=(
"plugins/glean/.cursor-plugin/plugin.json"
)

PLUGIN_PATHS='^(src/|plugins/glean/(dist/|skills/|start\.sh|\.mcp\.json|\.mcp\.codex\.json|package\.json|\.(claude|codex|cursor)-plugin/plugin\.json))'
PLUGIN_PATHS='^(src/|plugins/glean/(dist/|skills/|start\.mjs|\.mcp\.json|\.mcp\.codex\.json|package\.json|\.(claude|codex|cursor)-plugin/plugin\.json))'

if ! git diff --cached --name-only | grep -qE "$PLUGIN_PATHS"; then
exit 0
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ HITL ones — or in your shell:
Empty values and un-interpolated `${VAR}` placeholders are ignored, falling back
to the default.

The launcher (`plugins/glean/start.sh`) also derives and **exports** three more
variables the bundle reads, so these are internal — start.sh overwrites them on
The launcher (`plugins/glean/start.mjs`) also derives and **exports** three more
variables the bundle reads, so these are internal — start.mjs overwrites them on
every launch and setting them yourself has no effect:

- `PLUGIN_DATA_DIR` — directory for credentials, caches, the saved server URL,
Expand Down Expand Up @@ -195,9 +195,9 @@ plugins/glean/
by `npm run build`; checked in)
skills/glean_run/ Skill that tells the agent how to use the
tools. Uses the open SKILL.md standard.
start.sh Bash launcher: sanitizes env (SKILLS_BASE_DIR,
PLUGIN_DATA_DIR, GLEAN_SESSION_ID), then execs
node on the bundle
start.mjs Cross-platform Node launcher: sanitizes env
(SKILLS_BASE_DIR, PLUGIN_DATA_DIR, GLEAN_SESSION_ID),
then imports the dist/index.js bundle in-process
package.json Minimal "type": "module" manifest so Node
treats dist/index.js as ESM at runtime
src/ TypeScript sources for the MCP server
Expand Down
2 changes: 1 addition & 1 deletion plugins/glean/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "glean-vnext",
"version": "0.2.35",
"version": "0.2.36",
"description": "Glean plugin for discovering skills and running tools.",
"author": {
"name": "Glean"
Expand Down
2 changes: 1 addition & 1 deletion plugins/glean/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "glean-vnext",
"version": "0.2.35",
"version": "0.2.36",
"description": "Glean Codex plugin for discovering skills and running tools.",
"author": {
"name": "Glean"
Expand Down
2 changes: 1 addition & 1 deletion plugins/glean/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "glean-vnext",
"displayName": "Glean vNext",
"version": "0.2.35",
"version": "0.2.36",
"description": "Search and act across your company's apps — Jira, Slack, Salesforce, Google Workspace, and more — without leaving Cursor.",
"author": {
"name": "Glean"
Expand Down
4 changes: 2 additions & 2 deletions plugins/glean/.mcp.codex.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"mcpServers": {
"glean-local": {
"command": "bash",
"args": ["./start.sh"],
"command": "node",
"args": ["./start.mjs"],
"cwd": ".",
"env": {
"ENABLE_HITL": "true",
Expand Down
4 changes: 2 additions & 2 deletions plugins/glean/.mcp.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"mcpServers": {
"glean": {
"command": "bash",
"args": ["${CLAUDE_PLUGIN_ROOT}/start.sh"],
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/start.mjs"],
"env": {
"ENABLE_HITL": "true",
"HITL_TIMEOUT_MS": "300000"
Expand Down
4 changes: 2 additions & 2 deletions plugins/glean/dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 70 additions & 0 deletions plugins/glean/start.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env node
// @ts-check
// Invoked by the plugin host (Claude Code, Codex, or Cursor) to launch the Glean
// MCP server. The plugin ships a single-file esbuild output at dist/index.js with
// every non-builtin inlined — no node_modules next to it. This script handles
// env sanitation before launching the plugin proper.
import os from "node:os";
import path from "node:path";
import { execFileSync } from "node:child_process";

// Treat empty strings and un-interpolated "${VAR}" placeholders (which a host
// may pass through verbatim when a variable is unset) as "not set" — matching
// the bundle's own readEnv / resolveSessionId guards.
/** @param {string | undefined} v @returns {string | undefined} */
function val(v) {
if (v === undefined) return undefined;
const t = v.trim();
if (t === "" || t.startsWith("${")) return undefined;
return t;
}

const launchCwd = process.cwd();

// Resolve where credentials, caches, and config are stored.
// CLAUDE_PLUGIN_DATA is the managed lifecycle dir provided by the plugin host.
const pluginDataDir =
val(process.env.CLAUDE_PLUGIN_DATA) ??
path.join(os.homedir() || os.tmpdir(), ".glean");
process.env.PLUGIN_DATA_DIR = pluginDataDir;

// Discovered skill files are written under the data dir by default, so the
// skills cache tracks PLUGIN_DATA_DIR instead of being resolved separately.
let skillsBaseDir = path.join(pluginDataDir, "glean-skills-cache");

// Opt-in: when USE_CLAUDE_PROJECT_DIR=1, route the skills cache under the launch
// project's .claude/tmp/ so the glean_run skill's allowed-tools Read glob can
// match cache files via a path anchored to the project root. projectDir is the
// git repo root for the launch cwd, falling back to the launch cwd when it is
// not inside a git repo (or git is unavailable).
if (process.env.USE_CLAUDE_PROJECT_DIR === "1") {
let projectDir = launchCwd;
try {
const top = execFileSync(
"git",
["-C", launchCwd, "rev-parse", "--show-toplevel"],
{ encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] },
).trim();
if (top) projectDir = top;
} catch {
/* not a git repo or git missing: keep the launch cwd fallback */
}
skillsBaseDir = path.join(projectDir, ".claude", "tmp", "glean-skills-cache");
}
process.env.SKILLS_BASE_DIR = skillsBaseDir;

// Resolve the chat session id host-side. Host-awareness lives here, not in the
// plugin: the launcher reads whatever variable this host exposes and exports the
// normalized GLEAN_SESSION_ID that the Node bundle reads. Claude Code exposes
// CLAUDE_CODE_SESSION_ID; Codex exposes the conversation id as CODEX_THREAD_ID.
// Hosts that expose no session id (Cursor) leave it unset, and the plugin falls
// back to a generated per-process id.
const sessionId =
val(process.env.CLAUDE_CODE_SESSION_ID) ?? val(process.env.CODEX_THREAD_ID);
if (sessionId !== undefined) {
process.env.GLEAN_SESSION_ID = sessionId;
}

// Boot the server in-process. Import via a file URL resolved against this
// module so the dynamic specifier works regardless of cwd and on Windows paths.
await import(new URL("./dist/index.js", import.meta.url).href);
47 changes: 0 additions & 47 deletions plugins/glean/start.sh

This file was deleted.

2 changes: 1 addition & 1 deletion scripts/check-version-bump.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ MANIFESTS=(
)

# Only trigger on files that affect the plugin runtime — not CI or tooling.
PLUGIN_PATHS='^(src/|plugins/glean/(dist/|skills/|start\.sh|\.mcp\.json|\.mcp\.codex\.json|package\.json|\.(claude|codex|cursor)-plugin/plugin\.json))'
PLUGIN_PATHS='^(src/|plugins/glean/(dist/|skills/|start\.mjs|\.mcp\.json|\.mcp\.codex\.json|package\.json|\.(claude|codex|cursor)-plugin/plugin\.json))'

if ! git diff --name-only "$BASE_REF"...HEAD | grep -qE "$PLUGIN_PATHS"; then
echo "No plugin files changed — skipping version check."
Expand Down
2 changes: 1 addition & 1 deletion scripts/pack-plugin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ zip -r "$OUT" \
.mcp.json \
dist \
skills \
start.sh \
start.mjs \
package.json \
>/dev/null

Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "@modelcontextprotocol/sdk/types.js";
import path from "node:path";
import fs from "node:fs";
import { homedir } from "node:os";
import { homedir, tmpdir } from "node:os";
Comment thread
anirudh-bijay-glean marked this conversation as resolved.
import {
AuthRequiredError,
createRemoteClient,
Expand Down Expand Up @@ -118,7 +118,7 @@ function resolveSkillsBaseDir(): string {
if (process.env.SKILLS_BASE_DIR) {
return process.env.SKILLS_BASE_DIR;
}
return path.join("/tmp", "glean-skills-cache");
return path.join(tmpdir(), "glean-skills-cache");
}

const server = new Server(
Expand Down
2 changes: 1 addition & 1 deletion src/session-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ let fallbackSessionId: string | undefined;

/**
* Resolves the chat session id from GLEAN_SESSION_ID, which the host-aware
* launcher (start.sh) exports after reading whatever variable the current host
* launcher (start.mjs) exports after reading whatever variable the current host
* uses for the session/conversation id. The plugin itself stays host-agnostic
* and never reads host-specific env vars.
*
Expand Down
2 changes: 1 addition & 1 deletion src/tools/approval-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function formatArgumentsForFile(
}

// The full arguments are written to a per-session file under the plugin's data
// dir (CLAUDE_PLUGIN_DATA, exported by start.sh as PLUGIN_DATA_DIR). The file is
// dir (CLAUDE_PLUGIN_DATA, set by start.mjs as PLUGIN_DATA_DIR). The file is
// scoped to the chat session id so parallel sessions don't overwrite each
// other; within a session it is intentionally overwritten on each approval —
// only the most recent prompt's arguments need to be inspectable.
Expand Down
Loading