@leynier/ccst 0.5.1 → 0.5.3
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/AGENTS.md +105 -105
- package/CLAUDE.md +1 -1
- package/GEMINI.md +1 -1
- package/LICENSE +21 -21
- package/package.json +2 -2
- package/{README.md → readme.md} +321 -321
- package/src/commands/ccs/start.ts +41 -22
- package/src/utils/daemon.ts +25 -8
package/AGENTS.md
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
Default to using Bun instead of Node.js.
|
|
2
|
-
|
|
3
|
-
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
|
4
|
-
- Use `bun test` instead of `jest` or `vitest`
|
|
5
|
-
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
|
6
|
-
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
|
7
|
-
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
|
|
8
|
-
- Use `bunx <package> <command>` instead of `npx <package> <command>`
|
|
9
|
-
- Bun automatically loads .env, so don't use dotenv.
|
|
10
|
-
|
|
11
|
-
## APIs
|
|
12
|
-
|
|
13
|
-
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
|
|
14
|
-
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
|
|
15
|
-
- `Bun.redis` for Redis. Don't use `ioredis`.
|
|
16
|
-
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
|
|
17
|
-
- `WebSocket` is built-in. Don't use `ws`.
|
|
18
|
-
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
|
|
19
|
-
- Bun.$`ls` instead of execa.
|
|
20
|
-
|
|
21
|
-
## Testing
|
|
22
|
-
|
|
23
|
-
Use `bun test` to run tests.
|
|
24
|
-
|
|
25
|
-
```ts#index.test.ts
|
|
26
|
-
import { test, expect } from "bun:test";
|
|
27
|
-
|
|
28
|
-
test("hello world", () => {
|
|
29
|
-
expect(1).toBe(1);
|
|
30
|
-
});
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Frontend
|
|
34
|
-
|
|
35
|
-
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
|
|
36
|
-
|
|
37
|
-
Server:
|
|
38
|
-
|
|
39
|
-
```ts#index.ts
|
|
40
|
-
import index from "./index.html"
|
|
41
|
-
|
|
42
|
-
Bun.serve({
|
|
43
|
-
routes: {
|
|
44
|
-
"/": index,
|
|
45
|
-
"/api/users/:id": {
|
|
46
|
-
GET: (req) => {
|
|
47
|
-
return new Response(JSON.stringify({ id: req.params.id }));
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
// optional websocket support
|
|
52
|
-
websocket: {
|
|
53
|
-
open: (ws) => {
|
|
54
|
-
ws.send("Hello, world!");
|
|
55
|
-
},
|
|
56
|
-
message: (ws, message) => {
|
|
57
|
-
ws.send(message);
|
|
58
|
-
},
|
|
59
|
-
close: (ws) => {
|
|
60
|
-
// handle close
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
development: {
|
|
64
|
-
hmr: true,
|
|
65
|
-
console: true,
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
|
|
71
|
-
|
|
72
|
-
```html#index.html
|
|
73
|
-
<html>
|
|
74
|
-
<body>
|
|
75
|
-
<h1>Hello, world!</h1>
|
|
76
|
-
<script type="module" src="./frontend.tsx"></script>
|
|
77
|
-
</body>
|
|
78
|
-
</html>
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
With the following `frontend.tsx`:
|
|
82
|
-
|
|
83
|
-
```tsx#frontend.tsx
|
|
84
|
-
import React from "react";
|
|
85
|
-
import { createRoot } from "react-dom/client";
|
|
86
|
-
|
|
87
|
-
// import .css files directly and it works
|
|
88
|
-
import './index.css';
|
|
89
|
-
|
|
90
|
-
const root = createRoot(document.body);
|
|
91
|
-
|
|
92
|
-
export default function Frontend() {
|
|
93
|
-
return <h1>Hello, world!</h1>;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
root.render(<Frontend />);
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Then, run index.ts
|
|
100
|
-
|
|
101
|
-
```sh
|
|
102
|
-
bun --hot ./index.ts
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
|
|
1
|
+
Default to using Bun instead of Node.js.
|
|
2
|
+
|
|
3
|
+
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
|
4
|
+
- Use `bun test` instead of `jest` or `vitest`
|
|
5
|
+
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
|
6
|
+
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
|
7
|
+
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
|
|
8
|
+
- Use `bunx <package> <command>` instead of `npx <package> <command>`
|
|
9
|
+
- Bun automatically loads .env, so don't use dotenv.
|
|
10
|
+
|
|
11
|
+
## APIs
|
|
12
|
+
|
|
13
|
+
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
|
|
14
|
+
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
|
|
15
|
+
- `Bun.redis` for Redis. Don't use `ioredis`.
|
|
16
|
+
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
|
|
17
|
+
- `WebSocket` is built-in. Don't use `ws`.
|
|
18
|
+
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
|
|
19
|
+
- Bun.$`ls` instead of execa.
|
|
20
|
+
|
|
21
|
+
## Testing
|
|
22
|
+
|
|
23
|
+
Use `bun test` to run tests.
|
|
24
|
+
|
|
25
|
+
```ts#index.test.ts
|
|
26
|
+
import { test, expect } from "bun:test";
|
|
27
|
+
|
|
28
|
+
test("hello world", () => {
|
|
29
|
+
expect(1).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Frontend
|
|
34
|
+
|
|
35
|
+
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
|
|
36
|
+
|
|
37
|
+
Server:
|
|
38
|
+
|
|
39
|
+
```ts#index.ts
|
|
40
|
+
import index from "./index.html"
|
|
41
|
+
|
|
42
|
+
Bun.serve({
|
|
43
|
+
routes: {
|
|
44
|
+
"/": index,
|
|
45
|
+
"/api/users/:id": {
|
|
46
|
+
GET: (req) => {
|
|
47
|
+
return new Response(JSON.stringify({ id: req.params.id }));
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
// optional websocket support
|
|
52
|
+
websocket: {
|
|
53
|
+
open: (ws) => {
|
|
54
|
+
ws.send("Hello, world!");
|
|
55
|
+
},
|
|
56
|
+
message: (ws, message) => {
|
|
57
|
+
ws.send(message);
|
|
58
|
+
},
|
|
59
|
+
close: (ws) => {
|
|
60
|
+
// handle close
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
development: {
|
|
64
|
+
hmr: true,
|
|
65
|
+
console: true,
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
|
|
71
|
+
|
|
72
|
+
```html#index.html
|
|
73
|
+
<html>
|
|
74
|
+
<body>
|
|
75
|
+
<h1>Hello, world!</h1>
|
|
76
|
+
<script type="module" src="./frontend.tsx"></script>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
With the following `frontend.tsx`:
|
|
82
|
+
|
|
83
|
+
```tsx#frontend.tsx
|
|
84
|
+
import React from "react";
|
|
85
|
+
import { createRoot } from "react-dom/client";
|
|
86
|
+
|
|
87
|
+
// import .css files directly and it works
|
|
88
|
+
import './index.css';
|
|
89
|
+
|
|
90
|
+
const root = createRoot(document.body);
|
|
91
|
+
|
|
92
|
+
export default function Frontend() {
|
|
93
|
+
return <h1>Hello, world!</h1>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
root.render(<Frontend />);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Then, run index.ts
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
bun --hot ./index.ts
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
|
package/CLAUDE.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@./AGENTS.md
|
|
1
|
+
@./AGENTS.md
|
package/GEMINI.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@./AGENTS.md
|
|
1
|
+
@./AGENTS.md
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Leynier Gutiérrez González
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leynier Gutiérrez González
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
package/{README.md → readme.md}
RENAMED
|
@@ -1,321 +1,321 @@
|
|
|
1
|
-
# ccst - Claude Code Switch Tools
|
|
2
|
-
|
|
3
|
-
> Fast and predictable way to manage Claude Code contexts (`~/.claude/settings.json`)
|
|
4
|
-
|
|
5
|
-
**ccst** (Claude Code Switch Tools) is inspired by [cctx](https://github.com/nwiizo/cctx), which is itself inspired by kubectx, and ports the cctx experience to TypeScript with additional capabilities. Switch between permission sets, environments, and settings with a single command.
|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- Instant context switching
|
|
10
|
-
- Predictable UX with user-level defaults
|
|
11
|
-
- Security-first separation of work, personal, and project contexts
|
|
12
|
-
- Shell completions for major shells
|
|
13
|
-
- Previous context quick switch with `ccst -`
|
|
14
|
-
- File-based JSON contexts you can edit manually
|
|
15
|
-
|
|
16
|
-
## Quick Start
|
|
17
|
-
|
|
18
|
-
### Installation
|
|
19
|
-
|
|
20
|
-
Install globally with npm (default):
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm install -g @leynier/ccst
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Alternative package managers:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
# bun
|
|
30
|
-
bun add -g @leynier/ccst
|
|
31
|
-
|
|
32
|
-
# pnpm
|
|
33
|
-
pnpm add -g @leynier/ccst
|
|
34
|
-
|
|
35
|
-
# yarn
|
|
36
|
-
yarn global add @leynier/ccst
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### 30-Second Setup
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
# 1. Create your first context from current settings
|
|
43
|
-
ccst -n personal
|
|
44
|
-
|
|
45
|
-
# 2. Create a restricted work context
|
|
46
|
-
ccst -n work
|
|
47
|
-
|
|
48
|
-
# 3. Switch between contexts
|
|
49
|
-
ccst work # Switch to work
|
|
50
|
-
ccst personal # Switch to personal
|
|
51
|
-
ccst - # Switch back to previous
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Usage
|
|
55
|
-
|
|
56
|
-
### Basic Commands
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
# List all contexts
|
|
60
|
-
ccst
|
|
61
|
-
|
|
62
|
-
# Switch to a context
|
|
63
|
-
ccst work
|
|
64
|
-
|
|
65
|
-
# Switch to previous context
|
|
66
|
-
ccst -
|
|
67
|
-
|
|
68
|
-
# Show current context
|
|
69
|
-
ccst -c
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Settings Level Management
|
|
73
|
-
|
|
74
|
-
ccst respects Claude Code's settings hierarchy with explicit flags:
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
# Default: always uses user-level contexts
|
|
78
|
-
ccst # Manages ~/.claude/settings.json
|
|
79
|
-
|
|
80
|
-
# Explicit flags for project/local contexts
|
|
81
|
-
ccst --in-project # Manages ./.claude/settings.json
|
|
82
|
-
ccst --local # Manages ./.claude/settings.local.json
|
|
83
|
-
|
|
84
|
-
# All commands work with any level
|
|
85
|
-
ccst --in-project work # Switch to 'work' in project contexts
|
|
86
|
-
ccst --local staging # Switch to 'staging' in local contexts
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Context Management
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
# Create new context from current settings
|
|
93
|
-
ccst -n project-alpha
|
|
94
|
-
|
|
95
|
-
# Delete a context
|
|
96
|
-
ccst -d old-project
|
|
97
|
-
|
|
98
|
-
# Rename a context (prompts for new name)
|
|
99
|
-
ccst -r old-name
|
|
100
|
-
|
|
101
|
-
# Edit context with $EDITOR
|
|
102
|
-
ccst -e work
|
|
103
|
-
|
|
104
|
-
# Show context content (JSON)
|
|
105
|
-
ccst -s production
|
|
106
|
-
|
|
107
|
-
# Unset current context
|
|
108
|
-
ccst -u
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### Import/Export
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
# Export context to file
|
|
115
|
-
ccst --export production > prod-settings.json
|
|
116
|
-
|
|
117
|
-
# Import context from file
|
|
118
|
-
ccst --import staging < staging-settings.json
|
|
119
|
-
|
|
120
|
-
# Share contexts between machines
|
|
121
|
-
ccst --export work | ssh remote-host 'ccst --import work'
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Merge Permissions
|
|
125
|
-
|
|
126
|
-
Merge permissions from other contexts or files to build complex configurations:
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
# Merge user settings into current context
|
|
130
|
-
ccst --merge-from user
|
|
131
|
-
|
|
132
|
-
# Merge from another context
|
|
133
|
-
ccst --merge-from personal work
|
|
134
|
-
|
|
135
|
-
# Merge from a specific file
|
|
136
|
-
ccst --merge-from /path/to/permissions.json staging
|
|
137
|
-
|
|
138
|
-
# Remove previously merged permissions
|
|
139
|
-
ccst --unmerge user
|
|
140
|
-
|
|
141
|
-
# View merge history
|
|
142
|
-
ccst --merge-history
|
|
143
|
-
|
|
144
|
-
# Merge into a specific context (default is current)
|
|
145
|
-
ccst --merge-from user production
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Merge features:
|
|
149
|
-
|
|
150
|
-
- Smart deduplication
|
|
151
|
-
- History tracking
|
|
152
|
-
- Reversible unmerge
|
|
153
|
-
- Targeted merges
|
|
154
|
-
|
|
155
|
-
### Shell Completions
|
|
156
|
-
|
|
157
|
-
Enable tab completion for faster workflow:
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
# Bash
|
|
161
|
-
ccst --completions bash > ~/.local/share/bash-completion/completions/ccst
|
|
162
|
-
|
|
163
|
-
# Zsh
|
|
164
|
-
ccst --completions zsh > /usr/local/share/zsh/site-functions/_ccst
|
|
165
|
-
|
|
166
|
-
# Fish
|
|
167
|
-
ccst --completions fish > ~/.config/fish/completions/ccst.fish
|
|
168
|
-
|
|
169
|
-
# PowerShell
|
|
170
|
-
ccst --completions powershell > ccst.ps1
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Importing Profiles
|
|
174
|
-
|
|
175
|
-
ccst includes importer commands to migrate existing profiles:
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
# Import from CCS profiles (~/.ccs/*.settings.json)
|
|
179
|
-
ccst import ccs
|
|
180
|
-
|
|
181
|
-
# Import from configs directory (default: ~/.ccst)
|
|
182
|
-
ccst import configs
|
|
183
|
-
|
|
184
|
-
# Use a custom configs directory
|
|
185
|
-
ccst import configs -d /path/to/configs
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## File Structure
|
|
189
|
-
|
|
190
|
-
Contexts are stored as individual JSON files at different levels:
|
|
191
|
-
|
|
192
|
-
User level (`~/.claude/`):
|
|
193
|
-
|
|
194
|
-
```text
|
|
195
|
-
~/.claude/
|
|
196
|
-
├── settings.json # Active user context
|
|
197
|
-
└── settings/
|
|
198
|
-
├── work.json # Work context
|
|
199
|
-
├── personal.json # Personal context
|
|
200
|
-
└── .cctx-state.json # State tracking
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Project level (`./.claude/`):
|
|
204
|
-
|
|
205
|
-
```text
|
|
206
|
-
./.claude/
|
|
207
|
-
├── settings.json # Shared project context
|
|
208
|
-
├── settings.local.json # Local project context (gitignored)
|
|
209
|
-
└── settings/
|
|
210
|
-
├── staging.json # Staging context
|
|
211
|
-
├── production.json # Production context
|
|
212
|
-
├── .cctx-state.json # Project state
|
|
213
|
-
└── .cctx-state.local.json # Local state
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## Interactive Mode
|
|
217
|
-
|
|
218
|
-
When no arguments are provided, ccst can enter interactive mode:
|
|
219
|
-
|
|
220
|
-
- fzf integration when available
|
|
221
|
-
- Built-in fallback selector when fzf is not installed
|
|
222
|
-
- Current context highlighted in the list
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
# Interactive context selection
|
|
226
|
-
CCTX_INTERACTIVE=1 ccst
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
## Common Workflows
|
|
230
|
-
|
|
231
|
-
### Professional Setup
|
|
232
|
-
|
|
233
|
-
```bash
|
|
234
|
-
# Create restricted work context for safer collaboration
|
|
235
|
-
ccst -n work
|
|
236
|
-
ccst -e work # Edit to add restrictions
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Project-Based Contexts
|
|
240
|
-
|
|
241
|
-
```bash
|
|
242
|
-
# Create project-specific contexts
|
|
243
|
-
ccst -n client-alpha
|
|
244
|
-
ccst -n side-project
|
|
245
|
-
ccst -n experiments
|
|
246
|
-
|
|
247
|
-
# Switch based on current work
|
|
248
|
-
ccst client-alpha
|
|
249
|
-
ccst experiments
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### Daily Context Switching
|
|
253
|
-
|
|
254
|
-
```bash
|
|
255
|
-
# Morning: start with work context
|
|
256
|
-
ccst work
|
|
257
|
-
|
|
258
|
-
# Need full access for personal project
|
|
259
|
-
ccst personal
|
|
260
|
-
|
|
261
|
-
# Quick switch back to work
|
|
262
|
-
ccst -
|
|
263
|
-
|
|
264
|
-
# Check current context anytime
|
|
265
|
-
ccst -c
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
## Complete Command Reference
|
|
269
|
-
|
|
270
|
-
### Basic Operations
|
|
271
|
-
|
|
272
|
-
- `ccst` - List contexts (defaults to user-level)
|
|
273
|
-
- `ccst <name>` - Switch to context
|
|
274
|
-
- `ccst -` - Switch to previous context
|
|
275
|
-
- `ccst -c` - Show current context name
|
|
276
|
-
- `ccst -q` - Quiet mode (only show current context)
|
|
277
|
-
|
|
278
|
-
### Context Management Reference
|
|
279
|
-
|
|
280
|
-
- `ccst -n <name>` - Create new context from current settings
|
|
281
|
-
- `ccst -d <name>` - Delete context (interactive if no name)
|
|
282
|
-
- `ccst -r <name>` - Rename context (prompts for new name)
|
|
283
|
-
- `ccst -e [name]` - Edit context with $EDITOR
|
|
284
|
-
- `ccst -s [name]` - Show context content (JSON)
|
|
285
|
-
- `ccst -u` - Unset current context (removes settings file)
|
|
286
|
-
|
|
287
|
-
### Import/Export Reference
|
|
288
|
-
|
|
289
|
-
- `ccst --export [name]` - Export context to stdout
|
|
290
|
-
- `ccst --import <name>` - Import context from stdin
|
|
291
|
-
|
|
292
|
-
### Importer Commands
|
|
293
|
-
|
|
294
|
-
- `ccst import ccs` - Import from CCS settings (~/.ccs)
|
|
295
|
-
- `ccst import configs` - Import from configs directory (default: ~/.ccst)
|
|
296
|
-
- `ccst import configs -d <dir>` - Import from a custom configs directory
|
|
297
|
-
- `ccst import ccs -d <dir>` - Use custom configs directory for default.json
|
|
298
|
-
|
|
299
|
-
### Merge Operations
|
|
300
|
-
|
|
301
|
-
- `ccst --merge-from <source> [target]` - Merge permissions from source into target (default: current)
|
|
302
|
-
- Source can be: `user`, another context name, or file path
|
|
303
|
-
- `ccst --merge-from <source> --merge-full [target]` - Merge ALL settings (not just permissions)
|
|
304
|
-
- `ccst --unmerge <source> [target]` - Remove previously merged permissions
|
|
305
|
-
- `ccst --unmerge <source> --merge-full [target]` - Remove ALL previously merged settings
|
|
306
|
-
- `ccst --merge-history [name]` - Show merge history for context
|
|
307
|
-
|
|
308
|
-
### Settings Levels
|
|
309
|
-
|
|
310
|
-
- `ccst` - User-level contexts (default: `~/.claude/settings.json`)
|
|
311
|
-
- `ccst --in-project` - Project-level contexts (`./.claude/settings.json`)
|
|
312
|
-
- `ccst --local` - Local project contexts (`./.claude/settings.local.json`)
|
|
313
|
-
|
|
314
|
-
### Other Options
|
|
315
|
-
|
|
316
|
-
- `ccst --completions <shell>` - Generate shell completions
|
|
317
|
-
- `ccst --help` - Show help information
|
|
318
|
-
|
|
319
|
-
## Compatibility Note
|
|
320
|
-
|
|
321
|
-
ccst is a TypeScript port of cctx with some differences. Behavior and output are intentionally close to cctx, but there may be small UX or implementation differences.
|
|
1
|
+
# ccst - Claude Code Switch Tools
|
|
2
|
+
|
|
3
|
+
> Fast and predictable way to manage Claude Code contexts (`~/.claude/settings.json`)
|
|
4
|
+
|
|
5
|
+
**ccst** (Claude Code Switch Tools) is inspired by [cctx](https://github.com/nwiizo/cctx), which is itself inspired by kubectx, and ports the cctx experience to TypeScript with additional capabilities. Switch between permission sets, environments, and settings with a single command.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Instant context switching
|
|
10
|
+
- Predictable UX with user-level defaults
|
|
11
|
+
- Security-first separation of work, personal, and project contexts
|
|
12
|
+
- Shell completions for major shells
|
|
13
|
+
- Previous context quick switch with `ccst -`
|
|
14
|
+
- File-based JSON contexts you can edit manually
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Installation
|
|
19
|
+
|
|
20
|
+
Install globally with npm (default):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @leynier/ccst
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Alternative package managers:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# bun
|
|
30
|
+
bun add -g @leynier/ccst
|
|
31
|
+
|
|
32
|
+
# pnpm
|
|
33
|
+
pnpm add -g @leynier/ccst
|
|
34
|
+
|
|
35
|
+
# yarn
|
|
36
|
+
yarn global add @leynier/ccst
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 30-Second Setup
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 1. Create your first context from current settings
|
|
43
|
+
ccst -n personal
|
|
44
|
+
|
|
45
|
+
# 2. Create a restricted work context
|
|
46
|
+
ccst -n work
|
|
47
|
+
|
|
48
|
+
# 3. Switch between contexts
|
|
49
|
+
ccst work # Switch to work
|
|
50
|
+
ccst personal # Switch to personal
|
|
51
|
+
ccst - # Switch back to previous
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Basic Commands
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# List all contexts
|
|
60
|
+
ccst
|
|
61
|
+
|
|
62
|
+
# Switch to a context
|
|
63
|
+
ccst work
|
|
64
|
+
|
|
65
|
+
# Switch to previous context
|
|
66
|
+
ccst -
|
|
67
|
+
|
|
68
|
+
# Show current context
|
|
69
|
+
ccst -c
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Settings Level Management
|
|
73
|
+
|
|
74
|
+
ccst respects Claude Code's settings hierarchy with explicit flags:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Default: always uses user-level contexts
|
|
78
|
+
ccst # Manages ~/.claude/settings.json
|
|
79
|
+
|
|
80
|
+
# Explicit flags for project/local contexts
|
|
81
|
+
ccst --in-project # Manages ./.claude/settings.json
|
|
82
|
+
ccst --local # Manages ./.claude/settings.local.json
|
|
83
|
+
|
|
84
|
+
# All commands work with any level
|
|
85
|
+
ccst --in-project work # Switch to 'work' in project contexts
|
|
86
|
+
ccst --local staging # Switch to 'staging' in local contexts
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Context Management
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Create new context from current settings
|
|
93
|
+
ccst -n project-alpha
|
|
94
|
+
|
|
95
|
+
# Delete a context
|
|
96
|
+
ccst -d old-project
|
|
97
|
+
|
|
98
|
+
# Rename a context (prompts for new name)
|
|
99
|
+
ccst -r old-name
|
|
100
|
+
|
|
101
|
+
# Edit context with $EDITOR
|
|
102
|
+
ccst -e work
|
|
103
|
+
|
|
104
|
+
# Show context content (JSON)
|
|
105
|
+
ccst -s production
|
|
106
|
+
|
|
107
|
+
# Unset current context
|
|
108
|
+
ccst -u
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Import/Export
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Export context to file
|
|
115
|
+
ccst --export production > prod-settings.json
|
|
116
|
+
|
|
117
|
+
# Import context from file
|
|
118
|
+
ccst --import staging < staging-settings.json
|
|
119
|
+
|
|
120
|
+
# Share contexts between machines
|
|
121
|
+
ccst --export work | ssh remote-host 'ccst --import work'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Merge Permissions
|
|
125
|
+
|
|
126
|
+
Merge permissions from other contexts or files to build complex configurations:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Merge user settings into current context
|
|
130
|
+
ccst --merge-from user
|
|
131
|
+
|
|
132
|
+
# Merge from another context
|
|
133
|
+
ccst --merge-from personal work
|
|
134
|
+
|
|
135
|
+
# Merge from a specific file
|
|
136
|
+
ccst --merge-from /path/to/permissions.json staging
|
|
137
|
+
|
|
138
|
+
# Remove previously merged permissions
|
|
139
|
+
ccst --unmerge user
|
|
140
|
+
|
|
141
|
+
# View merge history
|
|
142
|
+
ccst --merge-history
|
|
143
|
+
|
|
144
|
+
# Merge into a specific context (default is current)
|
|
145
|
+
ccst --merge-from user production
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Merge features:
|
|
149
|
+
|
|
150
|
+
- Smart deduplication
|
|
151
|
+
- History tracking
|
|
152
|
+
- Reversible unmerge
|
|
153
|
+
- Targeted merges
|
|
154
|
+
|
|
155
|
+
### Shell Completions
|
|
156
|
+
|
|
157
|
+
Enable tab completion for faster workflow:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Bash
|
|
161
|
+
ccst --completions bash > ~/.local/share/bash-completion/completions/ccst
|
|
162
|
+
|
|
163
|
+
# Zsh
|
|
164
|
+
ccst --completions zsh > /usr/local/share/zsh/site-functions/_ccst
|
|
165
|
+
|
|
166
|
+
# Fish
|
|
167
|
+
ccst --completions fish > ~/.config/fish/completions/ccst.fish
|
|
168
|
+
|
|
169
|
+
# PowerShell
|
|
170
|
+
ccst --completions powershell > ccst.ps1
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Importing Profiles
|
|
174
|
+
|
|
175
|
+
ccst includes importer commands to migrate existing profiles:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# Import from CCS profiles (~/.ccs/*.settings.json)
|
|
179
|
+
ccst import ccs
|
|
180
|
+
|
|
181
|
+
# Import from configs directory (default: ~/.ccst)
|
|
182
|
+
ccst import configs
|
|
183
|
+
|
|
184
|
+
# Use a custom configs directory
|
|
185
|
+
ccst import configs -d /path/to/configs
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## File Structure
|
|
189
|
+
|
|
190
|
+
Contexts are stored as individual JSON files at different levels:
|
|
191
|
+
|
|
192
|
+
User level (`~/.claude/`):
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
~/.claude/
|
|
196
|
+
├── settings.json # Active user context
|
|
197
|
+
└── settings/
|
|
198
|
+
├── work.json # Work context
|
|
199
|
+
├── personal.json # Personal context
|
|
200
|
+
└── .cctx-state.json # State tracking
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Project level (`./.claude/`):
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
./.claude/
|
|
207
|
+
├── settings.json # Shared project context
|
|
208
|
+
├── settings.local.json # Local project context (gitignored)
|
|
209
|
+
└── settings/
|
|
210
|
+
├── staging.json # Staging context
|
|
211
|
+
├── production.json # Production context
|
|
212
|
+
├── .cctx-state.json # Project state
|
|
213
|
+
└── .cctx-state.local.json # Local state
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Interactive Mode
|
|
217
|
+
|
|
218
|
+
When no arguments are provided, ccst can enter interactive mode:
|
|
219
|
+
|
|
220
|
+
- fzf integration when available
|
|
221
|
+
- Built-in fallback selector when fzf is not installed
|
|
222
|
+
- Current context highlighted in the list
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Interactive context selection
|
|
226
|
+
CCTX_INTERACTIVE=1 ccst
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Common Workflows
|
|
230
|
+
|
|
231
|
+
### Professional Setup
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Create restricted work context for safer collaboration
|
|
235
|
+
ccst -n work
|
|
236
|
+
ccst -e work # Edit to add restrictions
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Project-Based Contexts
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# Create project-specific contexts
|
|
243
|
+
ccst -n client-alpha
|
|
244
|
+
ccst -n side-project
|
|
245
|
+
ccst -n experiments
|
|
246
|
+
|
|
247
|
+
# Switch based on current work
|
|
248
|
+
ccst client-alpha
|
|
249
|
+
ccst experiments
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Daily Context Switching
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Morning: start with work context
|
|
256
|
+
ccst work
|
|
257
|
+
|
|
258
|
+
# Need full access for personal project
|
|
259
|
+
ccst personal
|
|
260
|
+
|
|
261
|
+
# Quick switch back to work
|
|
262
|
+
ccst -
|
|
263
|
+
|
|
264
|
+
# Check current context anytime
|
|
265
|
+
ccst -c
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Complete Command Reference
|
|
269
|
+
|
|
270
|
+
### Basic Operations
|
|
271
|
+
|
|
272
|
+
- `ccst` - List contexts (defaults to user-level)
|
|
273
|
+
- `ccst <name>` - Switch to context
|
|
274
|
+
- `ccst -` - Switch to previous context
|
|
275
|
+
- `ccst -c` - Show current context name
|
|
276
|
+
- `ccst -q` - Quiet mode (only show current context)
|
|
277
|
+
|
|
278
|
+
### Context Management Reference
|
|
279
|
+
|
|
280
|
+
- `ccst -n <name>` - Create new context from current settings
|
|
281
|
+
- `ccst -d <name>` - Delete context (interactive if no name)
|
|
282
|
+
- `ccst -r <name>` - Rename context (prompts for new name)
|
|
283
|
+
- `ccst -e [name]` - Edit context with $EDITOR
|
|
284
|
+
- `ccst -s [name]` - Show context content (JSON)
|
|
285
|
+
- `ccst -u` - Unset current context (removes settings file)
|
|
286
|
+
|
|
287
|
+
### Import/Export Reference
|
|
288
|
+
|
|
289
|
+
- `ccst --export [name]` - Export context to stdout
|
|
290
|
+
- `ccst --import <name>` - Import context from stdin
|
|
291
|
+
|
|
292
|
+
### Importer Commands
|
|
293
|
+
|
|
294
|
+
- `ccst import ccs` - Import from CCS settings (~/.ccs)
|
|
295
|
+
- `ccst import configs` - Import from configs directory (default: ~/.ccst)
|
|
296
|
+
- `ccst import configs -d <dir>` - Import from a custom configs directory
|
|
297
|
+
- `ccst import ccs -d <dir>` - Use custom configs directory for default.json
|
|
298
|
+
|
|
299
|
+
### Merge Operations
|
|
300
|
+
|
|
301
|
+
- `ccst --merge-from <source> [target]` - Merge permissions from source into target (default: current)
|
|
302
|
+
- Source can be: `user`, another context name, or file path
|
|
303
|
+
- `ccst --merge-from <source> --merge-full [target]` - Merge ALL settings (not just permissions)
|
|
304
|
+
- `ccst --unmerge <source> [target]` - Remove previously merged permissions
|
|
305
|
+
- `ccst --unmerge <source> --merge-full [target]` - Remove ALL previously merged settings
|
|
306
|
+
- `ccst --merge-history [name]` - Show merge history for context
|
|
307
|
+
|
|
308
|
+
### Settings Levels
|
|
309
|
+
|
|
310
|
+
- `ccst` - User-level contexts (default: `~/.claude/settings.json`)
|
|
311
|
+
- `ccst --in-project` - Project-level contexts (`./.claude/settings.json`)
|
|
312
|
+
- `ccst --local` - Local project contexts (`./.claude/settings.local.json`)
|
|
313
|
+
|
|
314
|
+
### Other Options
|
|
315
|
+
|
|
316
|
+
- `ccst --completions <shell>` - Generate shell completions
|
|
317
|
+
- `ccst --help` - Show help information
|
|
318
|
+
|
|
319
|
+
## Compatibility Note
|
|
320
|
+
|
|
321
|
+
ccst is a TypeScript port of cctx with some differences. Behavior and output are intentionally close to cctx, but there may be small UX or implementation differences.
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ensureDaemonDir,
|
|
6
6
|
getCcsExecutable,
|
|
7
7
|
getLogPath,
|
|
8
|
+
getProcessByPort,
|
|
8
9
|
getRunningDaemonPid,
|
|
9
10
|
isProcessRunning,
|
|
10
11
|
killProcessTree,
|
|
@@ -44,29 +45,47 @@ export const ccsStartCommand = async (
|
|
|
44
45
|
ensureDaemonDir();
|
|
45
46
|
const logPath = getLogPath();
|
|
46
47
|
const ccsPath = getCcsExecutable();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
48
|
+
let pid: number | undefined;
|
|
49
|
+
if (process.platform === "win32") {
|
|
50
|
+
// On Windows, use cmd /c start /B to launch without creating a new window
|
|
51
|
+
// This works with npm-installed .cmd wrappers that create their own console
|
|
52
|
+
const proc = spawn("cmd", ["/c", `start /B "" "${ccsPath}" config`], {
|
|
53
|
+
stdio: "ignore",
|
|
54
|
+
windowsHide: true,
|
|
55
|
+
detached: true,
|
|
56
|
+
});
|
|
57
|
+
proc.unref();
|
|
58
|
+
|
|
59
|
+
// Wait for the process to start, then find it by port
|
|
60
|
+
console.log(pc.dim("Starting CCS config daemon..."));
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
62
|
+
|
|
63
|
+
// Find the process by port 3000 (dashboard port)
|
|
64
|
+
const foundPid = await getProcessByPort(3000);
|
|
65
|
+
if (foundPid === null) {
|
|
66
|
+
console.log(pc.red("Failed to start CCS config daemon"));
|
|
67
|
+
console.log(
|
|
68
|
+
pc.dim("Check if ccs is installed: npm install -g @anthropic/ccs"),
|
|
69
|
+
);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
pid = foundPid;
|
|
73
|
+
} else {
|
|
74
|
+
// On Unix, use regular spawn with detached mode
|
|
75
|
+
const logFd = openSync(logPath, "a");
|
|
76
|
+
const child = spawn(ccsPath, ["config"], {
|
|
77
|
+
detached: true,
|
|
78
|
+
stdio: ["ignore", logFd, logFd],
|
|
79
|
+
});
|
|
80
|
+
if (!child.pid) {
|
|
81
|
+
console.log(pc.red("Failed to start CCS config daemon"));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
pid = child.pid;
|
|
85
|
+
child.unref();
|
|
65
86
|
}
|
|
66
|
-
await writePid(
|
|
67
|
-
|
|
68
|
-
child.unref();
|
|
69
|
-
console.log(pc.green(`CCS config daemon started (PID: ${child.pid})`));
|
|
87
|
+
await writePid(pid);
|
|
88
|
+
console.log(pc.green(`CCS config daemon started (PID: ${pid})`));
|
|
70
89
|
console.log(pc.dim(`Logs: ${logPath}`));
|
|
71
90
|
console.log(pc.dim("Run 'ccst ccs status' to check status"));
|
|
72
91
|
console.log(pc.dim("Run 'ccst ccs logs' to view logs"));
|
package/src/utils/daemon.ts
CHANGED
|
@@ -114,24 +114,41 @@ export const getProcessByPort = async (
|
|
|
114
114
|
port: number,
|
|
115
115
|
): Promise<number | null> => {
|
|
116
116
|
if (process.platform === "win32") {
|
|
117
|
-
|
|
117
|
+
// Run netstat directly and parse ourselves for exact port matching
|
|
118
|
+
const proc = Bun.spawn(["netstat", "-ano", "-p", "tcp"], {
|
|
118
119
|
stdout: "pipe",
|
|
119
120
|
stderr: "ignore",
|
|
120
121
|
});
|
|
121
122
|
const output = await new Response(proc.stdout).text();
|
|
122
123
|
await proc.exited;
|
|
123
|
-
|
|
124
|
+
|
|
125
|
+
const lines = output.split("\n");
|
|
124
126
|
for (const line of lines) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
// Format: Proto Local Address Foreign Address State PID
|
|
128
|
+
// TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 12345
|
|
129
|
+
const parts = line.trim().split(/\s+/);
|
|
130
|
+
if (parts.length < 5) continue;
|
|
131
|
+
|
|
132
|
+
const state = parts[3];
|
|
133
|
+
const localAddr = parts[1];
|
|
134
|
+
const pidStr = parts[4];
|
|
135
|
+
if (state !== "LISTENING" || !localAddr || !pidStr) continue;
|
|
136
|
+
|
|
137
|
+
// Extract port from local address (last part after colon)
|
|
138
|
+
const portMatch = localAddr.match(/:(\d+)$/);
|
|
139
|
+
if (!portMatch?.[1]) continue;
|
|
140
|
+
|
|
141
|
+
const localPort = Number.parseInt(portMatch[1], 10);
|
|
142
|
+
if (localPort !== port) continue;
|
|
143
|
+
|
|
144
|
+
const pid = Number.parseInt(pidStr, 10);
|
|
145
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
146
|
+
return pid;
|
|
131
147
|
}
|
|
132
148
|
}
|
|
133
149
|
return null;
|
|
134
150
|
}
|
|
151
|
+
// Unix: use lsof
|
|
135
152
|
const proc = Bun.spawn(["lsof", "-ti", `:${port}`], {
|
|
136
153
|
stdout: "pipe",
|
|
137
154
|
stderr: "ignore",
|