@jkwd/inbase 0.1.3 → 0.1.5
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 +3 -1
- package/apps/explorer/scripts/open-editor.d.ts +6 -0
- package/apps/explorer/scripts/open-editor.mjs +219 -0
- package/apps/explorer/scripts/session-store.d.ts +51 -1
- package/apps/explorer/scripts/session-store.mjs +461 -48
- package/apps/explorer/src/App.tsx +178 -78
- package/apps/explorer/src/agentIntent.ts +54 -1
- package/apps/explorer/src/index.css +238 -2
- package/apps/explorer/src/layout.ts +46 -0
- package/apps/explorer/src/scene/FileBlock.tsx +110 -145
- package/apps/explorer/src/scene/FolderArea.tsx +17 -4
- package/apps/explorer/src/scene/MapSelectBorder.tsx +3 -1
- package/apps/explorer/src/scene/MapView.tsx +41 -18
- package/apps/explorer/src/scene/Player.tsx +27 -0
- package/apps/explorer/src/scene/RelationLines.tsx +36 -32
- package/apps/explorer/src/scene/SelectionController.tsx +21 -3
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +451 -0
- package/apps/explorer/src/scene/World.tsx +19 -50
- package/apps/explorer/src/theme.ts +10 -1
- package/apps/explorer/src/types.ts +12 -0
- package/apps/explorer/src/ui/HUD.tsx +699 -390
- package/apps/explorer/vite.config.ts +72 -22
- package/bin/session.mjs +17 -3
- package/package.json +3 -1
- package/skill/inbase/SKILL.md +22 -14
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/inbase-logo.png" alt="InBase — Dive into your codebase" width="520" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
5
|
A first-person 3D map of a JavaScript or TypeScript codebase. Files become blocks, folders become walkable areas, and imports become lines in the air.
|
|
4
6
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function editorFileUri(filePath: string): string
|
|
2
|
+
export function defaultCursorUserDataDir(): string
|
|
3
|
+
export function discoverCursorUserDataDirs(): string[]
|
|
4
|
+
export function openFoldersFromStorage(storage: unknown): string[]
|
|
5
|
+
export function cursorUserDataDirForFile(filePath: string): string | null
|
|
6
|
+
export function openInEditor(filePath: string): boolean
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
export function editorFileUri(filePath) {
|
|
8
|
+
return `vscode://file${encodeURI(filePath)}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function which(command) {
|
|
12
|
+
const result = spawnSync('which', [command], { encoding: 'utf8' })
|
|
13
|
+
if (result.error || result.status !== 0) return null
|
|
14
|
+
return result.stdout.trim().split('\n')[0] || null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function defaultCursorUserDataDir() {
|
|
18
|
+
if (process.platform === 'darwin') {
|
|
19
|
+
return path.join(os.homedir(), 'Library/Application Support/Cursor')
|
|
20
|
+
}
|
|
21
|
+
if (process.platform === 'win32') {
|
|
22
|
+
return path.join(os.homedir(), 'AppData/Roaming/Cursor')
|
|
23
|
+
}
|
|
24
|
+
return path.join(os.homedir(), '.config/Cursor')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function expandUser(value) {
|
|
28
|
+
if (!value) return null
|
|
29
|
+
const trimmed = String(value).trim().replace(/^['"]|['"]$/g, '')
|
|
30
|
+
if (!trimmed) return null
|
|
31
|
+
if (trimmed.startsWith('~')) {
|
|
32
|
+
return path.resolve(os.homedir() + trimmed.slice(1))
|
|
33
|
+
}
|
|
34
|
+
return path.resolve(trimmed)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isCursorUserDataDir(dir) {
|
|
38
|
+
try {
|
|
39
|
+
if (fs.existsSync(path.join(dir, 'User/globalStorage/storage.json'))) return true
|
|
40
|
+
return fs.readdirSync(dir).some((name) => name.endsWith('-main.sock'))
|
|
41
|
+
} catch {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function userDataDirFromHook(hook = process.env.VSCODE_IPC_HOOK) {
|
|
47
|
+
if (!hook || !hook.endsWith('.sock')) return null
|
|
48
|
+
const dir = path.dirname(hook)
|
|
49
|
+
return isCursorUserDataDir(dir) ? dir : null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function userDataDirsFromProcessList() {
|
|
53
|
+
const result = spawnSync('ps', ['-ax', '-o', 'command='], { encoding: 'utf8' })
|
|
54
|
+
if (result.error || result.status !== 0) return []
|
|
55
|
+
const dirs = []
|
|
56
|
+
for (const line of result.stdout.split('\n')) {
|
|
57
|
+
const match = line.match(/--user-data-dir(?:=|\s+)(\S+)/)
|
|
58
|
+
if (!match) continue
|
|
59
|
+
const dir = expandUser(match[1])
|
|
60
|
+
if (dir && isCursorUserDataDir(dir)) dirs.push(dir)
|
|
61
|
+
}
|
|
62
|
+
return dirs
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function profileDirsInHome() {
|
|
66
|
+
let names = []
|
|
67
|
+
try {
|
|
68
|
+
names = fs.readdirSync(os.homedir())
|
|
69
|
+
} catch {
|
|
70
|
+
return []
|
|
71
|
+
}
|
|
72
|
+
return names
|
|
73
|
+
.filter((name) => name.startsWith('cursor-profile'))
|
|
74
|
+
.map((name) => path.join(os.homedir(), name))
|
|
75
|
+
.filter(isCursorUserDataDir)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function discoverCursorUserDataDirs() {
|
|
79
|
+
const dirs = new Set()
|
|
80
|
+
const explicit = expandUser(
|
|
81
|
+
process.env.INBASE_CURSOR_USER_DATA_DIR || process.env.CURSOR_USER_DATA_DIR,
|
|
82
|
+
)
|
|
83
|
+
if (explicit && isCursorUserDataDir(explicit)) dirs.add(explicit)
|
|
84
|
+
const hookDir = userDataDirFromHook()
|
|
85
|
+
if (hookDir) dirs.add(hookDir)
|
|
86
|
+
const def = defaultCursorUserDataDir()
|
|
87
|
+
if (isCursorUserDataDir(def)) dirs.add(def)
|
|
88
|
+
for (const dir of profileDirsInHome()) dirs.add(dir)
|
|
89
|
+
for (const dir of userDataDirsFromProcessList()) dirs.add(dir)
|
|
90
|
+
return [...dirs]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function folderPathFromUri(value) {
|
|
94
|
+
if (typeof value !== 'string' || !value) return null
|
|
95
|
+
try {
|
|
96
|
+
return value.startsWith('file:') ? fileURLToPath(value) : path.resolve(value)
|
|
97
|
+
} catch {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function fileIsInside(folder, filePath) {
|
|
103
|
+
const root = path.resolve(folder)
|
|
104
|
+
const file = path.resolve(filePath)
|
|
105
|
+
return file === root || file.startsWith(`${root}${path.sep}`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function openFoldersFromStorage(storage) {
|
|
109
|
+
const state = storage?.windowsState
|
|
110
|
+
if (!state || typeof state !== 'object') return []
|
|
111
|
+
const windows = [
|
|
112
|
+
state.lastActiveWindow,
|
|
113
|
+
...(Array.isArray(state.openedWindows) ? state.openedWindows : []),
|
|
114
|
+
]
|
|
115
|
+
const folders = []
|
|
116
|
+
for (const window of windows) {
|
|
117
|
+
const folder = folderPathFromUri(window?.folder)
|
|
118
|
+
if (folder) folders.push(folder)
|
|
119
|
+
}
|
|
120
|
+
return folders
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readOpenFolders(userDataDir) {
|
|
124
|
+
const file = path.join(userDataDir, 'User/globalStorage/storage.json')
|
|
125
|
+
try {
|
|
126
|
+
return openFoldersFromStorage(JSON.parse(fs.readFileSync(file, 'utf8')))
|
|
127
|
+
} catch {
|
|
128
|
+
return []
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function lastActiveFolder(userDataDir) {
|
|
133
|
+
const file = path.join(userDataDir, 'User/globalStorage/storage.json')
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
136
|
+
return folderPathFromUri(parsed?.windowsState?.lastActiveWindow?.folder)
|
|
137
|
+
} catch {
|
|
138
|
+
return null
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function cursorUserDataDirForFile(filePath) {
|
|
143
|
+
const explicit = expandUser(
|
|
144
|
+
process.env.INBASE_CURSOR_USER_DATA_DIR || process.env.CURSOR_USER_DATA_DIR,
|
|
145
|
+
)
|
|
146
|
+
if (explicit && isCursorUserDataDir(explicit)) return explicit
|
|
147
|
+
|
|
148
|
+
const hookDir = userDataDirFromHook()
|
|
149
|
+
let best = null
|
|
150
|
+
let bestScore = 0
|
|
151
|
+
for (const dir of discoverCursorUserDataDirs()) {
|
|
152
|
+
const folders = readOpenFolders(dir)
|
|
153
|
+
if (!folders.some((folder) => fileIsInside(folder, filePath))) continue
|
|
154
|
+
let score = 1
|
|
155
|
+
if (hookDir && path.resolve(hookDir) === path.resolve(dir)) score += 2
|
|
156
|
+
const last = lastActiveFolder(dir)
|
|
157
|
+
if (last && fileIsInside(last, filePath)) score += 2
|
|
158
|
+
if (score > bestScore) {
|
|
159
|
+
best = dir
|
|
160
|
+
bestScore = score
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return best ?? hookDir
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function cursorCliPaths() {
|
|
167
|
+
const home = os.homedir()
|
|
168
|
+
const fromEnv =
|
|
169
|
+
process.env.CURSOR_CLI && fs.existsSync(process.env.CURSOR_CLI)
|
|
170
|
+
? process.env.CURSOR_CLI
|
|
171
|
+
: null
|
|
172
|
+
return [
|
|
173
|
+
fromEnv,
|
|
174
|
+
which('cursor'),
|
|
175
|
+
'/Applications/Cursor.app/Contents/Resources/app/bin/cursor',
|
|
176
|
+
path.join(home, '.local/bin/cursor'),
|
|
177
|
+
'/usr/local/bin/cursor',
|
|
178
|
+
'/opt/homebrew/bin/cursor',
|
|
179
|
+
].filter((candidate, index, list) => {
|
|
180
|
+
if (!candidate || typeof candidate !== 'string') return false
|
|
181
|
+
return list.indexOf(candidate) === index
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function editorEnv() {
|
|
186
|
+
const env = { ...process.env }
|
|
187
|
+
delete env.ELECTRON_RUN_AS_NODE
|
|
188
|
+
delete env.ELECTRON_NO_ASAR
|
|
189
|
+
delete env.CURSOR_AGENT
|
|
190
|
+
// A second Cursor instance uses its own sockets. Inherited hooks from
|
|
191
|
+
// another profile would send --goto to the wrong window.
|
|
192
|
+
delete env.VSCODE_IPC_HOOK
|
|
193
|
+
delete env.VSCODE_IPC_HOOK_CLI
|
|
194
|
+
return env
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function runEditor(command, args) {
|
|
198
|
+
if (command.includes(path.sep) && !fs.existsSync(command)) return false
|
|
199
|
+
const result = spawnSync(command, args, {
|
|
200
|
+
stdio: 'ignore',
|
|
201
|
+
env: editorEnv(),
|
|
202
|
+
})
|
|
203
|
+
return !result.error && result.status === 0
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function openInEditor(filePath) {
|
|
207
|
+
const userDataDir = cursorUserDataDirForFile(filePath)
|
|
208
|
+
const goto = ['--goto', `${filePath}:1`]
|
|
209
|
+
if (userDataDir) {
|
|
210
|
+
goto.unshift('--user-data-dir', userDataDir)
|
|
211
|
+
}
|
|
212
|
+
for (const command of cursorCliPaths()) {
|
|
213
|
+
if (runEditor(command, goto)) return true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const code = which('code')
|
|
217
|
+
if (code && runEditor(code, goto)) return true
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
@@ -45,10 +45,30 @@ export type DiffManifest = {
|
|
|
45
45
|
export function assertSessionId(value: unknown): string
|
|
46
46
|
export function readActiveSession(dataDir: string): string | null
|
|
47
47
|
export function writeActiveSession(dataDir: string, sessionId: string | null): void
|
|
48
|
+
export function touchSessionConnection(dataDir: string, sessionId: string): void
|
|
49
|
+
export function isSessionConnected(
|
|
50
|
+
dataDir: string,
|
|
51
|
+
sessionId: string,
|
|
52
|
+
waiterIds?: Set<string>,
|
|
53
|
+
): boolean
|
|
54
|
+
export function listStoredSessionIds(dataDir: string): string[]
|
|
55
|
+
export function listOpenSessionIds(dataDir: string): string[]
|
|
56
|
+
export function discardInactiveDiffSessions(
|
|
57
|
+
dataDir: string,
|
|
58
|
+
targetRoot?: string | null,
|
|
59
|
+
waiterIds?: Iterable<string>,
|
|
60
|
+
): string[]
|
|
61
|
+
export function listSessionIntents(
|
|
62
|
+
dataDir: string,
|
|
63
|
+
knownFileIds?: string[],
|
|
64
|
+
): Array<Record<string, unknown>>
|
|
48
65
|
export function readBlueprintSession(dataDir: string): string | null
|
|
49
66
|
export function writeBlueprintSession(dataDir: string, sessionId: string | null): void
|
|
50
67
|
export function readManifest(dataDir: string, sessionId: string): DiffManifest | null
|
|
51
68
|
export function writeManifest(dataDir: string, manifest: DiffManifest): void
|
|
69
|
+
export function isSessionStopped(dataDir: string, sessionId: string): boolean
|
|
70
|
+
export function isWorkflowStopped(dataDir: string, sessionId: string): boolean
|
|
71
|
+
export function sessionStoppedError(sessionId: string): Error
|
|
52
72
|
export function startSession(
|
|
53
73
|
dataDir: string,
|
|
54
74
|
input: {
|
|
@@ -119,6 +139,36 @@ export function sessionIntent(
|
|
|
119
139
|
knownFileIds?: string[],
|
|
120
140
|
selectedDiffId?: string,
|
|
121
141
|
): Record<string, unknown> | null
|
|
142
|
+
export function resolveTargetFile(
|
|
143
|
+
targetRoot: string,
|
|
144
|
+
fileId: string,
|
|
145
|
+
): { id: string; absolute: string }
|
|
146
|
+
export function captureBaseline(
|
|
147
|
+
dataDir: string,
|
|
148
|
+
sessionId: string,
|
|
149
|
+
targetRoot: string,
|
|
150
|
+
fileIds?: string[],
|
|
151
|
+
): { files: Record<string, { existed: boolean }> }
|
|
152
|
+
export function restoreBaseline(
|
|
153
|
+
dataDir: string,
|
|
154
|
+
sessionId: string,
|
|
155
|
+
targetRoot: string,
|
|
156
|
+
): void
|
|
157
|
+
export function materializeDiff(
|
|
158
|
+
dataDir: string,
|
|
159
|
+
targetRoot: string,
|
|
160
|
+
sessionId: string,
|
|
161
|
+
diffId?: string | null,
|
|
162
|
+
): DiffManifest
|
|
163
|
+
export function inspectTargetFile(
|
|
164
|
+
dataDir: string,
|
|
165
|
+
targetRoot: string,
|
|
166
|
+
input?: {
|
|
167
|
+
sessionId?: string | null
|
|
168
|
+
diffId?: string | null
|
|
169
|
+
fileId?: string | null
|
|
170
|
+
},
|
|
171
|
+
): string | null
|
|
122
172
|
export function appendDiff(
|
|
123
173
|
dataDir: string,
|
|
124
174
|
targetRoot: string,
|
|
@@ -142,7 +192,7 @@ export function requestReplan(
|
|
|
142
192
|
export function stopSession(
|
|
143
193
|
dataDir: string,
|
|
144
194
|
sessionId: string,
|
|
145
|
-
|
|
195
|
+
targetRoot?: string | null,
|
|
146
196
|
): null
|
|
147
197
|
export function decideDiff(
|
|
148
198
|
dataDir: string,
|