tabby-tabbyspaces 0.0.1 → 0.1.0
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 +28 -2
- package/CHANGELOG.md +46 -20
- package/CLAUDE.md +163 -15
- package/README.md +71 -61
- package/RELEASE.md +91 -0
- package/TEST_MCP.md +176 -0
- package/TODO.md +72 -0
- package/cdp-click.js +22 -0
- package/cdp-test.js +28 -0
- package/dist/components/paneEditor.component.d.ts +6 -1
- package/dist/components/paneEditor.component.d.ts.map +1 -1
- package/dist/components/splitPreview.component.d.ts +22 -7
- package/dist/components/splitPreview.component.d.ts.map +1 -1
- package/dist/components/workspaceEditor.component.d.ts +30 -4
- package/dist/components/workspaceEditor.component.d.ts.map +1 -1
- package/dist/components/workspaceList.component.d.ts +21 -9
- package/dist/components/workspaceList.component.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.LICENSE.txt +1 -1
- package/dist/index.js.map +1 -1
- package/dist/models/workspace.model.d.ts +4 -2
- package/dist/models/workspace.model.d.ts.map +1 -1
- package/dist/package.json +26 -0
- package/dist/providers/settings.provider.d.ts.map +1 -1
- package/dist/providers/toolbar.provider.d.ts +4 -1
- package/dist/providers/toolbar.provider.d.ts.map +1 -1
- package/dist/services/startupCommand.service.d.ts +20 -0
- package/dist/services/startupCommand.service.d.ts.map +1 -0
- package/dist/services/workspaceEditor.service.d.ts +11 -3
- package/dist/services/workspaceEditor.service.d.ts.map +1 -1
- package/docs/marketing_status.md +92 -0
- package/package.json +2 -7
- package/screenshots/editor.png +0 -0
- package/screenshots/pane-edit.png +0 -0
- package/scripts/build-prod.js +39 -0
- package/src/components/paneEditor.component.pug +2 -2
- package/src/components/paneEditor.component.ts +19 -1
- package/src/components/splitPreview.component.pug +45 -5
- package/src/components/splitPreview.component.scss +79 -22
- package/src/components/splitPreview.component.ts +91 -16
- package/src/components/workspaceEditor.component.pug +130 -70
- package/src/components/workspaceEditor.component.scss +205 -120
- package/src/components/workspaceEditor.component.ts +193 -6
- package/src/components/workspaceList.component.pug +31 -20
- package/src/components/workspaceList.component.scss +12 -6
- package/src/components/workspaceList.component.ts +116 -34
- package/src/index.ts +2 -0
- package/src/models/workspace.model.ts +33 -6
- package/src/providers/settings.provider.ts +2 -2
- package/src/providers/toolbar.provider.ts +41 -10
- package/src/services/startupCommand.service.ts +142 -0
- package/src/services/workspaceEditor.service.ts +70 -38
- package/test_cdp.py +50 -0
- package/RELEASE_PLAN.md +0 -161
- package/screenshots/workspace-edit.png +0 -0
|
@@ -11,22 +11,31 @@ import {
|
|
|
11
11
|
TabbySplitLayoutProfile,
|
|
12
12
|
} from '../models/workspace.model'
|
|
13
13
|
import { CONFIG_KEY, DISPLAY_NAME } from '../build-config'
|
|
14
|
+
import { PendingCommand } from './startupCommand.service'
|
|
14
15
|
|
|
15
16
|
@Injectable({ providedIn: 'root' })
|
|
16
17
|
export class WorkspaceEditorService {
|
|
18
|
+
private cachedProfiles: TabbyProfile[] = []
|
|
19
|
+
|
|
17
20
|
constructor(
|
|
18
21
|
private config: ConfigService,
|
|
19
22
|
private notifications: NotificationsService,
|
|
20
23
|
private profilesService: ProfilesService
|
|
21
24
|
) {}
|
|
22
25
|
|
|
26
|
+
private async cacheProfiles(): Promise<void> {
|
|
27
|
+
this.cachedProfiles = (await this.profilesService.getProfiles()) as TabbyProfile[]
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
getWorkspaces(): Workspace[] {
|
|
24
|
-
return this.config.store[CONFIG_KEY]?.workspaces ?? []
|
|
31
|
+
return this.config.store?.[CONFIG_KEY]?.workspaces ?? []
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
async saveWorkspaces(workspaces: Workspace[]): Promise<boolean> {
|
|
35
|
+
if (!this.config.store?.[CONFIG_KEY]) {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
28
38
|
this.config.store[CONFIG_KEY].workspaces = workspaces
|
|
29
|
-
this.syncTabbyProfiles(workspaces)
|
|
30
39
|
return await this.saveConfig()
|
|
31
40
|
}
|
|
32
41
|
|
|
@@ -60,28 +69,32 @@ export class WorkspaceEditorService {
|
|
|
60
69
|
async getAvailableProfiles(): Promise<TabbyProfile[]> {
|
|
61
70
|
const allProfiles = await this.profilesService.getProfiles()
|
|
62
71
|
return allProfiles.filter(
|
|
63
|
-
(p) =>
|
|
72
|
+
(p) =>
|
|
73
|
+
(p.type === 'local' || p.type?.startsWith('local:')) &&
|
|
74
|
+
!p.id?.startsWith('split-layout:')
|
|
64
75
|
) as TabbyProfile[]
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
78
|
+
/**
|
|
79
|
+
* Cleanup orphaned profiles from previous plugin versions.
|
|
80
|
+
* Call this once on plugin init.
|
|
81
|
+
*/
|
|
82
|
+
cleanupOrphanedProfiles(): void {
|
|
83
|
+
if (!this.config.store?.profiles) {
|
|
84
|
+
return
|
|
75
85
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
profiles
|
|
86
|
+
const profiles: TabbyProfile[] = this.config.store.profiles
|
|
87
|
+
const prefix = `split-layout:${CONFIG_KEY}:`
|
|
88
|
+
const filtered = profiles.filter((p) => !p.id?.startsWith(prefix))
|
|
89
|
+
if (filtered.length !== profiles.length) {
|
|
90
|
+
this.config.store.profiles = filtered
|
|
91
|
+
this.config.save()
|
|
92
|
+
console.log(`[${DISPLAY_NAME}] Cleaned up ${profiles.length - filtered.length} orphaned profiles`)
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
|
|
84
|
-
generateTabbyProfile(workspace: Workspace): TabbySplitLayoutProfile {
|
|
96
|
+
async generateTabbyProfile(workspace: Workspace): Promise<TabbySplitLayoutProfile> {
|
|
97
|
+
await this.cacheProfiles()
|
|
85
98
|
const safeName = this.sanitizeForProfileId(workspace.name)
|
|
86
99
|
return {
|
|
87
100
|
id: `split-layout:${CONFIG_KEY}:${safeName}:${workspace.id}`,
|
|
@@ -138,19 +151,8 @@ export class WorkspaceEditorService {
|
|
|
138
151
|
runAsAdministrator: false,
|
|
139
152
|
}
|
|
140
153
|
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
const cmd = baseProfile.options?.command || ''
|
|
144
|
-
if (cmd.includes('nu.exe') || baseProfile.name?.toLowerCase().includes('nushell')) {
|
|
145
|
-
options.args = ['-e', pane.startupCommand]
|
|
146
|
-
} else if (cmd.includes('powershell') || cmd.includes('pwsh')) {
|
|
147
|
-
options.args = ['-NoExit', '-Command', pane.startupCommand]
|
|
148
|
-
} else if (cmd.includes('cmd.exe')) {
|
|
149
|
-
options.args = ['/K', pane.startupCommand]
|
|
150
|
-
} else {
|
|
151
|
-
options.args = ['-c', pane.startupCommand]
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
+
// Note: startupCommand is handled via sendInput() in StartupCommandService
|
|
155
|
+
// to avoid re-execution when Tabby splits the pane
|
|
154
156
|
|
|
155
157
|
const profile = {
|
|
156
158
|
id: baseProfile.id,
|
|
@@ -168,13 +170,17 @@ export class WorkspaceEditorService {
|
|
|
168
170
|
behaviorOnSessionEnd: 'auto',
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
// Use pane.id for matching in StartupCommandService
|
|
174
|
+
// Original title will be restored after command execution
|
|
175
|
+
const cwd = pane.cwd || baseProfile.options?.cwd || ''
|
|
171
176
|
return {
|
|
172
177
|
type: 'app:local-tab',
|
|
173
178
|
profile,
|
|
174
179
|
savedState: false,
|
|
175
|
-
tabTitle: pane.
|
|
176
|
-
tabCustomTitle: pane.
|
|
177
|
-
disableDynamicTitle:
|
|
180
|
+
tabTitle: pane.id,
|
|
181
|
+
tabCustomTitle: pane.id,
|
|
182
|
+
disableDynamicTitle: false,
|
|
183
|
+
cwd,
|
|
178
184
|
}
|
|
179
185
|
}
|
|
180
186
|
|
|
@@ -182,7 +188,7 @@ export class WorkspaceEditorService {
|
|
|
182
188
|
const clone = JSON.parse(JSON.stringify(workspace)) as Workspace
|
|
183
189
|
clone.id = generateUUID()
|
|
184
190
|
clone.name = `${workspace.name} (Copy)`
|
|
185
|
-
clone.
|
|
191
|
+
clone.launchOnStartup = false
|
|
186
192
|
this.regenerateIds(clone.root)
|
|
187
193
|
return clone
|
|
188
194
|
}
|
|
@@ -207,12 +213,38 @@ export class WorkspaceEditorService {
|
|
|
207
213
|
}
|
|
208
214
|
|
|
209
215
|
private getProfileById(profileId: string): TabbyProfile | undefined {
|
|
210
|
-
const
|
|
211
|
-
|
|
216
|
+
const isLocalType = (type: string) => type === 'local' || type?.startsWith('local:')
|
|
217
|
+
|
|
218
|
+
// First: check user profiles in config
|
|
219
|
+
const userProfiles: TabbyProfile[] = this.config.store?.profiles ?? []
|
|
220
|
+
const found = userProfiles.find((p) => p.id === profileId && isLocalType(p.type))
|
|
221
|
+
if (found) return found
|
|
222
|
+
|
|
223
|
+
// Fallback: check cached profiles (includes built-ins)
|
|
224
|
+
return this.cachedProfiles.find((p) => p.id === profileId && isLocalType(p.type))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
collectStartupCommands(workspace: Workspace): PendingCommand[] {
|
|
228
|
+
const commands: PendingCommand[] = []
|
|
229
|
+
this.collectCommandsFromNode(workspace.root, commands)
|
|
230
|
+
return commands
|
|
212
231
|
}
|
|
213
232
|
|
|
214
|
-
|
|
215
|
-
|
|
233
|
+
private collectCommandsFromNode(
|
|
234
|
+
node: WorkspacePane | WorkspaceSplit,
|
|
235
|
+
commands: PendingCommand[]
|
|
236
|
+
): void {
|
|
237
|
+
if (isWorkspaceSplit(node)) {
|
|
238
|
+
for (const child of node.children) {
|
|
239
|
+
this.collectCommandsFromNode(child, commands)
|
|
240
|
+
}
|
|
241
|
+
} else if (node.startupCommand) {
|
|
242
|
+
commands.push({
|
|
243
|
+
paneId: node.id,
|
|
244
|
+
command: node.startupCommand,
|
|
245
|
+
originalTitle: node.title || '',
|
|
246
|
+
})
|
|
247
|
+
}
|
|
216
248
|
}
|
|
217
249
|
|
|
218
250
|
private async saveConfig(): Promise<boolean> {
|
package/test_cdp.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Test CDP connection to Tabby - click on TabbySpaces settings"""
|
|
2
|
+
import pychrome
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
# Connect to Tabby
|
|
6
|
+
browser = pychrome.Browser(url="http://localhost:9222")
|
|
7
|
+
tabs = browser.list_tab()
|
|
8
|
+
print(f"Found {len(tabs)} tabs")
|
|
9
|
+
|
|
10
|
+
# Get the first tab (main Tabby window)
|
|
11
|
+
tab = tabs[0]
|
|
12
|
+
print(f"Using first tab")
|
|
13
|
+
tab.start()
|
|
14
|
+
|
|
15
|
+
# Click on TabbySpaces link
|
|
16
|
+
print("\nClicking on 'TabbySpaces' link...")
|
|
17
|
+
result = tab.Runtime.evaluate(expression="""
|
|
18
|
+
const link = Array.from(document.querySelectorAll('a.nav-link'))
|
|
19
|
+
.find(a => a.innerText.trim() === 'TabbySpaces');
|
|
20
|
+
if (link) {
|
|
21
|
+
link.click();
|
|
22
|
+
'Clicked TabbySpaces!';
|
|
23
|
+
} else {
|
|
24
|
+
'TabbySpaces link not found';
|
|
25
|
+
}
|
|
26
|
+
""")
|
|
27
|
+
print(f"Result: {result.get('result', {}).get('value')}")
|
|
28
|
+
|
|
29
|
+
time.sleep(0.3)
|
|
30
|
+
|
|
31
|
+
# Verify we're on TabbySpaces page - look for workspace elements
|
|
32
|
+
result = tab.Runtime.evaluate(expression="""
|
|
33
|
+
// Check for TabbySpaces specific content
|
|
34
|
+
const content = document.body.innerText;
|
|
35
|
+
const hasWorkspaces = content.includes('Workspace') || content.includes('workspace');
|
|
36
|
+
const activeLink = document.querySelector('a.nav-link.active');
|
|
37
|
+
({
|
|
38
|
+
activePage: activeLink ? activeLink.innerText.trim() : 'unknown',
|
|
39
|
+
hasWorkspaceContent: hasWorkspaces,
|
|
40
|
+
pageSnippet: content.substring(0, 500)
|
|
41
|
+
});
|
|
42
|
+
""", returnByValue=True)
|
|
43
|
+
|
|
44
|
+
data = result.get('result', {}).get('value', {})
|
|
45
|
+
print(f"\nActive page: {data.get('activePage')}")
|
|
46
|
+
print(f"Has workspace content: {data.get('hasWorkspaceContent')}")
|
|
47
|
+
print(f"\nPage snippet:\n{data.get('pageSnippet', '')[:300]}...")
|
|
48
|
+
|
|
49
|
+
tab.stop()
|
|
50
|
+
print("\nDone!")
|
package/RELEASE_PLAN.md
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# TabbySpaces - Public Release Plan
|
|
2
|
-
|
|
3
|
-
## Cilj
|
|
4
|
-
|
|
5
|
-
Napraviti public repo koji omogućava:
|
|
6
|
-
1. Korisnicima da instaliraju plugin iz Tabby Plugin Manager-a
|
|
7
|
-
2. Developerima da doprinose (fork → develop → PR)
|
|
8
|
-
3. Maintaineru (Igor) da testira i dev i production verziju istovremeno
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 1. Package.json izmene
|
|
13
|
-
|
|
14
|
-
- [x] Verzija: `1.0.0` → `0.0.1`
|
|
15
|
-
- [x] Dodaj `repository` polje
|
|
16
|
-
- [x] Dodaj `homepage` polje
|
|
17
|
-
- [x] Dodaj `bugs` polje
|
|
18
|
-
- [x] Sredi `scripts` sekciju - uklonjena TABBY_PLUGINS zavisnost
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 2. Dev Workflow
|
|
23
|
-
|
|
24
|
-
**Pristup:** build:dev + instalacija u plugins folder
|
|
25
|
-
|
|
26
|
-
**Radi:**
|
|
27
|
-
- [x] `npm run build:dev` - kreira dist-dev/ sa izolovanim package-om
|
|
28
|
-
- [x] Instalacija u plugins folder
|
|
29
|
-
|
|
30
|
-
**Poznati problemi:**
|
|
31
|
-
- [x] ~~Prod i dev plugin imaju isto ime u Tabby UI~~ (REŠENO)
|
|
32
|
-
|
|
33
|
-
**TODO:**
|
|
34
|
-
- [x] ~~Popraviti izolaciju imena u UI~~ (DONE - toolbar, settings, profile groups)
|
|
35
|
-
- [ ] Testirati watch mode
|
|
36
|
-
- [ ] Dokumentovati u README
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## 3. Testiranje
|
|
41
|
-
|
|
42
|
-
**Koegzistencija (maintainer)** ✅ RADI
|
|
43
|
-
```
|
|
44
|
-
tabby-tabbyspaces (prod) + tabby-tabbyspaces-dev (dev) u plugins folderu
|
|
45
|
-
Različiti config keys, različita imena u UI
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
**Napomene:**
|
|
49
|
-
- Contributor workflow će biti dokumentovan u CONTRIBUTING.md
|
|
50
|
-
- Production test će biti live npm publish
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## 4. Potrebni fajlovi
|
|
55
|
-
|
|
56
|
-
| Fajl | Status | Prioritet |
|
|
57
|
-
|------|--------|-----------|
|
|
58
|
-
| `README.md` | DONE | P0 |
|
|
59
|
-
| `LICENSE` | DONE | P0 |
|
|
60
|
-
| `CHANGELOG.md` | DONE | P0 |
|
|
61
|
-
| `CONTRIBUTING.md` | DONE | P0 |
|
|
62
|
-
| `.github/ISSUE_TEMPLATE/*` | DONE | P0 |
|
|
63
|
-
| `CLAUDE.md` | DONE (EN) | P0 |
|
|
64
|
-
| `screenshots/` | DONE | P0 |
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## 5. Distribucija
|
|
69
|
-
|
|
70
|
-
**Metod:** npm publish (jedini način da se pojavi u Tabby Plugin Manager-u)
|
|
71
|
-
|
|
72
|
-
**Proces:**
|
|
73
|
-
```bash
|
|
74
|
-
# Setup (jednom)
|
|
75
|
-
npm login
|
|
76
|
-
|
|
77
|
-
# Release
|
|
78
|
-
npm version patch # ili minor/major
|
|
79
|
-
npm publish
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**Pre-publish checklist:**
|
|
83
|
-
- [ ] Verzija bump-ovana
|
|
84
|
-
- [ ] Build radi (`npm run build`)
|
|
85
|
-
- [ ] README ažuriran
|
|
86
|
-
- [ ] CHANGELOG ažuriran
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## 6. GitHub Repo Setup
|
|
91
|
-
|
|
92
|
-
- [ ] Kreiraj repo: `github.com/halilc4/tabbyspaces`
|
|
93
|
-
- [ ] Dodaj opis
|
|
94
|
-
- [ ] Dodaj topics: `tabby`, `tabby-plugin`, `terminal`, `workspace`
|
|
95
|
-
- [ ] Uključi Issues
|
|
96
|
-
- [ ] Push koda
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## 7. Redosled akcija
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
1. [x] Reši dev workflow → build:dev pristup
|
|
104
|
-
2. [x] Popravi dev vs prod izolaciju (ime u UI)
|
|
105
|
-
3. [x] Testiraj koegzistenciju (maintainer scenario)
|
|
106
|
-
4. [x] Napravi LICENSE (MIT, engleski)
|
|
107
|
-
5. [x] Napravi README.md (engleski, Claude Code attribution, screenshots)
|
|
108
|
-
6. [x] Napravi CHANGELOG.md
|
|
109
|
-
7. [x] Napravi CONTRIBUTING.md
|
|
110
|
-
8. [x] Napravi .github/ISSUE_TEMPLATE/*
|
|
111
|
-
9. [x] Prevedi CLAUDE.md na engleski
|
|
112
|
-
10. [ ] Kreiraj GitHub repo (halilc4/tabbyspaces)
|
|
113
|
-
11. [ ] Push
|
|
114
|
-
12. [ ] npm publish
|
|
115
|
-
13. [ ] Testiraj instalaciju iz Tabby Plugin Manager-a
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Odluke
|
|
121
|
-
|
|
122
|
-
1. **GitHub username** - `halilc4` → `github.com/halilc4/tabbyspaces`
|
|
123
|
-
2. **CHANGELOG** - DA, vodimo ga
|
|
124
|
-
3. **Jezici** - README i LICENSE na engleskom
|
|
125
|
-
4. **Attribution** - README mora jasno naglasiti:
|
|
126
|
-
- Kod 100% napisan od Claude Code-a
|
|
127
|
-
- Igor = ideja i product vision
|
|
128
|
-
- Igor toliko mrzi Angular da nije ni pogledao kod
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## Otvorena pitanja
|
|
133
|
-
|
|
134
|
-
~~1. **CLAUDE.md jezik** - Prevesti na engleski za contributore?~~ → DA, engleski
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
## 8. Final Review (2026-01-03)
|
|
139
|
-
|
|
140
|
-
**Status:** ✅ PASS
|
|
141
|
-
|
|
142
|
-
**Pregledano:**
|
|
143
|
-
- [x] Struktura projekta - čista, standardni Angular pattern
|
|
144
|
-
- [x] Kod - tipiziran, bez `any`, čitljiv
|
|
145
|
-
- [x] README.md - jasan, sa screenshotovima
|
|
146
|
-
- [x] LICENSE, CONTRIBUTING.md, CHANGELOG.md - kompletno
|
|
147
|
-
- [x] package.json metapodaci - repo, bugs, homepage
|
|
148
|
-
- [x] .github/ISSUE_TEMPLATE/* - bug report, feature request
|
|
149
|
-
- [x] Dev/Prod izolacija - radi
|
|
150
|
-
|
|
151
|
-
**Zaključak:** Projekat je spreman za public release.
|
|
152
|
-
|
|
153
|
-
**Sledeća sesija:** Full publish (GitHub repo + npm publish)
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## Stil dokumentacije
|
|
158
|
-
|
|
159
|
-
- Bez mainstream GitHub corporate stila
|
|
160
|
-
- Bez AI slop-a (generic, over-polished)
|
|
161
|
-
- Direktno, iskreno, sa karakterom
|
|
Binary file
|