starmux 0.13.1 → 0.15.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 (144) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +1 -1
  3. package/README.md +20 -28
  4. package/dist/src/access-control.js +1 -2
  5. package/dist/src/access-dashboard.js +2 -3
  6. package/dist/src/account-access-policy.js +54 -0
  7. package/dist/src/authorized-workspace-roots.js +0 -1
  8. package/dist/src/batch-read-policy.js +30 -0
  9. package/dist/src/cli/instances.js +27 -187
  10. package/dist/src/cli/oauth.js +0 -1
  11. package/dist/src/cli/starmux-status.js +25 -18
  12. package/dist/src/cli/starmux-supervisor.js +0 -1
  13. package/dist/src/cli/starmux.js +47 -37
  14. package/dist/src/config-schemas.js +1 -10
  15. package/dist/src/configured-mcp-upstreams.js +0 -1
  16. package/dist/src/contracts.js +0 -1
  17. package/dist/src/current-permission-probe.js +0 -1
  18. package/dist/src/dc-workspace-http.js +57 -33
  19. package/dist/src/deny-dashboard.js +2 -3
  20. package/dist/src/desktop-commander-facade.js +4 -4
  21. package/dist/src/explicit-deny-rules.js +2 -3
  22. package/dist/src/global-core-control.js +108 -56
  23. package/dist/src/home-write-sandbox.js +0 -1
  24. package/dist/src/http-host-policy.js +20 -0
  25. package/dist/src/instance-control-protocol.js +27 -0
  26. package/dist/src/instance-runtime.js +215 -76
  27. package/dist/src/instance-store.js +28 -22
  28. package/dist/src/instance-worker.js +2 -3
  29. package/dist/src/invocation.js +1 -2
  30. package/dist/src/local-desktop-read-probe.js +5 -88
  31. package/dist/src/managed-workspace-policy.js +38 -4
  32. package/dist/src/mcp-results.js +0 -1
  33. package/dist/src/mcp-upstream-router.js +0 -1
  34. package/dist/src/oauth-keycloak.js +0 -1
  35. package/dist/src/oauth-resource.js +0 -1
  36. package/dist/src/ops-dashboard.js +2 -179
  37. package/dist/src/permissions-dashboard.js +3 -4
  38. package/dist/src/platform.js +6 -4
  39. package/dist/src/privacy-diagnostics.js +0 -1
  40. package/dist/src/private-mcp-activity.js +0 -1
  41. package/dist/src/private-store-lock.js +0 -1
  42. package/dist/src/project-dev-presets.js +0 -1
  43. package/dist/src/project-git-workflow.js +0 -1
  44. package/dist/src/project-workspace-policy.js +10 -6
  45. package/dist/src/runtime-identity.js +0 -14
  46. package/dist/src/runtime-paths.js +0 -3
  47. package/dist/src/tool-dispatch.js +45 -46
  48. package/dist/src/validation.js +0 -1
  49. package/dist/src/workspace-grants.js +1 -2
  50. package/package.json +7 -15
  51. package/scripts/prepare.mjs +1 -1
  52. package/CONTRIBUTING.md +0 -18
  53. package/SECURITY.md +0 -7
  54. package/app/README.md +0 -29
  55. package/dist/src/access-control.js.map +0 -1
  56. package/dist/src/access-dashboard.js.map +0 -1
  57. package/dist/src/authorized-workspace-roots.js.map +0 -1
  58. package/dist/src/cli/access.js +0 -53
  59. package/dist/src/cli/access.js.map +0 -1
  60. package/dist/src/cli/community-remote-revoke.js +0 -32
  61. package/dist/src/cli/community-remote-revoke.js.map +0 -1
  62. package/dist/src/cli/instances.js.map +0 -1
  63. package/dist/src/cli/live-home-settings.js +0 -35
  64. package/dist/src/cli/live-home-settings.js.map +0 -1
  65. package/dist/src/cli/mac-mcp-gateway.js +0 -257
  66. package/dist/src/cli/mac-mcp-gateway.js.map +0 -1
  67. package/dist/src/cli/oauth.js.map +0 -1
  68. package/dist/src/cli/permissions-owner.js +0 -27
  69. package/dist/src/cli/permissions-owner.js.map +0 -1
  70. package/dist/src/cli/service-setup.js +0 -26
  71. package/dist/src/cli/service-setup.js.map +0 -1
  72. package/dist/src/cli/setup-wizard.js +0 -149
  73. package/dist/src/cli/setup-wizard.js.map +0 -1
  74. package/dist/src/cli/starmux-status.js.map +0 -1
  75. package/dist/src/cli/starmux-supervisor.js.map +0 -1
  76. package/dist/src/cli/starmux.js.map +0 -1
  77. package/dist/src/cli/verify-setup.js +0 -53
  78. package/dist/src/cli/verify-setup.js.map +0 -1
  79. package/dist/src/community-remote-revocations.js +0 -135
  80. package/dist/src/community-remote-revocations.js.map +0 -1
  81. package/dist/src/config-schemas.js.map +0 -1
  82. package/dist/src/configured-mcp-upstreams.js.map +0 -1
  83. package/dist/src/contracts.js.map +0 -1
  84. package/dist/src/current-permission-probe.js.map +0 -1
  85. package/dist/src/dc-workspace-http.js.map +0 -1
  86. package/dist/src/deny-dashboard.js.map +0 -1
  87. package/dist/src/desktop-commander-facade.js.map +0 -1
  88. package/dist/src/explicit-deny-rules.js.map +0 -1
  89. package/dist/src/gateway-status-format.js +0 -66
  90. package/dist/src/gateway-status-format.js.map +0 -1
  91. package/dist/src/global-core-control.js.map +0 -1
  92. package/dist/src/home-write-sandbox.js.map +0 -1
  93. package/dist/src/instance-migration.js +0 -138
  94. package/dist/src/instance-migration.js.map +0 -1
  95. package/dist/src/instance-runtime.js.map +0 -1
  96. package/dist/src/instance-store.js.map +0 -1
  97. package/dist/src/instance-worker.js.map +0 -1
  98. package/dist/src/invocation.js.map +0 -1
  99. package/dist/src/live-profile-home-settings.js +0 -80
  100. package/dist/src/live-profile-home-settings.js.map +0 -1
  101. package/dist/src/local-desktop-read-probe.js.map +0 -1
  102. package/dist/src/managed-workspace-policy.js.map +0 -1
  103. package/dist/src/mcp-results.js.map +0 -1
  104. package/dist/src/mcp-upstream-router.js.map +0 -1
  105. package/dist/src/oauth-keycloak.js.map +0 -1
  106. package/dist/src/oauth-resource.js.map +0 -1
  107. package/dist/src/operator-cli.js +0 -559
  108. package/dist/src/operator-cli.js.map +0 -1
  109. package/dist/src/ops-dashboard.js.map +0 -1
  110. package/dist/src/permissions-dashboard.js.map +0 -1
  111. package/dist/src/platform.js.map +0 -1
  112. package/dist/src/privacy-diagnostics.js.map +0 -1
  113. package/dist/src/private-mcp-activity.js.map +0 -1
  114. package/dist/src/private-store-lock.js.map +0 -1
  115. package/dist/src/project-dev-presets.js.map +0 -1
  116. package/dist/src/project-git-workflow.js.map +0 -1
  117. package/dist/src/project-workspace-policy.js.map +0 -1
  118. package/dist/src/read-scope.js +0 -44
  119. package/dist/src/read-scope.js.map +0 -1
  120. package/dist/src/release1-onboarding.js +0 -120
  121. package/dist/src/release1-onboarding.js.map +0 -1
  122. package/dist/src/release1-service.js +0 -131
  123. package/dist/src/release1-service.js.map +0 -1
  124. package/dist/src/remote-auth.js +0 -47
  125. package/dist/src/remote-auth.js.map +0 -1
  126. package/dist/src/runtime-identity.js.map +0 -1
  127. package/dist/src/runtime-paths.js.map +0 -1
  128. package/dist/src/starmux-migration.js +0 -172
  129. package/dist/src/starmux-migration.js.map +0 -1
  130. package/dist/src/tool-dispatch.js.map +0 -1
  131. package/dist/src/validation.js.map +0 -1
  132. package/dist/src/workspace-grants.js.map +0 -1
  133. package/docs/ACCESS_CONTROL.md +0 -51
  134. package/docs/CLIENTS.md +0 -22
  135. package/docs/CONNECT_CHATGPT.md +0 -148
  136. package/docs/INSTALL.md +0 -62
  137. package/docs/INSTANCES.md +0 -83
  138. package/docs/OAUTH.md +0 -116
  139. package/docs/PLATFORMS.md +0 -29
  140. package/docs/README.md +0 -16
  141. package/docs/REMOTE_SETUP.md +0 -280
  142. package/docs/SECURITY.md +0 -15
  143. package/src/dc-workspace-http.mjs +0 -3
  144. package/test/fixtures/p1-fixture.txt +0 -2
