Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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.37",
"version": "0.2.38",
"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.37",
"version": "0.2.38",
"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.37",
"version": "0.2.38",
"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