@bragduck/mcp 1.7.0 → 1.8.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.
Files changed (50) hide show
  1. package/README.md +29 -0
  2. package/dist/index.js +2006 -122
  3. package/dist/index.js.map +1 -1
  4. package/package.json +8 -4
  5. package/dist/api-client.d.ts +0 -29
  6. package/dist/api-client.js +0 -146
  7. package/dist/api-client.js.map +0 -1
  8. package/dist/auth.d.ts +0 -8
  9. package/dist/auth.js +0 -202
  10. package/dist/auth.js.map +0 -1
  11. package/dist/credentials.d.ts +0 -31
  12. package/dist/credentials.js +0 -150
  13. package/dist/credentials.js.map +0 -1
  14. package/dist/index.d.ts +0 -2
  15. package/dist/org-preference.d.ts +0 -7
  16. package/dist/org-preference.js +0 -22
  17. package/dist/org-preference.js.map +0 -1
  18. package/dist/tools/link-brag-to-key-result.d.ts +0 -30
  19. package/dist/tools/link-brag-to-key-result.js +0 -81
  20. package/dist/tools/link-brag-to-key-result.js.map +0 -1
  21. package/dist/tools/list-brags.d.ts +0 -53
  22. package/dist/tools/list-brags.js +0 -110
  23. package/dist/tools/list-brags.js.map +0 -1
  24. package/dist/tools/list-objectives.d.ts +0 -15
  25. package/dist/tools/list-objectives.js +0 -66
  26. package/dist/tools/list-objectives.js.map +0 -1
  27. package/dist/tools/list-organisations.d.ts +0 -15
  28. package/dist/tools/list-organisations.js +0 -73
  29. package/dist/tools/list-organisations.js.map +0 -1
  30. package/dist/tools/list-stories.d.ts +0 -41
  31. package/dist/tools/list-stories.js +0 -103
  32. package/dist/tools/list-stories.js.map +0 -1
  33. package/dist/tools/log-brag.d.ts +0 -88
  34. package/dist/tools/log-brag.js +0 -175
  35. package/dist/tools/log-brag.js.map +0 -1
  36. package/dist/tools/log-story.d.ts +0 -81
  37. package/dist/tools/log-story.js +0 -122
  38. package/dist/tools/log-story.js.map +0 -1
  39. package/dist/tools/login.d.ts +0 -24
  40. package/dist/tools/login.js +0 -46
  41. package/dist/tools/login.js.map +0 -1
  42. package/dist/tools/preview-brag.d.ts +0 -71
  43. package/dist/tools/preview-brag.js +0 -103
  44. package/dist/tools/preview-brag.js.map +0 -1
  45. package/dist/tools/quick-brag.d.ts +0 -38
  46. package/dist/tools/quick-brag.js +0 -143
  47. package/dist/tools/quick-brag.js.map +0 -1
  48. package/dist/tools/save-org-preference.d.ts +0 -30
  49. package/dist/tools/save-org-preference.js +0 -47
  50. package/dist/tools/save-org-preference.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,SAAS,EACT,sBAAsB,EACtB,QAAQ,GACT,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAmB,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACtF,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACxG,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAC/F,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE7E,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;IACE,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,OAAO;CACjB,EACD;IACE,YAAY,EAAE;QACZ,KAAK,EAAE,EAAE;KACV;IACD,YAAY,EACV,6FAA6F;QAC7F,iHAAiH;QACjH,MAAM;QACN,wCAAwC;QACxC,oGAAoG;QACpG,wEAAwE;QACxE,iHAAiH;QACjH,sDAAsD;QACtD,kFAAkF;QAClF,IAAI;QACJ,+BAA+B;QAC/B,2EAA2E;QAC3E,sEAAsE;QACtE,0EAA0E;QAC1E,oFAAoF;QACpF,oFAAoF;QACpF,qEAAqE;QACrE,oFAAoF;QACpF,IAAI;QACJ,iBAAiB;QACjB,oEAAoE;QACpE,uEAAuE;QACvE,4FAA4F;QAC5F,oEAAoE;QACpE,wDAAwD;QACxD,0CAA0C;QAC1C,2DAA2D;QAC3D,sFAAsF;QACtF,wEAAwE;QACxE,0CAA0C;QAC1C,IAAI;QACJ,gCAAgC;QAChC,qEAAqE;QACrE,2GAA2G;QAC3G,mFAAmF;QACnF,4GAA4G;QAC5G,8FAA8F;QAC9F,8FAA8F;QAC9F,qFAAqF;QACrF,IAAI;QACJ,0BAA0B;QAC1B,8CAA8C;QAC9C,kFAAkF;QAClF,0FAA0F;QAC1F,0FAA0F;QAC1F,uGAAuG;QACvG,uFAAuF;QACvF,+CAA+C;QAC/C,wEAAwE;QACxE,IAAI;QACJ,iBAAiB;QACjB,2EAA2E;QAC3E,IAAI;QACJ,sCAAsC;QACtC,kGAAkG;QAClG,yDAAyD;QACzD,wGAAwG;QACxG,oFAAoF;QACpF,IAAI;QACJ,iCAAiC;QACjC,8FAA8F;QAC9F,qGAAqG;QACrG,0GAA0G;QAC1G,wGAAwG;QACxG,iGAAiG;QACjG,4DAA4D;QAC5D,IAAI;QACJ,iBAAiB;QACjB,iEAAiE;QACjE,4EAA4E;QAC5E,iEAAiE;CACpE,CACF,CAAC;AAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE;IAC1D,OAAO;QACL,KAAK,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,YAAY,EAAE,eAAe,CAAC;KACxL,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAEjD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,OAAO;YACV,OAAO,WAAW,CAAC,IAAW,CAAC,CAAC;QAClC,KAAK,cAAc;YACjB,OAAO,iBAAiB,CAAC,IAAW,CAAC,CAAC;QACxC,KAAK,UAAU;YACb,OAAO,aAAa,CAAC,IAAW,CAAC,CAAC;QACpC,KAAK,YAAY;YACf,OAAO,eAAe,CAAC,IAAW,CAAC,CAAC;QACtC,KAAK,YAAY;YACf,OAAO,eAAe,CAAC,IAAW,CAAC,CAAC;QACtC,KAAK,iBAAiB;YACpB,OAAO,oBAAoB,EAAE,CAAC;QAChC,KAAK,yBAAyB;YAC5B,OAAO,yBAAyB,CAAC,IAAW,CAAC,CAAC;QAChD,KAAK,oBAAoB;YACvB,OAAO,uBAAuB,EAAE,CAAC;QACnC,KAAK,qBAAqB;YACxB,OAAO,uBAAuB,CAAC,IAAW,CAAC,CAAC;QAC9C,KAAK,WAAW;YACd,OAAO,cAAc,CAAC,IAAW,CAAC,CAAC;QACrC,KAAK,cAAc;YACjB,OAAO,iBAAiB,CAAC,IAAW,CAAC,CAAC;QACxC;YACE,MAAM,IAAI,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,iBAAiB,IAAI,EAAE,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
