starmux 0.10.0 → 0.12.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.
Files changed (48) hide show
  1. package/CONTRIBUTING.md +1 -1
  2. package/README.md +184 -59
  3. package/SECURITY.md +3 -3
  4. package/app/README.md +6 -2
  5. package/dist/src/access-control.js +23 -20
  6. package/dist/src/access-control.js.map +1 -1
  7. package/dist/src/access-dashboard.js +8 -2
  8. package/dist/src/access-dashboard.js.map +1 -1
  9. package/dist/src/cli/instances.js +87 -14
  10. package/dist/src/cli/instances.js.map +1 -1
  11. package/dist/src/cli/starmux-status.js +122 -0
  12. package/dist/src/cli/starmux-status.js.map +1 -0
  13. package/dist/src/cli/starmux.js +222 -49
  14. package/dist/src/cli/starmux.js.map +1 -1
  15. package/dist/src/deny-dashboard.js +2 -2
  16. package/dist/src/deny-dashboard.js.map +1 -1
  17. package/dist/src/instance-runtime.js +11 -2
  18. package/dist/src/instance-runtime.js.map +1 -1
  19. package/dist/src/instance-store.js +3 -1
  20. package/dist/src/instance-store.js.map +1 -1
  21. package/dist/src/ops-dashboard.js +4 -4
  22. package/dist/src/ops-dashboard.js.map +1 -1
  23. package/dist/src/permissions-dashboard.js +2 -2
  24. package/dist/src/permissions-dashboard.js.map +1 -1
  25. package/dist/src/starmux-migration.js +1 -1
  26. package/dist/src/starmux-migration.js.map +1 -1
  27. package/docs/ACCESS_CONTROL.md +5 -5
  28. package/docs/CLIENTS.md +11 -19
  29. package/docs/CONNECT_CHATGPT.md +4 -6
  30. package/docs/INSTALL.md +9 -5
  31. package/docs/INSTANCES.md +5 -2
  32. package/docs/OAUTH.md +6 -14
  33. package/docs/PLATFORMS.md +4 -4
  34. package/docs/README.md +2 -11
  35. package/docs/REMOTE_SETUP.md +13 -13
  36. package/docs/SECURITY.md +5 -5
  37. package/docs/assets/starmux-app-en.png +0 -0
  38. package/docs/assets/starmux-architecture.svg +315 -0
  39. package/package.json +1 -1
  40. package/docs/ARCHITECTURE.md +0 -51
  41. package/docs/MIGRATION.md +0 -37
  42. package/docs/RELEASE_STATUS.md +0 -22
  43. package/docs/TYPESCRIPT_MIGRATION.md +0 -56
  44. package/docs/VALIDATION_OAUTH.md +0 -79
  45. package/docs/VALIDATION_TUNNEL.md +0 -39
  46. package/docs/VALIDATION_WORKBUDDY.md +0 -59
  47. package/docs/assets/mcp-connection-route.svg +0 -104
  48. package/docs/assets/mcp-gateway-simple.svg +0 -62
