starmux 0.13.1 → 0.14.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 (142) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +18 -28
  3. package/dist/src/access-control.js +1 -2
  4. package/dist/src/access-dashboard.js +2 -3
  5. package/dist/src/account-access-policy.js +54 -0
  6. package/dist/src/authorized-workspace-roots.js +0 -1
  7. package/dist/src/batch-read-policy.js +30 -0
  8. package/dist/src/cli/instances.js +6 -175
  9. package/dist/src/cli/oauth.js +0 -1
  10. package/dist/src/cli/starmux-status.js +10 -14
  11. package/dist/src/cli/starmux-supervisor.js +0 -1
  12. package/dist/src/cli/starmux.js +13 -25
  13. package/dist/src/config-schemas.js +0 -9
  14. package/dist/src/configured-mcp-upstreams.js +0 -1
  15. package/dist/src/contracts.js +0 -1
  16. package/dist/src/current-permission-probe.js +0 -1
  17. package/dist/src/dc-workspace-http.js +57 -33
  18. package/dist/src/deny-dashboard.js +2 -3
  19. package/dist/src/desktop-commander-facade.js +4 -4
  20. package/dist/src/explicit-deny-rules.js +2 -3
  21. package/dist/src/global-core-control.js +2 -3
  22. package/dist/src/home-write-sandbox.js +0 -1
  23. package/dist/src/http-host-policy.js +20 -0
  24. package/dist/src/instance-runtime.js +3 -3
  25. package/dist/src/instance-store.js +28 -22
  26. package/dist/src/instance-worker.js +2 -3
  27. package/dist/src/invocation.js +1 -2
  28. package/dist/src/local-desktop-read-probe.js +5 -88
  29. package/dist/src/managed-workspace-policy.js +38 -4
  30. package/dist/src/mcp-results.js +0 -1
  31. package/dist/src/mcp-upstream-router.js +0 -1
  32. package/dist/src/oauth-keycloak.js +0 -1
  33. package/dist/src/oauth-resource.js +0 -1
  34. package/dist/src/ops-dashboard.js +2 -179
  35. package/dist/src/permissions-dashboard.js +3 -4
  36. package/dist/src/platform.js +3 -4
  37. package/dist/src/privacy-diagnostics.js +0 -1
  38. package/dist/src/private-mcp-activity.js +0 -1
  39. package/dist/src/private-store-lock.js +0 -1
  40. package/dist/src/project-dev-presets.js +0 -1
  41. package/dist/src/project-git-workflow.js +0 -1
  42. package/dist/src/project-workspace-policy.js +10 -6
  43. package/dist/src/runtime-identity.js +0 -14
  44. package/dist/src/runtime-paths.js +0 -3
  45. package/dist/src/tool-dispatch.js +45 -46
  46. package/dist/src/validation.js +0 -1
  47. package/dist/src/workspace-grants.js +1 -2
  48. package/package.json +5 -15
  49. package/scripts/prepare.mjs +1 -1
  50. package/CONTRIBUTING.md +0 -18
  51. package/SECURITY.md +0 -7
  52. package/app/README.md +0 -29
  53. package/dist/src/access-control.js.map +0 -1
  54. package/dist/src/access-dashboard.js.map +0 -1
  55. package/dist/src/authorized-workspace-roots.js.map +0 -1
  56. package/dist/src/cli/access.js +0 -53
  57. package/dist/src/cli/access.js.map +0 -1
  58. package/dist/src/cli/community-remote-revoke.js +0 -32
  59. package/dist/src/cli/community-remote-revoke.js.map +0 -1
  60. package/dist/src/cli/instances.js.map +0 -1
  61. package/dist/src/cli/live-home-settings.js +0 -35
  62. package/dist/src/cli/live-home-settings.js.map +0 -1
  63. package/dist/src/cli/mac-mcp-gateway.js +0 -257
  64. package/dist/src/cli/mac-mcp-gateway.js.map +0 -1
  65. package/dist/src/cli/oauth.js.map +0 -1
  66. package/dist/src/cli/permissions-owner.js +0 -27
  67. package/dist/src/cli/permissions-owner.js.map +0 -1
  68. package/dist/src/cli/service-setup.js +0 -26
  69. package/dist/src/cli/service-setup.js.map +0 -1
  70. package/dist/src/cli/setup-wizard.js +0 -149
  71. package/dist/src/cli/setup-wizard.js.map +0 -1
  72. package/dist/src/cli/starmux-status.js.map +0 -1
  73. package/dist/src/cli/starmux-supervisor.js.map +0 -1
  74. package/dist/src/cli/starmux.js.map +0 -1
  75. package/dist/src/cli/verify-setup.js +0 -53
  76. package/dist/src/cli/verify-setup.js.map +0 -1
  77. package/dist/src/community-remote-revocations.js +0 -135
  78. package/dist/src/community-remote-revocations.js.map +0 -1
  79. package/dist/src/config-schemas.js.map +0 -1
  80. package/dist/src/configured-mcp-upstreams.js.map +0 -1
  81. package/dist/src/contracts.js.map +0 -1
  82. package/dist/src/current-permission-probe.js.map +0 -1
  83. package/dist/src/dc-workspace-http.js.map +0 -1
  84. package/dist/src/deny-dashboard.js.map +0 -1
  85. package/dist/src/desktop-commander-facade.js.map +0 -1
  86. package/dist/src/explicit-deny-rules.js.map +0 -1
  87. package/dist/src/gateway-status-format.js +0 -66
  88. package/dist/src/gateway-status-format.js.map +0 -1
  89. package/dist/src/global-core-control.js.map +0 -1
  90. package/dist/src/home-write-sandbox.js.map +0 -1
  91. package/dist/src/instance-migration.js +0 -138
  92. package/dist/src/instance-migration.js.map +0 -1
  93. package/dist/src/instance-runtime.js.map +0 -1
  94. package/dist/src/instance-store.js.map +0 -1
  95. package/dist/src/instance-worker.js.map +0 -1
  96. package/dist/src/invocation.js.map +0 -1
  97. package/dist/src/live-profile-home-settings.js +0 -80
  98. package/dist/src/live-profile-home-settings.js.map +0 -1
  99. package/dist/src/local-desktop-read-probe.js.map +0 -1
  100. package/dist/src/managed-workspace-policy.js.map +0 -1
  101. package/dist/src/mcp-results.js.map +0 -1
  102. package/dist/src/mcp-upstream-router.js.map +0 -1
  103. package/dist/src/oauth-keycloak.js.map +0 -1
  104. package/dist/src/oauth-resource.js.map +0 -1
  105. package/dist/src/operator-cli.js +0 -559
  106. package/dist/src/operator-cli.js.map +0 -1
  107. package/dist/src/ops-dashboard.js.map +0 -1
  108. package/dist/src/permissions-dashboard.js.map +0 -1
  109. package/dist/src/platform.js.map +0 -1
  110. package/dist/src/privacy-diagnostics.js.map +0 -1
  111. package/dist/src/private-mcp-activity.js.map +0 -1
  112. package/dist/src/private-store-lock.js.map +0 -1
  113. package/dist/src/project-dev-presets.js.map +0 -1
  114. package/dist/src/project-git-workflow.js.map +0 -1
  115. package/dist/src/project-workspace-policy.js.map +0 -1
  116. package/dist/src/read-scope.js +0 -44
  117. package/dist/src/read-scope.js.map +0 -1
  118. package/dist/src/release1-onboarding.js +0 -120
  119. package/dist/src/release1-onboarding.js.map +0 -1
  120. package/dist/src/release1-service.js +0 -131
  121. package/dist/src/release1-service.js.map +0 -1
  122. package/dist/src/remote-auth.js +0 -47
  123. package/dist/src/remote-auth.js.map +0 -1
  124. package/dist/src/runtime-identity.js.map +0 -1
  125. package/dist/src/runtime-paths.js.map +0 -1
  126. package/dist/src/starmux-migration.js +0 -172
  127. package/dist/src/starmux-migration.js.map +0 -1
  128. package/dist/src/tool-dispatch.js.map +0 -1
  129. package/dist/src/validation.js.map +0 -1
  130. package/dist/src/workspace-grants.js.map +0 -1
  131. package/docs/ACCESS_CONTROL.md +0 -51
  132. package/docs/CLIENTS.md +0 -22
  133. package/docs/CONNECT_CHATGPT.md +0 -148
  134. package/docs/INSTALL.md +0 -62
  135. package/docs/INSTANCES.md +0 -83
  136. package/docs/OAUTH.md +0 -116
  137. package/docs/PLATFORMS.md +0 -29
  138. package/docs/README.md +0 -16
  139. package/docs/REMOTE_SETUP.md +0 -280
  140. package/docs/SECURITY.md +0 -15
  141. package/src/dc-workspace-http.mjs +0 -3
  142. package/test/fixtures/p1-fixture.txt +0 -2
