gitspace 0.2.0-rc.18 → 0.2.0-rc.19
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/README.md +16 -0
- package/package.json +5 -5
- package/src/app.web.tsx +85 -3
- package/src/commands/bundle.ts +10 -17
- package/src/commands/review.ts +787 -0
- package/src/components/DiffViewer.web.tsx +1192 -0
- package/src/components/SpacesBrowser.web.tsx +19 -2
- package/src/components/ThreadPanel.web.tsx +798 -0
- package/src/components/review-decision-colors.ts +11 -0
- package/src/core/__tests__/github-review.test.ts +781 -0
- package/src/core/git.ts +430 -0
- package/src/core/github-review.ts +761 -0
- package/src/core/review-executor.ts +316 -0
- package/src/core/review.ts +407 -0
- package/src/core/shell.ts +11 -8
- package/src/hooks/__tests__/useLocalSession.tui.test.ts +4 -0
- package/src/hooks/useReview.web.ts +248 -0
- package/src/index.ts +204 -0
- package/src/lib/remote-session/protocol.ts +25 -2
- package/src/lib/remote-session/session-handler.ts +82 -10
- package/src/lib/tmux-lite/cli.ts +7 -2
- package/src/lib/tmux-lite/protocol.ts +17 -1
- package/src/lib/tmux-lite/server.ts +54 -6
- package/src/pages/ReviewPage.web.tsx +511 -0
- package/src/session/__tests__/backend-manager.test.ts +3 -0
- package/src/session/__tests__/useRemoteSessionClient.test.ts +4 -0
- package/src/session/__tests__/workspace-shell-hooks.integration.test.ts +268 -0
- package/src/session/__tests__/workspace-shell-hooks.test.ts +24 -0
- package/src/session/backend.ts +3 -0
- package/src/session/backends/local-session-backend.ts +19 -22
- package/src/session/backends/remote-session-backend.ts +106 -0
- package/src/session/events.ts +6 -1
- package/src/session/useRemoteSessionClient.ts +17 -0
- package/src/session/useSessionEngine.ts +20 -0
- package/src/session/workspace-shell-hooks.ts +35 -0
- package/src/types/errors.ts +39 -0
- package/src/types/review.ts +349 -0
- package/src/utils/hunk-header.ts +17 -0
- package/src/utils/id.ts +9 -0
- package/src/utils/workspace-id.ts +55 -0
- package/web/bun.lock +97 -0
- package/web/package.json +1 -0
- package/web/tsconfig.app.json +3 -1
- package/web/vite.config.ts +3 -0
package/README.md
CHANGED
|
@@ -106,6 +106,22 @@ gssh switch
|
|
|
106
106
|
gssh switch my-feature
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
### Workspace Session Mode (`space`)
|
|
110
|
+
|
|
111
|
+
When GitSpace opens a workspace-scoped terminal session, it injects a `space` shell function (bash/zsh).
|
|
112
|
+
|
|
113
|
+
- Use `space ...` for workspace operations without repeating `--project` and `--workspace`
|
|
114
|
+
- `gssh` commands are restricted in this mode to avoid cross-workspace mistakes
|
|
115
|
+
- `gssh tmux ...` is blocked inside workspace sessions
|
|
116
|
+
|
|
117
|
+
Examples:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
space context --json
|
|
121
|
+
space review hunks src/app.ts --format json
|
|
122
|
+
space review add-hunk src/app.ts --index 1 --approve --body "Looks good"
|
|
123
|
+
```
|
|
124
|
+
|
|
109
125
|
## Repo Config Bundles
|
|
110
126
|
|
|
111
127
|
Repo config bundles allow repository owners to share onboarding configurations with their team. When someone clones a project that contains a bundle, they'll be guided through setup steps and have scripts automatically installed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitspace",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.19",
|
|
4
4
|
"description": "CLI for managing GitHub workspaces with git worktrees and secure remote terminal access",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gssh": "./bin/gssh"
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"relay": "bun src/relay/index.ts"
|
|
18
18
|
},
|
|
19
19
|
"optionalDependencies": {
|
|
20
|
-
"@gitspace/darwin-arm64": "0.2.0-rc.
|
|
21
|
-
"@gitspace/darwin-x64": "0.2.0-rc.
|
|
22
|
-
"@gitspace/linux-x64": "0.2.0-rc.
|
|
23
|
-
"@gitspace/linux-arm64": "0.2.0-rc.
|
|
20
|
+
"@gitspace/darwin-arm64": "0.2.0-rc.19",
|
|
21
|
+
"@gitspace/darwin-x64": "0.2.0-rc.19",
|
|
22
|
+
"@gitspace/linux-x64": "0.2.0-rc.19",
|
|
23
|
+
"@gitspace/linux-arm64": "0.2.0-rc.19"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"cli",
|
package/src/app.web.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import { useUserActivity } from "./hooks/index.js";
|
|
|
19
19
|
import { useBundleRefreshAttachFlow } from './session/useBundleRefreshAttachFlow.js';
|
|
20
20
|
import { useAttachController } from './app/session/useAttachController.js';
|
|
21
21
|
import { useWorkspaceDeleteFlow } from './app/session/useWorkspaceDeleteFlow.js';
|
|
22
|
+
import { ReviewPage } from './pages/ReviewPage.web.js';
|
|
22
23
|
|
|
23
24
|
// Import shared components and hooks
|
|
24
25
|
import {
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
useFlow,
|
|
28
29
|
getDefaultShortcuts,
|
|
29
30
|
type MachineInfo,
|
|
31
|
+
type WorkspaceInfo,
|
|
30
32
|
} from "./components/index.js";
|
|
31
33
|
import { MachineListWeb } from "./components/MachineList.web.js";
|
|
32
34
|
import { SpacesBrowserWeb } from "./components/SpacesBrowser.web.js";
|
|
@@ -46,7 +48,7 @@ import {
|
|
|
46
48
|
resolveSessionBrowserCommand,
|
|
47
49
|
} from './app/input/sessionCommands.js';
|
|
48
50
|
|
|
49
|
-
type View = "machines" | "terminal";
|
|
51
|
+
type View = "machines" | "terminal" | "review";
|
|
50
52
|
|
|
51
53
|
const PAGE_UP = '\x1b[5~';
|
|
52
54
|
const PAGE_DOWN = '\x1b[6~';
|
|
@@ -88,6 +90,13 @@ export default function App() {
|
|
|
88
90
|
inviteToken?: string;
|
|
89
91
|
} | null>(null);
|
|
90
92
|
|
|
93
|
+
// Review workspace/project state
|
|
94
|
+
const [reviewWorkspace, setReviewWorkspace] = useState<{
|
|
95
|
+
projectName: string;
|
|
96
|
+
workspaceId: string;
|
|
97
|
+
workspaceLabel?: string;
|
|
98
|
+
} | null>(null);
|
|
99
|
+
|
|
91
100
|
// Relay connection (for machine list)
|
|
92
101
|
const relay = useRelayConnection();
|
|
93
102
|
|
|
@@ -234,7 +243,7 @@ export default function App() {
|
|
|
234
243
|
setLocalNotificationConfig(terminal.notificationConfig);
|
|
235
244
|
}, [terminal.notificationConfig]);
|
|
236
245
|
|
|
237
|
-
// Parse invite from URL hash on load
|
|
246
|
+
// Parse invite from URL hash on load, and review params from query string
|
|
238
247
|
useEffect(() => {
|
|
239
248
|
const hash = window.location.hash;
|
|
240
249
|
if (hash.startsWith("#invite=")) {
|
|
@@ -248,6 +257,16 @@ export default function App() {
|
|
|
248
257
|
}
|
|
249
258
|
});
|
|
250
259
|
}
|
|
260
|
+
|
|
261
|
+
const params = new URLSearchParams(window.location.search);
|
|
262
|
+
if (params.get('view') === 'review') {
|
|
263
|
+
const ws = params.get('workspace');
|
|
264
|
+
const proj = params.get('project');
|
|
265
|
+
if (ws && proj) {
|
|
266
|
+
setReviewWorkspace({ projectName: proj, workspaceId: ws, workspaceLabel: ws });
|
|
267
|
+
setView('review');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
251
270
|
}, []);
|
|
252
271
|
|
|
253
272
|
// Auto-connect on load (no token required for personal relays)
|
|
@@ -419,6 +438,16 @@ export default function App() {
|
|
|
419
438
|
await attachController.attachFromSelection(params);
|
|
420
439
|
}, [attachController]);
|
|
421
440
|
|
|
441
|
+
// Handle opening review for a workspace
|
|
442
|
+
const handleOpenReview = useCallback((workspace: WorkspaceInfo) => {
|
|
443
|
+
setReviewWorkspace({
|
|
444
|
+
projectName: workspace.projectName,
|
|
445
|
+
workspaceId: workspace.id,
|
|
446
|
+
workspaceLabel: workspace.name,
|
|
447
|
+
});
|
|
448
|
+
setView('review');
|
|
449
|
+
}, []);
|
|
450
|
+
|
|
422
451
|
// Spaces browser hook
|
|
423
452
|
const spacesBrowserProps = useSpacesBrowser({
|
|
424
453
|
workspaces: terminal.workspaces,
|
|
@@ -740,6 +769,59 @@ export default function App() {
|
|
|
740
769
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
741
770
|
}, [notifications.activeToast, notifications.attachToActiveToast, flow]);
|
|
742
771
|
|
|
772
|
+
// ========== Review View ==========
|
|
773
|
+
if (view === 'review' && reviewWorkspace) {
|
|
774
|
+
if (terminal.status === 'established') {
|
|
775
|
+
return (
|
|
776
|
+
<>
|
|
777
|
+
<ReviewPage
|
|
778
|
+
projectName={reviewWorkspace.projectName}
|
|
779
|
+
workspaceName={reviewWorkspace.workspaceId}
|
|
780
|
+
workspaceLabel={reviewWorkspace.workspaceLabel}
|
|
781
|
+
machineName={selectedMachine?.label || selectedMachine?.machineId}
|
|
782
|
+
sendReviewRequest={terminal.sendReviewRequest}
|
|
783
|
+
onBack={() => {
|
|
784
|
+
setView('terminal');
|
|
785
|
+
setReviewWorkspace(null);
|
|
786
|
+
}}
|
|
787
|
+
/>
|
|
788
|
+
<Toaster theme="dark" position="top-right" richColors />
|
|
789
|
+
</>
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Connection not yet established — show a targeted connecting screen
|
|
794
|
+
// rather than falling through to the generic machine list.
|
|
795
|
+
const statusMessage = {
|
|
796
|
+
disconnected: "Disconnected",
|
|
797
|
+
connecting: "Connecting to relay...",
|
|
798
|
+
connected: "Connected, authenticating...",
|
|
799
|
+
handshaking: "Establishing secure connection...",
|
|
800
|
+
established: "Connected!",
|
|
801
|
+
error: "Connection failed",
|
|
802
|
+
}[terminal.status];
|
|
803
|
+
|
|
804
|
+
return (
|
|
805
|
+
<>
|
|
806
|
+
<div className="h-screen w-screen flex flex-col items-center justify-center bg-[#0d1117] px-4">
|
|
807
|
+
<div className="text-center">
|
|
808
|
+
<div className="text-lg text-[#e6edf3] mb-2">
|
|
809
|
+
Loading review for <span className="text-[#58a6ff]">{reviewWorkspace.workspaceLabel ?? reviewWorkspace.workspaceId}</span>
|
|
810
|
+
</div>
|
|
811
|
+
<div className="text-sm text-[#8b949e]">{statusMessage}</div>
|
|
812
|
+
<button
|
|
813
|
+
onClick={() => { setView('machines'); setReviewWorkspace(null); }}
|
|
814
|
+
className="mt-4 px-6 py-3 text-base bg-[#21262d] hover:bg-[#30363d] rounded-lg text-[#e6edf3] min-h-[48px] border border-[#30363d]"
|
|
815
|
+
>
|
|
816
|
+
Back to Machines
|
|
817
|
+
</button>
|
|
818
|
+
</div>
|
|
819
|
+
</div>
|
|
820
|
+
<Toaster theme="dark" position="top-right" richColors />
|
|
821
|
+
</>
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
|
|
743
825
|
// ========== Spaces Browser View (browsing mode) ==========
|
|
744
826
|
if (
|
|
745
827
|
view === 'terminal' &&
|
|
@@ -819,7 +901,7 @@ export default function App() {
|
|
|
819
901
|
</div>
|
|
820
902
|
</div>
|
|
821
903
|
<div className="flex-1 overflow-hidden">
|
|
822
|
-
<SpacesBrowserWeb {...spacesBrowserProps} />
|
|
904
|
+
<SpacesBrowserWeb {...spacesBrowserProps} onReview={handleOpenReview} />
|
|
823
905
|
</div>
|
|
824
906
|
</div>
|
|
825
907
|
<FlowWeb flow={flow} />
|
package/src/commands/bundle.ts
CHANGED
|
@@ -8,15 +8,16 @@ import { exec } from 'child_process';
|
|
|
8
8
|
import { promisify } from 'util';
|
|
9
9
|
import { logger } from '../utils/logger.js';
|
|
10
10
|
import { SpacesError } from '../types/errors.js';
|
|
11
|
-
import { getCurrentProject, getProjectBaseDir, getProjectWorkspacesDir } from '../core/config.js';
|
|
11
|
+
import { getCurrentProject, getGitspaceDir, getProjectBaseDir, getProjectWorkspacesDir } from '../core/config.js';
|
|
12
12
|
import {
|
|
13
13
|
detectBundleChanges,
|
|
14
14
|
formatBundleChangeDetails,
|
|
15
15
|
refreshBundle,
|
|
16
16
|
type BundleRefreshOptions,
|
|
17
17
|
} from '../core/bundle-refresh.js';
|
|
18
|
-
import { join
|
|
18
|
+
import { join } from 'path';
|
|
19
19
|
import { existsSync } from 'fs';
|
|
20
|
+
import { detectWorkspaceContextFromCwd } from '../utils/workspace-id.js';
|
|
20
21
|
|
|
21
22
|
const execAsync = promisify(exec);
|
|
22
23
|
|
|
@@ -43,24 +44,16 @@ export interface BundleStatusOptions {
|
|
|
43
44
|
* Returns the workspace path if found, undefined otherwise
|
|
44
45
|
*/
|
|
45
46
|
function detectWorkspaceFromCwd(projectName: string): string | undefined {
|
|
46
|
-
const cwd = process.cwd();
|
|
47
47
|
const workspacesDir = getProjectWorkspacesDir(projectName);
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (resolvedCwd.startsWith(resolvedWorkspacesDir + '/') || resolvedCwd === resolvedWorkspacesDir) {
|
|
54
|
-
// Extract the workspace name (first path segment after workspaces/)
|
|
55
|
-
const relativePath = resolvedCwd.slice(resolvedWorkspacesDir.length + 1);
|
|
56
|
-
const workspaceName = relativePath.split('/')[0];
|
|
49
|
+
const context = detectWorkspaceContextFromCwd(process.cwd(), getGitspaceDir());
|
|
50
|
+
if (!context || context.projectName !== projectName) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return workspacePath;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
54
|
+
const workspacePath = join(workspacesDir, context.workspaceName);
|
|
55
|
+
if (existsSync(workspacePath)) {
|
|
56
|
+
return workspacePath;
|
|
64
57
|
}
|
|
65
58
|
|
|
66
59
|
return undefined;
|