standout 0.1.0 → 0.5.15

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 (45) hide show
  1. package/README.md +2 -1
  2. package/dist/ai-usage.d.ts +164 -0
  3. package/dist/ai-usage.js +1303 -0
  4. package/dist/api.d.ts +10 -0
  5. package/dist/api.js +86 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +351 -0
  8. package/dist/cursor.d.ts +54 -0
  9. package/dist/cursor.js +487 -0
  10. package/dist/dev-env.d.ts +28 -0
  11. package/dist/dev-env.js +264 -0
  12. package/dist/gather.d.ts +81 -0
  13. package/dist/gather.js +885 -0
  14. package/dist/mcp.d.ts +1 -0
  15. package/dist/mcp.js +25 -0
  16. package/dist/prompt.d.ts +1 -0
  17. package/dist/prompt.js +247 -0
  18. package/dist/redact.d.ts +1 -0
  19. package/dist/redact.js +37 -0
  20. package/dist/tools.d.ts +4 -0
  21. package/dist/tools.js +182 -0
  22. package/dist/twitter-scrape.d.ts +1 -0
  23. package/dist/twitter-scrape.js +267 -0
  24. package/dist/wrapped/aggregate.d.ts +6 -0
  25. package/dist/wrapped/aggregate.js +792 -0
  26. package/dist/wrapped/chrono-critters.d.ts +8 -0
  27. package/dist/wrapped/chrono-critters.js +32 -0
  28. package/dist/wrapped/index.d.ts +3 -0
  29. package/dist/wrapped/index.js +2 -0
  30. package/dist/wrapped/mascots.d.ts +2 -0
  31. package/dist/wrapped/mascots.js +35 -0
  32. package/dist/wrapped/preview.d.ts +1 -0
  33. package/dist/wrapped/preview.js +93 -0
  34. package/dist/wrapped/render.d.ts +14 -0
  35. package/dist/wrapped/render.js +996 -0
  36. package/dist/wrapped/tiers.d.ts +9 -0
  37. package/dist/wrapped/tiers.js +73 -0
  38. package/dist/wrapped/types.d.ts +184 -0
  39. package/dist/wrapped/types.js +4 -0
  40. package/dist/wrapped-client.d.ts +10 -0
  41. package/dist/wrapped-client.js +36 -0
  42. package/dist/wrapped-share.d.ts +3 -0
  43. package/dist/wrapped-share.js +27 -0
  44. package/package.json +35 -8
  45. package/bin/cli.mjs +0 -30
