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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for workspace fuzzy matching
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A workspace candidate for fuzzy matching
|
|
7
|
+
*/
|
|
8
|
+
export interface WorkspaceCandidate {
|
|
9
|
+
/** Workspace name */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Absolute path to workspace */
|
|
12
|
+
path: string;
|
|
13
|
+
/** Current git branch */
|
|
14
|
+
branch: string;
|
|
15
|
+
/** Commits ahead of remote */
|
|
16
|
+
ahead: number;
|
|
17
|
+
/** Commits behind remote */
|
|
18
|
+
behind: number;
|
|
19
|
+
/** Number of uncommitted changes */
|
|
20
|
+
uncommittedChanges: number;
|
|
21
|
+
/** Last commit message */
|
|
22
|
+
lastCommit: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Result of fuzzy matching
|
|
27
|
+
*/
|
|
28
|
+
export interface FuzzyMatch {
|
|
29
|
+
/** Original candidate item */
|
|
30
|
+
item: WorkspaceCandidate;
|
|
31
|
+
/** Match score (0-100, higher is better) */
|
|
32
|
+
score: number;
|
|
33
|
+
/** Indices of matched characters in item.name */
|
|
34
|
+
matchedIndices: number[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Workspace with ranking information
|
|
39
|
+
*/
|
|
40
|
+
export interface RankedWorkspace {
|
|
41
|
+
/** Workspace candidate */
|
|
42
|
+
workspace: WorkspaceCandidate;
|
|
43
|
+
/** Fuzzy match score */
|
|
44
|
+
matchScore: number;
|
|
45
|
+
/** Final ranking score (includes bonuses) */
|
|
46
|
+
finalScore: number;
|
|
47
|
+
/** Character indices that matched */
|
|
48
|
+
matchedIndices: number[];
|
|
49
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for workspace management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Information about a git worktree workspace
|
|
7
|
+
*/
|
|
8
|
+
export interface WorktreeInfo {
|
|
9
|
+
/** Workspace name (directory name) */
|
|
10
|
+
name: string
|
|
11
|
+
/** Absolute path to the workspace directory */
|
|
12
|
+
path: string
|
|
13
|
+
/** Current git branch */
|
|
14
|
+
branch: string
|
|
15
|
+
/** Number of commits ahead of base branch */
|
|
16
|
+
ahead: number
|
|
17
|
+
/** Number of commits behind base branch */
|
|
18
|
+
behind: number
|
|
19
|
+
/** Number of uncommitted changes */
|
|
20
|
+
uncommittedChanges: number
|
|
21
|
+
/** Last commit message */
|
|
22
|
+
lastCommit: string
|
|
23
|
+
/** Last commit date */
|
|
24
|
+
lastCommitDate: Date
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Project information for listing
|
|
29
|
+
*/
|
|
30
|
+
export interface ProjectInfo {
|
|
31
|
+
/** Project name */
|
|
32
|
+
name: string
|
|
33
|
+
/** GitHub repository (owner/repo) */
|
|
34
|
+
repository: string
|
|
35
|
+
/** Absolute path to project directory */
|
|
36
|
+
path: string
|
|
37
|
+
/** Number of workspaces in this project */
|
|
38
|
+
workspaceCount: number
|
|
39
|
+
/** Whether this is the current project */
|
|
40
|
+
isCurrent: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Dependency information
|
|
45
|
+
*/
|
|
46
|
+
export interface Dependency {
|
|
47
|
+
/** Display name of the dependency */
|
|
48
|
+
name: string
|
|
49
|
+
/** Command to check (e.g., "gh", "git") */
|
|
50
|
+
command: string
|
|
51
|
+
/** Arguments to run for version check */
|
|
52
|
+
checkArgs: string[]
|
|
53
|
+
/** URL for installation instructions */
|
|
54
|
+
installUrl: string
|
|
55
|
+
/** Optional custom auth check function */
|
|
56
|
+
authCheck?: () => Promise<boolean>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Options for creating a workspace
|
|
61
|
+
*/
|
|
62
|
+
export interface CreateWorkspaceOptions {
|
|
63
|
+
/** Workspace name */
|
|
64
|
+
name: string
|
|
65
|
+
/** Branch name (defaults to workspace name) */
|
|
66
|
+
branchName?: string
|
|
67
|
+
/** Base branch to create from (defaults to project base branch) */
|
|
68
|
+
fromBranch?: string
|
|
69
|
+
/** Whether to skip opening interactive shell */
|
|
70
|
+
noShell?: boolean
|
|
71
|
+
/** Whether to skip running setup commands */
|
|
72
|
+
noSetup?: boolean
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Label attached to an issue
|
|
77
|
+
*/
|
|
78
|
+
export interface Label {
|
|
79
|
+
/** Unique identifier for the label */
|
|
80
|
+
id: string
|
|
81
|
+
/** Display name of the label */
|
|
82
|
+
name: string
|
|
83
|
+
/** Hex color code for the label */
|
|
84
|
+
color: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* User assigned to an issue
|
|
89
|
+
*/
|
|
90
|
+
export interface User {
|
|
91
|
+
/** Unique identifier for the user */
|
|
92
|
+
id: string
|
|
93
|
+
/** Display name of the user */
|
|
94
|
+
name: string
|
|
95
|
+
/** Email address of the user */
|
|
96
|
+
email: string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Issue state information
|
|
101
|
+
*/
|
|
102
|
+
export interface IssueState {
|
|
103
|
+
/** Unique identifier for the state */
|
|
104
|
+
id: string
|
|
105
|
+
/** Display name of the state (e.g., "In Progress", "Done") */
|
|
106
|
+
name: string
|
|
107
|
+
/** State type (e.g., "started", "completed", "canceled") */
|
|
108
|
+
type: string
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Linear issue attachment
|
|
113
|
+
*/
|
|
114
|
+
export interface LinearAttachment {
|
|
115
|
+
/** Unique identifier for the attachment */
|
|
116
|
+
id: string
|
|
117
|
+
/** URL to the attachment */
|
|
118
|
+
url: string
|
|
119
|
+
/** Title of the attachment */
|
|
120
|
+
title: string | null
|
|
121
|
+
/** Source type (e.g., "upload", "url") */
|
|
122
|
+
sourceType: string | null
|
|
123
|
+
/** Creation timestamp */
|
|
124
|
+
createdAt: Date
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Linear issue for workspace creation
|
|
129
|
+
*/
|
|
130
|
+
export interface LinearIssue {
|
|
131
|
+
/** Issue ID */
|
|
132
|
+
id: string
|
|
133
|
+
/** Issue identifier (e.g., "ENG-123") */
|
|
134
|
+
identifier: string
|
|
135
|
+
/** Issue title */
|
|
136
|
+
title: string
|
|
137
|
+
/** Issue state */
|
|
138
|
+
state: Promise<IssueState> | undefined
|
|
139
|
+
/** Issue description/body (can be null) */
|
|
140
|
+
description: string | null
|
|
141
|
+
/** Web URL to view the issue */
|
|
142
|
+
url: string
|
|
143
|
+
/** User assigned to the issue (null if unassigned) */
|
|
144
|
+
assignee: Promise<User> | undefined
|
|
145
|
+
/** Timestamp when issue was created */
|
|
146
|
+
createdAt: Date
|
|
147
|
+
/** Timestamp when issue was last updated */
|
|
148
|
+
updatedAt: Date
|
|
149
|
+
/** Issue attachments (images, files, etc.) - lazy-loaded function */
|
|
150
|
+
attachments: () => Promise<LinearAttachment[]>
|
|
151
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Socket } from "bun";
|
|
2
|
+
|
|
3
|
+
type WritableData = Buffer | Uint8Array | ArrayBuffer;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Bun sockets are unbuffered. `socket.write()` can write fewer bytes than provided
|
|
7
|
+
* under backpressure. For framed protocols, partial writes will desync the reader.
|
|
8
|
+
*
|
|
9
|
+
* This helper buffers pending bytes and flushes them when the socket drains.
|
|
10
|
+
*/
|
|
11
|
+
export function createBufferedSocketWriter(socket: Socket<any>) {
|
|
12
|
+
const queue: Buffer[] = [];
|
|
13
|
+
let headOffset = 0;
|
|
14
|
+
|
|
15
|
+
const flush = () => {
|
|
16
|
+
while (queue.length > 0) {
|
|
17
|
+
const head = queue[0]!;
|
|
18
|
+
const remaining = head.length - headOffset;
|
|
19
|
+
|
|
20
|
+
if (remaining <= 0) {
|
|
21
|
+
queue.shift();
|
|
22
|
+
headOffset = 0;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const written = socket.write(head, headOffset, remaining);
|
|
27
|
+
|
|
28
|
+
// -1: closed/shutting down. 0: backpressure.
|
|
29
|
+
if (written <= 0) return;
|
|
30
|
+
|
|
31
|
+
headOffset += written;
|
|
32
|
+
if (headOffset >= head.length) {
|
|
33
|
+
queue.shift();
|
|
34
|
+
headOffset = 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const write = (data: WritableData) => {
|
|
40
|
+
// Copy into a stable Buffer (avoids subarray lifetime issues and ensures queue owns bytes)
|
|
41
|
+
const buf = Buffer.from(data as any);
|
|
42
|
+
|
|
43
|
+
// Fast-path if nothing queued.
|
|
44
|
+
if (queue.length === 0) {
|
|
45
|
+
const written = socket.write(buf);
|
|
46
|
+
|
|
47
|
+
if (written <= 0) {
|
|
48
|
+
queue.push(buf);
|
|
49
|
+
headOffset = 0;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (written >= buf.length) return;
|
|
54
|
+
|
|
55
|
+
queue.push(buf);
|
|
56
|
+
headOffset = written;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
queue.push(buf);
|
|
61
|
+
flush();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const clear = () => {
|
|
65
|
+
queue.length = 0;
|
|
66
|
+
headOffset = 0;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const pendingBytes = () => {
|
|
70
|
+
let total = 0;
|
|
71
|
+
for (let i = 0; i < queue.length; i++) total += queue[i]!.length;
|
|
72
|
+
return Math.max(0, total - headOffset);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return { write, flush, clear, pendingBytes };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency checking utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import type { Dependency } from '../types/workspace.js';
|
|
8
|
+
import { DependencyError, GitHubAuthError } from '../types/errors.js';
|
|
9
|
+
import { logger } from './logger.js';
|
|
10
|
+
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Required system dependencies
|
|
15
|
+
*/
|
|
16
|
+
export const REQUIRED_DEPS: Dependency[] = [
|
|
17
|
+
{
|
|
18
|
+
name: 'GitHub CLI',
|
|
19
|
+
command: 'gh',
|
|
20
|
+
checkArgs: ['--version'],
|
|
21
|
+
installUrl: 'https://cli.github.com/',
|
|
22
|
+
authCheck: async () => {
|
|
23
|
+
try {
|
|
24
|
+
await execAsync('gh auth status');
|
|
25
|
+
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'Git',
|
|
33
|
+
command: 'git',
|
|
34
|
+
checkArgs: ['--version'],
|
|
35
|
+
installUrl: 'https://git-scm.com/',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'jq',
|
|
39
|
+
command: 'jq',
|
|
40
|
+
checkArgs: ['--version'],
|
|
41
|
+
installUrl: 'https://stedolan.github.io/jq/',
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if a command exists
|
|
47
|
+
*/
|
|
48
|
+
async function commandExists(command: string, checkArgs: string[]): Promise<boolean> {
|
|
49
|
+
try {
|
|
50
|
+
await execAsync(`${command} ${checkArgs.join(' ')}`);
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check all required dependencies
|
|
59
|
+
*/
|
|
60
|
+
export async function checkDependencies(deps: Dependency[] = REQUIRED_DEPS): Promise<{
|
|
61
|
+
missing: Dependency[];
|
|
62
|
+
allPresent: boolean;
|
|
63
|
+
}> {
|
|
64
|
+
const missing: Dependency[] = [];
|
|
65
|
+
|
|
66
|
+
for (const dep of deps) {
|
|
67
|
+
const exists = await commandExists(dep.command, dep.checkArgs);
|
|
68
|
+
if (!exists) {
|
|
69
|
+
missing.push(dep);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
missing,
|
|
75
|
+
allPresent: missing.length === 0,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check GitHub CLI authentication
|
|
81
|
+
*/
|
|
82
|
+
export async function checkGitHubAuth(): Promise<void> {
|
|
83
|
+
const ghDep = REQUIRED_DEPS.find((d) => d.command === 'gh');
|
|
84
|
+
if (!ghDep?.authCheck) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const isAuthenticated = await ghDep.authCheck();
|
|
89
|
+
if (!isAuthenticated) {
|
|
90
|
+
throw new GitHubAuthError();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Display missing dependencies and exit
|
|
96
|
+
*/
|
|
97
|
+
export function displayMissingDependencies(missing: Dependency[]): never {
|
|
98
|
+
logger.error('Missing required dependencies:\n');
|
|
99
|
+
|
|
100
|
+
for (const dep of missing) {
|
|
101
|
+
logger.log(` ${dep.name} (${dep.command})`);
|
|
102
|
+
logger.dim(` Install: ${dep.installUrl}\n`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw new DependencyError(
|
|
106
|
+
'Please install the missing dependencies and try again.'
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check dependencies and throw if any are missing
|
|
112
|
+
*/
|
|
113
|
+
export async function ensureDependencies(deps?: Dependency[]): Promise<void> {
|
|
114
|
+
const { missing, allPresent } = await checkDependencies(deps);
|
|
115
|
+
|
|
116
|
+
if (!allPresent) {
|
|
117
|
+
displayMissingDependencies(missing);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check specific dependencies (subset of all required)
|
|
123
|
+
*/
|
|
124
|
+
export async function checkSpecificDeps(commands: string[]): Promise<void> {
|
|
125
|
+
const depsToCheck = REQUIRED_DEPS.filter((d) => commands.includes(d.command));
|
|
126
|
+
await ensureDependencies(depsToCheck);
|
|
127
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzzy matching algorithm
|
|
3
|
+
* Matches query against candidates and returns scored results
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FuzzyMatch, WorkspaceCandidate } from '../types/workspace-fuzzy.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Perform fuzzy matching on workspace candidates
|
|
10
|
+
*
|
|
11
|
+
* Algorithm:
|
|
12
|
+
* 1. For each candidate, calculate match score
|
|
13
|
+
* 2. Filter out scores below threshold (30)
|
|
14
|
+
* 3. Sort by score descending
|
|
15
|
+
*
|
|
16
|
+
* @param query Search string
|
|
17
|
+
* @param candidates List of workspace candidates
|
|
18
|
+
* @returns Sorted array of matches with scores
|
|
19
|
+
*/
|
|
20
|
+
export function fuzzyMatch(
|
|
21
|
+
query: string,
|
|
22
|
+
candidates: WorkspaceCandidate[]
|
|
23
|
+
): FuzzyMatch[] {
|
|
24
|
+
const matches: FuzzyMatch[] = [];
|
|
25
|
+
|
|
26
|
+
for (const candidate of candidates) {
|
|
27
|
+
const result = calculateMatchScore(query, candidate.name);
|
|
28
|
+
|
|
29
|
+
if (result.score >= 30) { // Minimum threshold
|
|
30
|
+
matches.push({
|
|
31
|
+
item: candidate,
|
|
32
|
+
score: result.score,
|
|
33
|
+
matchedIndices: result.matchedIndices,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Sort by score descending
|
|
39
|
+
matches.sort((a, b) => b.score - a.score);
|
|
40
|
+
|
|
41
|
+
return matches;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Calculate fuzzy match score between query and candidate
|
|
46
|
+
*
|
|
47
|
+
* Scoring rules:
|
|
48
|
+
* - All query characters must exist in order: otherwise score = 0
|
|
49
|
+
* - Consecutive character matches: +10 points per consecutive char
|
|
50
|
+
* - Word boundary matches: +5 points (e.g., "fb" matches "feature-branch")
|
|
51
|
+
* - Exact case match: +2 points per char
|
|
52
|
+
* - Start of string match: +15 points
|
|
53
|
+
* - Character proximity: -1 point per gap
|
|
54
|
+
* - Base score: 50 points if all chars match
|
|
55
|
+
*
|
|
56
|
+
* @param query Search string
|
|
57
|
+
* @param candidate String to match against
|
|
58
|
+
* @returns Object with score and matched character indices
|
|
59
|
+
*/
|
|
60
|
+
function calculateMatchScore(
|
|
61
|
+
query: string,
|
|
62
|
+
candidate: string
|
|
63
|
+
): { score: number; matchedIndices: number[] } {
|
|
64
|
+
if (!query || !candidate) {
|
|
65
|
+
return { score: 0, matchedIndices: [] };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const queryLower = query.toLowerCase();
|
|
69
|
+
const candidateLower = candidate.toLowerCase();
|
|
70
|
+
|
|
71
|
+
// Find all query characters in candidate
|
|
72
|
+
const matchedIndices: number[] = [];
|
|
73
|
+
let candidateIndex = 0;
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < queryLower.length; i++) {
|
|
76
|
+
const queryChar = queryLower[i];
|
|
77
|
+
const foundIndex = candidateLower.indexOf(queryChar, candidateIndex);
|
|
78
|
+
|
|
79
|
+
if (foundIndex === -1) {
|
|
80
|
+
// Character not found - no match
|
|
81
|
+
return { score: 0, matchedIndices: [] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
matchedIndices.push(foundIndex);
|
|
85
|
+
candidateIndex = foundIndex + 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// All characters matched - start with base score
|
|
89
|
+
let score = 50;
|
|
90
|
+
|
|
91
|
+
// Bonus: Start of string match
|
|
92
|
+
if (matchedIndices[0] === 0) {
|
|
93
|
+
score += 15;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Calculate bonuses for consecutive matches, word boundaries, and case
|
|
97
|
+
for (let i = 0; i < matchedIndices.length; i++) {
|
|
98
|
+
const index = matchedIndices[i];
|
|
99
|
+
const queryChar = query[i];
|
|
100
|
+
const candidateChar = candidate[index];
|
|
101
|
+
|
|
102
|
+
// Consecutive match bonus
|
|
103
|
+
if (i > 0 && matchedIndices[i] === matchedIndices[i - 1] + 1) {
|
|
104
|
+
score += 10;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Word boundary bonus (after -, _, /, or at start)
|
|
108
|
+
if (index === 0 || ['-', '_', '/'].includes(candidate[index - 1])) {
|
|
109
|
+
score += 5;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Exact case match bonus
|
|
113
|
+
if (queryChar === candidateChar) {
|
|
114
|
+
score += 2;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Proximity penalty (gap between matched characters)
|
|
118
|
+
if (i > 0) {
|
|
119
|
+
const gap = matchedIndices[i] - matchedIndices[i - 1] - 1;
|
|
120
|
+
score -= gap; // -1 point per character gap
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { score, matchedIndices };
|
|
125
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging utilities with chalk for colored output
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Log levels
|
|
9
|
+
*/
|
|
10
|
+
export type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'debug';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Logger instance
|
|
14
|
+
*/
|
|
15
|
+
class Logger {
|
|
16
|
+
private debugMode: boolean = false;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Enable or disable debug mode
|
|
20
|
+
*/
|
|
21
|
+
setDebugMode(enabled: boolean): void {
|
|
22
|
+
this.debugMode = enabled;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Log an info message
|
|
27
|
+
*/
|
|
28
|
+
info(message: string): void {
|
|
29
|
+
console.log(chalk.blue('ℹ'), message);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Log a success message
|
|
34
|
+
*/
|
|
35
|
+
success(message: string): void {
|
|
36
|
+
console.log(chalk.green('✓'), message);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Log a warning message
|
|
41
|
+
*/
|
|
42
|
+
warning(message: string): void {
|
|
43
|
+
console.log(chalk.yellow('⚠'), message);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Log an error message
|
|
48
|
+
*/
|
|
49
|
+
error(message: string): void {
|
|
50
|
+
console.error(chalk.red('✗'), message);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Log a debug message (only if debug mode is enabled)
|
|
55
|
+
*/
|
|
56
|
+
debug(message: string): void {
|
|
57
|
+
if (this.debugMode) {
|
|
58
|
+
console.log(chalk.gray('DEBUG:'), message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Log a message without any prefix
|
|
64
|
+
*/
|
|
65
|
+
log(message: string): void {
|
|
66
|
+
console.log(message);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Log a dim/muted message
|
|
71
|
+
*/
|
|
72
|
+
dim(message: string): void {
|
|
73
|
+
console.log(chalk.dim(message));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Log a bold message
|
|
78
|
+
*/
|
|
79
|
+
bold(message: string): void {
|
|
80
|
+
console.log(chalk.bold(message));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create a spinner-like loading message
|
|
85
|
+
*/
|
|
86
|
+
loading(message: string): () => void {
|
|
87
|
+
process.stdout.write(chalk.blue('⠋') + ' ' + message);
|
|
88
|
+
|
|
89
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
90
|
+
let i = 0;
|
|
91
|
+
|
|
92
|
+
const interval = setInterval(() => {
|
|
93
|
+
process.stdout.write('\r' + chalk.blue(frames[i]) + ' ' + message);
|
|
94
|
+
i = (i + 1) % frames.length;
|
|
95
|
+
}, 80);
|
|
96
|
+
|
|
97
|
+
// Return a function to stop the spinner
|
|
98
|
+
return () => {
|
|
99
|
+
clearInterval(interval);
|
|
100
|
+
process.stdout.write('\r' + ' '.repeat(message.length + 2) + '\r');
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Format a command for display
|
|
106
|
+
*/
|
|
107
|
+
command(cmd: string): string {
|
|
108
|
+
return chalk.cyan(cmd);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Format a path for display
|
|
113
|
+
*/
|
|
114
|
+
path(path: string): string {
|
|
115
|
+
return chalk.magenta(path);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Format a highlight for display
|
|
120
|
+
*/
|
|
121
|
+
highlight(text: string): string {
|
|
122
|
+
return chalk.yellow(text);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Export singleton instance
|
|
127
|
+
export const logger = new Logger();
|