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.
- package/CONTRIBUTING.md +1 -1
- package/README.md +184 -59
- package/SECURITY.md +3 -3
- package/app/README.md +6 -2
- package/dist/src/access-control.js +23 -20
- package/dist/src/access-control.js.map +1 -1
- package/dist/src/access-dashboard.js +8 -2
- package/dist/src/access-dashboard.js.map +1 -1
- package/dist/src/cli/instances.js +87 -14
- package/dist/src/cli/instances.js.map +1 -1
- package/dist/src/cli/starmux-status.js +122 -0
- package/dist/src/cli/starmux-status.js.map +1 -0
- package/dist/src/cli/starmux.js +222 -49
- package/dist/src/cli/starmux.js.map +1 -1
- package/dist/src/deny-dashboard.js +2 -2
- package/dist/src/deny-dashboard.js.map +1 -1
- package/dist/src/instance-runtime.js +11 -2
- package/dist/src/instance-runtime.js.map +1 -1
- package/dist/src/instance-store.js +3 -1
- package/dist/src/instance-store.js.map +1 -1
- package/dist/src/ops-dashboard.js +4 -4
- package/dist/src/ops-dashboard.js.map +1 -1
- package/dist/src/permissions-dashboard.js +2 -2
- package/dist/src/permissions-dashboard.js.map +1 -1
- package/dist/src/starmux-migration.js +1 -1
- package/dist/src/starmux-migration.js.map +1 -1
- package/docs/ACCESS_CONTROL.md +5 -5
- package/docs/CLIENTS.md +11 -19
- package/docs/CONNECT_CHATGPT.md +4 -6
- package/docs/INSTALL.md +9 -5
- package/docs/INSTANCES.md +5 -2
- package/docs/OAUTH.md +6 -14
- package/docs/PLATFORMS.md +4 -4
- package/docs/README.md +2 -11
- package/docs/REMOTE_SETUP.md +13 -13
- package/docs/SECURITY.md +5 -5
- package/docs/assets/starmux-app-en.png +0 -0
- package/docs/assets/starmux-architecture.svg +315 -0
- package/package.json +1 -1
- package/docs/ARCHITECTURE.md +0 -51
- package/docs/MIGRATION.md +0 -37
- package/docs/RELEASE_STATUS.md +0 -22
- package/docs/TYPESCRIPT_MIGRATION.md +0 -56
- package/docs/VALIDATION_OAUTH.md +0 -79
- package/docs/VALIDATION_TUNNEL.md +0 -39
- package/docs/VALIDATION_WORKBUDDY.md +0 -59
- package/docs/assets/mcp-connection-route.svg +0 -104
- 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>
|
|
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>
|
|
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 ({\"&\":\"&\",\"<\":\"<\",\">\":\">\",'\"':\""\",\"'\":\"'\"}[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 ({\"&\":\"&\",\"<\":\"<\",\">\":\">\",'\"':\""\",\"'\":\"'\"}[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"]}
|
package/docs/ACCESS_CONTROL.md
CHANGED
|
@@ -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
|
-
| `
|
|
10
|
-
| `api-key` | Your client accepts a Bearer key | `starmux auth key "My
|
|
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
|
|
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
|
|
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
|
|
3
|
+
Choose a client you already use, then check its recommended connection below. Transport, authentication and host platform are configured independently.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
| Client | Current project validation | Connection guide |
|
|
5
|
+
| Client | Validation status | Connection guide |
|
|
9
6
|
| --- | --- | --- |
|
|
10
|
-
| **ChatGPT
|
|
11
|
-
| **Claude App** | Remote MCP
|
|
12
|
-
| **Codex** | Tested:
|
|
13
|
-
| **WorkBuddy** | Tested:
|
|
14
|
-
| **Doubao (豆包)** |
|
|
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
|
-
|
|
13
|
+
See [OAuth setup](OAUTH.md) and [remote setup](REMOTE_SETUP.md) for step-by-step configuration.
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
## Use WorkBuddy or Desktop AI Clients with a Linux / NVIDIA Jetson Host
|
|
24
16
|
|
|
25
|
-
**
|
|
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
|
-
|
|
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)
|
package/docs/CONNECT_CHATGPT.md
CHANGED
|
@@ -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
|
|
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
|
|
24
|
-
4. Start with
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
7
|
+
## Implementation overview
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
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
|
-
|
|
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
|
-
##
|
|
23
|
+
## Supported hosts
|
|
24
24
|
|
|
25
|
-
|
|
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
|
|
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
|
|
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
|
-
- [
|
|
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)
|
package/docs/REMOTE_SETUP.md
CHANGED
|
@@ -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 |
|
|
9
|
+
| Authentication | Choose it when | CLI Command |
|
|
10
10
|
| --- | --- | --- |
|
|
11
|
-
|
|
|
12
|
-
| Bearer API key | Your client accepts an `Authorization
|
|
13
|
-
| OAuth | Your client
|
|
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.
|
|
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
|
-
"
|
|
82
|
+
"starmux": {
|
|
85
83
|
"type": "streamable-http",
|
|
86
84
|
"url": "https://mcp.example.com/mcp",
|
|
87
|
-
"headers": {
|
|
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
|
|
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
|
|
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 `
|
|
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
|
|
7
|
-
- HTTP MCP listens on loopback by default. **Never expose the raw loopback port publicly.** Direct HTTPS requires a publicly reachable TLS endpoint **and
|
|
8
|
-
- OpenAI Tunnel is outbound and uses OpenAI's forwarding infrastructure. Tunnel readiness is **not**
|
|
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
|
-
-
|
|
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 [
|
|
15
|
+
See [connection steps](CONNECT_CHATGPT.md) and [platform support](PLATFORMS.md).
|
|
Binary file
|