@@ -9,12 +9,12 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
9
9
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
10
10
  import { createAccessControl } from '../access-control.js';
11
11
  import { instanceStatus, instanceRequest, runInstance } from '../instance-runtime.js';
12
- import { atomicPrivate, availablePort, changeRegistry, configJSONSchema, createInstance, ensureAccessReady, exists, instanceConfigSchema, instancePaths, loadInstanceConfig, parseConfig, patchInstanceConfig, readRegistry, readTrusted, resolveInstance, saveInstanceConfig, suggestPort, validateConfig } from '../instance-store.js';
12
+ import { atomicPrivate, availablePort, changeRegistry, configJSONSchema, createInstance, ensureAccessReady, exists, instanceConfigSchema, instancePaths, loadInstanceConfig, parseConfig, patchInstanceConfig, readRegistry, readTrusted, resolveInstance, saveInstanceConfig, suggestPort, validateConfig, usesGlobalPermissions } from '../instance-store.js';
13
13
  import { handle } from './instances.js';
14
- import { desktopCommanderEntry } from '../runtime-paths.js';
14
+ import { packageRoot, desktopCommanderEntry } from '../runtime-paths.js';
15
15
  import { shellQuote } from '../platform.js';
16
16
  import { formatStarmuxStatus } from './starmux-status.js';
17
- const version = '0.13.1';
17
+ const { version } = JSON.parse(await readFile(path.join(packageRoot, 'package.json'), 'utf8'));
18
18
  const strings = ['instance', 'port', 'lines', 'expires', 'issuer', 'resource', 'resource-aliases', 'client-id', 'subject', 'subjects', 'registration', 'token-auth', 'callback', 'revision', 'patch', 'file', 'output', 'name', 'target', 'mode', 'app-parent-pid'];
19
19
  const booleans = ['help', 'version', 'json', 'write', 'code', 'read', 'yes', 'foreground', 'follow', 'archived', 'all', 'effective', 'link', 'allow-loopback-http', 'token-stdin', 'no-open', 'token'];
20
20
  const options = {};
@@ -52,12 +52,11 @@ function help() {
52
52
  verify: 'verify [--token-stdin]\n Test MCP initialization, tool discovery and workspace read. Authenticated instances use STARMUX_TOKEN or stdin.',
53
53
  doctor: 'doctor\n Check Node, bundled dependencies, workspace, configuration, port and authentication.',
54
54
  logs: 'logs [-f | --follow] [--lines 1..200]\n Follow continues across restarts. Ctrl+C exits the follower only.',
55
- migrate: 'migrate [registry|setup|live] [-y]\n No source: preview. Select a source to migrate, retaining originals.',
56
55
  status: 'status [--all]\n Show the selected instance, its actual endpoint and connection guide.',
57
56
  ls: 'ls [--archived]\n List local workspace instances.',
58
57
  rm: 'rm [NAME] (alias for: config archive)\n Stop and archive an instance.',
59
58
  down: 'down (alias: stop)\n Stop the selected instance.',
60
- permissions: 'permissions [status | token]\n permissions disable-tool|enable-tool|deny-directory|allow-directory TARGET\n Manage the selected instance’s tool and directory permissions.',
59
+ permissions: 'permissions [status]\n Show the selected instance’s mode. Switch with up --read or up --code.',
61
60
  privacy: 'privacy [--all] [--target PATH]\n Check privacy access through the running instance.'
62
61
  };
63
62
  if (topic && topic !== 'help' && !sections[topic])
@@ -81,8 +80,7 @@ function help() {
81
80
  ' starmux ls List workspace instances',
82
81
  ' starmux rm [name] Stop and archive an instance',
83
82
  ' starmux init [path] Save instance settings without starting',
84
- ' starmux config Inspect, edit or apply configuration',
85
- ' starmux migrate Import settings from a legacy installation'
83
+ ' starmux config Inspect, edit or apply configuration'
86
84
  ].join('\n');
87
85
  process.stdout.write(`Starmux ${version}\n\n${sections[topic ?? ''] ?? overview}\n\nGlobal: -i, --instance NAME_OR_ID --json -h, --help\nUse starmux COMMAND --help for details.\n`);
88
86
  }
