@synsci/thesis 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/agents.mjs ADDED
@@ -0,0 +1,259 @@
1
+ import { access } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ export const SERVER_NAME = "thesis";
6
+ export const ALL_HOST_NAMES = [
7
+ "claude",
8
+ "opencode",
9
+ "codex",
10
+ "cursor",
11
+ "pi-mono",
12
+ "hermes-agent",
13
+ "openclaw",
14
+ ];
15
+ export const SETUP_HOST_NAMES = {
16
+ claude: "Claude Code",
17
+ opencode: "OpenCode",
18
+ codex: "Codex",
19
+ cursor: "Cursor",
20
+ "pi-mono": "Pi (pi-mono)",
21
+ "hermes-agent": "Hermes Agent",
22
+ openclaw: "OpenClaw",
23
+ };
24
+
25
+ const HOME = os.homedir();
26
+
27
+ const hosts = {
28
+ claude: {
29
+ name: "claude",
30
+ displayName: "Claude Code",
31
+ mcp: {
32
+ configType: "json",
33
+ projectPaths: [".mcp.json"],
34
+ globalPaths: [path.join(HOME, ".claude.json")],
35
+ configKey: "mcpServers",
36
+ buildEntry: ({ serverUrl, apiKey }) => ({
37
+ type: "http",
38
+ url: serverUrl,
39
+ headers: {
40
+ Authorization: `Bearer ${apiKey}`,
41
+ },
42
+ }),
43
+ },
44
+ detect: {
45
+ projectPaths: [".mcp.json", ".claude"],
46
+ globalPaths: [
47
+ path.join(HOME, ".claude.json"),
48
+ path.join(HOME, ".claude"),
49
+ ],
50
+ },
51
+ },
52
+ opencode: {
53
+ name: "opencode",
54
+ displayName: "OpenCode",
55
+ mcp: {
56
+ configType: "json",
57
+ projectPaths: [
58
+ "opencode.json",
59
+ "opencode.jsonc",
60
+ ".opencode.json",
61
+ ".opencode.jsonc",
62
+ ],
63
+ globalPaths: [
64
+ path.join(HOME, ".config", "opencode", "opencode.json"),
65
+ path.join(HOME, ".config", "opencode", "opencode.jsonc"),
66
+ path.join(HOME, ".config", "opencode", ".opencode.json"),
67
+ path.join(HOME, ".config", "opencode", ".opencode.jsonc"),
68
+ ],
69
+ configKey: "mcp",
70
+ buildEntry: ({ serverUrl, apiKey }) => ({
71
+ type: "remote",
72
+ url: serverUrl,
73
+ enabled: true,
74
+ headers: {
75
+ Authorization: `Bearer ${apiKey}`,
76
+ },
77
+ }),
78
+ },
79
+ detect: {
80
+ projectPaths: [
81
+ "opencode.json",
82
+ "opencode.jsonc",
83
+ ".opencode.json",
84
+ ".opencode.jsonc",
85
+ ],
86
+ globalPaths: [path.join(HOME, ".config", "opencode")],
87
+ },
88
+ },
89
+ codex: {
90
+ name: "codex",
91
+ displayName: "Codex",
92
+ mcp: {
93
+ configType: "toml",
94
+ projectPaths: [path.join(".codex", "config.toml")],
95
+ globalPaths: [path.join(HOME, ".codex", "config.toml")],
96
+ configKey: "mcp_servers",
97
+ buildEntry: ({ serverUrl, apiKey }) => ({
98
+ type: "http",
99
+ url: serverUrl,
100
+ headers: {
101
+ Authorization: `Bearer ${apiKey}`,
102
+ },
103
+ }),
104
+ },
105
+ detect: {
106
+ projectPaths: [".codex", path.join(".codex", "config.toml")],
107
+ globalPaths: [path.join(HOME, ".codex")],
108
+ },
109
+ },
110
+ cursor: {
111
+ name: "cursor",
112
+ displayName: "Cursor",
113
+ mcp: {
114
+ configType: "json",
115
+ projectPaths: [path.join(".cursor", "mcp.json")],
116
+ globalPaths: [path.join(HOME, ".cursor", "mcp.json")],
117
+ configKey: "mcpServers",
118
+ buildEntry: ({ serverUrl, apiKey }) => ({
119
+ url: serverUrl,
120
+ headers: {
121
+ Authorization: `Bearer ${apiKey}`,
122
+ },
123
+ }),
124
+ },
125
+ detect: {
126
+ projectPaths: [".cursor", path.join(".cursor", "mcp.json")],
127
+ globalPaths: [path.join(HOME, ".cursor")],
128
+ },
129
+ },
130
+ "pi-mono": {
131
+ name: "pi-mono",
132
+ displayName: "Pi (pi-mono)",
133
+ mcp: {
134
+ configType: "json",
135
+ projectPaths: [path.join(".pi", "settings.json")],
136
+ globalPaths: [path.join(HOME, ".pi", "agent", "settings.json")],
137
+ configKey: ["mcp", "servers"],
138
+ buildEntry: ({ serverUrl, apiKey }) => ({
139
+ url: serverUrl,
140
+ headers: {
141
+ Authorization: `Bearer ${apiKey}`,
142
+ },
143
+ }),
144
+ },
145
+ detect: {
146
+ projectPaths: [".pi", path.join(".pi", "settings.json")],
147
+ globalPaths: [path.join(HOME, ".pi"), path.join(HOME, ".pi", "agent")],
148
+ },
149
+ },
150
+ "hermes-agent": {
151
+ name: "hermes-agent",
152
+ displayName: "Hermes Agent",
153
+ mcp: {
154
+ configType: "yaml",
155
+ projectPaths: [path.join(".hermes", "config.yaml")],
156
+ globalPaths: [path.join(HOME, ".hermes", "config.yaml")],
157
+ configKey: "mcp_servers",
158
+ buildEntry: ({ serverUrl, apiKey }) => ({
159
+ url: serverUrl,
160
+ headers: {
161
+ Authorization: `Bearer ${apiKey}`,
162
+ },
163
+ }),
164
+ },
165
+ detect: {
166
+ projectPaths: [".hermes", path.join(".hermes", "config.yaml")],
167
+ globalPaths: [
168
+ path.join(HOME, ".hermes"),
169
+ path.join(HOME, ".hermes", "config.yaml"),
170
+ ],
171
+ },
172
+ },
173
+ openclaw: {
174
+ name: "openclaw",
175
+ displayName: "OpenClaw",
176
+ mcp: {
177
+ configType: "json",
178
+ projectPaths: [path.join(".openclaw", "openclaw.json")],
179
+ globalPaths: [path.join(HOME, ".openclaw", "openclaw.json")],
180
+ configKey: ["mcp", "servers"],
181
+ buildEntry: ({ serverUrl, apiKey }) => ({
182
+ url: serverUrl,
183
+ headers: {
184
+ Authorization: `Bearer ${apiKey}`,
185
+ },
186
+ }),
187
+ },
188
+ detect: {
189
+ projectPaths: [".openclaw", path.join(".openclaw", "openclaw.json")],
190
+ globalPaths: [
191
+ path.join(HOME, ".openclaw"),
192
+ path.join(HOME, ".openclaw", "openclaw.json"),
193
+ ],
194
+ },
195
+ },
196
+ };
197
+
198
+ export function getHost(name) {
199
+ const host = hosts[name];
200
+ if (!host) {
201
+ throw new Error(`Unsupported host: ${name}`);
202
+ }
203
+ return host;
204
+ }
205
+
206
+ async function pathExists(filePath) {
207
+ try {
208
+ await access(filePath);
209
+ return true;
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
214
+
215
+ export async function detectHosts(scope) {
216
+ const detected = [];
217
+ for (const hostName of ALL_HOST_NAMES) {
218
+ const host = getHost(hostName);
219
+ const candidates =
220
+ scope === "global" ? host.detect.globalPaths : host.detect.projectPaths;
221
+ for (const candidate of candidates) {
222
+ const fullPath =
223
+ scope === "global" ? candidate : path.join(process.cwd(), candidate);
224
+ // eslint-disable-next-line no-await-in-loop
225
+ if (await pathExists(fullPath)) {
226
+ detected.push(hostName);
227
+ break;
228
+ }
229
+ }
230
+ }
231
+ return detected;
232
+ }
233
+
234
+ export function normalizeScope(scope) {
235
+ if (scope === "global" || scope === "project") return scope;
236
+ return "global";
237
+ }
238
+
239
+ export function normalizeHosts(rawHosts) {
240
+ if (!Array.isArray(rawHosts) || rawHosts.length === 0) {
241
+ return [...ALL_HOST_NAMES];
242
+ }
243
+ const normalized = [];
244
+ for (const host of rawHosts) {
245
+ const candidate = String(host || "")
246
+ .trim()
247
+ .toLowerCase();
248
+ if (!candidate) continue;
249
+ if (!ALL_HOST_NAMES.includes(candidate)) {
250
+ throw new Error(
251
+ `Unsupported host '${candidate}'. Supported: ${ALL_HOST_NAMES.join(", ")}`,
252
+ );
253
+ }
254
+ if (!normalized.includes(candidate)) {
255
+ normalized.push(candidate);
256
+ }
257
+ }
258
+ return normalized.length > 0 ? normalized : [...ALL_HOST_NAMES];
259
+ }