inscope 0.5.2 โ†’ 0.6.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 CHANGED
@@ -7,12 +7,12 @@
7
7
  [![downloads](https://img.shields.io/npm/dt/inscope?color=red&logo=npm)](https://www.npmjs.com/package/inscope)
8
8
  [![stars](https://img.shields.io/github/stars/nrjdalal/inscope?color=blue)](https://github.com/nrjdalal/inscope)
9
9
 
10
- ๐Ÿ“– **The why behind the design:** [Race-Free Identity in Claude Code](https://zerostarter.dev/blog/mcp-per-workspace)
10
+ ๐Ÿ“– **The why behind the design:** [Race-Free Identity in Claude Code](https://zerostarter.dev/blog/mcp-per-workspace) aka multiple gh, linear, notion, slack and other accounts.
11
11
 
12
12
  > #### `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.
13
13
 
14
14
  <p align="center">
15
- <img src="https://raw.githubusercontent.com/nrjdalal/inscope/main/.github/assets/demo.gif" alt="inscope demo: interactive add, list, and doctor" width="900" />
15
+ <img src="https://raw.githubusercontent.com/nrjdalal/demo-kit/main/inscope/demo.gif" alt="inscope demo: interactive add, list, and doctor" width="900" />
16
16
  </p>
17
17
 
18
18
  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:
@@ -63,7 +63,7 @@ inscope rm acme
63
63
  `cd ~/acme/api` and you are the work account, with work MCP servers and your work commit email. `cd ~/personal/blog` and you are you.
64
64
 
65
65
  <p align="center">
66
- <img src="https://raw.githubusercontent.com/nrjdalal/inscope/main/.github/assets/demo-switch.gif" alt="inscope switching git identity and tokens on cd" width="900" />
66
+ <img src="https://raw.githubusercontent.com/nrjdalal/demo-kit/main/inscope/demo-switch.gif" alt="inscope switching git identity and tokens on cd" width="900" />
67
67
  </p>
68
68
 
69
69
  ---
@@ -138,7 +138,7 @@ inscope doctor Verify tokens, identities, and the hook resolve correctly
138
138
  Run any command with `-h` for its options.
139
139
 
140
140
  <p align="center">
141
- <img src="https://raw.githubusercontent.com/nrjdalal/inscope/main/.github/assets/demo-manage.gif" alt="inscope edit and rm with type-to-confirm" width="900" />
141
+ <img src="https://raw.githubusercontent.com/nrjdalal/demo-kit/main/inscope/demo-manage.gif" alt="inscope edit and rm with type-to-confirm" width="900" />
142
142
  </p>
143
143
 
144
144
  ### `inscope add`
@@ -208,6 +208,10 @@ inscope add ~/acme --gh neeraj-acme-org --servers github,slack --seed-slack
208
208
 
209
209
  You need a Slack app with a user OAuth (`xoxp`) token first. If you don't have one, follow the [slack-mcp-server authentication guide](https://github.com/korotovsky/slack-mcp-server/blob/HEAD/docs/01-authentication-setup.md#option-2-using-slack_mcp_xoxp_token-user-oauth). inscope points you there during `add` when Slack is enabled.
210
210
 
211
+ <p align="center">
212
+ <img src="https://raw.githubusercontent.com/nrjdalal/demo-kit/main/inscope/demo-slack.gif" alt="inscope adding Slack: keychain prompt and Yes/No selector confirms" width="900" />
213
+ </p>
214
+
211
215
  ---
212
216
 
213
217
  ## ๐Ÿ“‹ Config File
@@ -2,19 +2,19 @@
2
2
  import{parseArgs as e}from"node:util";import t from"node:fs";import n from"node:path";import r from"node:os";import{spawnSync as i}from"node:child_process";import a from"node:readline";const o=()=>r.homedir(),s=()=>process.env.XDG_CONFIG_HOME?.trim()||n.join(o(),`.config`),c=e=>e===`~`?o():e.startsWith(`~/`)?n.join(o(),e.slice(2)):e,l=e=>{let t=c(e),r=o();return t===r?`~`:t.startsWith(r+n.sep)?`~/`+t.slice(r.length+1):t},u=e=>n.resolve(c(e)),d=()=>n.join(s(),`inscope`),f=()=>n.join(d(),`inscope.json`),p=()=>n.join(d(),`inscope.zsh`),m=()=>n.join(d(),`git`),h=()=>n.join(o(),`.gitconfig`),g=()=>n.join(o(),`.zshrc`),_=()=>({version:1,workspaces:[]}),v=()=>t.existsSync(f()),y=()=>{let e=f(),n=t.readFileSync(e,`utf8`),r=JSON.parse(n);return x(r),r},b=e=>{let r=f();t.mkdirSync(n.dirname(r),{recursive:!0}),t.writeFileSync(r,JSON.stringify(e,null,2)+`
3
3
  `)},x=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)}},S=e=>n.basename(u(e)),C=(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)},w=(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}},ee=(e,t)=>{let n=C(e,t);return n?{cfg:{...e,workspaces:e.workspaces.filter(e=>e.name!==n.name)},removed:n}:{cfg:e}},te={atlassian:`https://mcp.atlassian.com/v1/mcp`,canva:`https://mcp.canva.com/mcp`,clickup:`https://mcp.clickup.com/mcp`,hubspot:`https://mcp.hubspot.com`,intercom:`https://mcp.intercom.com/mcp`,linear:`https://mcp.linear.app/mcp`,monday:`https://mcp.monday.com/mcp`,notion:`https://mcp.notion.com/mcp`,plane:`https://mcp.plane.so/http/mcp`,sentry:`https://mcp.sentry.dev/mcp`,stripe:`https://mcp.stripe.com`,vercel:`https://mcp.vercel.com`,webflow:`https://mcp.webflow.com/`},T=[`github`,`atlassian`,`canva`,`clickup`,`hubspot`,`intercom`,`linear`,`monday`,`notion`,`plane`,`sentry`,`slack`,`stripe`,`vercel`,`webflow`],E=e=>T.map(t=>`${t}-${e}`),D=e=>n.join(u(e.path),`.mcp.json`),ne=(e,t)=>e&&typeof e==`object`&&e.url?e.url:t,re=e=>{let t=e.servers,n={};for(let r of T){let i=t[r];if(!i)continue;let a=`${r}-${e.name}`;if(r===`github`)n[a]={type:`http`,url:`https://api.githubcopilot.com/mcp/`,headers:{Authorization:"Bearer ${GITHUB_TOKEN}"}};else if(r===`slack`){let e=i,t={SLACK_MCP_XOXP_TOKEN:"${SLACK_MCP_XOXP_TOKEN}"};e.addMessageTool&&(t.SLACK_MCP_ADD_MESSAGE_TOOL=`true`),n[a]={type:`stdio`,command:`npx`,args:[`-y`,`slack-mcp-server@1.3.0`,`--transport`,`stdio`],env:t}}else n[a]={type:`http`,url:ne(i,te[r])}}return n},ie=e=>{if(!t.existsSync(e))return{};try{return JSON.parse(t.readFileSync(e,`utf8`))}catch{return{}}},O=e=>{if(!t.existsSync(e))return{};try{return JSON.parse(t.readFileSync(e,`utf8`))}catch{throw Error(`${e} is not valid JSON; fix or remove it, then re-run inscope (left it untouched)`)}},ae=e=>{let n=D(e);return t.existsSync(n)?ie(n):null},oe=e=>{let r=D(e);t.mkdirSync(n.dirname(r),{recursive:!0});let i=O(r),a=i.mcpServers&&typeof i.mcpServers==`object`?{...i.mcpServers}:{};for(let t of E(e.name))delete a[t];Object.assign(a,re(e)),i.mcpServers=a,t.writeFileSync(r,JSON.stringify(i,null,2)+`
4
4
  `)},se=e=>{let n=D(e);if(!t.existsSync(n))return;let r=O(n);if(r.mcpServers&&typeof r.mcpServers==`object`)for(let t of E(e.name))delete r.mcpServers[t];t.writeFileSync(n,JSON.stringify(r,null,2)+`
5
- `)},k=(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??``}},ce=()=>process.platform===`darwin`,A=()=>process.env.USER||``,le=(e,t=k)=>{let n=t(`gh`,[`auth`,`token`,`-u`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},ue=(e=k)=>{let t=e(`gh`,[`auth`,`status`]);return(t.stdout+t.stderr).trim()},de=(e=k)=>{let t=[];for(let n of ue(e).matchAll(/account (\S+) \(/g))t.includes(n[1])||t.push(n[1]);return t},fe=(e,t=k)=>{let n=t(`git`,[`config`,`--global`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},j=(e,t=k)=>{let n=t(`security`,[`find-generic-password`,`-a`,A(),`-s`,e,`-w`]);return n.status===0&&n.stdout.trim().length>0},pe=(e,t,n=k)=>{let r=n(`security`,[`add-generic-password`,`-U`,`-a`,A(),`-s`,e,`-w`,t]);if(r.status!==0)throw Error(`security add-generic-password failed: ${r.stderr.trim()||`unknown error`}`)},me=e=>`security add-generic-password -U -a "${A()||`$USER`}" -s ${e} -w 'xoxp-...'`,he=(e,t=k)=>{let n=t(`git`,[`config`,`--file`,e,`user.email`]);return n.status===0?n.stdout.trim():null},M=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),N=e=>{let t=process.stdin;t.isTTY&&typeof t.setRawMode==`function`&&t.setRawMode(e)};let P=``;const F=e=>new Promise(t=>{process.stdout.write(e);let n=()=>{let e=P.indexOf(`
6
- `);if(e<0)return!1;let n=P.slice(0,e).replace(/\r$/,``);return P=P.slice(e+1),t(n),!0};if(n())return;let r=e=>{P+=e.toString(`utf8`),P.includes(`
7
- `)&&(process.stdin.off(`data`,r),process.stdin.off(`end`,i),process.stdin.pause(),n())},i=()=>{process.stdin.off(`data`,r),process.stdin.off(`end`,i);let e=P.replace(/\r$/,``);P=``,t(e)};process.stdin.on(`data`,r),process.stdin.on(`end`,i),process.stdin.resume()}),I=async(e,t=``)=>(await F(`${e}${t?` [${t}]`:``}: `)).trim()||t,L=async(e,t=!1)=>{let n=(await F(`${e} [${t?`Y/n`:`y/N`}]: `)).trim().toLowerCase();return n?n===`y`||n===`yes`:t},ge=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(`
8
- `),n.close(),t(e.trim())})}),R=`\x1B[36m`,z=`\x1B[0m`,B=(e,t,n=0)=>new Promise(r=>{if(!M()||t.length===0){r(t[Math.min(n,t.length-1)]?.value);return}let i=Math.max(0,Math.min(n,t.length-1)),o=process.stdout;o.write(e+`
9
- `);let s=e=>{e||o.write(`\x1b[${t.length}A`);for(let e=0;e<t.length;e++){let n=e===i,r=`${n?`โฏ`:` `} ${t[e].label}`;o.write(`\x1b[2K ${n?R+r+z:r}\n`)}};s(!0),a.emitKeypressEvents(process.stdin),N(!0),process.stdin.resume();let c=()=>{process.stdin.off(`keypress`,l),N(!1),process.stdin.pause()},l=(e,n)=>{n.name===`up`||n.name===`k`?(i=(i-1+t.length)%t.length,s(!1)):n.name===`down`||n.name===`j`?(i=(i+1)%t.length,s(!1)):n.name===`return`||n.name===`enter`?(c(),r(t[i].value)):n.ctrl&&n.name===`c`&&(c(),o.write(`
10
- `),process.exit(130))};process.stdin.on(`keypress`,l)}),V=(e,t)=>new Promise(n=>{let r=t.map(e=>!!e.checked),i=()=>t.filter((e,t)=>r[t]).map(e=>e.value);if(!M()||t.length===0){n(i());return}let o=0,s=process.stdout;s.write(e+`
11
- `);let c=e=>{e||s.write(`\x1b[${t.length}A`);for(let e=0;e<t.length;e++){let n=e===o,i=`${n?`โฏ`:` `} ${r[e]?`โ—‰`:`โ—ฏ`} ${t[e].label}`;s.write(`\x1b[2K ${n?R+i+z:i}\n`)}};c(!0),a.emitKeypressEvents(process.stdin),N(!0),process.stdin.resume();let l=()=>{process.stdin.off(`keypress`,u),N(!1),process.stdin.pause()},u=(e,a)=>{a.name===`up`||a.name===`k`?(o=(o-1+t.length)%t.length,c(!1)):a.name===`down`||a.name===`j`?(o=(o+1)%t.length,c(!1)):a.name===`space`||e===` `?(r[o]=!r[o],c(!1)):a.name===`return`||a.name===`enter`?(l(),n(i())):a.ctrl&&a.name===`c`&&(l(),s.write(`
12
- `),process.exit(130))};process.stdin.on(`keypress`,u)}),H=e=>`# >>> inscope:${e} >>>`,U=e=>`# <<< inscope:${e} <<<`,W=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),G=e=>RegExp(`${W(H(e))}\\n[\\s\\S]*?\\n${W(U(e))}\\n?`),_e=(e,t)=>{let n=t.replace(/\n+$/,``);return`${H(e)}\n${n}\n${U(e)}\n`},K=e=>{try{return t.readFileSync(e,`utf8`)}catch{return``}},ve=(e,r,i)=>{t.mkdirSync(n.dirname(e),{recursive:!0});let a=K(e),o=_e(r,i),s=G(r),c;if(s.test(a))c=a.replace(s,o);else{let e=a.replace(/\n*$/,``);c=e.length?`${e}\n\n${o}`:o}t.writeFileSync(e,c)},ye=(e,n)=>{let r=K(e);if(!r)return;let i=r.replace(G(n),``).replace(/\n{3,}/g,`
13
-
14
- `).replace(/^\n+/,``);t.writeFileSync(e,i)},be=(e,t)=>{let n=K(e).match(RegExp(`${W(H(t))}\\n([\\s\\S]*?)\\n${W(U(t))}`));return n?n[1]:null},q=`gitconfig`,J=e=>!!(e.git&&(e.git.email||e.git.name)),Y=e=>n.join(m(),`${e}.gitconfig`),xe=e=>l(e).replace(/\/+$/,``)+`/`,Se=e=>e.workspaces.filter(J).map(e=>`[includeIf "gitdir:${xe(e.path)}"]\n\tpath = ${l(Y(e.name))}`).join(`
15
- `),Ce=e=>{let t=[`# Managed by inscope. Do not edit by hand.`,`[user]`];return e.git?.email&&t.push(`\temail = ${e.git.email}`),e.git?.name&&t.push(`\tname = ${e.git.name}`),t.join(`
5
+ `)},k=(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??``}},ce=()=>process.platform===`darwin`,A=()=>process.env.USER||``,le=(e,t=k)=>{let n=t(`gh`,[`auth`,`token`,`-u`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},ue=(e=k)=>{let t=e(`gh`,[`auth`,`status`]);return(t.stdout+t.stderr).trim()},de=(e=k)=>{let t=[];for(let n of ue(e).matchAll(/account (\S+) \(/g))t.includes(n[1])||t.push(n[1]);return t},fe=(e,t=k)=>{let n=t(`git`,[`config`,`--global`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},j=(e,t=k)=>{let n=t(`security`,[`find-generic-password`,`-a`,A(),`-s`,e,`-w`]);return n.status===0&&n.stdout.trim().length>0},pe=(e,t,n=k)=>{let r=n(`security`,[`add-generic-password`,`-U`,`-a`,A(),`-s`,e,`-w`,t]);if(r.status!==0)throw Error(`security add-generic-password failed: ${r.stderr.trim()||`unknown error`}`)},me=e=>`security add-generic-password -U -a "${A()||`$USER`}" -s ${e} -w 'xoxp-...'`,he=(e,t=k)=>{let n=t(`git`,[`config`,`--file`,e,`user.email`]);return n.status===0?n.stdout.trim():null},M=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),ge=(e,t=e)=>process.stdout.isTTY?`\x1b]8;;${e}\x07${t}\x1b]8;;\x07`:e,N=e=>process.stdout.isTTY?`\x1b[38;5;208m${e}\x1b[0m`:e,P=e=>{let t=process.stdin;t.isTTY&&typeof t.setRawMode==`function`&&t.setRawMode(e)};let F=``;const I=e=>new Promise(t=>{process.stdout.write(e);let n=()=>{let e=F.indexOf(`
6
+ `);if(e<0)return!1;let n=F.slice(0,e).replace(/\r$/,``);return F=F.slice(e+1),t(n),!0};if(n())return;let r=e=>{F+=e.toString(`utf8`),F.includes(`
7
+ `)&&(process.stdin.off(`data`,r),process.stdin.off(`end`,i),process.stdin.pause(),n())},i=()=>{process.stdin.off(`data`,r),process.stdin.off(`end`,i);let e=F.replace(/\r$/,``);F=``,t(e)};process.stdin.on(`data`,r),process.stdin.on(`end`,i),process.stdin.resume()}),L=async(e,t=``)=>(await I(`${e}${t?` [${t}]`:``}: `)).trim()||t,R=async(e,t=!1)=>{if(!M()){let n=(await I(`${e} [${t?`Y/n`:`y/N`}]: `)).trim().toLowerCase();return n?n===`y`||n===`yes`:t}return V(e,[{label:`Yes`,value:!0},{label:`No`,value:!1}],t?0:1)},_e=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(`
8
+ `),n.close(),t(e.trim())})}),z=`\x1B[36m`,B=`\x1B[0m`,V=(e,t,n=0)=>new Promise(r=>{if(!M()||t.length===0){r(t[Math.min(n,t.length-1)]?.value);return}let i=Math.max(0,Math.min(n,t.length-1)),o=process.stdout;o.write(e+`
9
+ `);let s=e=>{e||o.write(`\x1b[${t.length}A`);for(let e=0;e<t.length;e++){let n=e===i,r=`${n?`โฏ`:` `} ${t[e].label}`;o.write(`\x1b[2K ${n?z+r+B:r}\n`)}};s(!0),a.emitKeypressEvents(process.stdin),P(!0),process.stdin.resume();let c=()=>{process.stdin.off(`keypress`,l),P(!1),process.stdin.pause()},l=(e,n)=>{n.name===`up`||n.name===`k`?(i=(i-1+t.length)%t.length,s(!1)):n.name===`down`||n.name===`j`?(i=(i+1)%t.length,s(!1)):n.name===`return`||n.name===`enter`?(c(),r(t[i].value)):n.ctrl&&n.name===`c`&&(c(),o.write(`
10
+ `),process.exit(130))};process.stdin.on(`keypress`,l)}),H=(e,t)=>new Promise(n=>{let r=t.map(e=>!!e.checked),i=()=>t.filter((e,t)=>r[t]).map(e=>e.value);if(!M()||t.length===0){n(i());return}let o=0,s=process.stdout;s.write(e+`
11
+ `);let c=e=>{e||s.write(`\x1b[${t.length}A`);for(let e=0;e<t.length;e++){let n=e===o,i=`${n?`โฏ`:` `} ${r[e]?`โ—‰`:`โ—ฏ`} ${t[e].label}`;s.write(`\x1b[2K ${n?z+i+B:i}\n`)}};c(!0),a.emitKeypressEvents(process.stdin),P(!0),process.stdin.resume();let l=()=>{process.stdin.off(`keypress`,u),P(!1),process.stdin.pause()},u=(e,a)=>{a.name===`up`||a.name===`k`?(o=(o-1+t.length)%t.length,c(!1)):a.name===`down`||a.name===`j`?(o=(o+1)%t.length,c(!1)):a.name===`space`||e===` `?(r[o]=!r[o],c(!1)):a.name===`return`||a.name===`enter`?(l(),n(i())):a.ctrl&&a.name===`c`&&(l(),s.write(`
12
+ `),process.exit(130))};process.stdin.on(`keypress`,u)}),U=e=>`# >>> inscope:${e} >>>`,W=e=>`# <<< inscope:${e} <<<`,G=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),ve=e=>RegExp(`${G(U(e))}\\n[\\s\\S]*?\\n${G(W(e))}\\n?`),ye=(e,t)=>{let n=t.replace(/\n+$/,``);return`${U(e)}\n${n}\n${W(e)}\n`},K=e=>{try{return t.readFileSync(e,`utf8`)}catch{return``}},be=(e,r,i)=>{t.mkdirSync(n.dirname(e),{recursive:!0});let a=K(e),o=ye(r,i),s=ve(r),c;if(s.test(a))c=a.replace(s,o);else{let e=a.replace(/\n*$/,``);c=e.length?`${e}\n\n${o}`:o}t.writeFileSync(e,c)},xe=(e,n)=>{let r=K(e);if(!r)return;let i=r.replace(ve(n),``).replace(/\n{3,}/g,`
13
+
14
+ `).replace(/^\n+/,``);t.writeFileSync(e,i)},Se=(e,t)=>{let n=K(e).match(RegExp(`${G(U(t))}\\n([\\s\\S]*?)\\n${G(W(t))}`));return n?n[1]:null},q=`gitconfig`,J=e=>!!(e.git&&(e.git.email||e.git.name)),Y=e=>n.join(m(),`${e}.gitconfig`),Ce=e=>l(e).replace(/\/+$/,``)+`/`,we=e=>e.workspaces.filter(J).map(e=>`[includeIf "gitdir:${Ce(e.path)}"]\n\tpath = ${l(Y(e.name))}`).join(`
15
+ `),Te=e=>{let t=[`# Managed by inscope. Do not edit by hand.`,`[user]`];return e.git?.email&&t.push(`\temail = ${e.git.email}`),e.git?.name&&t.push(`\tname = ${e.git.name}`),t.join(`
16
16
  `)+`
17
- `},we=e=>{t.mkdirSync(m(),{recursive:!0});for(let n of e.workspaces)J(n)&&t.writeFileSync(Y(n.name),Ce(n));let n=Se(e);n?ve(h(),q,n):ye(h(),q)},Te=e=>{let n=Y(e);t.existsSync(n)&&t.rmSync(n)},Ee=e=>{let t=l(e);return t===`~`?`"$HOME/"*`:t.startsWith(`~/`)?`"$HOME/${t.slice(2)}/"*`:`"${t}/"*`},De=e=>e.servers.slack?e.servers.slack.keychain:``,Oe=e=>{let t=[...e.workspaces].sort((e,t)=>e.name.localeCompare(t.name));return`# Managed by inscope. Do not edit by hand.
17
+ `},Ee=e=>{t.mkdirSync(m(),{recursive:!0});for(let n of e.workspaces)J(n)&&t.writeFileSync(Y(n.name),Te(n));let n=we(e);n?be(h(),q,n):xe(h(),q)},De=e=>{let n=Y(e);t.existsSync(n)&&t.rmSync(n)},Oe=e=>{let t=l(e);return t===`~`?`"$HOME/"*`:t.startsWith(`~/`)?`"$HOME/${t.slice(2)}/"*`:`"${t}/"*`},ke=e=>e.servers.slack?e.servers.slack.keychain:``,Ae=e=>{let t=[...e.workspaces].sort((e,t)=>e.name.localeCompare(t.name));return`# Managed by inscope. Do not edit by hand.
18
18
  # Source of truth: ~/.config/inscope/inscope.json
19
19
  # Edit there, then run \`inscope apply\` to regenerate this file.
20
20
  #
@@ -25,7 +25,7 @@ import{parseArgs as e}from"node:util";import t from"node:fs";import n from"node:
25
25
  __inscope_resolve_identity() {
26
26
  local ws
27
27
  case "\${PWD}/" in
28
- ${t.map(e=>` ${Ee(e.path)}) ws=${e.name} ;;`).join(`
28
+ ${t.map(e=>` ${Oe(e.path)}) ws=${e.name} ;;`).join(`
29
29
  `)||` # no workspaces configured`}