@@ -127,8 +125,6 @@ async function configure() {
127
125
  instance = await resolveInstance();
128
126
  if (instance?.archived)
129
127
  fail('Instance is archived. Use starmux config restore -i ' + instance.name);
130
- if (!instance && await exists(path.join(homedir(), '.config', 'mac-mcp-gateway')) && !registry.instances.length)
131
- fail('Existing installation found. Run starmux migrate first.');
132
128
  const loaded = instance ? await loadInstanceConfig(instance) : undefined;
133
129
  const writing = flags.write || flags.code;
134
130
  if (flags.read && writing)
@@ -364,7 +360,7 @@ async function auth(instance) {
364
360
  return;
365
361
  }
366
362
  if (op === 'oauth') {
367
- process.env.MAC_MCP_ACCESS_FILE = instancePaths(instance).access;
363
+ process.env.STARMUX_ACCESS_FILE = instancePaths(instance).access;
368
364
  const action = positional[1] ?? 'status';
369
365
  const map = { 'init-keycloak': 'keycloak-realm', setup: 'configure', doctor: 'doctor', status: 'status' };
370
366
  if (!map[action])
@@ -593,7 +589,7 @@ async function main() {
593
589
  }
594
590
  const allowed = {
595
591
  up: ['port', 'read', 'write', 'code', 'yes', 'foreground', 'app-parent-pid'], init: ['port', 'read', 'write', 'code', 'yes'],
596
- status: ['all'], ls: ['archived'], rm: [], down: [], stop: [], logs: ['lines', 'follow'], doctor: [], verify: ['token-stdin'], dashboard: ['token', 'no-open'], migrate: ['yes'],
592
+ status: ['all'], ls: ['archived'], rm: [], down: [], stop: [], logs: ['lines', 'follow'], doctor: [], verify: ['token-stdin'], dashboard: ['token', 'no-open'],
597
593
  auth: ['expires', 'issuer', 'resource', 'resource-aliases', 'client-id', 'subject', 'subjects', 'registration', 'token-auth', 'callback', 'allow-loopback-http'],
598
594
  config: ['yes', 'archived', 'effective', 'link', 'name', 'revision', 'patch', 'file', 'output'], permissions: ['target', 'mode'], privacy: ['all', 'target']
599
595
  };
@@ -609,15 +605,6 @@ async function main() {
609
605
  fail('Usage: starmux rm [NAME]');
610
606
  if (command === 'auth' && ['key', 'mode', 'revoke'].includes(positional[0] ?? '') && positional.length !== 2)
611
607
  fail('Usage: starmux auth ' + positional[0] + ' VALUE');
612
- if (command === 'migrate' && positional.length > 1)
613
- fail('Usage: starmux migrate [registry|setup|live] [-y]');
614
- // Public commands never inherit old environment overlays. Core's private protocol is retained.
615
- for (const key of Object.keys(process.env))
616
- if (key.startsWith('MAC_MCP_'))
617
- delete process.env[key];
618
- for (const [key, value] of Object.entries(process.env))
619
- if (key.startsWith('STARMUX_') && key !== 'STARMUX_TOKEN')
620
- process.env['MAC_MCP_' + key.slice(8)] = value;
621
608
  if (command === 'up' || command === 'init') {
622
609
  const instance = await configure();
623
610
  if (command === 'up')
@@ -630,10 +617,6 @@ async function main() {
630
617
  }
631
618
  return;
632
619
  }
633
- if (command === 'migrate') {
634
- await (await import('../starmux-migration.js')).migrate(positional[0], yes(), json);
635
- return;
636
- }
637
620
  if (command === 'ls' || command === 'status' && flags.all) {
638
621
  await handle('instance', ['list', ...(flags.archived ? ['--archived'] : []), ...(json ? ['--json'] : [])]);
639
622
  return;
@@ -702,6 +685,12 @@ async function main() {
702
685
  }
703
686
  }
704
687
  else {
688
+ if (command === 'permissions' && usesGlobalPermissions((await loadInstanceConfig(instance)).config)) {
689
+ if (positional.length && positional[0] !== 'status')
690
+ fail('Directory grants and tool filters do not apply to global modes. Use starmux up --read or starmux up --code.');
691
+ await show(instance);
692
+ return;
693
+ }
705
694
  const args = [...positional, '--instance', instance.id];
706
695
  for (const [key, value] of Object.entries(flags))
707
696
  if (key !== 'instance' && value !== undefined)
@@ -720,4 +709,3 @@ catch (error) {
720
709
  process.stderr.write(message + '\n');
721
710
  process.exitCode = 1;
722
711
  }
723
- //# sourceMappingURL=starmux.js.map
@@ -1,14 +1,5 @@
1
1
  import { z } from 'zod';
2
- const common = { projectRoot: z.string(), mode: z.enum(['read-only', 'read-write']), connection: z.enum(['tunnel', 'https']), port: z.number().int() };
3
- export const setupSchema = z.discriminatedUnion('version', [
4
- z.object({ ...common, version: z.literal(1) }).strict(),
5
- z.object({ ...common, version: z.literal(2), accessRole: z.enum(['restricted', 'trusted']) }).strict(),
6
- z.object({ ...common, version: z.literal(3), accessRole: z.literal('trusted'), writeScope: z.literal('home-only') }).strict()
7
- ]);
8
2
  export const grantsSchema = z.object({ version: z.literal(1), homeRead: z.boolean(), outsideRead: z.boolean(), custom: z.array(z.object({ path: z.string(), mode: z.enum(['read-only', 'read-write']) }).strict()) }).strict();
9
3
  export const denySchema = z.object({ version: z.literal(1), deniedTools: z.array(z.string()), deniedDirectories: z.array(z.string()) }).strict();
10
- export const revocationsSchema = z.object({ version: z.literal(1), revokedSubjects: z.array(z.string()), revokedJtis: z.array(z.string()) }).strict();
11
4
  export const upstreamDefinitionSchema = z.object({ id: z.string(), transport: z.literal('stdio'), command: z.string(), args: z.array(z.string()), env: z.record(z.string()).optional(), allowTools: z.array(z.string()) }).strict();
12
5
  export const ownerSchema = z.object({ port: z.number().int(), pid: z.number().int().min(2), token: z.string().regex(/^[a-f0-9]{48}$/), launcher: z.enum(['cli', 'app']), runId: z.string().regex(/^[a-f0-9]{32}$/) }).passthrough();
13
- export const privateAgentSchema = z.object({ Label: z.string(), ProgramArguments: z.tuple([z.string(), z.string()]), EnvironmentVariables: z.record(z.string()), WorkingDirectory: z.string(), StandardOutPath: z.string().optional(), StandardErrorPath: z.string().optional(), RunAtLoad: z.boolean().optional() }).passthrough();
14
- //# sourceMappingURL=config-schemas.js.map
@@ -191,4 +191,3 @@ export async function bootstrapConfiguredUpstreams(configFile, audit = () => { }
191
191
  }
192
192
  return { routes, upstreams, status: () => upstreams.map(x => x.status) };
193
193
  }
194
- //# sourceMappingURL=configured-mcp-upstreams.js.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=contracts.js.map
@@ -61,4 +61,3 @@ export async function probeCurrentDirectoryAccess({ home = os.homedir(), openDir
61
61
  checks
62
62
  };
63
63
  }
64
- //# sourceMappingURL=current-permission-probe.js.map
@@ -4,7 +4,7 @@ import { createAccessControl } from './access-control.js';
4
4
  import { accessPage, accessPost, accessHeaders } from './access-dashboard.js';
5
5
  import { isSafeInteger } from "./validation.js";
6
6
  // Explicit project-root Desktop Commander MCP endpoint. Never exposed to public Caddy.
7
- // Start only with owner-approved MAC_MCP_PROJECT_ROOT; no default HOME traversal.
7
+ // Start only with owner-approved STARMUX_PROJECT_ROOT; no default HOME traversal.
8
8
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
9
  import { createServer } from "node:http";
10
10
  import { createAuthorizedWorkspacePolicy } from "./authorized-workspace-roots.js";
@@ -22,6 +22,8 @@ import { createPrivateMCPActivity } from "./private-mcp-activity.js";
22
22
  import { createGitWorkflow } from "./project-git-workflow.js";
23
23
  import { createProjectPolicy } from "./project-workspace-policy.js";
24
24
  import { createWorkspaceGrants } from "./workspace-grants.js";
25
+ import { createAccountAccessPolicy } from './account-access-policy.js';
26
+ import { isAllowedGatewayHost } from './http-host-policy.js';
25
27
  /** Construction and imports are inert; only start acquires resources. */
26
28
  export function createGatewayRuntime(options = {}) {
27
29
  let running = null;
@@ -53,27 +55,31 @@ export function createGatewayRuntime(options = {}) {
53
55
  };
54
56
  }
55
57
  async function initializeGateway({ env = process.env, audit: externalAudit }) {
56
- const access = await createAccessControl(env.MAC_MCP_ACCESS_FILE);
57
- const page = (html) => instancePage(html, env.MAC_MCP_INSTANCE_NAME, env.MAC_MCP_INSTANCE_ID);
58
- const root = env.MAC_MCP_PROJECT_ROOT;
58
+ const access = await createAccessControl(env.STARMUX_ACCESS_FILE);
59
+ const page = (html) => instancePage(html, env.STARMUX_INSTANCE_NAME, env.STARMUX_INSTANCE_ID);
60
+ const root = env.STARMUX_PROJECT_ROOT;
59
61
  if (!root)
60
62
  throw new Error("OWNER_APPROVED_PROJECT_ROOT_REQUIRED");
61
- const mode = env.MAC_MCP_WORKSPACE_MODE || "read-only";
62
- const developerCommands = env.MAC_MCP_DEV_COMMANDS || "presets";
63
- const nativeAccessMode = env.MAC_MCP_DC_ACCESS_MODE || "workspace";
64
- const writeScope = env.MAC_MCP_DC_WRITE_SCOPE || "unrestricted";
63
+ const mode = env.STARMUX_WORKSPACE_MODE || "read-only";
64
+ const developerCommands = env.STARMUX_DEV_COMMANDS || "presets";
65
+ const nativeAccessMode = env.STARMUX_DC_ACCESS_MODE || "workspace";
66
+ const writeScope = env.STARMUX_DC_WRITE_SCOPE || "unrestricted";
67
+ const globalPermissions = env.STARMUX_PERMISSION_MODE === 'global';
68
+ const globalReadOnly = globalPermissions && mode === 'read-only';
65
69
  if (!["unrestricted", "home-only"].includes(writeScope) ||
66
70
  (writeScope === "home-only" && nativeAccessMode !== "native"))
67
71
  throw Error("HOME_WRITE_SCOPE_REQUIRES_NATIVE_ROLE");
68
- if (writeScope === "home-only" && env.MAC_MCP_UPSTREAMS_FILE)
72
+ if (writeScope === "home-only" && env.STARMUX_UPSTREAMS_FILE)
69
73
  throw Error("HOME_WRITE_SCOPE_UNSANDBOXED_EXTRA_UPSTREAMS");
70
74
  if (!["workspace", "native"].includes(nativeAccessMode) ||
71
75
  (nativeAccessMode === "native" &&
72
76
  (developerCommands !== "trusted-project" || mode !== "read-write")))
73
77
  throw new Error("NATIVE_ACCESS_REQUIRES_OWNER_TRUSTED_DEVELOPER_PROFILE");
74
- const policy = await createProjectPolicy({ root, mode, developerCommands,
78
+ if (globalPermissions && (writeScope !== 'unrestricted' || (!globalReadOnly && nativeAccessMode !== 'native')))
79
+ throw Error('GLOBAL_ACCESS_MODE_INVALID');
80
+ const policy = globalPermissions ? await createAccountAccessPolicy(root, mode) : await createProjectPolicy({ root, mode, developerCommands,
75
81
  allowTrustedHomeRoot: nativeAccessMode === "native" && developerCommands === "trusted-project" });
76
- const desiredPort = Number(env.MAC_MCP_PROJECT_PORT || 8793);
82
+ const desiredPort = Number(env.STARMUX_PROJECT_PORT || 18893);
77
83
  if (!isSafeInteger(desiredPort) || desiredPort < 0 || desiredPort > 65535)
78
84
  throw new Error("INVALID_PORT");
79
85
  let activity = null;
@@ -103,32 +109,31 @@ async function initializeGateway({ env = process.env, audit: externalAudit }) {
103
109
  await upstream.connect();
104
110
  // Default configuration remains the original Desktop Commander-only profile.
105
111
  // Additional trusted operator-owned routes require an explicit private config.
106
- configured = await bootstrapConfiguredUpstreams(env.MAC_MCP_UPSTREAMS_FILE, audit);
112
+ configured = await bootstrapConfiguredUpstreams(env.STARMUX_UPSTREAMS_FILE, audit);
107
113
  const router = createMCPUpstreamRouter({ routes: [
108
114
  { id: "desktop", upstream, exposeNativeNames: true }, ...configured.routes
109
115
  ] });
110
- // Owner explicitly opts in on isolated development runs only.
111
- // Not enabled in the currently deployed 8793 process.
112
- if (env.MAC_MCP_PRIVATE_ACTIVITY_OBSERVER === "1")
116
+ // The instance supervisor enables this bounded, owner-local activity observer.
117
+ if (env.STARMUX_PRIVATE_ACTIVITY_OBSERVER === "1")
113
118
  activity = createPrivateMCPActivity({ routeFor: name => router.routeFor(name) });
114
- const worktreeMode = env.MAC_MCP_WORKTREE_MODE || "off";
119
+ const worktreeMode = env.STARMUX_WORKTREE_MODE || "off";
115
120
  if (!["off", "linked"].includes(worktreeMode))
116
121
  throw Error("INVALID_WORKTREE_MODE");
117
122
  const worktreePolicy = createAuthorizedWorkspacePolicy({
118
- basePolicy: policy, linkedGitWorktrees: worktreeMode
123
+ basePolicy: policy, linkedGitWorktrees: globalPermissions ? 'off' : worktreeMode
119
124
  });
120
125
  // Explicit opt-in: existing/private deployments remain unchanged without this file.
121
- const grants = env.MAC_MCP_GRANTS_FILE ?
122
- await createWorkspaceGrants({ file: env.MAC_MCP_GRANTS_FILE, audit }) : null;
126
+ const grants = !globalPermissions && env.STARMUX_GRANTS_FILE ?
127
+ await createWorkspaceGrants({ file: env.STARMUX_GRANTS_FILE, audit }) : null;
123
128
  const workspacePolicy = grants ?
124
129
  createManagedWorkspacePolicy({ basePolicy: worktreePolicy, grants }) : worktreePolicy;
125
130
  // Optional explicit deny preferences: default empty, independent from DC tools.
126
131
  // Owner-managed only via the same local token as Workspace Permissions.
127
- if (env.MAC_MCP_DENY_FILE && !grants)
132
+ if (!globalPermissions && env.STARMUX_DENY_FILE && !grants)
128
133
  throw Error("DENY_CONSOLE_REQUIRES_OWNER_GRANTS");
129
- const denyRules = env.MAC_MCP_DENY_FILE ?
130
- await createDenyRules({ file: env.MAC_MCP_DENY_FILE, audit }) : null;
131
- const gitMode = env.MAC_MCP_GIT_MODE || "off";
134
+ const denyRules = !globalPermissions && env.STARMUX_DENY_FILE ?
135
+ await createDenyRules({ file: env.STARMUX_DENY_FILE, audit }) : null;
136
+ const gitMode = globalPermissions ? 'off' : env.STARMUX_GIT_MODE || "off";
132
137
  if (!["off", "review", "write", "push"].includes(gitMode))
133
138
  throw new Error("INVALID_GIT_MODE");
134
139
  if (writeScope === "home-only" && gitMode !== "off")
@@ -138,15 +143,33 @@ async function initializeGateway({ env = process.env, audit: externalAudit }) {
138
143
  pushEnabled: gitMode === "push"
139
144
  });
140
145
  http = createServer(async (req, res) => {
141
- const local = req.socket.remoteAddress === "127.0.0.1";
146
+ const denyRequest = (reason) => {
147
+ audit({ event: 'HTTP_REQUEST_DENIED', reason });
148
+ res.writeHead(403, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' }).end(reason);
149
+ };
150
+ if (req.socket.remoteAddress !== "127.0.0.1") {
151
+ denyRequest('LOOPBACK_REQUIRED');
152
+ return;
153
+ }
154
+ let route;
155
+ try {
156
+ route = new URL(req.url || "/", "http://localhost").pathname;
157
+ }
158
+ catch {
159
+ res.writeHead(400).end('Invalid request target');
160
+ return;
161
+ }
142
162
  const address = http?.address();
143
163
  const port = address && typeof address !== "string" ? address.port : null;
144
- const hostOk = [`127.0.0.1:${port}`, `localhost:${port}`].includes(req.headers.host ?? "");
145
- if (!local || !hostOk) {
146
- res.writeHead(403).end("Denied");
164
+ if (!isAllowedGatewayHost(req, port, route)) {
165
+ denyRequest('HOST_NOT_ALLOWED');
166
+ return;
167
+ }
168
+ if (globalPermissions && (route === '/permissions' || route === '/filters')) {
169
+ res.writeHead(410, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' });
170
+ res.end('Starmux uses global Read-only or Coding mode. Directory grants and tool filters do not apply. Use starmux up --read or starmux up --code.');
147
171
  return;
148
172
  }
149
- const route = new URL(req.url || "/", "http://localhost").pathname;
150
173
  if (route.startsWith('/.well-known/oauth-protected-resource') && (req.method === 'GET' || req.method === 'OPTIONS')) {
151
174
  try {
152
175
  const config = await access.oauthSettings();
@@ -175,10 +198,10 @@ async function initializeGateway({ env = process.env, audit: externalAudit }) {
175
198
  }
176
199
  else if (req.method === "POST") {
177
200
  const result = await accessPost(req, access);
178
- if (result.status === 200 && env.MAC_MCP_INSTANCE_ID) {
201
+ if (result.status === 200 && env.STARMUX_INSTANCE_ID) {
179
202
  const { changeRegistry, instancePaths } = await import('./instance-store.js');
180
203
  await changeRegistry(async (registry) => {
181
- const instance = registry.instances.find(i => i.id === env.MAC_MCP_INSTANCE_ID);
204
+ const instance = registry.instances.find(i => i.id === env.STARMUX_INSTANCE_ID);
182
205
  if (instance && instancePaths(instance).access === access.file)
183
206
  instance.requiredAccess = (await access.snapshot()).mode;
184
207
  });
@@ -251,10 +274,11 @@ async function initializeGateway({ env = process.env, audit: externalAudit }) {
251
274
  if ((route === "/dashboard" || route === "/dashboard.json") && req.method === "GET") {
252
275
  try {
253
276
  const snapshot = { ...await collectOperationsSnapshot(), configuredUpstreams: configured.status(),
254
- ...(env.MAC_MCP_INSTANCE_ID ? { instance: { id: env.MAC_MCP_INSTANCE_ID, name: env.MAC_MCP_INSTANCE_NAME ?? env.MAC_MCP_INSTANCE_ID, port: desiredPort } } : {}),
277
+ ...(env.STARMUX_INSTANCE_ID ? { instance: { id: env.STARMUX_INSTANCE_ID, name: env.STARMUX_INSTANCE_NAME ?? env.STARMUX_INSTANCE_ID, port: desiredPort } } : {}),
255
278
  accessControl: await access.snapshot(),
256
279
  permissionManagementEnabled: Boolean(grants),
257
280
  nativeAccessEnabled: nativeAccessMode === "native",
281
+ permissionMode: globalPermissions ? 'global' : 'workspace',
258
282
  denyRuleManagementEnabled: Boolean(denyRules) };
259
283
  const json = route.endsWith(".json");
260
284
  res.writeHead(200, { ...dashboardHeaders,
@@ -291,7 +315,7 @@ async function initializeGateway({ env = process.env, audit: externalAudit }) {
291
315
  res.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
292
316
  const accessMode = await access.snapshot().then(s => s.mode).catch(() => "unavailable");
293
317
  res.end(JSON.stringify({ status: "ok", service: "dc-project-workspace", mode,
294
- writeScope, accessMode, ...(env.MAC_MCP_INSTANCE_ID ? { instanceId: env.MAC_MCP_INSTANCE_ID, instanceName: env.MAC_MCP_INSTANCE_NAME } : {}) }));
318
+ writeScope, accessMode, ...(env.STARMUX_INSTANCE_ID ? { instanceId: env.STARMUX_INSTANCE_ID, instanceName: env.STARMUX_INSTANCE_NAME } : {}) }));
295
319
  return;
296
320
  }
297
321
  if (route !== "/mcp") {
@@ -331,6 +355,7 @@ async function initializeGateway({ env = process.env, audit: externalAudit }) {
331
355
  const gateway = createDesktopCommanderBridge(router, audit, {
332
356
  request, ...(oauth ? { oauthScope: oauth.scope } : {}), workspacePolicy, upstreamRouter: router,
333
357
  nativePassthrough: nativeAccessMode === "native", denyRules,
358
+ globalReadOnly, permissionMode: globalPermissions ? 'global' : 'workspace',
334
359
  ...(gitWorkflow ? { gitWorkflow } : {})
335
360
  });
336
361
  bridges.add(gateway);
@@ -385,4 +410,3 @@ export async function runGateway(options = {}) {
385
410
  process.on('SIGTERM', stop);
386
411
  return runtime;
387
412
  }
388
- //# sourceMappingURL=dc-workspace-http.js.map
@@ -43,7 +43,7 @@ export function renderDenyDashboard({ rules, toolCatalog = [] }) {
43
43
  '<p>仅检查具有明确文件路径参数的直接工具调用。' +
44
44
  '<strong>不限制 Shell、Python、脚本或上游工具间接访问文件。</strong> ' +
45
45
  '也不是操作系统沙箱;对目录的递归枚举或搜索亦不能提供可靠隔离。' +
46
- '需要真正隔离时,应由 Desktop Commander / macOS / 容器负责。</p>' +
46
+ '需要真正隔离时,应由 Desktop Commander / 操作系统 / 容器负责。</p>' +
47
47
  '<table><thead><tr><th>过滤目录</th><th>操作</th></tr></thead><tbody>' +
48
48
  (paths || '<tr><td colspan="2">没有目录过滤规则</td></tr>') +
49
49
  '</tbody></table><h3>添加禁止的目录</h3>' +
@@ -56,7 +56,7 @@ export function renderDenyDashboard({ rules, toolCatalog = [] }) {
56
56
  '</section><p><a href="/dashboard">运行 Dashboard</a> · ' +
57
57
  '<a href="/permissions">目录授权</a></p>' +
58
58
  '<small>黑名单是防误操作护栏,不是抵御恶意 Shell 或提示注入的安全边界。' +
59
- '这里的管理密钥只由设备所有者在 Mac 本机使用,不得发送给远程 AI。</small>' +
59
+ '这里的管理密钥只由设备所有者在本机使用,不得发送给远程 AI。</small>' +
60
60
  '</body></html>';
61
61
  }
62
62
  export async function applyDenyPost(req, ruleStore, authorize, host) {
@@ -89,4 +89,3 @@ export async function applyDenyPost(req, ruleStore, authorize, host) {
89
89
  return { status: 400, message: "Rule rejected: check name or directory" };
90
90
  }
91
91
  }
92
- //# sourceMappingURL=deny-dashboard.js.map
@@ -1,5 +1,6 @@
1
1
  import { createInvocationPipeline, safeAuditSink } from "./invocation.js";
2
2
  import { createToolDispatcher } from "./tool-dispatch.js";
3
+ import { isReadOnlyTool } from './account-access-policy.js';
3
4
  // Desktop Commander tool-schema facade. Upstream catalog remains unmodified.
4
5
  // Preserve all native upstream tools; owner-approved native mode forwards unchanged.
5
6
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -10,7 +11,7 @@ export function createDesktopCommanderBridge(upstream, audit = () => { }, option
10
11
  const resourcesEnabled = typeof upstream.listResources === "function" &&
11
12
  typeof upstream.readResource === "function";
12
13
  const server = new Server({
13
- name: "mac-mcp-gateway-desktop-commander-bridge", version: "0.1.0"
14
+ name: "starmux-desktop-commander-bridge", version: "0.1.0"
14
15
  }, { capabilities: { tools: {}, ...(resourcesEnabled ? { resources: {} } : {}) } });
15
16
  if (resourcesEnabled) {
16
17
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
@@ -40,10 +41,10 @@ export function createDesktopCommanderBridge(upstream, audit = () => { }, option
40
41
  annotations: { readOnlyHint: true, destructiveHint: false }
41
42
  }] : []), {
42
43
  name: "health_check",
43
- description: "Mac MCP Gateway bridge connection and genuine upstream availability check",
44
+ description: "Starmux bridge connection, upstream availability and active mode check. Read-only blocks writes and arbitrary execution; Coding uses upstream tools with OS account permissions.",
44
45
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
45
46
  annotations: { readOnlyHint: true, destructiveHint: false }
46
- }].filter(tool => !state?.deniedTools.includes(tool.name)).map(tool => options.oauthScope ? { ...tool,
47
+ }].filter(tool => !state?.deniedTools.includes(tool.name) && (!options.globalReadOnly || isReadOnlyTool(tool.name))).map(tool => options.oauthScope ? { ...tool,
47
48
  securitySchemes: [{ type: 'oauth2', scopes: [options.oauthScope] }],
48
49
  _meta: { ...('_meta' in tool ? tool._meta : {}), securitySchemes: [{ type: 'oauth2', scopes: [options.oauthScope] }] } } : tool) };
49
50
  });
@@ -57,4 +58,3 @@ export function createDesktopCommanderBridge(upstream, audit = () => { }, option
57
58
  server.setRequestHandler(CallToolRequestSchema, request => invoke(request.params));
58
59
  return server;
59
60
  }
60
- //# sourceMappingURL=desktop-commander-facade.js.map
@@ -3,7 +3,7 @@ import { ownedByUser, createPrivateDirectory, within } from "./platform.js";
3
3
  import { denySchema } from "./config-schemas.js";
4
4
  import { GatewayError, errorCode, parseJSON, record } from "./validation.js";
5
5
  // Optional explicit-deny preferences. Never a sandbox; native tools and command
6
- // semantics belong to upstream Desktop Commander and the macOS execution user.
6
+ // semantics belong to upstream Desktop Commander and the OS execution user.
7
7
  // Reads the owner-only 0600 profile on EVERY discovery/call, so revocation applies
8
8
  // to stale-client tool calls without restarting the Gateway.
9
9
  import { randomBytes } from "node:crypto";
@@ -15,7 +15,7 @@ function issue(code) { const e = new GatewayError(code); throw e; }
15
15
  const toolName = /^[a-zA-Z_][a-zA-Z0-9_.:-]{0,127}$/;
16
16
  const pathKeys = new Set(["path", "file_path", "paths", "source", "destination", "outputPath"]);
17
17
  const defaults = () => ({ version: 1, deniedTools: [], deniedDirectories: [] });
18
- export const defaultDenyRulesPath = (home = homedir()) => path.join(home, ".config", "mac-mcp-gateway", "deny-rules-v1.json");
18
+ export const defaultDenyRulesPath = (home = homedir()) => path.join(home, ".config", "starmux", "deny-rules-v1.json");
19
19
  function verify(value) {
20
20
  const obj = record(value, "DENY_RULES_INVALID");
21
21
  if (!obj || typeof obj !== "object" || Array.isArray(obj) ||
@@ -169,4 +169,3 @@ export async function createDenyRules({ file = defaultDenyRulesPath(), audit = (
169
169
  await snapshot();
170
170
  return Object.freeze({ file, snapshot, update, check });
171
171
  }
172
- //# sourceMappingURL=explicit-deny-rules.js.map
@@ -17,7 +17,7 @@ export function controlPaths(port, root = controlRoot()) {
17
17
  deny("CORE_INVALID_PORT");
18
18
  const lock = path.join(root, `core-${port}.lease`);
19
19
  return { root, lock, recovery: lock + ".recovery",
20
- meta: path.join(lock, "owner.json"), socket: windows ? `\\\\.\\pipe\\mac-mcp-gateway-${crypto.createHash("sha256").update(path.resolve(lock).toLowerCase()).digest("hex").slice(0, 32)}` : path.join(lock, "control.sock") };
20
+ meta: path.join(lock, "owner.json"), socket: windows ? `\\\\.\\pipe\\starmux-${crypto.createHash("sha256").update(path.resolve(lock).toLowerCase()).digest("hex").slice(0, 32)}` : path.join(lock, "control.sock") };
21
21
  }
22
22
  function secureDir(p, create = false) {
23
23
  if (create)
@@ -335,7 +335,7 @@ export async function acquireControl(port, { root, launcher = "cli" } = {}) {
335
335
  };
336
336
  }
337
337
  // Private worker handoff for the setup profile. This is a same-UID
338
- // supervisor identity check, not an OS boundary against the Mac owner.
338
+ // supervisor identity check, not an OS boundary against the local owner.
339
339
  export function verifyInternalSetupWorker({ port, ownerPid, runId, token }) {
340
340
  const paths = controlPaths(port);
341
341
  const owner = readOwner(paths);
@@ -345,4 +345,3 @@ export function verifyInternalSetupWorker({ port, ownerPid, runId, token }) {
345
345
  deny("CORE_SETUP_WORKER_NOT_SUPERVISED");
346
346
  return true;
347
347
  }
348
- //# sourceMappingURL=global-core-control.js.map
@@ -47,4 +47,3 @@ export async function sandboxedDesktopSpawn({ home, nodeBinary, entryArgs, binar
47
47
  args: ["-p", homeWriteProfile(h), nodeBinary, ...entryArgs]
48
48
  };
49
49
  }
50
- //# sourceMappingURL=home-write-sandbox.js.map
@@ -0,0 +1,20 @@
1
+ /** Host is an HTTP authority, not a URL; do not normalize alternate IP spellings or userinfo. */
2
+ export function isAllowedGatewayHost(req, listenerPort, route) {
3
+ // Node keeps only one Host in headers; inspect rawHeaders to reject duplicates too.
4
+ let hostCount = 0;
5
+ for (let i = 0; i < req.rawHeaders.length; i += 2)
6
+ if (req.rawHeaders[i]?.toLowerCase() === 'host')
7
+ hostCount++;
8
+ const host = req.headers.host;
9
+ if (hostCount !== 1 || !host || listenerPort === null)
10
+ return false;
11
+ // Owner pages and discovery retain their existing listener-specific boundary.
12
+ if (route !== '/mcp')
13
+ return host === `127.0.0.1:${listenerPort}` || host === `localhost:${listenerPort}`;
14
+ // SSH forwards TCP bytes unchanged, including the client's local Host port.
15
+ // Keep the hostname allowlist and validate the entire authority, including its port.
16
+ const match = /^(?:127\.0\.0\.1|localhost)(?::([0-9]{1,5}))?$/i.exec(host);
17
+ if (!match || match[0] !== host)
18
+ return false;
19
+ return match[1] === undefined || (Number(match[1]) >= 1 && Number(match[1]) <= 65535);
20
+ }
@@ -228,10 +228,10 @@ export async function runInstance(instance, options = {}) {
228
228
  for (const key of childEnvKeys)
229
229
  if (process.env[key])
230
230
  env[key] = process.env[key];
231
- Object.assign(env, instanceEnv(instance, config.config, home), { MAC_MCP_INTERNAL_SETUP_OWNER_PID: String(process.pid), MAC_MCP_INTERNAL_SETUP_RUN_ID: lease.owner.runId, MAC_MCP_INTERNAL_SETUP_TOKEN: lease.owner.token });
231
+ Object.assign(env, instanceEnv(instance, config.config, home), { STARMUX_INTERNAL_SETUP_OWNER_PID: String(process.pid), STARMUX_INTERNAL_SETUP_RUN_ID: lease.owner.runId, STARMUX_INTERNAL_SETUP_TOKEN: lease.owner.token });
232
232
  const upstreamFile = await prepareUpstreams(instance, config.config, home);
233
233
  if (upstreamFile)
234
- env.MAC_MCP_UPSTREAMS_FILE = upstreamFile;
234
+ env.STARMUX_UPSTREAMS_FILE = upstreamFile;
235
235
  const log = path.join(instancePaths(instance, home).logs, 'core.log');
236
236
  if (await exists(log))
237
237
  await readTrusted(log, true, 32 * 1024 * 1024);
@@ -254,6 +254,7 @@ export async function runInstance(instance, options = {}) {
254
254
  child.on('message', ready);
255
255
  });
256
256
  state.active = config;
257
+ state.permissionMode = env.STARMUX_PERMISSION_MODE === 'global' ? 'global' : 'workspace';
257
258
  state.port = config.config.port;
258
259
  state.phase = 'running';
259
260
  lease.markRunning();
@@ -393,4 +394,3 @@ export async function runInstance(instance, options = {}) {
393
394
  await rm(p.root, { recursive: true, force: true });
394
395
  }
395
396
  }
396
- //# sourceMappingURL=instance-runtime.js.map
@@ -10,8 +10,10 @@ import { z } from 'zod';
10
10
  import { zodToJsonSchema } from 'zod-to-json-schema';
11
11
  import { validateConfiguredUpstreams } from './configured-mcp-upstreams.js';
12
12
  import { createPrivateDirectory, ownedByUser } from './platform.js';
13
- import { checkSetupValues, setupRuntimeEnv } from './release1-onboarding.js';
13
+ import { supportedHost } from './platform.js';
14
+ import { createProjectPolicy } from './project-workspace-policy.js';
14
15
  import { createAccessControl } from './access-control.js';
16
+ import { createAccountAccessPolicy } from './account-access-policy.js';
15
17
  const nameSchema = z.string().trim().min(1).max(80).regex(/^[^\x00-\x1f\x7f/\\]+$/);
16
18
  const sourceSchema = z.object({ kind: z.enum(['managed', 'linked']), path: z.string() }).strict();
17
19
  export const instanceSchema = z.object({ id: z.string().uuid(), name: nameSchema, source: sourceSchema, archived: z.boolean().default(false),
@@ -118,17 +120,6 @@ export function parseConfig(text) {
118
120
  const prefix = text.slice(0, e.offset);
119
121
  throw Error(`CONFIG_JSON_INVALID line ${prefix.split('\n').length}, column ${e.offset - (prefix.lastIndexOf('\n') + 1) + 1}: ${printParseErrorCode(e.error)}`);
120
122
  }
121
- // Import the three historical onboarding formats without changing their authority.
122
- if (value && typeof value === 'object' && 'version' in value) {
123
- const legacy = value;
124
- if (![1, 2, 3].includes(Number(legacy.version)))
125
- throw Error('CONFIG_VERSION_UNSUPPORTED');
126
- const allowed = new Set(['version', 'projectRoot', 'mode', 'connection', 'port', 'accessRole', 'writeScope']);
127
- if (Object.keys(legacy).some(k => !allowed.has(k)))
128
- throw Error('CONFIG_UNKNOWN_LEGACY_FIELD');
129
- value = { schemaVersion: 1, projectRoot: legacy.projectRoot, mode: legacy.mode, connection: legacy.connection, port: legacy.port,
130
- accessRole: legacy.version === 1 ? 'restricted' : legacy.accessRole, writeScope: legacy.version === 3 ? 'home-only' : 'unrestricted' };
131
- }
132
123
  const result = instanceConfigSchema.safeParse(value);
133
124
  if (!result.success)
134
125
  throw Error('CONFIG_INVALID: ' + result.error.issues.map(i => `${i.path.join('.') || 'config'}: ${i.message}`).join('; '));
@@ -138,8 +129,19 @@ export async function validateConfig(config) {
138
129
  const { accessRole, writeScope } = config;
139
130
  if (writeScope === 'home-only' && (accessRole !== 'trusted' || config.upstreams.length || config.gitMode !== 'off'))
140
131
  throw Error('CONFIG_INVALID writeScope: home-only requires trusted mode without extra upstreams or Gateway Git');
141
- const legacy = { projectRoot: config.projectRoot, mode: config.mode, port: config.port, connection: config.connection === 'local' ? 'https' : config.connection };
142
- await checkSetupValues(accessRole === 'restricted' && config.mode === 'read-write' ? { ...legacy, version: 1 } : writeScope === 'home-only' ? { ...legacy, version: 3, accessRole, writeScope } : { ...legacy, version: 2, accessRole });
132
+ supportedHost();
133
+ instanceConfigSchema.parse(config);
134
+ if (!path.isAbsolute(config.projectRoot))
135
+ throw Error('CONFIG_INVALID projectRoot: absolute path required');
136
+ if (accessRole === 'trusted' && config.mode !== 'read-write')
137
+ throw Error('CONFIG_ACCESS_ROLE_MISMATCH');
138
+ if (writeScope === 'home-only' && process.platform !== 'darwin')
139
+ throw Error('HOME_WRITE_SANDBOX_PLATFORM_UNSUPPORTED');
140
+ if (usesGlobalPermissions(config))
141
+ await createAccountAccessPolicy(config.projectRoot, config.mode);
142
+ else
143
+ await createProjectPolicy({ root: config.projectRoot, mode: config.mode,
144
+ developerCommands: accessRole === 'trusted' ? 'trusted-project' : 'presets', allowTrustedHomeRoot: accessRole === 'trusted' });
143
145
  validateConfiguredUpstreams({ version: 1, upstreams: config.upstreams.map(({ envRefs, ...u }) => ({ ...u, env: Object.fromEntries(Object.keys(envRefs).map(k => [k, 'reference'])) })) });
144
146
  return config;
145
147
  }
@@ -185,7 +187,7 @@ export async function createInstance(name, options = {}, home = homedir()) {
185
187
  const id = randomUUID();
186
188
  const root = path.join(instancesRoot(home), 'instances', id);
187
189
  const instance = instanceSchema.parse({ id, name, archived: false, requiredAccess: options.requiredAccess ?? 'ingress',
188
- source: { kind: options.link ? 'linked' : 'managed', path: options.link ? input : path.join(root, 'config.jsonc') }, ...(options.legacy ? { legacy: options.legacy } : {}) });
190
+ source: { kind: options.link ? 'linked' : 'managed', path: options.link ? input : path.join(root, 'config.jsonc') } });
189
191
  createPrivateDirectory(path.join(instancesRoot(home), 'instances'));
190
192
  createPrivateDirectory(root);
191
193
  for (const dir of ['state', 'logs', 'snapshots'])
@@ -193,7 +195,7 @@ export async function createInstance(name, options = {}, home = homedir()) {
193
195
  try {
194
196
  await atomicPrivate(path.join(instancesRoot(home), 'gateway.schema.json'), JSON.stringify(configJSONSchema(), null, 2) + '\n');
195
197
  if (!options.link) {
196
- const original = text && !('version' in parse(text)) ? text : configTemplate(config);
198
+ const original = text ?? configTemplate(config);
197
199
  await atomicPrivate(instance.source.path, applyEdits(original, modify(original, ['$schema'], '../../gateway.schema.json', { formattingOptions: { insertSpaces: true, tabSize: 2 } })));
198
200
  }
199
201
  registry.instances.push(instance);
@@ -234,13 +236,18 @@ export async function patchInstanceConfig(instance, patch, revision, home = home
234
236
  text = applyEdits(text, modify(text, [key], value, { formattingOptions: { insertSpaces: true, tabSize: 2 } }));
235
237
  return saveInstanceConfig(instance, text, revision, home);
236
238
  }
239
+ export function usesGlobalPermissions(config) {
240
+ return config.writeScope === 'unrestricted' &&
241
+ (config.accessRole === 'restricted' && config.mode === 'read-only' || config.accessRole === 'trusted' && config.mode === 'read-write');
242
+ }
237
243
  export function instanceEnv(instance, config, home = homedir()) {
238
- const legacy = { projectRoot: config.projectRoot, mode: config.mode, port: config.port, connection: config.connection === 'local' ? 'https' : config.connection,
239
- ...(config.accessRole === 'restricted' && config.mode === 'read-write' ? { version: 1 } : config.writeScope === 'home-only' ? { version: 3, accessRole: config.accessRole, writeScope: 'home-only' } : { version: 2, accessRole: config.accessRole }) };
240
244
  const p = instancePaths(instance, home);
241
- return { ...setupRuntimeEnv(legacy), MAC_MCP_ACCESS_FILE: p.access, MAC_MCP_GRANTS_FILE: p.grants, MAC_MCP_DENY_FILE: p.deny,
242
- MAC_MCP_GIT_MODE: config.gitMode, MAC_MCP_WORKTREE_MODE: config.worktreeMode, MAC_MCP_INSTANCE_ID: instance.id, MAC_MCP_INSTANCE_NAME: instance.name,
243
- MAC_MCP_PRIVATE_ACTIVITY_OBSERVER: '1' };
245
+ return { STARMUX_PROJECT_ROOT: config.projectRoot, STARMUX_PROJECT_PORT: String(config.port),
246
+ STARMUX_WORKSPACE_MODE: config.mode, STARMUX_DEV_COMMANDS: config.accessRole === 'trusted' ? 'trusted-project' : 'presets',
247
+ STARMUX_DC_ACCESS_MODE: config.accessRole === 'trusted' ? 'native' : 'workspace', STARMUX_DC_WRITE_SCOPE: config.writeScope,
248
+ STARMUX_ACCESS_FILE: p.access, STARMUX_GRANTS_FILE: p.grants, STARMUX_DENY_FILE: p.deny,
249
+ STARMUX_GIT_MODE: config.gitMode, STARMUX_WORKTREE_MODE: config.worktreeMode, STARMUX_INSTANCE_ID: instance.id, STARMUX_INSTANCE_NAME: instance.name,
250
+ STARMUX_PRIVATE_ACTIVITY_OBSERVER: '1', STARMUX_PERMISSION_MODE: usesGlobalPermissions(config) ? 'global' : 'workspace' };
244
251
  }
245
252
  export async function ensureAccessReady(instance, home = homedir()) {
246
253
  const access = await createAccessControl(instancePaths(instance, home).access);
@@ -266,4 +273,3 @@ export async function prepareUpstreams(instance, config, home = homedir()) {
266
273
  await atomicPrivate(file, JSON.stringify({ version: 1, upstreams: definitions }));
267
274
  return file;
268
275
  }
269
- //# sourceMappingURL=instance-store.js.map
@@ -2,8 +2,7 @@ import { verifyInternalSetupWorker } from './global-core-control.js';
2
2
  import { runGateway } from './dc-workspace-http.js';
3
3
  if (!process.send)
4
4
  throw Error('INSTANCE_WORKER_REQUIRES_SUPERVISOR');
5
- verifyInternalSetupWorker({ port: Number(process.env.MAC_MCP_PROJECT_PORT), ownerPid: Number(process.env.MAC_MCP_INTERNAL_SETUP_OWNER_PID),
6
- runId: process.env.MAC_MCP_INTERNAL_SETUP_RUN_ID, token: process.env.MAC_MCP_INTERNAL_SETUP_TOKEN });
5
+ verifyInternalSetupWorker({ port: Number(process.env.STARMUX_PROJECT_PORT), ownerPid: Number(process.env.STARMUX_INTERNAL_SETUP_OWNER_PID),
6
+ runId: process.env.STARMUX_INTERNAL_SETUP_RUN_ID, token: process.env.STARMUX_INTERNAL_SETUP_TOKEN });
7
7
  await runGateway();
8
8
  process.send({ type: 'gateway-ready' });
9
- //# sourceMappingURL=instance-worker.js.map