shakers-cli 0.0.0-stage → 0.14.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 (237) hide show
  1. package/LICENSE +59 -0
  2. package/README.md +1233 -2
  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 +99 -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 +124 -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 +216 -0
  20. package/bin/mcp.js +124 -0
  21. package/bin/mcpb.js +5 -0
  22. package/bin/me.js +69 -0
  23. package/bin/onboarding.js +134 -0
  24. package/bin/portfolios.js +69 -0
  25. package/bin/rate.js +70 -0
  26. package/bin/register.js +337 -0
  27. package/bin/report-html.js +134 -0
  28. package/bin/save.js +77 -0
  29. package/bin/shakers.js +363 -0
  30. package/bin/share.js +72 -0
  31. package/bin/show-project.js +72 -0
  32. package/bin/socials.js +70 -0
  33. package/bin/superadmin.js +351 -0
  34. package/bin/update-check-worker.js +70 -0
  35. package/bin/update.js +111 -0
  36. package/package.json +40 -4
  37. package/src/agent-category.js +13 -0
  38. package/src/agent-evaluation.js +177 -0
  39. package/src/agent-org-chart.js +355 -0
  40. package/src/agent-synthesis.js +132 -0
  41. package/src/agent-usage.js +118 -0
  42. package/src/agents-client.js +165 -0
  43. package/src/ai-fluency-cell.js +72 -0
  44. package/src/ai-profile-client.js +45 -0
  45. package/src/ai-profile-preview.js +154 -0
  46. package/src/ai.js +66 -0
  47. package/src/alma-client.js +123 -0
  48. package/src/alma-flow.js +32 -0
  49. package/src/alma-repo-context.js +47 -0
  50. package/src/alma-shell.js +161 -0
  51. package/src/ansi.js +62 -0
  52. package/src/applications-client.js +79 -0
  53. package/src/applications-flow.js +58 -0
  54. package/src/auth-client.js +144 -0
  55. package/src/auth-device-client.js +118 -0
  56. package/src/auth-method-picker.js +44 -0
  57. package/src/auth-methods.js +35 -0
  58. package/src/auth-session-store.js +215 -0
  59. package/src/authorship-mismatch.js +62 -0
  60. package/src/authorship.js +204 -0
  61. package/src/automations-detector.js +170 -0
  62. package/src/availability-client.js +93 -0
  63. package/src/availability-flow.js +124 -0
  64. package/src/backend-request.js +191 -0
  65. package/src/blind-approval-extract.js +48 -0
  66. package/src/brand-ansi.js +14 -0
  67. package/src/brand-svg.js +9 -0
  68. package/src/browser-tools-detector.js +21 -0
  69. package/src/build-next-level.js +56 -0
  70. package/src/certifications-client.js +89 -0
  71. package/src/certifications-flow.js +75 -0
  72. package/src/certify-dimension-client.js +163 -0
  73. package/src/certify-dimension-flow.js +135 -0
  74. package/src/certify-disclaimer.js +51 -0
  75. package/src/certify-remediation-prompt.js +23 -0
  76. package/src/cli-args.js +83 -0
  77. package/src/command-audit.js +105 -0
  78. package/src/config-dir.js +120 -0
  79. package/src/config.js +877 -0
  80. package/src/consent-flow.js +115 -0
  81. package/src/consent-skip.js +27 -0
  82. package/src/cv-file.js +62 -0
  83. package/src/cv-text.js +92 -0
  84. package/src/decisions-extract.js +43 -0
  85. package/src/detect-repos.js +145 -0
  86. package/src/detected-tools.js +15 -0
  87. package/src/detection-evidence.js +105 -0
  88. package/src/detection-noise.js +132 -0
  89. package/src/detectors.js +221 -0
  90. package/src/device-auth-store.js +100 -0
  91. package/src/device-login.js +120 -0
  92. package/src/email-verification.js +152 -0
  93. package/src/env-paths.js +30 -0
  94. package/src/find-projects-cache.js +89 -0
  95. package/src/find-projects-client.js +148 -0
  96. package/src/find-projects-flow.js +164 -0
  97. package/src/git-activity.js +124 -0
  98. package/src/git-log-prefetch.js +53 -0
  99. package/src/graph-certs.js +58 -0
  100. package/src/graph-generator.js +230 -0
  101. package/src/graph-scan.js +239 -0
  102. package/src/i18n.js +3341 -0
  103. package/src/inter-fonts.js +6 -0
  104. package/src/interactive-select.js +183 -0
  105. package/src/interview-client.js +226 -0
  106. package/src/interview-transcript.js +56 -0
  107. package/src/inventory-client.js +90 -0
  108. package/src/invitations-client.js +66 -0
  109. package/src/invitations-flow.js +50 -0
  110. package/src/lang-client.js +147 -0
  111. package/src/lang-codes.js +14 -0
  112. package/src/lang-flow.js +85 -0
  113. package/src/legal-notice.js +53 -0
  114. package/src/locale.js +72 -0
  115. package/src/markdown-ansi.js +50 -0
  116. package/src/maturity.js +111 -0
  117. package/src/mcp-ai-usage-result-tool.js +111 -0
  118. package/src/mcp-ai-usage-tool.js +401 -0
  119. package/src/mcp-alma-tools.js +95 -0
  120. package/src/mcp-applications-tools.js +44 -0
  121. package/src/mcp-auth-form.js +480 -0
  122. package/src/mcp-availability-tools.js +45 -0
  123. package/src/mcp-certifications-tools.js +44 -0
  124. package/src/mcp-certify-dimension-tools.js +66 -0
  125. package/src/mcp-choice.js +95 -0
  126. package/src/mcp-detector.js +223 -0
  127. package/src/mcp-device-login-tools.js +177 -0
  128. package/src/mcp-find-projects-tools.js +69 -0
  129. package/src/mcp-install.js +275 -0
  130. package/src/mcp-instructions.js +27 -0
  131. package/src/mcp-invitations-tools.js +44 -0
  132. package/src/mcp-lang-tools.js +41 -0
  133. package/src/mcp-onboarding-tools.js +379 -0
  134. package/src/mcp-portfolios-tools.js +50 -0
  135. package/src/mcp-profile-tools.js +48 -0
  136. package/src/mcp-prompts.js +73 -0
  137. package/src/mcp-rate-tools.js +44 -0
  138. package/src/mcp-register-tools.js +441 -0
  139. package/src/mcp-report-tools.js +105 -0
  140. package/src/mcp-role-tools.js +372 -0
  141. package/src/mcp-saved-positions-tools.js +59 -0
  142. package/src/mcp-server.js +290 -0
  143. package/src/mcp-show-project-tools.js +52 -0
  144. package/src/mcp-signup-client.js +118 -0
  145. package/src/mcp-signup-tools.js +1171 -0
  146. package/src/mcp-socials-tools.js +41 -0
  147. package/src/memory-structure-detector.js +108 -0
  148. package/src/model-call-record.js +198 -0
  149. package/src/onboarding-availability.js +26 -0
  150. package/src/onboarding-client.js +374 -0
  151. package/src/onboarding-flow.js +1162 -0
  152. package/src/onboarding-funnel.js +272 -0
  153. package/src/onboarding-livekit-client.js +102 -0
  154. package/src/onboarding-status-client.js +57 -0
  155. package/src/open-file.js +32 -0
  156. package/src/osc-link.js +16 -0
  157. package/src/portfolio-client.js +164 -0
  158. package/src/portfolio-git-info.js +61 -0
  159. package/src/portfolios-client.js +85 -0
  160. package/src/portfolios-flow.js +79 -0
  161. package/src/profile-client.js +133 -0
  162. package/src/profile-flow.js +115 -0
  163. package/src/profile-switch.js +46 -0
  164. package/src/prompt-select.js +66 -0
  165. package/src/rate-client.js +81 -0
  166. package/src/rate-flow.js +137 -0
  167. package/src/register-context.js +145 -0
  168. package/src/render-ai-fluency.js +266 -0
  169. package/src/render-certification.js +177 -0
  170. package/src/render-html.js +140 -0
  171. package/src/render-report-text.js +27 -0
  172. package/src/render-sheet.js +261 -0
  173. package/src/render-terminal.js +109 -0
  174. package/src/render-usage-report-text.js +155 -0
  175. package/src/repo-context.js +215 -0
  176. package/src/report-gating.js +49 -0
  177. package/src/report-store.js +226 -0
  178. package/src/report-theme.js +177 -0
  179. package/src/report-view-client.js +50 -0
  180. package/src/reveal-output.js +29 -0
  181. package/src/roadmap-content.js +453 -0
  182. package/src/roadmap-personalization.js +132 -0
  183. package/src/roadmap-prompt.js +154 -0
  184. package/src/roles-client.js +112 -0
  185. package/src/roles-flow.js +192 -0
  186. package/src/sanitize-network-text.js +36 -0
  187. package/src/saved-positions-client.js +71 -0
  188. package/src/saved-positions-flow.js +50 -0
  189. package/src/scan-exclusions.js +50 -0
  190. package/src/scanner.js +624 -0
  191. package/src/scope-signals.js +148 -0
  192. package/src/secret-prompt.js +72 -0
  193. package/src/session-gate.js +30 -0
  194. package/src/session-normalizers.js +174 -0
  195. package/src/session-refresh.js +51 -0
  196. package/src/session-scan.js +475 -0
  197. package/src/session-signals.js +36 -0
  198. package/src/shakers-wordmark.js +17 -0
  199. package/src/share-card.js +362 -0
  200. package/src/share-certification.js +111 -0
  201. package/src/share-consent-state.js +157 -0
  202. package/src/share-payload.js +300 -0
  203. package/src/share.js +185 -0
  204. package/src/show-project-client.js +58 -0
  205. package/src/show-project-flow.js +119 -0
  206. package/src/signup-client.js +103 -0
  207. package/src/signup-copy.js +450 -0
  208. package/src/signup-language.js +21 -0
  209. package/src/skills-client.js +102 -0
  210. package/src/socials-client.js +91 -0
  211. package/src/socials-flow.js +83 -0
  212. package/src/specificity-extract.js +41 -0
  213. package/src/start-add-agents.js +306 -0
  214. package/src/start-add-portfolio.js +388 -0
  215. package/src/start-add-skills.js +260 -0
  216. package/src/stdin-ask.js +129 -0
  217. package/src/steering-extract.js +43 -0
  218. package/src/strip-internal-comments.js +45 -0
  219. package/src/tech-detector.js +295 -0
  220. package/src/templates/report-sheet.html +572 -0
  221. package/src/terminal-agents.js +207 -0
  222. package/src/terminal-format.js +85 -0
  223. package/src/terminal-progress.js +106 -0
  224. package/src/terminal-sections.js +219 -0
  225. package/src/tier-analysis.js +126 -0
  226. package/src/tier-engine.js +114 -0
  227. package/src/tier-projection.js +117 -0
  228. package/src/transcript-turns.js +49 -0
  229. package/src/translations-client.js +84 -0
  230. package/src/typewriter.js +154 -0
  231. package/src/update-flow.js +223 -0
  232. package/src/update-notifier.js +113 -0
  233. package/src/usage-model-calls.js +119 -0
  234. package/src/usage-oneshot-commands.js +180 -0
  235. package/src/usage-repo-scope.js +162 -0
  236. package/src/usage-run-steps.js +354 -0
  237. package/src/work-streams.js +79 -0
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ const { execFileSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ // Anti-noise CLASSIFICATION for detected candidates (hybrid evaluation, Slice 3 — issue 019).
8
+
9
+ const GIT_TIMEOUT_MS = 15000;
10
+ const README_MAX_BYTES = 8 * 1024;
11
+
12
+ // Directory segments that mark a path as vendored / generated (dependency trees, build output, virtualenvs, package caches).
13
+ const VENDORED_SEGMENTS = new Set([
14
+ // JS / general
15
+ 'node_modules', 'bower_components', '.pnp', 'jspm_packages',
16
+ // generic vendoring
17
+ 'vendor', 'vendored', 'third_party', 'third-party', 'external', 'deps',
18
+ // build / generated output
19
+ 'dist', 'build', 'out', 'generated', '__generated__',
20
+ '.next', '.nuxt', '.output', '.svelte-kit', '.turbo', '.cache',
21
+ // Python
22
+ '.venv', 'venv', 'virtualenv', 'site-packages', '__pycache__',
23
+ // iOS / macOS / other package managers
24
+ 'pods', 'carthage',
25
+ // JVM / Rust build output
26
+ 'target', '.gradle',
27
+ // IaC provider plugins
28
+ '.terraform',
29
+ ]);
30
+
31
+ // True when ANY segment of the (root-relative, POSIX) path is a vendored
32
+ // segment. Case-insensitive so `Pods`/`pods` both match.
33
+ function isVendoredPath(relPath) {
34
+ if (typeof relPath !== 'string' || !relPath) return false;
35
+ return relPath
36
+ .split('/')
37
+ .some((seg) => VENDORED_SEGMENTS.has(seg.toLowerCase()));
38
+ }
39
+
40
+ // A candidate's evidence is VENDORED-dominated when it has at least one evidence path AND every one of them is vendored.
41
+ function classifyVendored(evidencePaths) {
42
+ if (!Array.isArray(evidencePaths) || evidencePaths.length === 0) return false;
43
+ return evidencePaths.every(isVendoredPath);
44
+ }
45
+
46
+ function runGit(root, args) {
47
+ try {
48
+ return execFileSync('git', ['-C', root, ...args], {
49
+ encoding: 'utf8',
50
+ timeout: GIT_TIMEOUT_MS,
51
+ stdio: ['ignore', 'pipe', 'ignore'],
52
+ maxBuffer: 4 * 1024 * 1024,
53
+ });
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ // LOW-CONFIDENCE fork signal: a git remote named `upstream` alongside `origin` is the canonical local convention for a fork's parent.
60
+ function detectFork(root) {
61
+ const out = runGit(root, ['remote']);
62
+ if (out === null) return { fork: false, forkSignal: null };
63
+ const remotes = new Set(
64
+ out
65
+ .split('\n')
66
+ .map((l) => l.trim())
67
+ .filter(Boolean),
68
+ );
69
+ if (remotes.has('upstream')) {
70
+ return { fork: true, forkSignal: 'upstream-remote' };
71
+ }
72
+ return { fork: false, forkSignal: null };
73
+ }
74
+
75
+ // Scaffolder README signatures.
76
+ const BOILERPLATE_MARKERS = [
77
+ { id: 'create-react-app', re: /bootstrapped with \[?create react app/i },
78
+ { id: 'create-next-app', re: /bootstrapped with \[?`?create-next-app/i },
79
+ { id: 'vite-starter', re: /this template provides a minimal setup to get .*working in vite/i },
80
+ { id: 'angular-cli', re: /generated with \[?angular ?cli/i },
81
+ { id: 'nuxt-starter', re: /nuxt[\s\d]*minimal starter/i },
82
+ { id: 'create-svelte', re: /(create-svelte|everything you need to build a svelte project)/i },
83
+ ];
84
+
85
+ const README_NAMES = ['README.md', 'README', 'readme.md', 'Readme.md'];
86
+
87
+ function readReadmeHead(root) {
88
+ for (const name of README_NAMES) {
89
+ const abs = path.join(root, name);
90
+ try {
91
+ const fd = fs.openSync(abs, 'r');
92
+ try {
93
+ const buf = Buffer.alloc(README_MAX_BYTES);
94
+ const bytes = fs.readSync(fd, buf, 0, README_MAX_BYTES, 0);
95
+ return buf.toString('utf8', 0, bytes);
96
+ } finally {
97
+ fs.closeSync(fd);
98
+ }
99
+ } catch {
100
+ // try the next candidate name
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+
106
+ // Repo-level boilerplate flag from an unmodified scaffolder README.
107
+ function detectBoilerplate(root) {
108
+ const head = readReadmeHead(root);
109
+ if (!head) return { boilerplate: false, boilerplateMarker: null };
110
+ for (const { id, re } of BOILERPLATE_MARKERS) {
111
+ if (re.test(head)) return { boilerplate: true, boilerplateMarker: id };
112
+ }
113
+ return { boilerplate: false, boilerplateMarker: null };
114
+ }
115
+
116
+ // Repo-level provenance context for the whole scan.
117
+ function collectProvenance(root) {
118
+ const scanRoot = root || process.cwd();
119
+ return {
120
+ ...detectFork(scanRoot),
121
+ ...detectBoilerplate(scanRoot),
122
+ };
123
+ }
124
+
125
+ module.exports = {
126
+ VENDORED_SEGMENTS,
127
+ isVendoredPath,
128
+ classifyVendored,
129
+ detectFork,
130
+ detectBoilerplate,
131
+ collectProvenance,
132
+ };
@@ -0,0 +1,221 @@
1
+ 'use strict';
2
+
3
+ // Catalog of AI tools for developers.
4
+
5
+ const CATEGORIES = {
6
+ AGENTIC_CLI: 'CLI agéntica',
7
+ AI_EDITOR: 'Editor con IA',
8
+ IDE_ASSISTANT: 'Asistente en IDE',
9
+ COMPLETION: 'Autocompletado',
10
+ AI_TERMINAL: 'Terminal con IA',
11
+ };
12
+
13
+ const detectors = [
14
+ {
15
+ id: 'claude-code',
16
+ name: 'Claude Code',
17
+ vendor: 'Anthropic',
18
+ category: CATEGORIES.AGENTIC_CLI,
19
+ signals: [
20
+ { type: 'projectPath', path: '.claude' },
21
+ { type: 'projectPath', path: 'CLAUDE.md' },
22
+ { type: 'projectPath', path: '.mcp.json' },
23
+ { type: 'homePath', path: '.claude' },
24
+ { type: 'bin', name: 'claude' },
25
+ ],
26
+ },
27
+ {
28
+ id: 'cursor',
29
+ name: 'Cursor',
30
+ vendor: 'Anysphere',
31
+ category: CATEGORIES.AI_EDITOR,
32
+ signals: [
33
+ { type: 'projectPath', path: '.cursor' },
34
+ { type: 'projectPath', path: '.cursorrules' },
35
+ { type: 'homePath', path: '.cursor' },
36
+ { type: 'bin', name: 'cursor' },
37
+ ],
38
+ },
39
+ {
40
+ id: 'github-copilot',
41
+ name: 'GitHub Copilot',
42
+ vendor: 'GitHub',
43
+ category: CATEGORIES.IDE_ASSISTANT,
44
+ signals: [
45
+ { type: 'projectPath', path: '.github/copilot-instructions.md' },
46
+ { type: 'vscodeExt', prefix: 'github.copilot' },
47
+ { type: 'bin', name: 'copilot' },
48
+ ],
49
+ },
50
+ {
51
+ id: 'windsurf',
52
+ name: 'Windsurf',
53
+ vendor: 'Codeium',
54
+ category: CATEGORIES.AI_EDITOR,
55
+ signals: [
56
+ { type: 'projectPath', path: '.windsurf' },
57
+ { type: 'projectPath', path: '.windsurfrules' },
58
+ { type: 'homePath', path: '.codeium/windsurf' },
59
+ { type: 'bin', name: 'windsurf' },
60
+ ],
61
+ },
62
+ {
63
+ id: 'aider',
64
+ name: 'Aider',
65
+ vendor: 'Aider AI',
66
+ category: CATEGORIES.AGENTIC_CLI,
67
+ signals: [
68
+ { type: 'projectPath', path: '.aider.conf.yml' },
69
+ { type: 'projectPath', path: '.aider.chat.history.md' },
70
+ { type: 'homePath', path: '.aider.conf.yml' },
71
+ { type: 'bin', name: 'aider' },
72
+ ],
73
+ },
74
+ {
75
+ id: 'continue',
76
+ name: 'Continue',
77
+ vendor: 'Continue',
78
+ category: CATEGORIES.IDE_ASSISTANT,
79
+ signals: [
80
+ { type: 'projectPath', path: '.continue' },
81
+ { type: 'homePath', path: '.continue' },
82
+ { type: 'vscodeExt', prefix: 'continue.continue' },
83
+ ],
84
+ },
85
+ {
86
+ id: 'cline',
87
+ name: 'Cline',
88
+ vendor: 'Cline',
89
+ category: CATEGORIES.IDE_ASSISTANT,
90
+ signals: [
91
+ { type: 'projectPath', path: '.clinerules' },
92
+ { type: 'vscodeExt', prefix: 'saoudrizwan.claude-dev' },
93
+ ],
94
+ },
95
+ {
96
+ id: 'gemini-cli',
97
+ name: 'Gemini CLI',
98
+ vendor: 'Google',
99
+ category: CATEGORIES.AGENTIC_CLI,
100
+ signals: [
101
+ { type: 'projectPath', path: '.gemini' },
102
+ { type: 'projectPath', path: 'GEMINI.md' },
103
+ { type: 'homePath', path: '.gemini' },
104
+ { type: 'bin', name: 'gemini' },
105
+ ],
106
+ },
107
+ {
108
+ id: 'codex-cli',
109
+ name: 'Codex CLI',
110
+ vendor: 'OpenAI',
111
+ category: CATEGORIES.AGENTIC_CLI,
112
+ signals: [
113
+ { type: 'projectPath', path: 'AGENTS.md' },
114
+ { type: 'homePath', path: '.codex' },
115
+ { type: 'bin', name: 'codex' },
116
+ ],
117
+ },
118
+ {
119
+ id: 'cody',
120
+ name: 'Cody',
121
+ vendor: 'Sourcegraph',
122
+ category: CATEGORIES.IDE_ASSISTANT,
123
+ signals: [
124
+ { type: 'projectPath', path: '.sourcegraph' },
125
+ { type: 'vscodeExt', prefix: 'sourcegraph.cody-ai' },
126
+ { type: 'bin', name: 'cody' },
127
+ ],
128
+ },
129
+ {
130
+ id: 'zed',
131
+ name: 'Zed',
132
+ vendor: 'Zed Industries',
133
+ category: CATEGORIES.AI_EDITOR,
134
+ signals: [
135
+ { type: 'homePath', path: '.config/zed' },
136
+ { type: 'bin', name: 'zed' },
137
+ ],
138
+ },
139
+ {
140
+ id: 'tabnine',
141
+ name: 'Tabnine',
142
+ vendor: 'Tabnine',
143
+ category: CATEGORIES.COMPLETION,
144
+ signals: [
145
+ { type: 'homePath', path: '.config/TabNine' },
146
+ { type: 'vscodeExt', prefix: 'tabnine.tabnine-vscode' },
147
+ ],
148
+ },
149
+
150
+ // ---- Expansion (talents-ai-score, more signals) — confidence per entry below ----
151
+ {
152
+ id: 'amazon-q-developer',
153
+ name: 'Amazon Q Developer',
154
+ vendor: 'AWS',
155
+ category: CATEGORIES.AGENTIC_CLI,
156
+ signals: [
157
+ // High confidence: binary and extension id are stable for the official CLI/extension.
158
+ { type: 'bin', name: 'q' },
159
+ { type: 'vscodeExt', prefix: 'amazonwebservices.amazon-q-vscode' },
160
+ // Medium confidence: CLI credentials/cache directory under .aws.
161
+ { type: 'homePath', path: '.aws/amazonq' },
162
+ ],
163
+ },
164
+ {
165
+ id: 'codeium',
166
+ name: 'Codeium',
167
+ vendor: 'Codeium (Exafunction)',
168
+ category: CATEGORIES.IDE_ASSISTANT,
169
+ signals: [
170
+ // Codeium's base extension (autocomplete in any editor), distinct from Windsurf (its own editor, already cataloged above).
171
+ { type: 'vscodeExt', prefix: 'codeium.codeium' },
172
+ { type: 'homePath', path: '.codeium/config.json' },
173
+ ],
174
+ },
175
+ {
176
+ id: 'supermaven',
177
+ name: 'Supermaven',
178
+ vendor: 'Supermaven',
179
+ category: CATEGORIES.COMPLETION,
180
+ signals: [
181
+ // High confidence in the extension id; medium in the local state directory.
182
+ { type: 'vscodeExt', prefix: 'supermaven.supermaven' },
183
+ { type: 'homePath', path: '.supermaven' },
184
+ ],
185
+ },
186
+ {
187
+ id: 'pieces',
188
+ name: 'Pieces for Developers',
189
+ vendor: 'Mesh Intelligent Technologies',
190
+ category: CATEGORIES.IDE_ASSISTANT,
191
+ signals: [
192
+ // Medium confidence: extension id recalled from memory, could not be verified against the marketplace in this environment.
193
+ { type: 'vscodeExt', prefix: 'meshintelligenttechnologiesinc.pieces-vscode' },
194
+ ],
195
+ },
196
+ {
197
+ id: 'warp-ai',
198
+ name: 'Warp (AI terminal)',
199
+ vendor: 'Warp',
200
+ category: CATEGORIES.AI_TERMINAL,
201
+ signals: [
202
+ // Medium confidence: config directory of the Warp terminal app.
203
+ { type: 'homePath', path: '.warp' },
204
+ // On Linux, Warp's config lives under ~/.config/warp-terminal.
205
+ { type: 'homePath', path: '.config/warp-terminal' },
206
+ ],
207
+ },
208
+ {
209
+ id: 'trae',
210
+ name: 'Trae',
211
+ vendor: 'ByteDance',
212
+ category: CATEGORIES.AI_EDITOR,
213
+ signals: [
214
+ { type: 'projectPath', path: '.trae' },
215
+ { type: 'homePath', path: '.trae' },
216
+ { type: 'bin', name: 'trae' },
217
+ ],
218
+ },
219
+ ];
220
+
221
+ module.exports = { detectors, CATEGORIES };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { getConfigDir } = require('./config-dir');
7
+
8
+ // `~/.config/shakers/device-auth.json` — bridges the MCP start/poll tools; the secret device_code lives here (0600), never in a tool result.
9
+
10
+ const DEVICE_FILE_NAME = 'device-auth.json';
11
+ const DEVICE_FILE_VERSION = 1;
12
+
13
+ function devicePath(env = process.env) {
14
+ return path.join(getConfigDir(env), DEVICE_FILE_NAME);
15
+ }
16
+
17
+ // Atomic 0600 write (temp + fsync + rename), same recipe as auth-session-store.
18
+ function savePendingDevice(pending, env = process.env, { fs: fsImpl = fs } = {}) {
19
+ const payload = `${JSON.stringify(
20
+ {
21
+ deviceCode: typeof pending.deviceCode === 'string' && pending.deviceCode ? pending.deviceCode : null,
22
+ verificationUrl: typeof pending.verificationUrl === 'string' && pending.verificationUrl ? pending.verificationUrl : null,
23
+ userCode: typeof pending.userCode === 'string' && pending.userCode ? pending.userCode : null,
24
+ tokenEndpoint: typeof pending.tokenEndpoint === 'string' && pending.tokenEndpoint ? pending.tokenEndpoint : null,
25
+ authTokenEndpoint: typeof pending.authTokenEndpoint === 'string' && pending.authTokenEndpoint ? pending.authTokenEndpoint : null,
26
+ interval: typeof pending.interval === 'number' && pending.interval > 0 ? Math.floor(pending.interval) : 5,
27
+ expiresAt: typeof pending.expiresAt === 'string' && pending.expiresAt ? pending.expiresAt : null,
28
+ mode: pending.mode === 'register' ? 'register' : 'login',
29
+ provider: pending.provider === 'linkedin' ? 'linkedin' : 'google',
30
+ registerContext: pending.registerContext && typeof pending.registerContext === 'object' ? pending.registerContext : null,
31
+ email: typeof pending.email === 'string' && pending.email ? pending.email : null,
32
+ version: DEVICE_FILE_VERSION,
33
+ },
34
+ null,
35
+ 2,
36
+ )}\n`;
37
+
38
+ const target = devicePath(env);
39
+ const dir = path.dirname(target);
40
+ fsImpl.mkdirSync(dir, { recursive: true, mode: 0o700 });
41
+
42
+ const tmp = path.join(dir, `.${DEVICE_FILE_NAME}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`);
43
+ let fd = null;
44
+ try {
45
+ fd = fsImpl.openSync(tmp, 'wx', 0o600);
46
+ fsImpl.writeFileSync(fd, payload);
47
+ try { fsImpl.fsyncSync(fd); } catch { /* e.g. EINVAL on some filesystems */ }
48
+ fsImpl.closeSync(fd);
49
+ fd = null;
50
+ fsImpl.renameSync(tmp, target);
51
+ } catch (e) {
52
+ if (fd !== null) { try { fsImpl.closeSync(fd); } catch { /* already closed */ } }
53
+ try { fsImpl.unlinkSync(tmp); } catch { /* never existed */ }
54
+ throw e;
55
+ }
56
+ return target;
57
+ }
58
+
59
+ function loadPendingDevice(env = process.env, { fs: fsImpl = fs } = {}) {
60
+ let parsed;
61
+ try {
62
+ parsed = JSON.parse(fsImpl.readFileSync(devicePath(env), 'utf8'));
63
+ } catch {
64
+ return null;
65
+ }
66
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
67
+ if (parsed.version !== DEVICE_FILE_VERSION) return null;
68
+ if (typeof parsed.deviceCode !== 'string' || !parsed.deviceCode) return null;
69
+ return {
70
+ deviceCode: parsed.deviceCode,
71
+ verificationUrl: typeof parsed.verificationUrl === 'string' && parsed.verificationUrl ? parsed.verificationUrl : null,
72
+ userCode: typeof parsed.userCode === 'string' && parsed.userCode ? parsed.userCode : null,
73
+ tokenEndpoint: typeof parsed.tokenEndpoint === 'string' && parsed.tokenEndpoint ? parsed.tokenEndpoint : null,
74
+ authTokenEndpoint: typeof parsed.authTokenEndpoint === 'string' && parsed.authTokenEndpoint ? parsed.authTokenEndpoint : null,
75
+ interval: typeof parsed.interval === 'number' && parsed.interval > 0 ? Math.floor(parsed.interval) : 5,
76
+ expiresAt: typeof parsed.expiresAt === 'string' && parsed.expiresAt ? parsed.expiresAt : null,
77
+ mode: parsed.mode === 'register' ? 'register' : 'login',
78
+ provider: parsed.provider === 'linkedin' ? 'linkedin' : 'google',
79
+ registerContext: parsed.registerContext && typeof parsed.registerContext === 'object' ? parsed.registerContext : null,
80
+ email: typeof parsed.email === 'string' && parsed.email ? parsed.email : null,
81
+ };
82
+ }
83
+
84
+ function clearPendingDevice(env = process.env, { fs: fsImpl = fs } = {}) {
85
+ try {
86
+ fsImpl.unlinkSync(devicePath(env));
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ module.exports = {
94
+ DEVICE_FILE_NAME,
95
+ DEVICE_FILE_VERSION,
96
+ devicePath,
97
+ savePendingDevice,
98
+ loadPendingDevice,
99
+ clearPendingDevice,
100
+ };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ const { requestDeviceAuthorization, requestDeviceToken, requestApiToken } = require('./auth-device-client');
4
+
5
+ const SLOW_DOWN_STEP_SECONDS = 5;
6
+ const DEFAULT_MAX_WAIT_MS = 15 * 60 * 1000;
7
+
8
+ function defaultSleep(ms) {
9
+ // NOT unref'd: the loop's await sleep(...) is the only handle keeping the process alive while it waits.
10
+ return new Promise((resolve) => { setTimeout(resolve, ms); });
11
+ }
12
+
13
+ // The /cli-login page reads `provider` from the query to pick the social button.
14
+ function appendProviderParam(url, provider) {
15
+ if (!url || !provider) return url;
16
+ try {
17
+ const u = new URL(url);
18
+ u.searchParams.set('provider', provider);
19
+ return u.href;
20
+ } catch {
21
+ return `${url}${url.includes('?') ? '&' : '?'}provider=${encodeURIComponent(provider)}`;
22
+ }
23
+ }
24
+
25
+ async function startDeviceLogin({ authorizeEndpoint, clientId, scope, timeoutMs } = {}) {
26
+ return requestDeviceAuthorization({ endpoint: authorizeEndpoint, clientId, scope, timeoutMs });
27
+ }
28
+
29
+ async function pollDeviceToken({ deviceCode, tokenEndpoint, clientId, timeoutMs } = {}) {
30
+ return requestDeviceToken({ deviceCode, clientId }, { endpoint: tokenEndpoint, timeoutMs });
31
+ }
32
+
33
+ // ORDER: a NEW user needs complete-registration BEFORE GET /auth/token (else the Hub 500s: no domain user).
34
+ async function finalizeDeviceSession(
35
+ { sessionToken, isNewUser, authTokenEndpoint, completeRegistrationEndpoint = null, registrationContext = null, claimCode = null, timeoutMs } = {},
36
+ {
37
+ requestCompleteRegistration = require('./signup-client').requestCompleteRegistration,
38
+ requestApiToken: apiToken = requestApiToken,
39
+ } = {},
40
+ ) {
41
+ if (!authTokenEndpoint) return { ok: false, reason: 'no-endpoint' };
42
+
43
+ let claimed = false;
44
+ if (isNewUser && completeRegistrationEndpoint) {
45
+ const done = await requestCompleteRegistration(registrationContext || {}, { endpoint: completeRegistrationEndpoint, bearerToken: sessionToken, claimCode, timeoutMs });
46
+ if (!done.ok) return { ok: false, reason: done.reason || 'complete-registration-failed' };
47
+ claimed = done.claimed === true;
48
+ }
49
+
50
+ const jwt = await apiToken({ accessToken: sessionToken }, { endpoint: authTokenEndpoint, timeoutMs });
51
+ if (!jwt.ok) return { ok: false, reason: 'token-exchange-failed' };
52
+ return claimCode ? { ok: true, token: jwt.token, claimed } : { ok: true, token: jwt.token };
53
+ }
54
+
55
+ async function runDeviceLogin(
56
+ { authorizeEndpoint, tokenEndpoint, authTokenEndpoint, completeRegistrationEndpoint = null, registrationContext = null, clientId, scope, timeoutMs } = {},
57
+ {
58
+ onPrompt = null,
59
+ onWaiting = null,
60
+ sleep = defaultSleep,
61
+ now = () => Date.now(),
62
+ maxWaitMs = DEFAULT_MAX_WAIT_MS,
63
+ } = {},
64
+ ) {
65
+ if (!authorizeEndpoint || !tokenEndpoint || !authTokenEndpoint) return { ok: false, reason: 'no-endpoint' };
66
+
67
+ const authz = await startDeviceLogin({ authorizeEndpoint, clientId, scope, timeoutMs });
68
+ if (!authz.ok) return { ok: false, reason: authz.reason || 'authorize-failed' };
69
+
70
+ if (typeof onPrompt === 'function') {
71
+ onPrompt({
72
+ verificationUriComplete: authz.verificationUriComplete,
73
+ verificationUri: authz.verificationUri,
74
+ userCode: authz.userCode,
75
+ expiresIn: authz.expiresIn,
76
+ });
77
+ }
78
+
79
+ let intervalSeconds = authz.interval;
80
+ const deadline = now() + Math.min(authz.expiresIn * 1000, maxWaitMs);
81
+
82
+ for (;;) {
83
+ if (now() >= deadline) return { ok: false, reason: 'expired' };
84
+ if (typeof onWaiting === 'function') onWaiting();
85
+ await sleep(intervalSeconds * 1000);
86
+
87
+ const res = await pollDeviceToken({ deviceCode: authz.deviceCode, tokenEndpoint, clientId, timeoutMs });
88
+ if (res.status === 'authorized') {
89
+ const fin = await finalizeDeviceSession({
90
+ sessionToken: res.accessToken,
91
+ isNewUser: res.isNewUser,
92
+ authTokenEndpoint,
93
+ completeRegistrationEndpoint,
94
+ registrationContext,
95
+ timeoutMs,
96
+ });
97
+ if (!fin.ok) return { ok: false, reason: fin.reason };
98
+ return { ok: true, token: fin.token, sessionToken: res.accessToken, isNewUser: res.isNewUser, email: res.email || null };
99
+ }
100
+ if (res.status === 'pending') continue;
101
+ if (res.status === 'slow_down') {
102
+ intervalSeconds += SLOW_DOWN_STEP_SECONDS;
103
+ continue;
104
+ }
105
+ if (res.status === 'denied') return { ok: false, reason: 'denied' };
106
+ if (res.status === 'expired') return { ok: false, reason: 'expired' };
107
+ return { ok: false, reason: res.reason || 'poll-failed' };
108
+ }
109
+ }
110
+
111
+ module.exports = {
112
+ startDeviceLogin,
113
+ pollDeviceToken,
114
+ finalizeDeviceSession,
115
+ runDeviceLogin,
116
+ appendProviderParam,
117
+ defaultSleep,
118
+ SLOW_DOWN_STEP_SECONDS,
119
+ DEFAULT_MAX_WAIT_MS,
120
+ };