gitspace 0.2.0-rc.1
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/.claude/settings.local.json +21 -0
- package/.gitspace/bundle.json +50 -0
- package/.gitspace/select/01-status.sh +40 -0
- package/.gitspace/setup/01-install-deps.sh +12 -0
- package/.gitspace/setup/02-typecheck.sh +16 -0
- package/AGENTS.md +439 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +25 -0
- package/README.md +607 -0
- package/bin/gssh +62 -0
- package/bun.lock +647 -0
- package/docs/CONNECTION.md +623 -0
- package/docs/GATEWAY-WORKER.md +319 -0
- package/docs/GETTING-STARTED.md +448 -0
- package/docs/GITSPACE-PLATFORM.md +1819 -0
- package/docs/INFRASTRUCTURE.md +1347 -0
- package/docs/PROTOCOL.md +619 -0
- package/docs/QUICKSTART.md +174 -0
- package/docs/RELAY.md +327 -0
- package/docs/REMOTE-DESIGN.md +549 -0
- package/docs/ROADMAP.md +564 -0
- package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
- package/docs/STACK-DESIGN.md +588 -0
- package/docs/UNIFIED_ARCHITECTURE.md +292 -0
- package/experiments/pty-benchmark.ts +148 -0
- package/experiments/pty-latency.ts +100 -0
- package/experiments/router/client.ts +199 -0
- package/experiments/router/protocol.ts +74 -0
- package/experiments/router/router.ts +217 -0
- package/experiments/router/session.ts +180 -0
- package/experiments/router/test.ts +133 -0
- package/experiments/socket-bandwidth.ts +77 -0
- package/homebrew/gitspace.rb +45 -0
- package/landing-page/ATTRIBUTIONS.md +3 -0
- package/landing-page/README.md +11 -0
- package/landing-page/bun.lock +801 -0
- package/landing-page/guidelines/Guidelines.md +61 -0
- package/landing-page/index.html +37 -0
- package/landing-page/package.json +90 -0
- package/landing-page/postcss.config.mjs +15 -0
- package/landing-page/public/_redirects +1 -0
- package/landing-page/public/favicon.png +0 -0
- package/landing-page/src/app/App.tsx +53 -0
- package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
- package/landing-page/src/app/components/ui/accordion.tsx +66 -0
- package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
- package/landing-page/src/app/components/ui/alert.tsx +66 -0
- package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
- package/landing-page/src/app/components/ui/avatar.tsx +53 -0
- package/landing-page/src/app/components/ui/badge.tsx +46 -0
- package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
- package/landing-page/src/app/components/ui/button.tsx +57 -0
- package/landing-page/src/app/components/ui/calendar.tsx +75 -0
- package/landing-page/src/app/components/ui/card.tsx +92 -0
- package/landing-page/src/app/components/ui/carousel.tsx +241 -0
- package/landing-page/src/app/components/ui/chart.tsx +353 -0
- package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
- package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
- package/landing-page/src/app/components/ui/command.tsx +177 -0
- package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
- package/landing-page/src/app/components/ui/dialog.tsx +135 -0
- package/landing-page/src/app/components/ui/drawer.tsx +132 -0
- package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
- package/landing-page/src/app/components/ui/form.tsx +168 -0
- package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
- package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
- package/landing-page/src/app/components/ui/input.tsx +21 -0
- package/landing-page/src/app/components/ui/label.tsx +24 -0
- package/landing-page/src/app/components/ui/menubar.tsx +276 -0
- package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
- package/landing-page/src/app/components/ui/pagination.tsx +127 -0
- package/landing-page/src/app/components/ui/popover.tsx +48 -0
- package/landing-page/src/app/components/ui/progress.tsx +31 -0
- package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
- package/landing-page/src/app/components/ui/resizable.tsx +56 -0
- package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
- package/landing-page/src/app/components/ui/select.tsx +189 -0
- package/landing-page/src/app/components/ui/separator.tsx +28 -0
- package/landing-page/src/app/components/ui/sheet.tsx +139 -0
- package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
- package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
- package/landing-page/src/app/components/ui/slider.tsx +63 -0
- package/landing-page/src/app/components/ui/sonner.tsx +25 -0
- package/landing-page/src/app/components/ui/switch.tsx +31 -0
- package/landing-page/src/app/components/ui/table.tsx +116 -0
- package/landing-page/src/app/components/ui/tabs.tsx +66 -0
- package/landing-page/src/app/components/ui/textarea.tsx +18 -0
- package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
- package/landing-page/src/app/components/ui/toggle.tsx +47 -0
- package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
- package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
- package/landing-page/src/app/components/ui/utils.ts +6 -0
- package/landing-page/src/components/docs/DocsContent.tsx +718 -0
- package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
- package/landing-page/src/components/landing/CTA.tsx +59 -0
- package/landing-page/src/components/landing/Comparison.tsx +84 -0
- package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
- package/landing-page/src/components/landing/Features.tsx +201 -0
- package/landing-page/src/components/landing/Hero.tsx +142 -0
- package/landing-page/src/components/landing/Pricing.tsx +140 -0
- package/landing-page/src/components/landing/Roadmap.tsx +86 -0
- package/landing-page/src/components/landing/Security.tsx +81 -0
- package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
- package/landing-page/src/components/landing/UseCases.tsx +55 -0
- package/landing-page/src/components/landing/Workflow.tsx +101 -0
- package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
- package/landing-page/src/components/layout/Footer.tsx +55 -0
- package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
- package/landing-page/src/components/ui/badge.tsx +39 -0
- package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
- package/landing-page/src/components/ui/button.tsx +57 -0
- package/landing-page/src/components/ui/card.tsx +79 -0
- package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
- package/landing-page/src/components/ui/separator.tsx +28 -0
- package/landing-page/src/lib/utils.ts +6 -0
- package/landing-page/src/main.tsx +10 -0
- package/landing-page/src/pages/Dashboard.tsx +133 -0
- package/landing-page/src/pages/DocsPage.tsx +79 -0
- package/landing-page/src/pages/LandingPage.tsx +31 -0
- package/landing-page/src/pages/TerminalView.tsx +106 -0
- package/landing-page/src/styles/fonts.css +0 -0
- package/landing-page/src/styles/index.css +3 -0
- package/landing-page/src/styles/tailwind.css +4 -0
- package/landing-page/src/styles/theme.css +181 -0
- package/landing-page/vite.config.ts +19 -0
- package/npm/darwin-arm64/bin/gssh +0 -0
- package/npm/darwin-arm64/package.json +20 -0
- package/package.json +74 -0
- package/scripts/build.ts +284 -0
- package/scripts/release.ts +140 -0
- package/src/__tests__/test-utils.ts +298 -0
- package/src/commands/__tests__/serve-messages.test.ts +190 -0
- package/src/commands/access.ts +298 -0
- package/src/commands/add.ts +452 -0
- package/src/commands/auth.ts +364 -0
- package/src/commands/connect.ts +287 -0
- package/src/commands/directory.ts +16 -0
- package/src/commands/host.ts +396 -0
- package/src/commands/identity.ts +184 -0
- package/src/commands/list.ts +200 -0
- package/src/commands/relay.ts +315 -0
- package/src/commands/remove.ts +241 -0
- package/src/commands/serve.ts +1493 -0
- package/src/commands/share.ts +456 -0
- package/src/commands/status.ts +125 -0
- package/src/commands/switch.ts +353 -0
- package/src/commands/tmux.ts +317 -0
- package/src/core/__tests__/access.test.ts +240 -0
- package/src/core/access.ts +277 -0
- package/src/core/bundle.ts +342 -0
- package/src/core/config.ts +510 -0
- package/src/core/git.ts +317 -0
- package/src/core/github.ts +151 -0
- package/src/core/identity.ts +631 -0
- package/src/core/linear.ts +225 -0
- package/src/core/shell.ts +161 -0
- package/src/core/trusted-relays.ts +315 -0
- package/src/index.ts +821 -0
- package/src/lib/remote-session/index.ts +7 -0
- package/src/lib/remote-session/protocol.ts +267 -0
- package/src/lib/remote-session/session-handler.ts +581 -0
- package/src/lib/remote-session/workspace-scanner.ts +167 -0
- package/src/lib/tmux-lite/README.md +81 -0
- package/src/lib/tmux-lite/cli.ts +796 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
- package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
- package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
- package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
- package/src/lib/tmux-lite/crypto/frames.ts +141 -0
- package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
- package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
- package/src/lib/tmux-lite/crypto/identity.ts +286 -0
- package/src/lib/tmux-lite/crypto/index.ts +51 -0
- package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
- package/src/lib/tmux-lite/crypto/invites.ts +215 -0
- package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
- package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
- package/src/lib/tmux-lite/crypto/keys.ts +47 -0
- package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
- package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
- package/src/lib/tmux-lite/handshake-handler.ts +451 -0
- package/src/lib/tmux-lite/protocol.test.ts +307 -0
- package/src/lib/tmux-lite/protocol.ts +266 -0
- package/src/lib/tmux-lite/relay-client.ts +506 -0
- package/src/lib/tmux-lite/server.ts +1250 -0
- package/src/lib/tmux-lite/shell-integration.sh +37 -0
- package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
- package/src/lib/tmux-lite/terminal-queries.ts +49 -0
- package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
- package/src/relay/__tests__/helpers/auth.ts +354 -0
- package/src/relay/__tests__/helpers/ports.ts +51 -0
- package/src/relay/__tests__/protocol-validation.test.ts +265 -0
- package/src/relay/authorization.ts +303 -0
- package/src/relay/embedded-assets.generated.d.ts +15 -0
- package/src/relay/identity.ts +352 -0
- package/src/relay/index.ts +57 -0
- package/src/relay/pipes.test.ts +427 -0
- package/src/relay/pipes.ts +195 -0
- package/src/relay/protocol.ts +804 -0
- package/src/relay/registries.test.ts +437 -0
- package/src/relay/registries.ts +593 -0
- package/src/relay/server.test.ts +1323 -0
- package/src/relay/server.ts +1092 -0
- package/src/relay/signing.ts +238 -0
- package/src/relay/types.ts +69 -0
- package/src/serve/client-session-manager.ts +622 -0
- package/src/serve/daemon.ts +497 -0
- package/src/serve/pty-session.ts +236 -0
- package/src/serve/types.ts +169 -0
- package/src/shared/components/Flow.tsx +453 -0
- package/src/shared/components/Flow.tui.tsx +343 -0
- package/src/shared/components/Flow.web.tsx +442 -0
- package/src/shared/components/Inbox.tsx +446 -0
- package/src/shared/components/Inbox.tui.tsx +262 -0
- package/src/shared/components/Inbox.web.tsx +329 -0
- package/src/shared/components/MachineList.tsx +187 -0
- package/src/shared/components/MachineList.tui.tsx +161 -0
- package/src/shared/components/MachineList.web.tsx +210 -0
- package/src/shared/components/ProjectList.tsx +176 -0
- package/src/shared/components/ProjectList.tui.tsx +109 -0
- package/src/shared/components/ProjectList.web.tsx +143 -0
- package/src/shared/components/SpacesBrowser.tsx +332 -0
- package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
- package/src/shared/components/SpacesBrowser.web.tsx +221 -0
- package/src/shared/components/index.ts +103 -0
- package/src/shared/hooks/index.ts +16 -0
- package/src/shared/hooks/useNavigation.ts +226 -0
- package/src/shared/index.ts +122 -0
- package/src/shared/providers/LocalMachineProvider.ts +425 -0
- package/src/shared/providers/MachineProvider.ts +165 -0
- package/src/shared/providers/RemoteMachineProvider.ts +444 -0
- package/src/shared/providers/index.ts +26 -0
- package/src/shared/types.ts +145 -0
- package/src/tui/adapters.ts +120 -0
- package/src/tui/app.tsx +1816 -0
- package/src/tui/components/Terminal.tsx +580 -0
- package/src/tui/hooks/index.ts +35 -0
- package/src/tui/hooks/useAppState.ts +314 -0
- package/src/tui/hooks/useDaemonStatus.ts +174 -0
- package/src/tui/hooks/useInboxTUI.ts +113 -0
- package/src/tui/hooks/useRemoteMachines.ts +209 -0
- package/src/tui/index.ts +24 -0
- package/src/tui/state.ts +299 -0
- package/src/tui/terminal-bracketed-paste.test.ts +45 -0
- package/src/tui/terminal-bracketed-paste.ts +47 -0
- package/src/types/bundle.ts +112 -0
- package/src/types/config.ts +89 -0
- package/src/types/errors.ts +206 -0
- package/src/types/identity.ts +284 -0
- package/src/types/workspace-fuzzy.ts +49 -0
- package/src/types/workspace.ts +151 -0
- package/src/utils/bun-socket-writer.ts +80 -0
- package/src/utils/deps.ts +127 -0
- package/src/utils/fuzzy-match.ts +125 -0
- package/src/utils/logger.ts +127 -0
- package/src/utils/markdown.ts +254 -0
- package/src/utils/onboarding.ts +229 -0
- package/src/utils/prompts.ts +114 -0
- package/src/utils/run-commands.ts +112 -0
- package/src/utils/run-scripts.ts +142 -0
- package/src/utils/sanitize.ts +98 -0
- package/src/utils/secrets.ts +122 -0
- package/src/utils/shell-escape.ts +40 -0
- package/src/utils/utf8.ts +79 -0
- package/src/utils/workspace-state.ts +47 -0
- package/src/web/README.md +73 -0
- package/src/web/bun.lock +575 -0
- package/src/web/eslint.config.js +23 -0
- package/src/web/index.html +16 -0
- package/src/web/package.json +37 -0
- package/src/web/public/vite.svg +1 -0
- package/src/web/src/App.tsx +604 -0
- package/src/web/src/assets/react.svg +1 -0
- package/src/web/src/components/Terminal.tsx +207 -0
- package/src/web/src/hooks/useRelayConnection.ts +224 -0
- package/src/web/src/hooks/useTerminal.ts +699 -0
- package/src/web/src/index.css +55 -0
- package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
- package/src/web/src/lib/crypto/frames.ts +205 -0
- package/src/web/src/lib/crypto/handshake.ts +396 -0
- package/src/web/src/lib/crypto/identity.ts +128 -0
- package/src/web/src/lib/crypto/keyexchange.ts +246 -0
- package/src/web/src/lib/crypto/relay-signing.ts +53 -0
- package/src/web/src/lib/invite.ts +58 -0
- package/src/web/src/lib/storage/identity-store.ts +94 -0
- package/src/web/src/main.tsx +10 -0
- package/src/web/src/types/identity.ts +45 -0
- package/src/web/tsconfig.app.json +28 -0
- package/src/web/tsconfig.json +7 -0
- package/src/web/tsconfig.node.json +26 -0
- package/src/web/vite.config.ts +31 -0
- package/todo-security.md +92 -0
- package/tsconfig.json +23 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
- package/worker/bun.lock +237 -0
- package/worker/package.json +22 -0
- package/worker/schema.sql +96 -0
- package/worker/src/handlers/auth.ts +451 -0
- package/worker/src/handlers/subdomains.ts +376 -0
- package/worker/src/handlers/user.ts +98 -0
- package/worker/src/index.ts +70 -0
- package/worker/src/middleware/auth.ts +152 -0
- package/worker/src/services/cloudflare.ts +609 -0
- package/worker/src/types.ts +96 -0
- package/worker/tsconfig.json +15 -0
- package/worker/wrangler.toml +26 -0
package/docs/ROADMAP.md
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# GitSpace Roadmap
|
|
2
|
+
|
|
3
|
+
> **Living Document** - Captures the vision and implementation plan for GitSpace
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Vision
|
|
8
|
+
|
|
9
|
+
GitSpace is a unified development environment that combines:
|
|
10
|
+
- **Git worktrees** for parallel branch development
|
|
11
|
+
- **VM isolation** via Lima for secure, reproducible environments
|
|
12
|
+
- **Process management** via wide event observability
|
|
13
|
+
- **Remote access** via E2E encrypted relay
|
|
14
|
+
- **Instant hosting** via Cloudflare tunnels on `*.gitspace.sh`
|
|
15
|
+
- **Code intelligence** via ast-grep + AI for structural understanding
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Single Entry Point: `gssh`
|
|
20
|
+
|
|
21
|
+
The `gssh` command is the universal entry point:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Local development (default)
|
|
25
|
+
gssh
|
|
26
|
+
# → Starts relay daemon in background (if not running)
|
|
27
|
+
# → Launches TUI
|
|
28
|
+
# → Shows local workspaces
|
|
29
|
+
|
|
30
|
+
# Connect to remote machine
|
|
31
|
+
gssh --remote <machine-id>
|
|
32
|
+
gssh --remote brads-macbook
|
|
33
|
+
|
|
34
|
+
# Connect via invite token
|
|
35
|
+
gssh --connect <invite-token>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Startup Flow
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
42
|
+
│ gssh (entry point) │
|
|
43
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
44
|
+
│ │
|
|
45
|
+
│ 1. Check: Is relay daemon running? │
|
|
46
|
+
│ └── No → Start `gssh serve` in background │
|
|
47
|
+
│ │
|
|
48
|
+
│ 2. Check: --remote flag? │
|
|
49
|
+
│ └── Yes → Connect TUI to remote machine │
|
|
50
|
+
│ └── No → Connect TUI to local machine │
|
|
51
|
+
│ │
|
|
52
|
+
│ 3. Launch TUI │
|
|
53
|
+
│ └── Browse workspaces │
|
|
54
|
+
│ └── Select workspace → ensure Lima VM running → attach session │
|
|
55
|
+
│ │
|
|
56
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Feature Roadmap
|
|
62
|
+
|
|
63
|
+
### 1. Cloudflare Tunnel (Status: Partially Implemented)
|
|
64
|
+
|
|
65
|
+
Instant hosting on `*.gitspace.sh` subdomains.
|
|
66
|
+
|
|
67
|
+
**Implemented Commands:**
|
|
68
|
+
```bash
|
|
69
|
+
gssh auth login # GitHub OAuth authentication
|
|
70
|
+
gssh auth logout # Clear credentials
|
|
71
|
+
gssh auth status # Show auth status
|
|
72
|
+
gssh host reserve <name> # Reserve myapp.gitspace.sh
|
|
73
|
+
gssh host release [name] # Release subdomain
|
|
74
|
+
gssh host list # List your subdomains
|
|
75
|
+
gssh host set-primary <n> # Set primary subdomain
|
|
76
|
+
gssh host status # Show tunnel status
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Not Yet Implemented:**
|
|
80
|
+
- Service routing (e.g., `app.username.gitspace.sh` → `localhost:3000`)
|
|
81
|
+
- Per-service access control
|
|
82
|
+
- Gateway worker for auth at edge
|
|
83
|
+
|
|
84
|
+
**Architecture:**
|
|
85
|
+
```
|
|
86
|
+
Developer Mac Cloudflare Users
|
|
87
|
+
───────────── ────────── ─────
|
|
88
|
+
|
|
89
|
+
gssh serve ────────────────────► Tunnel ◄─────────────────── Browser
|
|
90
|
+
│ (cloudflared)
|
|
91
|
+
│
|
|
92
|
+
▼
|
|
93
|
+
localhost:4480 ◄─────────────── username.gitspace.sh (relay WebSocket)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Implementation Files:**
|
|
97
|
+
- `src/commands/auth.ts` - GitHub OAuth device flow
|
|
98
|
+
- `src/commands/host.ts` - Subdomain management CLI
|
|
99
|
+
- `worker/` - Cloudflare Worker API (D1 database)
|
|
100
|
+
- Token stored in keychain via `src/utils/secrets.ts`
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### 2. Lima VMs (Status: Not Implemented)
|
|
105
|
+
|
|
106
|
+
VM-per-workspace isolation using Lima. Works on all Apple Silicon (M1/M2/M3+).
|
|
107
|
+
|
|
108
|
+
**Why Lima:**
|
|
109
|
+
- Firecracker requires KVM (Linux-only)
|
|
110
|
+
- To run Firecracker on Mac, need nested virtualization
|
|
111
|
+
- Nested virt only works on M3+ with macOS 15+
|
|
112
|
+
- Lima VMs work on ALL Apple Silicon
|
|
113
|
+
- 5-10s boot time is acceptable for workspace-level isolation
|
|
114
|
+
|
|
115
|
+
**Architecture:**
|
|
116
|
+
```
|
|
117
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
118
|
+
│ MAC HOST │
|
|
119
|
+
│ │
|
|
120
|
+
│ gssh (TUI) │
|
|
121
|
+
│ │ │
|
|
122
|
+
│ ▼ │
|
|
123
|
+
│ gssh serve (daemon) │
|
|
124
|
+
│ │ │
|
|
125
|
+
│ ├── LimaManager (VM lifecycle) │
|
|
126
|
+
│ │ │ │
|
|
127
|
+
│ │ ├── Lima VM: feature-auth │
|
|
128
|
+
│ │ │ └── tmux-lite-server │
|
|
129
|
+
│ │ │ └── services (api, worker, db) │
|
|
130
|
+
│ │ │ │
|
|
131
|
+
│ │ └── Lima VM: bugfix-login │
|
|
132
|
+
│ │ └── tmux-lite-server │
|
|
133
|
+
│ │ └── services │
|
|
134
|
+
│ │ │
|
|
135
|
+
│ └── Relay connection (ONE machine identity) │
|
|
136
|
+
│ │
|
|
137
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Key Design Decisions:**
|
|
141
|
+
- **VM per workspace**, not per service
|
|
142
|
+
- **Invisible to relay** - Mac appears as one machine with multiple workspaces
|
|
143
|
+
- **Session routing** - `gssh serve` routes sessions to correct Lima VM
|
|
144
|
+
- **Dockerfile → rootfs** - Define VM environment via familiar Dockerfile
|
|
145
|
+
|
|
146
|
+
**Config:**
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"vm": {
|
|
150
|
+
"enabled": true,
|
|
151
|
+
"cpus": 4,
|
|
152
|
+
"memory": 4096,
|
|
153
|
+
"dockerfile": "./Dockerfile.dev"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Implementation:**
|
|
159
|
+
- `src/core/lima/manager.ts` - VM lifecycle (start/stop/status)
|
|
160
|
+
- `src/core/lima/config.ts` - Generate Lima YAML from workspace config
|
|
161
|
+
- `src/core/lima/connection.ts` - Connect to tmux-lite inside VM
|
|
162
|
+
- `src/shared/providers/LimaMachineProvider.ts` - MachineProvider implementation
|
|
163
|
+
|
|
164
|
+
**Session Lifecycle:**
|
|
165
|
+
1. User selects workspace in TUI
|
|
166
|
+
2. Check: Lima VM running for this workspace?
|
|
167
|
+
- No → Boot Lima VM (~5-10s)
|
|
168
|
+
- Yes → Continue
|
|
169
|
+
3. Check: Session exists?
|
|
170
|
+
- No → Create session in VM's tmux-lite
|
|
171
|
+
- Yes → Attach to existing session
|
|
172
|
+
4. User works in session
|
|
173
|
+
5. VM stays running until idle timeout
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### 3. Process Runner + Wide Events (Status: Not Implemented)
|
|
178
|
+
|
|
179
|
+
Service management with observability via wide events.
|
|
180
|
+
|
|
181
|
+
**Concepts:**
|
|
182
|
+
- **Services** - Defined in config, managed by GitSpace
|
|
183
|
+
- **Wide Events** - Rich structured events (not metrics/logs/traces separately)
|
|
184
|
+
- **Event Correlation** - eventId chains become single wide events
|
|
185
|
+
|
|
186
|
+
**Commands:**
|
|
187
|
+
```bash
|
|
188
|
+
gssh run # Start all services
|
|
189
|
+
gssh run api worker # Start specific services
|
|
190
|
+
gssh run --logs # With log tailing
|
|
191
|
+
|
|
192
|
+
gssh events # TUI explorer
|
|
193
|
+
gssh events tail # Live tail
|
|
194
|
+
gssh events "slow requests" # AI-powered query
|
|
195
|
+
gssh events --filter "http.status>=500" --group-by path
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Config:**
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"services": [
|
|
202
|
+
{
|
|
203
|
+
"name": "api",
|
|
204
|
+
"command": "bun run dev",
|
|
205
|
+
"readyWhen": { "port": 3000 },
|
|
206
|
+
"dependsOn": [],
|
|
207
|
+
"events": { "source": "stdout", "format": "jsonl" }
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"name": "worker",
|
|
211
|
+
"command": "bun run worker",
|
|
212
|
+
"dependsOn": ["api"]
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
"events": {
|
|
216
|
+
"enabled": true,
|
|
217
|
+
"store": "sqlite",
|
|
218
|
+
"flushTimeout": 5000
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Event Format (apps emit):**
|
|
224
|
+
```json
|
|
225
|
+
{"eventId": "req-123", "eventKey": "http.start", "method": "GET", "path": "/users"}
|
|
226
|
+
{"eventId": "req-123", "eventKey": "db.query", "table": "users", "duration_ms": 45}
|
|
227
|
+
{"eventId": "req-123", "eventKey": "http.end", "status": 200, "finalEvent": true}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Wide Event (after correlation):**
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"eventId": "req-123",
|
|
234
|
+
"http.method": "GET",
|
|
235
|
+
"http.path": "/users",
|
|
236
|
+
"http.status": 200,
|
|
237
|
+
"db.query_count": 1,
|
|
238
|
+
"db.total_duration_ms": 45
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Implementation:**
|
|
243
|
+
- `src/core/services/supervisor.ts` - Process manager
|
|
244
|
+
- `src/core/services/dependency.ts` - Dependency resolution
|
|
245
|
+
- `src/core/services/health.ts` - Ready checks
|
|
246
|
+
- `src/core/events/collector.ts` - Event ingestion
|
|
247
|
+
- `src/core/events/correlator.ts` - eventId → wide event
|
|
248
|
+
- `src/core/events/store/` - memory, sqlite, duckdb backends
|
|
249
|
+
- `src/core/events/query.ts` - Filter/group/aggregate
|
|
250
|
+
- `src/commands/run.ts` - CLI
|
|
251
|
+
- `src/commands/events.ts` - CLI
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### 4. Code Intelligence (Status: Not Implemented)
|
|
256
|
+
|
|
257
|
+
ast-grep + AI for structural code understanding, inspired by Lumen.
|
|
258
|
+
|
|
259
|
+
**Concept:**
|
|
260
|
+
- Use **ast-grep** for structural code search (not text grep)
|
|
261
|
+
- **AI generates patterns** from natural language queries
|
|
262
|
+
- **Correlate with runtime** - connect slow events back to code
|
|
263
|
+
- **AI synthesizes** findings into actionable insights
|
|
264
|
+
|
|
265
|
+
**Commands:**
|
|
266
|
+
```bash
|
|
267
|
+
gssh query "how does auth work?"
|
|
268
|
+
gssh query "what calls validateUser?"
|
|
269
|
+
gssh query "why is checkout slow?" # Uses runtime events + code
|
|
270
|
+
gssh query --impact "change User type" # Impact analysis
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Architecture:**
|
|
274
|
+
```
|
|
275
|
+
User: "why is checkout slow?"
|
|
276
|
+
│
|
|
277
|
+
▼
|
|
278
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
279
|
+
│ 1. Pattern Generator (AI → ast-grep rules) │
|
|
280
|
+
│ "Find all functions with 'checkout' in name" │
|
|
281
|
+
│ "Find all db queries in checkout flow" │
|
|
282
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
283
|
+
│
|
|
284
|
+
▼
|
|
285
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
286
|
+
│ 2. Structural Scan (ast-grep) │
|
|
287
|
+
│ → 5 checkout-related functions │
|
|
288
|
+
│ → 12 db queries in call graph │
|
|
289
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
290
|
+
│
|
|
291
|
+
▼
|
|
292
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
293
|
+
│ 3. Runtime Correlation (wide events) │
|
|
294
|
+
│ → checkout requests have p95 = 850ms │
|
|
295
|
+
│ → db.query in OrderRepository taking 500ms │
|
|
296
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
297
|
+
│
|
|
298
|
+
▼
|
|
299
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
300
|
+
│ 4. AI Synthesis │
|
|
301
|
+
│ "N+1 query in OrderRepository.findByUser() at src/repos/ │
|
|
302
|
+
│ order.ts:45. Called in loop by CartService.getItems()." │
|
|
303
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Implementation:**
|
|
307
|
+
- `src/core/insight/index.ts` - Orchestrator
|
|
308
|
+
- `src/core/insight/pattern-gen.ts` - NL → ast-grep patterns
|
|
309
|
+
- `src/core/insight/scanner.ts` - Execute patterns
|
|
310
|
+
- `src/core/insight/graph.ts` - Dependency/call graphs
|
|
311
|
+
- `src/core/insight/ai-synth.ts` - AI synthesis
|
|
312
|
+
- `src/lib/ast-grep/runner.ts` - Shell out to sg CLI
|
|
313
|
+
- `src/commands/query.ts` - CLI
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
### 5. Stack Splitting (Status: Not Implemented)
|
|
318
|
+
|
|
319
|
+
AI-assisted splitting of WIP commits into clean PRs. See `docs/STACK-DESIGN.md` for full design.
|
|
320
|
+
|
|
321
|
+
**Commands:**
|
|
322
|
+
```bash
|
|
323
|
+
gssh stack # Analyze and propose split
|
|
324
|
+
gssh stack --base develop # Specify base branch
|
|
325
|
+
gssh stack --dry-run # Just analyze, don't execute
|
|
326
|
+
gssh stack sync # Rebase after PR merges
|
|
327
|
+
gssh stack list # List active stacks
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Config Schema (Full)
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"name": "my-app",
|
|
337
|
+
"repository": "myorg/my-app",
|
|
338
|
+
"baseBranch": "main",
|
|
339
|
+
"createdAt": "2024-12-30T00:00:00.000Z",
|
|
340
|
+
"lastAccessed": "2024-12-30T00:00:00.000Z",
|
|
341
|
+
|
|
342
|
+
"vm": {
|
|
343
|
+
"enabled": true,
|
|
344
|
+
"cpus": 4,
|
|
345
|
+
"memory": 4096,
|
|
346
|
+
"disk": 50,
|
|
347
|
+
"dockerfile": "./Dockerfile.dev"
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
"services": [
|
|
351
|
+
{
|
|
352
|
+
"name": "api",
|
|
353
|
+
"command": "bun run dev",
|
|
354
|
+
"cwd": "./api",
|
|
355
|
+
"env": { "PORT": "3000" },
|
|
356
|
+
"dependsOn": ["db"],
|
|
357
|
+
"readyWhen": {
|
|
358
|
+
"port": 3000,
|
|
359
|
+
"timeout": 30000
|
|
360
|
+
},
|
|
361
|
+
"events": {
|
|
362
|
+
"source": "stdout",
|
|
363
|
+
"format": "jsonl"
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"name": "db",
|
|
368
|
+
"command": "postgres",
|
|
369
|
+
"readyWhen": { "port": 5432 }
|
|
370
|
+
}
|
|
371
|
+
],
|
|
372
|
+
|
|
373
|
+
"events": {
|
|
374
|
+
"enabled": true,
|
|
375
|
+
"store": "sqlite",
|
|
376
|
+
"flushTimeout": 5000,
|
|
377
|
+
"maxEvents": 100000
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
"host": {
|
|
381
|
+
"subdomain": "myapp",
|
|
382
|
+
"routes": [
|
|
383
|
+
{ "path": "/api", "service": "api" },
|
|
384
|
+
{ "path": "/", "port": 8080 }
|
|
385
|
+
],
|
|
386
|
+
"terminal": true,
|
|
387
|
+
"access": {
|
|
388
|
+
"mode": "private",
|
|
389
|
+
"allowedKeys": []
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
"bundleSecretKeys": ["GITSPACE_TOKEN"]
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## CLI Reference (Current + Planned)
|
|
400
|
+
|
|
401
|
+
### Implemented
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
# Entry point
|
|
405
|
+
gssh # Start TUI
|
|
406
|
+
gssh --relay <url> # TUI with remote machine access
|
|
407
|
+
|
|
408
|
+
# Identity
|
|
409
|
+
gssh identity init # Create machine keypair
|
|
410
|
+
gssh identity show # Show fingerprint
|
|
411
|
+
|
|
412
|
+
# Access control
|
|
413
|
+
gssh access add <pubkey> # Authorize client
|
|
414
|
+
gssh access list # List authorized
|
|
415
|
+
gssh access remove <key> # Revoke access
|
|
416
|
+
|
|
417
|
+
# Sharing
|
|
418
|
+
gssh share create # Create invite token
|
|
419
|
+
|
|
420
|
+
# Authentication (gitspace.sh)
|
|
421
|
+
gssh auth login # GitHub OAuth
|
|
422
|
+
gssh auth logout # Clear credentials
|
|
423
|
+
gssh auth status # Show auth status
|
|
424
|
+
|
|
425
|
+
# Hosting (gitspace.sh)
|
|
426
|
+
gssh host reserve <name> # Reserve subdomain
|
|
427
|
+
gssh host release [name] # Release subdomain
|
|
428
|
+
gssh host list # List subdomains
|
|
429
|
+
gssh host set-primary <n> # Set primary
|
|
430
|
+
gssh host status # Show status
|
|
431
|
+
|
|
432
|
+
# Remote access
|
|
433
|
+
gssh serve # Start daemon (foreground)
|
|
434
|
+
gssh serve start # Start daemon (background)
|
|
435
|
+
gssh serve stop # Stop daemon
|
|
436
|
+
gssh connect <token> # Connect via invite token
|
|
437
|
+
gssh status # Show daemon status
|
|
438
|
+
|
|
439
|
+
# Machine management
|
|
440
|
+
gssh machine invite # Create machine invite
|
|
441
|
+
gssh machine list # List machines (stub)
|
|
442
|
+
gssh machine remove <id> # Remove machine (stub)
|
|
443
|
+
|
|
444
|
+
# Projects/workspaces
|
|
445
|
+
gssh add project # Add project from GitHub
|
|
446
|
+
gssh add <name> # Create workspace
|
|
447
|
+
gssh list projects # List projects
|
|
448
|
+
gssh list workspaces # List workspaces
|
|
449
|
+
gssh remove workspace # Remove workspace
|
|
450
|
+
gssh remove project # Remove project
|
|
451
|
+
gssh directory # Print project directory
|
|
452
|
+
|
|
453
|
+
# Relay (internal)
|
|
454
|
+
gssh relay start # Start relay server
|
|
455
|
+
gssh relay authorize <pubkey> # Authorize machine
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Planned (Not Implemented)
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
# Services (requires Lima VMs)
|
|
462
|
+
gssh run # Start all services
|
|
463
|
+
gssh run <service...> # Start specific services
|
|
464
|
+
gssh stop # Stop all services
|
|
465
|
+
gssh logs <service> # Tail logs
|
|
466
|
+
|
|
467
|
+
# Events
|
|
468
|
+
gssh events # TUI explorer
|
|
469
|
+
gssh events tail # Live tail
|
|
470
|
+
gssh events query "..." # AI-powered query
|
|
471
|
+
|
|
472
|
+
# Code intelligence
|
|
473
|
+
gssh query "..." # Ask about code + runtime
|
|
474
|
+
|
|
475
|
+
# Stack splitting
|
|
476
|
+
gssh stack # AI-assisted PR splitting
|
|
477
|
+
gssh stack sync # Sync after merges
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## File Structure (New)
|
|
483
|
+
|
|
484
|
+
```
|
|
485
|
+
src/
|
|
486
|
+
├── commands/
|
|
487
|
+
│ ├── host.ts # gssh host *
|
|
488
|
+
│ ├── run.ts # gssh run
|
|
489
|
+
│ ├── events.ts # gssh events
|
|
490
|
+
│ └── query.ts # gssh query
|
|
491
|
+
├── core/
|
|
492
|
+
│ ├── lima/
|
|
493
|
+
│ │ ├── manager.ts # VM lifecycle
|
|
494
|
+
│ │ ├── config.ts # Generate Lima YAML
|
|
495
|
+
│ │ └── connection.ts # Connect to VM
|
|
496
|
+
│ ├── host/
|
|
497
|
+
│ │ ├── tunnel.ts # cloudflared integration
|
|
498
|
+
│ │ └── dns.ts # Cloudflare API
|
|
499
|
+
│ ├── services/
|
|
500
|
+
│ │ ├── supervisor.ts # Process manager
|
|
501
|
+
│ │ ├── dependency.ts # Dependency resolution
|
|
502
|
+
│ │ └── health.ts # Ready checks
|
|
503
|
+
│ ├── events/
|
|
504
|
+
│ │ ├── collector.ts # Event ingestion
|
|
505
|
+
│ │ ├── correlator.ts # eventId chaining
|
|
506
|
+
│ │ ├── store/
|
|
507
|
+
│ │ │ ├── memory.ts
|
|
508
|
+
│ │ │ ├── sqlite.ts
|
|
509
|
+
│ │ │ └── duckdb.ts
|
|
510
|
+
│ │ └── query.ts # Query interface
|
|
511
|
+
│ └── insight/
|
|
512
|
+
│ ├── index.ts # Orchestrator
|
|
513
|
+
│ ├── pattern-gen.ts # NL → ast-grep
|
|
514
|
+
│ ├── scanner.ts # Pattern execution
|
|
515
|
+
│ ├── graph.ts # Dependency graphs
|
|
516
|
+
│ └── ai-synth.ts # AI synthesis
|
|
517
|
+
├── shared/
|
|
518
|
+
│ └── providers/
|
|
519
|
+
│ └── LimaMachineProvider.ts
|
|
520
|
+
└── lib/
|
|
521
|
+
├── ast-grep/
|
|
522
|
+
│ ├── runner.ts # sg CLI wrapper
|
|
523
|
+
│ └── patterns.ts # Common patterns
|
|
524
|
+
└── events/
|
|
525
|
+
└── emitter.ts # SDK for apps
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Platform Support
|
|
531
|
+
|
|
532
|
+
| Platform | VM Isolation | Firecracker | Notes |
|
|
533
|
+
|----------|--------------|-------------|-------|
|
|
534
|
+
| Mac M1 | Lima VM | ❌ | No nested virt |
|
|
535
|
+
| Mac M2 | Lima VM | ❌ | No nested virt |
|
|
536
|
+
| Mac M3+ (macOS 15+) | Lima VM | ✅ (optional) | Nested virt works |
|
|
537
|
+
| Linux | Firecracker | ✅ | Native KVM |
|
|
538
|
+
| Cloud | Firecracker | ✅ | Via Flintlock/Nomad |
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Implementation Progress
|
|
543
|
+
|
|
544
|
+
| Feature | Status | Notes |
|
|
545
|
+
|---------|--------|-------|
|
|
546
|
+
| E2E Encrypted Remote Access | **Complete** | X3DH handshake, relay routing |
|
|
547
|
+
| Web Terminal Client | **Complete** | xterm.js, React |
|
|
548
|
+
| TUI Remote Access | **Complete** | `--relay` flag for TUI |
|
|
549
|
+
| Cloudflare Hosting | **Partial** | Auth, subdomains; missing service routing |
|
|
550
|
+
| Lima VMs | Not Started | Isolation layer for workspaces |
|
|
551
|
+
| Process Runner | Not Started | Depends on Lima |
|
|
552
|
+
| Event Collection | Not Started | Depends on Process Runner |
|
|
553
|
+
| Code Intelligence | Not Started | ast-grep + AI |
|
|
554
|
+
| Stack Splitting | Not Started | AI-assisted PR splitting |
|
|
555
|
+
|
|
556
|
+
### Next Steps
|
|
557
|
+
|
|
558
|
+
1. **Complete Cloudflare Hosting** - Service routing, gateway worker at edge
|
|
559
|
+
2. **Lima VMs** - Workspace isolation on Mac
|
|
560
|
+
3. **Process Runner** - Service management inside VMs
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
*Last updated: 2025-01*
|