inscope 0.8.8 → 0.8.9

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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import e from"node:fs";import{parseArgs as t}from"node:util";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=()=>process.env.HOME?.trim()||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`),_=t=>{try{return e.readFileSync(t,`utf8`)}catch{return null}},v=e=>_(e)??``,y=(t,r)=>{let i=t;try{i=e.realpathSync(t)}catch{}let a=n.dirname(i);e.mkdirSync(a,{recursive:!0});let o;try{o=e.statSync(i).mode}catch{}let s=n.join(a,`.${n.basename(i)}.inscope-${process.pid}.tmp`);e.writeFileSync(s,r),o!==void 0&&e.chmodSync(s,o&4095);try{e.renameSync(s,i)}catch(t){try{e.rmSync(s,{force:!0})}catch{}throw t}},b=[`slack-mcp-server`,`@nrjdalal/slack-mcp-server`],x=`slack-mcp-server`,S=()=>({version:1,workspaces:[]}),C=()=>e.existsSync(f()),w=()=>{let t=f(),n=e.readFileSync(t,`utf8`),r;try{r=JSON.parse(n)}catch{throw Error(`${l(t)} is not valid JSON; fix it, then re-run.`)}let i=ae(r);if(i)throw Error(i);try{D(r)}catch(e){let n=e instanceof Error?e.message:String(e);throw Error(`${n}\nFix it in ${l(t)}, then re-run.`)}return r},T=e=>{D(e),y(f(),JSON.stringify(e,null,2)+`
3
- `)},ee=/^[A-Za-z0-9._-]+$/,te=e=>e?ee.test(e)?null:`use only letters, digits, dot (.), dash (-), or underscore (_)`:`must not be empty`,ne=/[\\"`$\n]/,E=e=>ne.test(e)?'must not contain a backslash (\\), quote ("), backtick (`), $, or newline':null,re=e=>e?E(e):`must not be empty`,ie=e=>/[\n\r]/.test(e)?`must not contain a newline`:null,ae=e=>typeof e.version==`number`&&e.version>1?`config version ${e.version} is newer than this inscope supports (max 1); upgrade inscope`:null,oe=e=>e.toLowerCase().replace(/[^a-z0-9._-]+/g,`-`).replace(/^[-.]+|[-.]+$/g,``),D=e=>{if(!e||typeof e!=`object`)throw Error(`config is not an object`);let t=ae(e);if(t)throw Error(t);if(!Array.isArray(e.workspaces))throw Error(`config.workspaces must be an array`);let n=new Set;for(let t of e.workspaces){if(!t.name)throw Error(`a workspace is missing a name`);let e=te(t.name);if(e)throw Error(`workspace name "${t.name}" is invalid: ${e}`);if(!t.path)throw Error(`workspace "${t.name}" is missing a path`);let r=re(t.path);if(r)throw Error(`workspace "${t.name}" path "${t.path}" is invalid: ${r}`);if(t.gh){let e=E(t.gh);if(e)throw Error(`workspace "${t.name}" gh account "${t.gh}" is invalid: ${e}`)}if(t.git?.email){let e=ie(t.git.email);if(e)throw Error(`workspace "${t.name}" git email "${t.git.email}" is invalid: ${e}`)}if(t.git?.name){let e=ie(t.git.name);if(e)throw Error(`workspace "${t.name}" git name "${t.git.name}" is invalid: ${e}`)}let i=t.servers?.slack;if(i&&i.keychain){let e=E(i.keychain);if(e)throw Error(`workspace "${t.name}" Slack keychain "${i.keychain}" is invalid: ${e}`)}let a=i&&i.package;if(a&&!b.includes(a))throw Error(`workspace "${t.name}" Slack package "${a}" is invalid: use one of ${b.join(`, `)}`);if(n.has(t.name))throw Error(`duplicate workspace name "${t.name}"`);n.add(t.name)}},se=e=>oe(n.basename(u(e)))||`workspace`,O=(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)},ce=(e,t,n)=>{let r=u(t);return e.workspaces.find(e=>e.name!==n&&u(e.path)===r)},le=(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}},ue=(e,t)=>{let n=O(e,t);return n?{cfg:{...e,workspaces:e.workspaces.filter(e=>e.name!==n.name)},removed:n}:{cfg:e}},de=(e=x)=>e===`@nrjdalal/slack-mcp-server`?`@nrjdalal/slack-mcp-server@latest`:`slack-mcp-server@1.3.0`,fe=e=>{if(!Array.isArray(e))return null;let t=e.find(e=>typeof e==`string`&&e.includes(`slack-mcp-server`));return t?t.startsWith(`@nrjdalal/slack-mcp-server`)?`@nrjdalal/slack-mcp-server`:t.startsWith(`slack-mcp-server`)?`slack-mcp-server`:null:null},k={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/`},A=[`github`,`atlassian`,`canva`,`clickup`,`hubspot`,`intercom`,`linear`,`monday`,`notion`,`plane`,`sentry`,`slack`,`stripe`,`vercel`,`webflow`],pe=e=>A.map(t=>`${t}-${e}`),j=e=>n.join(u(e.path),`.mcp.json`),me=(e,t)=>e&&typeof e==`object`&&e.url?e.url:t,he=e=>{let t=e.servers,n={};for(let r of A){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=e.package??x,r={SLACK_MCP_XOXP_TOKEN:"${SLACK_MCP_XOXP_TOKEN}"},o=[`-y`,de(t)];t===`@nrjdalal/slack-mcp-server`?e.addMessageTool||(r.SLACK_MCP_ALLOW_WRITE=`false`):(o.push(`--transport`,`stdio`),e.addMessageTool&&(r.SLACK_MCP_ADD_MESSAGE_TOOL=`true`)),n[a]={type:`stdio`,command:`npx`,args:o,env:r}}else n[a]={type:`http`,url:me(i,k[r])}}return n},ge=t=>{if(!e.existsSync(t))return{};try{return JSON.parse(e.readFileSync(t,`utf8`))}catch{return{}}},M=t=>{if(!e.existsSync(t))return{};try{return JSON.parse(e.readFileSync(t,`utf8`))}catch{throw Error(`${t} is not valid JSON; fix or remove it, then re-run inscope (left it untouched)`)}},_e=t=>{let n=j(t);return e.existsSync(n)?ge(n):null},ve=(e,t)=>{let n=e.mcpServers&&typeof e.mcpServers==`object`?{...e.mcpServers}:{};for(let e of pe(t.name))delete n[e];return Object.assign(n,he(t)),{...e,mcpServers:n}},N=e=>JSON.stringify(e,null,2)+`
3
+ `)},ee=/^[A-Za-z0-9._-]+$/,te=e=>e?ee.test(e)?null:`use only letters, digits, dot (.), dash (-), or underscore (_)`:`must not be empty`,ne=/[\\"`$\n]/,E=e=>ne.test(e)?'must not contain a backslash (\\), quote ("), backtick (`), $, or newline':null,re=e=>e?E(e):`must not be empty`,ie=e=>/[\n\r]/.test(e)?`must not contain a newline`:null,ae=e=>typeof e.version==`number`&&e.version>1?`config version ${e.version} is newer than this inscope supports (max 1); upgrade inscope`:null,oe=e=>e.toLowerCase().replace(/[^a-z0-9._-]+/g,`-`).replace(/^[-.]+|[-.]+$/g,``),D=e=>{if(!e||typeof e!=`object`)throw Error(`config is not an object`);let t=ae(e);if(t)throw Error(t);if(!Array.isArray(e.workspaces))throw Error(`config.workspaces must be an array`);let n=new Set;for(let t of e.workspaces){if(!t.name)throw Error(`a workspace is missing a name`);let e=te(t.name);if(e)throw Error(`workspace name "${t.name}" is invalid: ${e}`);if(!t.path)throw Error(`workspace "${t.name}" is missing a path`);let r=re(t.path);if(r)throw Error(`workspace "${t.name}" path "${t.path}" is invalid: ${r}`);if(t.gh){let e=E(t.gh);if(e)throw Error(`workspace "${t.name}" gh account "${t.gh}" is invalid: ${e}`)}if(t.git?.email){let e=ie(t.git.email);if(e)throw Error(`workspace "${t.name}" git email "${t.git.email}" is invalid: ${e}`)}if(t.git?.name){let e=ie(t.git.name);if(e)throw Error(`workspace "${t.name}" git name "${t.git.name}" is invalid: ${e}`)}let i=t.servers?.slack;if(i&&i.keychain){let e=E(i.keychain);if(e)throw Error(`workspace "${t.name}" Slack keychain "${i.keychain}" is invalid: ${e}`)}let a=i&&i.package;if(a&&!b.includes(a))throw Error(`workspace "${t.name}" Slack package "${a}" is invalid: use one of ${b.join(`, `)}`);if(n.has(t.name))throw Error(`duplicate workspace name "${t.name}"`);n.add(t.name)}},se=e=>oe(n.basename(u(e)))||`workspace`,O=(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)},ce=(e,t,n)=>{let r=u(t);return e.workspaces.find(e=>e.name!==n&&u(e.path)===r)},le=(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}},ue=(e,t)=>{let n=O(e,t);return n?{cfg:{...e,workspaces:e.workspaces.filter(e=>e.name!==n.name)},removed:n}:{cfg:e}},de=(e=x)=>e===`@nrjdalal/slack-mcp-server`?`@nrjdalal/slack-mcp-server@latest`:`slack-mcp-server@1.3.0`,fe=e=>{if(!Array.isArray(e))return null;let t=e.find(e=>typeof e==`string`&&e.includes(`slack-mcp-server`));if(!t)return null;let n=e=>t===e||t.startsWith(`${e}@`);return n(`@nrjdalal/slack-mcp-server`)?`@nrjdalal/slack-mcp-server`:n(`slack-mcp-server`)?`slack-mcp-server`:null},k={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/`},A=[`github`,`atlassian`,`canva`,`clickup`,`hubspot`,`intercom`,`linear`,`monday`,`notion`,`plane`,`sentry`,`slack`,`stripe`,`vercel`,`webflow`],pe=e=>A.map(t=>`${t}-${e}`),j=e=>n.join(u(e.path),`.mcp.json`),me=(e,t)=>e&&typeof e==`object`&&e.url?e.url:t,he=e=>{let t=e.servers,n={};for(let r of A){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=e.package??x,r={SLACK_MCP_XOXP_TOKEN:"${SLACK_MCP_XOXP_TOKEN}"},o=[`-y`,de(t)];t===`@nrjdalal/slack-mcp-server`?e.addMessageTool||(r.SLACK_MCP_ALLOW_WRITE=`false`):(o.push(`--transport`,`stdio`),e.addMessageTool&&(r.SLACK_MCP_ADD_MESSAGE_TOOL=`true`)),n[a]={type:`stdio`,command:`npx`,args:o,env:r}}else n[a]={type:`http`,url:me(i,k[r])}}return n},ge=t=>{if(!e.existsSync(t))return{};try{return JSON.parse(e.readFileSync(t,`utf8`))}catch{return{}}},M=t=>{if(!e.existsSync(t))return{};try{return JSON.parse(e.readFileSync(t,`utf8`))}catch{throw Error(`${t} is not valid JSON; fix or remove it, then re-run inscope (left it untouched)`)}},_e=t=>{let n=j(t);return e.existsSync(n)?ge(n):null},ve=(e,t)=>{let n=e.mcpServers&&typeof e.mcpServers==`object`?{...e.mcpServers}:{};for(let e of pe(t.name))delete n[e];return Object.assign(n,he(t)),{...e,mcpServers:n}},N=e=>JSON.stringify(e,null,2)+`
4
4
  `,ye=e=>{for(let t of e)M(j(t))},be=e=>{let t=j(e);y(t,N(ve(M(t),e)))},xe=t=>{let n=j(t);if(!e.existsSync(n))return;let r=M(n);if(r.mcpServers&&typeof r.mcpServers==`object`)for(let e of pe(t.name))delete r.mcpServers[e];y(n,N(r))},P=(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??``}},Se=()=>process.platform===`darwin`,F=()=>process.env.USER||``,Ce=(e,t=P)=>{let n=t(`gh`,[`auth`,`token`,`-u`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},we=(e=P)=>{let t=e(`gh`,[`auth`,`status`]);return(t.stdout+t.stderr).trim()},Te=(e=P)=>{let t=[];for(let n of we(e).matchAll(/account (\S+) \(/g))t.includes(n[1])||t.push(n[1]);return t},Ee=(e,t=P)=>{let n=t(`git`,[`config`,`--global`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},De=(e,t=P)=>{let n=t(`security`,[`find-generic-password`,`-a`,F(),`-s`,e,`-w`]);return n.status===0&&n.stdout.trim().length>0},Oe=(e,t,n=P)=>{let r=n(`security`,[`add-generic-password`,`-U`,`-a`,F(),`-s`,e,`-w`,t]);if(r.status!==0)throw Error(`security add-generic-password failed: ${r.stderr.trim()||`unknown error`}`)},ke=e=>`security add-generic-password -U -a "${F()||`$USER`}" -s ${e} -w 'xoxp-...'`,Ae=(e,t=P)=>{let n=t(`git`,[`config`,`--file`,e,`user.email`]);return n.status===0?n.stdout.trim():null},I=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),je=(e,t=e)=>process.stdout.isTTY?`\x1b]8;;${e}\x07${t}\x1b]8;;\x07`:e,L=e=>t=>process.stdout.isTTY?`\x1b[${e}m${t}\x1b[0m`:t,R=L(`38;5;208`),Me=L(`38;2;63;185;80`),z=L(`38;2;210;153;34`),B=L(`38;2;248;81;73`),V=e=>{let t=process.stdin;t.isTTY&&typeof t.setRawMode==`function`&&t.setRawMode(e)},Ne=()=>{V(!0),process.stdin.resume();let e=()=>V(!1);return process.once(`exit`,e),()=>{process.removeListener(`exit`,e),V(!1),process.stdin.pause()}};let H=``;const Pe=e=>new Promise(t=>{process.stdout.write(e);let n=()=>{let e=H.indexOf(`
5
5
  `);if(e<0)return!1;let n=H.slice(0,e).replace(/\r$/,``);return H=H.slice(e+1),t(n),!0};if(n())return;let r=e=>{H+=e.toString(`utf8`),H.includes(`
6
6
  `)&&(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=H.replace(/\r$/,``);H=``,t(e)};process.stdin.on(`data`,r),process.stdin.on(`end`,i),process.stdin.resume()}),U=async(e,t=``)=>(await Pe(`${e}${t?` [${t}]`:``}: `)).trim()||t,W=async(e,t=!1)=>{if(!I()){let n=(await Pe(`${e} [${t?`Y/n`:`y/N`}]: `)).trim().toLowerCase();return n?n===`y`||n===`yes`:t}return G(e,[{label:`Yes`,value:!0},{label:`No`,value:!1}],t?0:1)},Fe=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(`
@@ -61,7 +61,7 @@ add-zsh-hook chpwd __inscope_resolve_identity
61
61
  __inscope_ws="__init__" # force the first resolve, clearing any inherited token
62
62
  __inscope_resolve_identity
63
63
  `},$e=e=>{let t=o();return e===t?`$HOME`:e.startsWith(t+n.sep)?`$HOME/${e.slice(t.length+1)}`:e},et=()=>{let e=$e(p());return`[ -r "${e}" ] && source "${e}"`},tt=e=>{let t=et();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`},nt=()=>{let e=g(),t=v(e),n=tt(t);n!==t&&y(e,n)},rt=()=>v(g()).includes(et()),Z=e=>{ye(e.workspaces);let t=p();y(t,Qe(e)),Je(e),nt();let n=[];for(let t of e.workspaces)be(t),n.push(j(t));return{hook:t,gitconfig:e.workspaces.some(e=>e.git?.email||e.git?.name),mcp:n}},it=A,at=e=>`SLACK_MCP_XOXP_TOKEN_${e.toUpperCase().replace(/[^A-Z0-9]+/g,`_`)}`,ot=e=>A.filter(t=>!!e[t]),st=(e,t)=>{let n={};for(let r of A)if(r===`slack`){if(!t){n[r]=!1;continue}let e={keychain:t.keychain,addMessageTool:t.addMessageTool};t.package&&t.package!==x&&(e.package=t.package),n[r]=e}else n[r]=e.includes(r);return n},Q=[{label:`slack-mcp-server (korotovsky, pinned)`,value:`slack-mcp-server`},{label:`@nrjdalal/slack-mcp-server (latest)`,value:`@nrjdalal/slack-mcp-server`}],ct=e=>{let t=(e??``).trim().toLowerCase();return t?[`@nrjdalal/slack-mcp-server`,`nrjdalal`,`nrj`].includes(t)?`@nrjdalal/slack-mcp-server`:[`slack-mcp-server`,`default`,`original`,`korotovsky`].includes(t)?`slack-mcp-server`:null:x},lt=e=>e?`global: ${e}`:`no global set`,ut=e=>{let t=C()?w():S(),n=t.workspaces.find(t=>t.name===e.name),r=le(t,e);T(r),Z(r),n&&u(n.path)!==u(e.path)&&xe(n)},dt=async(e,t)=>{if(!e.servers.slack)return;let n=e.servers.slack.keychain;if(t){let e=await Fe(`Paste the Slack xoxp token for ${n}: `);e?(Oe(n,e),console.log(`\n✓ stored ${n} in the macOS keychain`)):console.error(`
64
- No token entered; skipped keychain write.`)}else De(n)||console.log(`\nSlack token not in the keychain yet. Store it once with:\n${R(ke(n))}\n\nSetup guide: ${R(je(`https://github.com/korotovsky/slack-mcp-server/blob/HEAD/docs/01-authentication-setup.md#option-2-using-slack_mcp_xoxp_token-user-oauth`))}`)};var $=`inscope`,ft=`0.8.8`,pt={name:`Neeraj Dalal`,email:`admin@nrjdalal.com`,url:`https://nrjdalal.com`};const mt=`Map a directory to a GitHub account, git email, and MCP servers.
64
+ No token entered; skipped keychain write.`)}else De(n)||console.log(`\nSlack token not in the keychain yet. Store it once with:\n${R(ke(n))}\n\nSetup guide: ${R(je(`https://github.com/korotovsky/slack-mcp-server/blob/HEAD/docs/01-authentication-setup.md#option-2-using-slack_mcp_xoxp_token-user-oauth`))}`)};var $=`inscope`,ft=`0.8.9`,pt={name:`Neeraj Dalal`,email:`admin@nrjdalal.com`,url:`https://nrjdalal.com`};const mt=`Map a directory to a GitHub account, git email, and MCP servers.
65
65
  Runs interactively in a terminal; pass flags or -y to skip the prompts. Re-running
66
66
  with the same label updates that workspace; each directory maps to one workspace.
67
67
 
@@ -97,9 +97,9 @@ Options:
97
97
  -h, --help Display help message`,vt=e=>{let{values:n}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:e});n.help&&(console.log(_t),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let r=w(),i=Z(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)},yt=e=>{let t=v(e);if(!t)return null;try{return JSON.parse(t)}catch{return null}},bt=e=>N(ve(yt(j(e))??{},e)),xt=t=>{let n=j(t);if(!e.existsSync(n))return null;try{return JSON.parse(e.readFileSync(n,`utf8`)),null}catch{return"invalid JSON; `apply` will not touch it until you fix it"}},St=e=>{let t=[],n=p();t.push({label:`hook`,path:n,current:v(n),next:Qe(e)}),t.push({label:`gitconfig`,path:h(),current:We(h(),J)??``,next:Ke(e)});for(let n of e.workspaces){if(!Y(n))continue;let e=X(n.name);t.push({label:`gitconfig:${n.name}`,path:e,current:v(e),next:qe(n)})}for(let n of e.workspaces){let e=j(n),r=xt(n);if(r){t.push({label:`mcp:${n.name}`,path:e,current:``,next:``,error:r});continue}t.push({label:`mcp:${n.name}`,path:e,current:v(e),next:bt(n)})}return t.filter(e=>e.error!=null||e.current!==e.next)},Ct=(e,t)=>{let n=e.length?e.split(`
98
98
  `):[],r=t.length?t.split(`
99
99
  `):[],i=n.length,a=r.length,o=Array.from({length:i+1},()=>Array.from({length:a+1},()=>0));for(let e=i-1;e>=0;e--)for(let t=a-1;t>=0;t--)o[e][t]=n[e]===r[t]?o[e+1][t+1]+1:Math.max(o[e+1][t],o[e][t+1]);let s=[],c=0,l=0;for(;c<i&&l<a;)n[c]===r[l]?(s.push(` ${n[c]}`),c++,l++):o[c+1][l]>=o[c][l+1]?(s.push(`- ${n[c]}`),c++):(s.push(`+ ${r[l]}`),l++);for(;c<i;)s.push(`- ${n[c++]}`);for(;l<a;)s.push(`+ ${r[l++]}`);return s.join(`
100
- `)},wt=e=>{let t=[],n=e.workspaces.map(e=>{let n=yt(j(e))?.mcpServers;if(!n||typeof n!=`object`)return e;let r=e.servers,i=e.servers.slack;i&&!i.addMessageTool&&n[`slack-${e.name}`]?.env?.SLACK_MCP_ADD_MESSAGE_TOOL===`true`&&(r={...r,slack:{...i,addMessageTool:!0}},t.push(`${e.name}: slack.addMessageTool = true`));let a=r.slack;if(a){let i=fe(n[`slack-${e.name}`]?.args),o=a.package??x;if(i&&i!==o){let n={...a};i===x?delete n.package:n.package=i,r={...r,slack:n},t.push(`${e.name}: slack.package = ${i}`)}}for(let i of A){if(i===`github`||i===`slack`)continue;let a=n[`${i}-${e.name}`]?.url;if(typeof a!=`string`)continue;let o=e.servers[i];if(!o){r={...r,[i]:a===k[i]?!0:{url:a}},t.push(`${e.name}: ${i} = ${a===k[i]?`enabled`:a}`);continue}a!==((typeof o==`object`?o.url:void 0)??k[i])&&(r={...r,[i]:{url:a}},t.push(`${e.name}: ${i}.url = ${a}`))}return r===e.servers?e:{...e,servers:r}});return{cfg:{...e,workspaces:n},changes:t}},Tt=e=>{if(!process.stdout.isTTY)return e;let t=process.stdout.columns||80;return e.split(`
100
+ `)},wt=(e,t)=>{let n=e?.env??{};return t===`@nrjdalal/slack-mcp-server`?n.SLACK_MCP_ALLOW_WRITE!==`false`:n.SLACK_MCP_ADD_MESSAGE_TOOL===`true`},Tt=e=>{let t=[],n=e.workspaces.map(e=>{let n=yt(j(e))?.mcpServers;if(!n||typeof n!=`object`)return e;let r=e.servers,i=e.servers.slack,a=n[`slack-${e.name}`];if(i&&a&&typeof a==`object`){let n=i.package??x,o=fe(a.args)??n,s=wt(a,o),c={...i},l=!1;o!==n&&(o===x?delete c.package:c.package=o,t.push(`${e.name}: slack.package = ${o}`),l=!0),s!==!!i.addMessageTool&&(s?c.addMessageTool=!0:delete c.addMessageTool,t.push(`${e.name}: slack.addMessageTool = ${s}`),l=!0),l&&(r={...r,slack:c})}for(let i of A){if(i===`github`||i===`slack`)continue;let a=n[`${i}-${e.name}`]?.url;if(typeof a!=`string`)continue;let o=e.servers[i];if(!o){r={...r,[i]:a===k[i]?!0:{url:a}},t.push(`${e.name}: ${i} = ${a===k[i]?`enabled`:a}`);continue}a!==((typeof o==`object`?o.url:void 0)??k[i])&&(r={...r,[i]:{url:a}},t.push(`${e.name}: ${i}.url = ${a}`))}return r===e.servers?e:{...e,servers:r}});return{cfg:{...e,workspaces:n},changes:t}},Et=e=>{if(!process.stdout.isTTY)return e;let t=process.stdout.columns||80;return e.split(`
101
101
  `).map(e=>{if(!e.startsWith(`- `)&&!e.startsWith(`+ `))return e;let n=` `.repeat(Math.max(0,t-e.length));return`\x1b[${e.startsWith(`- `)?`48;2;48;27;31;38;2;248;81;73`:`48;2;18;38;30;38;2;63;185;80`}m${e}${n}\x1b[0m`}).join(`
102
- `)},Et=`Show what \`${$} apply\` would change: a diff of each managed
102
+ `)},Dt=`Show what \`${$} apply\` would change: a diff of each managed
103
103
  artifact (the zsh hook, git includes, and .mcp.json files) against what your
104
104
  config would generate. Read-only.
105
105
 
@@ -113,9 +113,9 @@ Usage:
113
113
  Options:
114
114
  --adopt Write config-expressible on-disk settings back into the config
115
115
  --exit-code Exit 1 if anything is out of sync (for CI / pre-commit gates)
116
- -h, --help Display help message`,Dt=e=>{let{values:n}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`},adopt:{type:`boolean`},"exit-code":{type:`boolean`}},args:e});n.help&&(console.log(Et),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let r=w();if(n.adopt){let{cfg:e,changes:t}=wt(r);t.length||(console.log(`
116
+ -h, --help Display help message`,Ot=e=>{let{values:n}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`},adopt:{type:`boolean`},"exit-code":{type:`boolean`}},args:e});n.help&&(console.log(Dt),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let r=w();if(n.adopt){let{cfg:e,changes:t}=Tt(r);t.length||(console.log(`
117
117
  Nothing to adopt: the config already covers your .mcp.json settings.`),process.exit(0)),D(e),T(e),console.log(`
118
- Adopted into config:`);for(let e of t)console.log(` ${e}`);console.log(`\nRun ${R(`${$} apply`)} to regenerate from the updated config.`),process.exit(0)}let i=St(r);i.length||(console.log("\nIn sync. `apply` would change nothing."),process.exit(0));let a=n[`exit-code`]?1:0;for(let e of i)console.log(`\n${R(`${l(e.path)} (${e.label})`)}`),e.error?console.log(B(` ${e.error}`)):console.log(Tt(Ct(e.current,e.next)));let{changes:o}=wt(r);if(o.length){console.log("\nThese .mcp.json settings aren't in your config, so `apply` would drop them:");for(let e of o)console.log(` ${e}`);console.log(`\nRun ${R(`${$} diff --adopt`)} to keep them by writing them into the config.`)}process.exit(a)},Ot=de(`@nrjdalal/slack-mcp-server`),kt=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.includes(Ot)){if(n.some(e=>typeof e==`string`&&e.endsWith(`@latest`)))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},At=(e,t=process.cwd())=>{let r=n.resolve(t),i,a=-1;for(let t of e.workspaces){let e=u(t.path);(r===e||r.startsWith(e+n.sep))&&e.length>a&&(i=t,a=e.length)}return i},jt=(e=P)=>{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}},Mt=(t,r=P)=>{let i=[];Se()||i.push({status:`warn`,label:`platform`,detail:`inscope's secret resolution targets macOS (gh keyring + Keychain)`});let a=process.env.SHELL??``;a&&!/(^|\/)zsh$/.test(a)&&i.push({status:`warn`,label:`shell`,detail:`login shell is ${n.basename(a)}; inscope targets zsh (the hook is written to ~/.zshrc)`});let o=p(),s=_(o);s===null?i.push({status:`fail`,label:`hook`,detail:`missing ${o}; run \`inscope init\``}):s===Qe(t)?i.push({status:`ok`,label:`hook`,detail:o}):i.push({status:`warn`,label:`hook`,detail:"out of date; run `inscope apply`"}),i.push(rt()?{status:`ok`,label:`zshrc`,detail:`sources the hook`}:{status:`warn`,label:`zshrc`,detail:"does not source the hook; run `inscope init`"}),t.workspaces.some(Y)&&i.push(We(h(),J)===null?{status:`fail`,label:`gitconfig`,detail:"missing includeIf block; run `inscope apply`"}:{status:`ok`,label:`gitconfig`,detail:`includeIf block present`});for(let n of t.workspaces){let t=`[${n.name}]`;if(n.gh&&i.push(Ce(n.gh,r)?{status:`ok`,label:`${t} gh`,detail:`token for ${n.gh}`}:{status:`fail`,label:`${t} gh`,detail:`no token for ${n.gh}; run \`gh auth login\``}),n.servers.slack){let e=n.servers.slack.keychain;i.push(De(e,r)?{status:`ok`,label:`${t} slack`,detail:e}:{status:`fail`,label:`${t} slack`,detail:`${e} not in keychain; run \`${ke(e)}\``})}if(Y(n)){let a=X(n.name);if(!e.existsSync(a))i.push({status:`fail`,label:`${t} git`,detail:`missing ${a}; run \`inscope apply\``});else if(n.git?.email){let e=Ae(a,r);i.push(e===n.git.email?{status:`ok`,label:`${t} git`,detail:n.git.email}:{status:`fail`,label:`${t} git`,detail:`email is ${e??`unset`}, expected ${n.git.email}`})}}let a=xt(n);if(a)i.push({status:`fail`,label:`${t} mcp`,detail:a});else{let e=_e(n);if(e===null)i.push({status:`warn`,label:`${t} mcp`,detail:"no .mcp.json; run `inscope apply`"});else{let r=pe(n.name).filter(t=>e.mcpServers?.[t]);i.push({status:`ok`,label:`${t} mcp`,detail:`${r.length} server(s)`}),_(j(n))!==bt(n)&&i.push({status:`warn`,label:`${t} mcp`,detail:"out of date; run `inscope diff`"});let a=kt(e);a.length&&i.push({status:`warn`,label:`${t} mcp`,detail:`unpinned: ${a.join(`, `)}`})}}}return i},Nt=`Verify the setup: gh tokens resolve, keychain entries exist,
118
+ Adopted into config:`);for(let e of t)console.log(` ${e}`);console.log(`\nRun ${R(`${$} apply`)} to regenerate from the updated config.`),process.exit(0)}let i=St(r);i.length||(console.log("\nIn sync. `apply` would change nothing."),process.exit(0));let a=n[`exit-code`]?1:0;for(let e of i)console.log(`\n${R(`${l(e.path)} (${e.label})`)}`),e.error?console.log(B(` ${e.error}`)):console.log(Et(Ct(e.current,e.next)));let{changes:o}=Tt(r);if(o.length){console.log("\nThese .mcp.json settings aren't in your config, so `apply` would drop them:");for(let e of o)console.log(` ${e}`);console.log(`\nRun ${R(`${$} diff --adopt`)} to keep them by writing them into the config.`)}process.exit(a)},kt=de(`@nrjdalal/slack-mcp-server`),At=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.includes(kt)){if(n.some(e=>typeof e==`string`&&e.endsWith(`@latest`)))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},jt=(e,t=process.cwd())=>{let r=n.resolve(t),i,a=-1;for(let t of e.workspaces){let e=u(t.path);(r===e||r.startsWith(e+n.sep))&&e.length>a&&(i=t,a=e.length)}return i},Mt=(e=P)=>{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}},Nt=(t,r=P)=>{let i=[];Se()||i.push({status:`warn`,label:`platform`,detail:`inscope's secret resolution targets macOS (gh keyring + Keychain)`});let a=process.env.SHELL??``;a&&!/(^|\/)zsh$/.test(a)&&i.push({status:`warn`,label:`shell`,detail:`login shell is ${n.basename(a)}; inscope targets zsh (the hook is written to ~/.zshrc)`});let o=p(),s=_(o);s===null?i.push({status:`fail`,label:`hook`,detail:`missing ${o}; run \`inscope init\``}):s===Qe(t)?i.push({status:`ok`,label:`hook`,detail:o}):i.push({status:`warn`,label:`hook`,detail:"out of date; run `inscope apply`"}),i.push(rt()?{status:`ok`,label:`zshrc`,detail:`sources the hook`}:{status:`warn`,label:`zshrc`,detail:"does not source the hook; run `inscope init`"}),t.workspaces.some(Y)&&i.push(We(h(),J)===null?{status:`fail`,label:`gitconfig`,detail:"missing includeIf block; run `inscope apply`"}:{status:`ok`,label:`gitconfig`,detail:`includeIf block present`});for(let n of t.workspaces){let t=`[${n.name}]`;if(n.gh&&i.push(Ce(n.gh,r)?{status:`ok`,label:`${t} gh`,detail:`token for ${n.gh}`}:{status:`fail`,label:`${t} gh`,detail:`no token for ${n.gh}; run \`gh auth login\``}),n.servers.slack){let e=n.servers.slack.keychain;i.push(De(e,r)?{status:`ok`,label:`${t} slack`,detail:e}:{status:`fail`,label:`${t} slack`,detail:`${e} not in keychain; run \`${ke(e)}\``})}if(Y(n)){let a=X(n.name);if(!e.existsSync(a))i.push({status:`fail`,label:`${t} git`,detail:`missing ${a}; run \`inscope apply\``});else if(n.git?.email){let e=Ae(a,r);i.push(e===n.git.email?{status:`ok`,label:`${t} git`,detail:n.git.email}:{status:`fail`,label:`${t} git`,detail:`email is ${e??`unset`}, expected ${n.git.email}`})}}let a=xt(n);if(a)i.push({status:`fail`,label:`${t} mcp`,detail:a});else{let e=_e(n);if(e===null)i.push({status:`warn`,label:`${t} mcp`,detail:"no .mcp.json; run `inscope apply`"});else{let r=pe(n.name).filter(t=>e.mcpServers?.[t]);i.push({status:`ok`,label:`${t} mcp`,detail:`${r.length} server(s)`}),_(j(n))!==bt(n)&&i.push({status:`warn`,label:`${t} mcp`,detail:"out of date; run `inscope diff`"});let a=At(e);a.length&&i.push({status:`warn`,label:`${t} mcp`,detail:`unpinned: ${a.join(`, `)}`})}}}return i},Pt=`Verify the setup: gh tokens resolve, keychain entries exist,
119
119
  git emails match per path, the hook is current, and no MCP server is unpinned.
120
120
  Exits non-zero if any check fails.
121
121
 
@@ -123,8 +123,8 @@ Usage:
123
123
  $ ${$} doctor
124
124
 
125
125
  Options:
126
- -h, --help Display help message`,Pt={ok:`✓`,warn:`!`,fail:`✗`},Ft={ok:Me,warn:z,fail:B},It=e=>{let{values:n}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:e});n.help&&(console.log(Nt),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let r=w(),i=Mt(r),a=i.map(e=>`${Ft[e.status](Pt[e.status])} ${e.label}${e.detail?` ${e.detail}`:``}`).join(`
127
- `);console.log(`\n${a}`);let o=At(r);if(o){let e=jt();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${B(`${s} check(s) failed.`)}`),process.exit(1)),console.log(`\n${Me(`All checks passed.`)}`),process.exit(0)},Lt=`Edit a configured workspace interactively, then re-apply.
126
+ -h, --help Display help message`,Ft={ok:`✓`,warn:`!`,fail:`✗`},It={ok:Me,warn:z,fail:B},Lt=e=>{let{values:n}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:e});n.help&&(console.log(Pt),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let r=w(),i=Nt(r),a=i.map(e=>`${It[e.status](Ft[e.status])} ${e.label}${e.detail?` ${e.detail}`:``}`).join(`
127
+ `);console.log(`\n${a}`);let o=jt(r);if(o){let e=Mt();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${B(`${s} check(s) failed.`)}`),process.exit(1)),console.log(`\n${Me(`All checks passed.`)}`),process.exit(0)},Rt=`Edit a configured workspace interactively, then re-apply.
128
128
  Pick a workspace (or pass its path/label), step through the prompts pre-filled
129
129
  with its current values, and inscope regenerates everything on save.
130
130
 
@@ -132,25 +132,25 @@ Usage:
132
132
  $ ${$} edit [path|label]
133
133
 
134
134
  Options:
135
- -h, --help Display help message`,Rt=async e=>{let{positionals:n,values:r}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:e});r.help&&(console.log(Lt),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let i=w();i.workspaces.length||(console.error(`No workspaces yet. Add one with \`${$} add <path>\`.`),process.exit(1));let a=n[0],o=await(async()=>{if(a){let e=O(i,a);return e||(console.error(`No workspace matching "${a}".`),process.exit(1)),e}if(i.workspaces.length===1)return i.workspaces[0];if(I())return G(`Edit which workspace?`,i.workspaces.map(e=>({label:`${e.name} (${e.path})`,value:e})));console.error(`Specify a workspace, e.g. \`${$} edit <label>\`.`),process.exit(1)})();console.log(`\nEditing "${o.name}" (${o.path})\n`);let s=[...Te().map(e=>({label:e,value:e})),{label:`(none)`,value:``}],c=await G(`GitHub account`,s,Math.max(0,s.findIndex(e=>e.value===(o.gh??``))))||void 0,l=o.git?.email,u=await U(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 U(f?`Git name (enter keeps ${f}, "-" to inherit global)`:`Git name (enter to inherit global)`,f??``),m=p===`-`?void 0:p||void 0,h=ot(o.servers),g=await Re(`MCP servers (space toggles, enter confirms)`,it.map(e=>({label:e,value:e,checked:h.includes(e)}))),_=g.includes(`slack`),v=o.servers.slack?o.servers.slack.keychain:at(o.name),y=o.servers.slack?!!o.servers.slack.addMessageTool:!1,b=o.servers.slack?o.servers.slack.package??x:x,S=!1;if(_&&(console.log(`
136
- Slack uses a user OAuth (xoxp) token.`),b=await G(`Slack MCP server package`,Q,Math.max(0,Q.findIndex(e=>e.value===b))),v=await U(`Slack keychain service`,v),y=await W(`Allow Slack to post messages?`,y),De(v)||(S=await W(`Store the Slack token now?`,!0))),_){let e=E(v);e&&(console.error(`\nInvalid Slack keychain service "${v}": ${e}`),process.exit(1))}let T={name:o.name,path:o.path,gh:c,git:d||m?{email:d,name:m}:void 0,servers:st(g,_?{keychain:v,addMessageTool:y,package:b}:null)};ut(T),console.log(`\n✓ updated "${T.name}" -> ${T.path}`),await dt(T,S),console.log(`\nRelaunch \`claude\` from ${T.path} to pick up the changes.`),process.exit(0)},zt=`Set up inscope: create the config, generate the chpwd hook, and
135
+ -h, --help Display help message`,zt=async e=>{let{positionals:n,values:r}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:e});r.help&&(console.log(Rt),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let i=w();i.workspaces.length||(console.error(`No workspaces yet. Add one with \`${$} add <path>\`.`),process.exit(1));let a=n[0],o=await(async()=>{if(a){let e=O(i,a);return e||(console.error(`No workspace matching "${a}".`),process.exit(1)),e}if(i.workspaces.length===1)return i.workspaces[0];if(I())return G(`Edit which workspace?`,i.workspaces.map(e=>({label:`${e.name} (${e.path})`,value:e})));console.error(`Specify a workspace, e.g. \`${$} edit <label>\`.`),process.exit(1)})();console.log(`\nEditing "${o.name}" (${o.path})\n`);let s=[...Te().map(e=>({label:e,value:e})),{label:`(none)`,value:``}],c=await G(`GitHub account`,s,Math.max(0,s.findIndex(e=>e.value===(o.gh??``))))||void 0,l=o.git?.email,u=await U(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 U(f?`Git name (enter keeps ${f}, "-" to inherit global)`:`Git name (enter to inherit global)`,f??``),m=p===`-`?void 0:p||void 0,h=ot(o.servers),g=await Re(`MCP servers (space toggles, enter confirms)`,it.map(e=>({label:e,value:e,checked:h.includes(e)}))),_=g.includes(`slack`),v=o.servers.slack?o.servers.slack.keychain:at(o.name),y=o.servers.slack?!!o.servers.slack.addMessageTool:!1,b=o.servers.slack?o.servers.slack.package??x:x,S=!1;if(_&&(console.log(`
136
+ Slack uses a user OAuth (xoxp) token.`),b=await G(`Slack MCP server package`,Q,Math.max(0,Q.findIndex(e=>e.value===b))),v=await U(`Slack keychain service`,v),y=await W(`Allow Slack to post messages?`,y),De(v)||(S=await W(`Store the Slack token now?`,!0))),_){let e=E(v);e&&(console.error(`\nInvalid Slack keychain service "${v}": ${e}`),process.exit(1))}let T={name:o.name,path:o.path,gh:c,git:d||m?{email:d,name:m}:void 0,servers:st(g,_?{keychain:v,addMessageTool:y,package:b}:null)};ut(T),console.log(`\n✓ updated "${T.name}" -> ${T.path}`),await dt(T,S),console.log(`\nRelaunch \`claude\` from ${T.path} to pick up the changes.`),process.exit(0)},Bt=`Set up inscope: create the config, generate the chpwd hook, and
137
137
  source it from ~/.zshrc. Safe to run again; it never overwrites your config.
138
138
 
139
139
  Usage:
140
140
  $ ${$} init
141
141
 
142
142
  Options:
143
- -h, --help Display help message`,Bt=e=>{let{values:n}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:e});n.help&&(console.log(zt),process.exit(0));let r;C()?(r=w(),console.log(`\nUsing existing config at ${f()}`)):(r=S(),T(r),console.log(`\nCreated ${f()}`)),Z(r),console.log(`Generated the chpwd hook and added a source line to ~/.zshrc.`),console.log(`
143
+ -h, --help Display help message`,Vt=e=>{let{values:n}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:e});n.help&&(console.log(Bt),process.exit(0));let r;C()?(r=w(),console.log(`\nUsing existing config at ${f()}`)):(r=S(),T(r),console.log(`\nCreated ${f()}`)),Z(r),console.log(`Generated the chpwd hook and added a source line to ~/.zshrc.`),console.log(`
144
144
  Next steps:
145
145
  1. Reload your shell: source ~/.zshrc (or open a new terminal)
146
- 2. Map a workspace: ${$} add ~/acme`),process.exit(0)},Vt=`List the configured workspaces. Run \`${$} doctor\` to verify
146
+ 2. Map a workspace: ${$} add ~/acme`),process.exit(0)},Ht=`List the configured workspaces. Run \`${$} doctor\` to verify
147
147
  that their tokens and identities actually resolve.
148
148
 
149
149
  Usage:
150
150
  $ ${$} list
151
151
 
152
152
  Options:
153
- -h, --help Display help message`,Ht=e=>{let{values:n}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:e});n.help&&(console.log(Vt),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let r=w();r.workspaces.length||(console.log(`No workspaces yet. Add one with \`${$} 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 ${ot(e.servers).join(`, `)||`none`}`),e.servers.slack&&console.log(` slack keychain: ${e.servers.slack.keychain}`);process.exit(0)},Ut=`Remove a workspace mapping. Drops its git include and the MCP
153
+ -h, --help Display help message`,Ut=e=>{let{values:n}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`}},args:e});n.help&&(console.log(Ht),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let r=w();r.workspaces.length||(console.log(`No workspaces yet. Add one with \`${$} 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 ${ot(e.servers).join(`, `)||`none`}`),e.servers.slack&&console.log(` slack keychain: ${e.servers.slack.keychain}`);process.exit(0)},Wt=`Remove a workspace mapping. Drops its git include and the MCP
154
154
  servers inscope manages; leaves your keychain and gh accounts untouched. Pick a
155
155
  workspace, or pass its path/label.
156
156
 
@@ -159,7 +159,7 @@ Usage:
159
159
 
160
160
  Options:
161
161
  -y, --yes Skip the type-the-label confirmation
162
- -h, --help Display help message`,Wt=async e=>{let{positionals:n,values:r}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`},yes:{type:`boolean`,short:`y`}},args:e});r.help&&(console.log(Ut),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let i=w();i.workspaces.length||(console.error(`No workspaces to remove.`),process.exit(1));let a=n[0],o;if(a){let e=O(i,a);e||(console.error(`No workspace matching "${a}".`),process.exit(1)),o=e}else I()?o=await G(`Remove which workspace?`,i.workspaces.map(e=>({label:`${e.name} (${e.path})`,value:e}))):(console.error(`Specify a workspace, e.g. \`${$} rm <label>\`.`),process.exit(1));if(!r.yes){console.log(`\n⚠ Removing "${o.name}" (${o.path}) unmaps it from inscope.`);let e=await U(`Type "${o.name}" to confirm`);e!==o.name&&(console.error(`Aborted: "${e}" does not match "${o.name}".`),process.exit(1))}let{cfg:s}=ue(i,o.name);xe(o),Ye(o.name),T(s),Z(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: ${R(`security delete-generic-password -s ${o.servers.slack.keychain}`)}`),process.exit(0)},Gt=`Version:
162
+ -h, --help Display help message`,Gt=async e=>{let{positionals:n,values:r}=t({allowPositionals:!0,options:{help:{type:`boolean`,short:`h`},yes:{type:`boolean`,short:`y`}},args:e});r.help&&(console.log(Wt),process.exit(0)),C()||(console.error(`No config found. Run \`${$} init\` first.`),process.exit(1));let i=w();i.workspaces.length||(console.error(`No workspaces to remove.`),process.exit(1));let a=n[0],o;if(a){let e=O(i,a);e||(console.error(`No workspace matching "${a}".`),process.exit(1)),o=e}else I()?o=await G(`Remove which workspace?`,i.workspaces.map(e=>({label:`${e.name} (${e.path})`,value:e}))):(console.error(`Specify a workspace, e.g. \`${$} rm <label>\`.`),process.exit(1));if(!r.yes){console.log(`\n⚠ Removing "${o.name}" (${o.path}) unmaps it from inscope.`);let e=await U(`Type "${o.name}" to confirm`);e!==o.name&&(console.error(`Aborted: "${e}" does not match "${o.name}".`),process.exit(1))}let{cfg:s}=ue(i,o.name);xe(o),Ye(o.name),T(s),Z(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: ${R(`security delete-generic-password -s ${o.servers.slack.keychain}`)}`),process.exit(0)},Kt=`Version:
163
163
  ${$}@${ft}
164
164
 
165
165
  Per-workspace identity for Claude Code: scope MCP servers, GitHub auth, and git
@@ -183,4 +183,4 @@ Options:
183
183
  -h, --help Display help
184
184
 
185
185
  Author:
186
- ${pt.name} <${pt.email}> (${pt.url})`;(async()=>{try{let e=process.argv.slice(2),t=e[0],n=e.slice(1);switch(t){case`init`:return Bt(n);case`add`:return await gt(n);case`edit`:return Rt(n);case`rm`:case`remove`:return await Wt(n);case`ls`:case`list`:return Ht(n);case`diff`:return Dt(n);case`apply`:case`sync`:return vt(n);case`doctor`:return It(n)}(t===`-v`||t===`--version`)&&(console.log(`${$}@${ft}`),process.exit(0)),(!t||t===`-h`||t===`--help`)&&(console.log(Gt),process.exit(0)),console.error(`unknown command: ${e.join(` `)}\n`),console.error(Gt),process.exit(1)}catch(e){console.error(e.message),process.exit(1)}})();export{};
186
+ ${pt.name} <${pt.email}> (${pt.url})`;(async()=>{try{let e=process.argv.slice(2),t=e[0],n=e.slice(1);switch(t){case`init`:return Vt(n);case`add`:return await gt(n);case`edit`:return zt(n);case`rm`:case`remove`:return await Gt(n);case`ls`:case`list`:return Ut(n);case`diff`:return Ot(n);case`apply`:case`sync`:return vt(n);case`doctor`:return Lt(n)}(t===`-v`||t===`--version`)&&(console.log(`${$}@${ft}`),process.exit(0)),(!t||t===`-h`||t===`--help`)&&(console.log(Kt),process.exit(0)),console.error(`unknown command: ${e.join(` `)}\n`),console.error(Kt),process.exit(1)}catch(e){console.error(e.message),process.exit(1)}})();export{};
package/dist/index.mjs CHANGED
@@ -51,8 +51,8 @@ autoload -Uz add-zsh-hook
51
51
  add-zsh-hook chpwd __inscope_resolve_identity
52
52
  __inscope_ws="__init__" # force the first resolve, clearing any inherited token
53
53
  __inscope_resolve_identity
54
- `},z=`1.3.0`,B=(e=y)=>e===`@nrjdalal/slack-mcp-server`?`@nrjdalal/slack-mcp-server@latest`:`slack-mcp-server@${z}`,ye=e=>{if(!Array.isArray(e))return null;let t=e.find(e=>typeof e==`string`&&e.includes(`slack-mcp-server`));return t?t.startsWith(`@nrjdalal/slack-mcp-server`)?`@nrjdalal/slack-mcp-server`:t.startsWith(`slack-mcp-server`)?`slack-mcp-server`:null:null},V={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/`},H=[`github`,`atlassian`,`canva`,`clickup`,`hubspot`,`intercom`,`linear`,`monday`,`notion`,`plane`,`sentry`,`slack`,`stripe`,`vercel`,`webflow`],U=e=>H.map(t=>`${t}-${e}`),W=e=>t.join(c(e.path),`.mcp.json`),be=(e,t)=>e&&typeof e==`object`&&e.url?e.url:t,G=e=>{let t=e.servers,n={};for(let r of H){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=e.package??y,r={SLACK_MCP_XOXP_TOKEN:"${SLACK_MCP_XOXP_TOKEN}"},o=[`-y`,B(t)];t===`@nrjdalal/slack-mcp-server`?e.addMessageTool||(r.SLACK_MCP_ALLOW_WRITE=`false`):(o.push(`--transport`,`stdio`),e.addMessageTool&&(r.SLACK_MCP_ADD_MESSAGE_TOOL=`true`)),n[a]={type:`stdio`,command:`npx`,args:o,env:r}}else n[a]={type:`http`,url:be(i,V[r])}}return n},xe=e=>({mcpServers:G(e)}),Se=t=>{if(!e.existsSync(t))return{};try{return JSON.parse(e.readFileSync(t,`utf8`))}catch{return{}}},K=t=>{if(!e.existsSync(t))return{};try{return JSON.parse(e.readFileSync(t,`utf8`))}catch{throw Error(`${t} is not valid JSON; fix or remove it, then re-run inscope (left it untouched)`)}},Ce=t=>{let n=W(t);return e.existsSync(n)?Se(n):null},we=(e,t)=>{let n=e.mcpServers&&typeof e.mcpServers==`object`?{...e.mcpServers}:{};for(let e of U(t.name))delete n[e];return Object.assign(n,G(t)),{...e,mcpServers:n}},q=e=>JSON.stringify(e,null,2)+`
54
+ `},z=`1.3.0`,B=(e=y)=>e===`@nrjdalal/slack-mcp-server`?`@nrjdalal/slack-mcp-server@latest`:`slack-mcp-server@${z}`,ye=e=>{if(!Array.isArray(e))return null;let t=e.find(e=>typeof e==`string`&&e.includes(`slack-mcp-server`));if(!t)return null;let n=e=>t===e||t.startsWith(`${e}@`);return n(`@nrjdalal/slack-mcp-server`)?`@nrjdalal/slack-mcp-server`:n(`slack-mcp-server`)?`slack-mcp-server`:null},V={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/`},H=[`github`,`atlassian`,`canva`,`clickup`,`hubspot`,`intercom`,`linear`,`monday`,`notion`,`plane`,`sentry`,`slack`,`stripe`,`vercel`,`webflow`],U=e=>H.map(t=>`${t}-${e}`),W=e=>t.join(c(e.path),`.mcp.json`),be=(e,t)=>e&&typeof e==`object`&&e.url?e.url:t,G=e=>{let t=e.servers,n={};for(let r of H){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=e.package??y,r={SLACK_MCP_XOXP_TOKEN:"${SLACK_MCP_XOXP_TOKEN}"},o=[`-y`,B(t)];t===`@nrjdalal/slack-mcp-server`?e.addMessageTool||(r.SLACK_MCP_ALLOW_WRITE=`false`):(o.push(`--transport`,`stdio`),e.addMessageTool&&(r.SLACK_MCP_ADD_MESSAGE_TOOL=`true`)),n[a]={type:`stdio`,command:`npx`,args:o,env:r}}else n[a]={type:`http`,url:be(i,V[r])}}return n},xe=e=>({mcpServers:G(e)}),Se=t=>{if(!e.existsSync(t))return{};try{return JSON.parse(e.readFileSync(t,`utf8`))}catch{return{}}},K=t=>{if(!e.existsSync(t))return{};try{return JSON.parse(e.readFileSync(t,`utf8`))}catch{throw Error(`${t} is not valid JSON; fix or remove it, then re-run inscope (left it untouched)`)}},Ce=t=>{let n=W(t);return e.existsSync(n)?Se(n):null},we=(e,t)=>{let n=e.mcpServers&&typeof e.mcpServers==`object`?{...e.mcpServers}:{};for(let e of U(t.name))delete n[e];return Object.assign(n,G(t)),{...e,mcpServers:n}},q=e=>JSON.stringify(e,null,2)+`
55
55
  `,Te=e=>{for(let t of e)K(W(t))},Ee=e=>{let t=W(e);_(t,q(we(K(t),e)))},De=t=>{let n=W(t);if(!e.existsSync(n))return;let r=K(n);if(r.mcpServers&&typeof r.mcpServers==`object`)for(let e of U(t.name))delete r.mcpServers[e];_(n,q(r))},Oe=e=>{let n=i();return e===n?`$HOME`:e.startsWith(n+t.sep)?`$HOME/${e.slice(n.length+1)}`:e},ke=()=>{let e=Oe(d());return`[ -r "${e}" ] && source "${e}"`},Ae=e=>{let t=ke();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`},je=()=>{let e=m(),t=g(e),n=Ae(t);n!==t&&_(e,n)},Me=()=>g(m()).includes(ke()),Ne=e=>{Te(e.workspaces);let t=d();_(t,R(e)),L(e),je();let n=[];for(let t of e.workspaces)Ee(t),n.push(W(t));return{hook:t,gitconfig:e.workspaces.some(e=>e.git?.email||e.git?.name),mcp:n}},Pe=e=>{let t=g(e);if(!t)return null;try{return JSON.parse(t)}catch{return null}},J=e=>q(we(Pe(W(e))??{},e)),Y=t=>{let n=W(t);if(!e.existsSync(n))return null;try{return JSON.parse(e.readFileSync(n,`utf8`)),null}catch{return"invalid JSON; `apply` will not touch it until you fix it"}},Fe=e=>{let t=[],n=d();t.push({label:`hook`,path:n,current:g(n),next:R(e)}),t.push({label:`gitconfig`,path:p(),current:j(p(),M)??``,next:F(e)});for(let n of e.workspaces){if(!N(n))continue;let e=P(n.name);t.push({label:`gitconfig:${n.name}`,path:e,current:g(e),next:I(n)})}for(let n of e.workspaces){let e=W(n),r=Y(n);if(r){t.push({label:`mcp:${n.name}`,path:e,current:``,next:``,error:r});continue}t.push({label:`mcp:${n.name}`,path:e,current:g(e),next:J(n)})}return t.filter(e=>e.error!=null||e.current!==e.next)},Ie=(e,t)=>{let n=e.length?e.split(`
56
56
  `):[],r=t.length?t.split(`
57
57
  `):[],i=n.length,a=r.length,o=Array.from({length:i+1},()=>Array.from({length:a+1},()=>0));for(let e=i-1;e>=0;e--)for(let t=a-1;t>=0;t--)o[e][t]=n[e]===r[t]?o[e+1][t+1]+1:Math.max(o[e+1][t],o[e][t+1]);let s=[],c=0,l=0;for(;c<i&&l<a;)n[c]===r[l]?(s.push(` ${n[c]}`),c++,l++):o[c+1][l]>=o[c][l+1]?(s.push(`- ${n[c]}`),c++):(s.push(`+ ${r[l]}`),l++);for(;c<i;)s.push(`- ${n[c++]}`);for(;l<a;)s.push(`+ ${r[l++]}`);return s.join(`
58
- `)},Le=e=>{let t=[],n=e.workspaces.map(e=>{let n=Pe(W(e))?.mcpServers;if(!n||typeof n!=`object`)return e;let r=e.servers,i=e.servers.slack;i&&!i.addMessageTool&&n[`slack-${e.name}`]?.env?.SLACK_MCP_ADD_MESSAGE_TOOL===`true`&&(r={...r,slack:{...i,addMessageTool:!0}},t.push(`${e.name}: slack.addMessageTool = true`));let a=r.slack;if(a){let i=ye(n[`slack-${e.name}`]?.args),o=a.package??y;if(i&&i!==o){let n={...a};i===y?delete n.package:n.package=i,r={...r,slack:n},t.push(`${e.name}: slack.package = ${i}`)}}for(let i of H){if(i===`github`||i===`slack`)continue;let a=n[`${i}-${e.name}`]?.url;if(typeof a!=`string`)continue;let o=e.servers[i];if(!o){r={...r,[i]:a===V[i]?!0:{url:a}},t.push(`${e.name}: ${i} = ${a===V[i]?`enabled`:a}`);continue}a!==((typeof o==`object`?o.url:void 0)??V[i])&&(r={...r,[i]:{url:a}},t.push(`${e.name}: ${i}.url = ${a}`))}return r===e.servers?e:{...e,servers:r}});return{cfg:{...e,workspaces:n},changes:t}},X=(e,t,n)=>{let i=r(e,t,{encoding:`utf8`,input:n?.input});return{status:i.status??(i.error?127:1),stdout:i.stdout??``,stderr:i.stderr??``}},Re=()=>process.platform===`darwin`,Z=()=>process.env.USER||``,ze=(e,t=X)=>{let n=t(`gh`,[`auth`,`token`,`-u`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},Be=(e=X)=>{let t=e(`gh`,[`auth`,`status`]);return(t.stdout+t.stderr).trim()},Ve=(e=X)=>{let t=[];for(let n of Be(e).matchAll(/account (\S+) \(/g))t.includes(n[1])||t.push(n[1]);return t},He=(e,t=X)=>{let n=t(`git`,[`config`,`--global`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},Q=(e,t=X)=>{let n=t(`security`,[`find-generic-password`,`-a`,Z(),`-s`,e,`-w`]);return n.status===0&&n.stdout.trim().length>0},Ue=(e,t,n=X)=>{let r=n(`security`,[`add-generic-password`,`-U`,`-a`,Z(),`-s`,e,`-w`,t]);if(r.status!==0)throw Error(`security add-generic-password failed: ${r.stderr.trim()||`unknown error`}`)},We=e=>`security add-generic-password -U -a "${Z()||`$USER`}" -s ${e} -w 'xoxp-...'`,$=(e,t=X)=>{let n=t(`git`,[`config`,`--file`,e,`user.email`]);return n.status===0?n.stdout.trim():null},Ge=B(`@nrjdalal/slack-mcp-server`),Ke=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.includes(Ge)){if(n.some(e=>typeof e==`string`&&e.endsWith(`@latest`)))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},qe=(e,n=process.cwd())=>{let r=t.resolve(n),i,a=-1;for(let n of e.workspaces){let e=c(n.path);(r===e||r.startsWith(e+t.sep))&&e.length>a&&(i=n,a=e.length)}return i},Je=(e=X)=>{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}},Ye=(n,r=X)=>{let i=[];Re()||i.push({status:`warn`,label:`platform`,detail:`inscope's secret resolution targets macOS (gh keyring + Keychain)`});let a=process.env.SHELL??``;a&&!/(^|\/)zsh$/.test(a)&&i.push({status:`warn`,label:`shell`,detail:`login shell is ${t.basename(a)}; inscope targets zsh (the hook is written to ~/.zshrc)`});let o=d(),s=h(o);s===null?i.push({status:`fail`,label:`hook`,detail:`missing ${o}; run \`inscope init\``}):s===R(n)?i.push({status:`ok`,label:`hook`,detail:o}):i.push({status:`warn`,label:`hook`,detail:"out of date; run `inscope apply`"}),i.push(Me()?{status:`ok`,label:`zshrc`,detail:`sources the hook`}:{status:`warn`,label:`zshrc`,detail:"does not source the hook; run `inscope init`"}),n.workspaces.some(N)&&i.push(j(p(),M)===null?{status:`fail`,label:`gitconfig`,detail:"missing includeIf block; run `inscope apply`"}:{status:`ok`,label:`gitconfig`,detail:`includeIf block present`});for(let t of n.workspaces){let n=`[${t.name}]`;if(t.gh&&i.push(ze(t.gh,r)?{status:`ok`,label:`${n} gh`,detail:`token for ${t.gh}`}:{status:`fail`,label:`${n} gh`,detail:`no token for ${t.gh}; run \`gh auth login\``}),t.servers.slack){let e=t.servers.slack.keychain;i.push(Q(e,r)?{status:`ok`,label:`${n} slack`,detail:e}:{status:`fail`,label:`${n} slack`,detail:`${e} not in keychain; run \`${We(e)}\``})}if(N(t)){let a=P(t.name);if(!e.existsSync(a))i.push({status:`fail`,label:`${n} git`,detail:`missing ${a}; run \`inscope apply\``});else if(t.git?.email){let e=$(a,r);i.push(e===t.git.email?{status:`ok`,label:`${n} git`,detail:t.git.email}:{status:`fail`,label:`${n} git`,detail:`email is ${e??`unset`}, expected ${t.git.email}`})}}let a=Y(t);if(a)i.push({status:`fail`,label:`${n} mcp`,detail:a});else{let e=Ce(t);if(e===null)i.push({status:`warn`,label:`${n} mcp`,detail:"no .mcp.json; run `inscope apply`"});else{let r=U(t.name).filter(t=>e.mcpServers?.[t]);i.push({status:`ok`,label:`${n} mcp`,detail:`${r.length} server(s)`}),h(W(t))!==J(t)&&i.push({status:`warn`,label:`${n} mcp`,detail:"out of date; run `inscope diff`"});let a=Ke(e);a.length&&i.push({status:`warn`,label:`${n} mcp`,detail:`unpinned: ${a.join(`, `)}`})}}}return i};export{ee as CONFIG_VERSION,y as DEFAULT_SLACK_PACKAGE,z as SLACK_MCP_VERSION,v as SLACK_PACKAGES,b as WORKSPACE_NAME_RE,Le as adoptable,Ne as applyAll,L as applyGitconfig,Ee as applyMcp,Fe as computeDrift,ne as configExists,T as configVersionError,qe as currentWorkspace,te as defaultConfig,X as defaultRunner,Ie as diffLines,je as ensureZshrcSource,se as findWorkspace,Ve as ghAccounts,Be as ghStatus,ze as ghToken,$ as gitEmailForFile,He as gitGlobal,w as gitValueError,S as hookValueError,Re as isMacOS,Q as keychainHas,Ue as keychainSet,We as keychainSetCommand,oe as labelFromPath,Je as liveSnapshot,re as loadConfig,U as managedKeys,Y as mcpError,W as mcpFilePath,J as mcpTarget,ce as pathConflict,Ce as readMcp,De as removeMcp,ue as removeWorkspace,F as renderGitInclude,R as renderHook,xe as renderMcp,I as renderPerWorkspaceGitconfig,G as renderServers,Ae as renderZshrcSource,Ye as runDoctor,ie as saveConfig,E as slugify,le as upsertWorkspace,D as validateConfig,x as workspaceNameError,C as workspacePathError,Me as zshrcSourcesHook};
58
+ `)},Le=(e,t)=>{let n=e?.env??{};return t===`@nrjdalal/slack-mcp-server`?n.SLACK_MCP_ALLOW_WRITE!==`false`:n.SLACK_MCP_ADD_MESSAGE_TOOL===`true`},Re=e=>{let t=[],n=e.workspaces.map(e=>{let n=Pe(W(e))?.mcpServers;if(!n||typeof n!=`object`)return e;let r=e.servers,i=e.servers.slack,a=n[`slack-${e.name}`];if(i&&a&&typeof a==`object`){let n=i.package??y,o=ye(a.args)??n,s=Le(a,o),c={...i},l=!1;o!==n&&(o===y?delete c.package:c.package=o,t.push(`${e.name}: slack.package = ${o}`),l=!0),s!==!!i.addMessageTool&&(s?c.addMessageTool=!0:delete c.addMessageTool,t.push(`${e.name}: slack.addMessageTool = ${s}`),l=!0),l&&(r={...r,slack:c})}for(let i of H){if(i===`github`||i===`slack`)continue;let a=n[`${i}-${e.name}`]?.url;if(typeof a!=`string`)continue;let o=e.servers[i];if(!o){r={...r,[i]:a===V[i]?!0:{url:a}},t.push(`${e.name}: ${i} = ${a===V[i]?`enabled`:a}`);continue}a!==((typeof o==`object`?o.url:void 0)??V[i])&&(r={...r,[i]:{url:a}},t.push(`${e.name}: ${i}.url = ${a}`))}return r===e.servers?e:{...e,servers:r}});return{cfg:{...e,workspaces:n},changes:t}},X=(e,t,n)=>{let i=r(e,t,{encoding:`utf8`,input:n?.input});return{status:i.status??(i.error?127:1),stdout:i.stdout??``,stderr:i.stderr??``}},ze=()=>process.platform===`darwin`,Z=()=>process.env.USER||``,Be=(e,t=X)=>{let n=t(`gh`,[`auth`,`token`,`-u`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},Ve=(e=X)=>{let t=e(`gh`,[`auth`,`status`]);return(t.stdout+t.stderr).trim()},He=(e=X)=>{let t=[];for(let n of Ve(e).matchAll(/account (\S+) \(/g))t.includes(n[1])||t.push(n[1]);return t},Ue=(e,t=X)=>{let n=t(`git`,[`config`,`--global`,e]),r=n.stdout.trim();return n.status===0&&r?r:null},We=(e,t=X)=>{let n=t(`security`,[`find-generic-password`,`-a`,Z(),`-s`,e,`-w`]);return n.status===0&&n.stdout.trim().length>0},Ge=(e,t,n=X)=>{let r=n(`security`,[`add-generic-password`,`-U`,`-a`,Z(),`-s`,e,`-w`,t]);if(r.status!==0)throw Error(`security add-generic-password failed: ${r.stderr.trim()||`unknown error`}`)},Q=e=>`security add-generic-password -U -a "${Z()||`$USER`}" -s ${e} -w 'xoxp-...'`,$=(e,t=X)=>{let n=t(`git`,[`config`,`--file`,e,`user.email`]);return n.status===0?n.stdout.trim():null},Ke=B(`@nrjdalal/slack-mcp-server`),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.includes(Ke)){if(n.some(e=>typeof e==`string`&&e.endsWith(`@latest`)))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,n=process.cwd())=>{let r=t.resolve(n),i,a=-1;for(let n of e.workspaces){let e=c(n.path);(r===e||r.startsWith(e+t.sep))&&e.length>a&&(i=n,a=e.length)}return i},Ye=(e=X)=>{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=(n,r=X)=>{let i=[];ze()||i.push({status:`warn`,label:`platform`,detail:`inscope's secret resolution targets macOS (gh keyring + Keychain)`});let a=process.env.SHELL??``;a&&!/(^|\/)zsh$/.test(a)&&i.push({status:`warn`,label:`shell`,detail:`login shell is ${t.basename(a)}; inscope targets zsh (the hook is written to ~/.zshrc)`});let o=d(),s=h(o);s===null?i.push({status:`fail`,label:`hook`,detail:`missing ${o}; run \`inscope init\``}):s===R(n)?i.push({status:`ok`,label:`hook`,detail:o}):i.push({status:`warn`,label:`hook`,detail:"out of date; run `inscope apply`"}),i.push(Me()?{status:`ok`,label:`zshrc`,detail:`sources the hook`}:{status:`warn`,label:`zshrc`,detail:"does not source the hook; run `inscope init`"}),n.workspaces.some(N)&&i.push(j(p(),M)===null?{status:`fail`,label:`gitconfig`,detail:"missing includeIf block; run `inscope apply`"}:{status:`ok`,label:`gitconfig`,detail:`includeIf block present`});for(let t of n.workspaces){let n=`[${t.name}]`;if(t.gh&&i.push(Be(t.gh,r)?{status:`ok`,label:`${n} gh`,detail:`token for ${t.gh}`}:{status:`fail`,label:`${n} gh`,detail:`no token for ${t.gh}; run \`gh auth login\``}),t.servers.slack){let e=t.servers.slack.keychain;i.push(We(e,r)?{status:`ok`,label:`${n} slack`,detail:e}:{status:`fail`,label:`${n} slack`,detail:`${e} not in keychain; run \`${Q(e)}\``})}if(N(t)){let a=P(t.name);if(!e.existsSync(a))i.push({status:`fail`,label:`${n} git`,detail:`missing ${a}; run \`inscope apply\``});else if(t.git?.email){let e=$(a,r);i.push(e===t.git.email?{status:`ok`,label:`${n} git`,detail:t.git.email}:{status:`fail`,label:`${n} git`,detail:`email is ${e??`unset`}, expected ${t.git.email}`})}}let a=Y(t);if(a)i.push({status:`fail`,label:`${n} mcp`,detail:a});else{let e=Ce(t);if(e===null)i.push({status:`warn`,label:`${n} mcp`,detail:"no .mcp.json; run `inscope apply`"});else{let r=U(t.name).filter(t=>e.mcpServers?.[t]);i.push({status:`ok`,label:`${n} mcp`,detail:`${r.length} server(s)`}),h(W(t))!==J(t)&&i.push({status:`warn`,label:`${n} mcp`,detail:"out of date; run `inscope diff`"});let a=qe(e);a.length&&i.push({status:`warn`,label:`${n} mcp`,detail:`unpinned: ${a.join(`, `)}`})}}}return i};export{ee as CONFIG_VERSION,y as DEFAULT_SLACK_PACKAGE,z as SLACK_MCP_VERSION,v as SLACK_PACKAGES,b as WORKSPACE_NAME_RE,Re as adoptable,Ne as applyAll,L as applyGitconfig,Ee as applyMcp,Fe as computeDrift,ne as configExists,T as configVersionError,Je as currentWorkspace,te as defaultConfig,X as defaultRunner,Ie as diffLines,je as ensureZshrcSource,se as findWorkspace,He as ghAccounts,Ve as ghStatus,Be as ghToken,$ as gitEmailForFile,Ue as gitGlobal,w as gitValueError,S as hookValueError,ze as isMacOS,We as keychainHas,Ge as keychainSet,Q as keychainSetCommand,oe as labelFromPath,Ye as liveSnapshot,re as loadConfig,U as managedKeys,Y as mcpError,W as mcpFilePath,J as mcpTarget,ce as pathConflict,Ce as readMcp,De as removeMcp,ue as removeWorkspace,F as renderGitInclude,R as renderHook,xe as renderMcp,I as renderPerWorkspaceGitconfig,G as renderServers,Ae as renderZshrcSource,Xe as runDoctor,ie as saveConfig,E as slugify,le as upsertWorkspace,D as validateConfig,x as workspaceNameError,C as workspacePathError,Me as zshrcSourcesHook};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inscope",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
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
  "chpwd",