1
+ {"version":3,"sources":["../src/index.ts","../src/auth.ts","../src/credentials.ts","../src/tools/login.ts","../src/api-client.ts","../src/tools/log-brag.ts","../src/tools/quick-brag.ts","../src/tools/preview-brag.ts","../src/tools/list-brags.ts","../src/tools/list-objectives.ts","../src/tools/link-brag-to-key-result.ts","../src/org-preference.ts","../src/tools/list-organisations.ts","../src/tools/save-org-preference.ts","../src/tools/log-story.ts","../../server/src/utils/subscription/feature-config.ts","../../server/src/utils/access-control/roles.ts","../../server/src/utils/badges/badge.constants.ts","../../../packages/types/domain/story-categories.ts","../src/tools/story-tool-shared.ts","../src/tools/get-story.ts","../src/tools/update-story.ts","../src/tools/delete-story.ts","../src/tools/list-stories.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport { loginTool, handleLogin } from './tools/login.js';\nimport { logBragTool, handleLogBrag } from './tools/log-brag.js';\nimport { quickBragTool, handleQuickBrag } from './tools/quick-brag.js';\nimport { previewBragTool, handlePreviewBrag } from './tools/preview-brag.js';\nimport { listBragsTool, handleListBrags } from './tools/list-brags.js';\nimport { listObjectivesTool, handleListObjectives } from './tools/list-objectives.js';\nimport { linkBragToKeyResultTool, handleLinkBragToKeyResult } from './tools/link-brag-to-key-result.js';\nimport { listOrganisationsTool, handleListOrganisations } from './tools/list-organisations.js';\nimport { saveOrgPreferenceTool, handleSaveOrgPreference } from './tools/save-org-preference.js';\nimport { logStoryTool, handleLogStory } from './tools/log-story.js';\nimport { getStoryTool, handleGetStory } from './tools/get-story.js';\nimport { updateStoryTool, handleUpdateStory } from './tools/update-story.js';\nimport { deleteStoryTool, handleDeleteStory } from './tools/delete-story.js';\nimport { listStoriesTool, handleListStories } from './tools/list-stories.js';\n\nconst server = new Server(\n {\n name: 'bragduck',\n version: '1.0.0',\n },\n {\n capabilities: {\n tools: {},\n },\n instructions:\n 'BragDuck tracks career achievements (\"brags\") that feed into periodic performance reports. ' +\n 'A strong brag needs: a concrete accomplishment, measurable or observable impact, and ideally a reference link. ' +\n '\\n\\n' +\n 'RESOLVING \"THIS\" IN \"BRAG WITH THIS\"\\n' +\n 'When the user says \"brag with this\" (or \"brag about this\", \"log this\", etc.) without elaborating, ' +\n '\"this\" refers to the most recent work visible in the current session: ' +\n 'the latest git diff, the last commit message, the most recently discussed code change, or a PR just mentioned. ' +\n 'Look for that context first before asking anything. ' +\n 'Only start the interview (see below) if no such context exists in the session.\\n' +\n '\\n' +\n 'STEP 1 — ASSESS BRAGABILITY\\n' +\n 'Before doing anything else, judge whether the subject is worth logging:\\n' +\n '- NOT bragable: vague status updates, trivial tasks with no impact, ' +\n ' or things a reasonable person would not list on a performance review. ' +\n ' Tell the user directly that it is not bragable and explain why in one sentence. ' +\n ' Note that brags feed the AI report generator — weak entries dilute the signal.\\n' +\n '- POTENTIALLY bragable but thin: start the interview (see below).\\n' +\n '- Clearly bragable with full context: skip the interview, go straight to STEP 2.\\n' +\n '\\n' +\n 'THE INTERVIEW\\n' +\n 'Conduct a short conversational interview to gather what you need. ' +\n 'Ask ONE question at a time — do not list multiple questions at once. ' +\n 'Each question should follow naturally from the previous answer, like a real conversation. ' +\n 'Suggested question order (adapt based on what you already know):\\n' +\n ' 1. \"What did you work on?\" (skip if already known)\\n' +\n ' 2. \"What was the outcome or result?\"\\n' +\n ' 3. \"Who was affected — just you, the team, or wider?\"\\n' +\n ' 4. \"Do you have a link — PR, ticket, commit, or doc?\" (skip if already have one)\\n' +\n 'Stop the interview as soon as you have enough to write a strong brag. ' +\n 'Never ask more than 4 questions total.\\n' +\n '\\n' +\n 'STEP 2 — SELECT ORGANISATION\\n' +\n 'Before composing the brag, determine which company it belongs to:\\n' +\n '1. Call list_organisations. It returns the org list AND any saved preference for the current directory.\\n' +\n '2. If a saved preference exists, use that orgId silently — do not ask the user.\\n' +\n '3. If no saved preference, present the org list to the user and ask: \"Which company does this belong to?\" ' +\n ' Wait for their answer, then call save_org_preference with the chosen orgId and orgName.\\n' +\n '4. If the user has only one organisation, select it automatically and save the preference.\\n' +\n '5. If the user has no organisations, skip this step and proceed without an orgId.\\n' +\n '\\n' +\n 'STEP 3 — PREVIEW + LOG\\n' +\n 'Compose the brag from all gathered context. ' +\n 'Do NOT call preview_brag. Instead, in your response text render the brag fields ' +\n 'as a markdown table with two columns (Field | Value), one row per field, in this order: ' +\n 'Title, Type, Impact Level, Impact Details, Description, Tags, References, Organisation. ' +\n 'In the very same response, after the table, call log_brag with those field values (including orgId). ' +\n 'The native tool-permission prompt (Allow / Deny) that appears below the table IS the ' +\n 'confirmation — do not add any text question. ' +\n 'If the user denies or asks for changes, adjust and repeat this step.\\n' +\n '\\n' +\n 'STEP 4 — DONE\\n' +\n 'Once log_brag is allowed and succeeds, confirm with one short sentence.\\n' +\n '\\n' +\n 'QUICK BRAG (via /brag or raw text)\\n' +\n 'When the user says \"/brag\" followed by text, or pastes a long block of text and asks to log it, ' +\n 'use the quick_brag tool instead of the interview flow. ' +\n 'quick_brag accepts raw text and uses AI to extract and structure it into a proper brag automatically. ' +\n 'Skip the interview, skip the preview table — just call quick_brag with the text.\\n' +\n '\\n' +\n 'INTERVIEW STORIES (log_story)\\n' +\n 'When the user discusses interview prep, behavioral questions, or asks to save a STAR story, ' +\n 'use log_story instead of log_brag. Stories are structured with Situation/Task/Action/Result fields ' +\n 'and competency tags. Follow the same org-selection flow (list_organisations → saved preference or ask). ' +\n 'Preview the story as a markdown table (Field | Value) before calling log_story in the same response.\\n' +\n 'Use list_stories to browse existing stories. Supports filters: category, orgId, search, limit. ' +\n 'Stories include AI-generated strength and impact scores.\\n' +\n '\\n' +\n 'IMPROVING STORIES (get_story, update_story, delete_story)\\n' +\n 'To make a story stronger: find it with list_stories, read it in full with get_story (its AI summary says ' +\n 'what is weak), then propose a stronger version — a clear Situation, first-person Actions, and a Result ' +\n 'with measurable impact. Never invent numbers or facts; ask the user for anything missing. ' +\n 'Show the changes as a markdown table (Field | Current | Proposed) and call update_story in the same ' +\n 'response with only the changed fields; the permission prompt IS the confirmation. ' +\n 'Only call delete_story after the user explicitly says yes to deleting that named story.\\n' +\n '\\n' +\n 'GENERAL RULES\\n' +\n '- Never fabricate details, metrics, or links. If unsure, ask.\\n' +\n '- \"Is this bragable?\" deserves a direct honest answer — not a tool call.\\n' +\n '- Call login first if any tool returns an authentication error.',\n }\n);\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: [loginTool, logBragTool, quickBragTool, listBragsTool, listObjectivesTool, linkBragToKeyResultTool, listOrganisationsTool, saveOrgPreferenceTool, logStoryTool, listStoriesTool, getStoryTool, updateStoryTool, deleteStoryTool],\n };\n});\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n switch (name) {\n case 'login':\n return handleLogin(args as any);\n case 'preview_brag':\n return handlePreviewBrag(args as any);\n case 'log_brag':\n return handleLogBrag(args as any);\n case 'quick_brag':\n return handleQuickBrag(args as any);\n case 'list_brags':\n return handleListBrags(args as any);\n case 'list_objectives':\n return handleListObjectives();\n case 'link_brag_to_key_result':\n return handleLinkBragToKeyResult(args as any);\n case 'list_organisations':\n return handleListOrganisations();\n case 'save_org_preference':\n return handleSaveOrgPreference(args as any);\n case 'log_story':\n return handleLogStory(args as any);\n case 'list_stories':\n return handleListStories(args as any);\n case 'get_story':\n return handleGetStory(args as any);\n case 'update_story':\n return handleUpdateStory(args as any);\n case 'delete_story':\n return handleDeleteStory(args as any);\n default:\n throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);\n }\n});\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","import { randomBytes } from 'crypto';\nimport { createServer, type Server, type IncomingMessage, type ServerResponse } from 'http';\nimport { parse } from 'url';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { storeCredentials, storeUserInfo } from './credentials.js';\n\nconst execAsync = promisify(exec);\n\nconst API_BASE_URL = process.env.BRAGDUCK_API_URL || 'https://api.bragduck.com';\nconst OAUTH_CLIENT_ID = 'bragduck-cli';\nconst CALLBACK_PATH = '/callback';\nconst TIMEOUT_MS = 120000;\nconst MIN_PORT = 8010;\nconst MAX_PORT = 8014;\n\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n token_type: string;\n user?: { id: string; email: string; name: string };\n}\n\nasync function findAvailablePort(): Promise<number> {\n for (let port = MIN_PORT; port <= MAX_PORT; port++) {\n try {\n await new Promise<void>((resolve, reject) => {\n const testServer = createServer();\n testServer.once('error', reject);\n testServer.once('listening', () => {\n testServer.close(() => resolve());\n });\n testServer.listen(port, '127.0.0.1');\n });\n return port;\n } catch {\n continue;\n }\n }\n throw new Error(`No available ports found in range ${MIN_PORT}-${MAX_PORT}`);\n}\n\nasync function openBrowser(url: string): Promise<void> {\n const platform = process.platform;\n let command: string;\n switch (platform) {\n case 'darwin':\n command = `open \"${url}\"`;\n break;\n case 'win32':\n command = `start \"\" \"${url}\"`;\n break;\n default:\n command = `xdg-open \"${url}\"`;\n break;\n }\n await execAsync(command);\n}\n\nconst SUCCESS_HTML = `<!DOCTYPE html>\n<html><head><meta charset=\"UTF-8\"><title>BragDuck - Authenticated</title>\n<style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }\n .container { text-align: center; padding: 2rem; background: rgba(255,255,255,0.1);\n border-radius: 1rem; backdrop-filter: blur(10px); max-width: 500px; }\n h1 { font-size: 2rem; margin: 0 0 1rem; }\n .check { font-size: 4rem; animation: pop 0.3s ease-out; }\n p { font-size: 1.1rem; opacity: 0.9; }\n @keyframes pop { from { transform: scale(0); } to { transform: scale(1); } }\n</style></head><body>\n<div class=\"container\">\n <div class=\"check\">&#10003;</div>\n <h1>Authentication Successful</h1>\n <p>You can close this window and return to your editor.</p>\n</div></body></html>`;\n\nconst ERROR_HTML = (error: string) => `<!DOCTYPE html>\n<html><head><meta charset=\"UTF-8\"><title>BragDuck - Auth Failed</title>\n<style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0;\n background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; }\n .container { text-align: center; padding: 2rem; background: rgba(255,255,255,0.1);\n border-radius: 1rem; backdrop-filter: blur(10px); max-width: 500px; }\n h1 { font-size: 2rem; margin: 0 0 1rem; }\n .err { font-size: 4rem; }\n p { font-size: 1.1rem; opacity: 0.9; }\n .details { background: rgba(0,0,0,0.2); padding: 1rem; border-radius: 0.5rem;\n font-family: monospace; font-size: 0.9rem; margin-top: 1rem; }\n</style></head><body>\n<div class=\"container\">\n <div class=\"err\">&#10007;</div>\n <h1>Authentication Failed</h1>\n <p>Please try again.</p>\n <div class=\"details\">${error}</div>\n</div></body></html>`;\n\nasync function exchangeCodeForToken(code: string, callbackUrl: string): Promise<TokenResponse> {\n const response = await fetch(`${API_BASE_URL}/v1/auth/cli/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n code,\n redirect_uri: callbackUrl,\n client_id: OAUTH_CLIENT_ID,\n grant_type: 'authorization_code',\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as { message?: string };\n throw new Error(errorData.message || `Token exchange failed: ${response.statusText}`);\n }\n\n return response.json() as Promise<TokenResponse>;\n}\n\n/**\n * Run the full OAuth login flow: start callback server, open browser, wait for auth.\n * Returns user info on success.\n */\nexport async function login(): Promise<{ email: string; name: string }> {\n const state = randomBytes(32).toString('hex');\n const port = await findAvailablePort();\n const callbackUrl = `http://127.0.0.1:${port}${CALLBACK_PATH}`;\n\n const authParams = new URLSearchParams({\n client_id: OAUTH_CLIENT_ID,\n redirect_uri: callbackUrl,\n state,\n });\n const authUrl = `${API_BASE_URL}/v1/auth/cli/initiate?${authParams.toString()}`;\n\n return new Promise<{ email: string; name: string }>((resolve, reject) => {\n let server: Server | null = null;\n let timeoutId: ReturnType<typeof globalThis.setTimeout> | null = null;\n\n const cleanup = () => {\n if (timeoutId) globalThis.clearTimeout(timeoutId);\n if (server) {\n if (typeof (server as any).closeAllConnections === 'function') {\n (server as any).closeAllConnections();\n }\n server.close();\n server.unref();\n }\n };\n\n const handleRequest = async (req: IncomingMessage, res: ServerResponse) => {\n const parsedUrl = parse(req.url || '', true);\n\n if (parsedUrl.pathname !== CALLBACK_PATH) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return;\n }\n\n const { code, state: returnedState, error, error_description } = parsedUrl.query;\n\n if (error) {\n const errorMsg = String(error_description || error);\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(ERROR_HTML(errorMsg));\n cleanup();\n reject(new Error(`OAuth error: ${errorMsg}`));\n return;\n }\n\n if (!code || !returnedState) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(ERROR_HTML('Missing code or state parameter'));\n cleanup();\n reject(new Error('Missing code or state parameter'));\n return;\n }\n\n if (returnedState !== state) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(ERROR_HTML('Invalid state parameter'));\n cleanup();\n reject(new Error('Invalid state parameter (possible CSRF)'));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(SUCCESS_HTML);\n\n try {\n const tokenResponse = await exchangeCodeForToken(String(code), callbackUrl);\n\n const expiresAt = tokenResponse.expires_in\n ? Date.now() + tokenResponse.expires_in * 1000\n : undefined;\n\n storeCredentials({\n accessToken: tokenResponse.access_token,\n refreshToken: tokenResponse.refresh_token,\n expiresAt,\n });\n\n const user = tokenResponse.user || { id: 'unknown', email: 'unknown', name: 'Unknown' };\n storeUserInfo(user);\n\n globalThis.setTimeout(() => {\n cleanup();\n resolve({ email: user.email, name: user.name });\n }, 100);\n } catch (err) {\n cleanup();\n reject(err);\n }\n };\n\n server = createServer(handleRequest);\n\n server.on('error', (err) => {\n cleanup();\n reject(new Error(`OAuth server error: ${err.message}`));\n });\n\n server.listen(port, '127.0.0.1', () => {\n openBrowser(authUrl).catch(() => {\n // If browser opening fails, we'll report it but keep the server running\n });\n });\n\n timeoutId = globalThis.setTimeout(() => {\n cleanup();\n reject(new Error('Authentication timed out. No callback received within 2 minutes.'));\n }, TIMEOUT_MS);\n });\n}\n","import { createDecipheriv, scryptSync, randomBytes, createCipheriv } from 'crypto';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport Conf from 'conf';\n\nconst APP_NAME = 'bragduck';\nconst CREDENTIALS_DIR = '.bragduck';\nconst CREDENTIALS_FILE = 'credentials.enc';\n\nconst ENCRYPTION = {\n ALGORITHM: 'aes-256-gcm' as const,\n KEY_LENGTH: 32,\n IV_LENGTH: 16,\n AUTH_TAG_LENGTH: 16,\n SALT_LENGTH: 32,\n};\n\ninterface EncryptedData {\n encrypted: string;\n iv: string;\n authTag: string;\n salt: string;\n}\n\ninterface ServiceCredentials {\n accessToken: string;\n refreshToken?: string;\n expiresAt?: number;\n}\n\ninterface StoredCredentials {\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n services?: Partial<Record<string, ServiceCredentials>>;\n}\n\ninterface UserInfo {\n id: string;\n email: string;\n name: string;\n}\n\nfunction deriveEncryptionKey(salt: Buffer): Buffer {\n const password = `${APP_NAME}-${homedir()}-${process.platform}`;\n return scryptSync(password, salt, ENCRYPTION.KEY_LENGTH);\n}\n\nfunction decrypt(encryptedData: EncryptedData, key: Buffer): string {\n const decipher = createDecipheriv(\n ENCRYPTION.ALGORITHM,\n key,\n Buffer.from(encryptedData.iv, 'hex'),\n { authTagLength: ENCRYPTION.AUTH_TAG_LENGTH }\n );\n decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));\n let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');\n decrypted += decipher.final('utf8');\n return decrypted;\n}\n\nfunction encrypt(data: string, key: Buffer): EncryptedData {\n const iv = randomBytes(ENCRYPTION.IV_LENGTH);\n const cipher = createCipheriv(ENCRYPTION.ALGORITHM, key, iv, {\n authTagLength: ENCRYPTION.AUTH_TAG_LENGTH,\n });\n let encrypted = cipher.update(data, 'utf8', 'hex');\n encrypted += cipher.final('hex');\n const authTag = cipher.getAuthTag();\n return {\n encrypted,\n iv: iv.toString('hex'),\n authTag: authTag.toString('hex'),\n salt: '',\n };\n}\n\n/**\n * Read stored credentials from the CLI's encrypted file\n */\nfunction readStoredCredentials(): StoredCredentials | null {\n const credentialsPath = join(homedir(), CREDENTIALS_DIR, CREDENTIALS_FILE);\n if (!existsSync(credentialsPath)) {\n return null;\n }\n\n try {\n const encryptedData: EncryptedData = JSON.parse(\n readFileSync(credentialsPath, 'utf8')\n );\n const salt = Buffer.from(encryptedData.salt, 'hex');\n const key = deriveEncryptionKey(salt);\n const decrypted = decrypt(encryptedData, key);\n return JSON.parse(decrypted);\n } catch {\n return null;\n }\n}\n\n/**\n * Write credentials to the encrypted file (shared with CLI)\n */\nfunction writeStoredCredentials(credentials: StoredCredentials): void {\n const credentialsDir = join(homedir(), CREDENTIALS_DIR);\n const credentialsPath = join(credentialsDir, CREDENTIALS_FILE);\n\n if (!existsSync(credentialsDir)) {\n mkdirSync(credentialsDir, { recursive: true, mode: 0o700 });\n }\n\n const data = JSON.stringify(credentials);\n const salt = randomBytes(ENCRYPTION.SALT_LENGTH);\n const key = deriveEncryptionKey(salt);\n const encrypted = encrypt(data, key);\n encrypted.salt = salt.toString('hex');\n\n writeFileSync(credentialsPath, JSON.stringify(encrypted), {\n mode: 0o600,\n encoding: 'utf8',\n });\n}\n\n/**\n * Get the bragduck access token from stored credentials\n */\nexport function getAccessToken(): string | null {\n const credentials = readStoredCredentials();\n if (!credentials) return null;\n\n // Handle legacy format\n if (credentials.accessToken && !credentials.services) {\n return credentials.accessToken;\n }\n\n return credentials.services?.bragduck?.accessToken || null;\n}\n\n/**\n * Get the refresh token from stored credentials\n */\nexport function getRefreshToken(): string | null {\n const credentials = readStoredCredentials();\n if (!credentials) return null;\n\n if (credentials.accessToken && !credentials.services) {\n return credentials.refreshToken || null;\n }\n\n return credentials.services?.bragduck?.refreshToken || null;\n}\n\n/**\n * Store credentials after login or token refresh\n */\nexport function storeCredentials(creds: ServiceCredentials): void {\n let existing = readStoredCredentials();\n\n // Migrate legacy format\n if (existing && existing.accessToken && !existing.services) {\n existing = {\n services: {\n bragduck: {\n accessToken: existing.accessToken,\n refreshToken: existing.refreshToken,\n expiresAt: existing.expiresAt,\n },\n },\n };\n }\n\n const updated: StoredCredentials = {\n ...existing,\n services: {\n ...existing?.services,\n bragduck: creds,\n },\n };\n\n writeStoredCredentials(updated);\n}\n\n/**\n * Get stored user info from the Conf config store (shared with CLI)\n */\nexport function getUserInfo(): UserInfo | null {\n try {\n const config = new Conf({ projectName: APP_NAME });\n return (config.get('userInfo') as UserInfo) || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Store user info in the Conf config store (shared with CLI)\n */\nexport function storeUserInfo(userInfo: UserInfo): void {\n const config = new Conf({ projectName: APP_NAME });\n config.set('userInfo', userInfo);\n}\n","import { login } from '../auth.js';\nimport { getAccessToken } from '../credentials.js';\n\nexport const loginTool = {\n name: 'login',\n description:\n 'Authenticate with BragDuck. Opens the browser for sign-in/sign-up. ' +\n 'Call this when other tools return an authentication error.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n force: {\n type: 'boolean',\n description: 'Force re-authentication even if already logged in',\n },\n },\n },\n};\n\ninterface LoginInput {\n force?: boolean;\n}\n\nexport async function handleLogin(\n input?: LoginInput\n): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> {\n // Check if already authenticated (skip if force=true)\n if (!input?.force) {\n const existingToken = getAccessToken();\n if (existingToken) {\n return {\n content: [{ type: 'text', text: 'Already authenticated with BragDuck.' }],\n };\n }\n }\n\n try {\n const user = await login();\n return {\n content: [{\n type: 'text',\n text: `Authenticated as ${user.name} (${user.email}). You can now log brags and manage objectives.`,\n }],\n };\n } catch (error) {\n return {\n content: [{\n type: 'text',\n text: `Authentication failed: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n","import { getAccessToken, getRefreshToken, storeCredentials } from './credentials.js';\n\nconst API_BASE_URL = process.env.BRAGDUCK_API_URL || 'https://api.bragduck.com';\nconst DEBUG = !!process.env.BRAGDUCK_DEBUG;\n\nconst OAUTH_CLIENT_ID = 'bragduck-cli';\nconst TOKEN_ENDPOINT = '/v1/auth/cli/token';\n\nfunction debug(...args: unknown[]) {\n if (DEBUG) console.error('[bragduck-mcp]', ...args);\n}\n\n/**\n * Attempt to refresh the access token using the stored refresh token\n */\nasync function refreshAccessToken(): Promise<string | null> {\n const refreshToken = getRefreshToken();\n if (!refreshToken) return null;\n\n try {\n const response = await fetch(`${API_BASE_URL}${TOKEN_ENDPOINT}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n refresh_token: refreshToken,\n client_id: OAUTH_CLIENT_ID,\n grant_type: 'refresh_token',\n }),\n });\n\n if (!response.ok) return null;\n\n const data = await response.json() as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n };\n\n const expiresAt = data.expires_in\n ? Date.now() + data.expires_in * 1000\n : undefined;\n\n storeCredentials({\n accessToken: data.access_token,\n refreshToken: data.refresh_token || refreshToken,\n expiresAt,\n });\n\n return data.access_token;\n } catch {\n return null;\n }\n}\n\n/**\n * Make an authenticated API request to the BragDuck backend.\n * Automatically retries once on 401 with a refreshed token.\n */\nexport async function apiRequest<T>(\n endpoint: string,\n options: {\n method?: string;\n body?: unknown;\n params?: Record<string, string | string[]>;\n } = {}\n): Promise<T> {\n const { method = 'GET', body, params } = options;\n\n let token = getAccessToken();\n if (!token) {\n throw new Error('NOT_AUTHENTICATED');\n }\n\n let url = `${API_BASE_URL}${endpoint}`;\n if (params) {\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (Array.isArray(value)) {\n value.forEach((v) => searchParams.append(`${key}[]`, v));\n } else {\n searchParams.append(key, value);\n }\n }\n url += `?${searchParams.toString()}`;\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n 'User-Agent': 'BragDuck-MCP/1.0.0',\n };\n if (body) {\n headers['Content-Type'] = 'application/json';\n }\n\n const bodyString = body ? JSON.stringify(body) : undefined;\n\n debug(`${method} ${url}`);\n if (bodyString) debug('Request body:', bodyString);\n\n let response = await fetch(url, {\n method,\n headers,\n body: bodyString,\n });\n\n debug(`Response: ${response.status} ${response.statusText}`);\n\n // On 401, try refreshing the token once\n if (response.status === 401) {\n const newToken = await refreshAccessToken();\n if (!newToken) {\n throw new Error('TOKEN_EXPIRED');\n }\n\n headers.Authorization = `Bearer ${newToken}`;\n response = await fetch(url, {\n method,\n headers,\n body: bodyString,\n });\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n debug('Error response body:', text);\n let msg = `API error: ${response.status} ${response.statusText}`;\n try {\n const errorData = JSON.parse(text) as {\n message?: string;\n error?: string;\n };\n msg = errorData.message || errorData.error || msg;\n } catch {\n if (text) msg = text;\n }\n throw new Error(msg);\n }\n\n // DELETE endpoints answer 204 with no body\n if (response.status === 204) return undefined as T;\n\n return response.json() as Promise<T>;\n}\n\ninterface SubscriptionStatus {\n tier: string;\n status: string;\n}\n\n/**\n * Fetch the current user's subscription tier.\n */\nexport async function getSubscriptionStatus(): Promise<SubscriptionStatus> {\n return apiRequest<SubscriptionStatus>('/v1/subscriptions/status');\n}\n\n/**\n * Returns an MCP error response if the user is on the FREE plan, otherwise null.\n * Call this at the start of every non-login tool handler.\n */\nexport async function requirePaidPlan(): Promise<{\n content: Array<{ type: string; text: string }>;\n isError: true;\n} | null> {\n try {\n const { tier } = await getSubscriptionStatus();\n if (tier === 'FREE') {\n return {\n content: [\n {\n type: 'text',\n text:\n 'BragDuck MCP requires a PLUS or PRO subscription.\\n\\n' +\n 'The free plan does not include CLI or MCP access.\\n' +\n 'Upgrade at: https://bragduck.com/app/settings/plans',\n },\n ],\n isError: true,\n };\n }\n return null;\n } catch {\n // If subscription check fails (e.g. network issue), let the request proceed\n return null;\n }\n}\n","import { apiRequest, requirePaidPlan } from '../api-client.js';\nimport type { CreateCliBragsResponse } from '@bragduck/types';\n\nexport const logBragTool = {\n name: 'log_brag',\n description:\n 'Persist a career achievement (brag) to BragDuck. ' +\n 'Call this in the same response as the preview table — the tool permission prompt (Allow/Deny) ' +\n 'that appears to the user IS the confirmation. Do not ask the user to type anything.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n title: {\n type: 'string',\n minLength: 10,\n maxLength: 200,\n description: 'Short title of the achievement (must be 10-200 characters)',\n },\n description: {\n type: 'string',\n minLength: 50,\n maxLength: 2000,\n description: 'Detailed description of what was accomplished and its impact (must be 50-2000 characters)',\n },\n tags: {\n type: 'array',\n items: { type: 'string', minLength: 2, maxLength: 30 },\n maxItems: 10,\n description: 'Tags to categorize the brag (e.g., \"performance\", \"leadership\", \"backend\"). Each tag must be 2-30 characters. Maximum 10 tags.',\n },\n date: {\n type: 'string',\n description: 'ISO datetime string for when this happened (defaults to now)',\n },\n impact_level: {\n type: 'number',\n enum: [0, 1, 2, 3],\n description:\n 'Impact level of the achievement. ' +\n '0 = none (personal learning, routine tasks). ' +\n '1 = low (individual contributor impact, fixed small bug). ' +\n '2 = medium (team-level impact, improved team process, led project). ' +\n '3 = high (organization-level impact, reduced costs, improved key metrics).',\n },\n impact_description: {\n type: 'string',\n description:\n 'Narrative description of the impact. ' +\n 'Start with scope (Individual/Team/Organization/Company-wide), ' +\n 'then include 1-3 measurable outcomes. ' +\n 'Example: \"Team impact: Reduced deploy time by 60%, unblocking 4 engineers\"',\n },\n type: {\n type: 'string',\n enum: [\n 'progress',\n 'achievement',\n 'recognition',\n 'support',\n 'code',\n 'leadership',\n 'learning',\n 'collaboration',\n 'challenge',\n 'innovation',\n 'meeting',\n 'bug',\n 'others',\n ],\n description: 'Type of achievement. Choose the most appropriate category for this brag.',\n },\n references: {\n type: 'array',\n items: { type: 'string', format: 'uri' },\n maxItems: 10,\n description:\n 'URLs to reference materials for this achievement. ' +\n 'Include any relevant links that provide context or proof: ' +\n 'pull request URLs, Jira/Linear tickets, commit URLs, design docs, Notion pages, etc. ' +\n 'Example: [\"https://github.com/org/repo/pull/123\", \"https://jira.company.com/browse/PROJ-456\"]',\n },\n orgId: {\n type: 'string',\n description: 'Organisation ID to associate the brag with',\n },\n agent: {\n type: 'string',\n maxLength: 50,\n description:\n 'Optional AI agent/client name for MCP-origin brags, e.g. \"claude\", \"codex\", \"cursor\", or \"copilot\".',\n },\n },\n required: ['title', 'description', 'type', 'impact_level', 'impact_description', 'references'],\n },\n};\n\ninterface LogBragInput {\n title: string;\n description: string;\n type: string;\n impact_level: number;\n impact_description: string;\n tags?: string[];\n date?: string;\n references?: string[];\n orgId?: string;\n agent?: string;\n}\n\nfunction toAgentExternalType(agent?: string): string | undefined {\n if (!agent) return undefined;\n\n const normalized = agent\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, '-')\n .replace(/[^a-z0-9_-]/g, '');\n\n if (normalized.length === 0) return undefined;\n\n return `agent:${normalized.slice(0, 50)}`;\n}\n\nexport async function handleLogBrag(\n input: LogBragInput\n): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n const brag: Record<string, unknown> = {\n title: input.title,\n message: input.description,\n description: input.description,\n tags: input.tags || [],\n date: input.date || new Date().toISOString(),\n visibility: 'private',\n };\n\n if (input.impact_level !== undefined) brag.impactLevel = input.impact_level;\n if (input.impact_description) brag.impact_description = input.impact_description;\n if (input.type) brag.typeKey = input.type;\n if (input.references && input.references.length > 0) brag.attachments = input.references;\n if (input.orgId) brag.orgId = input.orgId;\n const agentExternalType = toAgentExternalType(input.agent);\n if (agentExternalType) brag.externalType = agentExternalType;\n\n const response = await apiRequest<CreateCliBragsResponse>('/v1/brags', {\n method: 'POST',\n body: { brags: [brag], source: 'mcp' },\n });\n\n if (response.brags && response.brags.length > 0) {\n const created = response.brags[0];\n const lines = [`Brag logged: \"${created.title}\"\\nView it at: ${created.url}`];\n if (created.keyResultIds && created.keyResultIds.length > 0) {\n lines.push(`Automatically linked to key result: ${created.keyResultIds[0]}`);\n }\n return {\n content: [{\n type: 'text',\n text: lines.join('\\n'),\n }],\n };\n }\n\n // Creation failed — surface the first error message from the server\n const firstError = response.errors && response.errors.length > 0\n ? response.errors[0].message\n : 'Server returned no brag and no error detail';\n return {\n content: [{\n type: 'text',\n text: `Failed to create brag: ${firstError}`,\n }],\n isError: true,\n };\n } catch (error) {\n if (error instanceof Error && (error.message === 'NOT_AUTHENTICATED' || error.message === 'TOKEN_EXPIRED')) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n return {\n content: [{\n type: 'text',\n text: `Failed to create brag: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n","import { apiRequest, requirePaidPlan } from '../api-client.js';\nimport type { CreateCliBragsResponse } from '@bragduck/types';\n\nexport const quickBragTool = {\n name: 'quick_brag',\n description:\n 'Log a brag from raw text. Accepts free-form text (a sentence, paragraph, or paste from a message/PR/doc) ' +\n 'and uses AI to extract and structure it into a proper brag. The AI will parse the text into a title, ' +\n 'description, type, impact, and tags, then log it. Use this when the user says \"/brag\" followed by text, ' +\n 'or pastes something and asks to log it as a brag.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n text: {\n type: 'string',\n minLength: 10,\n maxLength: 5000,\n description:\n 'Raw text describing the achievement. Can be a sentence, paragraph, paste from a PR description, ' +\n 'Slack message, commit message, or any free-form text. The AI will extract brag fields from it.',\n },\n orgId: {\n type: 'string',\n description: 'Organisation ID to associate the brag with (optional)',\n },\n agent: {\n type: 'string',\n maxLength: 50,\n description: 'Optional AI agent/client name, e.g. \"claude\", \"cursor\", \"copilot\".',\n },\n },\n required: ['text'],\n },\n};\n\ninterface QuickBragInput {\n text: string;\n orgId?: string;\n agent?: string;\n}\n\ninterface RefinedBrag {\n refined_title: string;\n refined_description: string;\n suggested_typeId: string;\n suggested_type_key: string;\n suggested_impactLevel: number;\n impact_description: string;\n suggested_tags: string[];\n extracted_links: string[];\n attachments: string[];\n date: string;\n}\n\ninterface RefineResponse {\n refined_brags: RefinedBrag[];\n processing_time_ms: number;\n tokens_used: number;\n}\n\nfunction toAgentExternalType(agent?: string): string | undefined {\n if (!agent) return undefined;\n const normalized = agent\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, '-')\n .replace(/[^a-z0-9_-]/g, '');\n if (normalized.length === 0) return undefined;\n return `agent:${normalized.slice(0, 50)}`;\n}\n\nexport async function handleQuickBrag(\n input: QuickBragInput\n): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n // Step 1: Refine raw text into structured brag via AI\n const refined = await apiRequest<RefineResponse>('/v1/brags/refine', {\n method: 'POST',\n body: { brags: [{ text: input.text }] },\n });\n\n if (!refined.refined_brags || refined.refined_brags.length === 0) {\n return {\n content: [{\n type: 'text',\n text: 'Could not extract a brag from the provided text. Try adding more detail about what was accomplished and its impact.',\n }],\n isError: true,\n };\n }\n\n // Step 2: Log the refined brag\n const extracted = refined.refined_brags[0];\n const brag: Record<string, unknown> = {\n title: extracted.refined_title,\n description: extracted.refined_description,\n tags: extracted.suggested_tags || [],\n date: new Date().toISOString(),\n visibility: 'private',\n impactLevel: extracted.suggested_impactLevel,\n impact_description: extracted.impact_description || '',\n typeKey: extracted.suggested_type_key,\n };\n\n const allLinks = [\n ...(extracted.extracted_links || []),\n ...(extracted.attachments || []),\n ].filter(Boolean);\n if (allLinks.length > 0) brag.attachments = allLinks;\n if (input.orgId) brag.orgId = input.orgId;\n const agentExternalType = toAgentExternalType(input.agent);\n if (agentExternalType) brag.externalType = agentExternalType;\n\n const response = await apiRequest<CreateCliBragsResponse>('/v1/brags', {\n method: 'POST',\n body: { brags: [brag], source: 'mcp' },\n });\n\n // Check for validation errors first\n if (response.errors && response.errors.length > 0) {\n const errorMessages = response.errors.map((e) => e.message).join('; ');\n return {\n content: [{\n type: 'text',\n text: `Failed to create brag: ${errorMessages}`,\n }],\n isError: true,\n };\n }\n\n if (response.brags && response.brags.length > 0) {\n const created = response.brags[0];\n const lines = [\n `Brag logged: \"${created.title}\"`,\n `${created.url}`,\n ];\n if (created.keyResultIds && created.keyResultIds.length > 0) {\n lines.push(`Automatically linked to key result: ${created.keyResultIds[0]}`);\n }\n return {\n content: [{\n type: 'text',\n text: lines.join('\\n'),\n }],\n };\n }\n\n return {\n content: [{\n type: 'text',\n text: 'Server returned no brag and no error detail',\n }],\n isError: true,\n };\n } catch (error) {\n if (error instanceof Error && (error.message === 'NOT_AUTHENTICATED' || error.message === 'TOKEN_EXPIRED')) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n return {\n content: [{\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n","const IMPACT_LABELS: Record<number, string> = {\n 0: 'None (personal learning / routine)',\n 1: 'Low (individual contributor impact)',\n 2: 'Medium (team-level impact)',\n 3: 'High (organisation-level impact)',\n};\n\nconst TYPE_EMOJIS: Record<string, string> = {\n progress: '📈',\n achievement: '🏆',\n recognition: '🌟',\n support: '🤝',\n code: '💻',\n leadership: '🧭',\n learning: '📚',\n collaboration: '👥',\n challenge: '⚔️',\n innovation: '💡',\n meeting: '🗣️',\n bug: '🐛',\n others: '📌',\n};\n\nexport const previewBragTool = {\n name: 'preview_brag',\n description:\n 'Show the user a formatted preview of a proposed brag BEFORE logging it. ' +\n 'Only call this when: (1) the user has confirmed they want to log a brag, ' +\n '(2) you have assessed the achievement as bragable, and ' +\n '(3) you have enough concrete detail to fill every required field without fabricating. ' +\n 'If any of those conditions are not met, do not call this — ask clarifying questions instead.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n title: {\n type: 'string',\n minLength: 10,\n maxLength: 200,\n description: 'Short title of the achievement (10-200 characters)',\n },\n description: {\n type: 'string',\n minLength: 50,\n maxLength: 2000,\n description: 'Detailed description of what was accomplished and its impact (50-2000 characters)',\n },\n type: {\n type: 'string',\n enum: [\n 'progress', 'achievement', 'recognition', 'support', 'code',\n 'leadership', 'learning', 'collaboration', 'challenge', 'innovation',\n 'meeting', 'bug', 'others',\n ],\n description: 'Category that best fits this achievement',\n },\n impact_level: {\n type: 'number',\n enum: [0, 1, 2, 3],\n description: '0=none, 1=low (individual), 2=medium (team), 3=high (org)',\n },\n impact_description: {\n type: 'string',\n description:\n 'Narrative of the impact. Start with scope (Individual/Team/Organization), ' +\n 'then 1-3 measurable outcomes.',\n },\n tags: {\n type: 'array',\n items: { type: 'string', minLength: 2, maxLength: 30 },\n maxItems: 10,\n description: 'Tags to categorise the brag',\n },\n references: {\n type: 'array',\n items: { type: 'string', format: 'uri' },\n maxItems: 10,\n description: 'URLs: PRs, tickets, commits, design docs, etc.',\n },\n },\n required: ['title', 'description', 'type', 'impact_level', 'impact_description'],\n },\n};\n\ninterface PreviewBragInput {\n title: string;\n description: string;\n type: string;\n impact_level: number;\n impact_description: string;\n tags?: string[];\n references?: string[];\n}\n\nexport function handlePreviewBrag(\n input: PreviewBragInput\n): { content: Array<{ type: string; text: string }> } {\n const emoji = TYPE_EMOJIS[input.type] ?? '📌';\n const impactLabel = IMPACT_LABELS[input.impact_level] ?? String(input.impact_level);\n\n // Return structured data so the AI renders the table natively in its own\n // response (AI-generated markdown renders correctly; tool-result text does not).\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n title: input.title,\n type: `${emoji} ${input.type}`,\n impact_level: impactLabel,\n impact_details: input.impact_description,\n description: input.description,\n tags: input.tags && input.tags.length > 0 ? input.tags.join(', ') : '—',\n references:\n input.references && input.references.length > 0\n ? input.references.join(', ')\n : '—',\n }),\n },\n ],\n };\n}\n","import { apiRequest, requirePaidPlan } from '../api-client.js';\nimport type { ListCliBragsResponse } from '@bragduck/types';\n\nexport const listBragsTool = {\n name: 'list_brags',\n description:\n 'List recent brags (career achievements) from BragDuck. ' +\n 'Use this to review past accomplishments, find brags to link to keyResults, or check recent entries.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n limit: {\n type: 'number',\n description: 'Number of brags to return (default: 10, max: 100)',\n },\n search: {\n type: 'string',\n description: 'Search term to filter brags by title or description',\n },\n tags: {\n type: 'array',\n items: { type: 'string' },\n description: 'Filter brags by tags',\n },\n date_from: {\n type: 'string',\n description: 'Filter brags from this date (YYYY-MM-DD)',\n },\n date_to: {\n type: 'string',\n description: 'Filter brags up to this date (YYYY-MM-DD)',\n },\n sort: {\n type: 'string',\n enum: ['date_asc', 'date_desc', 'title_asc', 'title_desc', 'created_asc', 'created_desc'],\n description: 'Sort order (default: date_desc)',\n },\n },\n },\n};\n\ninterface ListBragsInput {\n limit?: number;\n search?: string;\n tags?: string[];\n date_from?: string;\n date_to?: string;\n sort?: string;\n}\n\nconst IMPACT_LABELS: Record<number, string> = {\n 0: 'None',\n 1: 'Low',\n 2: 'Medium',\n 3: 'High',\n};\n\nexport async function handleListBrags(\n input: ListBragsInput\n): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n const params: Record<string, string | string[]> = {\n limit: String(input.limit || 10),\n offset: '0',\n };\n if (input.search) params.search = input.search;\n if (input.tags && input.tags.length > 0) params['tags[]'] = input.tags;\n if (input.date_from) params.date_from = input.date_from;\n if (input.date_to) params.date_to = input.date_to;\n if (input.sort) params.sort = input.sort;\n\n const response = await apiRequest<ListCliBragsResponse>('/v1/brags', { params });\n\n if (response.brags.length === 0) {\n return {\n content: [{ type: 'text', text: 'No brags found.' }],\n };\n }\n\n const lines = response.brags.map((brag) => {\n const tags = brag.tags.length > 0 ? ` [${brag.tags.join(', ')}]` : '';\n const date = new Date(brag.date).toLocaleDateString();\n const impact = `Impact: ${IMPACT_LABELS[brag.impactLevel ?? 0] ?? (brag.impactLevel ?? 0)}`;\n const type = brag.type ? ` | Type: ${brag.type}` : '';\n const keyResultNote = brag.keyResultIds?.length > 0\n ? `\\n Linked to key result: ${brag.keyResultIds[0]}`\n : '';\n const impactDesc = brag.impactDescription ? `\\n Impact: ${brag.impactDescription}` : '';\n return (\n `- **${brag.title}** (ID: ${brag.id}, ${date})${tags}\\n` +\n ` ${impact}${type}` +\n impactDesc +\n keyResultNote +\n `\\n ${brag.description.slice(0, 150)}${brag.description.length > 150 ? '...' : ''}` +\n `\\n View: ${brag.url}`\n );\n });\n\n const { total, has_more } = response.pagination;\n const summary = `Found ${total} brags (showing ${response.brags.length}${has_more ? ', more available' : ''}):`;\n\n return {\n content: [{ type: 'text', text: `${summary}\\n\\n${lines.join('\\n\\n')}` }],\n };\n } catch (error) {\n if (error instanceof Error && (error.message === 'NOT_AUTHENTICATED' || error.message === 'TOKEN_EXPIRED')) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n return {\n content: [{\n type: 'text',\n text: `Failed to list brags: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n","import { apiRequest, requirePaidPlan } from '../api-client.js';\nimport { getUserInfo } from '../credentials.js';\nimport type { ObjectiveResponse } from '@bragduck/types';\n\nexport const listObjectivesTool = {\n name: 'list_objectives',\n description:\n 'List OKRs (Objectives and Key Results) from BragDuck. ' +\n 'Use this to see current keyResults and their IDs, which can be used to link brags to specific keyResults.',\n inputSchema: {\n type: 'object' as const,\n properties: {},\n },\n};\n\ninterface ListObjectivesResponse {\n items: ObjectiveResponse[];\n total: number;\n}\n\nexport async function handleListObjectives(): Promise<{\n content: Array<{ type: string; text: string }>;\n isError?: boolean;\n}> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n const userInfo = getUserInfo();\n if (!userInfo) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n\n const response = await apiRequest<ListObjectivesResponse>(\n `/v1/users/${userInfo.id}/objectives`\n );\n\n if (response.items.length === 0) {\n return {\n content: [{ type: 'text', text: 'No objectives found.' }],\n };\n }\n\n const lines = response.items.map((obj) => {\n const keyResults = obj.keyResults\n .map((g) => ` - ${g.label} (KeyResult ID: ${g.id}, status: ${g.status})`)\n .join('\\n');\n const category = obj.category ? ` [${obj.category}]` : '';\n return `- **${obj.title}** (ID: ${obj.id}, status: ${obj.status})${category}\\n${keyResults || ' (no keyResults)'}`;\n });\n\n return {\n content: [{\n type: 'text',\n text: `Found ${response.total} objectives:\\n\\n${lines.join('\\n\\n')}`,\n }],\n };\n } catch (error) {\n if (error instanceof Error && (error.message === 'NOT_AUTHENTICATED' || error.message === 'TOKEN_EXPIRED')) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n return {\n content: [{\n type: 'text',\n text: `Failed to list objectives: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n","import { apiRequest, requirePaidPlan } from '../api-client.js';\nimport { getUserInfo } from '../credentials.js';\n\nexport const linkBragToKeyResultTool = {\n name: 'link_brag_to_key_result',\n description:\n 'Add a keyResult/key-result to a brag (supports multiple keyResults per brag). ' +\n 'Use list_objectives to find keyResult IDs and list_brags to find brag IDs.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n bragId: {\n type: 'string',\n description: 'The ID of the brag to update',\n },\n keyResultId: {\n type: 'string',\n description: 'The ID of the keyResult to link the brag to',\n },\n },\n required: ['bragId', 'keyResultId'],\n },\n};\n\ninterface LinkBragInput {\n bragId: string;\n keyResultId: string;\n}\n\nexport async function handleLinkBragToKeyResult(\n input: LinkBragInput\n): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n const userInfo = getUserInfo();\n if (!userInfo) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n\n // Fetch current brag to read existing keyResultIds\n const currentBrag = await apiRequest<{ keyResultIds?: string[] }>(\n `/v1/users/${userInfo.id}/brags/${input.bragId}`\n );\n\n const existingKeyResultIds: string[] = currentBrag.keyResultIds || [];\n\n // Skip if already linked\n if (existingKeyResultIds.includes(input.keyResultId)) {\n return {\n content: [{\n type: 'text',\n text: `Brag ${input.bragId} is already linked to keyResult ${input.keyResultId}.`,\n }],\n };\n }\n\n // Append new keyResult (deduplicated via the check above)\n const updatedKeyResultIds = [...existingKeyResultIds, input.keyResultId];\n\n await apiRequest(`/v1/users/${userInfo.id}/brags/${input.bragId}`, {\n method: 'PATCH',\n body: { keyResultIds: updatedKeyResultIds },\n });\n\n return {\n content: [{\n type: 'text',\n text: `KeyResult ${input.keyResultId} added to brag ${input.bragId} (now linked to ${updatedKeyResultIds.length} keyResult${updatedKeyResultIds.length === 1 ? '' : 's'}).`,\n }],\n };\n } catch (error) {\n if (error instanceof Error && (error.message === 'NOT_AUTHENTICATED' || error.message === 'TOKEN_EXPIRED')) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n return {\n content: [{\n type: 'text',\n text: `Failed to link brag to keyResult: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n","import Conf from 'conf';\n\nconst APP_NAME = 'bragduck';\n\ninterface OrgPreference {\n orgId: string;\n orgName: string;\n}\n\ntype OrgPreferences = Record<string, OrgPreference>;\n\nfunction getConfig(): Conf {\n return new Conf({ projectName: APP_NAME });\n}\n\nexport function getOrgPreference(cwd: string): OrgPreference | null {\n try {\n const config = getConfig();\n const prefs = (config.get('orgPreferences') as OrgPreferences) || {};\n return prefs[cwd] || null;\n } catch {\n return null;\n }\n}\n\nexport function saveOrgPreference(cwd: string, orgId: string, orgName: string): void {\n const config = getConfig();\n const prefs = (config.get('orgPreferences') as OrgPreferences) || {};\n prefs[cwd] = { orgId, orgName };\n config.set('orgPreferences', prefs);\n}\n","import { apiRequest, requirePaidPlan } from '../api-client.js';\nimport { getUserInfo } from '../credentials.js';\nimport { getOrgPreference } from '../org-preference.js';\n\ninterface OrganisationItem {\n id: string;\n name: string;\n role: string;\n current: boolean;\n startDate: string;\n endDate: string | null;\n}\n\ninterface ListOrganisationsResponse {\n items: OrganisationItem[];\n}\n\nexport const listOrganisationsTool = {\n name: 'list_organisations',\n description:\n 'List the user\\'s organisations (companies) from BragDuck. ' +\n 'Also returns the saved organisation preference for the current working directory, if any. ' +\n 'Use this before logging a brag to determine which company to associate it with.',\n inputSchema: {\n type: 'object' as const,\n properties: {},\n },\n};\n\nexport async function handleListOrganisations(): Promise<{\n content: Array<{ type: string; text: string }>;\n isError?: boolean;\n}> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n const userInfo = getUserInfo();\n if (!userInfo) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n\n const response = await apiRequest<ListOrganisationsResponse>(\n `/v1/users/${userInfo.id}/organisations`\n );\n\n if (response.items.length === 0) {\n return {\n content: [{\n type: 'text',\n text: 'No organisations found. You can add companies in the BragDuck web app.',\n }],\n };\n }\n\n const cwd = process.cwd();\n const saved = getOrgPreference(cwd);\n\n const lines = response.items.map((org) => {\n const status = org.current ? 'current' : 'former';\n return `- **${org.name}** (ID: ${org.id}, role: ${org.role}, ${status})`;\n });\n\n const savedLine = saved\n ? `\\nSaved preference for this directory: **${saved.orgName}** (ID: ${saved.orgId})`\n : '\\nNo saved preference for this directory.';\n\n return {\n content: [{\n type: 'text',\n text: `Found ${response.items.length} organisation(s):\\n\\n${lines.join('\\n')}${savedLine}`,\n }],\n };\n } catch (error) {\n if (error instanceof Error && (error.message === 'NOT_AUTHENTICATED' || error.message === 'TOKEN_EXPIRED')) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n return {\n content: [{\n type: 'text',\n text: `Failed to list organisations: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n","import { requirePaidPlan } from '../api-client.js';\nimport { saveOrgPreference } from '../org-preference.js';\n\nexport const saveOrgPreferenceTool = {\n name: 'save_org_preference',\n description:\n 'Save the organisation preference for the current working directory. ' +\n 'Call this after the user selects which company a brag belongs to, ' +\n 'so the same choice is remembered for future brags in this directory.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n orgId: {\n type: 'string',\n description: 'The organisation ID to save as the preference for this directory',\n },\n orgName: {\n type: 'string',\n description: 'The organisation name (for display purposes)',\n },\n },\n required: ['orgId', 'orgName'],\n },\n};\n\ninterface SaveOrgPreferenceInput {\n orgId: string;\n orgName: string;\n}\n\nexport async function handleSaveOrgPreference(\n input: SaveOrgPreferenceInput\n): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n const cwd = process.cwd();\n saveOrgPreference(cwd, input.orgId, input.orgName);\n return {\n content: [{\n type: 'text',\n text: `Saved: brags in this directory will be associated with **${input.orgName}**.`,\n }],\n };\n } catch (error) {\n return {\n content: [{\n type: 'text',\n text: `Failed to save preference: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n","import { apiRequest, requirePaidPlan } from '../api-client.js';\nimport { getUserInfo } from '../credentials.js';\nimport type { StoryItem } from '@bragduck/types';\n\nexport const logStoryTool = {\n name: 'log_story',\n description:\n 'Save a STAR-formatted interview story to BragDuck. ' +\n 'Use this when the user discusses interview prep, behavioral questions, or asks to save a story. ' +\n 'Each story has Situation, Task, Action, Result fields plus competency tags. ' +\n 'Call this in the same response as a preview — the tool permission prompt (Allow/Deny) ' +\n 'that appears to the user IS the confirmation.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n title: {\n type: 'string',\n minLength: 5,\n maxLength: 200,\n description: 'Short title for the story (e.g., \"Led migration to microservices\")',\n },\n situation: {\n type: 'string',\n minLength: 10,\n description: 'The context and background — what was happening, what was the environment',\n },\n task: {\n type: 'string',\n minLength: 10,\n description: 'What was your specific responsibility or challenge',\n },\n action: {\n type: 'string',\n minLength: 10,\n description: 'What you specifically did — the steps you took',\n },\n result: {\n type: 'string',\n minLength: 10,\n description: 'The outcome — measurable impact, what changed, what you learned',\n },\n competencies: {\n type: 'array',\n items: { type: 'string', minLength: 2, maxLength: 50 },\n maxItems: 20,\n description:\n 'Skills or competencies this story demonstrates (e.g., \"leadership\", \"system design\", \"conflict resolution\")',\n },\n bragIds: {\n type: 'array',\n items: { type: 'string' },\n maxItems: 20,\n description: 'IDs of BragDuck brags that back this story',\n },\n orgId: {\n type: 'string',\n description: 'Organisation ID to associate the story with',\n },\n category: {\n type: 'string',\n description: 'Interview category this story demonstrates (e.g. leadership, technical_excellence, problem_solving, innovation, teamwork, communication, project_delivery, customer_impact, mentorship, process_improvement)',\n },\n },\n required: ['title', 'situation', 'task', 'action', 'result'],\n },\n};\n\ninterface LogStoryInput {\n title: string;\n situation: string;\n task: string;\n action: string;\n result: string;\n competencies?: string[];\n bragIds?: string[];\n orgId?: string;\n category?: string;\n}\n\nexport async function handleLogStory(\n input: LogStoryInput\n): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n const userInfo = getUserInfo();\n if (!userInfo) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n\n const body: Record<string, unknown> = {\n title: input.title,\n situation: input.situation,\n task: input.task,\n action: input.action,\n result: input.result,\n competencies: input.competencies || [],\n bragIds: input.bragIds || [],\n source: 'mcp',\n };\n\n if (input.orgId) body.orgId = input.orgId;\n if (input.category) body.category = input.category;\n\n const story = await apiRequest<StoryItem>(\n `/v1/users/${userInfo.id}/stories`,\n { method: 'POST', body }\n );\n\n const competencyLine = story.competencies.length > 0\n ? `\\nCompetencies: ${story.competencies.join(', ')}`\n : '';\n\n return {\n content: [{\n type: 'text',\n text: `Story saved: \"${story.title}\"${competencyLine}`,\n }],\n };\n } catch (error) {\n if (error instanceof Error && (error.message === 'NOT_AUTHENTICATED' || error.message === 'TOKEN_EXPIRED')) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n return {\n content: [{\n type: 'text',\n text: `Failed to save story: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n","/**\n * Feature Flag Configuration for Subscription System\n *\n * Defines features and their limits across FREE, PLUS, and PRO tiers.\n */\n\nexport enum Feature {\n // Core features (always available)\n BASIC_BRAGS = 'basic_brags',\n BASIC_ORGANIZATIONS = 'basic_organizations',\n\n // Gated features with quotas\n AI_REPORT_GENERATION = 'ai_report_generation',\n PDF_EXPORT = 'pdf_export',\n SHARE_LINKS = 'share_links',\n OBJECTIVES = 'objectives',\n\n // Gated features (on/off)\n ADVANCED_NOTIFICATIONS = 'advanced_notifications',\n FULL_EXPORT_IMPORT = 'full_export_import',\n CLI_TURBO_MODE = 'cli_turbo_mode',\n PRIORITY_SUPPORT = 'priority_support',\n EARLY_ACCESS = 'early_access',\n SMART_GOAL_SUGGESTIONS = 'smart_goal_suggestions',\n AI_POWERED_SUGGESTIONS = 'ai_powered_suggestions',\n\n // Plus-gated AI assistant\n ASK_DUCKY = 'ask_ducky',\n\n // PRO-only token-based feature\n AI_TOKEN_BUDGET = 'ai_token_budget',\n\n // AI-powered feature: generate objectives from feedback text\n GENERATE_OBJECTIVES = 'generate_objectives',\n\n // Pro-only multi-email account\n MULTI_EMAIL = 'multi_email',\n\n // Org-role / team-role gated features (access-control unification — not tier-gated,\n // enabled: false on every tier below; granted instead via ROLE_FEATURES in\n // ../access-control/roles.ts based on org role or team role).\n // Guard-backing features (reproduce today's org-auth.ts / manager-auth.ts predicates)\n ORG_ACCESS = 'org_access',\n ORG_MUTATE_BASE = 'org_mutate_base',\n ORG_ADMIN_ACCESS = 'org_admin_access',\n ORG_OWNER_ACCESS = 'org_owner_access',\n ORG_GROWTH_ADMIN_ACCESS = 'org_growth_admin_access',\n MANAGER_TIER_ACCESS = 'manager_tier_access',\n TEAM_MANAGE = 'team_manage',\n TEAM_OWNER_ACTIONS = 'team_owner_actions',\n TEAM_MEMBERSHIP_VIEW = 'team_membership_view',\n // Granular features for future call-site migration (inert today — no route uses\n // them yet; each currently resolves identically to its coarse counterpart above)\n ORG_MEMBERS_MANAGE = 'org_members_manage',\n ORG_INVITES_MANAGE = 'org_invites_manage',\n ORG_TEAMS_MANAGE = 'org_teams_manage',\n ORG_GROWTH_VIEW = 'org_growth_view',\n ORG_CAREER_METRICS_MANAGE = 'org_career_metrics_manage',\n ORG_CYCLES_MANAGE = 'org_cycles_manage',\n ORG_STATS_VIEW = 'org_stats_view',\n ORG_AUDIT_LOG_VIEW = 'org_audit_log_view',\n ORG_BILLING_MANAGE = 'org_billing_manage',\n ORG_NOTIFICATIONS_MANAGE = 'org_notifications_manage',\n\n // ── Universal features (all tiers) ──\n // Core CRUD, profile management, basic notifications, team invite\n // accept/reject, nav pages, and settings tabs. Every authenticated user\n // gets these regardless of plan tier.\n\n // Content/core\n BRAGS_CREATE = 'brags_create',\n BRAGS_READ = 'brags_read',\n BRAGS_UPDATE = 'brags_update',\n BRAGS_DELETE = 'brags_delete',\n STORIES_CREATE = 'stories_create',\n STORIES_READ = 'stories_read',\n STORIES_UPDATE = 'stories_update',\n STORIES_DELETE = 'stories_delete',\n // ── Plus-gated features (Plus, Pro only) ──\n BRAGS_EXPORT = 'brags_export',\n BRAGS_IMPORT = 'brags_import',\n // (back to universal)\n BRAG_COMMENTS_CREATE = 'brag_comments_create',\n BRAG_COMMENTS_READ = 'brag_comments_read',\n BRAG_COMMENTS_UPDATE = 'brag_comments_update',\n BRAG_COMMENTS_DELETE = 'brag_comments_delete',\n BRAG_TYPES_CREATE = 'brag_types_create',\n BRAG_TYPES_READ = 'brag_types_read',\n BRAG_TYPES_UPDATE = 'brag_types_update',\n OBJECTIVES_READ = 'objectives_read',\n OBJECTIVES_UPDATE = 'objectives_update',\n OBJECTIVES_DELETE = 'objectives_delete',\n OBJECTIVES_KEY_RESULTS_CREATE = 'objectives_key_results_create',\n OBJECTIVES_KEY_RESULTS_UPDATE = 'objectives_key_results_update',\n OBJECTIVES_KEY_RESULTS_DELETE = 'objectives_key_results_delete',\n REPORTS_READ = 'reports_read',\n REPORTS_UPDATE = 'reports_update',\n REPORTS_DELETE = 'reports_delete',\n REPORTS_CSV_EXPORT = 'reports_csv_export', // Plus-gated (aligns with FULL_EXPORT_IMPORT)\n SHARES_READ = 'shares_read',\n SHARES_DELETE = 'shares_delete',\n FIELDS_READ = 'fields_read',\n\n // ── Plus-gated: BYOK (Bring Your Own Key) ──\n LLM_KEYS_CREATE = 'llm_keys_create',\n LLM_KEYS_READ = 'llm_keys_read',\n LLM_KEYS_UPDATE = 'llm_keys_update',\n LLM_KEYS_DELETE = 'llm_keys_delete',\n LLM_KEYS_VALIDATE = 'llm_keys_validate',\n LLM_SETTINGS_READ = 'llm_settings_read',\n LLM_SETTINGS_UPDATE = 'llm_settings_update',\n // (back to universal)\n USER_EMAILS_READ = 'user_emails_read',\n // ── Pro-gated: multi-email (Pro only) ──\n USER_EMAILS_UPDATE = 'user_emails_update',\n USER_EMAILS_DELETE = 'user_emails_delete',\n USER_EMAILS_VERIFY = 'user_emails_verify',\n // Plus-gated (legacy BYOK path)\n USER_SETTINGS_OPENAI_KEY_MANAGE = 'user_settings_openai_key_manage',\n NOTIFICATIONS_PREFERENCES_READ = 'notifications_preferences_read',\n NOTIFICATIONS_PREFERENCES_UPDATE = 'notifications_preferences_update',\n // Plus-gated (aligns with ADVANCED_NOTIFICATIONS)\n NOTIFICATIONS_SETTINGS_READ = 'notifications_settings_read',\n NOTIFICATIONS_SETTINGS_UPDATE = 'notifications_settings_update',\n SECURITY_INFO_READ = 'security_info_read',\n DATA_ACCESS_LOG_READ = 'data_access_log_read',\n USERS_READ = 'users_read',\n USERS_UPDATE = 'users_update',\n USERS_DELETE = 'users_delete',\n BADGES_READ = 'badges_read',\n BADGES_READ_OTHERS = 'badges_read_others', // Pro-gated (social feature, reserved)\n\n // ── Team-gated features (granted via team role, not tier) ──\n EVALUATION_COMMENTS_CREATE = 'evaluation_comments_create',\n EVALUATION_COMMENTS_READ = 'evaluation_comments_read',\n PEER_REVIEWS_VIEW = 'peer_reviews_view',\n PEER_REVIEWS_ACCEPT = 'peer_reviews_accept',\n PEER_REVIEWS_SUBMIT = 'peer_reviews_submit',\n USER_TEAMS_VIEW = 'user_teams_view',\n USER_TEAMS_NOMINATE_PEER = 'user_teams_nominate_peer',\n // (back to universal — must be open so users can respond before having membership)\n USER_TEAMS_ACCEPT_INVITE = 'user_teams_accept_invite',\n USER_TEAMS_REJECT_INVITE = 'user_teams_reject_invite',\n USER_CYCLES_VIEW = 'user_cycles_view',\n USER_CYCLES_SELF_REVIEW_GENERATE = 'user_cycles_self_review_generate',\n USER_CYCLES_SELF_REVIEW_SUBMIT = 'user_cycles_self_review_submit',\n\n // ── Plus-gated: CLI / integrations ──\n CLI_BRAGS_CREATE = 'cli_brags_create',\n CLI_BRAGS_READ = 'cli_brags_read',\n CLI_BRAGS_REFINE = 'cli_brags_refine',\n COMMITS_REFINE = 'commits_refine',\n\n // ── Universal: navigation pages ──\n PAGE_DASHBOARD_VIEW = 'page_dashboard_view',\n PAGE_BRAGS_VIEW = 'page_brags_view',\n PAGE_STORIES_VIEW = 'page_stories_view',\n PAGE_OKRS_VIEW = 'page_okrs_view',\n PAGE_REPORTS_VIEW = 'page_reports_view',\n PAGE_TEAM_VIEW = 'page_team_view', // Org/team role grant\n PAGE_SETTINGS_VIEW = 'page_settings_view',\n PAGE_FILES_VIEW = 'page_files_view',\n PAGE_INTEGRATIONS_VIEW = 'page_integrations_view',\n PAGE_PRACTICE_VIEW = 'page_practice_view', // Pro-gated (AI interview practice)\n PAGE_RESUME_VIEW = 'page_resume_view', // Pro-gated (AI resume generator)\n\n // ── Universal: settings tabs ──\n SETTINGS_TAB_ACCOUNT_VIEW = 'settings_tab_account_view',\n SETTINGS_TAB_PREFERENCES_VIEW = 'settings_tab_preferences_view',\n SETTINGS_TAB_NOTIFICATIONS_VIEW = 'settings_tab_notifications_view',\n SETTINGS_TAB_BILLING_VIEW = 'settings_tab_billing_view',\n SETTINGS_TAB_AI_KEYS_VIEW = 'settings_tab_ai_keys_view', // Plus-gated (BYOK tab)\n SETTINGS_TAB_ORG_VIEW = 'settings_tab_org_view',\n SETTINGS_TAB_PLANS_VIEW = 'settings_tab_plans_view',\n}\n\n/**\n * Org-role / team-role gated features that are never granted by subscription tier\n * alone. Every TIER_FEATURES entry below sets these to `enabled: false` — access\n * is instead granted via ROLE_FEATURES (../access-control/roles.ts) based on the\n * user's org role or team role in the request context.\n */\nexport const ORG_TEAM_ROLE_FEATURES: readonly Feature[] = [\n Feature.ORG_ACCESS,\n Feature.ORG_MUTATE_BASE,\n Feature.ORG_ADMIN_ACCESS,\n Feature.ORG_OWNER_ACCESS,\n Feature.ORG_GROWTH_ADMIN_ACCESS,\n Feature.MANAGER_TIER_ACCESS,\n Feature.TEAM_MANAGE,\n Feature.TEAM_OWNER_ACTIONS,\n Feature.TEAM_MEMBERSHIP_VIEW,\n Feature.ORG_MEMBERS_MANAGE,\n Feature.ORG_INVITES_MANAGE,\n Feature.ORG_TEAMS_MANAGE,\n Feature.ORG_GROWTH_VIEW,\n Feature.ORG_CAREER_METRICS_MANAGE,\n Feature.ORG_CYCLES_MANAGE,\n Feature.ORG_STATS_VIEW,\n Feature.ORG_AUDIT_LOG_VIEW,\n Feature.ORG_BILLING_MANAGE,\n Feature.ORG_NOTIFICATIONS_MANAGE,\n];\n\n/** `{enabled: false}` entries for every ORG_TEAM_ROLE_FEATURES member, spread into each tier below. */\nconst DISABLED_ORG_TEAM_FEATURES: Record<Feature, FeatureLimit> = Object.fromEntries(\n ORG_TEAM_ROLE_FEATURES.map((f) => [f, { enabled: false }])\n) as Record<Feature, FeatureLimit>;\n\n/**\n * Universal features — available to every plan tier. Core CRUD, profile\n * management, basic notification preferences, team invite accept/reject,\n * most nav pages, and most settings tabs.\n */\nexport const UNIVERSAL_FEATURES: readonly Feature[] = [\n Feature.BRAGS_CREATE,\n Feature.BRAGS_READ,\n Feature.BRAGS_UPDATE,\n Feature.BRAGS_DELETE,\n Feature.STORIES_CREATE,\n Feature.STORIES_READ,\n Feature.STORIES_UPDATE,\n Feature.STORIES_DELETE,\n Feature.BRAG_COMMENTS_CREATE,\n Feature.BRAG_COMMENTS_READ,\n Feature.BRAG_COMMENTS_UPDATE,\n Feature.BRAG_COMMENTS_DELETE,\n Feature.BRAG_TYPES_CREATE,\n Feature.BRAG_TYPES_READ,\n Feature.BRAG_TYPES_UPDATE,\n Feature.OBJECTIVES_READ,\n Feature.OBJECTIVES_UPDATE,\n Feature.OBJECTIVES_DELETE,\n Feature.OBJECTIVES_KEY_RESULTS_CREATE,\n Feature.OBJECTIVES_KEY_RESULTS_UPDATE,\n Feature.OBJECTIVES_KEY_RESULTS_DELETE,\n Feature.REPORTS_READ,\n Feature.REPORTS_UPDATE,\n Feature.REPORTS_DELETE,\n Feature.SHARES_READ,\n Feature.SHARES_DELETE,\n Feature.FIELDS_READ,\n Feature.USER_EMAILS_READ,\n Feature.NOTIFICATIONS_PREFERENCES_READ,\n Feature.NOTIFICATIONS_PREFERENCES_UPDATE,\n Feature.SECURITY_INFO_READ,\n Feature.DATA_ACCESS_LOG_READ,\n Feature.USERS_READ,\n Feature.USERS_UPDATE,\n Feature.USERS_DELETE,\n Feature.BADGES_READ,\n Feature.USER_TEAMS_ACCEPT_INVITE,\n Feature.USER_TEAMS_REJECT_INVITE,\n Feature.PAGE_DASHBOARD_VIEW,\n Feature.PAGE_BRAGS_VIEW,\n Feature.PAGE_STORIES_VIEW,\n Feature.PAGE_OKRS_VIEW,\n Feature.PAGE_REPORTS_VIEW,\n Feature.PAGE_FILES_VIEW,\n Feature.PAGE_SETTINGS_VIEW,\n Feature.PAGE_INTEGRATIONS_VIEW,\n Feature.SETTINGS_TAB_ACCOUNT_VIEW,\n Feature.SETTINGS_TAB_PREFERENCES_VIEW,\n Feature.SETTINGS_TAB_NOTIFICATIONS_VIEW,\n Feature.SETTINGS_TAB_BILLING_VIEW,\n Feature.SETTINGS_TAB_ORG_VIEW,\n Feature.SETTINGS_TAB_PLANS_VIEW,\n // Account features: plans belong to companies, so these are open to everyone\n Feature.BRAGS_EXPORT,\n Feature.BRAGS_IMPORT,\n Feature.REPORTS_CSV_EXPORT,\n Feature.LLM_KEYS_CREATE,\n Feature.LLM_KEYS_READ,\n Feature.LLM_KEYS_UPDATE,\n Feature.LLM_KEYS_DELETE,\n Feature.LLM_KEYS_VALIDATE,\n Feature.LLM_SETTINGS_READ,\n Feature.LLM_SETTINGS_UPDATE,\n Feature.USER_SETTINGS_OPENAI_KEY_MANAGE,\n Feature.NOTIFICATIONS_SETTINGS_READ,\n Feature.NOTIFICATIONS_SETTINGS_UPDATE,\n Feature.SETTINGS_TAB_AI_KEYS_VIEW,\n Feature.USER_EMAILS_UPDATE,\n Feature.USER_EMAILS_DELETE,\n Feature.USER_EMAILS_VERIFY,\n Feature.BADGES_READ_OTHERS,\n];\n\n/**\n * Plus-gated features — disabled on FREE, enabled on PLUS/PRO.\n * CLI access and Ask Ducky.\n */\nexport const PLUS_GATED_FEATURES: readonly Feature[] = [\n Feature.CLI_BRAGS_CREATE,\n Feature.CLI_BRAGS_READ,\n Feature.CLI_BRAGS_REFINE,\n Feature.COMMITS_REFINE,\n Feature.ASK_DUCKY,\n];\n\n/**\n * Features shown on plan cards / pricing UI.\n * Excludes granular CRUD, page, settings-tab, and org/team role access keys —\n * those exist for access control, not marketing comparison.\n */\nexport const PLAN_CARD_FEATURES: readonly Feature[] = [\n Feature.BASIC_BRAGS,\n Feature.BASIC_ORGANIZATIONS,\n Feature.AI_REPORT_GENERATION,\n Feature.PDF_EXPORT,\n Feature.SHARE_LINKS,\n Feature.OBJECTIVES,\n Feature.ADVANCED_NOTIFICATIONS,\n Feature.FULL_EXPORT_IMPORT,\n Feature.SMART_GOAL_SUGGESTIONS,\n Feature.AI_POWERED_SUGGESTIONS,\n Feature.GENERATE_OBJECTIVES,\n Feature.ASK_DUCKY,\n Feature.PRIORITY_SUPPORT,\n Feature.CLI_TURBO_MODE,\n Feature.EARLY_ACCESS,\n Feature.AI_TOKEN_BUDGET,\n Feature.MULTI_EMAIL,\n];\n\n/**\n * Boolean (on/off) features for plan-card display — no numeric quota line.\n */\nexport const PLAN_CARD_BOOLEAN_FEATURES: readonly Feature[] = [\n Feature.ADVANCED_NOTIFICATIONS,\n Feature.FULL_EXPORT_IMPORT,\n Feature.SMART_GOAL_SUGGESTIONS,\n Feature.GENERATE_OBJECTIVES,\n Feature.ASK_DUCKY,\n Feature.PRIORITY_SUPPORT,\n Feature.CLI_TURBO_MODE,\n Feature.EARLY_ACCESS,\n Feature.AI_TOKEN_BUDGET,\n Feature.MULTI_EMAIL,\n];\n\n/**\n * Pro-gated features — disabled on FREE/PLUS, enabled on PRO.\n * Practice and resume pages.\n */\nexport const PRO_GATED_FEATURES: readonly Feature[] = [\n Feature.PAGE_PRACTICE_VIEW,\n Feature.PAGE_RESUME_VIEW,\n];\n\n/**\n * Team-gated features — disabled on all plan tiers, granted only via team\n * role membership (team:owner, team:co_manager, team:member) in ROLE_FEATURES.\n * Evaluation flows that only make sense within a team context.\n */\nexport const TEAM_GATED_FEATURES: readonly Feature[] = [\n Feature.EVALUATION_COMMENTS_CREATE,\n Feature.EVALUATION_COMMENTS_READ,\n Feature.PEER_REVIEWS_VIEW,\n Feature.PEER_REVIEWS_ACCEPT,\n Feature.PEER_REVIEWS_SUBMIT,\n Feature.USER_TEAMS_VIEW,\n Feature.USER_TEAMS_NOMINATE_PEER,\n Feature.USER_CYCLES_VIEW,\n Feature.USER_CYCLES_SELF_REVIEW_GENERATE,\n Feature.USER_CYCLES_SELF_REVIEW_SUBMIT,\n];\n\nconst ENABLED_UNIVERSAL: Record<Feature, FeatureLimit> = Object.fromEntries(\n UNIVERSAL_FEATURES.map((f) => [f, { enabled: true }])\n) as Record<Feature, FeatureLimit>;\n\nconst DISABLED_PLUS_GATED: Record<Feature, FeatureLimit> = Object.fromEntries(\n PLUS_GATED_FEATURES.map((f) => [f, { enabled: false }])\n) as Record<Feature, FeatureLimit>;\n\nconst ENABLED_PLUS_GATED: Record<Feature, FeatureLimit> = Object.fromEntries(\n PLUS_GATED_FEATURES.map((f) => [f, { enabled: true }])\n) as Record<Feature, FeatureLimit>;\n\nconst DISABLED_PRO_GATED: Record<Feature, FeatureLimit> = Object.fromEntries(\n PRO_GATED_FEATURES.map((f) => [f, { enabled: false }])\n) as Record<Feature, FeatureLimit>;\n\nconst ENABLED_PRO_GATED: Record<Feature, FeatureLimit> = Object.fromEntries(\n PRO_GATED_FEATURES.map((f) => [f, { enabled: true }])\n) as Record<Feature, FeatureLimit>;\n\nconst DISABLED_TEAM_GATED: Record<Feature, FeatureLimit> = Object.fromEntries(\n TEAM_GATED_FEATURES.map((f) => [f, { enabled: false }])\n) as Record<Feature, FeatureLimit>;\n\nexport interface FeatureLimit {\n enabled: boolean;\n quota?: number;\n resetPeriod?: 'monthly' | 'never';\n tokenQuota?: number; // Token-based quota (e.g., 1M tokens/month for PRO)\n}\n\n/**\n * PRO tier token budget: 1 million tokens per month\n * Combined pool for all AI features (reports + text refinement)\n */\nexport const PRO_TIER_TOKEN_BUDGET = 1_000_000;\n\n/** AI features that need the user's own key (BYOK) on the Plus tier */\nexport const BYOK_AI_FEATURES: readonly Feature[] = [\n Feature.AI_REPORT_GENERATION,\n Feature.AI_POWERED_SUGGESTIONS,\n Feature.SMART_GOAL_SUGGESTIONS,\n Feature.GENERATE_OBJECTIVES,\n];\n\nexport interface TierConfig {\n tier: 'FREE' | 'PLUS' | 'PRO';\n features: Record<Feature, FeatureLimit>;\n}\n\n/**\n * Feature configuration per subscription tier\n *\n * FREE: $0/month - Limited features for trying out the platform\n * PLUS: $5/month - BYO OpenAI key, moderate limits\n * PRO: $10/month - High/unlimited limits with system OpenAI key\n */\nexport const TIER_FEATURES: Record<string, TierConfig> = {\n FREE: {\n tier: 'FREE',\n features: {\n ...DISABLED_ORG_TEAM_FEATURES,\n ...ENABLED_UNIVERSAL,\n ...DISABLED_PLUS_GATED,\n ...DISABLED_PRO_GATED,\n ...DISABLED_TEAM_GATED,\n [Feature.PAGE_TEAM_VIEW]: { enabled: false },\n [Feature.BASIC_BRAGS]: { enabled: true },\n [Feature.BASIC_ORGANIZATIONS]: { enabled: true },\n [Feature.AI_REPORT_GENERATION]: {\n enabled: true,\n quota: 2,\n resetPeriod: 'monthly',\n },\n [Feature.PDF_EXPORT]: { enabled: true, quota: 3, resetPeriod: 'monthly' },\n [Feature.SHARE_LINKS]: { enabled: true, quota: 5 }, // max 5 active\n [Feature.OBJECTIVES]: { enabled: true, quota: 3 }, // max 3 active\n [Feature.ADVANCED_NOTIFICATIONS]: { enabled: true },\n [Feature.FULL_EXPORT_IMPORT]: { enabled: true },\n [Feature.CLI_TURBO_MODE]: { enabled: false },\n [Feature.PRIORITY_SUPPORT]: { enabled: false },\n [Feature.EARLY_ACCESS]: { enabled: false },\n [Feature.SMART_GOAL_SUGGESTIONS]: { enabled: false },\n [Feature.AI_POWERED_SUGGESTIONS]: { enabled: false },\n [Feature.AI_TOKEN_BUDGET]: { enabled: false },\n [Feature.GENERATE_OBJECTIVES]: { enabled: false },\n [Feature.MULTI_EMAIL]: { enabled: true },\n },\n },\n\n PLUS: {\n tier: 'PLUS',\n features: {\n ...DISABLED_ORG_TEAM_FEATURES,\n ...ENABLED_UNIVERSAL,\n ...ENABLED_PLUS_GATED,\n ...DISABLED_PRO_GATED,\n ...DISABLED_TEAM_GATED,\n [Feature.PAGE_TEAM_VIEW]: { enabled: false },\n [Feature.BASIC_BRAGS]: { enabled: true },\n [Feature.BASIC_ORGANIZATIONS]: { enabled: true },\n [Feature.AI_REPORT_GENERATION]: { enabled: true, quota: 30, resetPeriod: 'monthly' },\n [Feature.PDF_EXPORT]: {\n enabled: true,\n quota: 15,\n resetPeriod: 'monthly',\n },\n [Feature.SHARE_LINKS]: { enabled: true, quota: 20 }, // max 20 active\n [Feature.OBJECTIVES]: { enabled: true, quota: 4 }, // max 4 active\n [Feature.ADVANCED_NOTIFICATIONS]: { enabled: true },\n [Feature.FULL_EXPORT_IMPORT]: { enabled: true },\n [Feature.CLI_TURBO_MODE]: { enabled: false },\n [Feature.PRIORITY_SUPPORT]: { enabled: true },\n [Feature.EARLY_ACCESS]: { enabled: false },\n [Feature.SMART_GOAL_SUGGESTIONS]: { enabled: true },\n [Feature.AI_POWERED_SUGGESTIONS]: { enabled: true, quota: 30, resetPeriod: 'monthly' },\n [Feature.AI_TOKEN_BUDGET]: { enabled: false },\n [Feature.GENERATE_OBJECTIVES]: { enabled: true },\n [Feature.MULTI_EMAIL]: { enabled: true },\n },\n },\n\n PRO: {\n tier: 'PRO',\n features: {\n ...DISABLED_ORG_TEAM_FEATURES,\n ...ENABLED_UNIVERSAL,\n ...ENABLED_PLUS_GATED,\n ...ENABLED_PRO_GATED,\n ...DISABLED_TEAM_GATED,\n [Feature.PAGE_TEAM_VIEW]: { enabled: false },\n [Feature.BASIC_BRAGS]: { enabled: true },\n [Feature.BASIC_ORGANIZATIONS]: { enabled: true },\n [Feature.AI_REPORT_GENERATION]: {\n enabled: true,\n tokenQuota: PRO_TIER_TOKEN_BUDGET,\n resetPeriod: 'monthly',\n },\n [Feature.PDF_EXPORT]: { enabled: true }, // unlimited\n [Feature.SHARE_LINKS]: { enabled: true }, // unlimited\n [Feature.OBJECTIVES]: { enabled: true }, // unlimited\n [Feature.ADVANCED_NOTIFICATIONS]: { enabled: true },\n [Feature.FULL_EXPORT_IMPORT]: { enabled: true },\n [Feature.CLI_TURBO_MODE]: { enabled: true },\n [Feature.PRIORITY_SUPPORT]: { enabled: true },\n [Feature.EARLY_ACCESS]: { enabled: true },\n [Feature.SMART_GOAL_SUGGESTIONS]: { enabled: true },\n [Feature.AI_POWERED_SUGGESTIONS]: { enabled: true },\n [Feature.AI_TOKEN_BUDGET]: {\n enabled: true,\n tokenQuota: PRO_TIER_TOKEN_BUDGET,\n resetPeriod: 'monthly',\n },\n [Feature.GENERATE_OBJECTIVES]: { enabled: true },\n [Feature.MULTI_EMAIL]: { enabled: true },\n },\n },\n};\n\n/**\n * Human-readable feature metadata for UI/error messages\n */\nexport const FEATURE_METADATA: Record<\n Feature,\n { name: string; description: string; tier: string }\n> = {\n [Feature.BASIC_BRAGS]: {\n name: 'Brags',\n description: 'Track your career achievements',\n tier: 'Free',\n },\n [Feature.BASIC_ORGANIZATIONS]: {\n name: 'Organizations',\n description: 'Track achievements across multiple companies',\n tier: 'Free (2), Plus & Pro (unlimited)',\n },\n [Feature.AI_REPORT_GENERATION]: {\n name: 'AI Report Generation',\n description: 'Generate AI-powered career summary reports',\n tier: 'Free (2/mo), Plus (30/mo with BYOK), Pro (1M tokens/mo)',\n },\n [Feature.PDF_EXPORT]: {\n name: 'PDF Export',\n description: 'Export reports as professional PDFs',\n tier: 'Free (3/mo), Plus (15/mo), Pro (unlimited)',\n },\n [Feature.SHARE_LINKS]: {\n name: 'Share Links',\n description: 'Share your brags with others via links',\n tier: 'Free (5 active), Plus (20 active), Pro (unlimited)',\n },\n [Feature.OBJECTIVES]: {\n name: 'Active Objectives',\n description: 'Set and track active career objectives',\n tier: 'Free (2 active), Plus (4 active), Pro (unlimited)',\n },\n [Feature.ADVANCED_NOTIFICATIONS]: {\n name: 'Advanced Notifications',\n description: 'Weekly summaries and objective reminders',\n tier: 'Plus & Pro',\n },\n [Feature.FULL_EXPORT_IMPORT]: {\n name: 'Full Export/Import',\n description: 'Complete data portability',\n tier: 'Plus & Pro',\n },\n [Feature.CLI_TURBO_MODE]: {\n name: 'CLI Turbo Mode',\n description: 'Auto-collect achievements from git, calendar, email',\n tier: 'Pro',\n },\n [Feature.PRIORITY_SUPPORT]: {\n name: 'Priority Support',\n description: 'Get faster responses and dedicated support',\n tier: 'Plus & Pro',\n },\n [Feature.EARLY_ACCESS]: {\n name: 'Early Access',\n description: 'Get early access to new features before general release',\n tier: 'Pro',\n },\n [Feature.SMART_GOAL_SUGGESTIONS]: {\n name: 'Auto-match with OKRs',\n description: 'Automatically suggest matching OKRs when logging a brag',\n tier: 'Plus & Pro',\n },\n [Feature.AI_POWERED_SUGGESTIONS]: {\n name: 'AI-Powered Suggestions',\n description: 'AI key result suggestions and brag matching for your objectives',\n tier: 'Plus & Pro',\n },\n [Feature.ASK_DUCKY]: {\n name: 'Ask Ducky AI Assistant',\n description: 'AI-powered assistant with full access to your career data',\n tier: 'Plus & Pro',\n },\n [Feature.AI_TOKEN_BUDGET]: {\n name: 'AI Token Budget',\n description: 'Generous token budget for AI reports and text refinements',\n tier: 'Pro (1M/mo)',\n },\n [Feature.GENERATE_OBJECTIVES]: {\n name: 'Generate Objectives from Feedback',\n description: 'Use AI to turn performance feedback into actionable objectives',\n tier: 'Plus & Pro',\n },\n [Feature.MULTI_EMAIL]: {\n name: 'Multi-Email Account',\n description: 'Link multiple email addresses to your account',\n tier: 'Pro',\n },\n\n // Org-role / team-role gated features — not shown in upgrade prompts today,\n // metadata kept minimal. See ../access-control/roles.ts for the actual grants.\n [Feature.ORG_ACCESS]: { name: 'Org Access', description: 'View an organisation', tier: 'Org role' },\n [Feature.ORG_MUTATE_BASE]: { name: 'Org Mutate', description: 'Modify organisation data', tier: 'Org role' },\n [Feature.ORG_ADMIN_ACCESS]: { name: 'Org Admin Access', description: 'Administer an organisation', tier: 'Org role' },\n [Feature.ORG_OWNER_ACCESS]: { name: 'Org Owner Access', description: 'Irreversible org-level actions', tier: 'Org role' },\n [Feature.ORG_GROWTH_ADMIN_ACCESS]: { name: 'Org Growth Admin Access', description: 'Manage growth assets', tier: 'Org role' },\n [Feature.MANAGER_TIER_ACCESS]: { name: 'Manager Tier Access', description: 'Access manager-tier features', tier: 'Org/team role' },\n [Feature.TEAM_MANAGE]: { name: 'Team Manage', description: 'Manage a team', tier: 'Team role' },\n [Feature.TEAM_OWNER_ACTIONS]: { name: 'Team Owner Actions', description: 'Destructive team actions', tier: 'Team role' },\n [Feature.TEAM_MEMBERSHIP_VIEW]: { name: 'Team Membership View', description: 'View team membership', tier: 'Team role' },\n [Feature.ORG_MEMBERS_MANAGE]: { name: 'Org Members Manage', description: 'Manage org members', tier: 'Org role' },\n [Feature.ORG_INVITES_MANAGE]: { name: 'Org Invites Manage', description: 'Manage org invites', tier: 'Org role' },\n [Feature.ORG_TEAMS_MANAGE]: { name: 'Org Teams Manage', description: 'Manage org teams', tier: 'Org role' },\n [Feature.ORG_GROWTH_VIEW]: { name: 'Org Growth View', description: 'View growth ladders/cycles', tier: 'Org role' },\n [Feature.ORG_CAREER_METRICS_MANAGE]: { name: 'Org Career Metrics Manage', description: 'Manage career ladders', tier: 'Org role' },\n [Feature.ORG_CYCLES_MANAGE]: { name: 'Org Cycles Manage', description: 'Manage evaluation cycles', tier: 'Org role' },\n [Feature.ORG_STATS_VIEW]: { name: 'Org Stats View', description: 'View org overview stats', tier: 'Org role' },\n [Feature.ORG_AUDIT_LOG_VIEW]: { name: 'Org Audit Log View', description: 'View the org audit log', tier: 'Org role' },\n [Feature.ORG_BILLING_MANAGE]: { name: 'Org Billing Manage', description: 'Manage org billing/seats', tier: 'Org role' },\n [Feature.ORG_NOTIFICATIONS_MANAGE]: { name: 'Org Notifications Manage', description: 'Manage org notification policy', tier: 'Org role' },\n\n // Universal features (all tiers)\n [Feature.BRAGS_CREATE]: { name: 'Create Brags', description: 'Create a brag', tier: 'All' },\n [Feature.BRAGS_READ]: { name: 'Read Brags', description: 'View your brags', tier: 'All' },\n [Feature.BRAGS_UPDATE]: { name: 'Update Brags', description: 'Edit a brag', tier: 'All' },\n [Feature.BRAGS_DELETE]: { name: 'Delete Brags', description: 'Delete a brag', tier: 'All' },\n [Feature.STORIES_CREATE]: { name: 'Create Stories', description: 'Create an interview story', tier: 'All' },\n [Feature.STORIES_READ]: { name: 'Read Stories', description: 'View interview stories', tier: 'All' },\n [Feature.STORIES_UPDATE]: { name: 'Update Stories', description: 'Edit an interview story', tier: 'All' },\n [Feature.STORIES_DELETE]: { name: 'Delete Stories', description: 'Delete an interview story', tier: 'All' },\n [Feature.BRAG_COMMENTS_CREATE]: { name: 'Create Brag Comments', description: 'Comment on a brag', tier: 'All' },\n [Feature.BRAG_COMMENTS_READ]: { name: 'Read Brag Comments', description: 'View brag comments', tier: 'All' },\n [Feature.BRAG_COMMENTS_UPDATE]: { name: 'Update Brag Comments', description: 'Edit a brag comment', tier: 'All' },\n [Feature.BRAG_COMMENTS_DELETE]: { name: 'Delete Brag Comments', description: 'Delete a brag comment', tier: 'All' },\n [Feature.BRAG_TYPES_CREATE]: { name: 'Create Brag Types', description: 'Create a custom brag type', tier: 'All' },\n [Feature.BRAG_TYPES_READ]: { name: 'Read Brag Types', description: 'View brag types', tier: 'All' },\n [Feature.BRAG_TYPES_UPDATE]: { name: 'Update Brag Types', description: 'Edit a brag type', tier: 'All' },\n [Feature.OBJECTIVES_READ]: { name: 'Read Objectives', description: 'View objectives', tier: 'All' },\n [Feature.OBJECTIVES_UPDATE]: { name: 'Update Objectives', description: 'Edit an objective', tier: 'All' },\n [Feature.OBJECTIVES_DELETE]: { name: 'Delete Objectives', description: 'Delete an objective', tier: 'All' },\n [Feature.OBJECTIVES_KEY_RESULTS_CREATE]: { name: 'Create Key Results', description: 'Add a key result', tier: 'All' },\n [Feature.OBJECTIVES_KEY_RESULTS_UPDATE]: { name: 'Update Key Results', description: 'Edit a key result', tier: 'All' },\n [Feature.OBJECTIVES_KEY_RESULTS_DELETE]: { name: 'Delete Key Results', description: 'Delete a key result', tier: 'All' },\n [Feature.REPORTS_READ]: { name: 'Read Reports', description: 'View reports', tier: 'All' },\n [Feature.REPORTS_UPDATE]: { name: 'Update Reports', description: 'Edit a report', tier: 'All' },\n [Feature.REPORTS_DELETE]: { name: 'Delete Reports', description: 'Delete a report', tier: 'All' },\n [Feature.SHARES_READ]: { name: 'Read Shares', description: 'View share links', tier: 'All' },\n [Feature.SHARES_DELETE]: { name: 'Delete Shares', description: 'Revoke a share link', tier: 'All' },\n [Feature.FIELDS_READ]: { name: 'Read Fields', description: 'View custom field definitions', tier: 'All' },\n [Feature.USER_EMAILS_READ]: { name: 'Read User Emails', description: 'View linked emails', tier: 'All' },\n [Feature.NOTIFICATIONS_PREFERENCES_READ]: { name: 'Read Notification Preferences', description: 'View notification preferences', tier: 'All' },\n [Feature.NOTIFICATIONS_PREFERENCES_UPDATE]: { name: 'Update Notification Preferences', description: 'Edit notification preferences', tier: 'All' },\n [Feature.SECURITY_INFO_READ]: { name: 'Read Security Info', description: 'View account security info', tier: 'All' },\n [Feature.DATA_ACCESS_LOG_READ]: { name: 'Read Data Access Log', description: 'View your data access log', tier: 'All' },\n [Feature.USERS_READ]: { name: 'Read User Profile', description: 'View your profile', tier: 'All' },\n [Feature.USERS_UPDATE]: { name: 'Update User Profile', description: 'Edit your profile', tier: 'All' },\n [Feature.USERS_DELETE]: { name: 'Delete User Account', description: 'Delete your account', tier: 'All' },\n [Feature.BADGES_READ]: { name: 'Read Badges', description: 'View your earned badges', tier: 'All' },\n [Feature.USER_TEAMS_ACCEPT_INVITE]: { name: 'Accept Team Invite', description: 'Accept a team invite', tier: 'All' },\n [Feature.USER_TEAMS_REJECT_INVITE]: { name: 'Reject Team Invite', description: 'Reject a team invite', tier: 'All' },\n [Feature.PAGE_DASHBOARD_VIEW]: { name: 'View Dashboard Page', description: 'View the dashboard page', tier: 'All' },\n [Feature.PAGE_BRAGS_VIEW]: { name: 'View Brags Page', description: 'View the brags page', tier: 'All' },\n [Feature.PAGE_STORIES_VIEW]: { name: 'View Stories Page', description: 'View the interview stories page', tier: 'All' },\n [Feature.PAGE_OKRS_VIEW]: { name: 'View OKRs Page', description: 'View the OKRs page', tier: 'All' },\n [Feature.PAGE_REPORTS_VIEW]: { name: 'View Reports Page', description: 'View the reports page', tier: 'All' },\n [Feature.PAGE_SETTINGS_VIEW]: { name: 'View Settings Page', description: 'View the settings page', tier: 'All' },\n [Feature.PAGE_FILES_VIEW]: { name: 'View Files Page', description: 'View the org files page', tier: 'All' },\n [Feature.PAGE_INTEGRATIONS_VIEW]: { name: 'View Integrations Page', description: 'View the integrations page', tier: 'All' },\n [Feature.PAGE_PRACTICE_VIEW]: { name: 'View Practice Page', description: 'View the AI interview practice page', tier: 'Pro' },\n [Feature.PAGE_RESUME_VIEW]: { name: 'View Resume Generator Page', description: 'View the AI resume generator page', tier: 'Pro' },\n [Feature.SETTINGS_TAB_ACCOUNT_VIEW]: { name: 'View Account Settings Tab', description: 'View the account settings tab', tier: 'All' },\n [Feature.SETTINGS_TAB_PREFERENCES_VIEW]: { name: 'View Preferences Settings Tab', description: 'View the preferences settings tab', tier: 'All' },\n [Feature.SETTINGS_TAB_NOTIFICATIONS_VIEW]: { name: 'View Notifications Settings Tab', description: 'View the notifications settings tab', tier: 'All' },\n [Feature.SETTINGS_TAB_BILLING_VIEW]: { name: 'View Billing Settings Tab', description: 'View the billing settings tab', tier: 'All' },\n [Feature.SETTINGS_TAB_ORG_VIEW]: { name: 'View Org Settings Tab', description: 'View the org settings tab', tier: 'All' },\n [Feature.SETTINGS_TAB_PLANS_VIEW]: { name: 'View Plans Settings Tab', description: 'View the plans settings tab', tier: 'All' },\n // Plus-gated features (Plus & Pro)\n [Feature.BRAGS_EXPORT]: { name: 'Export Brags', description: 'Export brags', tier: 'Plus & Pro' },\n [Feature.BRAGS_IMPORT]: { name: 'Import Brags', description: 'Import brags', tier: 'Plus & Pro' },\n [Feature.REPORTS_CSV_EXPORT]: { name: 'Export Reports (CSV)', description: 'Export report data as CSV', tier: 'Plus & Pro' },\n [Feature.LLM_KEYS_CREATE]: { name: 'Create LLM Keys', description: 'Add a BYOK LLM key', tier: 'Plus & Pro' },\n [Feature.LLM_KEYS_READ]: { name: 'Read LLM Keys', description: 'View BYOK LLM keys', tier: 'Plus & Pro' },\n [Feature.LLM_KEYS_UPDATE]: { name: 'Update LLM Keys', description: 'Edit a BYOK LLM key', tier: 'Plus & Pro' },\n [Feature.LLM_KEYS_DELETE]: { name: 'Delete LLM Keys', description: 'Remove a BYOK LLM key', tier: 'Plus & Pro' },\n [Feature.LLM_KEYS_VALIDATE]: { name: 'Validate LLM Keys', description: 'Validate a BYOK LLM key', tier: 'Plus & Pro' },\n [Feature.LLM_SETTINGS_READ]: { name: 'Read LLM Settings', description: 'View LLM preferences/usage', tier: 'Plus & Pro' },\n [Feature.LLM_SETTINGS_UPDATE]: { name: 'Update LLM Settings', description: 'Edit LLM preferences', tier: 'Plus & Pro' },\n [Feature.USER_SETTINGS_OPENAI_KEY_MANAGE]: { name: 'Manage Legacy OpenAI Key', description: 'Manage legacy OpenAI key', tier: 'Plus & Pro' },\n [Feature.NOTIFICATIONS_SETTINGS_READ]: { name: 'Read Notification Settings', description: 'View advanced notification settings', tier: 'Plus & Pro' },\n [Feature.NOTIFICATIONS_SETTINGS_UPDATE]: { name: 'Update Notification Settings', description: 'Edit advanced notification settings', tier: 'Plus & Pro' },\n [Feature.CLI_BRAGS_CREATE]: { name: 'Create Brags via CLI', description: 'Create brags via CLI/MCP', tier: 'Plus & Pro' },\n [Feature.CLI_BRAGS_READ]: { name: 'Read Brags via CLI', description: 'List brags via CLI/MCP', tier: 'Plus & Pro' },\n [Feature.CLI_BRAGS_REFINE]: { name: 'Refine Brags via CLI', description: 'AI-refine brag text via CLI/MCP', tier: 'Plus & Pro' },\n [Feature.COMMITS_REFINE]: { name: 'Refine Commits', description: 'AI-refine a commit/brag draft', tier: 'Plus & Pro' },\n [Feature.SETTINGS_TAB_AI_KEYS_VIEW]: { name: 'View AI Keys Settings Tab', description: 'View the AI keys settings tab', tier: 'Plus & Pro' },\n // Pro-gated features (Pro)\n [Feature.USER_EMAILS_UPDATE]: { name: 'Update User Emails', description: 'Add/edit a linked email', tier: 'Pro' },\n [Feature.USER_EMAILS_DELETE]: { name: 'Delete User Emails', description: 'Remove a linked email', tier: 'Pro' },\n [Feature.USER_EMAILS_VERIFY]: { name: 'Verify User Emails', description: 'Verify a linked email', tier: 'Pro' },\n [Feature.BADGES_READ_OTHERS]: { name: 'Read Other Users\\' Badges', description: 'View another user\\'s badges', tier: 'Pro' },\n // Team-gated features (team membership required)\n [Feature.EVALUATION_COMMENTS_CREATE]: { name: 'Create Evaluation Comments', description: 'Comment on your evaluation', tier: 'Org/team role' },\n [Feature.EVALUATION_COMMENTS_READ]: { name: 'Read Evaluation Comments', description: 'View evaluation comments', tier: 'Org/team role' },\n [Feature.PEER_REVIEWS_VIEW]: { name: 'View Peer Review Requests', description: 'View peer review requests assigned to you', tier: 'Org/team role' },\n [Feature.PEER_REVIEWS_ACCEPT]: { name: 'Accept Peer Review Requests', description: 'Accept a peer review request', tier: 'Org/team role' },\n [Feature.PEER_REVIEWS_SUBMIT]: { name: 'Submit Peer Reviews', description: 'Submit a peer review', tier: 'Org/team role' },\n [Feature.USER_TEAMS_VIEW]: { name: 'View Own Team', description: 'View your team', tier: 'Org/team role' },\n [Feature.USER_TEAMS_NOMINATE_PEER]: { name: 'Nominate Peer Reviewer', description: 'Nominate a peer reviewer', tier: 'Org/team role' },\n [Feature.USER_CYCLES_VIEW]: { name: 'View Own Evaluation Cycles', description: 'View your evaluation cycles', tier: 'Org/team role' },\n [Feature.USER_CYCLES_SELF_REVIEW_GENERATE]: { name: 'Generate Self-Review', description: 'AI-generate a self-review draft', tier: 'Org/team role' },\n [Feature.USER_CYCLES_SELF_REVIEW_SUBMIT]: { name: 'Submit Self-Review', description: 'Submit a self-review', tier: 'Org/team role' },\n // PAGE_TEAM_VIEW: org/team role grant\n [Feature.PAGE_TEAM_VIEW]: { name: 'View Team Page', description: 'View the employee team page', tier: 'Org/team role' },\n};\n","/**\n * Role -> Feature Catalog\n *\n * Single source of truth for \"what can this role reach.\" Three role families:\n * - plan:* subscription-tier roles, derived from TIER_FEATURES (never hand-duplicated)\n * - org:* enterprise org roles (OrgRole from @bragduck/types)\n * - team:* team roles (teams live in enterprise orgs)\n *\n * Consumed by:\n * - apps/server/src/utils/access-control/check-access.ts (new unified checkAccess primitive)\n * - apps/server/src/middleware/org-auth.ts / manager-auth.ts (legacy guards derive their\n * role predicates — isOrgAdminRole, isOrgViewerOnly, canManageGrowth, etc. — from this\n * catalog instead of hardcoding role lists, while keeping their own control flow/error\n * shapes unchanged)\n */\n\nimport { Feature, TIER_FEATURES, TEAM_GATED_FEATURES } from '../subscription/feature-config';\nimport type { OrgRole } from '@bragduck/types';\n\nexport type PlanRoleId = 'plan:FREE' | 'plan:PLUS' | 'plan:PRO';\nexport type OrgRoleId =\n | 'org:org_owner'\n | 'org:org_admin'\n | 'org:growth_admin'\n | 'org:org_member'\n | 'org:org_viewer';\nexport type TeamRoleId = 'team:owner' | 'team:co_manager' | 'team:member';\nexport type RoleId = PlanRoleId | OrgRoleId | TeamRoleId;\n\nexport function isPlanRole(role: RoleId): role is PlanRoleId {\n return role.startsWith('plan:');\n}\n\n/** Namespace a raw OrgRole DB value into its RoleId form. Returns null for unknown/legacy values. */\nexport function orgRoleToRoleId(role: OrgRole | string): OrgRoleId | null {\n switch (role) {\n case 'org_owner':\n return 'org:org_owner';\n case 'org_admin':\n return 'org:org_admin';\n case 'growth_admin':\n return 'org:growth_admin';\n case 'org_member':\n return 'org:org_member';\n case 'org_viewer':\n return 'org:org_viewer';\n default:\n return null;\n }\n}\n\n/** Namespace a raw subscription tier value into its RoleId form. Returns null for unknown values. */\nexport function planTierToRoleId(tier: string): PlanRoleId | null {\n switch (tier) {\n case 'FREE':\n return 'plan:FREE';\n case 'PLUS':\n return 'plan:PLUS';\n case 'PRO':\n return 'plan:PRO';\n default:\n return null;\n }\n}\n\nfunction deriveEnabledFeatures(tier: keyof typeof TIER_FEATURES): Feature[] {\n const config = TIER_FEATURES[tier];\n if (!config) return [];\n return Object.entries(config.features)\n .filter(([, limit]) => limit.enabled)\n .map(([f]) => f as Feature);\n}\n\nconst PLAN_ROLE_FEATURES: Record<PlanRoleId, ReadonlyArray<Feature>> = {\n 'plan:FREE': deriveEnabledFeatures('FREE'),\n 'plan:PLUS': deriveEnabledFeatures('PLUS'),\n 'plan:PRO': deriveEnabledFeatures('PRO'),\n};\n\n// org_owner gets everything org_admin gets, plus ORG_OWNER_ACCESS.\nconst ORG_ADMIN_SHARED_FEATURES: ReadonlyArray<Feature> = [\n Feature.ORG_ACCESS,\n Feature.ORG_MUTATE_BASE,\n Feature.ORG_ADMIN_ACCESS,\n Feature.ORG_GROWTH_ADMIN_ACCESS,\n Feature.MANAGER_TIER_ACCESS,\n Feature.ORG_MEMBERS_MANAGE,\n Feature.ORG_INVITES_MANAGE,\n Feature.ORG_TEAMS_MANAGE,\n Feature.ORG_GROWTH_VIEW,\n Feature.ORG_CAREER_METRICS_MANAGE,\n Feature.ORG_CYCLES_MANAGE,\n Feature.ORG_STATS_VIEW,\n Feature.ORG_AUDIT_LOG_VIEW,\n Feature.ORG_BILLING_MANAGE,\n Feature.ORG_NOTIFICATIONS_MANAGE,\n];\n\nconst ORG_ROLE_FEATURES: Record<OrgRoleId, ReadonlyArray<Feature>> = {\n 'org:org_owner': [...ORG_ADMIN_SHARED_FEATURES, Feature.ORG_OWNER_ACCESS, Feature.PAGE_TEAM_VIEW, ...TEAM_GATED_FEATURES],\n 'org:org_admin': [...ORG_ADMIN_SHARED_FEATURES, Feature.PAGE_TEAM_VIEW, ...TEAM_GATED_FEATURES],\n // growth_admin: matches canManageGrowth (owner/admin/growth_admin) but NOT\n // isOrgAdminRole (owner/admin only) — no ORG_ADMIN_ACCESS or its dependents\n // (members/invites/teams/stats/audit-log/billing/notifications manage).\n 'org:growth_admin': [\n Feature.ORG_ACCESS,\n Feature.ORG_MUTATE_BASE,\n Feature.ORG_GROWTH_ADMIN_ACCESS,\n Feature.MANAGER_TIER_ACCESS,\n Feature.ORG_GROWTH_VIEW,\n Feature.ORG_CAREER_METRICS_MANAGE,\n Feature.ORG_CYCLES_MANAGE,\n Feature.PAGE_TEAM_VIEW,\n ...TEAM_GATED_FEATURES,\n ],\n 'org:org_member': [Feature.ORG_ACCESS, Feature.ORG_MUTATE_BASE, Feature.PAGE_TEAM_VIEW, ...TEAM_GATED_FEATURES],\n // org_viewer: read-only — gets view/read subset of team features only.\n 'org:org_viewer': [\n Feature.ORG_ACCESS,\n Feature.PAGE_TEAM_VIEW,\n Feature.EVALUATION_COMMENTS_READ,\n Feature.PEER_REVIEWS_VIEW,\n Feature.USER_TEAMS_VIEW,\n Feature.USER_CYCLES_VIEW,\n ],\n};\n\nconst TEAM_ROLE_FEATURES: Record<TeamRoleId, ReadonlyArray<Feature>> = {\n 'team:owner': [\n Feature.TEAM_MANAGE,\n Feature.TEAM_OWNER_ACTIONS,\n Feature.TEAM_MEMBERSHIP_VIEW,\n Feature.MANAGER_TIER_ACCESS,\n Feature.PAGE_TEAM_VIEW,\n ...TEAM_GATED_FEATURES,\n ],\n 'team:co_manager': [\n Feature.TEAM_MANAGE,\n Feature.TEAM_MEMBERSHIP_VIEW,\n Feature.MANAGER_TIER_ACCESS,\n Feature.PAGE_TEAM_VIEW,\n ...TEAM_GATED_FEATURES,\n ],\n 'team:member': [\n Feature.TEAM_MEMBERSHIP_VIEW,\n Feature.PAGE_TEAM_VIEW,\n ...TEAM_GATED_FEATURES,\n ],\n};\n\nexport const ROLE_FEATURES: Record<RoleId, ReadonlyArray<Feature>> = {\n ...PLAN_ROLE_FEATURES,\n ...ORG_ROLE_FEATURES,\n ...TEAM_ROLE_FEATURES,\n};\n\n/**\n * Org roles that grant ORG_GROWTH_ADMIN_ACCESS (owner/admin/growth_admin).\n * Reused by both requireGrowthAdmin (canManageGrowth) and requireManagerTier /\n * requireTeamManager's \"org admin manages any team/gets manager access\" fallback —\n * those three checks hardcode this same three-role set today.\n */\nexport const ORG_GROWTH_CAPABLE_ROLE_IDS: ReadonlyArray<OrgRoleId> = (\n Object.keys(ORG_ROLE_FEATURES) as OrgRoleId[]\n).filter((id) => ROLE_FEATURES[id].includes(Feature.ORG_GROWTH_ADMIN_ACCESS));\n\n/** Raw OrgRole string values (not namespaced) for the same set, for Prisma `role: { in: [...] }` filters. */\nexport const ORG_GROWTH_CAPABLE_ROLES: ReadonlyArray<string> = ORG_GROWTH_CAPABLE_ROLE_IDS.map((id) =>\n id.replace('org:', '')\n);\n\n/** Returns true if the role has org-admin level access (owner or admin). */\nexport function isOrgAdminRole(role: OrgRole | string): boolean {\n const roleId = orgRoleToRoleId(role);\n return !!roleId && ROLE_FEATURES[roleId].includes(Feature.ORG_ADMIN_ACCESS);\n}\n\n/** Returns true if the role is read-only (org_viewer cannot mutate anything). */\nexport function isOrgViewerOnly(role: OrgRole | string): boolean {\n return orgRoleToRoleId(role) === 'org:org_viewer';\n}\n\n/** Returns true if the role can manage growth assets (ladders, cycles, competencies). */\nexport function canManageGrowth(role: OrgRole | string): boolean {\n const roleId = orgRoleToRoleId(role);\n return !!roleId && ROLE_FEATURES[roleId].includes(Feature.ORG_GROWTH_ADMIN_ACCESS);\n}\n","export const BadgeKeys = {\n // Milestone - Brags\n FIRST_BRAG: 'first_brag',\n BRAGS_10: '10_brags',\n BRAGS_25: '25_brags',\n BRAGS_100: '100_brags',\n BRAGS_500: '500_brags',\n\n // Milestone - Features\n FIRST_OBJECTIVE: 'first_objective',\n FIRST_COMPANY: 'first_company',\n FIRST_REPORT: 'first_report',\n\n // Streaks\n STREAK_7_DAY: '7_day_streak',\n STREAK_30_DAY: '30_day_streak',\n STREAK_90_DAY: '90_day_streak',\n\n // Goal Completion\n FIRST_OBJECTIVE_COMPLETED: 'first_objective_completed',\n OBJECTIVES_COMPLETED_5: '5_objectives_completed',\n OBJECTIVES_COMPLETED_25: '25_objectives_completed',\n} as const;\n\nexport type BadgeKey = (typeof BadgeKeys)[keyof typeof BadgeKeys];\n\nexport const BadgeCategories = {\n MILESTONE: 'milestone',\n STREAK: 'streak',\n GOAL: 'goal',\n} as const;\n\nexport type BadgeCategory = (typeof BadgeCategories)[keyof typeof BadgeCategories];\n\nexport interface BadgeDefinition {\n key: BadgeKey;\n category: BadgeCategory;\n threshold: number | null;\n}\n\nexport const BADGE_DEFINITIONS: BadgeDefinition[] = [\n // Milestone - Brags\n { key: BadgeKeys.FIRST_BRAG, category: BadgeCategories.MILESTONE, threshold: 1 },\n { key: BadgeKeys.BRAGS_10, category: BadgeCategories.MILESTONE, threshold: 10 },\n { key: BadgeKeys.BRAGS_25, category: BadgeCategories.MILESTONE, threshold: 25 },\n { key: BadgeKeys.BRAGS_100, category: BadgeCategories.MILESTONE, threshold: 100 },\n { key: BadgeKeys.BRAGS_500, category: BadgeCategories.MILESTONE, threshold: 500 },\n\n // Milestone - Features\n { key: BadgeKeys.FIRST_OBJECTIVE, category: BadgeCategories.MILESTONE, threshold: 1 },\n { key: BadgeKeys.FIRST_COMPANY, category: BadgeCategories.MILESTONE, threshold: 1 },\n { key: BadgeKeys.FIRST_REPORT, category: BadgeCategories.MILESTONE, threshold: 1 },\n\n // Streaks\n { key: BadgeKeys.STREAK_7_DAY, category: BadgeCategories.STREAK, threshold: 7 },\n { key: BadgeKeys.STREAK_30_DAY, category: BadgeCategories.STREAK, threshold: 30 },\n { key: BadgeKeys.STREAK_90_DAY, category: BadgeCategories.STREAK, threshold: 90 },\n\n // Goal Completion\n {\n key: BadgeKeys.FIRST_OBJECTIVE_COMPLETED,\n category: BadgeCategories.GOAL,\n threshold: 1,\n },\n { key: BadgeKeys.OBJECTIVES_COMPLETED_5, category: BadgeCategories.GOAL, threshold: 5 },\n { key: BadgeKeys.OBJECTIVES_COMPLETED_25, category: BadgeCategories.GOAL, threshold: 25 },\n];\n\n// Badge keys grouped by trigger type for easier lookup\nexport const BRAG_MILESTONE_BADGES = [\n BadgeKeys.FIRST_BRAG,\n BadgeKeys.BRAGS_10,\n BadgeKeys.BRAGS_25,\n BadgeKeys.BRAGS_100,\n BadgeKeys.BRAGS_500,\n] as const;\n\nexport const STREAK_BADGES = [\n BadgeKeys.STREAK_7_DAY,\n BadgeKeys.STREAK_30_DAY,\n BadgeKeys.STREAK_90_DAY,\n] as const;\n\nexport const OBJECTIVE_COMPLETED_BADGES = [\n BadgeKeys.FIRST_OBJECTIVE_COMPLETED,\n BadgeKeys.OBJECTIVES_COMPLETED_5,\n BadgeKeys.OBJECTIVES_COMPLETED_25,\n] as const;\n","export const STORY_CATEGORIES = [\n 'leadership',\n 'technical_excellence',\n 'problem_solving',\n 'innovation',\n 'teamwork',\n 'communication',\n 'project_delivery',\n 'customer_impact',\n 'mentorship',\n 'process_improvement',\n] as const;\n\nexport type StoryCategory = (typeof STORY_CATEGORIES)[number];\n\nexport const STORY_CATEGORY_LABELS: Record<StoryCategory, string> = {\n leadership: 'Leadership',\n technical_excellence: 'Technical Excellence',\n problem_solving: 'Problem Solving',\n innovation: 'Innovation',\n teamwork: 'Teamwork',\n communication: 'Communication',\n project_delivery: 'Project Delivery',\n customer_impact: 'Customer Impact',\n mentorship: 'Mentorship',\n process_improvement: 'Process Improvement',\n};\n","import { requirePaidPlan } from '../api-client.js';\nimport { getUserInfo } from '../credentials.js';\n\nexport interface ToolResult {\n content: Array<{ type: string; text: string }>;\n isError?: boolean;\n}\n\nconst NOT_AUTHENTICATED: ToolResult = {\n content: [{ type: 'text', text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.' }],\n isError: true,\n};\n\nexport const toolError = (text: string): ToolResult => ({ content: [{ type: 'text', text }], isError: true });\n\n/**\n * Runs a story tool for the signed-in user: the paid-plan gate, the login check, and readable\n * errors (prefixed with `failure`) when the API call fails.\n */\nexport async function runForUser(\n failure: string,\n run: (userId: string) => Promise<ToolResult>\n): Promise<ToolResult> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n const userInfo = getUserInfo();\n if (!userInfo) return NOT_AUTHENTICATED;\n return await run(userInfo.id);\n } catch (error) {\n if (error instanceof Error && (error.message === 'NOT_AUTHENTICATED' || error.message === 'TOKEN_EXPIRED')) {\n return NOT_AUTHENTICATED;\n }\n return toolError(`${failure}: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n","import { apiRequest } from '../api-client.js';\nimport type { StoryCategory, StoryItem } from '@bragduck/types';\nimport { STORY_CATEGORY_LABELS } from '@bragduck/types';\nimport { runForUser, type ToolResult } from './story-tool-shared.js';\n\nexport const getStoryTool = {\n name: 'get_story',\n description:\n 'Get one STAR interview story from BragDuck in full: Situation, Task, Action, Result, competencies, ' +\n 'category, Strength/Impact scores (1-10) and the AI summary of how to improve it. ' +\n 'Call this before update_story so the improvement builds on the current text. ' +\n 'Story IDs come from list_stories.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n story_id: { type: 'string', description: 'ID of the story (from list_stories)' },\n },\n required: ['story_id'],\n },\n annotations: { title: 'Get story', readOnlyHint: true },\n};\n\ninterface GetStoryInput {\n story_id: string;\n}\n\nexport function formatStory(story: StoryItem): string {\n const category = story.category ? STORY_CATEGORY_LABELS[story.category as StoryCategory] : null;\n return [\n `**${story.title}** (ID: ${story.id})`,\n `${category ? `Category: ${category} | ` : ''}Strength: ${story.strengthScore ?? '-'}/10 | Impact: ${story.impactScore ?? '-'}/10`,\n `How to improve: ${story.aiSummary || 'Not rated yet'}`,\n `Competencies: ${story.competencies.length > 0 ? story.competencies.join(', ') : 'None'}`,\n '',\n `Situation: ${story.situation}`,\n `Task: ${story.task}`,\n `Action: ${story.action}`,\n `Result: ${story.result}`,\n ].join('\\n');\n}\n\nexport function handleGetStory(input: GetStoryInput): Promise<ToolResult> {\n return runForUser('Failed to get story', async (userId) => {\n const story = await apiRequest<StoryItem>(`/v1/users/${userId}/stories/${input.story_id}`);\n return { content: [{ type: 'text', text: formatStory(story) }] };\n });\n}\n","import { apiRequest } from '../api-client.js';\nimport type { StoryItem } from '@bragduck/types';\nimport { runForUser, toolError, type ToolResult } from './story-tool-shared.js';\n\nexport const updateStoryTool = {\n name: 'update_story',\n description:\n 'Update a STAR interview story in BragDuck, for example to make it stronger: a sharper Situation, ' +\n 'concrete first-person Actions, and a Result with real, measurable impact. ' +\n 'Read the story with get_story first and only send the fields you change; the rest are kept. ' +\n 'Never invent metrics or facts — ask the user for missing numbers. ' +\n 'Show the changes as a markdown table (Field | Current | Proposed) and call this in the same response — ' +\n 'the tool permission prompt (Allow/Deny) IS the confirmation. ' +\n 'Changing the title or STAR text re-scores the story shortly after.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n story_id: { type: 'string', description: 'ID of the story (from list_stories)' },\n title: { type: 'string', minLength: 5, maxLength: 200, description: 'New title' },\n situation: { type: 'string', minLength: 10, description: 'New Situation: context and background' },\n task: { type: 'string', minLength: 10, description: 'New Task: your responsibility or challenge' },\n action: { type: 'string', minLength: 10, description: 'New Action: what you specifically did' },\n result: { type: 'string', minLength: 10, description: 'New Result: the outcome and measurable impact' },\n competencies: {\n type: 'array',\n items: { type: 'string', minLength: 2, maxLength: 50 },\n maxItems: 20,\n description: 'Replaces the competencies the story demonstrates',\n },\n bragIds: {\n type: 'array',\n items: { type: 'string' },\n maxItems: 20,\n description: 'Replaces the IDs of the brags that back the story',\n },\n category: {\n type: ['string', 'null'],\n description:\n 'Interview category (e.g. leadership, technical_excellence, problem_solving, innovation, teamwork, ' +\n 'communication, project_delivery, customer_impact, mentorship, process_improvement), or null to clear it',\n },\n orgId: {\n type: ['string', 'null'],\n description: 'Organisation ID to move the story to, or null to remove it from any organisation',\n },\n },\n required: ['story_id'],\n },\n annotations: { title: 'Update story', destructiveHint: false, idempotentHint: true },\n};\n\nconst UPDATABLE_FIELDS = [\n 'title',\n 'situation',\n 'task',\n 'action',\n 'result',\n 'competencies',\n 'bragIds',\n 'category',\n 'orgId',\n] as const;\n\n/** Changing any of these makes the server rate the story again */\nconst RATED_FIELDS = new Set(['title', 'situation', 'task', 'action', 'result']);\n\ntype UpdateStoryInput = { story_id: string } & Partial<\n Record<(typeof UPDATABLE_FIELDS)[number], string | string[] | null>\n>;\n\nexport function handleUpdateStory(input: UpdateStoryInput): Promise<ToolResult> {\n const body = Object.fromEntries(\n UPDATABLE_FIELDS.filter((field) => input[field] !== undefined).map((field) => [field, input[field]])\n );\n if (Object.keys(body).length === 0) {\n return Promise.resolve(toolError('Nothing to update: pass at least one field to change.'));\n }\n\n return runForUser('Failed to update story', async (userId) => {\n const story = await apiRequest<StoryItem>(`/v1/users/${userId}/stories/${input.story_id}`, {\n method: 'PATCH',\n body,\n });\n const rescored = Object.keys(body).some((field) => RATED_FIELDS.has(field))\n ? '\\nThe story is being re-scored; call get_story in a moment to see the new scores.'\n : '';\n return { content: [{ type: 'text', text: `Story updated: \"${story.title}\"${rescored}` }] };\n });\n}\n","import { apiRequest } from '../api-client.js';\nimport { runForUser, type ToolResult } from './story-tool-shared.js';\n\nexport const deleteStoryTool = {\n name: 'delete_story',\n description:\n 'Delete a STAR interview story from BragDuck. It cannot be restored from the agent. ' +\n 'Before calling, name the story (title) and ask the user for an explicit yes in plain text; ' +\n 'never delete as part of improving a story — use update_story for that.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n story_id: { type: 'string', description: 'ID of the story to delete (from list_stories)' },\n },\n required: ['story_id'],\n },\n annotations: { title: 'Delete story', destructiveHint: true, idempotentHint: true },\n};\n\ninterface DeleteStoryInput {\n story_id: string;\n}\n\nexport function handleDeleteStory(input: DeleteStoryInput): Promise<ToolResult> {\n return runForUser('Failed to delete story', async (userId) => {\n await apiRequest<void>(`/v1/users/${userId}/stories/${input.story_id}`, { method: 'DELETE' });\n return { content: [{ type: 'text', text: 'Story deleted.' }] };\n });\n}\n","import { apiRequest, requirePaidPlan } from '../api-client.js';\nimport { getUserInfo } from '../credentials.js';\nimport type { ListStoriesResponse, StoryCategory } from '@bragduck/types';\nimport { STORY_CATEGORY_LABELS } from '@bragduck/types';\n\nexport const listStoriesTool = {\n name: 'list_stories',\n description:\n 'List STAR interview stories from BragDuck. ' +\n 'Use when the user wants to review their interview stories or search for stories by category.',\n inputSchema: {\n type: 'object' as const,\n properties: {\n limit: {\n type: 'number',\n minimum: 1,\n maximum: 100,\n description: 'Max stories to return (default 10)',\n },\n category: {\n type: 'string',\n description: 'Filter by category (e.g. leadership, technical_excellence, problem_solving)',\n },\n orgId: {\n type: 'string',\n description: 'Filter by organisation ID',\n },\n search: {\n type: 'string',\n description: 'Search stories by keyword',\n },\n },\n },\n};\n\ninterface ListStoriesInput {\n limit?: number;\n category?: string;\n orgId?: string;\n search?: string;\n}\n\nexport async function handleListStories(\n input: ListStoriesInput\n): Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }> {\n const planError = await requirePaidPlan();\n if (planError) return planError;\n\n try {\n const userInfo = getUserInfo();\n if (!userInfo) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n\n const params: Record<string, string> = {\n page: '1',\n perPage: String(input.limit || 10),\n };\n if (input.category) params.category = input.category;\n if (input.orgId) params.orgId = input.orgId;\n if (input.search) params.search = input.search;\n\n const response = await apiRequest<ListStoriesResponse>(\n `/v1/users/${userInfo.id}/stories`,\n { params }\n );\n\n if (response.items.length === 0) {\n return {\n content: [{ type: 'text', text: 'No stories found.' }],\n };\n }\n\n const lines = response.items.map((story) => {\n const categoryLabel = story.category\n ? STORY_CATEGORY_LABELS[story.category as StoryCategory]\n : null;\n const categoryPart = categoryLabel ? `Category: ${categoryLabel} | ` : '';\n const strengthPart = `Strength: ${story.strengthScore ?? '-'}/10`;\n const impactPart = `Impact: ${story.impactScore ?? '-'}/10`;\n const summary = story.aiSummary || 'No AI summary yet';\n const competencies = story.competencies.length > 0\n ? story.competencies.join(', ')\n : 'None';\n\n return (\n `**${story.title}** (ID: ${story.id})\\n` +\n `${categoryPart}${strengthPart} | ${impactPart}\\n` +\n `${summary}\\n` +\n `Competencies: ${competencies}`\n );\n });\n\n const summary = `Found ${response.total} stories (showing ${response.items.length}):`;\n\n return {\n content: [{ type: 'text', text: `${summary}\\n\\n${lines.join('\\n\\n')}` }],\n };\n } catch (error) {\n if (error instanceof Error && (error.message === 'NOT_AUTHENTICATED' || error.message === 'TOKEN_EXPIRED')) {\n return {\n content: [{\n type: 'text',\n text: 'Not authenticated with BragDuck. Please call the \"login\" tool first.',\n }],\n isError: true,\n };\n }\n return {\n content: [{\n type: 'text',\n text: `Failed to list stories: ${error instanceof Error ? error.message : String(error)}`,\n }],\n isError: true,\n };\n }\n}\n"],"mappings":"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","names":["randomBytes","randomBytes","server","API_BASE_URL","OAUTH_CLIENT_ID","toAgentExternalType","IMPACT_LABELS","Conf","APP_NAME","summary"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bragduck/mcp",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "BragDuck MCP server for logging career achievements from AI agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,15 +40,19 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^20.11.24",
43
+ "tsup": "^8.3.5",
43
44
  "typescript": "^5.3.3",
45
+ "vitest": "^2.1.8",
44
46
  "@bragduck/types": "0.1.36"
45
47
  },
46
48
  "engines": {
47
49
  "node": ">=20.0.0"
48
50
  },
49
51
  "scripts": {
50
- "build": "tsc",
51
- "dev": "tsc --watch",
52
- "type-check": "tsc --noEmit"
52
+ "build": "tsup",
53
+ "dev": "tsup --watch",
54
+ "type-check": "tsc --noEmit",
55
+ "test": "vitest run && tsup && node scripts/check-bundle.mjs",
56
+ "test:unit": "vitest run"
53
57
  }
54
58
  }
@@ -1,29 +0,0 @@
1
- /**
2
- * Make an authenticated API request to the BragDuck backend.
3
- * Automatically retries once on 401 with a refreshed token.
4
- */
5
- export declare function apiRequest<T>(endpoint: string, options?: {
6
- method?: string;
7
- body?: unknown;
8
- params?: Record<string, string | string[]>;
9
- }): Promise<T>;
10
- interface SubscriptionStatus {
11
- tier: string;
12
- status: string;
13
- }
14
- /**
15
- * Fetch the current user's subscription tier.
16
- */
17
- export declare function getSubscriptionStatus(): Promise<SubscriptionStatus>;
18
- /**
19
- * Returns an MCP error response if the user is on the FREE plan, otherwise null.
20
- * Call this at the start of every non-login tool handler.
21
- */
22
- export declare function requirePaidPlan(): Promise<{
23
- content: Array<{
24
- type: string;
25
- text: string;
26
- }>;
27
- isError: true;
28
- } | null>;
29
- export {};