inscope 0.1.0 โ 0.2.0-canary.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/README.md +174 -36
- package/dist/bin/index.mjs +1 -1
- package/package.json +3 -42
package/README.md
CHANGED
|
@@ -1,41 +1,104 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Inscope
|
|
2
2
|
|
|
3
|
-
Per-workspace identity for [Claude Code](https://claude.com/claude-code)
|
|
4
|
-
MCP servers, GitHub auth, and git commit identity to the directory you are in,
|
|
5
|
-
so concurrent sessions in different projects never bleed work and personal
|
|
6
|
-
accounts into each other.
|
|
3
|
+
**Per-workspace identity for [Claude Code](https://claude.com/claude-code): scope MCP servers, GitHub auth, and git commit identity to the directory you are in.**
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
[](https://twitter.com/nrjdalal_dev)
|
|
6
|
+
[](https://www.npmjs.com/package/inscope)
|
|
7
|
+
[](https://www.npmjs.com/package/inscope)
|
|
8
|
+
[](https://github.com/nrjdalal/inscope)
|
|
9
|
+
|
|
10
|
+
> #### `cd` into a project and you are the right person: the right GitHub token, the right MCP servers, the right git commit email, all resolved live from `$PWD`. No toggles, no profile switching, and it holds up with several Claude Code sessions open at once.
|
|
11
|
+
|
|
12
|
+
Concurrent sessions in different projects should never bleed work and personal accounts into each other. You describe each workspace once; `inscope` owns the moving parts and keeps them in sync:
|
|
10
13
|
|
|
11
14
|
- a `.mcp.json` at each workspace root, with uniquely named servers
|
|
12
15
|
- a single zsh `chpwd` hook that resolves the right tokens from `$PWD`
|
|
13
16
|
- git `includeIf` rules so commits get the right email per path
|
|
14
17
|
|
|
15
|
-
Nothing sensitive is written to disk. GitHub tokens come from the `gh` keyring
|
|
16
|
-
|
|
18
|
+
Nothing sensitive is written to disk. GitHub tokens come from the `gh` keyring and Slack tokens from the macOS Keychain, resolved live by the hook.
|
|
19
|
+
|
|
20
|
+
> Background and the why behind the design: [Race-Free Identity in Claude Code](https://zerostarter.dev/blog/mcp-per-workspace).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
### Table of Contents
|
|
25
|
+
|
|
26
|
+
- [Some Examples](#-some-examples)
|
|
27
|
+
- [Features](#-features)
|
|
28
|
+
- [Requirements](#-requirements)
|
|
29
|
+
- [Quick Usage](#-quick-usage)
|
|
30
|
+
- [Commands](#-commands)
|
|
31
|
+
- [What It Manages](#-what-it-manages)
|
|
32
|
+
- [MCP Servers](#-mcp-servers)
|
|
33
|
+
- [Config File](#-config-file)
|
|
34
|
+
- [Contributing](#-contributing)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## ๐ Some Examples
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
# set up the config + hook, and source it from ~/.zshrc
|
|
42
|
+
inscope init
|
|
43
|
+
|
|
44
|
+
# map a work directory: work gh account, work email, work + slack servers
|
|
45
|
+
inscope add ~/acme --gh acme --email you@acme.com --servers github,linear,notion,slack
|
|
46
|
+
|
|
47
|
+
# map a personal directory: just your gh account and personal email
|
|
48
|
+
inscope add ~/nrjdalal --gh nrjdalal --email you@personal.dev
|
|
49
|
+
|
|
50
|
+
# list what is configured
|
|
51
|
+
inscope list
|
|
52
|
+
|
|
53
|
+
# verify tokens, identities, and the hook all resolve
|
|
54
|
+
inscope doctor
|
|
55
|
+
|
|
56
|
+
# regenerate everything after editing the config by hand
|
|
57
|
+
inscope apply
|
|
58
|
+
|
|
59
|
+
# remove a workspace mapping
|
|
60
|
+
inscope rm ~/acme
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`cd ~/acme/api` and you are the work account, with work MCP servers and your work commit email. `cd ~/nrjdalal/blog` and you are you.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## โจ Features
|
|
68
|
+
|
|
69
|
+
- ๐ชช Per-directory identity: GitHub token, git commit email, and MCP servers scoped to `$PWD`
|
|
70
|
+
- ๐งต Race-free across concurrent shells and Claude Code sessions, with no global toggles
|
|
71
|
+
- ๐ No secrets on disk: GitHub tokens from the `gh` keyring, Slack tokens from the macOS Keychain
|
|
72
|
+
- ๐ค Generates a `.mcp.json` per workspace with uniquely named GitHub, Linear, Notion and Slack servers
|
|
73
|
+
- โ๏ธ Git `includeIf` rules so every commit lands with the right author email per path
|
|
74
|
+
- ๐ช A single zsh `chpwd` hook does all the resolution; nothing else touches your shell
|
|
75
|
+
- ๐ฉบ `inscope doctor` verifies tokens, identities, and the hook before you trust them
|
|
76
|
+
- โป๏ธ Idempotent and surgical: only the managed blocks in `.zshrc`, `.gitconfig` and `.mcp.json` are touched
|
|
77
|
+
|
|
78
|
+
---
|
|
17
79
|
|
|
18
|
-
|
|
19
|
-
> [Race-Free Identity in Claude Code](https://zerostarter.dev/blog/mcp-per-workspace).
|
|
80
|
+
## ๐งฐ Requirements
|
|
20
81
|
|
|
21
|
-
|
|
82
|
+
macOS, zsh, [`gh`](https://cli.github.com), and [Claude Code](https://claude.com/claude-code).
|
|
22
83
|
|
|
23
|
-
|
|
84
|
+
---
|
|
24
85
|
|
|
25
|
-
##
|
|
86
|
+
## ๐ Quick Usage
|
|
26
87
|
|
|
27
|
-
|
|
88
|
+
Install globally (the CLI manages your shell hook, so a global install is expected):
|
|
89
|
+
|
|
90
|
+
```sh
|
|
28
91
|
npm i -g inscope
|
|
29
92
|
```
|
|
30
93
|
|
|
31
|
-
|
|
94
|
+
Then walk through the setup once:
|
|
32
95
|
|
|
33
|
-
```
|
|
96
|
+
```sh
|
|
34
97
|
# 1. set up the config + hook, and source it from ~/.zshrc
|
|
35
98
|
inscope init
|
|
36
99
|
|
|
37
|
-
# 2. sign each GitHub account into gh (once)
|
|
38
|
-
gh auth login
|
|
100
|
+
# 2. sign each GitHub account into gh (once per account)
|
|
101
|
+
gh auth login
|
|
39
102
|
|
|
40
103
|
# 3. map your workspaces
|
|
41
104
|
inscope add ~/acme --gh acme --email you@acme.com --servers github,linear,notion,slack
|
|
@@ -46,24 +109,44 @@ source ~/.zshrc
|
|
|
46
109
|
inscope doctor
|
|
47
110
|
```
|
|
48
111
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
112
|
+
Launch `claude` from inside a mapped directory (or relaunch) to pick up the identity. No toggles, and it holds up with several terminals open at once.
|
|
113
|
+
|
|
114
|
+
---
|
|
52
115
|
|
|
53
|
-
## Commands
|
|
116
|
+
## ๐ง Commands
|
|
54
117
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
118
|
+
```
|
|
119
|
+
inscope init Create the config, generate the hook, source it from ~/.zshrc
|
|
120
|
+
inscope add <path> Map a directory to a GitHub account, git email, and MCP servers
|
|
121
|
+
inscope rm <path> Remove a workspace mapping (alias: remove)
|
|
122
|
+
inscope list List configured workspaces (alias: ls)
|
|
123
|
+
inscope apply Regenerate the hook, git includes, and .mcp.json (alias: sync)
|
|
124
|
+
inscope doctor Verify tokens, identities, and the hook resolve correctly
|
|
125
|
+
|
|
126
|
+
-v, --version Display version
|
|
127
|
+
-h, --help Display help
|
|
128
|
+
```
|
|
63
129
|
|
|
64
130
|
Run any command with `-h` for its options.
|
|
65
131
|
|
|
66
|
-
|
|
132
|
+
### `inscope add` options
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
--gh <account> gh account whose token this workspace uses
|
|
136
|
+
--email <email> git commit email for this workspace
|
|
137
|
+
--git-name <name> git commit author name (optional)
|
|
138
|
+
--label <name> workspace name; defaults to the directory basename
|
|
139
|
+
--servers <list> comma-separated: github,linear,notion,slack
|
|
140
|
+
(default: github,linear,notion)
|
|
141
|
+
--slack-keychain <s> keychain service for the Slack token
|
|
142
|
+
(default: slack-<label>-mcp-xoxp when slack is on)
|
|
143
|
+
--slack-message allow the Slack MCP server to post messages
|
|
144
|
+
--seed-slack prompt for the Slack token and store it in the keychain
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## ๐งฉ What It Manages
|
|
67
150
|
|
|
68
151
|
| Surface | Location |
|
|
69
152
|
| ------------ | ----------------------------------------------------------- |
|
|
@@ -72,10 +155,65 @@ Run any command with `-h` for its options.
|
|
|
72
155
|
| MCP servers | `<workspace>/.mcp.json` |
|
|
73
156
|
| Git identity | `~/.gitconfig` includeIf + `~/.config/git/<name>.gitconfig` |
|
|
74
157
|
|
|
75
|
-
Edit `workspaces.json` by hand if you like, then run `inscope apply`.
|
|
76
|
-
|
|
77
|
-
|
|
158
|
+
`inscope` only touches the blocks it owns; your other `.zshrc`, `.gitconfig` and `.mcp.json` content is left alone. Edit `workspaces.json` by hand if you like, then run `inscope apply`.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## ๐ค MCP Servers
|
|
163
|
+
|
|
164
|
+
Each enabled server is written into the workspace `.mcp.json` with a name suffixed by the workspace label (for example `github-acme`), so servers from different workspaces never collide.
|
|
165
|
+
|
|
166
|
+
| Server | Transport | Token source |
|
|
167
|
+
| -------- | --------- | ---------------------------------------------- |
|
|
168
|
+
| `github` | http | `GITHUB_TOKEN` from the active `gh` account |
|
|
169
|
+
| `linear` | http | OAuth via the Linear MCP endpoint |
|
|
170
|
+
| `notion` | http | OAuth via the Notion MCP endpoint |
|
|
171
|
+
| `slack` | stdio | `SLACK_MCP_XOXP_TOKEN` from the macOS Keychain |
|
|
172
|
+
|
|
173
|
+
Slack is opt-in. Enable it with `--servers ...,slack`, then store the token once:
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
inscope add ~/acme --gh acme --servers github,linear,notion,slack --seed-slack
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`--seed-slack` prompts for the `xoxp` token and writes it to the Keychain. Pass `--slack-message` to allow the Slack MCP server to post messages.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## ๐ Config File
|
|
184
|
+
|
|
185
|
+
The source of truth is `~/.config/claude/workspaces.json`:
|
|
186
|
+
|
|
187
|
+
```jsonc
|
|
188
|
+
{
|
|
189
|
+
"version": 1,
|
|
190
|
+
"workspaces": [
|
|
191
|
+
{
|
|
192
|
+
"name": "acme",
|
|
193
|
+
"path": "~/acme",
|
|
194
|
+
"gh": "acme",
|
|
195
|
+
"git": { "email": "you@acme.com" },
|
|
196
|
+
"servers": {
|
|
197
|
+
"github": true,
|
|
198
|
+
"linear": true,
|
|
199
|
+
"notion": true,
|
|
200
|
+
"slack": { "keychain": "slack-acme-mcp-xoxp", "addMessageTool": false }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Edit it directly, then run `inscope apply` to regenerate the hook, git includes, and every `.mcp.json`. `inscope doctor` will tell you if anything no longer resolves.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## ๐ค Contributing
|
|
212
|
+
|
|
213
|
+
Issues and pull requests are welcome. Run the tests with `bun test` and the type checks with `bun run typecheck` before opening a PR.
|
|
214
|
+
|
|
215
|
+
---
|
|
78
216
|
|
|
79
217
|
## License
|
|
80
218
|
|
|
81
|
-
MIT
|
|
219
|
+
[MIT](./LICENSE) ยฉ [Neeraj Dalal](https://nrjdalal.com)
|
package/dist/bin/index.mjs
CHANGED
|
@@ -55,7 +55,7 @@ __inscope_resolve_identity
|
|
|
55
55
|
`)},le=e=>{let n=M(e);if(!t.existsSync(n))return;let r=P(n);if(r.mcpServers&&typeof r.mcpServers==`object`)for(let t of j(e.name))delete r.mcpServers[t];t.writeFileSync(n,JSON.stringify(r,null,2)+`
|
|
56
56
|
`)},F=`zshrc`,ue=e=>{let t=o();return e===t?`$HOME`:e.startsWith(t+n.sep)?`$HOME/${e.slice(t.length+1)}`:e},de=()=>{let e=ue(f()),t=`# Loads each workspace's tokens (GitHub, Slack) from $PWD on every cd.\n[ -r "${e}" ] && source "${e}"`;S(h(),F,t)},I=e=>{let r=f();t.mkdirSync(n.dirname(r),{recursive:!0}),t.writeFileSync(r,k(e)),ie(e),de();let i=[];for(let t of e.workspaces)ce(t),i.push(M(t));return{hook:r,gitconfig:e.workspaces.some(e=>e.git?.email||e.git?.name),mcp:i}},L=()=>({version:1,workspaces:[]}),R=()=>t.existsSync(d()),z=()=>{let e=d(),n=t.readFileSync(e,`utf8`),r=JSON.parse(n);return V(r),r},B=e=>{let r=d();t.mkdirSync(n.dirname(r),{recursive:!0}),t.writeFileSync(r,JSON.stringify(e,null,2)+`
|
|
57
57
|
`)},V=e=>{if(!e||typeof e!=`object`)throw Error(`config is not an object`);if(!Array.isArray(e.workspaces))throw Error(`config.workspaces must be an array`);let t=new Set;for(let n of e.workspaces){if(!n.name)throw Error(`a workspace is missing a name`);if(!n.path)throw Error(`workspace "${n.name}" is missing a path`);if(t.has(n.name))throw Error(`duplicate workspace name "${n.name}"`);t.add(n.name)}},H=e=>n.basename(u(e)),fe=(e,t)=>{let n=e.workspaces.find(e=>e.name===t);if(n)return n;let r=u(t);return e.workspaces.find(e=>u(e.path)===r)},pe=(e,t)=>{let n=e.workspaces.filter(e=>e.name!==t.name);return n.push({...t,path:l(t.path)}),n.sort((e,t)=>e.name.localeCompare(t.name)),{...e,workspaces:n}},me=(e,t)=>{let n=fe(e,t);return n?{cfg:{...e,workspaces:e.workspaces.filter(e=>e.name!==n.name)},removed:n}:{cfg:e}},U=(e,t,n)=>{let r=i(e,t,{encoding:`utf8`,input:n?.input});return{status:r.status??(r.error?127:1),stdout:r.stdout??``,stderr:r.stderr??``}},he=()=>process.platform===`darwin`,W=()=>process.env.USER||``,ge=(e,t=U)=>{let n=t(`gh`,[`auth`,`token`,`-u`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},G=(e,t=U)=>{let n=t(`security`,[`find-generic-password`,`-a`,W(),`-s`,e,`-w`]);return n.status===0&&n.stdout.trim().length>0},_e=(e,t,n=U)=>{let r=n(`security`,[`add-generic-password`,`-U`,`-a`,W(),`-s`,e,`-w`,t]);if(r.status!==0)throw Error(`security add-generic-password failed: ${r.stderr.trim()||`unknown error`}`)},K=e=>`security add-generic-password -U -a "${W()||`$USER`}" -s ${e} -w 'xoxp-...'`,ve=(e,t=U)=>{let n=t(`git`,[`config`,`--file`,e,`user.email`]);return n.status===0?n.stdout.trim():null},ye=e=>new Promise(t=>{let n=a.createInterface({input:process.stdin,output:process.stdout}),r=n.output,i=!1;n._writeToOutput=t=>{if(!i){r.write(t),t.includes(e)&&(i=!0);return}},n.question(e,e=>{r.write(`
|
|
58
|
-
`),n.close(),t(e.trim())})});var q=`inscope`,J=`0.
|
|
58
|
+
`),n.close(),t(e.trim())})});var q=`inscope`,J=`0.2.0-canary.0`,Y={name:`Neeraj Dalal`,email:`admin@nrjdalal.com`,url:`https://nrjdalal.com`};const X=`Map a directory to a GitHub account, git email, and MCP servers.
|
|
59
59
|
Re-running with the same path or label updates that workspace.
|
|
60
60
|
|
|
61
61
|
Usage:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inscope",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-canary.0",
|
|
4
4
|
"description": "Per-workspace identity for Claude Code: scope MCP servers, GitHub auth, and git commit identity to the directory you are in.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -26,49 +26,10 @@
|
|
|
26
26
|
"type": "module",
|
|
27
27
|
"exports": "./dist/index.mjs",
|
|
28
28
|
"types": "./dist/index.d.mts",
|
|
29
|
-
"bin": {
|
|
30
|
-
"inscope": "./dist/bin/index.mjs"
|
|
31
|
-
},
|
|
32
29
|
"files": [
|
|
33
30
|
"dist"
|
|
34
31
|
],
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"build": "tsdown",
|
|
38
|
-
"dev": "tsdown --watch",
|
|
39
|
-
"test": "bun test",
|
|
40
|
-
"typecheck": "tsc --noEmit",
|
|
41
|
-
"prepare": "npx simple-git-hooks"
|
|
42
|
-
},
|
|
43
|
-
"simple-git-hooks": {
|
|
44
|
-
"pre-commit": "npx lint-staged",
|
|
45
|
-
"commit-msg": "npx commitlint --edit $1"
|
|
46
|
-
},
|
|
47
|
-
"commitlint": {
|
|
48
|
-
"extends": [
|
|
49
|
-
"@commitlint/config-conventional"
|
|
50
|
-
]
|
|
51
|
-
},
|
|
52
|
-
"lint-staged": {
|
|
53
|
-
"*": "prettier --write --ignore-unknown",
|
|
54
|
-
"package.json": "sort-package-json"
|
|
55
|
-
},
|
|
56
|
-
"prettier": {
|
|
57
|
-
"plugins": [
|
|
58
|
-
"@ianvs/prettier-plugin-sort-imports"
|
|
59
|
-
],
|
|
60
|
-
"semi": false
|
|
61
|
-
},
|
|
62
|
-
"devDependencies": {
|
|
63
|
-
"@commitlint/cli": "^19.8.1",
|
|
64
|
-
"@commitlint/config-conventional": "^19.8.1",
|
|
65
|
-
"@ianvs/prettier-plugin-sort-imports": "^4.4.2",
|
|
66
|
-
"@types/node": "^22.15.32",
|
|
67
|
-
"lint-staged": "^15.5.2",
|
|
68
|
-
"prettier": "^3.5.3",
|
|
69
|
-
"simple-git-hooks": "^2.13.0",
|
|
70
|
-
"sort-package-json": "^3.2.1",
|
|
71
|
-
"tsdown": "^0.16.4",
|
|
72
|
-
"typescript": "^5.8.3"
|
|
32
|
+
"bin": {
|
|
33
|
+
"inscope": "dist/bin/index.mjs"
|
|
73
34
|
}
|
|
74
35
|
}
|