orkestrate 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -112,21 +112,64 @@ var init_ui = __esm({
112
112
 
113
113
  // src/lib/config.ts
114
114
  import Conf from "conf";
115
+ import { readFileSync, writeFileSync, existsSync } from "fs";
116
+ import { dirname } from "path";
117
+ import { mkdirSync } from "fs";
115
118
  function getServerUrl() {
116
119
  return config.get("serverUrl");
117
120
  }
121
+ function setConfigWithRetry(key, value, maxAttempts = 5) {
122
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
123
+ try {
124
+ config.set(key, value);
125
+ return;
126
+ } catch (err) {
127
+ if (err?.code !== "EPERM" && err?.code !== "EBUSY") {
128
+ throw err;
129
+ }
130
+ const delay = 50 * Math.pow(2, attempt);
131
+ if (attempt < maxAttempts - 1) {
132
+ const start = Date.now();
133
+ while (Date.now() - start < delay) {
134
+ }
135
+ }
136
+ }
137
+ }
138
+ try {
139
+ const configPath = config.path;
140
+ const dir = dirname(configPath);
141
+ if (!existsSync(dir)) {
142
+ mkdirSync(dir, { recursive: true });
143
+ }
144
+ let currentConfig = {};
145
+ if (existsSync(configPath)) {
146
+ try {
147
+ currentConfig = JSON.parse(readFileSync(configPath, "utf-8"));
148
+ } catch {
149
+ currentConfig = {};
150
+ }
151
+ }
152
+ currentConfig[key] = value;
153
+ writeFileSync(configPath, JSON.stringify(currentConfig, null, 2), "utf-8");
154
+ return;
155
+ } catch (fallbackErr) {
156
+ throw new Error(
157
+ `Failed to save config after ${maxAttempts} attempts. Original error: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`
158
+ );
159
+ }
160
+ }
118
161
  function setCredentials(creds) {
119
- config.set("credentials", creds);
162
+ setConfigWithRetry("credentials", creds);
120
163
  }
121
164
  function getCredentials() {
122
165
  return config.get("credentials");
123
166
  }
124
167
  function clearCredentials() {
125
- config.set("credentials", null);
168
+ setConfigWithRetry("credentials", null);
126
169
  }
127
170
  function setActiveWorkspace(id, name) {
128
- config.set("activeWorkspaceId", id);
129
- config.set("activeWorkspaceName", name);
171
+ setConfigWithRetry("activeWorkspaceId", id);
172
+ setConfigWithRetry("activeWorkspaceName", name);
130
173
  }
131
174
  function getActiveWorkspace() {
132
175
  return {
@@ -137,6 +180,64 @@ function getActiveWorkspace() {
137
180
  function getConfigPath() {
138
181
  return config.path;
139
182
  }
183
+ function getUserToolSettings() {
184
+ return config.get("userToolSettings") || {};
185
+ }
186
+ function setUserToolSettings(settings) {
187
+ setConfigWithRetry("userToolSettings", settings);
188
+ }
189
+ function getEnabledTools() {
190
+ const settings = getUserToolSettings();
191
+ return settings.enabledTools ?? null;
192
+ }
193
+ function getDisabledTools() {
194
+ const settings = getUserToolSettings();
195
+ return settings.disabledTools || [];
196
+ }
197
+ function setEnabledTools(tools) {
198
+ const settings = getUserToolSettings();
199
+ settings.enabledTools = tools;
200
+ setUserToolSettings(settings);
201
+ }
202
+ function setDisabledTools(tools) {
203
+ const settings = getUserToolSettings();
204
+ settings.disabledTools = tools;
205
+ setUserToolSettings(settings);
206
+ }
207
+ function isToolAllowed(toolName) {
208
+ const enabledTools = getEnabledTools();
209
+ const disabledTools = getDisabledTools();
210
+ if (enabledTools !== null) {
211
+ return enabledTools.includes(toolName);
212
+ }
213
+ return !disabledTools.includes(toolName);
214
+ }
215
+ function enableTool(toolName) {
216
+ const enabledTools = getEnabledTools();
217
+ const disabledTools = getDisabledTools();
218
+ if (enabledTools !== null) {
219
+ if (!enabledTools.includes(toolName)) {
220
+ setEnabledTools([...enabledTools, toolName]);
221
+ }
222
+ } else {
223
+ if (disabledTools.includes(toolName)) {
224
+ setDisabledTools(disabledTools.filter((t) => t !== toolName));
225
+ }
226
+ }
227
+ }
228
+ function disableTool(toolName) {
229
+ const enabledTools = getEnabledTools();
230
+ const disabledTools = getDisabledTools();
231
+ if (enabledTools !== null) {
232
+ if (enabledTools.includes(toolName)) {
233
+ setEnabledTools(enabledTools.filter((t) => t !== toolName));
234
+ }
235
+ } else {
236
+ if (!disabledTools.includes(toolName)) {
237
+ setDisabledTools([...disabledTools, toolName]);
238
+ }
239
+ }
240
+ }
140
241
  function setGithubTokens(tokens) {
141
242
  const creds = getCredentials();
142
243
  if (!creds) return;
@@ -145,6 +246,25 @@ function setGithubTokens(tokens) {
145
246
  creds.githubExpiresAt = tokens.expiresAt;
146
247
  setCredentials(creds);
147
248
  }
249
+ function getGithubTokens() {
250
+ const creds = getCredentials();
251
+ if (!creds?.githubAccessToken || !creds.githubExpiresAt) return null;
252
+ return {
253
+ accessToken: creds.githubAccessToken,
254
+ refreshToken: creds.githubRefreshToken,
255
+ expiresAt: creds.githubExpiresAt
256
+ };
257
+ }
258
+ function getValidGithubToken() {
259
+ const tokens = getGithubTokens();
260
+ if (!tokens) return null;
261
+ const now = Math.floor(Date.now() / 1e3);
262
+ if (tokens.expiresAt <= now + 60) return null;
263
+ return tokens.accessToken;
264
+ }
265
+ function hasGithubToken() {
266
+ return getValidGithubToken() !== null;
267
+ }
148
268
  var config;
149
269
  var init_config = __esm({
150
270
  "src/lib/config.ts"() {
@@ -156,7 +276,8 @@ var init_config = __esm({
156
276
  credentials: null,
157
277
  activeWorkspaceId: null,
158
278
  activeWorkspaceName: null,
159
- serverUrl: "https://orkestrate.space"
279
+ serverUrl: "https://orkestrate.space",
280
+ userToolSettings: {}
160
281
  }
161
282
  });
162
283
  }
@@ -399,7 +520,7 @@ async function performLogin() {
399
520
  });
400
521
  if (meRes.ok) {
401
522
  const me = await meRes.json();
402
- userId = me.id || "";
523
+ userId = me.user?.id || me.id || "";
403
524
  }
404
525
  } catch {
405
526
  }
@@ -626,10 +747,16 @@ __export(detect_exports, {
626
747
  detectTools: () => detectTools,
627
748
  getToolNames: () => getToolNames
628
749
  });
629
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
750
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
630
751
  import { homedir } from "os";
631
752
  import { join } from "path";
632
753
  import { execSync } from "child_process";
754
+ function getZedConfigPath() {
755
+ if (process.platform === "win32") {
756
+ return join(homedir(), "AppData", "Roaming", "Zed", "settings.json");
757
+ }
758
+ return join(homedir(), ".config", "zed", "settings.json");
759
+ }
633
760
  function detectTools(projectDir = process.cwd()) {
634
761
  const tools = [];
635
762
  tools.push({
@@ -641,111 +768,197 @@ function detectTools(projectDir = process.cwd()) {
641
768
  tools.push({
642
769
  name: "opencode",
643
770
  displayName: "OpenCode",
644
- detected: existsSync(opencodeConfig) || isCommandAvailable("opencode"),
771
+ detected: existsSync2(opencodeConfig) || isCommandAvailable("opencode"),
645
772
  configPath: opencodeConfig
646
773
  });
647
774
  const cursorConfig = join(projectDir, ".cursor", "mcp.json");
648
775
  tools.push({
649
776
  name: "cursor",
650
777
  displayName: "Cursor",
651
- detected: existsSync(join(projectDir, ".cursor")) || existsSync(cursorConfig),
778
+ detected: existsSync2(join(projectDir, ".cursor")) || existsSync2(cursorConfig),
652
779
  configPath: cursorConfig
653
780
  });
654
781
  const windsurfConfig = join(projectDir, ".windsurf", "mcp.json");
655
782
  tools.push({
656
783
  name: "windsurf",
657
784
  displayName: "Windsurf",
658
- detected: existsSync(join(projectDir, ".windsurf")) || existsSync(windsurfConfig),
785
+ detected: existsSync2(join(projectDir, ".windsurf")) || existsSync2(windsurfConfig),
659
786
  configPath: windsurfConfig
660
787
  });
661
788
  const codexDir = join(homedir(), ".codex");
662
789
  tools.push({
663
790
  name: "codex",
664
791
  displayName: "Codex CLI",
665
- detected: isCommandAvailable("codex") || existsSync(codexDir)
792
+ detected: isCommandAvailable("codex") || existsSync2(codexDir)
793
+ });
794
+ const zedConfig = getZedConfigPath();
795
+ tools.push({
796
+ name: "zed",
797
+ displayName: "Zed",
798
+ detected: existsSync2(zedConfig),
799
+ configPath: zedConfig
666
800
  });
667
801
  return tools;
668
802
  }
669
803
  async function configureTool(tool, projectDir = process.cwd()) {
670
- const bridge = resolveMcpBridge();
671
- const mcpUrl = `${getServerUrl()}/api/mcp`;
804
+ const bridge = resolveMcpBridge(tool);
672
805
  switch (tool) {
673
806
  case "claude":
674
807
  return configureClaudeCode(bridge);
675
808
  case "opencode":
676
- return configureOpenCode(join(projectDir, "opencode.json"), bridge);
809
+ return configureOpenCode(join(projectDir, "opencode.json"), bridge, tool);
677
810
  case "cursor":
678
- return configureMcpServersJson("Cursor", join(projectDir, ".cursor", "mcp.json"), bridge);
811
+ return configureMcpServersJson(
812
+ "Cursor",
813
+ join(projectDir, ".cursor", "mcp.json"),
814
+ bridge,
815
+ tool
816
+ );
679
817
  case "windsurf":
680
- return configureMcpServersJson("Windsurf", join(projectDir, ".windsurf", "mcp.json"), bridge);
818
+ return configureMcpServersJson(
819
+ "Windsurf",
820
+ join(projectDir, ".windsurf", "mcp.json"),
821
+ bridge,
822
+ tool
823
+ );
681
824
  case "codex":
682
- return configureCodex(bridge);
825
+ return configureCodex(bridge, tool);
826
+ case "zed":
827
+ return configureZed(bridge, tool);
683
828
  default:
684
829
  return { success: false, message: `Unknown tool: ${tool}` };
685
830
  }
686
831
  }
687
- function resolveMcpBridge() {
832
+ function resolveMcpBridge(tool) {
688
833
  return {
689
834
  command: "orkestrate",
690
- args: ["mcp"]
835
+ args: ["mcp", "--parent-tool", tool]
691
836
  };
692
837
  }
693
838
  function configureClaudeCode(bridge) {
694
- if (!isCommandAvailable("claude")) return { success: false, message: "Claude Code CLI not found." };
839
+ if (!isCommandAvailable("claude"))
840
+ return { success: false, message: "Claude Code CLI not found." };
695
841
  try {
696
- try {
697
- execSync("claude mcp remove Orkestrate", { stdio: "pipe" });
698
- } catch {
699
- }
700
842
  const commandToRun = bridge.command;
701
843
  const argsToRun = bridge.args.join(" ");
844
+ const mcpJsonPath = join(process.cwd(), ".mcp.json");
845
+ if (existsSync2(mcpJsonPath)) {
846
+ try {
847
+ const mcpConfig = JSON.parse(readFileSync2(mcpJsonPath, "utf-8"));
848
+ if (mcpConfig.mcpServers?.Orkestrate) {
849
+ delete mcpConfig.mcpServers.Orkestrate;
850
+ writeFileSync2(
851
+ mcpJsonPath,
852
+ JSON.stringify(mcpConfig, null, 2) + "\n",
853
+ "utf-8"
854
+ );
855
+ }
856
+ } catch {
857
+ }
858
+ }
702
859
  execSync(
703
- `claude mcp add --transport stdio --scope project Orkestrate ${commandToRun} ${argsToRun}`,
860
+ `claude mcp add --transport stdio --scope project Orkestrate -- ${commandToRun} ${argsToRun}`,
704
861
  { stdio: "pipe", encoding: "utf-8" }
705
862
  );
706
863
  return { success: true, message: "MCP added to Claude Code." };
707
864
  } catch (err) {
708
- return { success: false, message: `Claude config failed: ${err instanceof Error ? err.message : String(err)}` };
865
+ return {
866
+ success: false,
867
+ message: `Claude config failed: ${err instanceof Error ? err.message : String(err)}`
868
+ };
709
869
  }
710
870
  }
711
- function configureOpenCode(configPath, bridge) {
871
+ function configureOpenCode(configPath, bridge, tool) {
712
872
  try {
713
- let config2 = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : {};
873
+ const config2 = existsSync2(configPath) ? JSON.parse(readFileSync2(configPath, "utf-8")) : {};
714
874
  if (!config2.mcp || typeof config2.mcp !== "object") config2.mcp = {};
715
875
  config2.mcp["Orkestrate"] = {
716
876
  type: "local",
717
877
  command: [bridge.command, ...bridge.args],
718
878
  enabled: true
719
879
  };
720
- writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
880
+ writeFileSync2(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
721
881
  return { success: true, message: `Configured OpenCode at ${configPath}` };
722
882
  } catch (err) {
723
- return { success: false, message: `OpenCode config failed: ${err instanceof Error ? err.message : String(err)}` };
883
+ return {
884
+ success: false,
885
+ message: `OpenCode config failed: ${err instanceof Error ? err.message : String(err)}`
886
+ };
724
887
  }
725
888
  }
726
- function configureMcpServersJson(displayName, configPath, bridge) {
889
+ function configureMcpServersJson(displayName, configPath, bridge, tool) {
727
890
  try {
728
891
  const dir = join(configPath, "..");
729
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
730
- let config2 = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : {};
731
- if (!config2.mcpServers || typeof config2.mcpServers !== "object") config2.mcpServers = {};
892
+ if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
893
+ const config2 = existsSync2(configPath) ? JSON.parse(readFileSync2(configPath, "utf-8")) : {};
894
+ if (!config2.mcpServers || typeof config2.mcpServers !== "object")
895
+ config2.mcpServers = {};
732
896
  config2.mcpServers["Orkestrate"] = {
733
897
  command: bridge.command,
734
898
  args: bridge.args
735
899
  };
736
- writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
737
- return { success: true, message: `Configured ${displayName} at ${configPath}` };
900
+ writeFileSync2(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
901
+ return {
902
+ success: true,
903
+ message: `Configured ${displayName} at ${configPath}`
904
+ };
905
+ } catch (err) {
906
+ return {
907
+ success: false,
908
+ message: `${displayName} config failed: ${err instanceof Error ? err.message : String(err)}`
909
+ };
910
+ }
911
+ }
912
+ function configureZed(bridge, tool) {
913
+ const configPath = getZedConfigPath();
914
+ try {
915
+ const dir = join(configPath, "..");
916
+ if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
917
+ const serverEntry = {
918
+ enabled: true,
919
+ remote: false,
920
+ command: bridge.command,
921
+ args: bridge.args
922
+ };
923
+ let config2 = {};
924
+ if (existsSync2(configPath)) {
925
+ const content = readFileSync2(configPath, "utf-8").trim();
926
+ const jsonStart = content.indexOf("{");
927
+ const trimmedContent = jsonStart >= 0 ? content.slice(jsonStart) : "";
928
+ if (trimmedContent) {
929
+ try {
930
+ config2 = new Function("return " + trimmedContent)();
931
+ } catch {
932
+ return {
933
+ success: false,
934
+ message: "Zed config is not valid JSON - manual configuration required"
935
+ };
936
+ }
937
+ }
938
+ }
939
+ if (!config2.context_servers || typeof config2.context_servers !== "object") {
940
+ config2.context_servers = {};
941
+ }
942
+ config2.context_servers["mcp-server-orkestrate"] = serverEntry;
943
+ writeFileSync2(configPath, JSON.stringify(config2, null, 2), "utf-8");
944
+ return {
945
+ success: true,
946
+ message: `Configured Zed at ${configPath}`
947
+ };
738
948
  } catch (err) {
739
- return { success: false, message: `${displayName} config failed: ${err instanceof Error ? err.message : String(err)}` };
949
+ return {
950
+ success: false,
951
+ message: `Zed config failed: ${err instanceof Error ? err.message : String(err)}`
952
+ };
740
953
  }
741
954
  }
742
- function configureCodex(bridge) {
955
+ function configureCodex(bridge, tool) {
743
956
  const home = homedir();
744
957
  const codexDir = join(home, ".codex");
745
958
  const configPath = join(codexDir, "config.toml");
746
959
  try {
747
- if (!existsSync(codexDir)) mkdirSync(codexDir, { recursive: true });
748
- let content = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "[mcp_servers]\n";
960
+ if (!existsSync2(codexDir)) mkdirSync2(codexDir, { recursive: true });
961
+ let content = existsSync2(configPath) ? readFileSync2(configPath, "utf-8") : "[mcp_servers]\n";
749
962
  const proxySection = `[mcp_servers.Orkestrate]
750
963
  command = "${bridge.command}"
751
964
  args = [${bridge.args.map((a) => `"${a}"`).join(", ")}]
@@ -763,10 +976,13 @@ args = [${bridge.args.map((a) => `"${a}"`).join(", ")}]
763
976
  content = content.trimEnd() + "\n\n[mcp_servers]\n\n" + proxySection;
764
977
  }
765
978
  }
766
- writeFileSync(configPath, content, "utf-8");
979
+ writeFileSync2(configPath, content, "utf-8");
767
980
  return { success: true, message: `Configured Codex.` };
768
981
  } catch (err) {
769
- return { success: false, message: `Codex config failed: ${err instanceof Error ? err.message : String(err)}` };
982
+ return {
983
+ success: false,
984
+ message: `Codex config failed: ${err instanceof Error ? err.message : String(err)}`
985
+ };
770
986
  }
771
987
  }
772
988
  function isCommandAvailable(command) {
@@ -780,12 +996,11 @@ function isCommandAvailable(command) {
780
996
  }
781
997
  }
782
998
  function getToolNames() {
783
- return ["claude", "opencode", "cursor", "windsurf", "codex"];
999
+ return ["claude", "opencode", "cursor", "windsurf", "codex", "zed"];
784
1000
  }
785
1001
  var init_detect = __esm({
786
1002
  "src/lib/detect.ts"() {
787
1003
  "use strict";
788
- init_config();
789
1004
  }
790
1005
  });
791
1006
 
@@ -845,7 +1060,6 @@ async function connectCommand(toolName) {
845
1060
  }
846
1061
  }
847
1062
  function displayGenericInstructions() {
848
- const mcpUrl = `${getServerUrl()}/api/mcp`;
849
1063
  const bridgeCommand = "orkestrate";
850
1064
  const bridgeArgs = "mcp";
851
1065
  ui.info("Generic MCP Setup (for any tool that supports stdio MCP):");
@@ -882,7 +1096,12 @@ function displayGenericInstructions() {
882
1096
  ui.line(` command = "${bridgeCommand}"`);
883
1097
  ui.line(` args = ["${bridgeArgs}"]`);
884
1098
  ui.blank();
885
- ui.dim(
1099
+ ui.line(" Zed:");
1100
+ ui.line(" Add to settings.json under context_servers:");
1101
+ ui.line(" ~/.config/zed/settings.json (Linux/macOS)");
1102
+ ui.line(" ~/AppData/Roaming/Zed/settings.json (Windows)");
1103
+ ui.blank();
1104
+ ui.line(
886
1105
  " Not sure if your tool supports MCP? Check its docs for 'MCP server'."
887
1106
  );
888
1107
  }
@@ -898,7 +1117,8 @@ var init_connect = __esm({
898
1117
  "opencode",
899
1118
  "cursor",
900
1119
  "windsurf",
901
- "codex"
1120
+ "codex",
1121
+ "zed"
902
1122
  ];
903
1123
  }
904
1124
  });
@@ -909,6 +1129,7 @@ __export(api_exports, {
909
1129
  ApiError: () => ApiError,
910
1130
  checkHealth: () => checkHealth,
911
1131
  createWorkspace: () => createWorkspace,
1132
+ getGithubStatus: () => getGithubStatus,
912
1133
  getMe: () => getMe,
913
1134
  getTeamStatus: () => getTeamStatus,
914
1135
  listWorkspaces: () => listWorkspaces,
@@ -937,7 +1158,7 @@ async function request(method, path, body) {
937
1158
  method,
938
1159
  headers: {
939
1160
  ...headers,
940
- "Accept": "application/json",
1161
+ Accept: "application/json",
941
1162
  "User-Agent": "Orkestrate-CLI-v1"
942
1163
  },
943
1164
  body: body ? JSON.stringify(body) : void 0
@@ -952,8 +1173,18 @@ async function getMe() {
952
1173
  const data = await request("GET", "/api/auth/me");
953
1174
  return data.user;
954
1175
  }
1176
+ async function getGithubStatus() {
1177
+ const data = await request(
1178
+ "GET",
1179
+ "/api/github/status"
1180
+ );
1181
+ return { connected: Boolean(data.connected) };
1182
+ }
955
1183
  async function listWorkspaces() {
956
- const data = await request("GET", "/api/workspaces");
1184
+ const data = await request(
1185
+ "GET",
1186
+ "/api/workspaces"
1187
+ );
957
1188
  return data.workspaces || [];
958
1189
  }
959
1190
  async function switchWorkspace(workspaceId) {
@@ -963,12 +1194,16 @@ async function switchWorkspace(workspaceId) {
963
1194
  });
964
1195
  }
965
1196
  async function createWorkspace(name, repoUrl, defaultBranch) {
966
- const data = await request("POST", "/api/workspaces", {
967
- action: "create",
968
- name,
969
- repoUrl,
970
- defaultBranch
971
- });
1197
+ const data = await request(
1198
+ "POST",
1199
+ "/api/workspaces",
1200
+ {
1201
+ action: "create",
1202
+ name,
1203
+ repoUrl,
1204
+ defaultBranch
1205
+ }
1206
+ );
972
1207
  return data.workspace;
973
1208
  }
974
1209
  async function getTeamStatus() {
@@ -1143,7 +1378,9 @@ async function workspaceCommand(action, nameOrId, extra) {
1143
1378
  case "create":
1144
1379
  case "new":
1145
1380
  if (!nameOrId) {
1146
- ui.error("Usage: orkestrate workspace create <name> <repo-url> [branch]");
1381
+ ui.error(
1382
+ "Usage: orkestrate workspace create <name> <repo-url> [branch]"
1383
+ );
1147
1384
  process.exit(1);
1148
1385
  }
1149
1386
  return workspaceCreate(nameOrId, extra);
@@ -1164,7 +1401,9 @@ async function workspaceList() {
1164
1401
  const active = getActiveWorkspace();
1165
1402
  if (workspaces.length === 0) {
1166
1403
  ui.dim("No workspaces found.");
1167
- ui.info("Create one at orkestrate.space or run `orkestrate workspace create`.");
1404
+ ui.info(
1405
+ "Create one at orkestrate.space or run `orkestrate workspace create`."
1406
+ );
1168
1407
  return;
1169
1408
  }
1170
1409
  for (const ws of workspaces) {
@@ -1183,7 +1422,9 @@ async function workspaceList() {
1183
1422
  ui.blank();
1184
1423
  ui.dim(`${workspaces.length} workspace(s)`);
1185
1424
  } catch (err) {
1186
- ui.error(`Failed to list workspaces: ${err instanceof Error ? err.message : String(err)}`);
1425
+ ui.error(
1426
+ `Failed to list workspaces: ${err instanceof Error ? err.message : String(err)}`
1427
+ );
1187
1428
  process.exit(1);
1188
1429
  }
1189
1430
  }
@@ -1206,11 +1447,32 @@ async function workspaceSwitch(nameOrId) {
1206
1447
  setActiveWorkspace(target.id, target.name);
1207
1448
  ui.success(`Switched to workspace: ${target.name}`);
1208
1449
  } catch (err) {
1209
- ui.error(`Failed to switch workspace: ${err instanceof Error ? err.message : String(err)}`);
1450
+ ui.error(
1451
+ `Failed to switch workspace: ${err instanceof Error ? err.message : String(err)}`
1452
+ );
1210
1453
  process.exit(1);
1211
1454
  }
1212
1455
  }
1213
1456
  async function workspaceCreate(name, repoUrl) {
1457
+ const localGithubTokenAvailable = hasGithubToken();
1458
+ let serverGithubConnected = false;
1459
+ try {
1460
+ const status = await getGithubStatus();
1461
+ serverGithubConnected = status.connected;
1462
+ } catch (err) {
1463
+ ui.error(
1464
+ `Failed to verify GitHub connection with server: ${err instanceof Error ? err.message : String(err)}`
1465
+ );
1466
+ ui.info("Please re-run `orkestrate login` and ensure GitHub is connected.");
1467
+ process.exit(1);
1468
+ }
1469
+ if (!localGithubTokenAvailable || !serverGithubConnected) {
1470
+ ui.error("GitHub is not connected. Workspace creation is blocked.");
1471
+ ui.info(
1472
+ "Run `orkestrate login` and complete GitHub connection, then try again."
1473
+ );
1474
+ process.exit(1);
1475
+ }
1214
1476
  if (!repoUrl) {
1215
1477
  ui.error("Repository URL is required.");
1216
1478
  ui.info("Usage: orkestrate workspace create <name> <repo-url>");
@@ -1223,7 +1485,9 @@ async function workspaceCreate(name, repoUrl) {
1223
1485
  ui.kv("ID", workspace.id);
1224
1486
  ui.kv("Repo", repoUrl);
1225
1487
  } catch (err) {
1226
- ui.error(`Failed to create workspace: ${err instanceof Error ? err.message : String(err)}`);
1488
+ ui.error(
1489
+ `Failed to create workspace: ${err instanceof Error ? err.message : String(err)}`
1490
+ );
1227
1491
  process.exit(1);
1228
1492
  }
1229
1493
  }
@@ -1252,7 +1516,15 @@ function git(cmd, cwd) {
1252
1516
  function detectGitContext(cwd = process.cwd()) {
1253
1517
  const repoRoot = git("rev-parse --show-toplevel", cwd);
1254
1518
  if (!repoRoot) return null;
1255
- const remote = git("remote get-url origin", cwd);
1519
+ const remotes = git("remote", cwd).split("\n").filter(Boolean);
1520
+ let remote = "";
1521
+ for (const r of remotes) {
1522
+ const url = git(`remote get-url ${r}`, cwd);
1523
+ if (url) {
1524
+ remote = url;
1525
+ break;
1526
+ }
1527
+ }
1256
1528
  const branch = git("rev-parse --abbrev-ref HEAD", cwd);
1257
1529
  const headSha = git("rev-parse HEAD", cwd);
1258
1530
  const status = git("status --porcelain", cwd);
@@ -1276,7 +1548,7 @@ var init_exports = {};
1276
1548
  __export(init_exports, {
1277
1549
  initCommand: () => initCommand
1278
1550
  });
1279
- import { existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
1551
+ import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
1280
1552
  import { join as join2 } from "path";
1281
1553
  async function initCommand() {
1282
1554
  const creds = getCredentials();
@@ -1386,7 +1658,7 @@ async function initCommand() {
1386
1658
  }
1387
1659
  const configFile = join2(cwd, ".orkestrate.json");
1388
1660
  const activeWs = getActiveWorkspace();
1389
- if (!existsSync2(configFile)) {
1661
+ if (!existsSync3(configFile)) {
1390
1662
  if (activeWs.id) {
1391
1663
  const projectConfig = {
1392
1664
  $schema: "https://orkestrate.space/schema/project.json",
@@ -1394,18 +1666,18 @@ async function initCommand() {
1394
1666
  server: "https://orkestrate.space",
1395
1667
  initialized: (/* @__PURE__ */ new Date()).toISOString()
1396
1668
  };
1397
- writeFileSync2(configFile, JSON.stringify(projectConfig, null, 2) + "\n", "utf-8");
1669
+ writeFileSync3(configFile, JSON.stringify(projectConfig, null, 2) + "\n", "utf-8");
1398
1670
  ui.blank();
1399
1671
  ui.success(`Created .orkestrate.json`);
1400
1672
  ui.dim("This file identifies your project workspace. It is gitignored by default.");
1401
1673
  }
1402
1674
  } else {
1403
1675
  try {
1404
- const { readFileSync: readFileSync2 } = await import("fs");
1405
- const existing = JSON.parse(readFileSync2(configFile, "utf-8"));
1676
+ const { readFileSync: readFileSync3 } = await import("fs");
1677
+ const existing = JSON.parse(readFileSync3(configFile, "utf-8"));
1406
1678
  if (activeWs.id && existing.workspaceId !== activeWs.id) {
1407
1679
  existing.workspaceId = activeWs.id;
1408
- writeFileSync2(configFile, JSON.stringify(existing, null, 2) + "\n", "utf-8");
1680
+ writeFileSync3(configFile, JSON.stringify(existing, null, 2) + "\n", "utf-8");
1409
1681
  ui.success(`Updated .orkestrate.json with workspace ID ${activeWs.id}`);
1410
1682
  }
1411
1683
  } catch {
@@ -1441,33 +1713,36 @@ var mcp_exports = {};
1441
1713
  __export(mcp_exports, {
1442
1714
  mcpCommand: () => mcpCommand
1443
1715
  });
1444
- async function mcpCommand() {
1716
+ async function mcpCommand(opts) {
1717
+ const PARENT_TOOL = opts.parentTool ?? null;
1445
1718
  try {
1446
1719
  const serverUrl = getServerUrl();
1447
1720
  const mcpUrl = `${serverUrl}/api/mcp`;
1448
1721
  process.stdin.setEncoding("utf-8");
1449
1722
  let buffer = "";
1450
- process.stderr.write(`[Orkestrate-MCP] Starting bridge to ${mcpUrl}
1451
- `);
1723
+ const startupMsg = `[Orkestrate-MCP] Starting bridge to ${mcpUrl}${PARENT_TOOL ? ` (parent: ${PARENT_TOOL})` : ""}
1724
+ `;
1725
+ process.stderr.write(startupMsg);
1452
1726
  process.stdin.on("data", async (chunk) => {
1453
1727
  const rawChunk = String(chunk);
1454
- if (process.env.DEBUG) process.stderr.write(`[Orkestrate-MCP] Received chunk: ${rawChunk}
1728
+ if (process.env.DEBUG)
1729
+ process.stderr.write(`[Orkestrate-MCP] Received chunk: ${rawChunk}
1455
1730
  `);
1456
1731
  buffer += rawChunk;
1457
1732
  let lineEndIndex;
1458
1733
  while ((lineEndIndex = buffer.indexOf("\n")) >= 0) {
1459
- let line = buffer.slice(0, lineEndIndex).trim();
1734
+ const line = buffer.slice(0, lineEndIndex).trim();
1460
1735
  buffer = buffer.slice(lineEndIndex + 1);
1461
1736
  if (!line) continue;
1462
1737
  if (line.includes("}{")) {
1463
1738
  const parts = line.split("}{");
1464
- await processLine(parts[0] + "}", mcpUrl);
1739
+ await processLine(parts[0] + "}", mcpUrl, PARENT_TOOL);
1465
1740
  for (let i = 1; i < parts.length - 1; i++) {
1466
- await processLine("{" + parts[i] + "}", mcpUrl);
1741
+ await processLine("{" + parts[i] + "}", mcpUrl, PARENT_TOOL);
1467
1742
  }
1468
- await processLine("{" + parts[parts.length - 1], mcpUrl);
1743
+ await processLine("{" + parts[parts.length - 1], mcpUrl, PARENT_TOOL);
1469
1744
  } else {
1470
- await processLine(line, mcpUrl);
1745
+ await processLine(line, mcpUrl, PARENT_TOOL);
1471
1746
  }
1472
1747
  }
1473
1748
  });
@@ -1483,14 +1758,16 @@ async function mcpCommand() {
1483
1758
  process.exit(1);
1484
1759
  }
1485
1760
  }
1486
- async function processLine(line, mcpUrl) {
1487
- if (process.env.DEBUG) process.stderr.write(`[Orkestrate-MCP] Processing line: ${line}
1761
+ async function processLine(line, mcpUrl, parentTool) {
1762
+ if (process.env.DEBUG)
1763
+ process.stderr.write(`[Orkestrate-MCP] Processing line: ${line}
1488
1764
  `);
1489
1765
  let payload;
1490
1766
  try {
1491
1767
  payload = JSON.parse(line);
1492
1768
  } catch {
1493
- if (process.env.DEBUG) process.stderr.write(`[Orkestrate-MCP] JSON Parse failed for: ${line}
1769
+ if (process.env.DEBUG)
1770
+ process.stderr.write(`[Orkestrate-MCP] JSON Parse failed for: ${line}
1494
1771
  `);
1495
1772
  return;
1496
1773
  }
@@ -1499,47 +1776,100 @@ async function processLine(line, mcpUrl) {
1499
1776
  try {
1500
1777
  const token = await getValidToken();
1501
1778
  if (!token) {
1502
- throw new Error("NOT_LOGGED_IN: Please run 'orkestrate login' to authenticate.");
1779
+ throw new Error(
1780
+ "NOT_LOGGED_IN: Please run 'orkestrate login' to authenticate."
1781
+ );
1503
1782
  }
1783
+ const headers = {
1784
+ "Content-Type": "application/json",
1785
+ Authorization: `Bearer ${token}`,
1786
+ "User-Agent": "Orkestrate-CLI-Proxy"
1787
+ };
1788
+ const enrichedPayload = parentTool ? { ...payload, parentTool } : payload;
1504
1789
  const res = await fetch(mcpUrl, {
1505
1790
  method: "POST",
1506
- headers: {
1507
- "Content-Type": "application/json",
1508
- "Authorization": `Bearer ${token}`,
1509
- "User-Agent": "Orkestrate-CLI-Proxy"
1510
- },
1511
- body: line
1791
+ headers,
1792
+ body: JSON.stringify(enrichedPayload)
1512
1793
  });
1513
1794
  if (isNotification) return;
1514
1795
  const responseBody = await res.text();
1515
1796
  if (!res.ok) {
1516
- process.stderr.write(`[Orkestrate-MCP] Backend error (${res.status}): ${responseBody}
1517
- `);
1797
+ process.stderr.write(
1798
+ `[Orkestrate-MCP] Backend error (${res.status}): ${responseBody}
1799
+ `
1800
+ );
1518
1801
  if (!isNotification) {
1519
- process.stdout.write(JSON.stringify({
1520
- jsonrpc: "2.0",
1521
- id: requestId,
1522
- error: {
1523
- code: -32603,
1524
- message: `Orkestrate Cloud Error (${res.status}): ${responseBody || "Unauthorized"}. Please try 'orkestrate login'.`
1525
- }
1526
- }) + "\n");
1802
+ process.stdout.write(
1803
+ JSON.stringify({
1804
+ jsonrpc: "2.0",
1805
+ id: requestId,
1806
+ error: {
1807
+ code: -32603,
1808
+ message: `Orkestrate Cloud Error (${res.status}): ${responseBody || "Unauthorized"}. Please try 'orkestrate login'.`
1809
+ }
1810
+ }) + "\n"
1811
+ );
1527
1812
  }
1528
1813
  return;
1529
1814
  }
1530
1815
  if (responseBody) {
1531
- process.stdout.write(responseBody + "\n");
1816
+ if (payload.method === "tools/list") {
1817
+ const filtered = filterToolsList(responseBody);
1818
+ process.stdout.write(filtered + "\n");
1819
+ } else {
1820
+ if (payload.method === "tools/call") {
1821
+ const toolName = payload.params?.name;
1822
+ if (toolName && !isToolAllowed(toolName)) {
1823
+ process.stdout.write(
1824
+ JSON.stringify({
1825
+ jsonrpc: "2.0",
1826
+ id: requestId,
1827
+ error: {
1828
+ code: -32600,
1829
+ message: `Tool '${toolName}' is disabled. Use 'orkestrate tools --list' to see available tools and 'orkestrate tools --enable ${toolName}' to enable it.`
1830
+ }
1831
+ }) + "\n"
1832
+ );
1833
+ return;
1834
+ }
1835
+ }
1836
+ process.stdout.write(responseBody + "\n");
1837
+ }
1532
1838
  }
1533
1839
  } catch (err) {
1534
1840
  if (isNotification) return;
1535
- process.stdout.write(JSON.stringify({
1536
- jsonrpc: "2.0",
1537
- id: requestId,
1538
- error: {
1539
- code: -32603,
1540
- message: err instanceof Error ? err.message : String(err)
1541
- }
1542
- }) + "\n");
1841
+ process.stdout.write(
1842
+ JSON.stringify({
1843
+ jsonrpc: "2.0",
1844
+ id: requestId,
1845
+ error: {
1846
+ code: -32603,
1847
+ message: err instanceof Error ? err.message : String(err)
1848
+ }
1849
+ }) + "\n"
1850
+ );
1851
+ }
1852
+ }
1853
+ function filterToolsList(responseBody) {
1854
+ try {
1855
+ const parsed = JSON.parse(responseBody);
1856
+ if (!parsed.result?.tools) return responseBody;
1857
+ const enabledTools = getEnabledTools();
1858
+ const disabledTools = getDisabledTools();
1859
+ let filteredTools = parsed.result.tools;
1860
+ if (enabledTools !== null) {
1861
+ filteredTools = filteredTools.filter(
1862
+ (tool) => enabledTools.includes(tool.name)
1863
+ );
1864
+ } else {
1865
+ filteredTools = filteredTools.filter(
1866
+ (tool) => !disabledTools.includes(tool.name)
1867
+ );
1868
+ }
1869
+ parsed.result.tools = filteredTools;
1870
+ return JSON.stringify(parsed);
1871
+ } catch {
1872
+ return responseBody;
1543
1873
  }
1544
1874
  }
1545
1875
  var init_mcp = __esm({
@@ -1609,11 +1939,82 @@ var init_whoami = __esm({
1609
1939
  }
1610
1940
  });
1611
1941
 
1942
+ // src/commands/tools.ts
1943
+ var tools_exports = {};
1944
+ __export(tools_exports, {
1945
+ toolsCommand: () => toolsCommand
1946
+ });
1947
+ async function toolsCommand(opts) {
1948
+ const { list, enable, disable } = opts;
1949
+ if (list) {
1950
+ const enabledTools = getEnabledTools();
1951
+ const disabledTools = getDisabledTools();
1952
+ ui.header("Tool Filter Settings");
1953
+ if (enabledTools !== null) {
1954
+ ui.info(`Mode: Additive (only listed tools are enabled)`);
1955
+ if (enabledTools.length === 0) {
1956
+ ui.dim(" No tools enabled");
1957
+ } else {
1958
+ enabledTools.forEach((tool) => {
1959
+ ui.success(tool);
1960
+ });
1961
+ }
1962
+ } else {
1963
+ ui.info(`Mode: Subtractive (all tools except disabled)`);
1964
+ if (disabledTools.length === 0) {
1965
+ ui.dim(" No tools disabled");
1966
+ } else {
1967
+ disabledTools.forEach((tool) => {
1968
+ ui.error(tool);
1969
+ });
1970
+ }
1971
+ }
1972
+ console.log();
1973
+ ui.dim(` Use 'orkestrate tools --enable <tool>' to enable a tool`);
1974
+ ui.dim(` Use 'orkestrate tools --disable <tool>' to disable a tool`);
1975
+ return;
1976
+ }
1977
+ if (enable) {
1978
+ const toolName = enable;
1979
+ if (isToolAllowed(toolName)) {
1980
+ ui.warn(`Tool '${toolName}' is already enabled`);
1981
+ } else {
1982
+ enableTool(toolName);
1983
+ ui.success(`Enabled tool: ${toolName}`);
1984
+ }
1985
+ return;
1986
+ }
1987
+ if (disable) {
1988
+ const toolName = disable;
1989
+ if (!isToolAllowed(toolName)) {
1990
+ ui.warn(`Tool '${toolName}' is already disabled`);
1991
+ } else {
1992
+ disableTool(toolName);
1993
+ ui.success(`Disabled tool: ${toolName}`);
1994
+ }
1995
+ return;
1996
+ }
1997
+ ui.header("Orkestrate Tools Management");
1998
+ ui.info("Manage which tools are available through the local MCP proxy");
1999
+ console.log();
2000
+ ui.dim("Usage:");
2001
+ ui.dim(" orkestrate tools --list Show current settings");
2002
+ ui.dim(" orkestrate tools --enable <tool> Enable a specific tool");
2003
+ ui.dim(" orkestrate tools --disable <tool> Disable a specific tool");
2004
+ }
2005
+ var init_tools = __esm({
2006
+ "src/commands/tools.ts"() {
2007
+ "use strict";
2008
+ init_config();
2009
+ init_ui();
2010
+ }
2011
+ });
2012
+
1612
2013
  // src/cli.ts
1613
2014
  init_ui();
1614
2015
  import { Command } from "commander";
1615
2016
  var program = new Command();
1616
- program.name("orkestrate").description("The coordination layer for autonomous AI coding agents").version("0.1.12").hook("preAction", () => {
2017
+ program.name("orkestrate").description("The coordination layer for autonomous AI coding agents").version("0.1.15").hook("preAction", () => {
1617
2018
  });
1618
2019
  program.command("login").description("Authenticate with Orkestrate via browser OAuth").action(async () => {
1619
2020
  const { loginCommand: loginCommand2 } = await Promise.resolve().then(() => (init_login(), login_exports));
@@ -1623,7 +2024,9 @@ program.command("logout").description("Clear stored credentials").action(async (
1623
2024
  const { logoutCommand: logoutCommand2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
1624
2025
  logoutCommand2();
1625
2026
  });
1626
- program.command("connect [tool]").description("Configure MCP endpoint for an AI coding tool (claude, opencode, cursor, windsurf, codex)").action(async (tool) => {
2027
+ program.command("connect [tool]").description(
2028
+ "Configure MCP endpoint for an AI coding tool (claude, opencode, cursor, windsurf, codex)"
2029
+ ).action(async (tool) => {
1627
2030
  const { connectCommand: connectCommand2 } = await Promise.resolve().then(() => (init_connect(), connect_exports));
1628
2031
  await connectCommand2(tool);
1629
2032
  });
@@ -1639,13 +2042,18 @@ program.command("init").description("Initialize Orkestrate in the current projec
1639
2042
  const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
1640
2043
  await initCommand2();
1641
2044
  });
1642
- program.command("mcp").description("Run as a local MCP server (stdio bridge to Orkestrate Cloud)").action(async () => {
2045
+ program.command("mcp").description("Run as a local MCP server (stdio bridge to Orkestrate Cloud)").option(
2046
+ "--parent-tool <tool>",
2047
+ "The AI coding tool invoking this bridge (claude, zed, cursor, etc.)"
2048
+ ).action(async (opts) => {
1643
2049
  try {
1644
2050
  const { mcpCommand: mcpCommand2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
1645
- await mcpCommand2();
2051
+ await mcpCommand2(opts);
1646
2052
  } catch (err) {
1647
- process.stderr.write(`[Orkestrate-MCP] ${err instanceof Error ? err.message : String(err)}
1648
- `);
2053
+ process.stderr.write(
2054
+ `[Orkestrate-MCP] ${err instanceof Error ? err.message : String(err)}
2055
+ `
2056
+ );
1649
2057
  process.exit(1);
1650
2058
  }
1651
2059
  });
@@ -1653,6 +2061,10 @@ program.command("whoami").description("Show current authentication and configura
1653
2061
  const { whoamiCommand: whoamiCommand2 } = await Promise.resolve().then(() => (init_whoami(), whoami_exports));
1654
2062
  await whoamiCommand2();
1655
2063
  });
2064
+ program.command("tools").description("Manage enabled/disabled tools for local MCP proxy").option("--list", "List current tool filter settings").option("--enable <tool>", "Enable a specific tool").option("--disable <tool>", "Disable a specific tool").action(async (opts) => {
2065
+ const { toolsCommand: toolsCommand2 } = await Promise.resolve().then(() => (init_tools(), tools_exports));
2066
+ await toolsCommand2(opts);
2067
+ });
1656
2068
  program.action(() => {
1657
2069
  ui.banner();
1658
2070
  program.help();