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,91 @@
1
+ 'use strict';
2
+
3
+ const { postJsonWithTimeout, extractSetCookie } = require('./backend-request');
4
+ const { SESSION_COOKIE_NAME } = require('./auth-client');
5
+ const { toUpperLang } = require('./lang-codes');
6
+
7
+ // Register a talent via the Hub's better-auth email engine, in two steps (owner decision, supersedes ADR-031 for the email path — WEB-login parity): 1.
8
+
9
+ const DEFAULT_TIMEOUT_MS = 30000;
10
+
11
+ function parseJson(raw) {
12
+ try { return raw ? JSON.parse(raw) : null; } catch { return null; }
13
+ }
14
+
15
+ function messageFrom(raw) {
16
+ const body = parseJson(raw);
17
+ if (!body || typeof body !== 'object') return null;
18
+ if (typeof body.message === 'string' && body.message) return body.message;
19
+ if (Array.isArray(body.message) && body.message.length) return body.message.join('; ');
20
+ return null;
21
+ }
22
+
23
+ function buildRegistrationContext({ preferredLanguage, newsletterConsent, freelanceType, freelanceIntent } = {}) {
24
+ const context = {};
25
+ if (typeof preferredLanguage === 'string' && preferredLanguage) context.preferredLanguage = toUpperLang(preferredLanguage);
26
+ context.newsletterConsent = newsletterConsent === true;
27
+ if (typeof freelanceType === 'string' && freelanceType) context.freelanceType = freelanceType;
28
+ if (typeof freelanceIntent === 'string' && freelanceIntent) context.freelanceIntent = freelanceIntent;
29
+ return context;
30
+ }
31
+
32
+ // Shared complete-registration call: authenticated by the email `cookie` or the device flow `bearerToken`.
33
+ async function requestCompleteRegistration(
34
+ context = {},
35
+ { endpoint, cookie = null, bearerToken = null, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
36
+ ) {
37
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
38
+ const headers = {};
39
+ if (cookie) headers.Cookie = cookie;
40
+ if (bearerToken) headers.Authorization = `Bearer ${bearerToken}`;
41
+ let complete;
42
+ try {
43
+ complete = await postJsonWithTimeout(
44
+ endpoint,
45
+ { userType: 'WORKS_TALENT', context },
46
+ timeoutMs,
47
+ 'POST',
48
+ null,
49
+ Object.keys(headers).length ? headers : null,
50
+ );
51
+ } catch (e) {
52
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
53
+ }
54
+ if (complete.status < 200 || complete.status >= 300) {
55
+ return { ok: false, reason: `complete-registration-http-${complete.status}`, status: complete.status, message: messageFrom(complete.raw) };
56
+ }
57
+ return { ok: true };
58
+ }
59
+
60
+ async function requestSignUp(
61
+ { name, lastName, email, password, preferredLanguage, newsletterConsent, freelanceType, freelanceIntent } = {},
62
+ { signUpEndpoint, completeRegistrationEndpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
63
+ ) {
64
+ if (!signUpEndpoint) return { ok: false, reason: 'no-endpoint' };
65
+ if (!name || !lastName || !email || !password) return { ok: false, reason: 'missing-fields' };
66
+
67
+ let signUp;
68
+ try {
69
+ signUp = await postJsonWithTimeout(signUpEndpoint, { email, password, name, lastName }, timeoutMs);
70
+ } catch (e) {
71
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
72
+ }
73
+
74
+ // A duplicate email is a normal outcome the caller handles by switching to login.
75
+ if (signUp.status === 409 || signUp.status === 422) return { ok: true, accountExists: true };
76
+ if (signUp.status < 200 || signUp.status >= 300) {
77
+ return { ok: false, reason: `http-${signUp.status}`, status: signUp.status, message: messageFrom(signUp.raw) };
78
+ }
79
+
80
+ const cookie = extractSetCookie(signUp.headers, SESSION_COOKIE_NAME);
81
+ if (!cookie) return { ok: false, reason: 'bad-response' };
82
+
83
+ if (!completeRegistrationEndpoint) return { ok: false, reason: 'no-endpoint' };
84
+
85
+ const context = buildRegistrationContext({ preferredLanguage, newsletterConsent, freelanceType, freelanceIntent });
86
+ const complete = await requestCompleteRegistration(context, { endpoint: completeRegistrationEndpoint, cookie, timeoutMs });
87
+ if (!complete.ok) return complete;
88
+ return { ok: true, accountExists: false };
89
+ }
90
+
91
+ module.exports = { requestSignUp, requestCompleteRegistration, buildRegistrationContext };
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ const { postJsonWithTimeout } = require('./backend-request');
4
+
5
+ // Talent-skills client (talents-ai-score, ADR-054/055 — `start`'s "Añadir skills" route, src/start-add-skills.js).
6
+
7
+ const DEFAULT_TIMEOUT_MS = 20000;
8
+
9
+ // `works.*` business codes (WorksExceptionFilter's `codeMap`) -> this client's own reason vocabulary.
10
+ const WORKS_CODE_TO_REASON = {
11
+ 'certification.dimension_on_cooldown': 'dimension-on-cooldown',
12
+ 'interview.non_conversational_blocks_in_text': 'needs-web',
13
+ 'works.hub_token_missing': 'no-hub-token',
14
+ 'works.hub_session_expired': 'hub-session-expired',
15
+ 'works.hub_upstream_error': 'hub-upstream-error',
16
+ 'works.hub_unavailable': 'hub-unavailable',
17
+ 'works.ai_consent_required': 'ai-consent-required',
18
+ 'interview.already_started': 'interview-already-started',
19
+ 'interview.onboarding_not_found': 'onboarding-not-found',
20
+ 'assigned-clusters.already_assigned': 'already-assigned',
21
+ 'assigned-clusters.cap_reached': 'role-cap-reached',
22
+ 'assigned-clusters.invalid_cluster': 'invalid-cluster',
23
+ 'assigned-clusters.not_assigned': 'not-assigned',
24
+ };
25
+
26
+ function parseJson(raw) {
27
+ try {
28
+ return raw ? JSON.parse(raw) : null;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ // The certs session Bearer (standard `AuthGuard`, required on ALL THREE talent-skills routes — none of them is `@Public()`) plus, only when present, the Talent's own Hub token.
35
+ function authHeaders({ accessToken, hubAccessToken } = {}) {
36
+ const headers = {};
37
+ if (typeof accessToken === 'string' && accessToken) headers.Authorization = `Bearer ${accessToken}`;
38
+ if (typeof hubAccessToken === 'string' && hubAccessToken) headers['X-Hub-Token'] = hubAccessToken;
39
+ return headers;
40
+ }
41
+
42
+ function reasonForError(status, raw) {
43
+ const body = parseJson(raw);
44
+ const code = body && typeof body.code === 'string' ? body.code : null;
45
+ return (code && WORKS_CODE_TO_REASON[code]) || `http-${status}`;
46
+ }
47
+
48
+ // `POST .../skills/resolve-addable`.
49
+ async function requestResolveAddableSkills({ technologies, accessToken } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
50
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
51
+
52
+ const body = { technologies: Array.isArray(technologies) ? technologies.filter((t) => typeof t === 'string' && t) : [] };
53
+ let res;
54
+ try {
55
+ res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'POST', null, authHeaders({ accessToken }));
56
+ } catch (e) {
57
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
58
+ }
59
+
60
+ if (res.status < 200 || res.status >= 300) {
61
+ return { ok: false, reason: reasonForError(res.status, res.raw) };
62
+ }
63
+
64
+ const json = parseJson(res.raw);
65
+ const data = json && typeof json === 'object' && Array.isArray(json.data) ? json.data : null;
66
+ if (!data) return { ok: false, reason: 'bad-response' };
67
+
68
+ const addable = data
69
+ .map((entry) => ({
70
+ skillId: entry && typeof entry.skillId === 'number' ? entry.skillId : null,
71
+ skillName: entry && typeof entry.skillName === 'string' ? entry.skillName : null,
72
+ technology: entry && typeof entry.technology === 'string' ? entry.technology : null,
73
+ }))
74
+ .filter((s) => s.skillId !== null);
75
+ return { ok: true, addable };
76
+ }
77
+
78
+ // `POST .../skills/declare`.
79
+ async function requestDeclareSkill({ skillId, accessToken, hubAccessToken } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
80
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
81
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
82
+
83
+ let res;
84
+ try {
85
+ res = await postJsonWithTimeout(endpoint, { skillId }, timeoutMs, 'POST', null, authHeaders({ accessToken, hubAccessToken }));
86
+ } catch (e) {
87
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
88
+ }
89
+
90
+ if (res.status < 200 || res.status >= 300) {
91
+ if (res.status === 409) return { ok: false, reason: 'skill-exists' };
92
+ return { ok: false, reason: reasonForError(res.status, res.raw) };
93
+ }
94
+
95
+ const json = parseJson(res.raw);
96
+ if (!json || json.status !== 'OK' || !Array.isArray(json.data)) {
97
+ return { ok: false, reason: 'bad-response' };
98
+ }
99
+ return { ok: true };
100
+ }
101
+
102
+ module.exports = { requestResolveAddableSkills, requestDeclareSkill, reasonForError, WORKS_CODE_TO_REASON };
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ // Social links client. VIEW = GET `works/me/social`; SET = PUT the same endpoint
4
+ // (upsert of the WHOLE object — a fixed set of named URL fields, not per-link rows).
5
+
6
+ const { postJsonWithTimeout } = require('./backend-request');
7
+ const { reasonForError } = require('./onboarding-client');
8
+
9
+ const DEFAULT_TIMEOUT_MS = 20000;
10
+ const NETWORKS = ['linkedin', 'github', 'website', 'twitter', 'instagram', 'facebook', 'dribbble', 'behance'];
11
+
12
+ function parseJson(raw) {
13
+ try {
14
+ return raw ? JSON.parse(raw) : null;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ function bearerHeaders(accessToken) {
21
+ const headers = {};
22
+ if (typeof accessToken === 'string' && accessToken) headers.Authorization = `Bearer ${accessToken}`;
23
+ return headers;
24
+ }
25
+
26
+ function normalizeSocial(data) {
27
+ const it = data && typeof data === 'object' ? data : {};
28
+ const out = {};
29
+ for (const k of NETWORKS) out[k] = typeof it[k] === 'string' && it[k] ? it[k] : null;
30
+ return out;
31
+ }
32
+
33
+ async function requestSocials({ hubAccessToken } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
34
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
35
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
36
+ let res;
37
+ try {
38
+ res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null, bearerHeaders(hubAccessToken));
39
+ } catch (e) {
40
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
41
+ }
42
+ // No social section yet is a normal empty state, not an error.
43
+ if (res.status === 404) return { ok: true, social: normalizeSocial({}) };
44
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
45
+ const env = parseJson(res.raw);
46
+ const data = env && env.status === 'OK' ? env.data : env;
47
+ if (!data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
48
+ return { ok: true, social: normalizeSocial(data) };
49
+ }
50
+
51
+ // Upsert the WHOLE object: `social` carries every network (value or null).
52
+ async function requestSetSocials({ hubAccessToken, social } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
53
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
54
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
55
+ if (!social || typeof social !== 'object') return { ok: false, reason: 'no-social' };
56
+ const body = {};
57
+ for (const k of NETWORKS) body[k] = typeof social[k] === 'string' && social[k] ? social[k] : null;
58
+ let res;
59
+ try {
60
+ res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'PUT', null, bearerHeaders(hubAccessToken));
61
+ } catch (e) {
62
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
63
+ }
64
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
65
+ return { ok: true };
66
+ }
67
+
68
+ async function fetchSocials(deps = {}, { hubAccessToken, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
69
+ const getEndpoint = deps.getMeSocialEndpoint || require('./config').getMeSocialEndpoint;
70
+ const request = deps.requestSocials || requestSocials;
71
+ const endpoint = getEndpoint();
72
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
73
+ return request({ hubAccessToken }, { endpoint, timeoutMs });
74
+ }
75
+
76
+ async function saveSocials(deps = {}, { hubAccessToken, social, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
77
+ const getEndpoint = deps.getMeSocialEndpoint || require('./config').getMeSocialEndpoint;
78
+ const request = deps.requestSetSocials || requestSetSocials;
79
+ const endpoint = getEndpoint();
80
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
81
+ return request({ hubAccessToken, social }, { endpoint, timeoutMs });
82
+ }
83
+
84
+ module.exports = {
85
+ NETWORKS,
86
+ normalizeSocial,
87
+ requestSocials,
88
+ requestSetSocials,
89
+ fetchSocials,
90
+ saveSocials,
91
+ };
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ // `socials` flow: view the talent's social links, and `--set` (interactive,
4
+ // confirmation) to add/update/clear one. The write upserts the WHOLE object, so
5
+ // we merge the chosen field into the current object and PUT it.
6
+
7
+ const { styleLabelPrefix } = require('./ansi');
8
+ const { NETWORKS } = require('./socials-client');
9
+
10
+ function makeSocialsDeps(overrides = {}) {
11
+ const client = require('./socials-client');
12
+ return {
13
+ fetchSocials: (opts) => client.fetchSocials({}, opts),
14
+ saveSocials: (opts) => client.saveSocials({}, opts),
15
+ promptSelect: (opts) => require('./prompt-select').promptSelect(opts),
16
+ ...overrides,
17
+ };
18
+ }
19
+
20
+ function setNetworks(social) {
21
+ return NETWORKS.filter((k) => social && social[k]);
22
+ }
23
+
24
+ function renderView(io, fp, social) {
25
+ io.section(fp.title);
26
+ const present = setNetworks(social);
27
+ if (!present.length) { io.notify(fp.none); io.success(fp.done); return; }
28
+ present.forEach((k) => io.notify(styleLabelPrefix(fp.line(fp.networkLabel(k), social[k]))));
29
+ io.success(fp.done);
30
+ }
31
+
32
+ async function runSet({ io, ask, session, catalog, opts, deps }) {
33
+ const fp = catalog.socials;
34
+ const hubAccessToken = session ? session.hubAccessToken : null;
35
+ if (!opts.stdinIsTTY) { io.error(fp.setNeedsInteractive); return { ok: false, reason: 'non-interactive' }; }
36
+ const selOut = (s) => io.notify(s);
37
+
38
+ const cur = await io.withProgress(fp.loading, () => deps.fetchSocials({ hubAccessToken }));
39
+ if (!cur.ok) { io.error(fp.fetchFailed(cur.reason)); return { ok: false, reason: cur.reason }; }
40
+ const social = { ...cur.social };
41
+
42
+ io.section(fp.setTitle);
43
+ const present = setNetworks(social);
44
+ if (present.length) present.forEach((k) => io.notify(styleLabelPrefix(fp.line(fp.networkLabel(k), social[k]))));
45
+
46
+ const net = await deps.promptSelect({
47
+ ask, stdinIsTTY: opts.stdinIsTTY, out: selOut, header: fp.pickNetwork,
48
+ items: NETWORKS.map((k) => ({ label: fp.networkLabel(k), value: k })), labelFor: (x) => x.label, input: opts.input, output: opts.output,
49
+ });
50
+ if (!net) { io.notify(fp.setCancelled); return { ok: true, cancelled: true }; }
51
+ const key = net.value;
52
+
53
+ const raw = (await io.ask(fp.askUrl(fp.networkLabel(key)))).trim();
54
+ const value = raw === '' ? null : raw;
55
+ social[key] = value;
56
+
57
+ io.notify(value ? fp.setDiffSet(fp.networkLabel(key), value) : fp.setDiffClear(fp.networkLabel(key)));
58
+ const confirmed = await io.confirm(fp.setConfirm);
59
+ if (!confirmed) { io.notify(fp.setCancelled); return { ok: true, cancelled: true }; }
60
+
61
+ const res = await io.withProgress(fp.setSaving, () => deps.saveSocials({ hubAccessToken, social }));
62
+ if (!res.ok) { io.error(fp.setFailed(res.reason)); return { ok: false, reason: res.reason }; }
63
+ io.success(value ? fp.setDone(fp.networkLabel(key)) : fp.setCleared(fp.networkLabel(key)));
64
+ return { ok: true, set: { key, value } };
65
+ }
66
+
67
+ async function runSocials({ io, rawOut, ask, session, catalog, opts = {}, deps = makeSocialsDeps() }) {
68
+ const fp = catalog.socials;
69
+ if (opts.set) return runSet({ io, ask, session, catalog, opts, deps });
70
+
71
+ const hubAccessToken = session ? session.hubAccessToken : null;
72
+ const doFetch = () => deps.fetchSocials({ hubAccessToken });
73
+ const res = opts.json ? await doFetch() : await io.withProgress(fp.loading, doFetch);
74
+ if (!res.ok) { io.error(fp.fetchFailed(res.reason)); return { ok: false, reason: res.reason }; }
75
+ if (opts.json) {
76
+ (rawOut || ((s) => process.stdout.write(s)))(`${JSON.stringify({ ok: true, social: res.social }, null, 2)}\n`);
77
+ return { ok: true, social: res.social };
78
+ }
79
+ renderView(io, fp, res.social);
80
+ return { ok: true, social: res.social };
81
+ }
82
+
83
+ module.exports = { makeSocialsDeps, runSocials, setNetworks, renderView };
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const { eachHumanText } = require('./transcript-turns');
4
+
5
+ const MIN_INSTRUCTION_LEN = 12;
6
+
7
+ const CONCRETE_RE =
8
+ /(`[^`]+`|\b[\w-]+\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|rb|php|css|scss|html|json|ya?ml|md|sql|sh|toml)\b|\/[\w./-]{2,}|"[^"]{2,}"|\d|\b(must|should|shouldn'?t|don'?t|do not|ensure|avoid|instead|because|so that|only|never|always|exactly|specifically|at least|at most|no more than|make sure|asegúrate|debe|deber[íi]as?|no (uses?|hagas|pongas)|en vez de|en lugar de|porque|exactamente|específicamente|solo|nunca|siempre)\b)/i;
9
+
10
+ function makeSpecificityAccumulator() {
11
+ let instructionTurnCount = 0;
12
+ let specificInstructionCount = 0;
13
+ let totalInstructionLength = 0;
14
+ return {
15
+ add(text) {
16
+ const trimmed = typeof text === 'string' ? text.trim() : '';
17
+ if (trimmed.length < MIN_INSTRUCTION_LEN) return;
18
+ instructionTurnCount += 1;
19
+ totalInstructionLength += trimmed.length;
20
+ if (CONCRETE_RE.test(trimmed)) specificInstructionCount += 1;
21
+ },
22
+ result() {
23
+ if (instructionTurnCount === 0) return null;
24
+ return {
25
+ specificInstructionCount,
26
+ instructionTurnCount,
27
+ avgInstructionLength: Math.round(
28
+ totalInstructionLength / instructionTurnCount,
29
+ ),
30
+ };
31
+ },
32
+ };
33
+ }
34
+
35
+ function collectSpecificity(env = process.env, selectedCwds = null) {
36
+ const acc = makeSpecificityAccumulator();
37
+ for (const text of eachHumanText(env, { selectedCwds })) acc.add(text);
38
+ return acc.result();
39
+ }
40
+
41
+ module.exports = { collectSpecificity, makeSpecificityAccumulator };