website-xp-phone 1.5.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 (123) hide show
  1. package/.astro/content-assets.mjs +1 -0
  2. package/.astro/content-modules.mjs +1 -0
  3. package/.astro/content.d.ts +199 -0
  4. package/.astro/data-store.json +1 -0
  5. package/.astro/settings.json +8 -0
  6. package/.astro/types.d.ts +1 -0
  7. package/.devcontainer/devcontainer.json +23 -0
  8. package/.env.firebase.example +8 -0
  9. package/.firebaserc +5 -0
  10. package/.gitattributes +2 -0
  11. package/.github/copilot-instructions.md +131 -0
  12. package/.github/dependabot.yml +11 -0
  13. package/.github/workflows/ci.yml +45 -0
  14. package/.github/workflows/deploy-admin.yml +48 -0
  15. package/.github/workflows/static.yml +43 -0
  16. package/.gitmodules +5 -0
  17. package/FIREBASE_SETUP.md +69 -0
  18. package/README.md +63 -0
  19. package/SECURITY.md +11 -0
  20. package/admin/Admin.csproj +7 -0
  21. package/admin/Dockerfile +14 -0
  22. package/admin/Program.cs +8 -0
  23. package/deploy-admin-cloud-run.md +229 -0
  24. package/eslint.config.js +28 -0
  25. package/firebase.json +5 -0
  26. package/firestore.rules +29 -0
  27. package/index.html +52 -0
  28. package/package.json +48 -0
  29. package/pagerts_output.json +1 -0
  30. package/public/5.html +967 -0
  31. package/public/BAHNSCHRIFT.TTF +0 -0
  32. package/public/Beep.ogg +0 -0
  33. package/public/Clippy.png +0 -0
  34. package/public/Layered Network Security Model for Home Networks (slides).pdf +0 -0
  35. package/public/Layered Network Security Model for Home Networks.pdf +0 -0
  36. package/public/TODO.pdf +0 -0
  37. package/public/WoW_Config.zip +3 -0
  38. package/public/addons/energy-swing.txt +1 -0
  39. package/public/addons/lego-yoda-death-readme.txt +11 -0
  40. package/public/addons/lego-yoda-death.mp3 +0 -0
  41. package/public/addons/mana-blast.txt +1 -0
  42. package/public/addons/rage-volley.txt +1 -0
  43. package/public/addons/rueg-cell.txt +1 -0
  44. package/public/addons/rueg-elvui-profile.txt +1 -0
  45. package/public/addons/rueg-grid2.txt +214 -0
  46. package/public/addons/rueg-plater-smol.txt +1 -0
  47. package/public/addons/rueg-plater.txt +1 -0
  48. package/public/addons/rueg-wa-druid.txt +1 -0
  49. package/public/addons/rueg-wa-priest.txt +1 -0
  50. package/public/addons/rueg-wa-rogue.txt +1 -0
  51. package/public/addons/rueg-wa-shaman.txt +1 -0
  52. package/public/addons/rueg-wa-warrior.txt +1 -0
  53. package/public/addons/spirit-smash.txt +1 -0
  54. package/public/avatar.jpg +0 -0
  55. package/public/avatar.png +0 -0
  56. package/public/crunchy_kick.ogg +0 -0
  57. package/public/documents/resume.html +312 -0
  58. package/public/favicon.ico +0 -0
  59. package/public/images/Ateric1.png +0 -0
  60. package/public/images/Ateric2.png +0 -0
  61. package/public/images/equal1.png +0 -0
  62. package/public/images/hyperawareofwhatacatis.png +0 -0
  63. package/public/images/kogg1.png +0 -0
  64. package/public/images/kogg2.png +0 -0
  65. package/public/images/rueg1.png +0 -0
  66. package/public/images/rueg2.png +0 -0
  67. package/public/incorrect_responses.txt +126 -0
  68. package/public/loading.css +51 -0
  69. package/public/resume.pdf +0 -0
  70. package/public/robots.txt +9 -0
  71. package/public/soundcloud.json +57 -0
  72. package/public/spinner.svg +12 -0
  73. package/public/tada.wav +0 -0
  74. package/public/yooh.mp3 +0 -0
  75. package/render.yaml +5 -0
  76. package/scripts/ensure-blog-worktree.mjs +24 -0
  77. package/scripts/generate-soundcloud-json.mjs +198 -0
  78. package/scripts/git-worktree-helper.mjs +122 -0
  79. package/scripts/hoist-dev-blog-local.mjs +149 -0
  80. package/scripts/music-schema.mjs +56 -0
  81. package/scripts/publish-soundcloud-json.mjs +32 -0
  82. package/scripts/sync-music-links-from-worktree.mjs +32 -0
  83. package/src/App.tsx +1500 -0
  84. package/src/addons.json +76 -0
  85. package/src/components/Addon.tsx +223 -0
  86. package/src/components/BlogContent.tsx +103 -0
  87. package/src/components/CopyToClipboardButton.tsx +21 -0
  88. package/src/components/MenuBar.tsx +151 -0
  89. package/src/components/MenuBarWithContext.tsx +6 -0
  90. package/src/components/Modal.tsx +17 -0
  91. package/src/components/MusicContent.tsx +309 -0
  92. package/src/components/NavBarController.tsx +55 -0
  93. package/src/components/NavBarControllerWrapper.tsx +13 -0
  94. package/src/components/Page.tsx +56 -0
  95. package/src/components/SitemapContent.tsx +125 -0
  96. package/src/contacts.json +32 -0
  97. package/src/env.d.ts +13 -0
  98. package/src/lib/assistantStateMachine.ts +80 -0
  99. package/src/lib/audioOverlap.ts +99 -0
  100. package/src/lib/keyboardInputUtils.ts +182 -0
  101. package/src/lib/musicSchema.ts +85 -0
  102. package/src/lib/naggingAssistantClient.ts +241 -0
  103. package/src/lib/resumeAnalytics.ts +163 -0
  104. package/src/main.tsx +35 -0
  105. package/src/pages.json +50 -0
  106. package/src/sections.json +243 -0
  107. package/src/src+addons.zip +3 -0
  108. package/src/styles/main.css +465 -0
  109. package/src/utils/blogSecurity.ts +87 -0
  110. package/src/utils/menuItems.ts +33 -0
  111. package/src/windowing/MinimizedSections.tsx +86 -0
  112. package/src/windowing/Section.tsx +586 -0
  113. package/src/windowing/context.tsx +13 -0
  114. package/src/windowing/hooks.ts +10 -0
  115. package/src/windowing/index.ts +7 -0
  116. package/src/windowing/provider.tsx +74 -0
  117. package/src/windowing/server.ts +3 -0
  118. package/src/windowing/types.ts +33 -0
  119. package/src/windowing/utils.ts +135 -0
  120. package/tests/generate-soundcloud-json.test.mjs +63 -0
  121. package/tests/music-schema.test.mjs +53 -0
  122. package/tsconfig.json +26 -0
  123. package/vite.config.ts +304 -0
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,126 @@
1
+ 5be31d58-407d-4262-9c23
2
+
3
+ ================================================================================
4
+ INCORRECT RESPONSE REPORT
5
+ Generated from: benchmark_results.sqlite
6
+ Total incorrect responses: 18
7
+ ================================================================================
8
+
9
+
10
+ ######################################################################
11
+ HOST: minifridge (http://minifridge:11434)
12
+ ######################################################################
13
+
14
+ MODEL: devstral:latest
15
+ ────────────────────────────────────────────────────────────
16
+
17
+ TEST CASE: Simple Greeting
18
+
19
+ Run : 20260423_122322_113ab5fc (2026-04-23 12:23:22)
20
+ Latency : 5.99s
21
+ Prompt : Say 'Hello World'
22
+ Response : Hello, I'm Devstral! How can I assist you today?
23
+
24
+ Run : 20260423_123535_a966363f (2026-04-23 12:35:35)
25
+ Latency : 3.58s
26
+ Prompt : Say 'Hello World'
27
+ Response : Hello! How can I assist you today?
28
+
29
+ Run : 20260423_131155_86d5f17e (2026-04-23 13:11:55)
30
+ Latency : 3.41s
31
+ Prompt : Say 'Hello World'
32
+ Response : Hello! How can I assist you today?
33
+
34
+ Run : 20260423_132359_a83b6f5b (2026-04-23 13:23:59)
35
+ Latency : 3.29s
36
+ Prompt : Say 'Hello World'
37
+ Response : Hello! How can I assist you today?
38
+
39
+ MODEL: glm-4.7-flash:latest
40
+ ────────────────────────────────────────────────────────────
41
+
42
+ TEST CASE: Simple Greeting
43
+
44
+ Run : 20260423_122322_113ab5fc (2026-04-23 12:23:22)
45
+ Latency : 13.52s
46
+ Prompt : Say 'Hello World'
47
+ Response : Hello, World!
48
+
49
+ Run : 20260423_123535_a966363f (2026-04-23 12:35:35)
50
+ Latency : 10.63s
51
+ Prompt : Say 'Hello World'
52
+ Response : Hello, World!
53
+
54
+ Run : 20260423_124655_ce871a4d (2026-04-23 12:46:55)
55
+ Latency : 12.74s
56
+ Prompt : Say 'Hello World'
57
+ Response : Hello, World
58
+
59
+ MODEL: llama3.1:8b-instruct-q4_K_M
60
+ ────────────────────────────────────────────────────────────
61
+
62
+ TEST CASE: Simple Greeting
63
+
64
+ Run : 20260423_114440_c0c853d7 (2026-04-23 11:44:40)
65
+ Latency : 0.27s
66
+ Prompt : Say 'Hello World'
67
+ Response : Hello, World!
68
+
69
+ Run : 20260423_121008_e767634b (2026-04-23 12:10:08)
70
+ Latency : 0.28s
71
+ Prompt : Say 'Hello World'
72
+ Response : Hello, World!
73
+
74
+ Run : 20260423_123535_a966363f (2026-04-23 12:35:35)
75
+ Latency : 0.27s
76
+ Prompt : Say 'Hello World'
77
+ Response : Hello, World!
78
+
79
+ Run : 20260423_125902_05a87892 (2026-04-23 12:59:02)
80
+ Latency : 0.27s
81
+ Prompt : Say 'Hello World'
82
+ Response : Hello, World!
83
+
84
+ Run : 20260423_131155_86d5f17e (2026-04-23 13:11:55)
85
+ Latency : 0.29s
86
+ Prompt : Say 'Hello World'
87
+ Response : Hello, World!
88
+
89
+ Run : 20260423_133705_cd17867d (2026-04-23 13:37:05)
90
+ Latency : 0.28s
91
+ Prompt : Say 'Hello World'
92
+ Response : Hello, World!
93
+
94
+ MODEL: llama3.1:latest
95
+ ────────────────────────────────────────────────────────────
96
+
97
+ TEST CASE: Simple Greeting
98
+
99
+ Run : 20260423_114440_c0c853d7 (2026-04-23 11:44:40)
100
+ Latency : 0.27s
101
+ Prompt : Say 'Hello World'
102
+ Response : Hello, World!
103
+
104
+ Run : 20260423_115628_38518096 (2026-04-23 11:56:28)
105
+ Latency : 0.28s
106
+ Prompt : Say 'Hello World'
107
+ Response : Hello, World!
108
+
109
+ Run : 20260423_124655_ce871a4d (2026-04-23 12:46:55)
110
+ Latency : 0.27s
111
+ Prompt : Say 'Hello World'
112
+ Response : Hello, World!
113
+
114
+ Run : 20260423_125902_05a87892 (2026-04-23 12:59:02)
115
+ Latency : 0.29s
116
+ Prompt : Say 'Hello World'
117
+ Response : Hello, World!
118
+
119
+ Run : 20260423_131155_86d5f17e (2026-04-23 13:11:55)
120
+ Latency : 0.28s
121
+ Prompt : Say 'Hello World'
122
+ Response : Hello, World!
123
+
124
+
125
+ // why would you do this to me
126
+ // 20c8f20342fb
@@ -0,0 +1,51 @@
1
+ :root {
2
+ --text-color: #000;
3
+ --primary-color: #2f2f2f;
4
+ --background-color: #2f2f2f;
5
+
6
+ --mid-color: color-mix(in srgb, #f5f5dc, #2f2f2f 50%);
7
+ }
8
+
9
+ /* Hide content until styles are loaded, but keep menu bar visible */
10
+ body:not(.styles-loaded) main,
11
+ body:not(.styles-loaded) main *,
12
+ body:not(.styles-loaded) main h1,
13
+ body:not(.styles-loaded) main h2,
14
+ body:not(.styles-loaded) main h3,
15
+ body:not(.styles-loaded) main h4,
16
+ body:not(.styles-loaded) main h5,
17
+ body:not(.styles-loaded) main h6,
18
+ body:not(.styles-loaded) main p,
19
+ body:not(.styles-loaded) main span,
20
+ body:not(.styles-loaded) main label,
21
+ body:not(.styles-loaded) main li,
22
+ body:not(.styles-loaded) main a,
23
+ body:not(.styles-loaded) main button,
24
+ body:not(.styles-loaded) main input,
25
+ body:not(.styles-loaded) main textarea {
26
+ color: transparent !important;
27
+ opacity: 0 !important;
28
+ }
29
+
30
+ .light-mode-switch {
31
+ border-radius: 2em;
32
+ border: 2px solid --var(--text-color);
33
+ font-family: "Courier New", Courier, monospace;
34
+ }
35
+
36
+ @media (prefers-color-scheme: light) {
37
+ :root {
38
+ --text-color: #2f2f2f;
39
+ --primary-color: #f5f5dc;
40
+ --background-color: #f5f5dc;
41
+ }
42
+ .light-mode-switch {
43
+ display: none;
44
+ }
45
+ }
46
+
47
+ body,
48
+ #root {
49
+ background-color: var(--mid-color);
50
+ margin: 0;
51
+ }
Binary file
@@ -0,0 +1,9 @@
1
+ # Crawl directives for akinevz.com.
2
+ # Full syntax: https://developers.google.com/search/docs/advanced/robots/create-robots-txt
3
+
4
+ # Keep the canonical site indexable while reducing duplicate query-string URLs.
5
+ User-agent: *
6
+ Allow: /
7
+ Disallow: /*?*
8
+
9
+ Sitemap: https://akinevz.com/sitemap.xml
@@ -0,0 +1,57 @@
1
+ {
2
+ "source": "https://soundcloud.com/akinevz",
3
+ "generatedAt": "2026-07-03T02:00:43.893Z",
4
+ "trackCount": 10,
5
+ "tracks": [
6
+ {
7
+ "path": "/akinevz/hope-kine-reeq",
8
+ "title": "hope-kine-reeq",
9
+ "url": "https://soundcloud.com/akinevz/hope-kine-reeq"
10
+ },
11
+ {
12
+ "path": "/akinevz/try-try-try",
13
+ "title": "try-try-try",
14
+ "url": "https://soundcloud.com/akinevz/try-try-try"
15
+ },
16
+ {
17
+ "path": "/akinevz/space-space-space",
18
+ "title": "space-space-space",
19
+ "url": "https://soundcloud.com/akinevz/space-space-space"
20
+ },
21
+ {
22
+ "path": "/akinevz/late-late-late",
23
+ "title": "late-late-late",
24
+ "url": "https://soundcloud.com/akinevz/late-late-late"
25
+ },
26
+ {
27
+ "path": "/akinevz/repeating-static-noise",
28
+ "title": "repeating-static-noise",
29
+ "url": "https://soundcloud.com/akinevz/repeating-static-noise"
30
+ },
31
+ {
32
+ "path": "/akinevz/door-jamming-button",
33
+ "title": "door-jamming-button",
34
+ "url": "https://soundcloud.com/akinevz/door-jamming-button"
35
+ },
36
+ {
37
+ "path": "/akinevz/resummoning-fried-beans",
38
+ "title": "resummoning-fried-beans",
39
+ "url": "https://soundcloud.com/akinevz/resummoning-fried-beans"
40
+ },
41
+ {
42
+ "path": "/akinevz/calculated-reception",
43
+ "title": "calculated-reception",
44
+ "url": "https://soundcloud.com/akinevz/calculated-reception"
45
+ },
46
+ {
47
+ "path": "/akinevz/out-of-breath-loop",
48
+ "title": "out-of-breath-loop",
49
+ "url": "https://soundcloud.com/akinevz/out-of-breath-loop"
50
+ },
51
+ {
52
+ "path": "/akinevz/microtonal-vudoo",
53
+ "title": "microtonal-vudoo",
54
+ "url": "https://soundcloud.com/akinevz/microtonal-vudoo"
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" role="img" aria-label="Loading">
2
+ <defs>
3
+ <linearGradient id="spinner-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
4
+ <stop offset="0%" stop-color="#0a246a" stop-opacity="1" />
5
+ <stop offset="100%" stop-color="#a6caf0" stop-opacity="0.2" />
6
+ </linearGradient>
7
+ </defs>
8
+ <circle cx="24" cy="24" r="18" fill="none" stroke="#d9d9d9" stroke-width="6" />
9
+ <path d="M24 6a18 18 0 0 1 18 18" fill="none" stroke="url(#spinner-gradient)" stroke-width="6" stroke-linecap="round">
10
+ <animateTransform attributeName="transform" type="rotate" from="0 24 24" to="360 24 24" dur="0.9s" repeatCount="indefinite" />
11
+ </path>
12
+ </svg>
Binary file
Binary file
package/render.yaml ADDED
@@ -0,0 +1,5 @@
1
+ services:
2
+ - type: web
3
+ name: your-astro-site
4
+ buildCommand: git submodule update --init --recursive && npm install && npm run build
5
+ staticPublishPath: ./dist
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ // Verifies that the blog-posts branch CDN assets are reachable.
3
+ // music-links.json now lives on the blog-posts branch and is fetched
4
+ // at runtime by MusicContent.tsx — no local worktree is required.
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const MUSIC_LINKS_URL =
8
+ "https://raw.githubusercontent.com/akinevz2/frontend/blog-posts/music-links.json";
9
+
10
+ try {
11
+ const response = await fetch(MUSIC_LINKS_URL, { method: "HEAD" });
12
+ if (!response.ok) {
13
+ process.stderr.write(
14
+ `blog-posts CDN asset unreachable: ${MUSIC_LINKS_URL} returned HTTP ${response.status}\n`,
15
+ );
16
+ process.exit(1);
17
+ }
18
+ process.stdout.write(`blog-posts CDN OK: ${MUSIC_LINKS_URL}\n`);
19
+ } catch (error) {
20
+ process.stderr.write(
21
+ `blog-posts CDN check failed: ${error instanceof Error ? error.message : String(error)}\n`,
22
+ );
23
+ process.exit(1);
24
+ }
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { spawn } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { dirname, resolve } from "node:path";
6
+
7
+ const USER_PATH_PREFIX = "/akinevz/";
8
+ const SOURCE_URL = "https://soundcloud.com/akinevz";
9
+ const RESERVED_PROFILE_ROUTES = new Set([
10
+ "likes",
11
+ "sets",
12
+ "tracks",
13
+ "comments",
14
+ "reposts",
15
+ "popular-tracks",
16
+ ]);
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
20
+ const outputFile = resolve(__dirname, "../public/soundcloud.json");
21
+ const cacheDir = resolve(__dirname, "../.cache");
22
+ const cachedArtistPage = resolve(cacheDir, "soundcloud-artist.html");
23
+
24
+ const runCommand = ({ command, args, cwd }) =>
25
+ new Promise((resolvePromise, reject) => {
26
+ const child = spawn(command, args, {
27
+ cwd,
28
+ stdio: ["ignore", "pipe", "pipe"],
29
+ });
30
+
31
+ let stdout = "";
32
+ let stderr = "";
33
+
34
+ child.stdout.on("data", (chunk) => {
35
+ stdout += chunk.toString();
36
+ });
37
+
38
+ child.stderr.on("data", (chunk) => {
39
+ stderr += chunk.toString();
40
+ });
41
+
42
+ child.on("error", reject);
43
+ child.on("close", (exitCode) => {
44
+ resolvePromise({ exitCode, stdout, stderr });
45
+ });
46
+ });
47
+
48
+ export const parseOutput = (raw) => {
49
+ const trimmed = raw.trim();
50
+ const start = trimmed.indexOf("[");
51
+
52
+ if (start === -1) {
53
+ throw new Error("pagerts output does not contain JSON payload");
54
+ }
55
+
56
+ let inString = false;
57
+ let isEscaped = false;
58
+ let depth = 0;
59
+ let end = -1;
60
+
61
+ for (let i = start; i < trimmed.length; i += 1) {
62
+ const char = trimmed[i];
63
+
64
+ if (inString) {
65
+ if (isEscaped) {
66
+ isEscaped = false;
67
+ continue;
68
+ }
69
+
70
+ if (char === "\\") {
71
+ isEscaped = true;
72
+ continue;
73
+ }
74
+
75
+ if (char === '"') {
76
+ inString = false;
77
+ }
78
+
79
+ continue;
80
+ }
81
+
82
+ if (char === '"') {
83
+ inString = true;
84
+ continue;
85
+ }
86
+
87
+ if (char === "[") {
88
+ depth += 1;
89
+ continue;
90
+ }
91
+
92
+ if (char === "]") {
93
+ depth -= 1;
94
+ if (depth === 0) {
95
+ end = i;
96
+ break;
97
+ }
98
+ }
99
+ }
100
+
101
+ if (end === -1) {
102
+ throw new Error("pagerts output contains incomplete JSON payload");
103
+ }
104
+
105
+ const payload = JSON.parse(trimmed.slice(start, end + 1));
106
+ if (!Array.isArray(payload) || payload.length === 0) {
107
+ throw new Error("pagerts returned an empty payload");
108
+ }
109
+
110
+ return payload;
111
+ };
112
+
113
+ export const isTrackPath = (value) =>
114
+ typeof value === "string" &&
115
+ value.startsWith(USER_PATH_PREFIX) &&
116
+ value !== "/akinevz" &&
117
+ !value.startsWith("/akinevz/sets/") &&
118
+ !RESERVED_PROFILE_ROUTES.has(value.split("/").filter(Boolean).at(-1) ?? "") &&
119
+ /^\/akinevz\/[^/]+$/.test(value);
120
+
121
+ export const titleFromPath = (value) => value.split("/").filter(Boolean).at(-1) ?? value;
122
+
123
+ export const buildTracks = (resources) => {
124
+ const seen = new Set();
125
+
126
+ return resources
127
+ .filter((resource) => isTrackPath(resource?.link?.value))
128
+ .map((resource) => resource.link.value)
129
+ .filter((value) => {
130
+ if (seen.has(value)) {
131
+ return false;
132
+ }
133
+ seen.add(value);
134
+ return true;
135
+ })
136
+ .map((path) => ({
137
+ path,
138
+ title: titleFromPath(path),
139
+ url: `https://soundcloud.com${path}`,
140
+ }));
141
+ };
142
+
143
+ export const run = async () => {
144
+ const projectRoot = resolve(__dirname, "..");
145
+ await mkdir(cacheDir, { recursive: true });
146
+
147
+ const curlResult = await runCommand({
148
+ command: "curl",
149
+ args: ["-fLsS", SOURCE_URL, "-o", cachedArtistPage],
150
+ cwd: projectRoot,
151
+ });
152
+
153
+ if (curlResult.exitCode !== 0) {
154
+ throw new Error(
155
+ `curl failed with exit code ${curlResult.exitCode}: ${curlResult.stderr.trim()}`,
156
+ );
157
+ }
158
+
159
+ const pagertsResult = await runCommand({
160
+ command: "npx",
161
+ args: ["--yes", "pagerts@latest", cachedArtistPage],
162
+ cwd: projectRoot,
163
+ });
164
+
165
+ if (pagertsResult.exitCode !== 0) {
166
+ throw new Error(
167
+ `pagerts failed with exit code ${pagertsResult.exitCode}: ${pagertsResult.stderr.trim()}`,
168
+ );
169
+ }
170
+
171
+ const payload = parseOutput(pagertsResult.stdout);
172
+ const resources = payload[0]?.resources;
173
+
174
+ if (!Array.isArray(resources)) {
175
+ throw new Error("pagerts payload is missing resources");
176
+ }
177
+
178
+ const tracks = buildTracks(resources);
179
+
180
+ const result = {
181
+ source: SOURCE_URL,
182
+ generatedAt: new Date().toISOString(),
183
+ trackCount: tracks.length,
184
+ tracks,
185
+ };
186
+
187
+ await writeFile(outputFile, `${JSON.stringify(result, null, 2)}\n`, "utf8");
188
+ process.stdout.write(
189
+ `Fetched artist page to ${cachedArtistPage} and wrote ${tracks.length} tracks to ${outputFile}\n`,
190
+ );
191
+ };
192
+
193
+ if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
194
+ run().catch((error) => {
195
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
196
+ process.exit(1);
197
+ });
198
+ }
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, resolve } from "node:path";
5
+ import { unlinkSync, rmdirSync, existsSync } from "node:fs";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ /**
11
+ * Execute a callback within a git worktree
12
+ * @param {string} dir - Directory path for the worktree
13
+ * @param {string} branch - Branch name to checkout
14
+ * @param {Function} callback - Function to execute within the worktree
15
+ */
16
+ export async function withGitWorktree(dir, branch, callback) {
17
+ const worktreeDir = resolve(__dirname, "..", dir);
18
+
19
+ console.log(`🌳 Creating git worktree at ${worktreeDir} for branch ${branch}...`);
20
+
21
+ // Create worktree
22
+ const gitWorktreeAdd = spawn("git", ["worktree", "add", worktreeDir, branch], {
23
+ stdio: "inherit"
24
+ });
25
+
26
+ await new Promise((resolve, reject) => {
27
+ gitWorktreeAdd.on("close", (code) => {
28
+ if (code !== 0) {
29
+ reject(new Error("Git worktree add failed"));
30
+ return;
31
+ }
32
+ resolve();
33
+ });
34
+ gitWorktreeAdd.on("error", reject);
35
+ });
36
+
37
+ try {
38
+ // Execute callback
39
+ await callback(worktreeDir);
40
+ } finally {
41
+ // Before removing the work tree, commit and push the added content
42
+
43
+ // Step 3: Commit and push to blog-posts branch
44
+ console.log("📤 Committing and pushing to blog-posts branch...");
45
+ const gitAdd = spawn("git", ["add", "-A"], {
46
+ cwd: worktreeDir,
47
+ stdio: "inherit"
48
+ });
49
+
50
+ await new Promise((resolve, reject) => {
51
+ gitAdd.on("close", (code) => {
52
+ if (code !== 0) {
53
+ reject(new Error("Git add failed"));
54
+ return;
55
+ }
56
+ resolve();
57
+ });
58
+ gitAdd.on("error", reject);
59
+ });
60
+
61
+ const gitCommit = spawn("git", ["commit"], {
62
+ cwd: worktreeDir,
63
+ stdio: "inherit"
64
+ });
65
+
66
+ await new Promise((resolve, reject) => {
67
+ gitCommit.on("close", (code) => {
68
+ if (code !== 0) {
69
+ reject(new Error("Git commit failed"));
70
+ return;
71
+ }
72
+ resolve();
73
+ });
74
+ gitCommit.on("error", reject);
75
+ });
76
+
77
+ const gitPush = spawn("git", ["push", "origin", "blog-posts"], {
78
+ cwd: worktreeDir,
79
+ stdio: "inherit"
80
+ });
81
+
82
+ await new Promise((resolve, reject) => {
83
+ gitPush.on("close", (code) => {
84
+ if (code !== 0) {
85
+ reject(new Error("Git push failed"));
86
+ return;
87
+ }
88
+ resolve();
89
+ });
90
+ gitPush.on("error", reject);
91
+ });
92
+
93
+ console.log(`🗑️ Removing git worktree at ${worktreeDir}...`);
94
+ try {
95
+ const gitWorktreeRemove = spawn("git", ["worktree", "remove", worktreeDir], {
96
+ stdio: "inherit"
97
+ });
98
+
99
+ await new Promise((resolve, reject) => {
100
+ gitWorktreeRemove.on("close", (code) => {
101
+ if (code !== 0) {
102
+ reject(new Error("Git worktree remove failed"));
103
+ return;
104
+ }
105
+ resolve();
106
+ });
107
+ gitWorktreeRemove.on("error", reject);
108
+ });
109
+
110
+ console.log("✅ Worktree removed successfully");
111
+ } catch (error) {
112
+ console.error("⚠️ Failed to remove worktree, please remove manually:", worktreeDir);
113
+ throw error;
114
+ }
115
+ }
116
+ }
117
+
118
+ // Example usage:
119
+ // await withGitWorktree(".worktree-blog", "blog-posts", async (worktreeDir) => {
120
+ // // Your code here
121
+ // console.log("Working in worktree:", worktreeDir);
122
+ // });