shakers-cli-beta 0.8.0-beta.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 (218) hide show
  1. package/LICENSE +59 -0
  2. package/README.md +1175 -0
  3. package/bin/add-agent.js +88 -0
  4. package/bin/add-project.js +88 -0
  5. package/bin/add-role.js +61 -0
  6. package/bin/add-skill.js +88 -0
  7. package/bin/ai-usage.js +81 -0
  8. package/bin/alma.js +99 -0
  9. package/bin/applications.js +69 -0
  10. package/bin/availability.js +70 -0
  11. package/bin/certifications.js +69 -0
  12. package/bin/certify.js +73 -0
  13. package/bin/change-role.js +61 -0
  14. package/bin/config.js +120 -0
  15. package/bin/experiences.js +69 -0
  16. package/bin/find-projects.js +89 -0
  17. package/bin/invitations.js +69 -0
  18. package/bin/lang.js +70 -0
  19. package/bin/login.js +283 -0
  20. package/bin/mcp.js +98 -0
  21. package/bin/me.js +69 -0
  22. package/bin/onboarding.js +134 -0
  23. package/bin/portfolios.js +69 -0
  24. package/bin/rate.js +70 -0
  25. package/bin/register.js +325 -0
  26. package/bin/report-html.js +134 -0
  27. package/bin/save.js +77 -0
  28. package/bin/shakers.js +344 -0
  29. package/bin/share.js +72 -0
  30. package/bin/show-project.js +72 -0
  31. package/bin/socials.js +70 -0
  32. package/bin/superadmin.js +351 -0
  33. package/package.json +40 -0
  34. package/src/agent-category.js +13 -0
  35. package/src/agent-evaluation.js +177 -0
  36. package/src/agent-org-chart.js +355 -0
  37. package/src/agent-synthesis.js +132 -0
  38. package/src/agent-usage.js +118 -0
  39. package/src/agents-client.js +165 -0
  40. package/src/ai-fluency-cell.js +72 -0
  41. package/src/ai-profile-client.js +45 -0
  42. package/src/ai-profile-preview.js +154 -0
  43. package/src/ai.js +66 -0
  44. package/src/alma-client.js +123 -0
  45. package/src/alma-flow.js +32 -0
  46. package/src/alma-repo-context.js +47 -0
  47. package/src/alma-shell.js +161 -0
  48. package/src/ansi.js +62 -0
  49. package/src/applications-client.js +79 -0
  50. package/src/applications-flow.js +58 -0
  51. package/src/auth-client.js +144 -0
  52. package/src/auth-device-client.js +118 -0
  53. package/src/auth-method-picker.js +44 -0
  54. package/src/auth-methods.js +35 -0
  55. package/src/auth-session-store.js +214 -0
  56. package/src/authorship.js +203 -0
  57. package/src/automations-detector.js +170 -0
  58. package/src/availability-client.js +92 -0
  59. package/src/availability-flow.js +105 -0
  60. package/src/backend-request.js +170 -0
  61. package/src/blind-approval-extract.js +48 -0
  62. package/src/brand-ansi.js +14 -0
  63. package/src/brand-svg.js +9 -0
  64. package/src/browser-tools-detector.js +21 -0
  65. package/src/build-next-level.js +56 -0
  66. package/src/certifications-client.js +89 -0
  67. package/src/certifications-flow.js +75 -0
  68. package/src/certify-dimension-client.js +217 -0
  69. package/src/certify-dimension-flow.js +193 -0
  70. package/src/certify-disclaimer.js +51 -0
  71. package/src/certify-remediation-prompt.js +23 -0
  72. package/src/cli-args.js +83 -0
  73. package/src/config-dir.js +120 -0
  74. package/src/config.js +778 -0
  75. package/src/consent-flow.js +115 -0
  76. package/src/consent-skip.js +27 -0
  77. package/src/decisions-extract.js +43 -0
  78. package/src/detect-repos.js +145 -0
  79. package/src/detected-tools.js +15 -0
  80. package/src/detection-evidence.js +105 -0
  81. package/src/detection-noise.js +132 -0
  82. package/src/detectors.js +221 -0
  83. package/src/device-auth-store.js +100 -0
  84. package/src/device-login.js +118 -0
  85. package/src/email-verification.js +152 -0
  86. package/src/env-paths.js +16 -0
  87. package/src/find-projects-cache.js +89 -0
  88. package/src/find-projects-client.js +138 -0
  89. package/src/find-projects-flow.js +164 -0
  90. package/src/git-activity.js +123 -0
  91. package/src/graph-certs.js +58 -0
  92. package/src/graph-generator.js +230 -0
  93. package/src/graph-scan.js +239 -0
  94. package/src/i18n.js +3264 -0
  95. package/src/inter-fonts.js +6 -0
  96. package/src/interactive-select.js +183 -0
  97. package/src/interview-client.js +226 -0
  98. package/src/interview-transcript.js +56 -0
  99. package/src/inventory-client.js +90 -0
  100. package/src/invitations-client.js +66 -0
  101. package/src/invitations-flow.js +50 -0
  102. package/src/lang-client.js +147 -0
  103. package/src/lang-codes.js +14 -0
  104. package/src/lang-flow.js +85 -0
  105. package/src/legal-notice.js +53 -0
  106. package/src/locale.js +51 -0
  107. package/src/markdown-ansi.js +50 -0
  108. package/src/maturity.js +111 -0
  109. package/src/mcp-ai-usage-result-tool.js +109 -0
  110. package/src/mcp-ai-usage-tool.js +333 -0
  111. package/src/mcp-alma-tools.js +91 -0
  112. package/src/mcp-applications-tools.js +42 -0
  113. package/src/mcp-ask-tools.js +50 -0
  114. package/src/mcp-auth-form.js +282 -0
  115. package/src/mcp-availability-tools.js +43 -0
  116. package/src/mcp-certifications-tools.js +42 -0
  117. package/src/mcp-certify-dimension-tools.js +58 -0
  118. package/src/mcp-detector.js +223 -0
  119. package/src/mcp-device-login-tools.js +173 -0
  120. package/src/mcp-find-projects-tools.js +67 -0
  121. package/src/mcp-install.js +245 -0
  122. package/src/mcp-invitations-tools.js +42 -0
  123. package/src/mcp-lang-tools.js +39 -0
  124. package/src/mcp-onboarding-tools.js +388 -0
  125. package/src/mcp-portfolios-tools.js +46 -0
  126. package/src/mcp-profile-tools.js +46 -0
  127. package/src/mcp-rate-tools.js +42 -0
  128. package/src/mcp-register-tools.js +517 -0
  129. package/src/mcp-report-tools.js +101 -0
  130. package/src/mcp-role-tools.js +112 -0
  131. package/src/mcp-saved-positions-tools.js +55 -0
  132. package/src/mcp-server.js +170 -0
  133. package/src/mcp-show-project-tools.js +50 -0
  134. package/src/mcp-socials-tools.js +39 -0
  135. package/src/memory-structure-detector.js +108 -0
  136. package/src/model-call-record.js +198 -0
  137. package/src/onboarding-availability.js +26 -0
  138. package/src/onboarding-client.js +364 -0
  139. package/src/onboarding-flow.js +1078 -0
  140. package/src/onboarding-livekit-client.js +102 -0
  141. package/src/onboarding-status-client.js +42 -0
  142. package/src/open-file.js +32 -0
  143. package/src/osc-link.js +16 -0
  144. package/src/portfolio-client.js +164 -0
  145. package/src/portfolio-git-info.js +61 -0
  146. package/src/portfolios-client.js +85 -0
  147. package/src/portfolios-flow.js +79 -0
  148. package/src/profile-client.js +125 -0
  149. package/src/profile-flow.js +115 -0
  150. package/src/profile-switch.js +46 -0
  151. package/src/prompt-select.js +29 -0
  152. package/src/rate-client.js +82 -0
  153. package/src/rate-flow.js +137 -0
  154. package/src/render-ai-fluency.js +266 -0
  155. package/src/render-certification.js +177 -0
  156. package/src/render-html.js +140 -0
  157. package/src/render-report-text.js +27 -0
  158. package/src/render-sheet.js +261 -0
  159. package/src/render-terminal.js +109 -0
  160. package/src/render-usage-report-text.js +155 -0
  161. package/src/repo-context.js +215 -0
  162. package/src/report-gating.js +49 -0
  163. package/src/report-store.js +226 -0
  164. package/src/report-theme.js +177 -0
  165. package/src/report-view-client.js +50 -0
  166. package/src/reveal-output.js +29 -0
  167. package/src/roadmap-content.js +453 -0
  168. package/src/roadmap-personalization.js +132 -0
  169. package/src/roadmap-prompt.js +154 -0
  170. package/src/roles-client.js +114 -0
  171. package/src/roles-flow.js +192 -0
  172. package/src/sanitize-network-text.js +36 -0
  173. package/src/saved-positions-client.js +71 -0
  174. package/src/saved-positions-flow.js +50 -0
  175. package/src/scan-exclusions.js +50 -0
  176. package/src/scanner.js +612 -0
  177. package/src/scope-signals.js +148 -0
  178. package/src/secret-prompt.js +72 -0
  179. package/src/session-gate.js +30 -0
  180. package/src/session-refresh.js +51 -0
  181. package/src/session-scan.js +336 -0
  182. package/src/session-signals.js +36 -0
  183. package/src/shakers-wordmark.js +17 -0
  184. package/src/share-card.js +362 -0
  185. package/src/share-certification.js +111 -0
  186. package/src/share-consent-state.js +157 -0
  187. package/src/share-payload.js +300 -0
  188. package/src/share.js +184 -0
  189. package/src/show-project-client.js +58 -0
  190. package/src/show-project-flow.js +119 -0
  191. package/src/signup-client.js +91 -0
  192. package/src/skills-client.js +102 -0
  193. package/src/socials-client.js +91 -0
  194. package/src/socials-flow.js +83 -0
  195. package/src/specificity-extract.js +41 -0
  196. package/src/start-add-agents.js +306 -0
  197. package/src/start-add-portfolio.js +388 -0
  198. package/src/start-add-skills.js +260 -0
  199. package/src/stdin-ask.js +129 -0
  200. package/src/steering-extract.js +43 -0
  201. package/src/strip-internal-comments.js +45 -0
  202. package/src/tech-detector.js +295 -0
  203. package/src/templates/report-sheet.html +572 -0
  204. package/src/terminal-agents.js +199 -0
  205. package/src/terminal-format.js +85 -0
  206. package/src/terminal-progress.js +106 -0
  207. package/src/terminal-sections.js +219 -0
  208. package/src/tier-analysis.js +126 -0
  209. package/src/tier-engine.js +114 -0
  210. package/src/tier-projection.js +117 -0
  211. package/src/transcript-turns.js +49 -0
  212. package/src/translations-client.js +84 -0
  213. package/src/typewriter.js +154 -0
  214. package/src/usage-model-calls.js +119 -0
  215. package/src/usage-oneshot-commands.js +180 -0
  216. package/src/usage-repo-scope.js +162 -0
  217. package/src/usage-run-steps.js +338 -0
  218. package/src/work-streams.js +74 -0
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ // MCP: role/cluster management — list addable roles, add one, list held roles,
4
+ // and set the main role. The onboarding final step is set_main_role after the
5
+ // interview (or after skipping it).
6
+
7
+ const EMPTY_SCHEMA = { type: 'object', properties: {} };
8
+
9
+ const CLUSTER_SCHEMA = {
10
+ type: 'object',
11
+ properties: {
12
+ clusterId: { type: 'string', description: 'The role clusterId from list_available_roles / list_my_roles. Internal handle — refer to the role by its name to the talent, never show the id.' },
13
+ },
14
+ required: ['clusterId'],
15
+ };
16
+
17
+ function makeRoleTools(deps = {}) {
18
+ const {
19
+ getAvailableRolesEndpoint = require('./config').getAvailableRolesEndpoint,
20
+ getSetMainRoleEndpoint = require('./config').getSetMainRoleEndpoint,
21
+ getAssignedClustersEndpoint = require('./config').getAssignedClustersEndpoint,
22
+ getMyCertificationsEndpoint = require('./config').getMyCertificationsEndpoint,
23
+ requestAvailableRoles = require('./roles-client').requestAvailableRoles,
24
+ requestAssignedClusters = require('./roles-client').requestAssignedClusters,
25
+ requestAddGrowthRole = require('./roles-client').requestAddGrowthRole,
26
+ requestSetMainRole = require('./roles-client').requestSetMainRole,
27
+ requestMeCertifications = require('./certifications-client').requestMeCertifications,
28
+ loadAuthSession = require('./auth-session-store').loadAuthSession,
29
+ sessionStatus = require('./auth-session-store').sessionStatus,
30
+ dedupeByCluster = require('./roles-flow').dedupeByCluster,
31
+ } = deps;
32
+
33
+ const requireSession = () => {
34
+ const session = loadAuthSession();
35
+ if (sessionStatus(session) !== 'active') {
36
+ throw new Error('no active Shakers session — call the login tool (or run `shakers login`) first.');
37
+ }
38
+ return session;
39
+ };
40
+
41
+ const shape = (r) => ({ clusterId: r.clusterId, name: r.name, category: r.category, ...(r.proposalKind ? { proposalKind: r.proposalKind } : {}) });
42
+
43
+ async function listAvailableRoles() {
44
+ const session = requireSession();
45
+ const res = await requestAvailableRoles({ hubAccessToken: session.hubAccessToken }, { endpoint: getAvailableRolesEndpoint() });
46
+ if (!res.ok) return { ok: false, reason: res.reason };
47
+ return { ok: true, roles: dedupeByCluster(res.roles).map(shape) };
48
+ }
49
+
50
+ async function addRole(args = {}) {
51
+ const session = requireSession();
52
+ if (!args.clusterId) return { ok: false, reason: 'no-cluster' };
53
+ const res = await requestAddGrowthRole({ clusterId: args.clusterId, hubAccessToken: session.hubAccessToken }, { endpoint: getAssignedClustersEndpoint() });
54
+ if (!res.ok) {
55
+ if (res.reason === 'already-assigned') return { ok: true, clusterId: args.clusterId, alreadyAssigned: true };
56
+ return { ok: false, reason: res.reason };
57
+ }
58
+ return { ok: true, clusterId: args.clusterId };
59
+ }
60
+
61
+ async function listMyRoles() {
62
+ const session = requireSession();
63
+ const hubAccessToken = session.hubAccessToken;
64
+ const held = [];
65
+ let mainClusterId = null;
66
+ const cert = await requestMeCertifications({ hubAccessToken }, { endpoint: getMyCertificationsEndpoint() });
67
+ if (cert.ok) {
68
+ if (cert.mainRole && cert.mainRole.clusterId) { held.push(cert.mainRole); mainClusterId = cert.mainRole.clusterId; }
69
+ for (const g of cert.growingInto || []) held.push(g);
70
+ }
71
+ const assigned = await requestAssignedClusters({ hubAccessToken }, { endpoint: getAssignedClustersEndpoint() });
72
+ if (assigned.ok) for (const c of assigned.clusters || []) held.push(c);
73
+ return { ok: true, mainClusterId, roles: dedupeByCluster(held).map(shape) };
74
+ }
75
+
76
+ async function setMainRole(args = {}) {
77
+ const session = requireSession();
78
+ if (!args.clusterId) return { ok: false, reason: 'no-cluster' };
79
+ const res = await requestSetMainRole({ clusterId: args.clusterId, hubAccessToken: session.hubAccessToken }, { endpoint: getSetMainRoleEndpoint() });
80
+ if (!res.ok) return { ok: false, reason: res.reason };
81
+ return { ok: true, clusterId: args.clusterId };
82
+ }
83
+
84
+ return [
85
+ {
86
+ name: 'list_available_roles',
87
+ description: "List the roles the talent can add to their profile (the cluster catalog minus roles they already hold). Each row has clusterId (internal handle), name, category and an optional proposalKind (why it is suggested). Present roles to the talent by NAME; never show clusterId. Requires an active session.",
88
+ inputSchema: EMPTY_SCHEMA,
89
+ handler: listAvailableRoles,
90
+ },
91
+ {
92
+ name: 'add_role',
93
+ description: "Add a role to the talent's profile (a GROWTH role) by clusterId from list_available_roles. Requires an active session. Returns { clusterId, alreadyAssigned? }.",
94
+ inputSchema: CLUSTER_SCHEMA,
95
+ handler: addRole,
96
+ },
97
+ {
98
+ name: 'list_my_roles',
99
+ description: "List the roles the talent already holds, plus mainClusterId (their current main role). Use it before set_main_role. clusterId is an internal handle — refer to roles by name to the talent. Requires an active session.",
100
+ inputSchema: EMPTY_SCHEMA,
101
+ handler: listMyRoles,
102
+ },
103
+ {
104
+ name: 'set_main_role',
105
+ description: "Set the talent's MAIN role to one they already hold, by clusterId from list_my_roles. This is the onboarding FINAL step: after onboarding_interview_complete (or if the talent skipped the interview), offer them their held roles and set the one they pick. Also usable standalone to change the main role later. Requires an active session.",
106
+ inputSchema: CLUSTER_SCHEMA,
107
+ handler: setMainRole,
108
+ },
109
+ ];
110
+ }
111
+
112
+ module.exports = { makeRoleTools, EMPTY_SCHEMA, CLUSTER_SCHEMA };
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ // MCP: bookmark / un-bookmark a Position. Both idempotent, reversible, low-risk.
4
+
5
+ const ID_SCHEMA = {
6
+ type: 'object',
7
+ properties: { id: { type: 'string', description: 'Position id (UUID) — an internal handle from a prior list; never shown or mentioned to the talent.' } },
8
+ required: ['id'],
9
+ };
10
+
11
+ function makeSavedPositionsTools(deps = {}) {
12
+ const {
13
+ savePosition = (opts) => require('./saved-positions-client').savePosition({}, opts),
14
+ unsavePosition = (opts) => require('./saved-positions-client').unsavePosition({}, opts),
15
+ loadAuthSession = require('./auth-session-store').loadAuthSession,
16
+ sessionStatus = require('./auth-session-store').sessionStatus,
17
+ } = deps;
18
+
19
+ const requireLoginSession = () => {
20
+ const session = loadAuthSession();
21
+ if (sessionStatus(session) !== 'active') {
22
+ throw new Error('no active Shakers session — call the login tool (or run `shakers login`) first (this tool is session-gated).');
23
+ }
24
+ return session;
25
+ };
26
+
27
+ const toggle = (op) => async (args = {}) => {
28
+ const session = requireLoginSession();
29
+ const positionId = typeof args.id === 'string' ? args.id.trim() : null;
30
+ if (!positionId) return { ok: false, reason: 'no-id' };
31
+ const res = await op({ hubAccessToken: session.hubAccessToken, positionId });
32
+ if (!res.ok) return { ok: false, reason: res.reason };
33
+ return { ok: true, positionId };
34
+ };
35
+
36
+ return [
37
+ {
38
+ name: 'save_project',
39
+ description: 'Bookmark a project/Position for the talent (Find Positions "Saved"). Idempotent — saving twice is a no-op. Requires an active session and the position id. The position id is an INTERNAL handle for calling tools — never show or mention it to the talent.',
40
+ inputSchema: ID_SCHEMA,
41
+ handler: toggle(savePosition),
42
+ },
43
+ {
44
+ name: 'unsave_project',
45
+ description: 'Remove a project/Position bookmark for the talent. Idempotent — un-saving something not saved is not an error. Requires an active session and the position id. The position id is an INTERNAL handle for calling tools — never show or mention it to the talent.',
46
+ inputSchema: ID_SCHEMA,
47
+ handler: toggle(unsavePosition),
48
+ },
49
+ ];
50
+ }
51
+
52
+ module.exports = {
53
+ makeSavedPositionsTools,
54
+ ID_SCHEMA,
55
+ };
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+
5
+ const JSONRPC = '2.0';
6
+ const DEFAULT_PROTOCOL_VERSION = '2025-06-18';
7
+
8
+ const ERROR = {
9
+ PARSE: -32700,
10
+ INVALID_REQUEST: -32600,
11
+ METHOD_NOT_FOUND: -32601,
12
+ INVALID_PARAMS: -32602,
13
+ INTERNAL: -32603,
14
+ };
15
+
16
+ function isNotification(msg) {
17
+ return !msg || msg.id === undefined || msg.id === null;
18
+ }
19
+
20
+ function resultResponse(id, result) {
21
+ return { jsonrpc: JSONRPC, id, result };
22
+ }
23
+
24
+ function errorResponse(id, code, message, data) {
25
+ const error = { code, message };
26
+ if (data !== undefined) error.data = data;
27
+ return { jsonrpc: JSONRPC, id: id === undefined ? null : id, error };
28
+ }
29
+
30
+ function createMcpServer({ tools = [], serverInfo = {}, protocolVersion, beforeCall = null } = {}) {
31
+ const registry = new Map();
32
+ for (const t of tools) {
33
+ if (!t || !t.name || typeof t.handler !== 'function') {
34
+ throw new Error('each tool needs a name and a handler function');
35
+ }
36
+ registry.set(t.name, t);
37
+ }
38
+
39
+ const advertisedVersion = protocolVersion || DEFAULT_PROTOCOL_VERSION;
40
+
41
+ function shapeTool(t) {
42
+ return {
43
+ name: t.name,
44
+ description: t.description || '',
45
+ inputSchema: t.inputSchema || { type: 'object' },
46
+ };
47
+ }
48
+
49
+ function listTools() {
50
+ return { tools: [...registry.values()].map(shapeTool) };
51
+ }
52
+
53
+ async function listAvailableTools() {
54
+ const out = [];
55
+ for (const t of registry.values()) {
56
+ if (typeof t.available === 'function') {
57
+ let visible = true;
58
+ try {
59
+ visible = (await t.available()) !== false;
60
+ } catch {
61
+ visible = true;
62
+ }
63
+ if (!visible) continue;
64
+ }
65
+ out.push(shapeTool(t));
66
+ }
67
+ return { tools: out };
68
+ }
69
+
70
+ async function callTool(params) {
71
+ const name = params && params.name;
72
+ const tool = name && registry.get(name);
73
+ if (!tool) {
74
+ const err = new Error(`unknown tool: ${name}`);
75
+ err.rpcCode = ERROR.INVALID_PARAMS;
76
+ throw err;
77
+ }
78
+ const args = (params && params.arguments) || {};
79
+ // Optional per-call hook (auth-agnostic): the MCP server is long-lived, so this is where the email session's hub JWT gets refreshed before each tool runs.
80
+ if (typeof beforeCall === 'function') {
81
+ try { await beforeCall(name); } catch { /* hook must never break a tool call */ }
82
+ }
83
+ try {
84
+ const value = await tool.handler(args);
85
+ return {
86
+ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
87
+ isError: false,
88
+ };
89
+ } catch (e) {
90
+ return {
91
+ content: [{ type: 'text', text: `${name} tool error: ${e && e.message ? e.message : String(e)}` }],
92
+ isError: true,
93
+ };
94
+ }
95
+ }
96
+
97
+ async function handleMessage(msg) {
98
+ if (!msg || msg.jsonrpc !== JSONRPC || typeof msg.method !== 'string') {
99
+ if (isNotification(msg)) return null;
100
+ return errorResponse(msg && msg.id, ERROR.INVALID_REQUEST, 'invalid JSON-RPC request');
101
+ }
102
+
103
+ const { method, id } = msg;
104
+
105
+ if (method.startsWith('notifications/')) {
106
+ return null;
107
+ }
108
+
109
+ try {
110
+ switch (method) {
111
+ case 'initialize': {
112
+ const requested = msg.params && msg.params.protocolVersion;
113
+ return resultResponse(id, {
114
+ protocolVersion: requested || advertisedVersion,
115
+ capabilities: { tools: { listChanged: false } },
116
+ serverInfo: {
117
+ name: serverInfo.name || 'shakers-mcp',
118
+ version: serverInfo.version || '0.0.0',
119
+ },
120
+ });
121
+ }
122
+ case 'ping':
123
+ return resultResponse(id, {});
124
+ case 'tools/list':
125
+ return resultResponse(id, await listAvailableTools());
126
+ case 'tools/call':
127
+ return resultResponse(id, await callTool(msg.params));
128
+ default:
129
+ if (isNotification(msg)) return null;
130
+ return errorResponse(id, ERROR.METHOD_NOT_FOUND, `method not found: ${method}`);
131
+ }
132
+ } catch (e) {
133
+ if (isNotification(msg)) return null;
134
+ const code = (e && e.rpcCode) || ERROR.INTERNAL;
135
+ return errorResponse(id, code, e && e.message ? e.message : 'internal error');
136
+ }
137
+ }
138
+
139
+ function start({ input = process.stdin, output = process.stdout, onError } = {}) {
140
+ const rl = readline.createInterface({ input, crlfDelay: Infinity });
141
+ const write = (obj) => output.write(`${JSON.stringify(obj)}\n`);
142
+
143
+ rl.on('line', async (line) => {
144
+ const trimmed = line.trim();
145
+ if (!trimmed) return;
146
+ let msg;
147
+ try {
148
+ msg = JSON.parse(trimmed);
149
+ } catch {
150
+ write(errorResponse(null, ERROR.PARSE, 'parse error: invalid JSON'));
151
+ return;
152
+ }
153
+ try {
154
+ const response = await handleMessage(msg);
155
+ if (response) write(response);
156
+ } catch (e) {
157
+ if (onError) onError(e);
158
+ if (!isNotification(msg)) {
159
+ write(errorResponse(msg.id, ERROR.INTERNAL, e && e.message ? e.message : 'internal error'));
160
+ }
161
+ }
162
+ });
163
+
164
+ return rl;
165
+ }
166
+
167
+ return { handleMessage, listTools, listAvailableTools, callTool, start };
168
+ }
169
+
170
+ module.exports = { createMcpServer, ERROR, DEFAULT_PROTOCOL_VERSION };
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ // MCP: detail of one Position/Project (Find Positions detail view).
4
+
5
+ const SHOW_PROJECT_SCHEMA = {
6
+ type: 'object',
7
+ properties: {
8
+ id: { type: 'string', description: 'Position id (UUID or legacy Mongo id) — an internal handle from a prior list; never shown to the talent.' },
9
+ },
10
+ required: ['id'],
11
+ };
12
+
13
+ function makeShowProjectTools(deps = {}) {
14
+ const {
15
+ fetchPositionDetail = (opts) => require('./show-project-client').fetchPositionDetail({}, opts),
16
+ loadAuthSession = require('./auth-session-store').loadAuthSession,
17
+ sessionStatus = require('./auth-session-store').sessionStatus,
18
+ } = deps;
19
+
20
+ const requireLoginSession = () => {
21
+ const session = loadAuthSession();
22
+ if (sessionStatus(session) !== 'active') {
23
+ throw new Error('no active Shakers session — call the login tool (or run `shakers login`) first (show_project is session-gated).');
24
+ }
25
+ return session;
26
+ };
27
+
28
+ async function showProject(args = {}) {
29
+ const session = requireLoginSession();
30
+ const positionId = typeof args.id === 'string' ? args.id.trim() : null;
31
+ if (!positionId) return { ok: false, reason: 'no-id' };
32
+ const res = await fetchPositionDetail({ hubAccessToken: session.hubAccessToken, positionId });
33
+ if (!res.ok) return { ok: false, reason: res.reason };
34
+ return { ok: true, position: res.position, redirectUrl: res.redirectUrl };
35
+ }
36
+
37
+ return [
38
+ {
39
+ name: 'show_project',
40
+ description: "Get the full detail of one Position/Project available to the talent (Shakers Find Positions detail): title, parent project, company, budget, work mode, required skills/languages, description, goals, FAQs, and the talent's saved/applied/canApply state. Requires an active session and the position id. The `id` (and any positionId/projectId in the result) is an INTERNAL handle for calling tools — NEVER show or mention it to the talent; refer to the project by its title/company.",
41
+ inputSchema: SHOW_PROJECT_SCHEMA,
42
+ handler: showProject,
43
+ },
44
+ ];
45
+ }
46
+
47
+ module.exports = {
48
+ makeShowProjectTools,
49
+ SHOW_PROJECT_SCHEMA,
50
+ };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ // MCP: view the talent's social links. Read-only (editing is CLI-only).
4
+
5
+ const LIST_SOCIALS_SCHEMA = { type: 'object', properties: {} };
6
+
7
+ function makeSocialsTools(deps = {}) {
8
+ const {
9
+ fetchSocials = (opts) => require('./socials-client').fetchSocials({}, opts),
10
+ loadAuthSession = require('./auth-session-store').loadAuthSession,
11
+ sessionStatus = require('./auth-session-store').sessionStatus,
12
+ } = deps;
13
+
14
+ const requireLoginSession = () => {
15
+ const session = loadAuthSession();
16
+ if (sessionStatus(session) !== 'active') {
17
+ throw new Error('no active Shakers session — call the login tool (or run `shakers login`) first (list_socials is session-gated).');
18
+ }
19
+ return session;
20
+ };
21
+
22
+ async function listSocials() {
23
+ const session = requireLoginSession();
24
+ const res = await fetchSocials({ hubAccessToken: session.hubAccessToken });
25
+ if (!res.ok) return { ok: false, reason: res.reason };
26
+ return { ok: true, social: res.social };
27
+ }
28
+
29
+ return [
30
+ {
31
+ name: 'list_socials',
32
+ description: "View the talent's social links (linkedin/github/website/twitter/instagram/facebook/dribbble/behance — null when unset). Read-only. Requires an active session.",
33
+ inputSchema: LIST_SOCIALS_SCHEMA,
34
+ handler: listSocials,
35
+ },
36
+ ];
37
+ }
38
+
39
+ module.exports = { makeSocialsTools, LIST_SOCIALS_SCHEMA };
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { getHomeDir } = require('./env-paths');
6
+
7
+ // Deterministic (no-LLM) memory STRUCTURE detector (talents-ai-score, issue 016 / ADR-013-014).
8
+
9
+ const MAX_FILE_BYTES = 2_000_000; // safety cap: never read a pathologically huge file
10
+ const MAX_IMPORT_DEPTH = 5; // mirrors Claude Code's own documented import-hop limit
11
+ const MAX_FILES_VISITED = 100; // safety cap against import fan-out/cycles
12
+
13
+ // Claude Code's `@path/to/file` import syntax: an `@` at the start of a line or preceded by whitespace, followed by a non-space path.
14
+ const IMPORT_RE = /(?:^|\s)@(\S+)/gm;
15
+ const HEADER_RE = /^#{1,6}\s+\S/gm;
16
+
17
+ function safeReadText(file) {
18
+ try {
19
+ const st = fs.statSync(file);
20
+ if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;
21
+ return fs.readFileSync(file, 'utf8');
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function countMatches(re, text) {
28
+ const matches = text.match(re);
29
+ return matches ? matches.length : 0;
30
+ }
31
+
32
+ function extractImportPaths(text) {
33
+ const paths = [];
34
+ const re = new RegExp(IMPORT_RE);
35
+ let m;
36
+ while ((m = re.exec(text)) !== null) paths.push(m[1]);
37
+ return paths;
38
+ }
39
+
40
+ function resolveImportPath(importPath, fromDir, home) {
41
+ if (importPath.startsWith('~/')) return path.join(home, importPath.slice(2));
42
+ return path.resolve(fromDir, importPath);
43
+ }
44
+
45
+ // Recursively walks `@imports` starting at `file`.
46
+ function walkImports(file, home, depth, visited, budget) {
47
+ if (depth > MAX_IMPORT_DEPTH || visited.has(file) || budget.files >= MAX_FILES_VISITED) {
48
+ return { maxDepth: depth - 1, importCount: 0 };
49
+ }
50
+ visited.add(file);
51
+ budget.files += 1;
52
+
53
+ const text = safeReadText(file);
54
+ if (text === null) return { maxDepth: depth - 1, importCount: 0 };
55
+
56
+ const importPaths = extractImportPaths(text);
57
+ if (importPaths.length === 0) return { maxDepth: depth, importCount: 0 };
58
+
59
+ let maxDepth = depth;
60
+ let importCount = importPaths.length;
61
+ const fromDir = path.dirname(file);
62
+ for (const p of importPaths) {
63
+ const resolved = resolveImportPath(p, fromDir, home);
64
+ const sub = walkImports(resolved, home, depth + 1, visited, budget);
65
+ maxDepth = Math.max(maxDepth, sub.maxDepth);
66
+ importCount += sub.importCount;
67
+ }
68
+ return { maxDepth, importCount };
69
+ }
70
+
71
+ function analyzeFile(file, home) {
72
+ const text = safeReadText(file);
73
+ if (text === null) return null;
74
+ const sections = countMatches(HEADER_RE, text);
75
+ const sizeBytes = Buffer.byteLength(text, 'utf8');
76
+ const { maxDepth, importCount } = walkImports(file, home, 1, new Set(), { files: 0 });
77
+ return { sizeBytes, sections, imports: importCount, depth: maxDepth };
78
+ }
79
+
80
+ function knownContextFiles(root, home) {
81
+ return [
82
+ { id: 'CLAUDE.md', file: path.join(root, 'CLAUDE.md') },
83
+ { id: 'CLAUDE.md (home)', file: path.join(home, '.claude', 'CLAUDE.md') },
84
+ { id: 'AGENTS.md', file: path.join(root, 'AGENTS.md') },
85
+ { id: 'GEMINI.md', file: path.join(root, 'GEMINI.md') },
86
+ ];
87
+ }
88
+
89
+ // Deterministic (no-LLM) memory structure analysis, scoped to known context files (project ∪ home).
90
+ function analyzeMemoryStructure(root) {
91
+ const home = getHomeDir();
92
+ const files = [];
93
+ let totalImports = 0;
94
+ let maxDepth = 0;
95
+
96
+ for (const { id, file } of knownContextFiles(root, home)) {
97
+ const analysis = analyzeFile(file, home);
98
+ if (!analysis) continue;
99
+ files.push({ id, ...analysis });
100
+ totalImports += analysis.imports;
101
+ maxDepth = Math.max(maxDepth, analysis.depth);
102
+ }
103
+
104
+ // "layered" (anidada/por capas) means a REAL import chain — an import that itself imports further files — not just a single flat `@import`.
105
+ return { files, totalImports, maxDepth, layered: maxDepth > 2 };
106
+ }
107
+
108
+ module.exports = { analyzeMemoryStructure };