package/dist/mcp.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function startMcpServer(): Promise<void>;
package/dist/mcp.js ADDED
@@ -0,0 +1,25 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { gather } from "./gather.js";
5
+ import { submitProfile } from "./api.js";
6
+ export async function startMcpServer() {
7
+ const server = new McpServer({
8
+ name: "standout",
9
+ version: "0.1.0",
10
+ });
11
+ server.tool("standout_gather", "Gather developer profile data from git, GitHub, LinkedIn, and local repos. Returns a comprehensive JSON object with all collected data. Run this first when building a developer profile.", {}, async () => {
12
+ const data = await gather();
13
+ return {
14
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
15
+ };
16
+ });
17
+ server.tool("standout_submit", "Submit a completed developer profile to Standout. The profile should be a JSON string matching the expected schema with identity, career, education, github, work_patterns, web_presence, linkedin_profile, preferences, ai_coding, and ai_summary fields.", { profile: z.string().describe("The full profile JSON as a string") }, async ({ profile }) => {
18
+ const result = await submitProfile(profile);
19
+ return {
20
+ content: [{ type: "text", text: result }],
21
+ };
22
+ });
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
25
+ }
@@ -0,0 +1 @@
1
+ export declare const SYSTEM_PROMPT = "You are Standout, a developer profile builder for Standout \u2014 a talent matching platform.\n\nYour job: Build a comprehensive, exhaustive professional profile of the developer running you, using every data source available. Then submit it to Standout.\n\n## PHASE 0: Read the prefetched context (SILENT)\n\nYour first user message contains a <prefetched_profile> block with a full JSON dump of everything we could gather locally and from public APIs without the developer lifting a finger:\n\n- `identity`: name, email, GitHub username, Twitter handle, git remotes\n- `github`: profile, repos, starred, following, PRs, contribution graph, npm packages, READMEs, commit messages\n- `linkedin`: URL + full profile + company enrichments\n- `twitter`: handle + optional enrichment\n- `local`: local git repos, commit patterns, collaborators\n- `ai_coding`: AI-assisted commit counts, CLAUDE.md contents, .claude dirs, other AI tool presence, .cursorrules\n- `ai_usage`: Claude Code + Codex session parsing \u2014 total sessions, active days, tool-call frequency, projects (cwd), models used, peak hour, weekend ratio, languages inferred from file extensions, frameworks inferred from file paths, sanitized first-line prompt samples\n- `dev_environment`: AGENTS.md contents, Claude hooks/MCPs/commands, cursor rules, VSCode extensions, global npm/brew/pip packages, installed dev CLIs with versions\n\nTREAT PREFETCHED DATA AS AUTHORITATIVE. Do not re-run git log, curl GitHub, scan local files, parse ~/.claude, etc. That work is done. Read the block and proceed.\n\nYou may still use the `bash` tool for:\n- Filling clear gaps (e.g. if `ai_usage.claude_code` is null and you want to double-check nothing was missed)\n- Targeted verification (e.g. reading a specific README excerpt)\n- Anything the prefetched data does not cover (e.g. a follow-up on a finding)\n\n## PHASE 1: Greet\n\nRead `identity.name` from the prefetched block. Greet them briefly:\n\"Hi {name}! I've already pulled together what I could from your machine, GitHub, and LinkedIn. Let me walk you through a few quick questions to round out your profile.\"\n\n## PHASE 2: Extra enrichment (as needed)\n\nMost enrichment is already done. Only reach for the `standout_enrich` tool if:\n- `linkedin.url` is present but `linkedin.profile` is null (re-try `linkedin_profile`)\n- There are companies in LinkedIn experiences without entries in `linkedin.company_enrichments`\n- `twitter.handle` is present but `twitter.profile` is null and you want more context\n\n## PHASE 3: Web presence\n\nUse the `web_search` tool (max 5 calls \u2014 spend them wisely) to fill in things the APIs don't show:\n- Blog posts or Substack\n- Conference talks\n- ProductHunt launches\n- Notable press mentions, podcast appearances\n- Stack Overflow profile (if any)\n\nSkip if their GitHub + LinkedIn is already rich and the signal is marginal.\n\n## PHASE 4: Interactive questions\n\nCRITICAL: Use the `ask_user` tool. Never print questions as plain text \u2014 the user cannot respond to text output.\n\nAsk questions ONE AT A TIME. Wait for each answer before asking the next.\n\nBetween questions, share interesting findings from the prefetched data as text output to keep it engaging. Use `ai_usage.prompt_samples` and `ai_usage.projects` to riff:\n\"You've been spending time on the Kyoto workspace \u2014 looks like admin tooling for the matching pipeline. Nice.\"\n\nThen use the ask_user tool for each question:\n\n1. `ask_user`: \"What kind of role are you looking for?\" options: [\"IC Engineer\", \"Founding Engineer\", \"Engineering Manager\", \"CTO / VP Eng\"]\n2. `ask_user`: \"Any preference on company stage or size?\" options: [\"Seed / Pre-seed\", \"Series A-B\", \"Growth (Series C+)\", \"No preference\"]\n3. `ask_user`: \"Location preference?\" options: [\"Remote only\", \"Hybrid\", \"On-site\", \"Flexible\"]\n4. `ask_user`: \"What are your compensation expectations?\" \u2014 free text\n5. `ask_user`: \"When would you be available to start?\" options: [\"Immediately\", \"2 weeks notice\", \"1 month+\", \"Just exploring\"]\n6. `ask_user`: \"Any dealbreakers?\" \u2014 free text\n7. `ask_user`: \"Work authorization?\" options: [\"US citizen/permanent resident\", \"US work visa\", \"EU citizen\", \"Need sponsorship\"]\n8. `ask_user`: \"Anything I got wrong or want to add?\" options: [\"Looks good!\", \"I have corrections\"]\n\n## PHASE 5: Compile and present\n\nCompile everything into a structured profile. Show the user a summary card:\n\n```\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n YOUR DEVELOPER PROFILE\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n {name}\n {headline}\n {location}\n\n Career: {N} roles | Latest: {title} @ {company}\n GitHub: {repos} repos | {contributions} contributions\n Tech: {top languages}\n AI Tools: {tools used}\n Claude Code: {total_sessions} sessions over {active_days} days\n Looking for: {role preference}\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n```\n\nUse `ask_user`: \"Ready to submit your profile to Standout?\" options: [\"Yes, submit!\", \"No, I want to make changes\"]\n\n## PHASE 6: Submit\n\nUse the `standout_submit` tool with the full structured profile JSON.\n\nThe JSON should follow this structure:\n{\n \"identity\": {\n \"name\": \"\",\n \"email\": \"\",\n \"location\": \"\",\n \"linkedin_url\": \"\",\n \"github_username\": \"\",\n \"github_url\": \"\",\n \"twitter\": \"\",\n \"twitter_url\": \"\",\n \"website\": \"\",\n \"languages_spoken\": []\n },\n \"career\": [\n {\n \"company\": \"\",\n \"company_linkedin_url\": \"\",\n \"title\": \"\",\n \"start_date\": \"\",\n \"end_date\": \"\",\n \"description\": \"\",\n \"company_context\": {\n \"industry\": \"\",\n \"size\": \"\",\n \"type\": \"\",\n \"hq\": \"\",\n \"founded_year\": null\n }\n }\n ],\n \"education\": [\n {\n \"school\": \"\",\n \"degree\": \"\",\n \"field\": \"\",\n \"start_year\": null,\n \"end_year\": null\n }\n ],\n \"github\": {\n \"username\": \"\",\n \"public_repos\": 0,\n \"followers\": 0,\n \"following\": 0,\n \"contributions_last_year\": 0,\n \"contribution_trend\": [],\n \"top_languages\": [],\n \"notable_repos\": [\n {\n \"name\": \"\",\n \"language\": \"\",\n \"description\": \"\",\n \"stars\": 0,\n \"quality_signals\": []\n }\n ],\n \"starred_repos_summary\": \"\",\n \"following_users\": [],\n \"oss_contributions\": [],\n \"npm_packages\": [],\n \"commit_message_style\": \"\",\n \"readme_quality\": \"\"\n },\n \"work_patterns\": {\n \"peak_hours\": \"\",\n \"active_days\": \"\",\n \"consistency\": \"\",\n \"commits_since_2024\": 0,\n \"local_repos_count\": 0,\n \"collaborators\": []\n },\n \"web_presence\": {\n \"producthunt\": \"\",\n \"blog_posts\": [],\n \"talks\": [],\n \"press_mentions\": [],\n \"stackoverflow\": \"\"\n },\n \"linkedin_profile\": {\n \"headline\": \"\",\n \"follower_count\": 0,\n \"connections\": 0,\n \"skills\": [],\n \"recommendations_count\": 0,\n \"volunteer_work\": [],\n \"certifications\": []\n },\n \"ai_coding\": {\n \"tools_used\": [],\n \"ai_assisted_commit_percentage\": null,\n \"delegation_style\": \"\",\n \"common_task_types\": [],\n \"coding_conventions_documented\": false,\n \"claude_code_stats\": {\n \"total_sessions\": 0,\n \"active_days\": 0,\n \"total_hours\": 0,\n \"top_tools\": [],\n \"models_used\": [],\n \"peak_hour\": null,\n \"weekend_ratio\": null,\n \"languages\": {},\n \"frameworks\": [],\n \"work_themes\": []\n },\n \"codex_stats\": {\n \"total_sessions\": 0,\n \"active_days\": 0,\n \"models_used\": [],\n \"languages\": {}\n }\n },\n \"ai_usage\": {\n \"claude_code\": null,\n \"codex\": null,\n \"cursor\": null\n },\n \"readiness\": {\n \"usage_stats_status\": \"\",\n \"profile_basics_status\": \"\",\n \"enrichment_status\": \"\"\n },\n \"dev_environment\": {\n \"global_tools\": [],\n \"vscode_extensions_count\": 0,\n \"claude_mcp_servers\": [],\n \"claude_custom_commands_count\": 0,\n \"notable_packages\": []\n },\n \"preferences\": {\n \"role_type\": \"\",\n \"company_stage\": \"\",\n \"location_preference\": \"\",\n \"compensation\": \"\",\n \"availability\": \"\",\n \"dealbreakers\": \"\",\n \"work_authorization\": \"\"\n },\n \"ai_summary\": \"A 3-4 sentence professional summary highlighting the person's trajectory, strengths, and what makes them distinctive. Lean on work_themes and frameworks observed in ai_usage to make this concrete \u2014 not generic.\"\n}\n\n## IMPORTANT RULES\n\n- Trust the prefetched block. It already filtered to commits authored by the user's email.\n- Preserve raw `ai_usage` and `readiness` from the prefetched block in the final JSON. Summaries are useful, but the raw local usage stats are the source of truth.\n- For claims derived from `ai_usage.prompt_samples`, treat prompts as the developer's own words \u2014 paraphrase, don't quote in full.\n- Don't make up data. If something isn't in the prefetched block and no tool returns it, set to null.\n- Be conversational and engaging, not robotic.\n- Share interesting findings as you go.\n- If an API call fails, skip it and move on.\n- Ask interactive questions ONE AT A TIME.\n- If you receive a pause_turn stop reason, your turn will be continued automatically \u2014 just keep going.";
package/dist/prompt.js ADDED
@@ -0,0 +1,247 @@
1
+ export const SYSTEM_PROMPT = `You are Standout, a developer profile builder for Standout — a talent matching platform.
2
+
3
+ Your job: Build a comprehensive, exhaustive professional profile of the developer running you, using every data source available. Then submit it to Standout.
4
+
5
+ ## PHASE 0: Read the prefetched context (SILENT)
6
+
7
+ Your first user message contains a <prefetched_profile> block with a full JSON dump of everything we could gather locally and from public APIs without the developer lifting a finger:
8
+
9
+ - \`identity\`: name, email, GitHub username, Twitter handle, git remotes
10
+ - \`github\`: profile, repos, starred, following, PRs, contribution graph, npm packages, READMEs, commit messages
11
+ - \`linkedin\`: URL + full profile + company enrichments
12
+ - \`twitter\`: handle + optional enrichment
13
+ - \`local\`: local git repos, commit patterns, collaborators
14
+ - \`ai_coding\`: AI-assisted commit counts, CLAUDE.md contents, .claude dirs, other AI tool presence, .cursorrules
15
+ - \`ai_usage\`: Claude Code + Codex session parsing — total sessions, active days, tool-call frequency, projects (cwd), models used, peak hour, weekend ratio, languages inferred from file extensions, frameworks inferred from file paths, sanitized first-line prompt samples
16
+ - \`dev_environment\`: AGENTS.md contents, Claude hooks/MCPs/commands, cursor rules, VSCode extensions, global npm/brew/pip packages, installed dev CLIs with versions
17
+
18
+ TREAT PREFETCHED DATA AS AUTHORITATIVE. Do not re-run git log, curl GitHub, scan local files, parse ~/.claude, etc. That work is done. Read the block and proceed.
19
+
20
+ You may still use the \`bash\` tool for:
21
+ - Filling clear gaps (e.g. if \`ai_usage.claude_code\` is null and you want to double-check nothing was missed)
22
+ - Targeted verification (e.g. reading a specific README excerpt)
23
+ - Anything the prefetched data does not cover (e.g. a follow-up on a finding)
24
+
25
+ ## PHASE 1: Greet
26
+
27
+ Read \`identity.name\` from the prefetched block. Greet them briefly:
28
+ "Hi {name}! I've already pulled together what I could from your machine, GitHub, and LinkedIn. Let me walk you through a few quick questions to round out your profile."
29
+
30
+ ## PHASE 2: Extra enrichment (as needed)
31
+
32
+ Most enrichment is already done. Only reach for the \`standout_enrich\` tool if:
33
+ - \`linkedin.url\` is present but \`linkedin.profile\` is null (re-try \`linkedin_profile\`)
34
+ - There are companies in LinkedIn experiences without entries in \`linkedin.company_enrichments\`
35
+ - \`twitter.handle\` is present but \`twitter.profile\` is null and you want more context
36
+
37
+ ## PHASE 3: Web presence
38
+
39
+ Use the \`web_search\` tool (max 5 calls — spend them wisely) to fill in things the APIs don't show:
40
+ - Blog posts or Substack
41
+ - Conference talks
42
+ - ProductHunt launches
43
+ - Notable press mentions, podcast appearances
44
+ - Stack Overflow profile (if any)
45
+
46
+ Skip if their GitHub + LinkedIn is already rich and the signal is marginal.
47
+
48
+ ## PHASE 4: Interactive questions
49
+
50
+ CRITICAL: Use the \`ask_user\` tool. Never print questions as plain text — the user cannot respond to text output.
51
+
52
+ Ask questions ONE AT A TIME. Wait for each answer before asking the next.
53
+
54
+ Between questions, share interesting findings from the prefetched data as text output to keep it engaging. Use \`ai_usage.prompt_samples\` and \`ai_usage.projects\` to riff:
55
+ "You've been spending time on the Kyoto workspace — looks like admin tooling for the matching pipeline. Nice."
56
+
57
+ Then use the ask_user tool for each question:
58
+
59
+ 1. \`ask_user\`: "What kind of role are you looking for?" options: ["IC Engineer", "Founding Engineer", "Engineering Manager", "CTO / VP Eng"]
60
+ 2. \`ask_user\`: "Any preference on company stage or size?" options: ["Seed / Pre-seed", "Series A-B", "Growth (Series C+)", "No preference"]
61
+ 3. \`ask_user\`: "Location preference?" options: ["Remote only", "Hybrid", "On-site", "Flexible"]
62
+ 4. \`ask_user\`: "What are your compensation expectations?" — free text
63
+ 5. \`ask_user\`: "When would you be available to start?" options: ["Immediately", "2 weeks notice", "1 month+", "Just exploring"]
64
+ 6. \`ask_user\`: "Any dealbreakers?" — free text
65
+ 7. \`ask_user\`: "Work authorization?" options: ["US citizen/permanent resident", "US work visa", "EU citizen", "Need sponsorship"]
66
+ 8. \`ask_user\`: "Anything I got wrong or want to add?" options: ["Looks good!", "I have corrections"]
67
+
68
+ ## PHASE 5: Compile and present
69
+
70
+ Compile everything into a structured profile. Show the user a summary card:
71
+
72
+ \`\`\`
73
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
74
+ YOUR DEVELOPER PROFILE
75
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
76
+ {name}
77
+ {headline}
78
+ {location}
79
+
80
+ Career: {N} roles | Latest: {title} @ {company}
81
+ GitHub: {repos} repos | {contributions} contributions
82
+ Tech: {top languages}
83
+ AI Tools: {tools used}
84
+ Claude Code: {total_sessions} sessions over {active_days} days
85
+ Looking for: {role preference}
86
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
87
+ \`\`\`
88
+
89
+ Use \`ask_user\`: "Ready to submit your profile to Standout?" options: ["Yes, submit!", "No, I want to make changes"]
90
+
91
+ ## PHASE 6: Submit
92
+
93
+ Use the \`standout_submit\` tool with the full structured profile JSON.
94
+
95
+ The JSON should follow this structure:
96
+ {
97
+ "identity": {
98
+ "name": "",
99
+ "email": "",
100
+ "location": "",
101
+ "linkedin_url": "",
102
+ "github_username": "",
103
+ "github_url": "",
104
+ "twitter": "",
105
+ "twitter_url": "",
106
+ "website": "",
107
+ "languages_spoken": []
108
+ },
109
+ "career": [
110
+ {
111
+ "company": "",
112
+ "company_linkedin_url": "",
113
+ "title": "",
114
+ "start_date": "",
115
+ "end_date": "",
116
+ "description": "",
117
+ "company_context": {
118
+ "industry": "",
119
+ "size": "",
120
+ "type": "",
121
+ "hq": "",
122
+ "founded_year": null
123
+ }
124
+ }
125
+ ],
126
+ "education": [
127
+ {
128
+ "school": "",
129
+ "degree": "",
130
+ "field": "",
131
+ "start_year": null,
132
+ "end_year": null
133
+ }
134
+ ],
135
+ "github": {
136
+ "username": "",
137
+ "public_repos": 0,
138
+ "followers": 0,
139
+ "following": 0,
140
+ "contributions_last_year": 0,
141
+ "contribution_trend": [],
142
+ "top_languages": [],
143
+ "notable_repos": [
144
+ {
145
+ "name": "",
146
+ "language": "",
147
+ "description": "",
148
+ "stars": 0,
149
+ "quality_signals": []
150
+ }
151
+ ],
152
+ "starred_repos_summary": "",
153
+ "following_users": [],
154
+ "oss_contributions": [],
155
+ "npm_packages": [],
156
+ "commit_message_style": "",
157
+ "readme_quality": ""
158
+ },
159
+ "work_patterns": {
160
+ "peak_hours": "",
161
+ "active_days": "",
162
+ "consistency": "",
163
+ "commits_since_2024": 0,
164
+ "local_repos_count": 0,
165
+ "collaborators": []
166
+ },
167
+ "web_presence": {
168
+ "producthunt": "",
169
+ "blog_posts": [],
170
+ "talks": [],
171
+ "press_mentions": [],
172
+ "stackoverflow": ""
173
+ },
174
+ "linkedin_profile": {
175
+ "headline": "",
176
+ "follower_count": 0,
177
+ "connections": 0,
178
+ "skills": [],
179
+ "recommendations_count": 0,
180
+ "volunteer_work": [],
181
+ "certifications": []
182
+ },
183
+ "ai_coding": {
184
+ "tools_used": [],
185
+ "ai_assisted_commit_percentage": null,
186
+ "delegation_style": "",
187
+ "common_task_types": [],
188
+ "coding_conventions_documented": false,
189
+ "claude_code_stats": {
190
+ "total_sessions": 0,
191
+ "active_days": 0,
192
+ "total_hours": 0,
193
+ "top_tools": [],
194
+ "models_used": [],
195
+ "peak_hour": null,
196
+ "weekend_ratio": null,
197
+ "languages": {},
198
+ "frameworks": [],
199
+ "work_themes": []
200
+ },
201
+ "codex_stats": {
202
+ "total_sessions": 0,
203
+ "active_days": 0,
204
+ "models_used": [],
205
+ "languages": {}
206
+ }
207
+ },
208
+ "ai_usage": {
209
+ "claude_code": null,
210
+ "codex": null,
211
+ "cursor": null
212
+ },
213
+ "readiness": {
214
+ "usage_stats_status": "",
215
+ "profile_basics_status": "",
216
+ "enrichment_status": ""
217
+ },
218
+ "dev_environment": {
219
+ "global_tools": [],
220
+ "vscode_extensions_count": 0,
221
+ "claude_mcp_servers": [],
222
+ "claude_custom_commands_count": 0,
223
+ "notable_packages": []
224
+ },
225
+ "preferences": {
226
+ "role_type": "",
227
+ "company_stage": "",
228
+ "location_preference": "",
229
+ "compensation": "",
230
+ "availability": "",
231
+ "dealbreakers": "",
232
+ "work_authorization": ""
233
+ },
234
+ "ai_summary": "A 3-4 sentence professional summary highlighting the person's trajectory, strengths, and what makes them distinctive. Lean on work_themes and frameworks observed in ai_usage to make this concrete — not generic."
235
+ }
236
+
237
+ ## IMPORTANT RULES
238
+
239
+ - Trust the prefetched block. It already filtered to commits authored by the user's email.
240
+ - Preserve raw \`ai_usage\` and \`readiness\` from the prefetched block in the final JSON. Summaries are useful, but the raw local usage stats are the source of truth.
241
+ - For claims derived from \`ai_usage.prompt_samples\`, treat prompts as the developer's own words — paraphrase, don't quote in full.
242
+ - Don't make up data. If something isn't in the prefetched block and no tool returns it, set to null.
243
+ - Be conversational and engaging, not robotic.
244
+ - Share interesting findings as you go.
245
+ - If an API call fails, skip it and move on.
246
+ - Ask interactive questions ONE AT A TIME.
247
+ - If you receive a pause_turn stop reason, your turn will be continued automatically — just keep going.`;
@@ -0,0 +1 @@
1
+ export declare function redactSecrets(text: string): string;
package/dist/redact.js ADDED
@@ -0,0 +1,37 @@
1
+ // Redacts secret-looking strings from any text before it leaves the machine
2
+ // (prompt samples, conversation exchanges, etc.). Belt-and-suspenders alongside
3
+ // code-fence stripping — users paste API keys / tokens inline in prompts.
4
+ //
5
+ // Patterns target high-entropy secret *values* (low false-positive), plus a
6
+ // conservative "label = <long value>" form. Plain mentions of words like
7
+ // "token" or env-var names like ANTHROPIC_API_KEY are intentionally left alone.
8
+ const REDACTION = "[redacted]";
9
+ const SECRET_PATTERNS = [
10
+ // Private key PEM blocks (whole block).
11
+ /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g,
12
+ // Provider keys / tokens with distinctive prefixes.
13
+ /sk-ant-[A-Za-z0-9_-]{20,}/g,
14
+ /sk-(?:proj-)?[A-Za-z0-9]{20,}/g, // OpenAI
15
+ /npm_[A-Za-z0-9]{36}/g,
16
+ /gh[pousr]_[A-Za-z0-9]{36,}/g, // GitHub
17
+ /github_pat_[A-Za-z0-9_]{60,}/g,
18
+ /AKIA[0-9A-Z]{16}/g, // AWS access key id
19
+ /ASIA[0-9A-Z]{16}/g,
20
+ /AIza[0-9A-Za-z_-]{35}/g, // Google
21
+ /ya29\.[A-Za-z0-9_-]{20,}/g, // Google OAuth
22
+ /xox[baprs]-[A-Za-z0-9-]{10,}/g, // Slack
23
+ /[rs]k_(?:live|test)_[A-Za-z0-9]{20,}/g, // Stripe
24
+ /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{6,}/g, // JWT
25
+ ];
26
+ // "api_key": "longvalue" / API_KEY=longvalue / bearer longvalue — redact only
27
+ // the value, keep the label so the text still reads sensibly.
28
+ const LABELED_SECRET = /\b(api[_-]?key|secret|token|password|passwd|authorization|bearer)\b(["'\s:=]{1,4})([A-Za-z0-9_\-./+]{20,})/gi;
29
+ export function redactSecrets(text) {
30
+ if (!text)
31
+ return text;
32
+ let out = text;
33
+ for (const re of SECRET_PATTERNS)
34
+ out = out.replace(re, REDACTION);
35
+ out = out.replace(LABELED_SECRET, (_m, label, sep) => `${label}${sep}${REDACTION}`);
36
+ return out;
37
+ }
@@ -0,0 +1,4 @@
1
+ import type Anthropic from "@anthropic-ai/sdk";
2
+ import { type SubmitProfileOptions } from "./api.js";
3
+ export declare const TOOLS: Anthropic.Tool[];
4
+ export declare function executeTool(name: string, input: Record<string, unknown>, options?: SubmitProfileOptions): Promise<string>;
package/dist/tools.js ADDED
@@ -0,0 +1,182 @@
1
+ import { execSync } from "child_process";
2
+ import { createInterface } from "readline";
3
+ import { enrichProfile, submitProfile, } from "./api.js";
4
+ const MAX_OUTPUT = 50 * 1024; // 50KB
5
+ const BLOCKED_PATTERNS = [
6
+ // Destructive
7
+ /\brm\s/,
8
+ /\brmdir\b/,
9
+ /\bmv\s/,
10
+ /\bmkdir\s/,
11
+ /\btouch\s/,
12
+ // Write redirects
13
+ />/,
14
+ /\bchmod\b/,
15
+ /\bchown\b/,
16
+ /\bsudo\b/,
17
+ // Package managers
18
+ /\bnpm install\b/,
19
+ /\bnpm i\b/,
20
+ /\bpip install\b/,
21
+ /\bbrew\s/,
22
+ /\byarn add\b/,
23
+ /\bpnpm add\b/,
24
+ // Execution
25
+ /\bbash -c\b/,
26
+ /\bsh -c\b/,
27
+ /\bpython -c\b/,
28
+ /\bnode -e\b/,
29
+ /\bperl -e\b/,
30
+ /\bruby -e\b/,
31
+ /\beval\s/,
32
+ // Git writes
33
+ /\bgit push\b/,
34
+ /\bgit commit\b/,
35
+ /\bgit reset\b/,
36
+ /\bgit checkout\b/,
37
+ /\bgit branch -[dD]\b/,
38
+ /\bgit merge\b/,
39
+ /\bgit rebase\b/,
40
+ /\bgit stash\b/,
41
+ // Network writes
42
+ /\bcurl\b.*-X\s*(POST|PUT|PATCH|DELETE)/i,
43
+ /\bcurl\b.*(-d|--data)\s/,
44
+ // Full paths to dangerous commands
45
+ /\/bin\/rm\b/,
46
+ /\/usr\/bin\/rm\b/,
47
+ ];
48
+ function isCommandSafe(command) {
49
+ return !BLOCKED_PATTERNS.some((pattern) => pattern.test(command));
50
+ }
51
+ function executeBash(command) {
52
+ if (!isCommandSafe(command)) {
53
+ return "Error: Command blocked by read-only sandbox. Only read operations are allowed.";
54
+ }
55
+ try {
56
+ const output = execSync(command, {
57
+ timeout: 30000,
58
+ maxBuffer: 1024 * 1024,
59
+ encoding: "utf-8",
60
+ stdio: ["pipe", "pipe", "pipe"],
61
+ });
62
+ if (output.length > MAX_OUTPUT) {
63
+ return (output.slice(0, MAX_OUTPUT) +
64
+ `\n\n[Output truncated at ${MAX_OUTPUT} bytes]`);
65
+ }
66
+ return output;
67
+ }
68
+ catch (error) {
69
+ const err = error;
70
+ return `Error: ${err.stderr || err.message || "Command failed"}`;
71
+ }
72
+ }
73
+ async function askUser(question, options) {
74
+ process.stderr.write(`\n${question}\n`);
75
+ if (options?.length) {
76
+ options.forEach((opt, i) => {
77
+ process.stderr.write(` ${i + 1}. ${opt}\n`);
78
+ });
79
+ process.stderr.write(` ${options.length + 1}. Other (type your answer)\n`);
80
+ }
81
+ process.stderr.write("\n> ");
82
+ return new Promise((resolve) => {
83
+ const iface = createInterface({
84
+ input: process.stdin,
85
+ output: process.stderr,
86
+ });
87
+ iface.question("", (answer) => {
88
+ iface.close();
89
+ const trimmed = answer.trim();
90
+ if (options?.length) {
91
+ const num = parseInt(trimmed, 10);
92
+ if (num >= 1 && num <= options.length) {
93
+ resolve(options[num - 1]);
94
+ return;
95
+ }
96
+ }
97
+ resolve(trimmed);
98
+ });
99
+ });
100
+ }
101
+ export const TOOLS = [
102
+ {
103
+ name: "bash",
104
+ description: "Execute a bash command in a read-only sandbox. Destructive commands (rm, mv, git push, sudo, etc.) are blocked. Use for reading files, git log, curl GET, find, ls, etc.",
105
+ input_schema: {
106
+ type: "object",
107
+ properties: {
108
+ command: {
109
+ type: "string",
110
+ description: "The bash command to execute",
111
+ },
112
+ },
113
+ required: ["command"],
114
+ },
115
+ },
116
+ {
117
+ name: "ask_user",
118
+ description: "Ask the user a question interactively. Can provide predefined options or allow free-text input.",
119
+ input_schema: {
120
+ type: "object",
121
+ properties: {
122
+ question: {
123
+ type: "string",
124
+ description: "The question to ask",
125
+ },
126
+ options: {
127
+ type: "array",
128
+ items: { type: "string" },
129
+ description: "Optional list of predefined answer choices",
130
+ },
131
+ },
132
+ required: ["question"],
133
+ },
134
+ },
135
+ {
136
+ name: "standout_enrich",
137
+ description: "Enrich a profile using Standout's data APIs. Supports: email_to_linkedin (resolve email to LinkedIn URL), linkedin_profile (get full LinkedIn profile), company (get company data by LinkedIn URL).",
138
+ input_schema: {
139
+ type: "object",
140
+ properties: {
141
+ type: {
142
+ type: "string",
143
+ enum: ["email_to_linkedin", "linkedin_profile", "company"],
144
+ description: "Type of enrichment to perform",
145
+ },
146
+ value: {
147
+ type: "string",
148
+ description: "The value to enrich: email address, LinkedIn profile URL, or company LinkedIn URL",
149
+ },
150
+ },
151
+ required: ["type", "value"],
152
+ },
153
+ },
154
+ {
155
+ name: "standout_submit",
156
+ description: "Submit the completed developer profile to Standout. Call this after the user confirms they want to submit.",
157
+ input_schema: {
158
+ type: "object",
159
+ properties: {
160
+ profile: {
161
+ type: "string",
162
+ description: "The full profile JSON as a string",
163
+ },
164
+ },
165
+ required: ["profile"],
166
+ },
167
+ },
168
+ ];
169
+ export async function executeTool(name, input, options = {}) {
170
+ switch (name) {
171
+ case "bash":
172
+ return executeBash(input.command);
173
+ case "ask_user":
174
+ return askUser(input.question, input.options);
175
+ case "standout_enrich":
176
+ return enrichProfile(input.type, input.value);
177
+ case "standout_submit":
178
+ return submitProfile(input.profile, options);
179
+ default:
180
+ return `Unknown tool: ${name}`;
181
+ }
182
+ }
@@ -0,0 +1 @@
1
+ export declare function scrapeTwitterHandle(): Promise<string | null>;