gitspace 0.2.0-rc.11 → 0.2.0-rc.13
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 +9 -0
- package/AGENTS.md +2 -2
- package/README.md +24 -26
- package/bun.lock +7 -6
- package/docs/SITE_DOCS_FIGMA_MAKE.md +35 -30
- package/landing-page/src/components/docs/DocsContent.tsx +12 -9
- package/package.json +6 -5
- package/src/commands/add.ts +2 -2
- package/src/core/shell.ts +3 -3
- package/src/core/workspace.ts +1 -1
- package/src/lib/tmux-lite/server-lifecycle.test.ts +4 -0
- package/src/shared/notifications/policy.test.ts +6 -6
- package/src/tui/app.tsx +1 -1
- package/src/tui/components/ScriptTerminal.tsx +12 -19
- package/src/tui/components/Terminal.tsx +12 -19
- package/src/utils/__tests__/onboarding.test.ts +19 -14
- package/src/utils/__tests__/run-workspace-scripts.test.ts +4 -4
- package/src/utils/run-workspace-scripts.ts +3 -3
package/AGENTS.md
CHANGED
|
@@ -14,7 +14,7 @@ This document provides comprehensive information for AI assistants working on th
|
|
|
14
14
|
- **X3DH handshake** for forward-secret session encryption
|
|
15
15
|
- Web terminal interface (React + xterm.js)
|
|
16
16
|
- Linear issue integration for workspace creation
|
|
17
|
-
- Convention-based custom scripts in `.gitspace/` (pre, setup, select, remove phases)
|
|
17
|
+
- Convention-based custom scripts in `.gitspace/scripts/` (pre, setup, select, remove phases)
|
|
18
18
|
|
|
19
19
|
## Architecture
|
|
20
20
|
|
|
@@ -27,7 +27,7 @@ This document provides comprehensive information for AI assistants working on th
|
|
|
27
27
|
2. **Workspaces**: Individual git worktrees for features/branches
|
|
28
28
|
- Located at `~/gitspace/<project-name>/workspaces/<workspace-name>/`
|
|
29
29
|
- Each workspace has its own branch
|
|
30
|
-
- Scripts are sourced from `.gitspace/<phase>/` in the workspace (version-controlled)
|
|
30
|
+
- Scripts are sourced from `.gitspace/scripts/<phase>/` in the workspace (version-controlled)
|
|
31
31
|
|
|
32
32
|
3. **Sessions**: PTY terminal sessions managed by tmux-lite
|
|
33
33
|
- Can be attached from multiple clients
|
package/README.md
CHANGED
|
@@ -117,14 +117,15 @@ A bundle is a directory (typically `.gitspace/`) containing:
|
|
|
117
117
|
```
|
|
118
118
|
.gitspace/
|
|
119
119
|
├── bundle.json # Bundle manifest with onboarding steps
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
└──
|
|
127
|
-
└──
|
|
120
|
+
└── scripts/
|
|
121
|
+
├── pre/ # Scripts to run before setup
|
|
122
|
+
│ └── 01-copy-env.sh
|
|
123
|
+
├── setup/ # Scripts to run on first workspace creation
|
|
124
|
+
│ └── 01-install-deps.sh
|
|
125
|
+
├── select/ # Scripts to run every time workspace is opened
|
|
126
|
+
│ └── 01-status.sh
|
|
127
|
+
└── remove/ # Scripts to run before workspace deletion
|
|
128
|
+
└── 01-cleanup.sh
|
|
128
129
|
```
|
|
129
130
|
|
|
130
131
|
### Bundle Manifest (`bundle.json`)
|
|
@@ -188,7 +189,7 @@ Bundle values are passed to scripts as environment variables:
|
|
|
188
189
|
|
|
189
190
|
```bash
|
|
190
191
|
#!/bin/bash
|
|
191
|
-
# .gitspace/select/01-status.sh
|
|
192
|
+
# .gitspace/scripts/select/01-status.sh
|
|
192
193
|
|
|
193
194
|
WORKSPACE_NAME=$1
|
|
194
195
|
REPOSITORY=$2
|
|
@@ -361,10 +362,11 @@ inside each workspace so they can vary by branch:
|
|
|
361
362
|
|
|
362
363
|
```
|
|
363
364
|
~/gitspace/<project-name>/workspaces/<workspace-name>/.gitspace/
|
|
364
|
-
|
|
365
|
-
├──
|
|
366
|
-
├──
|
|
367
|
-
|
|
365
|
+
└── scripts/
|
|
366
|
+
├── pre/ # Run before setup (terminal)
|
|
367
|
+
├── setup/ # Run once on workspace creation
|
|
368
|
+
├── select/ # Run every time workspace is opened
|
|
369
|
+
└── remove/ # Run before workspace deletion
|
|
368
370
|
```
|
|
369
371
|
|
|
370
372
|
#### Script Execution Rules
|
|
@@ -406,19 +408,15 @@ export SPACES_CURRENT_PROJECT="my-app"
|
|
|
406
408
|
│ ├── workspaces/ # Git worktrees
|
|
407
409
|
│ │ └── <workspace-name>/
|
|
408
410
|
│ │ ├── gitspace.lock # Setup completion marker
|
|
409
|
-
│ │
|
|
410
|
-
│ │
|
|
411
|
-
│ └──
|
|
412
|
-
│
|
|
413
|
-
│ └──
|
|
414
|
-
│ ├── pre/
|
|
415
|
-
│ ├── setup/
|
|
416
|
-
│ ├── select/
|
|
417
|
-
│ └── remove/
|
|
418
|
-
│ ├── pre/
|
|
419
|
-
│ ├── setup/
|
|
420
|
-
│ ├── select/
|
|
421
|
-
│ └── remove/
|
|
411
|
+
│ │ ├── .prompt/ # Linear issue details (if applicable)
|
|
412
|
+
│ │ │ └── issue.md
|
|
413
|
+
│ │ └── .gitspace/
|
|
414
|
+
│ │ ├── bundle.json
|
|
415
|
+
│ │ └── scripts/ # Custom scripts (per worktree)
|
|
416
|
+
│ │ ├── pre/
|
|
417
|
+
│ │ ├── setup/
|
|
418
|
+
│ │ ├── select/
|
|
419
|
+
│ │ └── remove/
|
|
422
420
|
```
|
|
423
421
|
|
|
424
422
|
## Remote Access
|
package/bun.lock
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"open": "^11.0.0",
|
|
25
25
|
"ora": "^8.2.0",
|
|
26
26
|
"react": "^19.2.4",
|
|
27
|
+
"react-dom": "^19.2.4",
|
|
27
28
|
"simple-git": "^3.30.0",
|
|
28
29
|
"ws": "^8.19.0",
|
|
29
30
|
},
|
|
@@ -38,10 +39,10 @@
|
|
|
38
39
|
"typescript": "^5.9.3",
|
|
39
40
|
},
|
|
40
41
|
"optionalDependencies": {
|
|
41
|
-
"@gitspace/darwin-arm64": "0.2.0-rc.
|
|
42
|
-
"@gitspace/darwin-x64": "0.2.0-rc.
|
|
43
|
-
"@gitspace/linux-arm64": "0.2.0-rc.
|
|
44
|
-
"@gitspace/linux-x64": "0.2.0-rc.
|
|
42
|
+
"@gitspace/darwin-arm64": "0.2.0-rc.12",
|
|
43
|
+
"@gitspace/darwin-x64": "0.2.0-rc.12",
|
|
44
|
+
"@gitspace/linux-arm64": "0.2.0-rc.12",
|
|
45
|
+
"@gitspace/linux-x64": "0.2.0-rc.12",
|
|
45
46
|
},
|
|
46
47
|
},
|
|
47
48
|
},
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
|
|
63
64
|
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
|
|
64
65
|
|
|
65
|
-
"@gitspace/darwin-arm64": ["@gitspace/darwin-arm64@0.2.0-rc.
|
|
66
|
+
"@gitspace/darwin-arm64": ["@gitspace/darwin-arm64@0.2.0-rc.12", "", { "os": "darwin", "cpu": "arm64", "bin": { "gssh-darwin-arm64": "bin/gssh" } }, "sha512-FUCz7f6EThB1/epRZ+X3SV7wDq/m4mFRDy2AVXGurVK6OWAA+D8tddUYeGmxvYT9+PXEZDATu5ukBh2RKpr5OQ=="],
|
|
66
67
|
|
|
67
68
|
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
|
|
68
69
|
|
|
@@ -578,7 +579,7 @@
|
|
|
578
579
|
|
|
579
580
|
"react-devtools-core": ["react-devtools-core@7.0.1", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw=="],
|
|
580
581
|
|
|
581
|
-
"react-dom": ["react-dom@19.2.
|
|
582
|
+
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
|
|
582
583
|
|
|
583
584
|
"react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
|
584
585
|
|
|
@@ -281,14 +281,15 @@ D) CUSTOM SCRIPTS & BUNDLES
|
|
|
281
281
|
|
|
282
282
|
### Custom Scripts
|
|
283
283
|
|
|
284
|
-
GitSpace uses convention-based scripts stored per workspace in `.gitspace/`:
|
|
284
|
+
GitSpace uses convention-based scripts stored per workspace in `.gitspace/scripts/`:
|
|
285
285
|
|
|
286
286
|
```
|
|
287
287
|
~/gitspace/<project>/workspaces/<workspace>/.gitspace/
|
|
288
|
-
|
|
289
|
-
├──
|
|
290
|
-
├──
|
|
291
|
-
|
|
288
|
+
└── scripts/
|
|
289
|
+
├── pre/ # Run before setup (once, in terminal)
|
|
290
|
+
├── setup/ # Run on workspace creation (once)
|
|
291
|
+
├── select/ # Run every time workspace is opened
|
|
292
|
+
└── remove/ # Run before workspace deletion
|
|
292
293
|
```
|
|
293
294
|
|
|
294
295
|
Script execution rules:
|
|
@@ -298,7 +299,7 @@ Script execution rules:
|
|
|
298
299
|
- Arguments: `$1` = workspace name, `$2` = repository name
|
|
299
300
|
- Environment: Bundle values available as `SPACE_VALUE_*` and `SPACE_SECRET_*`
|
|
300
301
|
|
|
301
|
-
Example script (`.gitspace/select/01-status.sh`):
|
|
302
|
+
Example script (`.gitspace/scripts/select/01-status.sh`):
|
|
302
303
|
```bash
|
|
303
304
|
#!/bin/bash
|
|
304
305
|
WORKSPACE_NAME=$1
|
|
@@ -316,10 +317,11 @@ Bundles allow repository owners to share onboarding configurations. Place in `.g
|
|
|
316
317
|
```
|
|
317
318
|
.gitspace/
|
|
318
319
|
├── bundle.json # Bundle manifest with onboarding steps
|
|
319
|
-
|
|
320
|
-
├──
|
|
321
|
-
├──
|
|
322
|
-
|
|
320
|
+
└── scripts/
|
|
321
|
+
├── pre/ # Scripts to run before setup
|
|
322
|
+
├── setup/ # Scripts to run on first workspace creation
|
|
323
|
+
├── select/ # Scripts to run every time workspace is opened
|
|
324
|
+
└── remove/ # Scripts to run before workspace deletion
|
|
323
325
|
```
|
|
324
326
|
|
|
325
327
|
Bundle manifest example (`bundle.json`):
|
|
@@ -465,15 +467,15 @@ Location: `~/gitspace/.identity/`
|
|
|
465
467
|
│ ├── workspaces/ # Git worktrees
|
|
466
468
|
│ │ └── <workspace-name>/
|
|
467
469
|
│ │ ├── gitspace.lock # Setup completion marker
|
|
468
|
-
│ │
|
|
469
|
-
│ │
|
|
470
|
-
│ └──
|
|
471
|
-
│
|
|
472
|
-
│ └──
|
|
473
|
-
│ ├── pre/
|
|
474
|
-
│ ├── setup/
|
|
475
|
-
│ ├── select/
|
|
476
|
-
│ └── remove/
|
|
470
|
+
│ │ ├── .prompt/ # Linear issue details (if applicable)
|
|
471
|
+
│ │ │ └── issue.md
|
|
472
|
+
│ │ └── .gitspace/
|
|
473
|
+
│ │ ├── bundle.json
|
|
474
|
+
│ │ └── scripts/ # Custom scripts (per worktree)
|
|
475
|
+
│ │ ├── pre/
|
|
476
|
+
│ │ ├── setup/
|
|
477
|
+
│ │ ├── select/
|
|
478
|
+
│ │ └── remove/
|
|
477
479
|
```
|
|
478
480
|
|
|
479
481
|
============================================================
|
|
@@ -621,8 +623,8 @@ gssh identity init --label "My Device"
|
|
|
621
623
|
- Try: `git fetch origin` first
|
|
622
624
|
|
|
623
625
|
**"Setup scripts failed"**
|
|
624
|
-
- Check script is executable: `chmod +x .gitspace/setup/*.sh`
|
|
625
|
-
- Run manually to see error: `./.gitspace/setup/01-script.sh`
|
|
626
|
+
- Check script is executable: `chmod +x .gitspace/scripts/setup/*.sh`
|
|
627
|
+
- Run manually to see error: `./.gitspace/scripts/setup/01-script.sh`
|
|
626
628
|
- Check environment variables are set
|
|
627
629
|
|
|
628
630
|
### Bundle/Secrets Issues
|
|
@@ -893,14 +895,15 @@ gssh status # Show daemon statuses
|
|
|
893
895
|
|
|
894
896
|
SECTION: Local Workflow - Custom Scripts
|
|
895
897
|
|
|
896
|
-
GitSpace uses convention-based scripts stored per workspace in `.gitspace/`:
|
|
898
|
+
GitSpace uses convention-based scripts stored per workspace in `.gitspace/scripts/`:
|
|
897
899
|
|
|
898
900
|
```
|
|
899
901
|
~/gitspace/<project>/workspaces/<workspace>/.gitspace/
|
|
900
|
-
|
|
901
|
-
├──
|
|
902
|
-
├──
|
|
903
|
-
|
|
902
|
+
└── scripts/
|
|
903
|
+
├── pre/ # Run before setup (once)
|
|
904
|
+
├── setup/ # Run on workspace creation (once)
|
|
905
|
+
├── select/ # Run every time workspace is opened
|
|
906
|
+
└── remove/ # Run before workspace deletion
|
|
904
907
|
```
|
|
905
908
|
|
|
906
909
|
**Rules:**
|
|
@@ -912,7 +915,7 @@ GitSpace uses convention-based scripts stored per workspace in `.gitspace/`:
|
|
|
912
915
|
**Example:**
|
|
913
916
|
```bash
|
|
914
917
|
#!/bin/bash
|
|
915
|
-
# .gitspace/select/01-status.sh
|
|
918
|
+
# .gitspace/scripts/select/01-status.sh
|
|
916
919
|
echo "Switching to: $1"
|
|
917
920
|
git fetch origin
|
|
918
921
|
git status
|
|
@@ -927,9 +930,11 @@ Bundles allow teams to share onboarding configurations. Place in `.gitspace/`:
|
|
|
927
930
|
```
|
|
928
931
|
.gitspace/
|
|
929
932
|
├── bundle.json # Manifest
|
|
930
|
-
|
|
931
|
-
├──
|
|
932
|
-
|
|
933
|
+
└── scripts/
|
|
934
|
+
├── pre/ # Pre-setup scripts
|
|
935
|
+
├── setup/ # Setup scripts
|
|
936
|
+
├── select/ # Select scripts
|
|
937
|
+
└── remove/ # Remove scripts
|
|
933
938
|
```
|
|
934
939
|
|
|
935
940
|
**Manifest example:**
|
|
@@ -284,15 +284,16 @@ gssh status # Show daemon statuses`} multiLine language="bash" />
|
|
|
284
284
|
<h1 className="text-4xl font-bold mb-6">Custom Scripts</h1>
|
|
285
285
|
|
|
286
286
|
<p className="text-zinc-400 mb-4">
|
|
287
|
-
GitSpace uses convention-based scripts stored per workspace in <code className="text-zinc-300">~/gitspace/<project>/workspaces/<workspace>/.gitspace/</code>:
|
|
287
|
+
GitSpace uses convention-based scripts stored per workspace in <code className="text-zinc-300">~/gitspace/<project>/workspaces/<workspace>/.gitspace/scripts/</code>:
|
|
288
288
|
</p>
|
|
289
289
|
|
|
290
290
|
<div className="bg-zinc-900 rounded-lg border border-zinc-800 p-4 font-mono text-sm mb-8">
|
|
291
291
|
<pre className="text-zinc-300">{`~/gitspace/<project>/workspaces/<workspace>/.gitspace/
|
|
292
|
-
|
|
293
|
-
├──
|
|
294
|
-
├──
|
|
295
|
-
|
|
292
|
+
└── scripts/
|
|
293
|
+
├── pre/ # Run before setup (once)
|
|
294
|
+
├── setup/ # Run on workspace creation (once)
|
|
295
|
+
├── select/ # Run every time workspace is opened
|
|
296
|
+
└── remove/ # Run before workspace deletion`}</pre>
|
|
296
297
|
</div>
|
|
297
298
|
|
|
298
299
|
<h3 className="text-xl font-semibold text-white mb-4">Rules</h3>
|
|
@@ -304,7 +305,7 @@ gssh status # Show daemon statuses`} multiLine language="bash" />
|
|
|
304
305
|
</ul>
|
|
305
306
|
|
|
306
307
|
<h3 className="text-xl font-semibold text-white mb-4">Example Script</h3>
|
|
307
|
-
<p className="text-zinc-500 text-sm mb-2">.gitspace/select/01-status.sh</p>
|
|
308
|
+
<p className="text-zinc-500 text-sm mb-2">.gitspace/scripts/select/01-status.sh</p>
|
|
308
309
|
<JsonBlock code={`#!/bin/bash
|
|
309
310
|
echo "Switching to: $1"
|
|
310
311
|
git fetch origin
|
|
@@ -324,9 +325,11 @@ git status`} />
|
|
|
324
325
|
<div className="bg-zinc-900 rounded-lg border border-zinc-800 p-4 font-mono text-sm mb-8">
|
|
325
326
|
<pre className="text-zinc-300">{`.gitspace/
|
|
326
327
|
├── bundle.json # Manifest
|
|
327
|
-
|
|
328
|
-
├──
|
|
329
|
-
|
|
328
|
+
└── scripts/
|
|
329
|
+
├── pre/ # Pre-setup scripts
|
|
330
|
+
├── setup/ # Setup scripts
|
|
331
|
+
├── select/ # Select scripts
|
|
332
|
+
└── remove/ # Remove scripts`}</pre>
|
|
330
333
|
</div>
|
|
331
334
|
|
|
332
335
|
<h3 className="text-xl font-semibold text-white mb-4">Manifest Example</h3>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitspace",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.13",
|
|
4
4
|
"description": "CLI for managing GitHub workspaces with git worktrees and secure remote terminal access",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gssh": "./bin/gssh"
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"relay": "bun src/relay/index.ts"
|
|
18
18
|
},
|
|
19
19
|
"optionalDependencies": {
|
|
20
|
-
"@gitspace/darwin-arm64": "0.2.0-rc.
|
|
21
|
-
"@gitspace/darwin-x64": "0.2.0-rc.
|
|
22
|
-
"@gitspace/linux-x64": "0.2.0-rc.
|
|
23
|
-
"@gitspace/linux-arm64": "0.2.0-rc.
|
|
20
|
+
"@gitspace/darwin-arm64": "0.2.0-rc.13",
|
|
21
|
+
"@gitspace/darwin-x64": "0.2.0-rc.13",
|
|
22
|
+
"@gitspace/linux-x64": "0.2.0-rc.13",
|
|
23
|
+
"@gitspace/linux-arm64": "0.2.0-rc.13"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"cli",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"open": "^11.0.0",
|
|
60
60
|
"ora": "^8.2.0",
|
|
61
61
|
"react": "^19.2.4",
|
|
62
|
+
"react-dom": "^19.2.4",
|
|
62
63
|
"simple-git": "^3.30.0",
|
|
63
64
|
"ws": "^8.19.0"
|
|
64
65
|
},
|
package/src/commands/add.ts
CHANGED
|
@@ -184,7 +184,7 @@ export async function addProject(options: {
|
|
|
184
184
|
baseBranch
|
|
185
185
|
);
|
|
186
186
|
|
|
187
|
-
// Store bundle info if bundle was loaded (scripts are read from workspace .gitspace/)
|
|
187
|
+
// Store bundle info if bundle was loaded (scripts are read from workspace .gitspace/scripts/)
|
|
188
188
|
if (loadedBundle) {
|
|
189
189
|
// Store bundle values and info in project config
|
|
190
190
|
const configUpdates: Record<string, unknown> = {};
|
|
@@ -447,7 +447,7 @@ export async function addWorkspace(
|
|
|
447
447
|
|
|
448
448
|
// Run pre scripts if this is the first time (before tmux/setup)
|
|
449
449
|
if (isFirstTime && !options.noSetup) {
|
|
450
|
-
const preScriptsDir = join(workspacePath, '.gitspace', 'pre');
|
|
450
|
+
const preScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'pre');
|
|
451
451
|
await runScriptsInTerminal(preScriptsDir, workspacePath, workspaceName, projectConfig.repository);
|
|
452
452
|
}
|
|
453
453
|
|
package/src/core/shell.ts
CHANGED
|
@@ -59,7 +59,7 @@ export async function openWorkspaceShell(
|
|
|
59
59
|
|
|
60
60
|
if (selectOnly) {
|
|
61
61
|
// TUI mode: setup was done during creation, just run select scripts
|
|
62
|
-
const selectScriptsDir = join(workspacePath, '.gitspace', 'select')
|
|
62
|
+
const selectScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'select')
|
|
63
63
|
await runScriptsInTerminal(
|
|
64
64
|
selectScriptsDir,
|
|
65
65
|
workspacePath,
|
|
@@ -73,7 +73,7 @@ export async function openWorkspaceShell(
|
|
|
73
73
|
// Determine which scripts to run based on setup status
|
|
74
74
|
if (setupAlreadyRun) {
|
|
75
75
|
// Setup has been run before, run select scripts
|
|
76
|
-
const selectScriptsDir = join(workspacePath, '.gitspace', 'select')
|
|
76
|
+
const selectScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'select')
|
|
77
77
|
await runScriptsInTerminal(
|
|
78
78
|
selectScriptsDir,
|
|
79
79
|
workspacePath,
|
|
@@ -84,7 +84,7 @@ export async function openWorkspaceShell(
|
|
|
84
84
|
} else if (!noSetup) {
|
|
85
85
|
// First time setup, run setup scripts
|
|
86
86
|
printToTerminal('Running setup scripts (first time)...')
|
|
87
|
-
const setupScriptsDir = join(workspacePath, '.gitspace', 'setup')
|
|
87
|
+
const setupScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'setup')
|
|
88
88
|
await runScriptsInTerminal(
|
|
89
89
|
setupScriptsDir,
|
|
90
90
|
workspacePath,
|
package/src/core/workspace.ts
CHANGED
|
@@ -122,7 +122,7 @@ export async function deleteWorkspaceCore(
|
|
|
122
122
|
// Run remove scripts (cleanup before deletion)
|
|
123
123
|
try {
|
|
124
124
|
const projectConfig = readProjectConfig(projectName);
|
|
125
|
-
const removeScriptsDir = join(workspacePath, '.gitspace', 'remove');
|
|
125
|
+
const removeScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'remove');
|
|
126
126
|
options.onProgress?.('Running cleanup scripts...');
|
|
127
127
|
await runScriptsInTerminal(
|
|
128
128
|
removeScriptsDir,
|
|
@@ -22,6 +22,10 @@ async function runCli(command: string): Promise<{ stdout: string; stderr: string
|
|
|
22
22
|
cmd: ["bun", "run", CLI_SCRIPT, command, "--test"],
|
|
23
23
|
stdout: "pipe",
|
|
24
24
|
stderr: "pipe",
|
|
25
|
+
env: {
|
|
26
|
+
...process.env,
|
|
27
|
+
TMUX_LITE: "",
|
|
28
|
+
},
|
|
25
29
|
});
|
|
26
30
|
|
|
27
31
|
const stdout = await new Response(proc.stdout).text();
|
|
@@ -232,7 +232,7 @@ describe('itemToToast', () => {
|
|
|
232
232
|
expect(toast.sessionId).toBe('session-1');
|
|
233
233
|
expect(toast.sessionName).toBe('my-project:my-workspace:default');
|
|
234
234
|
expect(toast.icon).toBe('✅');
|
|
235
|
-
expect(toast.title).toBe('
|
|
235
|
+
expect(toast.title).toBe('my-project / my-workspace / default: Completed - npm test');
|
|
236
236
|
expect(toast.preview).toBe('Tests passed');
|
|
237
237
|
expect(toast.item).toBe(item);
|
|
238
238
|
});
|
|
@@ -248,7 +248,7 @@ describe('itemToToast', () => {
|
|
|
248
248
|
const toast = itemToToast(item);
|
|
249
249
|
|
|
250
250
|
expect(toast.icon).toBe('❌');
|
|
251
|
-
expect(toast.title).toBe('Exit code 1
|
|
251
|
+
expect(toast.title).toBe('my-project / my-workspace / default: Exit code 1 - npm build');
|
|
252
252
|
});
|
|
253
253
|
|
|
254
254
|
it('should convert bell item', () => {
|
|
@@ -261,7 +261,7 @@ describe('itemToToast', () => {
|
|
|
261
261
|
const toast = itemToToast(item);
|
|
262
262
|
|
|
263
263
|
expect(toast.icon).toBe('🔔');
|
|
264
|
-
expect(toast.title).toBe('Bell');
|
|
264
|
+
expect(toast.title).toBe('my-project / my-workspace / default: Bell');
|
|
265
265
|
});
|
|
266
266
|
|
|
267
267
|
it('should convert idle item', () => {
|
|
@@ -274,7 +274,7 @@ describe('itemToToast', () => {
|
|
|
274
274
|
const toast = itemToToast(item);
|
|
275
275
|
|
|
276
276
|
expect(toast.icon).toBe('⏸️');
|
|
277
|
-
expect(toast.title).toBe('Activity Complete
|
|
277
|
+
expect(toast.title).toBe('my-project / my-workspace / default: Activity Complete - vim');
|
|
278
278
|
});
|
|
279
279
|
|
|
280
280
|
it('should convert title item', () => {
|
|
@@ -287,7 +287,7 @@ describe('itemToToast', () => {
|
|
|
287
287
|
const toast = itemToToast(item);
|
|
288
288
|
|
|
289
289
|
expect(toast.icon).toBe('📝');
|
|
290
|
-
expect(toast.title).toBe('Title Change
|
|
290
|
+
expect(toast.title).toBe('my-project / my-workspace / default: Title Change - bash');
|
|
291
291
|
});
|
|
292
292
|
|
|
293
293
|
it('should convert osc item', () => {
|
|
@@ -300,7 +300,7 @@ describe('itemToToast', () => {
|
|
|
300
300
|
const toast = itemToToast(item);
|
|
301
301
|
|
|
302
302
|
expect(toast.icon).toBe('📟');
|
|
303
|
-
expect(toast.title).toBe('OSC Notification
|
|
303
|
+
expect(toast.title).toBe('my-project / my-workspace / default: OSC Notification - app');
|
|
304
304
|
});
|
|
305
305
|
|
|
306
306
|
it('should truncate long preview text', () => {
|
package/src/tui/app.tsx
CHANGED
|
@@ -1193,7 +1193,7 @@ function App({ relayConfig, onQuit }: AppProps) {
|
|
|
1193
1193
|
return;
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
-
// No onboarding, just create project (scripts are in workspace .gitspace/)
|
|
1196
|
+
// No onboarding, just create project (scripts are in workspace .gitspace/scripts/)
|
|
1197
1197
|
createProject(projectName, repo, baseBranch);
|
|
1198
1198
|
updateProjectConfig(projectName, {
|
|
1199
1199
|
appliedBundle: {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { useState, useRef, useCallback, useEffect, useImperativeHandle, forwardRef } from 'react';
|
|
9
9
|
import { extend, useRenderer } from '@opentui/react';
|
|
10
|
-
import type { ScrollBoxRenderable
|
|
10
|
+
import type { ScrollBoxRenderable } from '@opentui/core';
|
|
11
11
|
import { GhosttyTerminalRenderable } from 'ghostty-opentui/terminal-buffer';
|
|
12
12
|
import { toast } from '@opentui-ui/toast';
|
|
13
13
|
import { copyToClipboard } from '../../utils/clipboard.js';
|
|
@@ -114,24 +114,17 @@ export const ScriptTerminal = forwardRef<ScriptTerminalHandle, ScriptTerminalPro
|
|
|
114
114
|
}, []);
|
|
115
115
|
|
|
116
116
|
// Handle mouse-up after drag selection and copy selected text to clipboard
|
|
117
|
-
const handleMouseUp = useCallback((
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
} catch {
|
|
129
|
-
toast.error('Failed to copy to clipboard');
|
|
130
|
-
}
|
|
131
|
-
renderer.clearSelection();
|
|
132
|
-
}
|
|
133
|
-
})();
|
|
134
|
-
});
|
|
117
|
+
const handleMouseUp = useCallback(async () => {
|
|
118
|
+
const text = renderer.getSelection()?.getSelectedText();
|
|
119
|
+
if (text && text.length > 0) {
|
|
120
|
+
try {
|
|
121
|
+
await copyToClipboard(text);
|
|
122
|
+
toast.success('Copied to clipboard');
|
|
123
|
+
} catch {
|
|
124
|
+
toast.error('Failed to copy to clipboard');
|
|
125
|
+
}
|
|
126
|
+
renderer.clearSelection();
|
|
127
|
+
}
|
|
135
128
|
}, [renderer]);
|
|
136
129
|
|
|
137
130
|
// Feed data to terminal - exposed via ref
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
9
9
|
import { extend, useKeyboard, useRenderer } from '@opentui/react';
|
|
10
|
-
import type { PasteEvent, ScrollBoxRenderable
|
|
10
|
+
import type { PasteEvent, ScrollBoxRenderable } from '@opentui/core';
|
|
11
11
|
import { GhosttyTerminalRenderable } from 'ghostty-opentui/terminal-buffer';
|
|
12
12
|
import { appendFileSync } from 'fs';
|
|
13
13
|
import type { Session, SessionEvent } from '../../lib/tmux-lite/protocol.js';
|
|
@@ -542,24 +542,17 @@ export function Terminal({ session, onDetach, onExit, onKicked, onError, interce
|
|
|
542
542
|
});
|
|
543
543
|
|
|
544
544
|
// Handle mouse-up after drag selection and copy selected text to clipboard
|
|
545
|
-
const handleMouseUp = useCallback((
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
} catch {
|
|
557
|
-
toast.error('Failed to copy to clipboard');
|
|
558
|
-
}
|
|
559
|
-
renderer.clearSelection();
|
|
560
|
-
}
|
|
561
|
-
})();
|
|
562
|
-
});
|
|
545
|
+
const handleMouseUp = useCallback(async () => {
|
|
546
|
+
const text = renderer.getSelection()?.getSelectedText();
|
|
547
|
+
if (text && text.length > 0) {
|
|
548
|
+
try {
|
|
549
|
+
await copyToClipboard(text);
|
|
550
|
+
toast.success('Copied to clipboard');
|
|
551
|
+
} catch {
|
|
552
|
+
toast.error('Failed to copy to clipboard');
|
|
553
|
+
}
|
|
554
|
+
renderer.clearSelection();
|
|
555
|
+
}
|
|
563
556
|
}, [renderer]);
|
|
564
557
|
|
|
565
558
|
// Render
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
9
9
|
|
|
10
10
|
describe('onboarding', () => {
|
|
11
|
+
async function loadOnboardingModule() {
|
|
12
|
+
const cacheBust = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
13
|
+
return import(`../onboarding.ts?cacheBust=${cacheBust}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
// Store references to mock functions
|
|
12
17
|
let mockPromptInput: (message: string, options?: { default?: string }) => Promise<string | null>;
|
|
13
18
|
let mockPromptPassword: () => Promise<string | null>;
|
|
@@ -58,14 +63,14 @@ describe('onboarding', () => {
|
|
|
58
63
|
|
|
59
64
|
describe('KEEP_EXISTING_SECRET constant', () => {
|
|
60
65
|
it('should export the constant', async () => {
|
|
61
|
-
const { KEEP_EXISTING_SECRET } = await
|
|
66
|
+
const { KEEP_EXISTING_SECRET } = await loadOnboardingModule();
|
|
62
67
|
expect(KEEP_EXISTING_SECRET).toBe('__KEEP_EXISTING_SECRET__');
|
|
63
68
|
});
|
|
64
69
|
});
|
|
65
70
|
|
|
66
71
|
describe('runOnboarding', () => {
|
|
67
72
|
it('should complete with empty steps', async () => {
|
|
68
|
-
const { runOnboarding } = await
|
|
73
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
69
74
|
const result = await runOnboarding([]);
|
|
70
75
|
|
|
71
76
|
expect(result.completed).toBe(true);
|
|
@@ -75,7 +80,7 @@ describe('onboarding', () => {
|
|
|
75
80
|
it('should collect input values', async () => {
|
|
76
81
|
mockPromptInput = async () => 'my-input';
|
|
77
82
|
|
|
78
|
-
const { runOnboarding } = await
|
|
83
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
79
84
|
const result = await runOnboarding([
|
|
80
85
|
{
|
|
81
86
|
id: 'name-step',
|
|
@@ -93,7 +98,7 @@ describe('onboarding', () => {
|
|
|
93
98
|
it('should collect secret values', async () => {
|
|
94
99
|
mockPromptPassword = async () => 'my-secret';
|
|
95
100
|
|
|
96
|
-
const { runOnboarding } = await
|
|
101
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
97
102
|
const result = await runOnboarding([
|
|
98
103
|
{
|
|
99
104
|
id: 'api-key',
|
|
@@ -111,7 +116,7 @@ describe('onboarding', () => {
|
|
|
111
116
|
it('should handle info steps', async () => {
|
|
112
117
|
mockPromptConfirm = async () => true;
|
|
113
118
|
|
|
114
|
-
const { runOnboarding } = await
|
|
119
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
115
120
|
const result = await runOnboarding([
|
|
116
121
|
{
|
|
117
122
|
id: 'welcome',
|
|
@@ -127,7 +132,7 @@ describe('onboarding', () => {
|
|
|
127
132
|
it('should handle confirm steps', async () => {
|
|
128
133
|
mockPromptConfirm = async () => true;
|
|
129
134
|
|
|
130
|
-
const { runOnboarding } = await
|
|
135
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
131
136
|
const result = await runOnboarding([
|
|
132
137
|
{
|
|
133
138
|
id: 'confirm-install',
|
|
@@ -143,7 +148,7 @@ describe('onboarding', () => {
|
|
|
143
148
|
it('should handle cancelled input', async () => {
|
|
144
149
|
mockPromptInput = async () => null;
|
|
145
150
|
|
|
146
|
-
const { runOnboarding } = await
|
|
151
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
147
152
|
const result = await runOnboarding([
|
|
148
153
|
{
|
|
149
154
|
id: 'name-step',
|
|
@@ -161,7 +166,7 @@ describe('onboarding', () => {
|
|
|
161
166
|
it('should handle cancelled info step', async () => {
|
|
162
167
|
mockPromptConfirm = async () => false;
|
|
163
168
|
|
|
164
|
-
const { runOnboarding } = await
|
|
169
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
165
170
|
const result = await runOnboarding([
|
|
166
171
|
{
|
|
167
172
|
id: 'welcome',
|
|
@@ -184,7 +189,7 @@ describe('onboarding', () => {
|
|
|
184
189
|
return options?.default ?? '';
|
|
185
190
|
};
|
|
186
191
|
|
|
187
|
-
const { runOnboarding } = await
|
|
192
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
188
193
|
const result = await runOnboarding(
|
|
189
194
|
[
|
|
190
195
|
{
|
|
@@ -209,7 +214,7 @@ describe('onboarding', () => {
|
|
|
209
214
|
it('should allow overriding previous value', async () => {
|
|
210
215
|
mockPromptInput = async () => 'New Name';
|
|
211
216
|
|
|
212
|
-
const { runOnboarding } = await
|
|
217
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
213
218
|
const result = await runOnboarding(
|
|
214
219
|
[
|
|
215
220
|
{
|
|
@@ -234,7 +239,7 @@ describe('onboarding', () => {
|
|
|
234
239
|
// User confirms to keep existing
|
|
235
240
|
mockPromptConfirm = async () => true;
|
|
236
241
|
|
|
237
|
-
const { runOnboarding, KEEP_EXISTING_SECRET } = await
|
|
242
|
+
const { runOnboarding, KEEP_EXISTING_SECRET } = await loadOnboardingModule();
|
|
238
243
|
const result = await runOnboarding(
|
|
239
244
|
[
|
|
240
245
|
{
|
|
@@ -268,7 +273,7 @@ describe('onboarding', () => {
|
|
|
268
273
|
};
|
|
269
274
|
mockPromptPassword = async () => 'new-secret-value';
|
|
270
275
|
|
|
271
|
-
const { runOnboarding } = await
|
|
276
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
272
277
|
const result = await runOnboarding(
|
|
273
278
|
[
|
|
274
279
|
{
|
|
@@ -292,7 +297,7 @@ describe('onboarding', () => {
|
|
|
292
297
|
it('should not show keep existing prompt for new secrets', async () => {
|
|
293
298
|
mockPromptPassword = async () => 'brand-new-secret';
|
|
294
299
|
|
|
295
|
-
const { runOnboarding } = await
|
|
300
|
+
const { runOnboarding } = await loadOnboardingModule();
|
|
296
301
|
const result = await runOnboarding(
|
|
297
302
|
[
|
|
298
303
|
{
|
|
@@ -319,7 +324,7 @@ describe('onboarding', () => {
|
|
|
319
324
|
};
|
|
320
325
|
mockPromptConfirm = async () => true;
|
|
321
326
|
|
|
322
|
-
const { runOnboarding, KEEP_EXISTING_SECRET } = await
|
|
327
|
+
const { runOnboarding, KEEP_EXISTING_SECRET } = await loadOnboardingModule();
|
|
323
328
|
const result = await runOnboarding(
|
|
324
329
|
[
|
|
325
330
|
{
|
|
@@ -40,10 +40,10 @@ describe('runWorkspaceScripts', () => {
|
|
|
40
40
|
beforeEach(() => {
|
|
41
41
|
testDir = join(tmpdir(), `workspace-scripts-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
42
42
|
workspacePath = join(testDir, 'workspace');
|
|
43
|
-
// Scripts are
|
|
44
|
-
preScriptsDir = join(workspacePath, '.gitspace', 'pre');
|
|
45
|
-
setupScriptsDir = join(workspacePath, '.gitspace', 'setup');
|
|
46
|
-
selectScriptsDir = join(workspacePath, '.gitspace', 'select');
|
|
43
|
+
// Scripts are in workspace/.gitspace/scripts/<phase>/
|
|
44
|
+
preScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'pre');
|
|
45
|
+
setupScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'setup');
|
|
46
|
+
selectScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'select');
|
|
47
47
|
|
|
48
48
|
mkdirSync(preScriptsDir, { recursive: true });
|
|
49
49
|
mkdirSync(setupScriptsDir, { recursive: true });
|
|
@@ -76,7 +76,7 @@ export async function runWorkspaceScripts(
|
|
|
76
76
|
|
|
77
77
|
if (setupAlreadyRun) {
|
|
78
78
|
// Run select scripts for existing workspace
|
|
79
|
-
const selectScriptsDir = join(workspacePath, '.gitspace', 'select');
|
|
79
|
+
const selectScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'select');
|
|
80
80
|
try {
|
|
81
81
|
onPhaseStart?.('select');
|
|
82
82
|
await runScriptsInTerminal(selectScriptsDir, workspacePath, workspaceName, repository, scriptOptions);
|
|
@@ -91,12 +91,12 @@ export async function runWorkspaceScripts(
|
|
|
91
91
|
|
|
92
92
|
try {
|
|
93
93
|
onPhaseStart?.('pre');
|
|
94
|
-
const preScriptsDir = join(workspacePath, '.gitspace', 'pre');
|
|
94
|
+
const preScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'pre');
|
|
95
95
|
await runScriptsInTerminal(preScriptsDir, workspacePath, workspaceName, repository, scriptOptions);
|
|
96
96
|
preScriptsSucceeded = true;
|
|
97
97
|
|
|
98
98
|
onPhaseStart?.('setup');
|
|
99
|
-
const setupScriptsDir = join(workspacePath, '.gitspace', 'setup');
|
|
99
|
+
const setupScriptsDir = join(workspacePath, '.gitspace', 'scripts', 'setup');
|
|
100
100
|
await runScriptsInTerminal(setupScriptsDir, workspacePath, workspaceName, repository, scriptOptions);
|
|
101
101
|
|
|
102
102
|
// Only mark complete if both phases succeeded
|