30
30
  *) ws="" ;;
31
31
  esac
@@ -35,7 +35,7 @@ ${t.map(e=>` ${Ee(e.path)}) ws=${e.name} ;;`).join(`
35
35
 
36
36
  local gh_user="" slack_svc=""
37
37
  case "$ws" in
38
- ${t.map(e=>{let t=[];e.gh&&t.push(`gh_user=${e.gh}`);let n=De(e);return n&&t.push(`slack_svc=${n}`),` ${e.name}) ${t.length?t.join(`; `):`:`} ;;`}).join(`
38
+ ${t.map(e=>{let t=[];e.gh&&t.push(`gh_user=${e.gh}`);let n=ke(e);return n&&t.push(`slack_svc=${n}`),` ${e.name}) ${t.length?t.join(`; `):`:`} ;;`}).join(`
39
39
  `)||` # no workspaces configured`}
40
40
  *) return ;; # outside a mapped workspace: nothing set
41
41
  esac
@@ -61,12 +61,13 @@ autoload -Uz add-zsh-hook
61
61
  add-zsh-hook chpwd __inscope_resolve_identity
62
62
  __inscope_ws="__init__" # force the first resolve, clearing any inherited token
63
63
  __inscope_resolve_identity
64
- `},ke=e=>{let t=o();return e===t?`$HOME`:e.startsWith(t+n.sep)?`$HOME/${e.slice(t.length+1)}`:e},Ae=()=>{let e=ke(p());return`[ -r "${e}" ] && source "${e}"`},je=e=>{let t=Ae();if(e.includes(t))return e;let n=e.replace(/\n*$/,``),r=`# inscope: load each workspace's tokens (GitHub, Slack) from \$PWD on every cd\n${t}`;return n.length?`${n}\n\n${r}\n`:`${r}\n`},Me=()=>{let e=g(),n=``;try{n=t.readFileSync(e,`utf8`)}catch{}let r=je(n);r!==n&&t.writeFileSync(e,r)},Ne=()=>{try{return t.readFileSync(g(),`utf8`).includes(Ae())}catch{return!1}},X=e=>{let r=p();t.mkdirSync(n.dirname(r),{recursive:!0}),t.writeFileSync(r,Oe(e)),we(e),Me();let i=[];for(let t of e.workspaces)oe(t),i.push(D(t));return{hook:r,gitconfig:e.workspaces.some(e=>e.git?.email||e.git?.name),mcp:i}},Z=`https://github.com/korotovsky/slack-mcp-server/blob/HEAD/docs/01-authentication-setup.md#option-2-using-slack_mcp_xoxp_token-user-oauth`,Pe=T,Fe=e=>`SLACK_MCP_XOXP_TOKEN_${e.toUpperCase().replace(/[^A-Z0-9]+/g,`_`)}`,Ie=e=>T.filter(t=>!!e[t]),Le=(e,t)=>{let n={};for(let r of T)n[r]=r===`slack`?t?{keychain:t.keychain,addMessageTool:t.addMessageTool}:!1:e.includes(r);return n},Re=e=>{let t=w(v()?y():_(),e);b(t),X(t)},ze=async(e,t)=>{if(!e.servers.slack)return;let n=e.servers.slack.keychain;if(t){let e=await ge(`Paste the Slack xoxp token for ${n}: `);e?(pe(n,e),console.log(`โœ“ stored ${n} in the macOS keychain`)):console.error(`No token entered; skipped keychain write.`)}else j(n)||console.log(`\nSlack token not in the keychain yet. Create a Slack app (xoxp user OAuth):\n ${Z}\nthen store the token once with:\n ${me(n)}`)};var Q=`inscope`,Be=`0.5.2`,$={name:`Neeraj Dalal`,email:`admin@nrjdalal.com`,url:`https://nrjdalal.com`};const Ve=`Map a directory to a GitHub account, git email, and MCP servers.
64
+ `},je=e=>{let t=o();return e===t?`$HOME`:e.startsWith(t+n.sep)?`$HOME/${e.slice(t.length+1)}`:e},Me=()=>{let e=je(p());return`[ -r "${e}" ] && source "${e}"`},Ne=e=>{let t=Me();if(e.includes(t))return e;let n=e.replace(/\n*$/,``),r=`# inscope: load each workspace's tokens (GitHub, Slack) from \$PWD on every cd\n${t}`;return n.length?`${n}\n\n${r}\n`:`${r}\n`},Pe=()=>{let e=g(),n=``;try{n=t.readFileSync(e,`utf8`)}catch{}let r=Ne(n);r!==n&&t.writeFileSync(e,r)},Fe=()=>{try{return t.readFileSync(g(),`utf8`).includes(Me())}catch{return!1}},X=e=>{let r=p();t.mkdirSync(n.dirname(r),{recursive:!0}),t.writeFileSync(r,Ae(e)),Ee(e),Pe();let i=[];for(let t of e.workspaces)oe(t),i.push(D(t));return{hook:r,gitconfig:e.workspaces.some(e=>e.git?.email||e.git?.name),mcp:i}},Ie=T,Le=e=>`SLACK_MCP_XOXP_TOKEN_${e.toUpperCase().replace(/[^A-Z0-9]+/g,`_`)}`,Re=e=>T.filter(t=>!!e[t]),ze=(e,t)=>{let n={};for(let r of T)n[r]=r===`slack`?t?{keychain:t.keychain,addMessageTool:t.addMessageTool}:!1:e.includes(r);return n},Be=e=>{let t=w(v()?y():_(),e);b(t),X(t)},Ve=async(e,t)=>{if(!e.servers.slack)return;let n=e.servers.slack.keychain;if(t){let e=await _e(`Paste the Slack xoxp token for ${n}: `);e?(pe(n,e),console.log(`\nโœ“ stored ${n} in the macOS keychain`)):console.error(`
65
+ No token entered; skipped keychain write.`)}else j(n)||console.log(`\nSlack token not in the keychain yet. Store it once with:\n${N(me(n))}\n\nSetup guide: ${N(ge(`https://github.com/korotovsky/slack-mcp-server/blob/HEAD/docs/01-authentication-setup.md#option-2-using-slack_mcp_xoxp_token-user-oauth`))}`)};var Z=`inscope`,He=`0.6.0`,Q={name:`Neeraj Dalal`,email:`admin@nrjdalal.com`,url:`https://nrjdalal.com`};const Ue=`Map a directory to a GitHub account, git email, and MCP servers.
65
66
  Runs interactively in a terminal; pass flags or -y to skip the prompts. Re-running
