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
6 changes: 3 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
{
"name": "cuey",
"source": "./plugins/cuey",
"description": "Run Cuey's multi-model comparison and return a synthesized answer.",
"version": "0.3.3",
"description": "Run Cuey's multi-model comparison over prompts and attached Excel workbooks.",
"version": "0.4.0",
"author": {
"name": "TensorBlock"
},
"category": "productivity",
"tags": ["comparison", "verification", "multi-model"]
"tags": ["comparison", "verification", "multi-model", "excel", "spreadsheet"]
}
]
}
6 changes: 3 additions & 3 deletions plugins/cuey/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "cuey",
"version": "0.3.3",
"description": "Ask Cuey runs a three-model comparison and returns a synthesized answer with the original model responses available in Cuey.",
"version": "0.4.0",
"description": "Ask Cuey runs a three-model comparison over prompts and attached Excel workbooks, then returns a synthesized answer.",
"author": {
"name": "TensorBlock",
"url": "https://tensorblock.co"
},
"keywords": ["cuey", "comparison", "verification", "multi-model"]
"keywords": ["cuey", "comparison", "verification", "multi-model", "excel", "spreadsheet"]
}
47 changes: 45 additions & 2 deletions plugins/cuey/mcp/src/ask-cuey-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const DEFAULT_TIMEOUT_MS = 120000;
const DEFAULT_SYNTHESIS_TIMEOUT_MS = 90000;
const DEFAULT_SYNTHESIS_ATTEMPTS = 2;
const MAX_ERROR_MESSAGE_LENGTH = 260;
const MAX_SPREADSHEET_CONTEXT_LENGTH = 180000;

