@prmichaelsen/acp-visualizer 0.5.2 → 0.5.3
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/package.json +1 -1
- package/src/lib/file-watcher.ts +6 -1
- package/src/lib/useAutoRefresh.ts +3 -0
- package/src/routes/api/watch.ts +23 -17
- package/src/services/github.service.ts +56 -32
- package/vite.config.ts +4 -4
package/package.json
CHANGED
package/src/lib/file-watcher.ts
CHANGED
|
@@ -15,6 +15,11 @@ export async function getFileWatcher() {
|
|
|
15
15
|
const clients = new Set<Controller>()
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
+
// existsSync will throw on Workers where fs is stubbed
|
|
19
|
+
const { existsSync } = await import('fs')
|
|
20
|
+
if (!existsSync(filePath)) {
|
|
21
|
+
throw new Error(`File not found: ${filePath}`)
|
|
22
|
+
}
|
|
18
23
|
watch(filePath, (eventType) => {
|
|
19
24
|
if (eventType === 'change') {
|
|
20
25
|
for (const controller of clients) {
|
|
@@ -27,7 +32,7 @@ export async function getFileWatcher() {
|
|
|
27
32
|
}
|
|
28
33
|
})
|
|
29
34
|
} catch (err) {
|
|
30
|
-
|
|
35
|
+
throw new Error(`[FileWatcher] Cannot watch: ${err}`)
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
watcher = {
|
|
@@ -5,6 +5,9 @@ export function useAutoRefresh() {
|
|
|
5
5
|
const router = useRouter()
|
|
6
6
|
|
|
7
7
|
useEffect(() => {
|
|
8
|
+
// Skip file-watcher SSE in hosted mode — no local filesystem to watch
|
|
9
|
+
if (import.meta.env.VITE_HOSTED) return
|
|
10
|
+
|
|
8
11
|
const eventSource = new EventSource('/api/watch')
|
|
9
12
|
|
|
10
13
|
eventSource.onmessage = () => {
|
package/src/routes/api/watch.ts
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import { getFileWatcher } from '../../lib/file-watcher'
|
|
3
2
|
|
|
4
3
|
export const Route = createFileRoute('/api/watch')({
|
|
5
4
|
server: {
|
|
6
5
|
handlers: {
|
|
7
6
|
GET: async () => {
|
|
8
|
-
|
|
7
|
+
// In hosted mode (Cloudflare Workers), there's no filesystem to watch.
|
|
8
|
+
// Return 204 immediately instead of opening a stream that hangs forever.
|
|
9
|
+
try {
|
|
10
|
+
const { getFileWatcher } = await import('../../lib/file-watcher')
|
|
11
|
+
const watcher = await getFileWatcher()
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const stream = new ReadableStream({
|
|
14
|
+
start(controller) {
|
|
15
|
+
watcher.addClient(controller)
|
|
16
|
+
},
|
|
17
|
+
cancel(controller) {
|
|
18
|
+
watcher.removeClient(controller)
|
|
19
|
+
},
|
|
20
|
+
})
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
return new Response(stream, {
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'text/event-stream',
|
|
25
|
+
'Cache-Control': 'no-cache',
|
|
26
|
+
'Connection': 'keep-alive',
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
} catch {
|
|
30
|
+
return new Response(null, { status: 204 })
|
|
31
|
+
}
|
|
26
32
|
},
|
|
27
33
|
},
|
|
28
34
|
},
|
|
@@ -5,47 +5,71 @@ export type GitHubResult =
|
|
|
5
5
|
| { ok: true; data: ProgressData }
|
|
6
6
|
| { ok: false; error: string; message: string }
|
|
7
7
|
|
|
8
|
+
async function fetchBranch(
|
|
9
|
+
parseProgressYaml: (raw: string) => ProgressData,
|
|
10
|
+
owner: string,
|
|
11
|
+
repo: string,
|
|
12
|
+
branch: string,
|
|
13
|
+
headers: Record<string, string>,
|
|
14
|
+
): Promise<GitHubResult> {
|
|
15
|
+
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/agent/progress.yaml`
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(url, { headers })
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
if (response.status === 404) {
|
|
20
|
+
return { ok: false, error: 'NOT_FOUND', message: `No progress.yaml found at ${owner}/${repo} (branch: ${branch})` }
|
|
21
|
+
}
|
|
22
|
+
return { ok: false, error: 'FETCH_ERROR', message: `GitHub returned ${response.status}: ${response.statusText}` }
|
|
23
|
+
}
|
|
24
|
+
const raw = await response.text()
|
|
25
|
+
const data = parseProgressYaml(raw)
|
|
26
|
+
return { ok: true, data }
|
|
27
|
+
} catch (err) {
|
|
28
|
+
return { ok: false, error: 'NETWORK_ERROR', message: err instanceof Error ? err.message : 'Failed to fetch from GitHub' }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
8
32
|
export const fetchGitHubProgress = createServerFn({ method: 'GET' })
|
|
9
33
|
.inputValidator((input: { owner: string; repo: string; branch?: string; token?: string }) => input)
|
|
10
34
|
.handler(async ({ data: input }): Promise<GitHubResult> => {
|
|
11
35
|
const { parseProgressYaml } = await import('../lib/yaml-loader')
|
|
12
36
|
|
|
13
|
-
const
|
|
14
|
-
|
|
37
|
+
const fetchHeaders: Record<string, string> = { 'Accept': 'text/plain' }
|
|
38
|
+
if (input.token) {
|
|
39
|
+
fetchHeaders['Authorization'] = `token ${input.token}`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If branch specified, use it directly
|
|
43
|
+
if (input.branch) {
|
|
44
|
+
return fetchBranch(parseProgressYaml, input.owner, input.repo, input.branch, fetchHeaders)
|
|
45
|
+
}
|
|
15
46
|
|
|
47
|
+
// Try GitHub API for default branch, fall back to trying main → mainline → master
|
|
48
|
+
let detectedBranch: string | null = null
|
|
16
49
|
try {
|
|
17
|
-
const
|
|
18
|
-
'
|
|
19
|
-
}
|
|
20
|
-
if (
|
|
21
|
-
|
|
50
|
+
const metaRes = await fetch(`https://api.github.com/repos/${input.owner}/${input.repo}`, {
|
|
51
|
+
headers: { 'User-Agent': 'acp-visualizer', ...(input.token ? { Authorization: `token ${input.token}` } : {}) },
|
|
52
|
+
})
|
|
53
|
+
if (metaRes.ok) {
|
|
54
|
+
const meta = await metaRes.json() as { default_branch?: string }
|
|
55
|
+
detectedBranch = meta.default_branch || null
|
|
22
56
|
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Rate limited or network error — fall through to branch probing
|
|
59
|
+
}
|
|
23
60
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (response.status === 404) {
|
|
28
|
-
return {
|
|
29
|
-
ok: false,
|
|
30
|
-
error: 'NOT_FOUND',
|
|
31
|
-
message: `No progress.yaml found at ${input.owner}/${input.repo} (branch: ${branch})`,
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
ok: false,
|
|
36
|
-
error: 'FETCH_ERROR',
|
|
37
|
-
message: `GitHub returned ${response.status}: ${response.statusText}`,
|
|
38
|
-
}
|
|
39
|
-
}
|
|
61
|
+
const branches = detectedBranch
|
|
62
|
+
? [detectedBranch]
|
|
63
|
+
: ['main', 'mainline', 'master']
|
|
40
64
|
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
65
|
+
for (const branch of branches) {
|
|
66
|
+
const result = await fetchBranch(parseProgressYaml, input.owner, input.repo, branch, fetchHeaders)
|
|
67
|
+
if (result.ok || result.error !== 'NOT_FOUND') return result
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: 'NOT_FOUND',
|
|
73
|
+
message: `No progress.yaml found at ${input.owner}/${input.repo} (tried branches: ${branches.join(', ')})`,
|
|
50
74
|
}
|
|
51
75
|
})
|
package/vite.config.ts
CHANGED
|
@@ -7,10 +7,10 @@ import { cloudflare } from '@cloudflare/vite-plugin'
|
|
|
7
7
|
|
|
8
8
|
export default defineConfig(({ command }) => ({
|
|
9
9
|
plugins: [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// Only use Cloudflare plugin for production builds — local dev uses Node.js SSR
|
|
11
|
+
...(command === 'build'
|
|
12
|
+
? [cloudflare({ viteEnvironment: { name: 'ssr' } })]
|
|
13
|
+
: []),
|
|
14
14
|
viteTsConfigPaths({
|
|
15
15
|
projects: ['./tsconfig.json'],
|
|
16
16
|
}),
|