@@ -22,13 +22,13 @@ export function renderPermissionDashboard({ project, home, config, warning = "",
22
22
  '<button type="submit">撤销</button></form></td></tr>').join("");
23
23
  return '<!doctype html><html lang="zh-CN"><head>' +
24
24
  '<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">' +
25
- '<title>Mac MCP Gateway · Workspace Permissions</title>' +
25
+ '<title>Starmux · Workspace Permissions</title>' +
26
26
  '<style>body{font:15px system-ui,-apple-system,sans-serif;max-width:940px;margin:30px auto;padding:0 20px;color:#233045;background:#f6f7fa}' +
27
27
  'section{background:white;border:1px solid #ddd;padding:20px;margin:15px 0;border-radius:12px}' +
28
28
  'table{border-collapse:collapse;width:100%}td,th{padding:10px;border-bottom:1px solid #ddd;text-align:left}' +
29
29
  'input,select,button{font:inherit;padding:6px;margin:4px}input[name=target]{width:90%;max-width:550px}' +
30
30
  'small{display:block;color:#657083}code{overflow-wrap:anywhere}button{cursor:pointer}</style></head><body>' +
31
- '<h1>Mac MCP Gateway · 目录授权</h1>' +
31
+ '<h1>Starmux · 目录授权</h1>' +
32
32
  '<p>本页在 Mac 本机管理 Gateway 权限。远程 AI 不可自行增加权限。' +
33
33
  '增加或撤销授权后,下一次工具调用立即使用新权限。</p>' +
34
34
  (warning ? '<section role="alert">' + e(warning) + '</section>' : "") +
@@ -1 +1 @@
1
- {"version":3,"file":"permissions-dashboard.js","sourceRoot":"","sources":["../../src/permissions-dashboard.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from \"node:http\";\nimport type { GrantsConfig,PostResult,WorkspaceGrants } from \"./contracts.js\";\nimport { isSafeInteger } from \"./validation.js\";\n// Local owner-facing permissions console. No scripts, cookies or remote MCP\n// tool capable of elevating its own grants.\nconst e=(s: unknown)=>String(s??\"\").replace(/[&<>\"']/g,c=>\n ({\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",'\"':\"&quot;\",\"'\":\"&#39;\"}[c]!));\nexport const permissionHeaders=Object.freeze({\n \"cache-control\":\"no-store\",\"x-content-type-options\":\"nosniff\",\n \"referrer-policy\":\"no-referrer\",\n \"content-security-policy\":\"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'\"\n});\nconst selector=(grant: unknown,op: string,label: string,warning=\"\")=>\n '<form method=\"post\" action=\"/permissions\">'+\n '<input type=\"hidden\" name=\"op\" value=\"'+e(op)+'\">'+\n '<label>设备所有者管理密钥 <input name=\"token\" type=\"password\" required autocomplete=\"off\"></label> '+\n '<button type=\"submit\">'+e(label)+'</button>'+\n (warning?'<small>'+e(warning)+'</small>':\"\")+\n '</form>';\nexport function renderPermissionDashboard({project,home,config,warning=\"\",nativeAccessEnabled=false}: {project:{path:string;mode:string};home:string;config:GrantsConfig;warning?:string;nativeAccessEnabled?:boolean}){\n const rows=config.custom.map(grant=>\n '<tr><td><code>'+e(grant.path)+'</code></td><td>'+e(grant.mode)+\n '</td><td><form method=\"post\" action=\"/permissions\">'+\n '<input type=\"hidden\" name=\"op\" value=\"revoke\">'+\n '<input type=\"hidden\" name=\"target\" value=\"'+e(grant.path)+'\">'+\n '<input type=\"password\" name=\"token\" autocomplete=\"off\" required placeholder=\"管理密钥\">'+\n '<button type=\"submit\">撤销</button></form></td></tr>').join(\"\");\n return '<!doctype html><html lang=\"zh-CN\"><head>'+\n '<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">'+\n '<title>Mac MCP Gateway · Workspace Permissions</title>'+\n '<style>body{font:15px system-ui,-apple-system,sans-serif;max-width:940px;margin:30px auto;padding:0 20px;color:#233045;background:#f6f7fa}'+\n 'section{background:white;border:1px solid #ddd;padding:20px;margin:15px 0;border-radius:12px}'+\n 'table{border-collapse:collapse;width:100%}td,th{padding:10px;border-bottom:1px solid #ddd;text-align:left}'+\n 'input,select,button{font:inherit;padding:6px;margin:4px}input[name=target]{width:90%;max-width:550px}'+\n 'small{display:block;color:#657083}code{overflow-wrap:anywhere}button{cursor:pointer}</style></head><body>'+\n '<h1>Mac MCP Gateway · 目录授权</h1>'+\n '<p>本页在 Mac 本机管理 Gateway 权限。远程 AI 不可自行增加权限。'+\n '增加或撤销授权后,下一次工具调用立即使用新权限。</p>'+\n (warning?'<section role=\"alert\">'+e(warning)+'</section>':\"\")+\n (nativeAccessEnabled?'<section role=\"alert\"><h2>可信开发模式 · 完整本地执行权限</h2>'+\n '<p>此设备所有者启用了原生 Desktop Commander 工具透传。'+\n '以下 Home/外部/自定义目录开关<strong>不限制原生 DC 文件和 Shell 工具</strong>;'+\n '可访问范围由 Desktop Commander 和 macOS 用户账户决定。'+\n '开关仅在切回受限工作区模式后生效。请勿将此服务暴露给未经认证的客户端。</p>'+\n '</section>':\"\")+\n '<section><h2>工作目录 · 读写范围</h2><code>'+e(project.path)+'</code>'+\n '<p>工作模式:<b>'+e(project.mode)+'</b>。该项目沿用原有 Gateway 安全规则。</p></section>'+\n '<section><h2>Home · 只读</h2><code>'+e(home)+'</code>'+\n '<p>当前:<b>'+(config.homeRead?\"已开启只读扫描\":\"未授权\")+'</b></p>'+\n selector(null,config.homeRead?\"home-disable\":\"home-enable\",\n config.homeRead?\"关闭 Home 读取\":\"允许 Home 只读\",\n \"默认仍保护 SSH、密钥、凭据及配置路径。\")+'</section>'+\n '<section><h2>Home 外 · 只读</h2>'+\n '<p>当前:<b>'+(config.outsideRead?\"已开启只读扫描\":\"未授权\")+'</b></p>'+\n selector(null,config.outsideRead?\"outside-disable\":\"outside-enable\",\n config.outsideRead?\"关闭 Home 外读取\":\"允许 Home 外只读\",\n \"不会授予删除、任意 Shell 或写操作。\")+'</section>'+\n '<section><h2>自定义目录 · 单独授权</h2>'+\n '<table><thead><tr><th>目录</th><th>权限</th><th>操作</th></tr></thead><tbody>'+\n (rows||'<tr><td colspan=\"3\">暂无自定义授权</td></tr>')+'</tbody></table>'+\n '<h3>新增或修改</h3><form method=\"post\" action=\"/permissions\">'+\n '<input type=\"hidden\" name=\"op\" value=\"add\">'+\n '<label>绝对路径 <input type=\"text\" name=\"target\" required placeholder=\"/Volumes/Disk/Folder\"></label>'+\n '<label>权限 <select name=\"mode\"><option value=\"read-only\">只读</option>'+\n '<option value=\"read-write\">读写</option></select></label>'+\n '<label>管理密钥 <input type=\"password\" name=\"token\" required autocomplete=\"off\"></label>'+\n '<button type=\"submit\">确认授权</button></form></section>'+\n '<p><a href=\"/dashboard\">返回运行 Dashboard</a> · '+\n '<a href=\"/filters\">工具黑名单</a></p>'+\n '<small>授权管理密钥仅在 Mac 本地终端显示;不可交给远程 Agent。'+\n '本页不改变 trusted-project 的同 UID Shell 风险。</small></body></html>';\n}\nexport async function applyPermissionPost(req: IncomingMessage,grants: WorkspaceGrants,host: string|undefined): Promise<PostResult>{\n if(req.headers.origin!==\"http://\"+host||\n (req.headers[\"content-type\"]||\"\").split(\";\")[0]!==\"application/x-www-form-urlencoded\")\n return {status:403,message:\"Owner authorization required\"};\n const size=Number(req.headers[\"content-length\"]||0);\n if(!isSafeInteger(size)||size>4096||size<1)\n return {status:413,message:\"Invalid request\"};\n let body=\"\";\n for await(const chunk of req){\n body+=chunk.toString(\"utf8\");\n if(Buffer.byteLength(body)>4096)return {status:413,message:\"Invalid request\"};\n }\n const q=new URLSearchParams(body);\n for(const key of q.keys())\n if(![\"op\",\"target\",\"mode\",\"token\"].includes(key)||q.getAll(key).length!==1)\n return {status:400,message:\"Invalid form\"};\n if(!await grants.authorized(q.get(\"token\")))\n return {status:403,message:\"Owner authorization required\"};\n const operation=q.get(\"op\")??\"\";\n if(![\"home-enable\",\"home-disable\",\"outside-enable\",\"outside-disable\",\"add\",\"revoke\"]\n .includes(operation))return {status:400,message:\"Invalid operation\"};\n if(operation===\"add\"&&![\"read-only\",\"read-write\"].includes(q.get(\"mode\")??\"\"))\n return {status:400,message:\"Invalid mode\"};\n try{\n await grants.update(operation,{target:q.get(\"target\"),mode:q.get(\"mode\")});\n return {status:303,location:\"/permissions\"};\n }catch{\n return {status:400,message:\"Grant rejected: verify directory and permissions\"};\n }\n}\n"]}
1
+ {"version":3,"file":"permissions-dashboard.js","sourceRoot":"","sources":["../../src/permissions-dashboard.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage } from \"node:http\";\nimport type { GrantsConfig,PostResult,WorkspaceGrants } from \"./contracts.js\";\nimport { isSafeInteger } from \"./validation.js\";\n// Local owner-facing permissions console. No scripts, cookies or remote MCP\n// tool capable of elevating its own grants.\nconst e=(s: unknown)=>String(s??\"\").replace(/[&<>\"']/g,c=>\n ({\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",'\"':\"&quot;\",\"'\":\"&#39;\"}[c]!));\nexport const permissionHeaders=Object.freeze({\n \"cache-control\":\"no-store\",\"x-content-type-options\":\"nosniff\",\n \"referrer-policy\":\"no-referrer\",\n \"content-security-policy\":\"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'\"\n});\nconst selector=(grant: unknown,op: string,label: string,warning=\"\")=>\n '<form method=\"post\" action=\"/permissions\">'+\n '<input type=\"hidden\" name=\"op\" value=\"'+e(op)+'\">'+\n '<label>设备所有者管理密钥 <input name=\"token\" type=\"password\" required autocomplete=\"off\"></label> '+\n '<button type=\"submit\">'+e(label)+'</button>'+\n (warning?'<small>'+e(warning)+'</small>':\"\")+\n '</form>';\nexport function renderPermissionDashboard({project,home,config,warning=\"\",nativeAccessEnabled=false}: {project:{path:string;mode:string};home:string;config:GrantsConfig;warning?:string;nativeAccessEnabled?:boolean}){\n const rows=config.custom.map(grant=>\n '<tr><td><code>'+e(grant.path)+'</code></td><td>'+e(grant.mode)+\n '</td><td><form method=\"post\" action=\"/permissions\">'+\n '<input type=\"hidden\" name=\"op\" value=\"revoke\">'+\n '<input type=\"hidden\" name=\"target\" value=\"'+e(grant.path)+'\">'+\n '<input type=\"password\" name=\"token\" autocomplete=\"off\" required placeholder=\"管理密钥\">'+\n '<button type=\"submit\">撤销</button></form></td></tr>').join(\"\");\n return '<!doctype html><html lang=\"zh-CN\"><head>'+\n '<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">'+\n '<title>Starmux · Workspace Permissions</title>'+\n '<style>body{font:15px system-ui,-apple-system,sans-serif;max-width:940px;margin:30px auto;padding:0 20px;color:#233045;background:#f6f7fa}'+\n 'section{background:white;border:1px solid #ddd;padding:20px;margin:15px 0;border-radius:12px}'+\n 'table{border-collapse:collapse;width:100%}td,th{padding:10px;border-bottom:1px solid #ddd;text-align:left}'+\n 'input,select,button{font:inherit;padding:6px;margin:4px}input[name=target]{width:90%;max-width:550px}'+\n 'small{display:block;color:#657083}code{overflow-wrap:anywhere}button{cursor:pointer}</style></head><body>'+\n '<h1>Starmux · 目录授权</h1>'+\n '<p>本页在 Mac 本机管理 Gateway 权限。远程 AI 不可自行增加权限。'+\n '增加或撤销授权后,下一次工具调用立即使用新权限。</p>'+\n (warning?'<section role=\"alert\">'+e(warning)+'</section>':\"\")+\n (nativeAccessEnabled?'<section role=\"alert\"><h2>可信开发模式 · 完整本地执行权限</h2>'+\n '<p>此设备所有者启用了原生 Desktop Commander 工具透传。'+\n '以下 Home/外部/自定义目录开关<strong>不限制原生 DC 文件和 Shell 工具</strong>;'+\n '可访问范围由 Desktop Commander 和 macOS 用户账户决定。'+\n '开关仅在切回受限工作区模式后生效。请勿将此服务暴露给未经认证的客户端。</p>'+\n '</section>':\"\")+\n '<section><h2>工作目录 · 读写范围</h2><code>'+e(project.path)+'</code>'+\n '<p>工作模式:<b>'+e(project.mode)+'</b>。该项目沿用原有 Gateway 安全规则。</p></section>'+\n '<section><h2>Home · 只读</h2><code>'+e(home)+'</code>'+\n '<p>当前:<b>'+(config.homeRead?\"已开启只读扫描\":\"未授权\")+'</b></p>'+\n selector(null,config.homeRead?\"home-disable\":\"home-enable\",\n config.homeRead?\"关闭 Home 读取\":\"允许 Home 只读\",\n \"默认仍保护 SSH、密钥、凭据及配置路径。\")+'</section>'+\n '<section><h2>Home 外 · 只读</h2>'+\n '<p>当前:<b>'+(config.outsideRead?\"已开启只读扫描\":\"未授权\")+'</b></p>'+\n selector(null,config.outsideRead?\"outside-disable\":\"outside-enable\",\n config.outsideRead?\"关闭 Home 外读取\":\"允许 Home 外只读\",\n \"不会授予删除、任意 Shell 或写操作。\")+'</section>'+\n '<section><h2>自定义目录 · 单独授权</h2>'+\n '<table><thead><tr><th>目录</th><th>权限</th><th>操作</th></tr></thead><tbody>'+\n (rows||'<tr><td colspan=\"3\">暂无自定义授权</td></tr>')+'</tbody></table>'+\n '<h3>新增或修改</h3><form method=\"post\" action=\"/permissions\">'+\n '<input type=\"hidden\" name=\"op\" value=\"add\">'+\n '<label>绝对路径 <input type=\"text\" name=\"target\" required placeholder=\"/Volumes/Disk/Folder\"></label>'+\n '<label>权限 <select name=\"mode\"><option value=\"read-only\">只读</option>'+\n '<option value=\"read-write\">读写</option></select></label>'+\n '<label>管理密钥 <input type=\"password\" name=\"token\" required autocomplete=\"off\"></label>'+\n '<button type=\"submit\">确认授权</button></form></section>'+\n '<p><a href=\"/dashboard\">返回运行 Dashboard</a> · '+\n '<a href=\"/filters\">工具黑名单</a></p>'+\n '<small>授权管理密钥仅在 Mac 本地终端显示;不可交给远程 Agent。'+\n '本页不改变 trusted-project 的同 UID Shell 风险。</small></body></html>';\n}\nexport async function applyPermissionPost(req: IncomingMessage,grants: WorkspaceGrants,host: string|undefined): Promise<PostResult>{\n if(req.headers.origin!==\"http://\"+host||\n (req.headers[\"content-type\"]||\"\").split(\";\")[0]!==\"application/x-www-form-urlencoded\")\n return {status:403,message:\"Owner authorization required\"};\n const size=Number(req.headers[\"content-length\"]||0);\n if(!isSafeInteger(size)||size>4096||size<1)\n return {status:413,message:\"Invalid request\"};\n let body=\"\";\n for await(const chunk of req){\n body+=chunk.toString(\"utf8\");\n if(Buffer.byteLength(body)>4096)return {status:413,message:\"Invalid request\"};\n }\n const q=new URLSearchParams(body);\n for(const key of q.keys())\n if(![\"op\",\"target\",\"mode\",\"token\"].includes(key)||q.getAll(key).length!==1)\n return {status:400,message:\"Invalid form\"};\n if(!await grants.authorized(q.get(\"token\")))\n return {status:403,message:\"Owner authorization required\"};\n const operation=q.get(\"op\")??\"\";\n if(![\"home-enable\",\"home-disable\",\"outside-enable\",\"outside-disable\",\"add\",\"revoke\"]\n .includes(operation))return {status:400,message:\"Invalid operation\"};\n if(operation===\"add\"&&![\"read-only\",\"read-write\"].includes(q.get(\"mode\")??\"\"))\n return {status:400,message:\"Invalid mode\"};\n try{\n await grants.update(operation,{target:q.get(\"target\"),mode:q.get(\"mode\")});\n return {status:303,location:\"/permissions\"};\n }catch{\n return {status:400,message:\"Grant rejected: verify directory and permissions\"};\n }\n}\n"]}
@@ -126,7 +126,7 @@ async function migrateRegistry() {
126
126
  export async function migrate(source, yes = false, json = false) {
127
127
  if (!source) {
128
128
  const registry = await exists(path.join(oldRoot(), 'registry.json'));
129
- const legacy = await migrateLegacy();
129
+ const legacy = { ...await migrateLegacy(), message: 'Choose a source, then run starmux migrate setup or starmux migrate live. Use -y for non-interactive confirmation. Running legacy instances using these stores will be stopped; original files are retained.' };
130
130
  process.stdout.write(JSON.stringify({ registry: registry ? path.join(oldRoot(), 'registry.json') : null, legacy, next: 'starmux migrate registry|setup|live [-y]' }, null, 2) + '\n');
131
131
  return;
132
132
  }
@@ -1 +1 @@
1
- {"version":3,"file":"starmux-migration.js","sourceRoot":"","sources":["../../src/starmux-migration.ts"],"names":[],"mappings":"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","sourcesContent":["import {homedir} from 'node:os';\nimport path from 'node:path';\nimport {readdir,rename,rm,rmdir,lstat,mkdir} from 'node:fs/promises';\nimport {createInterface} from 'node:readline/promises';\nimport {createPrivateDirectory} from './platform.js';\nimport {instanceRequest} from './instance-runtime.js';\nimport {createAccessControl} from './access-control.js';\nimport {migrateLegacy} from './instance-migration.js';\nimport {atomicPrivate,configTemplate,exists,instancesRoot,parseConfig,readRegistry,readTrusted,registrySchema,sourceRevision,fingerprint,validateConfig,withLock} from './instance-store.js';\n\nconst oldRoot=()=>path.join(homedir(),'.config','mac-mcp-gateway');\n// Only recover locks positively identified as belonging to a dead migration process.\nasync function migrationLock<T>(file:string,action:()=>Promise<T>):Promise<T>{\n const marker=path.join(file,'starmux-migration-owner.json');\n if(await exists(marker)){\n const recovery=file+'.starmux-recovery';await mkdir(recovery,{mode:0o700});\n try{\n const owner=JSON.parse(await readTrusted(marker)) as {pid:number;source:string};\n if(!Number.isSafeInteger(owner.pid)||owner.pid<2||owner.source!==oldRoot())throw Error('Unrecognized migration lock.');\n let alive=true;try{process.kill(owner.pid,0);}catch(error){if((error as NodeJS.ErrnoException).code==='ESRCH')alive=false;else throw error;}\n if(!alive)await rm(file,{recursive:true});\n }finally{await rmdir(recovery);}\n }\n return withLock(file,async()=>{\n await atomicPrivate(marker,JSON.stringify({pid:process.pid,source:oldRoot()}));\n return action();\n });\n}\nasync function copyPrivate(source:string,target:string){\n const info=await lstat(source);\n if(info.isSymbolicLink())throw Error('Migration refuses symbolic links: '+source);\n if(info.isDirectory()){\n createPrivateDirectory(target);\n for(const name of await readdir(source)){\n if(name.endsWith('.lock')||name.endsWith('-lock')||name==='last-runtime.json')continue;\n await copyPrivate(path.join(source,name),path.join(target,name));\n }\n }else await atomicPrivate(target,await readTrusted(source,true,32*1024*1024));\n}\nasync function migrateRegistry(){\n const source=oldRoot(),target=instancesRoot(),stage=target+'.migration';\n if(await exists(target)&&(await readdir(target)).length)throw Error('Migration destination already exists; no files were overwritten.');\n return migrationLock(path.join(source,'.registry-lock'),async()=>{\n const registry=registrySchema.parse(JSON.parse(await readTrusted(path.join(source,'registry.json'))));\n if(new Set(registry.instances.map(i=>i.id)).size!==registry.instances.length||new Set(registry.instances.map(i=>i.name)).size!==registry.instances.length)throw Error('Invalid duplicate identities in old registry.');\n for(const instance of registry.instances){\n const managed=path.join(source,'instances',instance.id,'config.jsonc');\n if(instance.source.kind==='managed'&&instance.source.path!==managed)throw Error('Invalid old managed source path.');\n await validateConfig(parseConfig(await readTrusted(instance.source.path,instance.source.kind==='managed')));\n }\n if(await exists(stage)){\n const marker=JSON.parse(await readTrusted(path.join(stage,'migration.json'))) as {source?:string};\n if(marker.source!==source)throw Error('Unrecognized migration staging directory.');\n await rm(stage,{recursive:true});\n }\n createPrivateDirectory(stage);\n await atomicPrivate(path.join(stage,'migration.json'),JSON.stringify({source,result:'PREPARING',oldFilesRetained:true}));\n for(const instance of registry.instances){\n const stopped=await instanceRequest(instance,'stop');\n if(stopped&&stopped.phase!=='stopped')throw Error('Could not stop old instance '+instance.name);\n // Wait until the old supervisor has released its control identity.\n for(let n=0;n<100;n++){\n try{if(!await instanceRequest(instance,'status'))break;}catch{break;}\n if(n===99)throw Error('Old supervisor is still exiting: '+instance.name);\n await new Promise(r=>setTimeout(r,100));\n }\n const from=path.join(source,'instances',instance.id),to=path.join(stage,'instances',instance.id);\n await migrationLock(path.join(from,'state','access-v1.json.lock'),async()=>{await copyPrivate(from,to);});\n if(instance.source.kind==='managed')instance.source.path=path.join(target,'instances',instance.id,'config.jsonc');\n const accessFile=path.join(to,'state','access-v1.json');\n if(await exists(accessFile))await (await createAccessControl(accessFile)).snapshot();\n else if(instance.requiredAccess!=='ingress'||await exists(accessFile+'.initialized'))throw Error('Missing old authentication state: '+instance.name);\n const snapshot=path.join(to,'snapshots','active.json');\n if(await exists(snapshot)){\n const active=JSON.parse(await readTrusted(snapshot)) as {config:unknown;sourcePath:string;revision:string;effectiveRevision:string};\n const config=await validateConfig(parseConfig(JSON.stringify(active.config)));\n active.sourcePath=instance.source.path;\n active.revision=sourceRevision(instance.source.path,configTemplate(config));\n active.effectiveRevision=fingerprint(JSON.stringify({...config,$schema:undefined}));\n await atomicPrivate(snapshot,JSON.stringify(active));\n }\n }\n if(await exists(path.join(source,'gateway.schema.json')))await copyPrivate(path.join(source,'gateway.schema.json'),path.join(stage,'gateway.schema.json'));\n await atomicPrivate(path.join(stage,'registry.json'),JSON.stringify(registry,null,2));\n await atomicPrivate(path.join(stage,'migration.json'),JSON.stringify({source,result:'MIGRATED_NOT_STARTED',oldFilesRetained:true,at:new Date().toISOString()}));\n if(await exists(target))await rmdir(target);\n await rename(stage,target);\n return {result:'MIGRATED_NOT_STARTED',instances:registry.instances.length,oldFilesRetained:true};\n });\n}\nexport async function migrate(source:string|undefined,yes=false,json=false){\n if(!source){\n const registry=await exists(path.join(oldRoot(),'registry.json'));\n const legacy=await migrateLegacy();\n process.stdout.write(JSON.stringify({registry:registry?path.join(oldRoot(),'registry.json'):null,legacy,next:'starmux migrate registry|setup|live [-y]'},null,2)+'\\n');return;\n }\n if(!['registry','setup','live'].includes(source))throw Error('Choose registry, setup or live.');\n if(source!=='registry'&&await exists(path.join(oldRoot(),'registry.json')))throw Error('Old registry found. Migrate registry to preserve existing identities.');\n if(!yes){\n if(!process.stdin.isTTY)throw Error('Migration stops the selected old gateway. Re-run with -y to confirm.');\n const rl=createInterface({input:process.stdin,output:process.stderr});\n try{if(!/^y(es)?$/i.test((await rl.question('Migrate '+source+' and stop its old processes? Originals will be retained. [y/N] ')).trim()))throw Error('Cancelled.');}finally{rl.close();}\n }\n let result:unknown;\n if(source==='registry')result=await migrateRegistry();\n else {\n const marker=path.join(instancesRoot(),'migration-in-progress.json');\n if(await exists(instancesRoot())&&(await readdir(instancesRoot())).length){\n if(!await exists(marker))throw Error('Migration destination already exists.');\n const previous=JSON.parse(await readTrusted(marker)) as {source:string};\n if(previous.source!==source||(await readRegistry()).instances.length)throw Error('Migration destination already has instances.');\n }\n createPrivateDirectory(instancesRoot());await atomicPrivate(marker,JSON.stringify({source}));\n result=await migrateLegacy(source,true);\n await atomicPrivate(path.join(instancesRoot(),'migration.json'),JSON.stringify({source,result:'MIGRATED_NOT_STARTED',oldFilesRetained:true}));await rm(marker);\n }\n if(json)process.stdout.write(JSON.stringify(result,null,2)+'\\n');else process.stdout.write('Migrated. Originals retained. Start with starmux up.\\n');\n}\n"]}
1
+ {"version":3,"file":"starmux-migration.js","sourceRoot":"","sources":["../../src/starmux-migration.ts"],"names":[],"mappings":"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","sourcesContent":["import {homedir} from 'node:os';\nimport path from 'node:path';\nimport {readdir,rename,rm,rmdir,lstat,mkdir} from 'node:fs/promises';\nimport {createInterface} from 'node:readline/promises';\nimport {createPrivateDirectory} from './platform.js';\nimport {instanceRequest} from './instance-runtime.js';\nimport {createAccessControl} from './access-control.js';\nimport {migrateLegacy} from './instance-migration.js';\nimport {atomicPrivate,configTemplate,exists,instancesRoot,parseConfig,readRegistry,readTrusted,registrySchema,sourceRevision,fingerprint,validateConfig,withLock} from './instance-store.js';\n\nconst oldRoot=()=>path.join(homedir(),'.config','mac-mcp-gateway');\n// Only recover locks positively identified as belonging to a dead migration process.\nasync function migrationLock<T>(file:string,action:()=>Promise<T>):Promise<T>{\n const marker=path.join(file,'starmux-migration-owner.json');\n if(await exists(marker)){\n const recovery=file+'.starmux-recovery';await mkdir(recovery,{mode:0o700});\n try{\n const owner=JSON.parse(await readTrusted(marker)) as {pid:number;source:string};\n if(!Number.isSafeInteger(owner.pid)||owner.pid<2||owner.source!==oldRoot())throw Error('Unrecognized migration lock.');\n let alive=true;try{process.kill(owner.pid,0);}catch(error){if((error as NodeJS.ErrnoException).code==='ESRCH')alive=false;else throw error;}\n if(!alive)await rm(file,{recursive:true});\n }finally{await rmdir(recovery);}\n }\n return withLock(file,async()=>{\n await atomicPrivate(marker,JSON.stringify({pid:process.pid,source:oldRoot()}));\n return action();\n });\n}\nasync function copyPrivate(source:string,target:string){\n const info=await lstat(source);\n if(info.isSymbolicLink())throw Error('Migration refuses symbolic links: '+source);\n if(info.isDirectory()){\n createPrivateDirectory(target);\n for(const name of await readdir(source)){\n if(name.endsWith('.lock')||name.endsWith('-lock')||name==='last-runtime.json')continue;\n await copyPrivate(path.join(source,name),path.join(target,name));\n }\n }else await atomicPrivate(target,await readTrusted(source,true,32*1024*1024));\n}\nasync function migrateRegistry(){\n const source=oldRoot(),target=instancesRoot(),stage=target+'.migration';\n if(await exists(target)&&(await readdir(target)).length)throw Error('Migration destination already exists; no files were overwritten.');\n return migrationLock(path.join(source,'.registry-lock'),async()=>{\n const registry=registrySchema.parse(JSON.parse(await readTrusted(path.join(source,'registry.json'))));\n if(new Set(registry.instances.map(i=>i.id)).size!==registry.instances.length||new Set(registry.instances.map(i=>i.name)).size!==registry.instances.length)throw Error('Invalid duplicate identities in old registry.');\n for(const instance of registry.instances){\n const managed=path.join(source,'instances',instance.id,'config.jsonc');\n if(instance.source.kind==='managed'&&instance.source.path!==managed)throw Error('Invalid old managed source path.');\n await validateConfig(parseConfig(await readTrusted(instance.source.path,instance.source.kind==='managed')));\n }\n if(await exists(stage)){\n const marker=JSON.parse(await readTrusted(path.join(stage,'migration.json'))) as {source?:string};\n if(marker.source!==source)throw Error('Unrecognized migration staging directory.');\n await rm(stage,{recursive:true});\n }\n createPrivateDirectory(stage);\n await atomicPrivate(path.join(stage,'migration.json'),JSON.stringify({source,result:'PREPARING',oldFilesRetained:true}));\n for(const instance of registry.instances){\n const stopped=await instanceRequest(instance,'stop');\n if(stopped&&stopped.phase!=='stopped')throw Error('Could not stop old instance '+instance.name);\n // Wait until the old supervisor has released its control identity.\n for(let n=0;n<100;n++){\n try{if(!await instanceRequest(instance,'status'))break;}catch{break;}\n if(n===99)throw Error('Old supervisor is still exiting: '+instance.name);\n await new Promise(r=>setTimeout(r,100));\n }\n const from=path.join(source,'instances',instance.id),to=path.join(stage,'instances',instance.id);\n await migrationLock(path.join(from,'state','access-v1.json.lock'),async()=>{await copyPrivate(from,to);});\n if(instance.source.kind==='managed')instance.source.path=path.join(target,'instances',instance.id,'config.jsonc');\n const accessFile=path.join(to,'state','access-v1.json');\n if(await exists(accessFile))await (await createAccessControl(accessFile)).snapshot();\n else if(instance.requiredAccess!=='ingress'||await exists(accessFile+'.initialized'))throw Error('Missing old authentication state: '+instance.name);\n const snapshot=path.join(to,'snapshots','active.json');\n if(await exists(snapshot)){\n const active=JSON.parse(await readTrusted(snapshot)) as {config:unknown;sourcePath:string;revision:string;effectiveRevision:string};\n const config=await validateConfig(parseConfig(JSON.stringify(active.config)));\n active.sourcePath=instance.source.path;\n active.revision=sourceRevision(instance.source.path,configTemplate(config));\n active.effectiveRevision=fingerprint(JSON.stringify({...config,$schema:undefined}));\n await atomicPrivate(snapshot,JSON.stringify(active));\n }\n }\n if(await exists(path.join(source,'gateway.schema.json')))await copyPrivate(path.join(source,'gateway.schema.json'),path.join(stage,'gateway.schema.json'));\n await atomicPrivate(path.join(stage,'registry.json'),JSON.stringify(registry,null,2));\n await atomicPrivate(path.join(stage,'migration.json'),JSON.stringify({source,result:'MIGRATED_NOT_STARTED',oldFilesRetained:true,at:new Date().toISOString()}));\n if(await exists(target))await rmdir(target);\n await rename(stage,target);\n return {result:'MIGRATED_NOT_STARTED',instances:registry.instances.length,oldFilesRetained:true};\n });\n}\nexport async function migrate(source:string|undefined,yes=false,json=false){\n if(!source){\n const registry=await exists(path.join(oldRoot(),'registry.json'));\n const legacy={...await migrateLegacy(),message:'Choose a source, then run starmux migrate setup or starmux migrate live. Use -y for non-interactive confirmation. Running legacy instances using these stores will be stopped; original files are retained.'};\n process.stdout.write(JSON.stringify({registry:registry?path.join(oldRoot(),'registry.json'):null,legacy,next:'starmux migrate registry|setup|live [-y]'},null,2)+'\\n');return;\n }\n if(!['registry','setup','live'].includes(source))throw Error('Choose registry, setup or live.');\n if(source!=='registry'&&await exists(path.join(oldRoot(),'registry.json')))throw Error('Old registry found. Migrate registry to preserve existing identities.');\n if(!yes){\n if(!process.stdin.isTTY)throw Error('Migration stops the selected old gateway. Re-run with -y to confirm.');\n const rl=createInterface({input:process.stdin,output:process.stderr});\n try{if(!/^y(es)?$/i.test((await rl.question('Migrate '+source+' and stop its old processes? Originals will be retained. [y/N] ')).trim()))throw Error('Cancelled.');}finally{rl.close();}\n }\n let result:unknown;\n if(source==='registry')result=await migrateRegistry();\n else {\n const marker=path.join(instancesRoot(),'migration-in-progress.json');\n if(await exists(instancesRoot())&&(await readdir(instancesRoot())).length){\n if(!await exists(marker))throw Error('Migration destination already exists.');\n const previous=JSON.parse(await readTrusted(marker)) as {source:string};\n if(previous.source!==source||(await readRegistry()).instances.length)throw Error('Migration destination already has instances.');\n }\n createPrivateDirectory(instancesRoot());await atomicPrivate(marker,JSON.stringify({source}));\n result=await migrateLegacy(source,true);\n await atomicPrivate(path.join(instancesRoot(),'migration.json'),JSON.stringify({source,result:'MIGRATED_NOT_STARTED',oldFilesRetained:true}));await rm(marker);\n }\n if(json)process.stdout.write(JSON.stringify(result,null,2)+'\\n');else process.stdout.write('Migrated. Originals retained. Start with starmux up.\\n');\n}\n"]}
@@ -6,11 +6,11 @@ Authentication decides who can connect. Your instance configuration decides what
6
6
 
7
7
  | Mode | Use when | Configure |
8
8
  | --- | --- | --- |
9
- | `ingress` | An existing protected ingress controls callers | Protect ingress, then `starmux auth mode ingress` |
10
- | `api-key` | Your client accepts a Bearer key | `starmux auth key "My client"` |
11
- | `oauth` | Your client uses browser login and consent | [Configure a provider](OAUTH.md), then `starmux auth mode oauth` |
9
+ | `none` *(default)* | Connecting on the same computer, or your tunnel/reverse proxy already restricts callers | `starmux auth mode none` |
10
+ | `api-key` | Your client accepts a Bearer API key header | `starmux auth key "My AI Client"` |
11
+ | `oauth` | Your client uses browser login and consent (e.g., ChatGPT Web) | [Configure a provider](OAUTH.md), then `starmux auth mode oauth` |
12
12
 
13
- A new local instance uses `ingress`. For remote access in this mode, the ingress must restrict access; HTTPS encrypts traffic but does not authenticate callers. One instance has one active mode. All its authorized clients use the same approved tool permissions.
13
+ A new local instance defaults to `none` (also accepted as `ingress`). For remote access in this mode, your tunnel or reverse proxy must restrict access; HTTPS encrypts traffic but does not authenticate callers. Each instance has one active authentication mode at a time, and all authorized clients share the instance's approved workspace permissions.
14
14
 
15
15
  ## API keys
16
16
 
@@ -20,7 +20,7 @@ starmux auth
20
20
  starmux auth revoke CREDENTIAL_ID
21
21
  ```
22
22
 
23
- Creating a key also enables API-key mode. Copy the key when printed; Starmux stores only its digest. New keys begin with `smx_`; migrated `mgw_` keys continue to work. Expiration accepts positive durations (`30m`, `12h`, `30d`, `4w`) or a future ISO timestamp. Omit it for no expiry.
23
+ Creating a key automatically enables `api-key` mode and prints both Form UI fields and a ready-to-paste `mcpServers` JSON snippet. Copy the key when printed; Starmux stores only its digest. New keys begin with `smx_` (migrated `mgw_` keys continue to work). Expiration accepts positive durations (`30m`, `12h`, `30d`, `4w`) or a future ISO timestamp. Omit it for no expiry.
24
24
 
25
25
  Configure your client with `Authorization: Bearer YOUR_KEY`. See the [complete HTTPS example](REMOTE_SETUP.md#2-https--bearer-api-key).
26
26
 
package/docs/CLIENTS.md CHANGED
@@ -1,30 +1,22 @@
1
1
  # AI client compatibility
2
2
 
3
- Choose a client you already use, then check its tested connection below. Transport, authentication and host platform are separate parts of a deployment; success with one combination does not certify the others.
3
+ Choose a client you already use, then check its recommended connection below. Transport, authentication and host platform are configured independently.
4
4
 
5
- **Labels:** Tested means a recorded project workflow; owner-reported means a successful user report without a complete recorded test matrix; pending means a compatibility target, not a completed Gateway test.
6
-
7
-
8
- | Client | Current project validation | Connection guide |
5
+ | Client | Validation status | Connection guide |
9
6
  | --- | --- | --- |
10
- | **ChatGPT web** | Tested: Tunnel + OAuth; coding, session revocation and fresh login | [ChatGPT OAuth](REMOTE_SETUP.md#4-configure-core-and-chatgpt) |
11
- | **Claude App** | Remote MCP supported by the client; Gateway end-to-end validation pending | [Official custom connector setup](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp) |
12
- | **Codex** | Tested: installed Gateway plugin through protected Tunnel | [Tunnel](CONNECT_CHATGPT.md#2-secure-mcp-tunnel--no-inbound-port) · [HTTP API-key example](REMOTE_SETUP.md#configure-workbuddy-or-another-header-capable-client) |
13
- | **WorkBuddy** | Tested: remote HTTPS + IPv6 + API-key file reads/writes to macOS | [API Key setup](REMOTE_SETUP.md#2-https--bearer-api-key) |
14
- | **Doubao (豆包)** | Owner-reported successful use; exact connection combination not recorded | [Deployment options](REMOTE_SETUP.md) |
15
-
16
- A client listing does not imply every authentication or transport combination has passed. In particular, Claude App is a compatibility target, and ChatGPT direct-HTTPS OAuth remains unverified. [OAuth evidence](OAUTH.md) · [WorkBuddy evidence](VALIDATION_WORKBUDDY.md) · [Protected Tunnel evidence](VALIDATION_TUNNEL.md)
17
-
18
- ## Use WorkBuddy with a Linux workspace
19
-
7
+ | **ChatGPT Web** | ✅ Tested: Secure Tunnel + OAuth (coding, session revocation and login) | [ChatGPT OAuth](REMOTE_SETUP.md#4-configure-starmux-and-chatgpt) |
8
+ | **Claude App** | 🎯 Target: Remote MCP Custom Connector | [Official custom connector setup](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp) |
9
+ | **Codex** | ✅ Tested: Protected Tunnel | [Tunnel setup](CONNECT_CHATGPT.md#2-secure-mcp-tunnel--no-inbound-port) · [HTTP API-key example](REMOTE_SETUP.md#configure-workbuddy-or-another-header-capable-client) |
10
+ | **WorkBuddy** | ✅ Tested: Direct HTTPS + IPv6 + Bearer API key | [API Key setup](REMOTE_SETUP.md#2-https--bearer-api-key) |
11
+ | **Doubao (豆包)** | ✅ Community verified | [Deployment options](REMOTE_SETUP.md) |
20
12
 
21
- **Your AI app's operating system does not have to be your tools' operating system.** Keep WorkBuddy on your Mac or Windows computer and deploy Gateway Core with Desktop Commander on a Linux machine. Connect to that machine's authenticated HTTPS MCP endpoint to work with its files and development tools.
13
+ See [OAuth setup](OAUTH.md) and [remote setup](REMOTE_SETUP.md) for step-by-step configuration.
22
14
 
23
- For example, ask WorkBuddy to inspect a Linux project's configuration, update an approved file, and run its tests on that Linux host. WorkBuddy itself does not need to be installed there. This is remote MCP tool access, not remote desktop control or automatic file synchronization.
15
+ ## Use WorkBuddy or Desktop AI Clients with a Linux / NVIDIA Jetson Host
24
16
 
25
- **Validation:** remote WorkBuddy file reads/writes on a Mac have passed; the Linux Core has independently passed real Desktop Commander file-write and Shell tests. The combined **WorkBuddy → Linux** deployment is an intended use case, with end-to-end acceptance still pending. [WorkBuddy evidence](VALIDATION_WORKBUDDY.md) · [Linux setup and permissions](PLATFORMS.md)
17
+ **Your AI client's operating system does not have to match your target machine's operating system.** Many AI clients (such as WorkBuddy or ChatGPT Desktop) only offer macOS and Windows desktop apps. You can keep your AI client on your Mac or Windows laptop, run `starmux up --code` on a **Linux (`x64` / `ARM64`)** server or **NVIDIA Jetson** device, and connect over HTTPS + Bearer API Key or Tunnel to edit code and run terminal commands on the target machine remotely.
26
18
 
27
- WorkBuddy's [official desktop downloads](https://www.codebuddy.cn/docs/workbuddy/Download-History) currently list macOS and Windows. This deployment separates the client installation from the machine that executes tools.
19
+ See [platform support](PLATFORMS.md) for Linux and cross-platform details.
28
20
 
29
21
 
30
22
  [Choose authentication](ACCESS_CONTROL.md#choose-an-authentication-mode) · [Remote deployment](REMOTE_SETUP.md) · [All documentation](README.md)
@@ -19,9 +19,9 @@ This is a guide for **your own private ChatGPT connection**, not a claim that th
19
19
  ## Before either connection
20
20
 
21
21
  1. Install and start Starmux using [Local installation](INSTALL.md).
22
- 2. Run `starmux status` and confirm **Core: RUNNING / Healthy**. Note your **actual local port**. The initial default port is **18893**; use the actual value shown by `status`.
23
- 3. The **local** MCP address is `http://127.0.0.1:<PORT>/mcp`. You don't paste that into ChatGPT: ChatGPT runs in the cloud and cannot dial your Mac's `localhost`.
24
- 4. Start with a **restricted read-only** profile. Remote caller authentication and permission to use a tool are separate from simply discovering that tool.
22
+ 2. Run `starmux status` and confirm **`● Starmux is running`**. Note your **actual local port**. The default port is **18893**; use the actual value shown by `status`.
23
+ 3. The **local** MCP address is `http://127.0.0.1:<PORT>/mcp`. You don't paste that into ChatGPT Web: ChatGPT runs in the cloud and cannot dial your machine's `localhost` directly.
24
+ 4. Start with the default **Read-only** mode (`starmux up`). Switch to **Coding mode** (`starmux up --code`) when you want ChatGPT to edit files or run Shell commands.
25
25
 
26
26
  ## 1. Direct HTTPS — your domain, your Gateway
27
27
 
@@ -118,7 +118,7 @@ tunnel-client doctor --profile <YOUR_PROFILE> --explain
118
118
  tunnel-client run --profile <YOUR_PROFILE>
119
119
  ```
120
120
 
121
- Keep both Gateway and `tunnel-client` running. **Choosing Tunnel in the Gateway App does not itself create or start this connection.**
121
+ Keep both Starmux and `tunnel-client` running.
122
122
 
123
123
  ### Step 3 — Choose the Tunnel in ChatGPT
124
124
 
@@ -145,6 +145,4 @@ Neither route replaces Desktop Commander or changes your local file permissions.
145
145
  - **Tools appear, but write is denied:** This may be correct. Check remote caller authorization and local restricted/developer mode. Don't disable security just to make discovery green.
146
146
  - **No “Add custom MCP server” option:** ChatGPT's available custom MCP features depend on plan and workspace permissions. See [OpenAI's current connection instructions](https://help.openai.com/en/articles/12584461-connecting-custom-mcp-servers-in-chatgpt).
147
147
 
148
- **Community release gate:** Neither route is independently accepted as a public one-click workflow yet; ChatGPT OAuth Tunnel coding and revocation tests passed, while direct-HTTPS OAuth and independent onboarding remain open. No public endpoint should be deployed without verified caller identity and revocation.
149
-
150
148
  [Main README](../README.md) · [Local installation](INSTALL.md) · [Security](SECURITY.md)
package/docs/INSTALL.md CHANGED
@@ -10,6 +10,14 @@ starmux up
10
10
 
11
11
  The first start creates a private configuration and starts a background process on `127.0.0.1:18893`. The terminal returns when the service is ready.
12
12
 
13
+ Read the **Connect an AI client (MCP settings)** section printed by `up` (also available with `status`):
14
+
15
+ - **None (no token required):** a local client uses Streamable HTTP with authentication set to None. For a web or remote client, connect through a protected HTTPS proxy or tunnel that forwards to `127.0.0.1:18893`.
16
+ - **API key:** configure Bearer token authentication (`Authorization: Bearer <YOUR_API_KEY>`) with the key you saved when creating it. The output shows how many keys are active and the command to create one if needed.
17
+ - **OAuth:** use the configured **URL (Remote)** and complete browser sign-in with an allowed account.
18
+
19
+ `starmux dashboard` opens the local Web Access Console with its owner login so you can view and copy settings in your browser. `--json` retains structured status for scripts.
20
+
13
21
  ```sh
14
22
  starmux verify
15
23
  starmux status
@@ -36,11 +44,7 @@ With API-key or OAuth authentication enabled, give `verify` a valid Bearer crede
36
44
 
37
45
  ## Connect remotely
38
46
 
39
- Continue with [remote setup](REMOTE_SETUP.md): HTTPS + API key, ChatGPT OAuth, or an existing protected ingress. A local verification pass confirms the local service; the last step of each remote route is a real tool call from your client.
40
-
41
- ## Existing installations
42
-
43
- Use [migration](MIGRATION.md) to retain your existing workspaces and credentials. New installations store state in `~/.config/starmux` on all platforms.
47
+ Continue with [remote setup](REMOTE_SETUP.md): HTTPS + API key, ChatGPT OAuth, or an existing protected tunnel. A local verification pass confirms the local service; the last step of each remote route is a real tool call from your client.
44
48
 
45
49
  ## Install from a checkout
46
50
 
package/docs/INSTANCES.md CHANGED
@@ -9,6 +9,7 @@ starmux ls
9
9
  starmux status -i work
10
10
  starmux logs -f -i blog
11
11
  starmux down -i work
12
+ starmux rm blog
12
13
  ```
13
14
 
14
15
  Names such as `work` and `blog` are arbitrary labels. Names and stable IDs both work with `-i` / `--instance`, before or after the command. Instances share the OS account; coding mode uses that account's permissions.
@@ -37,7 +38,7 @@ Set `VISUAL` or `EDITOR` to your editor executable for `config edit`. JSONC supp
37
38
  snapshots/
38
39
  ```
39
40
 
40
- Configuration holds workspace, port, permissions and optional upstream settings. Credentials and secrets live in private state. `config validate` and `config schema` provide diagnostics and the full schema.
41
+ Configuration holds workspace, port, permissions and optional upstream settings. Credentials and secrets live in private state. `config validate` and `config schema` provide diagnostics and the full schema. `starmux config schema` also works before creating an instance.
41
42
 
42
43
  ## Import, bind and export
43
44
 
@@ -57,13 +58,15 @@ Import makes a private copy. `--link` keeps the external file authoritative. `ma
57
58
  starmux config copy experiment -i work
58
59
  starmux config rename development -i work
59
60
  starmux config default development
60
- starmux config archive -i experiment
61
+ starmux rm experiment # Alias for: starmux config archive -i experiment
61
62
  starmux ls --archived
62
63
  starmux config restore -i experiment
63
64
  ```
64
65
 
65
66
  Copies receive a new identity and port, without credentials or private secrets. An authenticated copy needs its authentication configured before startup. Archive stops the instance and retains recoverable data. The first created instance is the CLI default; `config default` changes it explicitly.
66
67
 
68
+ With no instances, `starmux ls` points to `starmux up` to get started. If all instances are archived, use `starmux ls --archived` and restore one with `starmux config restore -i NAME`. Instance operations reject missing or extra arguments; use `-i NAME` to select the instance to archive or restore.
69
+
67
70
  ## Snapshots, secrets and automation
68
71
 
69
72
  Concurrent edits return `CONFIG_CONFLICT`; reload before saving again. A failed apply attempts to restore the last successful worker configuration. Credential revocations are never rolled back. `config recover` repairs a missing source from the last successful snapshot without replacing an existing file.
package/docs/OAUTH.md CHANGED
@@ -1,18 +1,14 @@
1
1
  # OAuth for ChatGPT and other MCP clients
2
2
 
3
- Gateway is the OAuth **resource server**. A separate authorization server owns user login, consent, PKCE authorization codes, token issuance and refresh. The Core remains loopback-only; HTTPS/Tunnel transport is independent of authorization. Existing ingress and API-key installations remain unchanged until the owner explicitly selects OAuth.
3
+ Starmux acts as the OAuth **resource server**. A separate authorization server (such as Keycloak) owns user login, consent, PKCE authorization codes, token issuance and refresh. Starmux remains loopback-only (`127.0.0.1`); HTTPS or Tunnel transport is configured independently of authorization.
4
4
 
5
5
  **Start here:** [step-by-step remote installation](REMOTE_SETUP.md) covers Keycloak, HTTPS proxy, CLI configuration, ChatGPT fields and the tested Tunnel layout. This document covers provider setup and the OAuth reference contract.
6
6
 
7
- **Current result:** ChatGPT web OAuth through the official Tunnel passed real coding, session revocation and fresh-login recovery. The owner additionally confirmed a real read-only disk scan. Direct-HTTPS OAuth and native Linux/Windows OAuth acceptance remain open.
7
+ ## Implementation overview
8
8
 
9
- ## Current implementation and acceptance boundaries
10
-
11
- - Implemented: protected-resource discovery, Bearer challenges, JWT validation, owner subject/client allowlists, tool security metadata, live local session revocation, CLI and Dashboard management.
12
- - The first provider contract uses JWT access tokens with `sub`, `azp` (OAuth client ID), `sid` (stable authorization-server session ID), `iat`, `exp`, audience and scope. Opaque tokens/introspection and providers without this contract are not supported.
13
- - Resource metadata is available without a token only in OAuth mode at `/.well-known/oauth-protected-resource` and the resource-path-specific location. MCP initialization, listing, tools and resources all require authentication. Mixed anonymous/OAuth tools are not enabled.
14
- - Local signed-token tests do not establish real ChatGPT compatibility. HTTPS and Tunnel end-to-end acceptance must be recorded separately.
15
- - A reference Keycloak realm generator is provided. Keycloak's resource-indicator and CIMD features are experimental; do not claim full provider compatibility based only on successful discovery.
9
+ - **Supported features:** protected-resource discovery (`/.well-known/oauth-protected-resource`), Bearer challenges, JWT validation, owner subject/client allowlists, tool security metadata, live local session revocation, and CLI / Web Dashboard management.
10
+ - **Token contract:** JWT access tokens with `sub`, `azp` (OAuth client ID), `sid` (stable authorization-server session ID), `iat`, `exp`, audience and scope. Opaque tokens/introspection are not supported.
11
+ - **Discovery:** Resource metadata is available without a token in OAuth mode at `/.well-known/oauth-protected-resource` and the resource-path-specific location. MCP initialization, tool listing, tool calls and resources all require a valid OAuth token.
16
12
 
17
13
  ## Configure an existing authorization server
18
14
 
@@ -99,11 +95,7 @@ A maximum of 512 remembered OAuth sessions is enforced without evicting revocati
99
95
 
100
96
  JWT verification checks signatures, issuer, audience, expiry/not-before, issued-at, client, owner subject, session and scope. A wrong scope/owner is denied before MCP dispatch. The Core does not retry tools when authentication fails. Locally trusted Shell tools remain capable of accessing owner files; OAuth admission does not isolate them from the owner account.
101
97
 
102
- Existing Tunnel coding acceptance in ingress mode is recorded separately in [the Tunnel validation report](VALIDATION_TUNNEL.md); it is not OAuth acceptance.
103
-
104
- ## Validation history
105
-
106
- See [client compatibility](CLIENTS.md) and the [OAuth validation archive](VALIDATION_OAUTH.md).
98
+ See [client compatibility](CLIENTS.md) and [remote setup](REMOTE_SETUP.md) for deployment details.
107
99
 
108
100
  ## Reproduce the local provider test
109
101
 
package/docs/PLATFORMS.md CHANGED
@@ -20,10 +20,10 @@ starmux down
20
20
  - State is stored under `~/.config/starmux`; on Windows, `~` is the user profile. Private files use owner-only POSIX permissions or Windows ACLs.
21
21
  - Windows runtime control uses authenticated named pipes; Unix hosts use private sockets. Gateway-generated commands use host-appropriate quoting and shells.
22
22
 
23
- ## Validation status
23
+ ## Supported hosts
24
24
 
25
- The Starmux 0.10.0 CLI is a preview. Its new command workflow is validated on the development macOS host. Earlier Core tests passed on Ubuntu 24.04 arm64 / Node 22.12, including real file writes and Shell. Windows native execution remains pending; earlier implementation or CI configuration does not establish that the new release has passed there.
25
+ Starmux runs on **macOS** (`Apple Silicon` and `Intel`), **Linux** (`x64`, `ARM64`, and edge devices such as **NVIDIA Jetson**), and **Windows**.
26
26
 
27
- The native macOS App is a development build. Public signing, notarization and independent clean-Mac installation are separate release steps.
27
+ The native macOS menu bar App is provided as a development build in [`app/`](../app/README.md).
28
28
 
29
- `npm test` uses isolated home directories and ports. Portable tests cover process lifecycle, permissions, authentication, configuration and the new CLI. macOS also runs the historical lifecycle regression tests needed by migration.
29
+ `npm test` uses isolated home directories and ports to verify process lifecycle, workspace permissions, authentication, configuration, and the CLI.
package/docs/README.md CHANGED
@@ -9,17 +9,8 @@ Start locally, connect your client, then explore advanced configuration as neede
9
9
  ## Go further
10
10
 
11
11
  - [OAuth and ChatGPT](OAUTH.md)
12
+ - [Connect ChatGPT (HTTPS & Tunnel)](CONNECT_CHATGPT.md)
12
13
  - [Instances and JSONC configuration](INSTANCES.md)
13
- - [Migration from Mac MCP Gateway](MIGRATION.md)
14
14
  - [Permissions and security](SECURITY.md)
15
15
  - [Client compatibility](CLIENTS.md) and [platform support](PLATFORMS.md)
16
- - [Release status](RELEASE_STATUS.md)
17
- - [Architecture](ARCHITECTURE.md) and [contributing](../CONTRIBUTING.md)
18
-
19
- ## Validation archives
20
-
21
- These records describe specific tested deployments, not installation instructions:
22
-
23
- - [WorkBuddy](VALIDATION_WORKBUDDY.md)
24
- - [Tunnel](VALIDATION_TUNNEL.md)
25
- - [OAuth](VALIDATION_OAUTH.md)
16
+ - [Contributing](../CONTRIBUTING.md)
@@ -6,15 +6,13 @@ For help choosing between the three authentication modes, read [the access-contr
6
6
 
7
7
  ## Choose authentication independently of transport
8
8
 
9
- | Authentication | Choose it when | Validated path |
9
+ | Authentication | Choose it when | CLI Command |
10
10
  | --- | --- | --- |
11
- | Ingress-managed | Your existing ingress restricts access and you do not need a Gateway credential | Codex plugin through a protected OpenAI Tunnel |
12
- | Bearer API key | Your client accepts an `Authorization` header | WorkBuddy over public HTTPS + IPv6, including remote reads and writes |
13
- | OAuth | Your client presents a login/consent flow | ChatGPT web through OpenAI Tunnel + Keycloak, including coding and revocation |
11
+ | **None** (`none` / `ingress`) | Connecting locally, or your tunnel already restricts callers | `starmux auth mode none` |
12
+ | **Bearer API key** | Your client accepts an `Authorization: Bearer <key>` header (e.g., WorkBuddy, Cursor) | `starmux auth key "My AI Client"` |
13
+ | **OAuth 2.0** | Your client requires a browser login/consent flow (e.g., ChatGPT Web) | `starmux auth oauth setup ...` |
14
14
 
15
- HTTPS and Tunnel are transport choices; API key and OAuth are authentication choices. OAuth does not require Tunnel, and HTTPS alone does not authenticate callers. ChatGPT direct-HTTPS OAuth has not received the same end-to-end acceptance as the Tunnel deployment. Doubao compatibility is owner-reported; its precise transport/authentication combination has not been recorded. See the [client matrix](CLIENTS.md).
16
-
17
- A Core has one active authentication mode. Switching to OAuth makes existing API keys unusable on that Core, and vice versa. This guide configures one Core at a time, not simultaneous API-key/OAuth listeners. The separate acceptance instances used during development are not a prerequisite for ordinary installation.
15
+ HTTPS reverse proxy and Tunnel are network transport choices; API key and OAuth are authentication choices. Each Starmux instance has one active authentication mode at a time. Switching to OAuth disables API keys on that instance (and vice versa). See the [client matrix](CLIENTS.md).
18
16
 
19
17
  ## 1. Start locally
20
18
 
@@ -81,10 +79,12 @@ For WorkBuddy, merge this entry into `~/.workbuddy/mcp.json` without replacing u
81
79
  ```json
82
80
  {
83
81
  "mcpServers": {
84
- "my-gateway": {
82
+ "starmux": {
85
83
  "type": "streamable-http",
86
84
  "url": "https://mcp.example.com/mcp",
87
- "headers": { "Authorization": "Bearer REPLACE_WITH_YOUR_GATEWAY_KEY" }
85
+ "headers": {
86
+ "Authorization": "Bearer smx_REPLACE_WITH_YOUR_API_KEY"
87
+ }
88
88
  }
89
89
  }
90
90
  }
@@ -110,7 +110,7 @@ Set that variable securely in the environment of the actual Codex process; setti
110
110
  4. The next MCP request with that key must fail with HTTP 401. A missing/wrong key must also fail. Already-running tools are not canceled.
111
111
  5. Create a new key to restore access; update the client's private configuration.
112
112
 
113
- Local `starmux verify` in API-key mode reads `STARMUX_TOKEN` from its environment. Local PASS is not external acceptance. [WorkBuddy evidence and limits](VALIDATION_WORKBUDDY.md).
113
+ Local `starmux verify` in API-key mode reads `STARMUX_TOKEN` from its environment. Local PASS confirms the local endpoint; verify remote access with a real tool call from your client.
114
114
 
115
115
  ## 3. OAuth: deploy the authorization server
116
116
 
@@ -199,7 +199,7 @@ Validate and run Caddy as in the API-key section. This allowlist is for the **pr
199
199
 
200
200
  Before enabling public MCP access, configure Core authentication as below. If the proxy is already running while Core still uses ingress mode, keep the `/mcp` route disabled until OAuth mode is set. Issuer metadata can be exposed first for configuration.
201
201
 
202
- ## 4. Configure Core and ChatGPT
202
+ ## 4. Configure Starmux and ChatGPT
203
203
 
204
204
  ```sh
205
205
  starmux auth oauth setup \
@@ -263,8 +263,8 @@ Manage keys/sessions in the local **Dashboard → Access control**. The macOS Ap
263
263
  | HTTP 401 after revoke or idle expiry | Do not retry tools indefinitely; inspect local revocation state, then use a fresh authorized login if appropriate |
264
264
  | HTTP 403 | Allowed owner/client and `gateway:access`; do not expand permissions just to hide a failure |
265
265
  | OAuth works but a write is denied | Existing workspace/OS/upstream policy still applies |
266
- | Web answer/app fails to render | Correlate actual tool/audit results before deciding whether execution failed; never automatically repeat a side-effecting call |
267
- | CLI changes do not affect the server | Check `MAC_MCP_ACCESS_FILE`, launch environment and startup identity panel |
266
+ | Web answer/app fails to render | Correlate actual tool/audit results (`starmux logs -f`) before deciding whether execution failed; never automatically repeat a side-effecting call |
267
+ | CLI changes do not affect the server | Check `starmux ls` and `starmux status` to ensure you are targeting the intended instance (`-i <name>`) |
268
268
 
269
269
  The local App, Core, proxy, database, provider and Tunnel have separate process lifecycles. Stopping Core stops new tool service; it does not automatically shut down the others. Persist private configuration and PostgreSQL/Caddy data, configure service startup deliberately, and repeat acceptance after reboot or deployment changes.
270
270
 
package/docs/SECURITY.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Starmux is a self-hosted connection and routing layer around real upstream MCP tools. Treat exposure to remote clients as a powerful privilege.
4
4
 
5
5
  - The default **restricted read-only** role enforces project-scoped access and disables arbitrary Shell/write operations.
6
- - The **trusted developer** role can run commands using your Mac account. In the owner-tested configuration, new `--code` / `--write` configurations use full OS-account permissions. Migrated Home-only configurations keep their previous sandbox until explicitly changed.
7
- - HTTP MCP listens on loopback by default. **Never expose the raw loopback port publicly.** Direct HTTPS requires a publicly reachable TLS endpoint **and real caller authentication, authorization and revocation**. DNS/TLS alone are insufficient.
8
- - OpenAI Tunnel is outbound and uses OpenAI's forwarding infrastructure. Tunnel readiness is **not** proof of independent caller authorization.
6
+ - The **trusted coding** role (`starmux up --code`) can edit files and run commands using your OS account permissions. Migrated macOS Home-only configurations keep their previous sandbox until explicitly changed.
7
+ - HTTP MCP listens on loopback (`127.0.0.1`) by default. **Never expose the raw loopback port publicly.** Direct HTTPS requires a publicly reachable TLS endpoint **and caller authentication**. DNS/TLS alone are insufficient.
8
+ - OpenAI Tunnel is outbound and uses OpenAI's forwarding infrastructure. Tunnel readiness alone is **not** caller authorization unless paired with tunnel access controls or Starmux authentication.
9
9
  - **OAuth** mode validates tokens and allowed accounts/clients, and supports local session revocation, including refreshed tokens in that session. A fresh authorized session can restore access; session revocation is not an account ban.
10
- - Optional **Gateway API key** mode authenticates a client credential on every MCP request and supports independent revocation. The default **managed by ingress** mode does not authenticate callers in Core. See [access control](ACCESS_CONTROL.md). Keys inherit owner permissions and do not isolate trusted tools from owner-readable configuration.
10
+ - **Bearer API key** mode authenticates a client credential on every MCP request and supports instant revocation. The default **`none` (`ingress`)** mode does not verify a token in Starmux. See [access control](ACCESS_CONTROL.md). Keys inherit your instance's workspace permissions.
11
11
  - Filesystem permissions, macOS TCC/FDA and permissions in the actual upstream Desktop Commander remain relevant.
12
12
  - Do not commit API keys, tunnel runtime profiles, passwords or protected files; use personal private configuration and report vulnerabilities privately.
13
13
  - Multi-user/multi-host orchestration and user-defined policy scripts are **not shipped** in this preview.
14
14
 
15
- See [connection steps](CONNECT_CHATGPT.md) and [release status](RELEASE_STATUS.md).
15
+ See [connection steps](CONNECT_CHATGPT.md) and [platform support](PLATFORMS.md).
Binary file