function cleanBaseUrl(value) {
return String(value || DEFAULT_API_BASE_URL).replace(/\/+$/, "");
Expand Down Expand Up @@ -85,6 +86,18 @@ function normalizeReasoningLevel(value) {
return normalized === "advanced" ? "advanced" : "standard";
}

function normalizeSpreadsheet(input) {
if (!input || typeof input !== "object") return null;
const filename = String(input.filename || input.fileName || "").trim();
const documentId = String(input.documentId || input.document_id || "").trim();
let context = String(input.context || input.workbookContext || input.workbook_context || "").trim();
if (context.length > MAX_SPREADSHEET_CONTEXT_LENGTH) {
context = `${context.slice(0, MAX_SPREADSHEET_CONTEXT_LENGTH).trimEnd()}\n\n[Workbook context truncated by Cuey]`;
}
if (!filename && !context && !documentId) return null;
return { filename, context, documentId };
}

function requestHeaders({ token, anonymousId, json = true } = {}) {
const headers = {};
if (json) headers["Content-Type"] = "application/json";
Expand Down Expand Up @@ -194,16 +207,24 @@ function extractReasoningText(json) {
return typeof content === "string" ? content.trim() : "";
}

export function buildAskCueyMessages({ question, context, mode } = {}) {
export function buildAskCueyMessages({ question, context, mode, spreadsheet } = {}) {
const q = String(question || "").trim();
const ctx = String(context || "").trim();
const workbook = normalizeSpreadsheet(spreadsheet);
const normalizedMode = String(mode || "ask").trim().toLowerCase() || "ask";
const userParts = [];
if (ctx) {
userParts.push("Relevant context:");
userParts.push(ctx);
userParts.push("");
}
if (workbook?.context) {
userParts.push("Attached Excel workbook:");
if (workbook.filename) userParts.push(`Filename: ${workbook.filename}`);
userParts.push("Workbook context:");
userParts.push(workbook.context);
userParts.push("");
}
userParts.push(`Cuey mode: ${normalizedMode}`);
userParts.push("");
userParts.push("Question:");
Expand All @@ -223,6 +244,7 @@ export function normalizeCueyRequest(input = {}) {
mode: String(input.mode || "ask").trim().toLowerCase() || "ask",
question,
context: String(input.context || "").trim(),
spreadsheet: normalizeSpreadsheet(input.spreadsheet),
models: normalizeModels(input.models, reasoningLevel),
reasoningLevel,
source: String(input.source || "claude_skill").trim() || "claude_skill",
Expand Down Expand Up @@ -288,10 +310,22 @@ export function buildSynthesisRequest({ cueyMessageId, candidateResponses, metad
}

export async function writeLatestAskCueyResult(result, resultPath = LATEST_RESULT_PATH) {
const request = result?.request
? {
...result.request,
spreadsheet: result.request.spreadsheet
? {
filename: result.request.spreadsheet.filename || "",
documentId: result.request.spreadsheet.documentId || "",
hasContext: Boolean(result.request.spreadsheet.context),
}
: null,
}
: null;
const payload = {
schemaVersion: 1,
writtenAt: new Date().toISOString(),
request: result?.request || null,
request,
cueyMessageId: result?.cueyMessageId || null,
synthesis: result?.synthesis || null,
candidates: (result?.candidates || []).map((candidate) => ({
Expand Down Expand Up @@ -498,6 +532,15 @@ export async function runAskCuey(input = {}, options = {}) {
cuey_source: request.source,
cuey_mode: request.mode,
reasoning_level: request.reasoningLevel,
...(request.spreadsheet?.filename
? { spreadsheet_filename: request.spreadsheet.filename }
: {}),
...(request.spreadsheet?.context
? { has_spreadsheet_context: true }
: {}),
...(request.spreadsheet?.documentId
? { spreadsheet_document_id: request.spreadsheet.documentId }
: {}),
};

const candidateSettled = await Promise.allSettled(
Expand Down
22 changes: 20 additions & 2 deletions plugins/cuey/mcp/src/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import {
} from "./ask-cuey-client.mjs";

const SERVER_NAME = "cuey-claude-mcp";
const SERVER_VERSION = "0.3.3";
const SERVER_VERSION = "0.4.0";

const tools = [
{
name: "ask_cuey",
description: "Run Ask Cuey multi-model fanout and synthesis. Call this tool whenever the user explicitly asks to use Cuey, asks for a Cuey comparison, or the message instructs you to use the Cuey MCP tool. The text content is the final synthesis: return it verbatim without adding analysis, commentary, or a separate answer.",
description: "Run Ask Cuey multi-model fanout and synthesis, optionally using structured context extracted from an attached Excel workbook. Call this tool whenever the user explicitly asks to use Cuey, asks for a Cuey comparison, or the message instructs you to use the Cuey MCP tool. The text content is the final synthesis: return it verbatim without adding analysis, commentary, or a separate answer.",
inputSchema: {
type: "object",
properties: {
Expand All @@ -29,6 +29,24 @@ const tools = [
type: "string",
description: "Relevant Claude-visible context to include.",
},
spreadsheet: {
type: "object",
description: "Structured context extracted from the Excel workbook attached to the current Claude request.",
properties: {
filename: {
type: "string",
description: "Original .xlsx filename.",
},
context: {
type: "string",
description: "Workbook sheet names, used ranges, values, and formulas relevant to the question.",
},
documentId: {
type: "string",
description: "Optional Cuey document ID when the workbook was uploaded through Cuey.",
},
},
},
models: {
type: "array",
items: { type: "string" },
Expand Down
65 changes: 65 additions & 0 deletions plugins/cuey/mcp/tests/ask-cuey-client.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,39 @@ test("buildAskCueyMessages includes relevant context and question", () => {
assert.match(messages[1].content, /Cuey mode: verify/);
});

test("normalizeCueyRequest preserves structured Excel workbook context", () => {
const request = normalizeCueyRequest({
question: "Summarize this workbook",
spreadsheet: {
filename: "forecast.xlsx",
context: "Sheets: Revenue, Assumptions\nRevenue!A1:B2\nYear | ARR\n2026 | 12000000",
document_id: "doc-123",
},
});

assert.deepEqual(request.spreadsheet, {
filename: "forecast.xlsx",
context: "Sheets: Revenue, Assumptions\nRevenue!A1:B2\nYear | ARR\n2026 | 12000000",
documentId: "doc-123",
});
});

test("buildAskCueyMessages labels attached Excel context separately", () => {
const messages = buildAskCueyMessages({
question: "What changed year over year?",
mode: "summarize",
spreadsheet: {
filename: "forecast.xlsx",
context: "Sheet Revenue, used range A1:B2\nYear | ARR\n2026 | 12000000",
},
});

assert.match(messages[1].content, /Attached Excel workbook:/);
assert.match(messages[1].content, /Filename: forecast\.xlsx/);
assert.match(messages[1].content, /Sheet Revenue, used range A1:B2/);
assert.match(messages[1].content, /What changed year over year\?/);
});

test("buildCandidateRequest sends anonymous id for public requests", () => {
const request = buildCandidateRequest({
apiBaseUrl: "https://staging-api.cuey.io/api/cuey",
Expand Down Expand Up @@ -290,3 +323,35 @@ test("writeLatestAskCueyResult stores original model answers for the overlay", a
await rm(dir, { recursive: true, force: true });
}
});

test("writeLatestAskCueyResult records workbook presence without persisting cell context", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "cuey-result-"));
const resultPath = path.join(dir, "latest-ask-cuey-result.json");
try {
await writeLatestAskCueyResult(
{
request: {
question: "Summarize this workbook",
spreadsheet: {
filename: "forecast.xlsx",
context: "private workbook cells",
documentId: "doc-123",
},
},
synthesis: { answer: "Revenue increased." },
candidates: [],
},
resultPath,
);

const parsed = JSON.parse(await readFile(resultPath, "utf8"));
assert.deepEqual(parsed.request.spreadsheet, {
filename: "forecast.xlsx",
documentId: "doc-123",
hasContext: true,
});
assert.doesNotMatch(await readFile(resultPath, "utf8"), /private workbook cells/);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
18 changes: 17 additions & 1 deletion plugins/cuey/skills/cuey/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ argument-hint: <question>

# Cuey

When invoked, call the local MCP tool `cuey-plugin:ask_cuey` immediately. Do not inspect files, use bash, recall memory, search, or answer directly before calling the tool.
When invoked, call the local MCP tool `cuey-plugin:ask_cuey`. Do not use bash, recall memory, search, or answer directly before calling the tool.

If the current request includes an Excel `.xlsx` attachment, read that attachment first with Claude's available file or spreadsheet capability. Build compact workbook context containing:

- workbook filename;
- every sheet name;
- the used range and headers for each relevant sheet;
- cell values and formulas relevant to the user's question;
- any omitted sheets or ranges when the workbook is too large to include fully.

Do not infer missing cells or formulas. For a small workbook, include all populated cells. For a large workbook, prioritize the sheets and ranges relevant to `$ARGUMENTS` and clearly record the selection in the workbook context.

Send this payload:

Expand All @@ -15,6 +25,10 @@ Send this payload:
"mode": "ask | compare | verify | summarize",
"question": "$ARGUMENTS",
"context": "only relevant prior conversation context",
"spreadsheet": {
"filename": "attached workbook filename, or empty when none",
"context": "structured workbook context extracted from the attached .xlsx, or empty when none"
},
"models": ["grok-4.5-reasoning", "gpt-5.6-sol", "claude-opus-4-8"],
"reasoningLevel": "standard",
"source": "claude_plugin"
Expand All @@ -23,6 +37,8 @@ Send this payload:

Choose `compare` for comparisons, `verify` for risk or correctness checks, `summarize` for summaries, and `ask` otherwise.

When there is no Excel attachment, omit `spreadsheet`. Never substitute a filename-only description for workbook content.

After a successful call:

1. Return the first text item from the MCP result exactly as the complete answer.
Expand Down
Loading