Skip to content

Commit 57cd4ac

Browse files
committed
feat: implemented agent detection and suggestion display [skip ci]
1 parent b90e7f6 commit 57cd4ac

4 files changed

Lines changed: 131 additions & 0 deletions

File tree

src/agents/detector.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { access } from "node:fs/promises";
2+
import { join } from "node:path";
3+
import { homedir } from "node:os";
4+
5+
export interface AgentInfo {
6+
name: string;
7+
id: string;
8+
detectionPath: string;
9+
wrappedCommand: string;
10+
repoUrl: string;
11+
brandColor: string;
12+
}
13+
14+
export interface DetectedAgent extends AgentInfo {
15+
detected: boolean;
16+
}
17+
18+
const AGENTS: AgentInfo[] = [
19+
{
20+
name: "Claude Code",
21+
id: "claude",
22+
detectionPath: join(homedir(), ".claude"),
23+
wrappedCommand: "npx cc-wrapped",
24+
repoUrl: "https://github.com/numman-ali/cc-wrapped",
25+
brandColor: "#D97757",
26+
},
27+
{
28+
name: "Codex",
29+
id: "codex",
30+
detectionPath: join(homedir(), ".codex"),
31+
wrappedCommand: "npx codex-wrapped",
32+
repoUrl: "https://github.com/numman-ali/codex-wrapped",
33+
brandColor: "#3b82f6",
34+
},
35+
{
36+
name: "Gemini CLI",
37+
id: "gemini",
38+
detectionPath: join(homedir(), ".gemini"),
39+
wrappedCommand: "npx gemini-wrapped",
40+
repoUrl: "https://github.com/jackwotherspoon/gemini-cli-wrapped",
41+
brandColor: "#CBA6F7",
42+
},
43+
];
44+
45+
async function pathExists(path: string): Promise<boolean> {
46+
try {
47+
await access(path);
48+
return true;
49+
} catch {
50+
return false;
51+
}
52+
}
53+
54+
export async function detectInstalledAgents(): Promise<DetectedAgent[]> {
55+
const results = await Promise.all(
56+
AGENTS.map(async (agent) => ({
57+
...agent,
58+
detected: await pathExists(agent.detectionPath),
59+
}))
60+
);
61+
62+
return results.filter((agent) => agent.detected);
63+
}

src/agents/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./detector";
2+
export * from "./suggestions";

src/agents/suggestions.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as p from "@clack/prompts";
2+
import type { DetectedAgent } from "./detector";
3+
4+
function hexToRgb(hex: string): { r: number; g: number; b: number } {
5+
const clean = hex.replace(/^#/, "");
6+
const fullHex =
7+
clean.length === 3
8+
? clean
9+
.split("")
10+
.map((c) => c + c)
11+
.join("")
12+
: clean;
13+
const num = parseInt(fullHex, 16);
14+
if (isNaN(num)) return { r: 255, g: 255, b: 255 };
15+
return {
16+
r: (num >> 16) & 255,
17+
g: (num >> 8) & 255,
18+
b: num & 255,
19+
};
20+
}
21+
22+
function colorize(text: string, hex: string): string {
23+
const { r, g, b } = hexToRgb(hex);
24+
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
25+
}
26+
27+
function dim(text: string): string {
28+
return `\x1b[2m${text}\x1b[0m`;
29+
}
30+
31+
function reset(text: string): string {
32+
return `\x1b[0m${text}\x1b[0m`;
33+
}
34+
35+
function hyperlink(text: string, url: string): string {
36+
return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
37+
}
38+
39+
function underline(text: string): string {
40+
return `\x1b[4m${text}\x1b[0m`;
41+
}
42+
43+
export function displayStyledSuggestions(agents: DetectedAgent[]): void {
44+
if (agents.length === 0) return;
45+
46+
const maxNameLength = Math.max(...agents.map((a) => a.name.length));
47+
const lines = agents.map((agent) => {
48+
const coloredName = colorize(agent.name, agent.brandColor);
49+
const linkedName = underline(hyperlink(coloredName, agent.repoUrl));
50+
const padding = " ".repeat(maxNameLength - agent.name.length);
51+
52+
return reset(` ${linkedName}${padding} ${dim("→")} ${dim(agent.wrappedCommand)}`);
53+
});
54+
55+
const content = [...lines, "", "Generate wrapped stats for those too!"].join("\n");
56+
p.note(content, "Other AI agents detected on your system");
57+
}
58+
59+
export function displayAgentSuggestions(agents: DetectedAgent[]): void {
60+
if (agents.length === 0) return;
61+
displayStyledSuggestions(agents);
62+
}

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { displayInTerminal, getTerminalName } from "./terminal/display";
1313
import { copyImageToClipboard } from "./clipboard";
1414
import { isWrappedAvailable } from "./utils/dates";
1515
import { formatNumber } from "./utils/format";
16+
import { detectInstalledAgents, displayAgentSuggestions } from "./agents";
1617
import type { OpenCodeStats } from "./types";
1718

1819
const VERSION = "1.0.0";
@@ -180,6 +181,9 @@ async function main() {
180181
}
181182
}
182183

184+
const detectedAgents = await detectInstalledAgents();
185+
displayAgentSuggestions(detectedAgents);
186+
183187
p.outro("Share your wrapped!");
184188
process.exit(0);
185189
}

0 commit comments

Comments
 (0)