66
67
  with the same path or label updates that workspace.
67
68
 
68
69
  Usage:
69
- $ ${Q} add [path] [options]
70
+ $ ${Z} add [path] [options]
70
71
 
71
72
  Options:
72
73
  --gh <account> gh account whose token this workspace uses
@@ -82,65 +83,68 @@ Options:
82
83
  --slack-message allow the Slack MCP server to post messages
83
84
  --seed-slack prompt for the Slack token and store it in the keychain
84
85
  -y, --yes accept defaults, skip all prompts (non-interactive)
85
- -h, --help Display help message`,He=T.map(e=>({label:e,value:e,checked:e===`github`})),Ue=async t=>{let{positionals:n,values:r}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`},yes:{type:`boolean`,short:`y`},gh:{type:`string`},email:{type:`string`},"git-name":{type:`string`},label:{type:`string`},servers:{type:`string`},"slack-keychain":{type:`string`},"slack-message":{type:`boolean`},"seed-slack":{type:`boolean`}},args:t});r.help&&(console.log(Ve),process.exit(0));let i=M()&&!r.yes,a=n[0];if(!a)if(i)a=await I(`Workspace directory`,process.cwd());else throw Error(Ve);let o=r.label||S(a);i&&!r.label&&(o=await I(`Label`,o));let s=r.gh;s===void 0&&i&&(s=await B(`GitHub account for this workspace`,[...de().map(e=>({label:e,value:e})),{label:`(none)`,value:``}])||void 0);let c=r.email,u=r[`git-name`];if(i){if(c===void 0){let e=fe(`user.email`);c=await I(`Git email${e?` [${e} ยท global]`:``} (enter to inherit global)`)||void 0}if(u===void 0){let e=fe(`user.name`);u=await I(`Git name${e?` [${e} ยท global]`:``} (enter to inherit global)`)||void 0}}let d;d=r.servers===void 0?i?await V(`MCP servers (space toggles, enter confirms)`,He):[`github`]:r.servers.split(`,`).map(e=>e.trim()).filter(Boolean);let f=d.includes(`slack`)||!!r[`slack-keychain`]||!!r[`seed-slack`],p=r[`slack-keychain`]||Fe(o),m=!!r[`slack-message`],h=!!r[`seed-slack`];f&&i&&(console.log(`\nSlack uses a user OAuth (xoxp) token. If you haven't created the app yet,\nfollow the setup guide:\n ${Z}`),r[`slack-keychain`]||(p=await I(`Slack keychain service`,p)),r[`slack-message`]||(m=await L(`Allow Slack to post messages?`,!0)),r[`seed-slack`]||(h=await L(`Store the Slack token now?`,!0)));let g={name:o,path:l(a),gh:s,git:c||u?{email:c,name:u}:void 0,servers:Le(d,f?{keychain:p,addMessageTool:m}:null)};Re(g),console.log(`\nโœ“ workspace "${o}" -> ${g.path}`),console.log(`โœ“ regenerated the hook, git includes, and ${g.path}/.mcp.json`),await ze(g,h),console.log(`\nLaunch \`claude\` from ${g.path} (or relaunch) to pick up the new identity.`),process.exit(0)},We=`Regenerate the chpwd hook, git includes, and every .mcp.json
86
+ -h, --help Display help message`,We=T.map(e=>({label:e,value:e,checked:e===`github`})),Ge=async t=>{let{positionals:n,values:r}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`},yes:{type:`boolean`,short:`y`},gh:{type:`string`},email:{type:`string`},"git-name":{type:`string`},label:{type:`string`},servers:{type:`string`},"slack-keychain":{type:`string`},"slack-message":{type:`boolean`},"seed-slack":{type:`boolean`}},args:t});r.help&&(console.log(Ue),process.exit(0));let i=M()&&!r.yes;i&&console.log();let a=n[0];if(!a)if(i)a=await L(`Workspace directory`,process.cwd());else throw Error(Ue);let o=r.label||S(a);i&&!r.label&&(o=await L(`Label`,o));let s=r.gh;s===void 0&&i&&(s=await V(`
87
+ GitHub account for this workspace`,[...de().map(e=>({label:e,value:e})),{label:`(none)`,value:``}])||void 0);let c=r.email,u=r[`git-name`];if(i){if(c===void 0){let e=fe(`user.email`);c=await L(`Git email${e?` [${e} ยท global]`:``} (enter to inherit global)`)||void 0}if(u===void 0){let e=fe(`user.name`);u=await L(`Git name${e?` [${e} ยท global]`:``} (enter to inherit global)`)||void 0}}let d;d=r.servers===void 0?i?await H(`MCP servers (space toggles, enter confirms)`,We):[`github`]:r.servers.split(`,`).map(e=>e.trim()).filter(Boolean);let f=d.includes(`slack`)||!!r[`slack-keychain`]||!!r[`seed-slack`],p=r[`slack-keychain`]||Le(o),m=!!r[`slack-message`],h=!!r[`seed-slack`];f&&i&&(console.log(`
88
+ Slack uses a user OAuth (xoxp) token.`),r[`slack-keychain`]||(p=await L(`Slack keychain service`,p)),r[`slack-message`]||(m=await R(`Allow Slack to post messages?`,!0)),r[`seed-slack`]||(h=await R(`Store the Slack token now?`,!0)));let g={name:o,path:l(a),gh:s,git:c||u?{email:c,name:u}:void 0,servers:ze(d,f?{keychain:p,addMessageTool:m}:null)};Be(g),console.log(`\nโœ“ workspace "${o}" -> ${g.path}`),console.log(`โœ“ regenerated the hook, git includes, and ${g.path}/.mcp.json`),await Ve(g,h),console.log(`\nLaunch \`claude\` from ${g.path} (or relaunch) to pick up the new identity.`),process.exit(0)},Ke=`Regenerate the chpwd hook, git includes, and every .mcp.json
86
89
  from your config. Idempotent: run it any time the config changes.
87
90
 
88
91
  Usage:
89
- $ ${Q} apply
92
+ $ ${Z} apply
90
93
 
91
94
  Options:
92
- -h, --help Display help message`,Ge=t=>{let{values:n}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});n.help&&(console.log(We),process.exit(0)),v()||(console.error(`No config found. Run \`${Q} init\` first.`),process.exit(1));let r=y(),i=X(r);console.log(`โœ“ hook ${i.hook}`),i.gitconfig&&console.log(`โœ“ gitconfig ~/.gitconfig (includeIf block)`);for(let e of i.mcp)console.log(`โœ“ mcp ${e}`);console.log(`\nApplied ${r.workspaces.length} workspace(s).`),process.exit(0)},Ke=e=>{try{return t.readFileSync(e,`utf8`)}catch{return null}},qe=e=>{let t=[],n=e?.mcpServers;if(!n||typeof n!=`object`)return t;for(let[e,r]of Object.entries(n)){let n=Array.isArray(r?.args)?r.args:[];if(n.some(e=>typeof e==`string`&&/@latest$/.test(e)))t.push(e);else if(r?.command===`npx`){let r=n.find(e=>typeof e==`string`&&!e.startsWith(`-`));r&&!r.includes(`@`)&&t.push(e)}}return t},Je=(e,t=process.cwd())=>{let r=n.resolve(t);return e.workspaces.find(e=>{let t=u(e.path);return r===t||r.startsWith(t+n.sep)})},Ye=(e=k)=>{let t=e(`gh`,[`api`,`user`,`--jq`,`.login`]),n=e(`git`,[`config`,`user.email`]);return{pwd:process.cwd(),gh:t.status===0&&t.stdout.trim()?t.stdout.trim():`none`,gitEmail:n.status===0?n.stdout.trim():`none`,tokenSet:!!process.env.GITHUB_TOKEN}},Xe=(e,n=k)=>{let r=[];ce()||r.push({status:`warn`,label:`platform`,detail:`inscope's secret resolution targets macOS (gh keyring + Keychain)`});let i=p(),a=Ke(i);a===null?r.push({status:`fail`,label:`hook`,detail:`missing ${i}; run \`inscope init\``}):a===Oe(e)?r.push({status:`ok`,label:`hook`,detail:i}):r.push({status:`warn`,label:`hook`,detail:"out of date; run `inscope apply`"}),r.push(Ne()?{status:`ok`,label:`zshrc`,detail:`sources the hook`}:{status:`warn`,label:`zshrc`,detail:"does not source the hook; run `inscope init`"}),e.workspaces.some(J)&&r.push(be(h(),q)===null?{status:`fail`,label:`gitconfig`,detail:"missing includeIf block; run `inscope apply`"}:{status:`ok`,label:`gitconfig`,detail:`includeIf block present`});for(let i of e.workspaces){let e=`[${i.name}]`;if(i.gh&&r.push(le(i.gh,n)?{status:`ok`,label:`${e} gh`,detail:`token for ${i.gh}`}:{status:`fail`,label:`${e} gh`,detail:`no token for ${i.gh}; run \`gh auth login\``}),i.servers.slack){let t=i.servers.slack.keychain;r.push(j(t,n)?{status:`ok`,label:`${e} slack`,detail:t}:{status:`fail`,label:`${e} slack`,detail:`${t} not in keychain; run \`${me(t)}\``})}if(J(i)){let a=Y(i.name);if(!t.existsSync(a))r.push({status:`fail`,label:`${e} git`,detail:`missing ${a}; run \`inscope apply\``});else if(i.git?.email){let t=he(a,n);r.push(t===i.git.email?{status:`ok`,label:`${e} git`,detail:i.git.email}:{status:`fail`,label:`${e} git`,detail:`email is ${t??`unset`}, expected ${i.git.email}`})}}let a=ae(i);if(a===null)r.push({status:`warn`,label:`${e} mcp`,detail:"no .mcp.json; run `inscope apply`"});else{let t=E(i.name).filter(e=>a.mcpServers?.[e]);r.push({status:`ok`,label:`${e} mcp`,detail:`${t.length} server(s)`});let n=qe(a);n.length&&r.push({status:`warn`,label:`${e} mcp`,detail:`unpinned: ${n.join(`, `)}`})}}return r},Ze=`Verify the setup: gh tokens resolve, keychain entries exist,
95
+ -h, --help Display help message`,qe=t=>{let{values:n}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});n.help&&(console.log(Ke),process.exit(0)),v()||(console.error(`No config found. Run \`${Z} init\` first.`),process.exit(1));let r=y(),i=X(r);console.log(`\nโœ“ hook ${i.hook}`),i.gitconfig&&console.log(`โœ“ gitconfig ~/.gitconfig (includeIf block)`);for(let e of i.mcp)console.log(`โœ“ mcp ${e}`);console.log(`\nApplied ${r.workspaces.length} workspace(s).`),process.exit(0)},Je=e=>{try{return t.readFileSync(e,`utf8`)}catch{return null}},Ye=e=>{let t=[],n=e?.mcpServers;if(!n||typeof n!=`object`)return t;for(let[e,r]of Object.entries(n)){let n=Array.isArray(r?.args)?r.args:[];if(n.some(e=>typeof e==`string`&&/@latest$/.test(e)))t.push(e);else if(r?.command===`npx`){let r=n.find(e=>typeof e==`string`&&!e.startsWith(`-`));r&&!r.includes(`@`)&&t.push(e)}}return t},Xe=(e,t=process.cwd())=>{let r=n.resolve(t);return e.workspaces.find(e=>{let t=u(e.path);return r===t||r.startsWith(t+n.sep)})},Ze=(e=k)=>{let t=e(`gh`,[`api`,`user`,`--jq`,`.login`]),n=e(`git`,[`config`,`user.email`]);return{pwd:process.cwd(),gh:t.status===0&&t.stdout.trim()?t.stdout.trim():`none`,gitEmail:n.status===0?n.stdout.trim():`none`,tokenSet:!!process.env.GITHUB_TOKEN}},Qe=(e,n=k)=>{let r=[];ce()||r.push({status:`warn`,label:`platform`,detail:`inscope's secret resolution targets macOS (gh keyring + Keychain)`});let i=p(),a=Je(i);a===null?r.push({status:`fail`,label:`hook`,detail:`missing ${i}; run \`inscope init\``}):a===Ae(e)?r.push({status:`ok`,label:`hook`,detail:i}):r.push({status:`warn`,label:`hook`,detail:"out of date; run `inscope apply`"}),r.push(Fe()?{status:`ok`,label:`zshrc`,detail:`sources the hook`}:{status:`warn`,label:`zshrc`,detail:"does not source the hook; run `inscope init`"}),e.workspaces.some(J)&&r.push(Se(h(),q)===null?{status:`fail`,label:`gitconfig`,detail:"missing includeIf block; run `inscope apply`"}:{status:`ok`,label:`gitconfig`,detail:`includeIf block present`});for(let i of e.workspaces){let e=`[${i.name}]`;if(i.gh&&r.push(le(i.gh,n)?{status:`ok`,label:`${e} gh`,detail:`token for ${i.gh}`}:{status:`fail`,label:`${e} gh`,detail:`no token for ${i.gh}; run \`gh auth login\``}),i.servers.slack){let t=i.servers.slack.keychain;r.push(j(t,n)?{status:`ok`,label:`${e} slack`,detail:t}:{status:`fail`,label:`${e} slack`,detail:`${t} not in keychain; run \`${me(t)}\``})}if(J(i)){let a=Y(i.name);if(!t.existsSync(a))r.push({status:`fail`,label:`${e} git`,detail:`missing ${a}; run \`inscope apply\``});else if(i.git?.email){let t=he(a,n);r.push(t===i.git.email?{status:`ok`,label:`${e} git`,detail:i.git.email}:{status:`fail`,label:`${e} git`,detail:`email is ${t??`unset`}, expected ${i.git.email}`})}}let a=ae(i);if(a===null)r.push({status:`warn`,label:`${e} mcp`,detail:"no .mcp.json; run `inscope apply`"});else{let t=E(i.name).filter(e=>a.mcpServers?.[e]);r.push({status:`ok`,label:`${e} mcp`,detail:`${t.length} server(s)`});let n=Ye(a);n.length&&r.push({status:`warn`,label:`${e} mcp`,detail:`unpinned: ${n.join(`, `)}`})}}return r},$e=`Verify the setup: gh tokens resolve, keychain entries exist,
93
96
  git emails match per path, the hook is current, and no MCP server is unpinned.
94
97
  Exits non-zero if any check fails.
95
98
 
96
99
  Usage:
97
- $ ${Q} doctor
100
+ $ ${Z} doctor
98
101
 
99
102
  Options:
100
- -h, --help Display help message`,Qe={ok:`โœ“`,warn:`!`,fail:`โœ—`},$e=t=>{let{values:n}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});n.help&&(console.log(Ze),process.exit(0)),v()||(console.error(`No config found. Run \`${Q} init\` first.`),process.exit(1));let r=y(),i=Xe(r);for(let e of i)console.log(`${Qe[e.status]} ${e.label}${e.detail?` ${e.detail}`:``}`);let a=Je(r);if(a){let e=Ye();console.log(`\nThis shell (${a.name}):`),console.log(` pwd ${e.pwd}`),console.log(` gh ${e.gh}`),console.log(` git ${e.gitEmail}`),console.log(` token ${e.tokenSet?`set`:`unset`}`)}let o=i.filter(e=>e.status===`fail`).length;o&&(console.log(`\n${o} check(s) failed.`),process.exit(1)),console.log(`
101
- All checks passed.`),process.exit(0)},et=`Edit a configured workspace interactively, then re-apply.
103
+ -h, --help Display help message`,et={ok:`โœ“`,warn:`!`,fail:`โœ—`},tt=t=>{let{values:n}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});n.help&&(console.log($e),process.exit(0)),v()||(console.error(`No config found. Run \`${Z} init\` first.`),process.exit(1));let r=y(),i=Qe(r),a=i.map(e=>`${et[e.status]} ${e.label}${e.detail?` ${e.detail}`:``}`).join(`
104
+ `);console.log(`\n${a}`);let o=Xe(r);if(o){let e=Ze();console.log(`\nThis shell (${o.name}):`),console.log(` pwd ${e.pwd}`),console.log(` gh ${e.gh}`),console.log(` git ${e.gitEmail}`),console.log(` token ${e.tokenSet?`set`:`unset`}`)}let s=i.filter(e=>e.status===`fail`).length;s&&(console.log(`\n${s} check(s) failed.`),process.exit(1)),console.log(`
105
+ All checks passed.`),process.exit(0)},nt=`Edit a configured workspace interactively, then re-apply.
102
106
  Pick a workspace (or pass its path/label), step through the prompts pre-filled
103
107
  with its current values, and inscope regenerates everything on save.
104
108
 
105
109
  Usage:
106
- $ ${Q} edit [path|label]
110
+ $ ${Z} edit [path|label]
107
111
 
108
112
  Options:
109
- -h, --help Display help message`,tt=async t=>{let{positionals:n,values:r}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});r.help&&(console.log(et),process.exit(0)),v()||(console.error(`No config found. Run \`${Q} init\` first.`),process.exit(1));let i=y();i.workspaces.length||(console.error(`No workspaces yet. Add one with \`${Q} add <path>\`.`),process.exit(1));let a=n[0],o=await(async()=>{if(a){let e=C(i,a);return e||(console.error(`No workspace matching "${a}".`),process.exit(1)),e}if(i.workspaces.length===1)return i.workspaces[0];if(M())return B(`Edit which workspace?`,i.workspaces.map(e=>({label:`${e.name} (${e.path})`,value:e})));console.error(`Specify a workspace, e.g. \`${Q} edit <label>\`.`),process.exit(1)})();console.log(`\nEditing "${o.name}" (${o.path})\n`);let s=[...de().map(e=>({label:e,value:e})),{label:`(none)`,value:``}],c=await B(`GitHub account`,s,Math.max(0,s.findIndex(e=>e.value===(o.gh??``))))||void 0,l=o.git?.email,u=await I(l?`Git email (enter keeps ${l}, "-" to inherit global)`:`Git email (enter to inherit global)`,l??``),d=u===`-`?void 0:u||void 0,f=o.git?.name,p=await I(f?`Git name (enter keeps ${f}, "-" to inherit global)`:`Git name (enter to inherit global)`,f??``),m=p===`-`?void 0:p||void 0,h=Ie(o.servers),g=await V(`MCP servers (space toggles, enter confirms)`,Pe.map(e=>({label:e,value:e,checked:h.includes(e)}))),_=g.includes(`slack`),b=o.servers.slack?o.servers.slack.keychain:Fe(o.name),x=o.servers.slack?!!o.servers.slack.addMessageTool:!1,S=!1;_&&(console.log(`\nSlack uses a user OAuth (xoxp) token. Setup guide:\n ${Z}`),b=await I(`Slack keychain service`,b),x=await L(`Allow Slack to post messages?`,x),j(b)||(S=await L(`Store the Slack token now?`,!0)));let w={name:o.name,path:o.path,gh:c,git:d||m?{email:d,name:m}:void 0,servers:Le(g,_?{keychain:b,addMessageTool:x}:null)};Re(w),console.log(`\nโœ“ updated "${w.name}" -> ${w.path}`),await ze(w,S),console.log(`\nRelaunch \`claude\` from ${w.path} to pick up the changes.`),process.exit(0)},nt=`Set up inscope: create the config, generate the chpwd hook, and
113
+ -h, --help Display help message`,rt=async t=>{let{positionals:n,values:r}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});r.help&&(console.log(nt),process.exit(0)),v()||(console.error(`No config found. Run \`${Z} init\` first.`),process.exit(1));let i=y();i.workspaces.length||(console.error(`No workspaces yet. Add one with \`${Z} add <path>\`.`),process.exit(1));let a=n[0],o=await(async()=>{if(a){let e=C(i,a);return e||(console.error(`No workspace matching "${a}".`),process.exit(1)),e}if(i.workspaces.length===1)return i.workspaces[0];if(M())return V(`Edit which workspace?`,i.workspaces.map(e=>({label:`${e.name} (${e.path})`,value:e})));console.error(`Specify a workspace, e.g. \`${Z} edit <label>\`.`),process.exit(1)})();console.log(`\nEditing "${o.name}" (${o.path})\n`);let s=[...de().map(e=>({label:e,value:e})),{label:`(none)`,value:``}],c=await V(`GitHub account`,s,Math.max(0,s.findIndex(e=>e.value===(o.gh??``))))||void 0,l=o.git?.email,u=await L(l?`Git email (enter keeps ${l}, "-" to inherit global)`:`Git email (enter to inherit global)`,l??``),d=u===`-`?void 0:u||void 0,f=o.git?.name,p=await L(f?`Git name (enter keeps ${f}, "-" to inherit global)`:`Git name (enter to inherit global)`,f??``),m=p===`-`?void 0:p||void 0,h=Re(o.servers),g=await H(`MCP servers (space toggles, enter confirms)`,Ie.map(e=>({label:e,value:e,checked:h.includes(e)}))),_=g.includes(`slack`),b=o.servers.slack?o.servers.slack.keychain:Le(o.name),x=o.servers.slack?!!o.servers.slack.addMessageTool:!1,S=!1;_&&(console.log(`
114
+ Slack uses a user OAuth (xoxp) token.`),b=await L(`Slack keychain service`,b),x=await R(`Allow Slack to post messages?`,x),j(b)||(S=await R(`Store the Slack token now?`,!0)));let w={name:o.name,path:o.path,gh:c,git:d||m?{email:d,name:m}:void 0,servers:ze(g,_?{keychain:b,addMessageTool:x}:null)};Be(w),console.log(`\nโœ“ updated "${w.name}" -> ${w.path}`),await Ve(w,S),console.log(`\nRelaunch \`claude\` from ${w.path} to pick up the changes.`),process.exit(0)},it=`Set up inscope: create the config, generate the chpwd hook, and
110
115
  source it from ~/.zshrc. Safe to run again; it never overwrites your config.
111
116
 
112
117
  Usage:
113
- $ ${Q} init
118
+ $ ${Z} init
114
119
 
115
120
  Options:
116
- -h, --help Display help message`,rt=t=>{let{values:n}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});n.help&&(console.log(nt),process.exit(0));let r;v()?(r=y(),console.log(`Using existing config at ${f()}`)):(r=_(),b(r),console.log(`Created ${f()}`)),X(r),console.log(`Generated the chpwd hook and added a source line to ~/.zshrc.`),console.log(`
121
+ -h, --help Display help message`,at=t=>{let{values:n}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});n.help&&(console.log(it),process.exit(0));let r;v()?(r=y(),console.log(`\nUsing existing config at ${f()}`)):(r=_(),b(r),console.log(`\nCreated ${f()}`)),X(r),console.log(`Generated the chpwd hook and added a source line to ~/.zshrc.`),console.log(`
117
122
  Next steps:
118
123
  1. Reload your shell: source ~/.zshrc (or open a new terminal)
119
- 2. Sign each GitHub account in: gh auth login
120
- 3. Map a workspace: ${Q} add ~/acme --gh acme --email you@acme.com`),process.exit(0)},it=`List the configured workspaces. Run \`${Q} doctor\` to verify
124
+ 2. Map a workspace: ${Z} add ~/acme`),process.exit(0)},ot=`List the configured workspaces. Run \`${Z} doctor\` to verify
121
125
  that their tokens and identities actually resolve.
122
126
 
123
127
  Usage:
124
- $ ${Q} list
128
+ $ ${Z} list
125
129
 
126
130
  Options:
127
- -h, --help Display help message`,at=e=>T.filter(t=>!!e[t]).join(`, `)||`none`,ot=t=>{let{values:n}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});n.help&&(console.log(it),process.exit(0)),v()||(console.error(`No config found. Run \`${Q} init\` first.`),process.exit(1));let r=y();r.workspaces.length||(console.log(`No workspaces yet. Add one with \`${Q} add <path> --gh <account>\`.`),process.exit(0));for(let e of r.workspaces)console.log(`${e.name}`),console.log(` path ${e.path}`),console.log(` gh ${e.gh??`(none)`}`),console.log(` git ${e.git?.email??`(default)`}`),console.log(` servers ${at(e.servers)}`),e.servers.slack&&console.log(` slack keychain: ${e.servers.slack.keychain}`);process.exit(0)},st=`Remove a workspace mapping. Drops its git include and the MCP
131
+ -h, --help Display help message`,st=e=>T.filter(t=>!!e[t]).join(`, `)||`none`,ct=t=>{let{values:n}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:t});n.help&&(console.log(ot),process.exit(0)),v()||(console.error(`No config found. Run \`${Z} init\` first.`),process.exit(1));let r=y();r.workspaces.length||(console.log(`No workspaces yet. Add one with \`${Z} add <path> --gh <account>\`.`),process.exit(0));for(let e of r.workspaces)console.log(`\n${e.name}`),console.log(` path ${e.path}`),console.log(` gh ${e.gh??`(none)`}`),console.log(` git ${e.git?.email??`(default)`}`),console.log(` servers ${st(e.servers)}`),e.servers.slack&&console.log(` slack keychain: ${e.servers.slack.keychain}`);process.exit(0)},lt=`Remove a workspace mapping. Drops its git include and the MCP
128
132
  servers inscope manages; leaves your keychain and gh accounts untouched. Pick a
129
133
  workspace, or pass its path/label.
130
134
 
131
135
  Usage:
132
- $ ${Q} rm [path|label]
136
+ $ ${Z} rm [path|label]
133
137
 
134
138
  Options:
135
139
  -y, --yes Skip the type-the-label confirmation
136
- -h, --help Display help message`,ct=async t=>{let{positionals:n,values:r}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`},yes:{type:`boolean`,short:`y`}},args:t});r.help&&(console.log(st),process.exit(0)),v()||(console.error(`No config found. Run \`${Q} init\` first.`),process.exit(1));let i=y();i.workspaces.length||(console.error(`No workspaces to remove.`),process.exit(1));let a=n[0],o;if(a){let e=C(i,a);e||(console.error(`No workspace matching "${a}".`),process.exit(1)),o=e}else M()?o=await B(`Remove which workspace?`,i.workspaces.map(e=>({label:`${e.name} (${e.path})`,value:e}))):(console.error(`Specify a workspace, e.g. \`${Q} rm <label>\`.`),process.exit(1));if(!r.yes){console.log(`\nโš  Removing "${o.name}" (${o.path}) unmaps it from inscope.`);let e=await I(`Type "${o.name}" to confirm`);e!==o.name&&(console.error(`Aborted: "${e}" does not match "${o.name}".`),process.exit(1))}let{cfg:s}=ee(i,o.name);se(o),Te(o.name),b(s),X(s),console.log(`โœ“ removed workspace "${o.name}"`),o.servers.slack&&console.log(`Note: the keychain entry ${o.servers.slack.keychain} was left in place.\nDelete it with: security delete-generic-password -s ${o.servers.slack.keychain}`),process.exit(0)},lt=`Version:
137
- ${Q}@${Be}
140
+ -h, --help Display help message`,ut=async t=>{let{positionals:n,values:r}=e({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`},yes:{type:`boolean`,short:`y`}},args:t});r.help&&(console.log(lt),process.exit(0)),v()||(console.error(`No config found. Run \`${Z} init\` first.`),process.exit(1));let i=y();i.workspaces.length||(console.error(`No workspaces to remove.`),process.exit(1));let a=n[0],o;if(a){let e=C(i,a);e||(console.error(`No workspace matching "${a}".`),process.exit(1)),o=e}else M()?o=await V(`Remove which workspace?`,i.workspaces.map(e=>({label:`${e.name} (${e.path})`,value:e}))):(console.error(`Specify a workspace, e.g. \`${Z} rm <label>\`.`),process.exit(1));if(!r.yes){console.log(`\nโš  Removing "${o.name}" (${o.path}) unmaps it from inscope.`);let e=await L(`Type "${o.name}" to confirm`);e!==o.name&&(console.error(`Aborted: "${e}" does not match "${o.name}".`),process.exit(1))}let{cfg:s}=ee(i,o.name);se(o),De(o.name),b(s),X(s),console.log(`\nโœ“ removed workspace "${o.name}"`),o.servers.slack&&console.log(`\nNote: the keychain entry ${o.servers.slack.keychain} was left in place.\nDelete it with: ${N(`security delete-generic-password -s ${o.servers.slack.keychain}`)}`),process.exit(0)},$=`Version:
141
+ ${Z}@${He}
138
142
 
139
143
  Per-workspace identity for Claude Code: scope MCP servers, GitHub auth, and git
140
144
  commit identity to the directory you are in, so concurrent sessions never clash.
141
145
 
142
146
  Usage:
143
- $ ${Q} <command> [options]
147
+ $ ${Z} <command> [options]
144
148
 
145
149
  Commands:
146
150
  init Create the config, generate the hook, source it from ~/.zshrc
@@ -156,4 +160,4 @@ Options:
156
160
  -h, --help Display help
157
161
 
158
162
  Author:
159
- ${$.name} <${$.email}> (${$.url})`;(async()=>{try{let e=process.argv.slice(2),t=e[0],n=e.slice(1);switch(t){case`init`:return rt(n);case`add`:return await Ue(n);case`edit`:return tt(n);case`rm`:case`remove`:return await ct(n);case`ls`:case`list`:return ot(n);case`apply`:case`sync`:return Ge(n);case`doctor`:return $e(n)}(t===`-v`||t===`--version`)&&(console.log(`${Q}@${Be}`),process.exit(0)),(!t||t===`-h`||t===`--help`)&&(console.log(lt),process.exit(0)),console.error(`unknown command: ${e.join(` `)}\n`),console.error(lt),process.exit(1)}catch(e){console.error(e.message),process.exit(1)}})();export{};
163
+ ${Q.name} <${Q.email}> (${Q.url})`;(async()=>{try{let e=process.argv.slice(2),t=e[0],n=e.slice(1);switch(t){case`init`:return at(n);case`add`:return await Ge(n);case`edit`:return rt(n);case`rm`:case`remove`:return await ut(n);case`ls`:case`list`:return ct(n);case`apply`:case`sync`:return qe(n);case`doctor`:return tt(n)}(t===`-v`||t===`--version`)&&(console.log(`${Z}@${He}`),process.exit(0)),(!t||t===`-h`||t===`--help`)&&(console.log($),process.exit(0)),console.error(`unknown command: ${e.join(` `)}\n`),console.error($),process.exit(1)}catch(e){console.error(e.message),process.exit(1)}})();export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inscope",
3
- "version": "0.5.2",
3
+ "version": "0.6.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",