amp-wrapped 1.0.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.
package/.releaserc.mjs ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @type {import('semantic-release').GlobalConfig}
3
+ */
4
+ export default {
5
+ branches: [
6
+ "main",
7
+ { name: "beta", prerelease: true },
8
+ { name: "alpha", prerelease: true },
9
+ ],
10
+ plugins: [
11
+ "@semantic-release/commit-analyzer",
12
+ "@semantic-release/release-notes-generator",
13
+ [
14
+ "@semantic-release/exec",
15
+ {
16
+ publishCmd: "bun run ./scripts/publish.ts ${nextRelease.version}",
17
+ },
18
+ ],
19
+ [
20
+ "@semantic-release/git",
21
+ {
22
+ assets: ["package.json"],
23
+ message: "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}",
24
+ },
25
+ ],
26
+ "@semantic-release/github",
27
+ ],
28
+ };
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ajan Raj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ <div align="center">
2
+
3
+ # amp-wrapped
4
+
5
+ **Your year in code, beautifully visualized.**
6
+
7
+ <p>
8
+ <strong>Credit:</strong> Built on top of
9
+ <a href="https://github.com/moddi3/opencode-wrapped">opencode-wrapped</a>
10
+ by moddi3 (<a href="https://x.com/moddi3io">@moddi3io</a>).
11
+ </p>
12
+ <p>
13
+ Follow <a href="https://x.com/ajanraj25">@ajanraj25</a> for updates!
14
+ </p>
15
+
16
+ Generate a personalized "Spotify Wrapped"-style summary of your [Amp Code](https://ampcode.com) usage.
17
+
18
+ [![npm version](https://img.shields.io/npm/v/amp-wrapped.svg)](https://www.npmjs.com/package/amp-wrapped)
19
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
20
+ [![Bun](https://img.shields.io/badge/Bun-%23000000.svg?logo=bun&logoColor=white)](https://bun.sh)
21
+ [![GitHub](https://img.shields.io/github/stars/ajanraj/amp-wrapped?style=social)](https://github.com/ajanraj/amp-wrapped)
22
+
23
+ <img src="./assets/images/amp-wrapped-cover.png" alt="Amp Wrapped Example" width="600" />
24
+
25
+ </div>
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ### Quick Start
32
+
33
+ Run directly without installing:
34
+
35
+ ```bash
36
+ npx amp-wrapped # or bunx, or yarn/pnpm dlx
37
+ ```
38
+
39
+ ### Global Install
40
+
41
+ ```bash
42
+ npm install -g amp-wrapped # or bun/yarn/pnpm
43
+ ```
44
+
45
+ Then run anywhere:
46
+
47
+ ```bash
48
+ amp-wrapped
49
+ ```
50
+
51
+ ## Usage Options
52
+
53
+ | Option | Description |
54
+ | --------------- | ------------------------------------ |
55
+ | `--year, -y` | Generate wrapped for a specific year |
56
+ | `--help, -h` | Show help message |
57
+ | `--version, -v` | Show version number |
58
+
59
+ ## Features
60
+
61
+ - Sessions, messages, tokens, projects, and streaks
62
+ - GitHub-style activity heatmap
63
+ - Top models breakdown
64
+ - Credits usage tracking
65
+ - Shareable PNG image
66
+ - Inline image display (Ghostty, Kitty, iTerm2, WezTerm, Konsole)
67
+ - Auto-copy to clipboard
68
+
69
+ ## Terminal Support
70
+
71
+ The wrapped image displays natively in terminals that support inline images:
72
+
73
+ | Terminal | Protocol | Status |
74
+ | ------------------------------------------ | -------------- | --------------------------- |
75
+ | [Ghostty](https://ghostty.org) | Kitty Graphics | Full support |
76
+ | [Kitty](https://sw.kovidgoyal.net/kitty/) | Kitty Graphics | Full support |
77
+ | [WezTerm](https://wezfurlong.org/wezterm/) | Kitty + iTerm2 | Full support |
78
+ | [iTerm2](https://iterm2.com) | iTerm2 Inline | Full support |
79
+ | [Konsole](https://konsole.kde.org) | Kitty Graphics | Full support |
80
+ | Other terminals | — | Image saved to file only |
81
+
82
+ ## Output
83
+
84
+ The tool generates:
85
+
86
+ 1. **Terminal Summary** — Quick stats overview in your terminal
87
+ 2. **PNG Image** — A beautiful, shareable wrapped card saved to your home directory
88
+ 3. **Clipboard** — Automatically copies the image to your clipboard
89
+
90
+ ## Data Source
91
+
92
+ Amp Wrapped reads data from your local Amp installation:
93
+
94
+ ```
95
+ ~/.local/share/amp/threads/
96
+ ```
97
+
98
+ No data is sent anywhere. Everything is processed locally.
99
+
100
+ ## Building
101
+
102
+ ### Development
103
+
104
+ ```bash
105
+ # Run in development mode with hot reload
106
+ bun run dev
107
+ ```
108
+
109
+ ### Production Build
110
+
111
+ ```bash
112
+ # Build for all platforms
113
+ bun run build
114
+ ```
115
+
116
+ ### Releasing
117
+
118
+ Releases are automated via [semantic-release](https://semantic-release.gitbook.io). Merging PRs with [conventional commits](https://www.conventionalcommits.org) to `main` triggers a release.
119
+
120
+ ## Tech Stack
121
+
122
+ - **Runtime**: [Bun](https://bun.sh)
123
+ - **Image Generation**: [Satori](https://github.com/vercel/satori) + [Resvg](https://github.com/nicolo-ribaudo/resvg-js)
124
+ - **CLI UI**: [@clack/prompts](https://github.com/natemoo-re/clack)
125
+ - **Font**: IBM Plex Mono
126
+
127
+ ## Contributing
128
+
129
+ Contributions are welcome! Please feel free to submit a Pull Request.
130
+
131
+ ## License
132
+
133
+ MIT License - see [LICENSE](LICENSE) for details.
134
+
135
+ ---
136
+
137
+ <div align="center">
138
+
139
+ Built for the Amp Code community
140
+
141
+ </div>
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { platform as osPlatform, arch as osArch } from "node:os";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ function run(target) {
13
+ const result = spawnSync(target, process.argv.slice(2), {
14
+ stdio: "inherit",
15
+ });
16
+ if (result.error) {
17
+ console.error(result.error.message);
18
+ process.exit(1);
19
+ }
20
+ const code = typeof result.status === "number" ? result.status : 0;
21
+ process.exit(code);
22
+ }
23
+
24
+ const platformMap = {
25
+ darwin: "darwin",
26
+ linux: "linux",
27
+ win32: "windows",
28
+ };
29
+ const archMap = {
30
+ x64: "x64",
31
+ arm64: "arm64",
32
+ };
33
+
34
+ let platform = platformMap[osPlatform()];
35
+ if (!platform) {
36
+ platform = osPlatform();
37
+ }
38
+ let arch = archMap[osArch()];
39
+ if (!arch) {
40
+ arch = osArch();
41
+ }
42
+ const base = "amp-wrapped-" + platform + "-" + arch;
43
+ const binary = platform === "windows" ? "amp-wrapped.exe" : "amp-wrapped";
44
+
45
+ function findBinary(startDir) {
46
+ let current = startDir;
47
+ for (;;) {
48
+ const modules = join(current, "node_modules");
49
+ if (fs.existsSync(modules)) {
50
+ const entries = fs.readdirSync(modules);
51
+ for (const entry of entries) {
52
+ if (!entry.startsWith(base)) {
53
+ continue;
54
+ }
55
+ const candidate = join(modules, entry, "bin", binary);
56
+ if (fs.existsSync(candidate)) {
57
+ return candidate;
58
+ }
59
+ }
60
+ }
61
+ const parent = dirname(current);
62
+ if (parent === current) {
63
+ return;
64
+ }
65
+ current = parent;
66
+ }
67
+ }
68
+
69
+ const resolved = findBinary(__dirname);
70
+ if (!resolved) {
71
+ console.error(
72
+ 'It seems that your package manager failed to install the right version of amp-wrapped CLI for your platform. You can try manually installing "' +
73
+ base +
74
+ '" package'
75
+ );
76
+ process.exit(1);
77
+ }
78
+
79
+ run(resolved);
package/mise.toml ADDED
@@ -0,0 +1,6 @@
1
+ [tools]
2
+ bun = "1.3.5"
3
+
4
+ [env]
5
+ _.file = '.env'
6
+ NPM_TOKEN = { required = true }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "amp-wrapped",
3
+ "version": "1.0.0",
4
+ "description": "Generate a personalized Spotify Wrapped-style summary of your Amp Code usage",
5
+ "keywords": [
6
+ "amp",
7
+ "amp-code",
8
+ "sourcegraph",
9
+ "wrapped",
10
+ "amp-wrapped"
11
+ ],
12
+ "homepage": "https://github.com/ajanraj/amp-wrapped",
13
+ "bugs": {
14
+ "url": "https://github.com/ajanraj/amp-wrapped/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/ajanraj/amp-wrapped.git"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Ajan Raj <hey@ajanraj.com>",
22
+ "type": "module",
23
+ "main": "index.js",
24
+ "bin": {
25
+ "amp-wrapped": "bin/amp-wrapped"
26
+ },
27
+ "scripts": {
28
+ "start": "bun src/index.ts",
29
+ "dev": "bun run --watch src/index.ts",
30
+ "build": "tsgo --noEmit && bun run scripts/build.ts",
31
+ "publish": "bun run scripts/publish.ts",
32
+ "release": "semantic-release",
33
+ "clean": "rm -rf dist"
34
+ },
35
+ "dependencies": {
36
+ "@clack/prompts": "^0.11.0",
37
+ "@resvg/resvg-wasm": "^2.6.2",
38
+ "react": "^19.2.3",
39
+ "satori": "^0.18.3",
40
+ "xdg-basedir": "^5.1.0"
41
+ },
42
+ "devDependencies": {
43
+ "@semantic-release/exec": "^7.1.0",
44
+ "@semantic-release/git": "^10.0.1",
45
+ "@types/bun": "latest",
46
+ "@types/react": "^19.2.7",
47
+ "@typescript/native-preview": "^7.0.0-dev.20251223.1",
48
+ "bunup": "^0.16.10",
49
+ "semantic-release": "^25.0.2",
50
+ "typescript": "^5.0.0"
51
+ }
52
+ }
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall script for amp-wrapped
5
+ *
6
+ * This script runs after npm install and symlinks the correct platform-specific
7
+ * binary to the bin directory. It auto-detects:
8
+ * - Platform (darwin, linux, windows)
9
+ * - Architecture (arm64, x64)
10
+ * - Libc (glibc, musl) for Linux
11
+ * - AVX2 support (baseline vs optimized) for x64
12
+ */
13
+
14
+ import fs from "fs";
15
+ import path from "path";
16
+ import os from "os";
17
+ import { execSync } from "child_process";
18
+ import { fileURLToPath } from "url";
19
+ import { createRequire } from "module";
20
+
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ const require = createRequire(import.meta.url);
23
+
24
+ /**
25
+ * Detect if the system uses musl libc (Alpine Linux, etc.)
26
+ */
27
+ function detectMusl() {
28
+ if (os.platform() !== "linux") return false;
29
+
30
+ try {
31
+ // Method 1: Check ldd output
32
+ const lddOutput = execSync("ldd --version 2>&1 || true", { encoding: "utf8" });
33
+ if (lddOutput.toLowerCase().includes("musl")) {
34
+ return true;
35
+ }
36
+
37
+ // Method 2: Check for musl loader
38
+ const files = fs.readdirSync("/lib").filter((f) => f.startsWith("ld-musl-"));
39
+ if (files.length > 0) {
40
+ return true;
41
+ }
42
+ } catch {
43
+ // Ignore errors
44
+ }
45
+
46
+ return false;
47
+ }
48
+
49
+ /**
50
+ * Detect if the CPU supports AVX2 instructions
51
+ */
52
+ function detectAVX2() {
53
+ if (os.arch() !== "x64") return true; // Only relevant for x64
54
+
55
+ try {
56
+ if (os.platform() === "linux") {
57
+ const cpuinfo = fs.readFileSync("/proc/cpuinfo", "utf8");
58
+ return cpuinfo.toLowerCase().includes("avx2");
59
+ }
60
+
61
+ if (os.platform() === "darwin") {
62
+ const output = execSync("sysctl -n machdep.cpu.features 2>/dev/null || true", {
63
+ encoding: "utf8",
64
+ });
65
+ return output.toLowerCase().includes("avx2");
66
+ }
67
+
68
+ if (os.platform() === "win32") {
69
+ // Windows: Assume AVX2 support on modern systems
70
+ // A more robust check would require native code
71
+ return true;
72
+ }
73
+ } catch {
74
+ // If we can't detect, assume AVX2 is supported
75
+ }
76
+
77
+ return true;
78
+ }
79
+
80
+ /**
81
+ * Get the platform-specific package name
82
+ */
83
+ function getPackageName() {
84
+ let platform;
85
+ switch (os.platform()) {
86
+ case "darwin":
87
+ platform = "darwin";
88
+ break;
89
+ case "linux":
90
+ platform = "linux";
91
+ break;
92
+ case "win32":
93
+ platform = "windows";
94
+ break;
95
+ default:
96
+ return null;
97
+ }
98
+
99
+ let arch;
100
+ switch (os.arch()) {
101
+ case "x64":
102
+ arch = "x64";
103
+ break;
104
+ case "arm64":
105
+ arch = "arm64";
106
+ break;
107
+ default:
108
+ return null;
109
+ }
110
+
111
+ // Build package name parts
112
+ const parts = ["amp-wrapped", platform, arch];
113
+
114
+ // Add baseline suffix for x64 without AVX2
115
+ if (arch === "x64" && !detectAVX2()) {
116
+ parts.push("baseline");
117
+ }
118
+
119
+ // Add musl suffix for Linux with musl libc
120
+ if (platform === "linux" && detectMusl()) {
121
+ parts.push("musl");
122
+ }
123
+
124
+ return parts.join("-");
125
+ }
126
+
127
+ /**
128
+ * Find the binary from the platform package
129
+ */
130
+ function findBinary(packageName) {
131
+ const binaryName = os.platform() === "win32" ? "amp-wrapped.exe" : "amp-wrapped";
132
+
133
+ try {
134
+ const packageJsonPath = require.resolve(`${packageName}/package.json`);
135
+ const packageDir = path.dirname(packageJsonPath);
136
+ const binaryPath = path.join(packageDir, "bin", binaryName);
137
+
138
+ if (fs.existsSync(binaryPath)) {
139
+ return { binaryPath, binaryName };
140
+ }
141
+ } catch {
142
+ // Package not found via require.resolve
143
+ }
144
+
145
+ // Fallback: try common paths
146
+ const fallbackPaths = [
147
+ path.join(__dirname, "..", packageName, "bin", binaryName),
148
+ path.join(__dirname, "node_modules", packageName, "bin", binaryName),
149
+ ];
150
+
151
+ for (const p of fallbackPaths) {
152
+ if (fs.existsSync(p)) {
153
+ return { binaryPath: p, binaryName };
154
+ }
155
+ }
156
+
157
+ return null;
158
+ }
159
+
160
+ /**
161
+ * Prepare the bin directory
162
+ */
163
+ function prepareBinDirectory(binaryName) {
164
+ const binDir = path.join(__dirname, "bin");
165
+ const targetPath = path.join(binDir, binaryName);
166
+
167
+ // Ensure bin directory exists
168
+ if (!fs.existsSync(binDir)) {
169
+ fs.mkdirSync(binDir, { recursive: true });
170
+ }
171
+
172
+ // Remove existing binary/symlink if it exists
173
+ if (fs.existsSync(targetPath)) {
174
+ fs.unlinkSync(targetPath);
175
+ }
176
+
177
+ return { binDir, targetPath };
178
+ }
179
+
180
+ /**
181
+ * Create symlink (or copy on Windows)
182
+ */
183
+ function linkBinary(sourcePath, binaryName) {
184
+ const { targetPath } = prepareBinDirectory(binaryName);
185
+
186
+ if (os.platform() === "win32") {
187
+ // Windows: copy instead of symlink (symlinks require admin)
188
+ fs.copyFileSync(sourcePath, targetPath);
189
+ } else {
190
+ fs.symlinkSync(sourcePath, targetPath);
191
+ }
192
+
193
+ // Verify the file exists
194
+ if (!fs.existsSync(targetPath)) {
195
+ throw new Error(`Failed to create binary at ${targetPath}`);
196
+ }
197
+ }
198
+
199
+ async function main() {
200
+ try {
201
+ const packageName = getPackageName();
202
+
203
+ if (!packageName) {
204
+ console.error(`amp-wrapped: Unsupported platform: ${os.platform()}-${os.arch()}`);
205
+ console.error("Please download the binary manually from:");
206
+ console.error("https://github.com/ajanraj/amp-wrapped/releases");
207
+ process.exit(0); // Exit gracefully
208
+ }
209
+
210
+ console.log(`amp-wrapped: Detected platform package: ${packageName}`);
211
+
212
+ const result = findBinary(packageName);
213
+
214
+ if (!result) {
215
+ // Try fallback without baseline/musl
216
+ const baseParts = packageName.split("-").slice(0, 3);
217
+ const basePackage = baseParts.join("-");
218
+
219
+ if (basePackage !== packageName) {
220
+ console.log(`amp-wrapped: Trying fallback package: ${basePackage}`);
221
+ const fallbackResult = findBinary(basePackage);
222
+
223
+ if (fallbackResult) {
224
+ linkBinary(fallbackResult.binaryPath, fallbackResult.binaryName);
225
+ return;
226
+ }
227
+ }
228
+
229
+ console.error(`amp-wrapped: Could not find binary for ${packageName}`);
230
+ console.error("The optional dependency may have failed to install.");
231
+ console.error("Please download the binary manually from:");
232
+ console.error("https://github.com/ajanraj/amp-wrapped/releases");
233
+ process.exit(0);
234
+ }
235
+
236
+ linkBinary(result.binaryPath, result.binaryName);
237
+ } catch (error) {
238
+ console.error("amp-wrapped: Postinstall error:", error.message);
239
+ process.exit(0); // Exit gracefully to not break npm install
240
+ }
241
+ }
242
+
243
+ main();