@f0rbit/overview 0.2.0 → 0.2.2
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/bin/overview +1 -1
- package/dist/overview.js +11898 -0
- package/package.json +10 -13
- package/bunfig.toml +0 -7
- package/packages/core/__tests__/concurrency.test.ts +0 -111
- package/packages/core/__tests__/helpers.ts +0 -60
- package/packages/core/__tests__/integration/git-status.test.ts +0 -62
- package/packages/core/__tests__/integration/scanner.test.ts +0 -140
- package/packages/core/__tests__/ocn.test.ts +0 -164
- package/packages/core/package.json +0 -13
- package/packages/core/src/cache.ts +0 -31
- package/packages/core/src/concurrency.ts +0 -44
- package/packages/core/src/devpad.ts +0 -61
- package/packages/core/src/git-graph.ts +0 -54
- package/packages/core/src/git-stats.ts +0 -201
- package/packages/core/src/git-status.ts +0 -316
- package/packages/core/src/github.ts +0 -286
- package/packages/core/src/index.ts +0 -58
- package/packages/core/src/ocn.ts +0 -74
- package/packages/core/src/scanner.ts +0 -118
- package/packages/core/src/types.ts +0 -199
- package/packages/core/src/watcher.ts +0 -128
- package/packages/core/src/worktree.ts +0 -80
- package/packages/core/tsconfig.json +0 -5
- package/packages/render/bunfig.toml +0 -8
- package/packages/render/jsx-runtime.d.ts +0 -3
- package/packages/render/package.json +0 -18
- package/packages/render/src/components/__tests__/scrollbox-height.test.tsx +0 -780
- package/packages/render/src/components/__tests__/widget-container.integration.test.tsx +0 -306
- package/packages/render/src/components/git-graph.tsx +0 -127
- package/packages/render/src/components/help-overlay.tsx +0 -108
- package/packages/render/src/components/index.ts +0 -7
- package/packages/render/src/components/repo-list.tsx +0 -127
- package/packages/render/src/components/stats-panel.tsx +0 -116
- package/packages/render/src/components/status-badge.tsx +0 -70
- package/packages/render/src/components/status-bar.tsx +0 -56
- package/packages/render/src/components/widget-container.tsx +0 -286
- package/packages/render/src/components/widgets/__tests__/widget-rendering.test.tsx +0 -326
- package/packages/render/src/components/widgets/branch-list.tsx +0 -93
- package/packages/render/src/components/widgets/commit-activity.tsx +0 -112
- package/packages/render/src/components/widgets/devpad-milestones.tsx +0 -88
- package/packages/render/src/components/widgets/devpad-tasks.tsx +0 -81
- package/packages/render/src/components/widgets/file-changes.tsx +0 -78
- package/packages/render/src/components/widgets/git-status.tsx +0 -125
- package/packages/render/src/components/widgets/github-ci.tsx +0 -98
- package/packages/render/src/components/widgets/github-issues.tsx +0 -101
- package/packages/render/src/components/widgets/github-prs.tsx +0 -119
- package/packages/render/src/components/widgets/github-release.tsx +0 -73
- package/packages/render/src/components/widgets/index.ts +0 -12
- package/packages/render/src/components/widgets/recent-commits.tsx +0 -64
- package/packages/render/src/components/widgets/registry.ts +0 -23
- package/packages/render/src/components/widgets/repo-meta.tsx +0 -80
- package/packages/render/src/config/index.ts +0 -104
- package/packages/render/src/lib/__tests__/fetch-context.test.ts +0 -200
- package/packages/render/src/lib/__tests__/widget-grid.test.ts +0 -665
- package/packages/render/src/lib/actions.ts +0 -68
- package/packages/render/src/lib/fetch-context.ts +0 -102
- package/packages/render/src/lib/filter.ts +0 -94
- package/packages/render/src/lib/format.ts +0 -36
- package/packages/render/src/lib/use-devpad.ts +0 -167
- package/packages/render/src/lib/use-github.ts +0 -75
- package/packages/render/src/lib/widget-grid.ts +0 -204
- package/packages/render/src/lib/widget-state.ts +0 -96
- package/packages/render/src/overview.tsx +0 -16
- package/packages/render/src/screens/index.ts +0 -1
- package/packages/render/src/screens/main-screen.tsx +0 -410
- package/packages/render/src/theme/index.ts +0 -37
- package/packages/render/tsconfig.json +0 -9
- package/tsconfig.json +0 -23
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
// Git file change
|
|
2
|
-
export interface GitFileChange {
|
|
3
|
-
path: string;
|
|
4
|
-
status: "modified" | "added" | "deleted" | "renamed" | "copied" | "untracked" | "ignored" | "conflicted";
|
|
5
|
-
staged: boolean;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// Branch info
|
|
9
|
-
export interface BranchInfo {
|
|
10
|
-
name: string;
|
|
11
|
-
is_current: boolean;
|
|
12
|
-
upstream: string | null;
|
|
13
|
-
ahead: number;
|
|
14
|
-
behind: number;
|
|
15
|
-
last_commit_time: number; // unix timestamp
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Stash entry
|
|
19
|
-
export interface StashEntry {
|
|
20
|
-
index: number;
|
|
21
|
-
message: string;
|
|
22
|
-
date: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Recent commit
|
|
26
|
-
export interface RecentCommit {
|
|
27
|
-
hash: string;
|
|
28
|
-
message: string;
|
|
29
|
-
author: string;
|
|
30
|
-
time: number; // unix timestamp
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// OpenCode session status (from ocn)
|
|
34
|
-
export type OcnSessionStatus = "idle" | "busy" | "prompting" | "error";
|
|
35
|
-
|
|
36
|
-
export interface OcnStatus {
|
|
37
|
-
pid: number;
|
|
38
|
-
status: OcnSessionStatus;
|
|
39
|
-
session_id: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Health status
|
|
43
|
-
export type HealthStatus = "clean" | "dirty" | "ahead" | "behind" | "diverged" | "conflict";
|
|
44
|
-
|
|
45
|
-
// Full repo status
|
|
46
|
-
export interface RepoStatus {
|
|
47
|
-
// Identity
|
|
48
|
-
path: string;
|
|
49
|
-
name: string;
|
|
50
|
-
display_path: string; // relative to scan root
|
|
51
|
-
|
|
52
|
-
// Current state
|
|
53
|
-
current_branch: string;
|
|
54
|
-
head_commit: string;
|
|
55
|
-
head_message: string;
|
|
56
|
-
head_time: number;
|
|
57
|
-
|
|
58
|
-
// Tracking
|
|
59
|
-
remote_url: string | null;
|
|
60
|
-
ahead: number;
|
|
61
|
-
behind: number;
|
|
62
|
-
|
|
63
|
-
// Working tree
|
|
64
|
-
modified_count: number;
|
|
65
|
-
staged_count: number;
|
|
66
|
-
untracked_count: number;
|
|
67
|
-
conflict_count: number;
|
|
68
|
-
changes: GitFileChange[];
|
|
69
|
-
|
|
70
|
-
// Stash
|
|
71
|
-
stash_count: number;
|
|
72
|
-
stashes: StashEntry[];
|
|
73
|
-
|
|
74
|
-
// Branches
|
|
75
|
-
branches: BranchInfo[];
|
|
76
|
-
local_branch_count: number;
|
|
77
|
-
remote_branch_count: number;
|
|
78
|
-
|
|
79
|
-
// Metadata
|
|
80
|
-
tags: string[];
|
|
81
|
-
total_commits: number;
|
|
82
|
-
repo_size_bytes: number;
|
|
83
|
-
contributor_count: number;
|
|
84
|
-
|
|
85
|
-
// Recent activity
|
|
86
|
-
recent_commits: RecentCommit[];
|
|
87
|
-
|
|
88
|
-
// Commit activity (populated by fetchDetails, not initial scan)
|
|
89
|
-
commit_activity: { daily_counts: number[]; total_this_week: number; total_last_week: number } | null;
|
|
90
|
-
|
|
91
|
-
// OpenCode session status (from ocn)
|
|
92
|
-
ocn_status: OcnStatus | null;
|
|
93
|
-
|
|
94
|
-
// Derived
|
|
95
|
-
is_clean: boolean;
|
|
96
|
-
health: HealthStatus;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Worktree info
|
|
100
|
-
export interface WorktreeInfo {
|
|
101
|
-
path: string;
|
|
102
|
-
branch: string;
|
|
103
|
-
head: string;
|
|
104
|
-
is_bare: boolean;
|
|
105
|
-
is_main: boolean;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Git graph output
|
|
109
|
-
export interface GitGraphOutput {
|
|
110
|
-
lines: string[];
|
|
111
|
-
total_lines: number;
|
|
112
|
-
repo_path: string;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Repo tree node
|
|
116
|
-
export interface RepoNode {
|
|
117
|
-
name: string;
|
|
118
|
-
path: string;
|
|
119
|
-
type: "directory" | "repo" | "worktree";
|
|
120
|
-
status: RepoStatus | null;
|
|
121
|
-
worktrees: WorktreeInfo[];
|
|
122
|
-
children: RepoNode[];
|
|
123
|
-
depth: number;
|
|
124
|
-
expanded: boolean;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Widget system
|
|
128
|
-
export type WidgetId =
|
|
129
|
-
| "git-status"
|
|
130
|
-
| "recent-commits"
|
|
131
|
-
| "branch-list"
|
|
132
|
-
| "github-prs"
|
|
133
|
-
| "github-issues"
|
|
134
|
-
| "github-ci"
|
|
135
|
-
| "devpad-tasks"
|
|
136
|
-
| "devpad-milestones"
|
|
137
|
-
| "repo-meta"
|
|
138
|
-
| "file-changes"
|
|
139
|
-
| "commit-activity"
|
|
140
|
-
| "github-release";
|
|
141
|
-
|
|
142
|
-
export interface WidgetConfig {
|
|
143
|
-
id: WidgetId;
|
|
144
|
-
enabled: boolean;
|
|
145
|
-
priority: number;
|
|
146
|
-
collapsed: boolean;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export type WidgetSpan = "full" | "half" | "third" | "auto";
|
|
150
|
-
|
|
151
|
-
export interface WidgetSizeHint {
|
|
152
|
-
span: WidgetSpan;
|
|
153
|
-
min_height: number;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export interface WidgetRenderProps {
|
|
157
|
-
width: number;
|
|
158
|
-
focused: boolean;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Config
|
|
162
|
-
export interface OverviewConfig {
|
|
163
|
-
scan_dirs: string[];
|
|
164
|
-
depth: number;
|
|
165
|
-
refresh_interval: number;
|
|
166
|
-
layout: {
|
|
167
|
-
left_width_pct: number;
|
|
168
|
-
graph_height_pct: number;
|
|
169
|
-
};
|
|
170
|
-
sort: "name" | "status" | "last-commit";
|
|
171
|
-
filter: "all" | "dirty" | "clean" | "ahead" | "behind";
|
|
172
|
-
ignore: string[];
|
|
173
|
-
actions: {
|
|
174
|
-
ggi: string;
|
|
175
|
-
editor: string;
|
|
176
|
-
sessionizer: string | null;
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Default config factory
|
|
181
|
-
export function defaultConfig(): OverviewConfig {
|
|
182
|
-
return {
|
|
183
|
-
scan_dirs: ["~/dev"],
|
|
184
|
-
depth: 3,
|
|
185
|
-
refresh_interval: 30,
|
|
186
|
-
layout: {
|
|
187
|
-
left_width_pct: 35,
|
|
188
|
-
graph_height_pct: 45,
|
|
189
|
-
},
|
|
190
|
-
sort: "name",
|
|
191
|
-
filter: "all",
|
|
192
|
-
ignore: ["node_modules", ".git"],
|
|
193
|
-
actions: {
|
|
194
|
-
ggi: "ggi",
|
|
195
|
-
editor: "$EDITOR",
|
|
196
|
-
sessionizer: null,
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { watch, type FSWatcher } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { readFile, lstat } from "node:fs/promises";
|
|
4
|
-
|
|
5
|
-
export interface WatcherOptions {
|
|
6
|
-
/** Debounce interval in ms (default 500) */
|
|
7
|
-
debounce_ms?: number;
|
|
8
|
-
/** Callback when a repo changes */
|
|
9
|
-
on_change: (repoPath: string) => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface RepoWatcher {
|
|
13
|
-
/** Start watching a list of repo paths */
|
|
14
|
-
watch(repoPaths: string[]): void;
|
|
15
|
-
/** Stop watching all repos */
|
|
16
|
-
close(): void;
|
|
17
|
-
/** Add a single repo to watch */
|
|
18
|
-
add(repoPath: string): void;
|
|
19
|
-
/** Remove a single repo from watching */
|
|
20
|
-
remove(repoPath: string): void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function resolveGitDir(repo_path: string): Promise<string | null> {
|
|
24
|
-
const git_path = join(repo_path, ".git");
|
|
25
|
-
try {
|
|
26
|
-
const stats = await lstat(git_path);
|
|
27
|
-
if (stats.isDirectory()) return git_path;
|
|
28
|
-
if (stats.isFile()) {
|
|
29
|
-
const content = await readFile(git_path, "utf-8");
|
|
30
|
-
const match = content.match(/^gitdir:\s*(.+)$/m);
|
|
31
|
-
const target = match?.[1]?.trim();
|
|
32
|
-
if (!target) return null;
|
|
33
|
-
return target.startsWith("/") ? target : join(repo_path, target);
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
36
|
-
} catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function tryWatch(
|
|
42
|
-
target: string,
|
|
43
|
-
options: { recursive?: boolean },
|
|
44
|
-
callback: () => void,
|
|
45
|
-
): FSWatcher | null {
|
|
46
|
-
try {
|
|
47
|
-
const watcher = watch(target, options, callback);
|
|
48
|
-
watcher.on("error", () => {});
|
|
49
|
-
return watcher;
|
|
50
|
-
} catch {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function createRepoWatcher(options: WatcherOptions): RepoWatcher {
|
|
56
|
-
const debounce_ms = options.debounce_ms ?? 500;
|
|
57
|
-
const watchers = new Map<string, FSWatcher[]>();
|
|
58
|
-
const timers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
59
|
-
|
|
60
|
-
function debouncedChange(repo_path: string) {
|
|
61
|
-
const existing = timers.get(repo_path);
|
|
62
|
-
if (existing) clearTimeout(existing);
|
|
63
|
-
timers.set(
|
|
64
|
-
repo_path,
|
|
65
|
-
setTimeout(() => {
|
|
66
|
-
timers.delete(repo_path);
|
|
67
|
-
options.on_change(repo_path);
|
|
68
|
-
}, debounce_ms),
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function addRepo(repo_path: string) {
|
|
73
|
-
if (watchers.has(repo_path)) return;
|
|
74
|
-
|
|
75
|
-
const git_dir = await resolveGitDir(repo_path);
|
|
76
|
-
if (!git_dir) {
|
|
77
|
-
console.warn(`[watcher] skipping ${repo_path}: could not resolve .git directory`);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const repo_watchers: FSWatcher[] = [];
|
|
82
|
-
const on_event = () => debouncedChange(repo_path);
|
|
83
|
-
|
|
84
|
-
const index_watcher = tryWatch(join(git_dir, "index"), {}, on_event);
|
|
85
|
-
if (index_watcher) repo_watchers.push(index_watcher);
|
|
86
|
-
|
|
87
|
-
const refs_watcher = tryWatch(join(git_dir, "refs"), { recursive: true }, on_event);
|
|
88
|
-
if (refs_watcher) repo_watchers.push(refs_watcher);
|
|
89
|
-
|
|
90
|
-
if (repo_watchers.length === 0) {
|
|
91
|
-
console.warn(`[watcher] skipping ${repo_path}: no watchable targets`);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
watchers.set(repo_path, repo_watchers);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function removeRepo(repo_path: string) {
|
|
99
|
-
const repo_watchers = watchers.get(repo_path);
|
|
100
|
-
if (repo_watchers) {
|
|
101
|
-
repo_watchers.forEach((w) => w.close());
|
|
102
|
-
watchers.delete(repo_path);
|
|
103
|
-
}
|
|
104
|
-
const timer = timers.get(repo_path);
|
|
105
|
-
if (timer) {
|
|
106
|
-
clearTimeout(timer);
|
|
107
|
-
timers.delete(repo_path);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
watch(repo_paths: string[]) {
|
|
113
|
-
repo_paths.forEach((p) => addRepo(p));
|
|
114
|
-
},
|
|
115
|
-
close() {
|
|
116
|
-
watchers.forEach((ws) => ws.forEach((w) => w.close()));
|
|
117
|
-
watchers.clear();
|
|
118
|
-
timers.forEach((t) => clearTimeout(t));
|
|
119
|
-
timers.clear();
|
|
120
|
-
},
|
|
121
|
-
add(repo_path: string) {
|
|
122
|
-
addRepo(repo_path);
|
|
123
|
-
},
|
|
124
|
-
remove(repo_path: string) {
|
|
125
|
-
removeRepo(repo_path);
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { ok, err, type Result } from "@f0rbit/corpus";
|
|
2
|
-
import type { WorktreeInfo } from "./types";
|
|
3
|
-
|
|
4
|
-
export type WorktreeError =
|
|
5
|
-
| { kind: "not_a_repo"; path: string }
|
|
6
|
-
| { kind: "worktree_failed"; path: string; cause: string };
|
|
7
|
-
|
|
8
|
-
async function gitCommand(
|
|
9
|
-
args: string[],
|
|
10
|
-
cwd: string,
|
|
11
|
-
): Promise<Result<string, WorktreeError>> {
|
|
12
|
-
const proc = Bun.spawn(["git", ...args], {
|
|
13
|
-
cwd,
|
|
14
|
-
stdout: "pipe",
|
|
15
|
-
stderr: "pipe",
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
await proc.exited;
|
|
19
|
-
|
|
20
|
-
if (proc.exitCode !== 0) {
|
|
21
|
-
const stderr = await new Response(proc.stderr).text();
|
|
22
|
-
const is_not_repo =
|
|
23
|
-
stderr.includes("not a git repository") ||
|
|
24
|
-
stderr.includes("not a git repo");
|
|
25
|
-
if (is_not_repo) {
|
|
26
|
-
return err({ kind: "not_a_repo", path: cwd });
|
|
27
|
-
}
|
|
28
|
-
return err({
|
|
29
|
-
kind: "worktree_failed",
|
|
30
|
-
path: cwd,
|
|
31
|
-
cause: stderr.trim(),
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const stdout = await new Response(proc.stdout).text();
|
|
36
|
-
return ok(stdout);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function parseWorktreeBlock(
|
|
40
|
-
lines: string[],
|
|
41
|
-
is_main: boolean,
|
|
42
|
-
): WorktreeInfo | null {
|
|
43
|
-
const path = lines
|
|
44
|
-
.find((l) => l.startsWith("worktree "))
|
|
45
|
-
?.slice("worktree ".length);
|
|
46
|
-
if (!path) return null;
|
|
47
|
-
|
|
48
|
-
const head_line = lines.find((l) => l.startsWith("HEAD "));
|
|
49
|
-
const head = head_line ? head_line.slice("HEAD ".length, "HEAD ".length + 7) : "0000000";
|
|
50
|
-
|
|
51
|
-
const branch_line = lines.find((l) => l.startsWith("branch "));
|
|
52
|
-
const is_bare = lines.some((l) => l === "bare");
|
|
53
|
-
const branch = branch_line
|
|
54
|
-
? branch_line.slice("branch refs/heads/".length)
|
|
55
|
-
: is_bare
|
|
56
|
-
? "bare"
|
|
57
|
-
: "detached";
|
|
58
|
-
|
|
59
|
-
return { path, branch, head, is_bare, is_main };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function detectWorktrees(
|
|
63
|
-
repoPath: string,
|
|
64
|
-
): Promise<Result<WorktreeInfo[], WorktreeError>> {
|
|
65
|
-
const result = await gitCommand(["worktree", "list", "--porcelain"], repoPath);
|
|
66
|
-
if (!result.ok) return result;
|
|
67
|
-
|
|
68
|
-
const blocks = result.value
|
|
69
|
-
.split("\n\n")
|
|
70
|
-
.map((b) => b.trim())
|
|
71
|
-
.filter((b) => b.length > 0);
|
|
72
|
-
|
|
73
|
-
const worktrees = blocks
|
|
74
|
-
.map((block, i) => parseWorktreeBlock(block.split("\n"), i === 0))
|
|
75
|
-
.filter((w): w is WorktreeInfo => w !== null);
|
|
76
|
-
|
|
77
|
-
if (worktrees.length <= 1) return ok([]);
|
|
78
|
-
|
|
79
|
-
return ok(worktrees);
|
|
80
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@overview/render",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"main": "src/overview.tsx",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "bun run src/overview.tsx",
|
|
7
|
-
"test": "bun test",
|
|
8
|
-
"typecheck": "tsc --noEmit"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"@opentui/core": "^0.1.80",
|
|
12
|
-
"@opentui/solid": "^0.1.80",
|
|
13
|
-
"solid-js": "^1.9.11",
|
|
14
|
-
"@overview/core": "workspace:*",
|
|
15
|
-
"@f0rbit/corpus": "^0.3.5",
|
|
16
|
-
"@devpad/api": "^2.1.3"
|
|
17
|
-
}
|
|
18
|
-
}
|