@@ -1,5 +1,5 @@
1
1
  import { createAccessControl } from '../access-control.js';
2
- import { exists, instancePaths } from '../instance-store.js';
2
+ import { exists, instancePaths, usesGlobalPermissions } from '../instance-store.js';
3
3
  import { shellQuote } from '../platform.js';
4
4
  async function authentication(instance, status) {
5
5
  const file = instancePaths(instance).access;
@@ -23,6 +23,8 @@ async function authentication(instance, status) {
23
23
  }
24
24
  }
25
25
  export async function formatStarmuxStatus(instance, status, config, options = {}) {
26
+ // Only the live supervisor can confirm active permissions.
27
+ config = status.phase === 'stopped' ? status.saved?.config : status.active?.config;
26
28
  const auth = await authentication(instance, status);
27
29
  // Omit `-i default` for the default instance to keep everyday single-instance output clean.
28
30
  const omitSelector = options.isDefault === true && instance.name === 'default';
@@ -37,29 +39,35 @@ export async function formatStarmuxStatus(instance, status, config, options = {}
37
39
  'api-key': `API key (Bearer token; ${auth.activeKeys} active ${auth.activeKeys === 1 ? 'key' : 'keys'})`,
38
40
  oauth: 'OAuth (browser sign-in)'
39
41
  };
40
- const saved = status.phase === 'running' ? ' (saved; live state unavailable)' : ' (saved for next start)';
42
+ const saved = status.phase === 'stopped' ? ' (saved for next start)' : ' (saved; live state unavailable)';
41
43
  const row = (label, value) => ' ' + label.padEnd(16) + value;
42
44
  const codingCmd = command('up --code');
43
- const mode = !config
45
+ const globalMode = status.phase === 'running' ? status.runtime?.permissionMode === 'global' : !!config && usesGlobalPermissions(config);
46
+ const describeMode = (value, global) => !value
44
47
  ? 'Unavailable'
45
- : config.accessRole === 'trusted'
46
- ? (config.writeScope === 'home-only' ? 'Coding (Home sandbox · edits, shell & git)' : 'Coding (OS account · edits, shell & git)')
47
- : config.mode === 'read-write'
48
+ : value.accessRole === 'trusted'
49
+ ? value.writeScope === 'home-only' ? 'Coding (Home sandbox · edits, shell & git)' : 'Coding (OS account · edits, shell & git)'
50
+ : value.mode === 'read-write'
48
51
  ? 'File writes (restricted)'
49
- : `Read-only (run '${codingCmd}' to enable edits & shell)`;
50
- const runtime = status.executionMode === 'background'
51
- ? 'Background · continues after this terminal closes'
52
- : status.executionMode === 'foreground-owned'
53
- ? (status.runtime?.launcher === 'app' ? 'App-owned · stops when the app closes' : 'Foreground · Ctrl+C stops this instance')
54
- : 'Stopped';
55
- const title = omitSelector
56
- ? `${indicator} Starmux is ${status.phase}`
57
- : `${indicator} Starmux is ${status.phase} (instance: ${instance.name})`;
58
- const lines = ['', title];
52
+ : `Read-only (${global ? 'global · OS account' : 'project scope'})`;
53
+ const switchHint = config && (status.phase === 'running' || status.phase === 'stopped')
54
+ ? config.accessRole === 'trusted' ? ` (run '${command('up --read')}' to return to read-only)` : ` (run '${codingCmd}' to enable edits & shell)`
55
+ : '';
56
+ const mode = describeMode(config, globalMode) + switchHint;
57
+ const title = `${indicator} Starmux is ${status.phase} (instance: ${instance.name})`;
58
+ const lines = ['', title, row('Instance', instance.id)];
59
59
  if (ready && status.port) {
60
60
  lines.push(row('Listen', `http://127.0.0.1:${status.port}/mcp (this computer only)`));
61
61
  }
62
- lines.push(row('Workspace', config?.projectRoot ?? 'Unavailable'), row('Mode', mode), row('Auth', auth.mode ? labels[auth.mode] + (ready ? '' : saved) : 'Unavailable'), row('Runtime', runtime));
62
+ lines.push(row(status.phase === 'stopped' ? 'Saved project' : 'Project', config?.projectRoot ?? 'Unavailable'), row(status.phase === 'stopped' ? 'Saved mode' : status.phase === 'unknown' || status.configState === 'pending' ? 'Active mode' : 'Mode', mode), row('Auth', auth.mode ? labels[auth.mode] + (ready ? '' : saved) : 'Unavailable'));
63
+ if (status.phase !== 'stopped' && (status.configState === 'pending' || !status.active)) {
64
+ const savedConfig = status.saved?.config;
65
+ lines.push(row('Saved mode', describeMode(savedConfig, !!savedConfig && usesGlobalPermissions(savedConfig)) + (status.configState === 'pending' ? ' (pending)' : '')));
66
+ if (!status.active)
67
+ lines.push(row('Saved project', savedConfig?.projectRoot ?? 'Unavailable'));
68
+ }
69
+ if (status.runtime)
70
+ lines.push(row('Runtime', status.runtime.buildVersion));
63
71
  if (ready && status.port && auth.mode === 'oauth' && auth.oauth) {
64
72
  lines.push(row('Client URL', auth.oauth.resource), row('Issuer', auth.oauth.issuer));
65
73
  }
@@ -119,4 +127,3 @@ export async function formatStarmuxStatus(instance, status, config, options = {}
119
127
  lines.push(row('Stop', command('down')));
120
128
  return lines.join('\n') + '\n';
121
129
  }
122
- //# sourceMappingURL=starmux-status.js.map
@@ -8,4 +8,3 @@ catch (error) {
8
8
  process.stderr.write(String(error) + '\n');
9
9
  process.exitCode = 1;
10
10
  }
11
- //# sourceMappingURL=starmux-supervisor.js.map
@@ -8,13 +8,13 @@ import { fileURLToPath } from 'node:url';
8
8
  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
- 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';
11
+ import { instanceStatus, instanceRequest, stopInstance, 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, 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,11 @@ 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.');
128
+ if (command === 'up' && instance) {
129
+ const state = await instanceStatus(instance);
130
+ if (state.phase !== 'running' && state.phase !== 'stopped')
131
+ fail(`Cannot start ${instance.name}: ${state.runtimeProblem ?? state.phase}. Run starmux doctor -i ${instance.id}.`);
132
+ }
132
133
  const loaded = instance ? await loadInstanceConfig(instance) : undefined;
133
134
  const writing = flags.write || flags.code;
134
135
  if (flags.read && writing)
@@ -164,21 +165,29 @@ async function show(instance) {
164
165
  print(status);
165
166
  return;
166
167
  }
167
- const config = status.phase === 'running' ? status.active?.config : (await loadInstanceConfig(instance).catch(() => null))?.config;
168
+ const config = status.phase === 'stopped' ? status.saved?.config : status.active?.config;
168
169
  process.stdout.write(await formatStarmuxStatus(instance, status, config, { isDefault: await isDefaultInstance(instance) }));
169
170
  }
170
171
  async function start(instance) {
171
172
  const state = await instanceStatus(instance);
172
173
  if (state.phase === 'running') {
173
- if (state.configState === 'pending')
174
- await instanceRequest(instance, 'apply', (await loadInstanceConfig(instance)).revision);
174
+ if (state.configState === 'pending') {
175
+ const applied = await instanceRequest(instance, 'apply', (await loadInstanceConfig(instance)).revision);
176
+ if (!applied || applied.phase !== 'running')
177
+ fail('Configuration was not applied: instance changed while starting. Run starmux status.');
178
+ }
179
+ const current = await instanceStatus(instance);
180
+ if (current.phase !== 'running' || current.health !== 'ok')
181
+ fail(`Instance is not ready: ${current.runtimeProblem ?? current.health}. Run starmux doctor -i ${instance.id}.`);
175
182
  await show(instance);
176
183
  return;
177
184
  }
185
+ if (state.phase !== 'stopped')
186
+ fail(`Cannot start ${instance.name}: ${state.runtimeProblem ?? state.phase}. Run starmux doctor -i ${instance.id}.`);
178
187
  await ensureAccessReady(instance);
179
188
  const config = (await loadInstanceConfig(instance)).config;
180
189
  if (!await availablePort(config.port))
181
- fail(`Port ${config.port} is in use. Choose another with starmux up --port PORT.`);
190
+ fail(`Port ${config.port} is in use. Inspect starmux doctor -i ${instance.id} before changing the port.`);
182
191
  if (flags.foreground || string('app-parent-pid')) {
183
192
  await runInstance(instance, { json, ...(!json ? { onReady: () => show(instance) } : {}), ...(string('app-parent-pid') ? { appParentPid: Number(string('app-parent-pid')) } : {}) });
184
193
  return;
@@ -364,7 +373,7 @@ async function auth(instance) {
364
373
  return;
365
374
  }
366
375
  if (op === 'oauth') {
367
- process.env.MAC_MCP_ACCESS_FILE = instancePaths(instance).access;
376
+ process.env.STARMUX_ACCESS_FILE = instancePaths(instance).access;
368
377
  const action = positional[1] ?? 'status';
369
378
  const map = { 'init-keycloak': 'keycloak-realm', setup: 'configure', doctor: 'doctor', status: 'status' };
370
379
  if (!map[action])
@@ -593,7 +602,7 @@ async function main() {
593
602
  }
594
603
  const allowed = {
595
604
  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'],
605
+ status: ['all'], ls: ['archived'], rm: [], down: [], stop: [], logs: ['lines', 'follow'], doctor: [], verify: ['token-stdin'], dashboard: ['token', 'no-open'],
597
606
  auth: ['expires', 'issuer', 'resource', 'resource-aliases', 'client-id', 'subject', 'subjects', 'registration', 'token-auth', 'callback', 'allow-loopback-http'],
598
607
  config: ['yes', 'archived', 'effective', 'link', 'name', 'revision', 'patch', 'file', 'output'], permissions: ['target', 'mode'], privacy: ['all', 'target']
599
608
  };
@@ -609,15 +618,6 @@ async function main() {
609
618
  fail('Usage: starmux rm [NAME]');
610
619
  if (command === 'auth' && ['key', 'mode', 'revoke'].includes(positional[0] ?? '') && positional.length !== 2)
611
620
  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
621
  if (command === 'up' || command === 'init') {
622
622
  const instance = await configure();
623
623
  if (command === 'up')
@@ -630,10 +630,6 @@ async function main() {
630
630
  }
631
631
  return;
632
632
  }
633
- if (command === 'migrate') {
634
- await (await import('../starmux-migration.js')).migrate(positional[0], yes(), json);
635
- return;
636
- }
637
633
  if (command === 'ls' || command === 'status' && flags.all) {
638
634
  await handle('instance', ['list', ...(flags.archived ? ['--archived'] : []), ...(json ? ['--json'] : [])]);
639
635
  return;
@@ -662,11 +658,13 @@ async function main() {
662
658
  if (command === 'status')
663
659
  await show(instance);
664
660
  else if (command === 'down' || command === 'stop') {
665
- await instanceRequest(instance, 'stop');
661
+ const result = await stopInstance(instance);
666
662
  if (json)
667
- print({ result: 'STOPPED', instanceId: instance.id });
663
+ print(result);
664
+ else if (result.restarted)
665
+ process.stdout.write(`Stopped the previous run of ${instance.name}; another command has started a new run.\n`);
668
666
  else
669
- process.stdout.write('Stopped ' + instance.name + '\n');
667
+ process.stdout.write(`${result.result === 'ALREADY_STOPPED' ? 'Already stopped' : 'Stopped'} ${instance.name}\n`);
670
668
  }
671
669
  else if (command === 'auth')
672
670
  await auth(instance);
@@ -679,29 +677,42 @@ async function main() {
679
677
  else if (command === 'doctor') {
680
678
  if (Number(process.versions.node.split('.')[0]) < 22 || (process.versions.node.startsWith('22.') && Number(process.versions.node.split('.')[1]) < 12))
681
679
  fail('Node 22.12 or newer is required.');
680
+ const state = await instanceStatus(instance);
681
+ if (state.configProblem || state.runtimeProblem || state.runtime?.problem)
682
+ fail(state.configProblem ?? state.runtimeProblem ?? state.runtime.problem);
683
+ if (state.phase !== 'running' && state.phase !== 'stopped')
684
+ fail('Instance state is ' + state.phase + '. Inspect starmux logs -i ' + instance.id);
685
+ if (state.phase === 'running' && state.health !== 'ok')
686
+ fail('Instance health is ' + state.health + '. Inspect starmux logs -i ' + instance.id);
682
687
  await stat(desktopCommanderEntry);
683
688
  const loaded = await loadInstanceConfig(instance);
684
689
  await ensureAccessReady(instance);
685
- const state = await instanceStatus(instance);
686
- if (state.phase !== 'running' && !await availablePort(loaded.config.port))
690
+ if (state.phase === 'stopped' && !await availablePort(loaded.config.port))
687
691
  fail('Configured port is in use: ' + loaded.config.port);
688
692
  if (json)
689
693
  print({ result: 'PASS', node: process.version, instanceId: instance.id, configState: state.configState });
690
694
  else {
691
695
  const isDef = await isDefaultInstance(instance);
692
696
  const access = await (await createAccessControl(instancePaths(instance).access, { initialize: false })).snapshot();
693
- const modeLabel = loaded.config.accessRole === 'trusted' ? 'Coding mode' : 'Read-only mode';
697
+ const effective = state.phase === 'running' ? state.active?.config : loaded.config;
698
+ const modeLabel = !effective ? 'Mode unavailable' : effective.accessRole === 'trusted' ? 'Coding mode' : effective.mode === 'read-write' ? 'File writes' : 'Read-only mode';
694
699
  const authLabel = access.mode === 'ingress' ? 'None (no token required)' : access.mode === 'api-key' ? `API key (${access.credentials.filter(c => c.status === 'active').length} active)` : 'OAuth';
695
700
  const portState = state.phase === 'running' ? `Running on 127.0.0.1:${state.port}` : `Stopped · port ${loaded.config.port} available`;
696
701
  process.stdout.write(`✓ Runtime Node ${process.version} & Desktop Commander engine ready\n` +
697
702
  `✓ Service ${portState}${isDef ? '' : ` (instance: ${instance.name})`}\n` +
698
- `✓ Workspace ${loaded.config.projectRoot} (${modeLabel})\n` +
703
+ `✓ Workspace ${effective?.projectRoot ?? 'Unavailable'} (${modeLabel})\n` +
699
704
  `✓ Configuration ${instance.source.path} (${state.configState})\n` +
700
705
  `✓ Authentication ${authLabel}\n` +
701
706
  `PASS · Node, dependencies, workspace, configuration, port and authentication.\n`);
702
707
  }
703
708
  }
704
709
  else {
710
+ if (command === 'permissions' && usesGlobalPermissions((await loadInstanceConfig(instance)).config)) {
711
+ if (positional.length && positional[0] !== 'status')
712
+ fail('Directory grants and tool filters do not apply to global modes. Use starmux up --read or starmux up --code.');
713
+ await show(instance);
714
+ return;
715
+ }
705
716
  const args = [...positional, '--instance', instance.id];
706
717
  for (const [key, value] of Object.entries(flags))
707
718
  if (key !== 'instance' && value !== undefined)
@@ -720,4 +731,3 @@ catch (error) {
720
731
  process.stderr.write(message + '\n');
721
732
  process.exitCode = 1;
722
733
  }
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
- 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
5
+ export const ownerSchema = z.object({ version: z.literal(1), 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();
@@ -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