onlymcp 0.16.4 → 0.19.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/LICENSE +33 -17
- package/README.md +7 -1
- package/dist/bin/cli.js +46 -564
- package/dist/bin/mcp.js +45 -66
- package/package.json +6 -10
- package/dist/apiBaseUrl.d.ts +0 -12
- package/dist/apiBaseUrl.js +0 -28
- package/dist/apiBaseUrl.js.map +0 -1
- package/dist/apiClient.d.ts +0 -296
- package/dist/apiClient.js +0 -224
- package/dist/apiClient.js.map +0 -1
- package/dist/auth/deviceLogin.d.ts +0 -5
- package/dist/auth/deviceLogin.js +0 -40
- package/dist/auth/deviceLogin.js.map +0 -1
- package/dist/auth/keyring.d.ts +0 -5
- package/dist/auth/keyring.js +0 -28
- package/dist/auth/keyring.js.map +0 -1
- package/dist/bin/cli.d.ts +0 -2
- package/dist/bin/cli.js.map +0 -1
- package/dist/bin/mcp.d.ts +0 -2
- package/dist/bin/mcp.js.map +0 -1
- package/dist/cache.d.ts +0 -7
- package/dist/cache.js +0 -32
- package/dist/cache.js.map +0 -1
- package/dist/deviceId.d.ts +0 -2
- package/dist/deviceId.js +0 -97
- package/dist/deviceId.js.map +0 -1
- package/dist/export/unpack.d.ts +0 -33
- package/dist/export/unpack.js +0 -54
- package/dist/export/unpack.js.map +0 -1
- package/dist/identity.d.ts +0 -29
- package/dist/identity.js +0 -116
- package/dist/identity.js.map +0 -1
- package/dist/import/collect.d.ts +0 -25
- package/dist/import/collect.js +0 -101
- package/dist/import/collect.js.map +0 -1
- package/dist/import/frontmatter.d.ts +0 -44
- package/dist/import/frontmatter.js +0 -93
- package/dist/import/frontmatter.js.map +0 -1
- package/dist/import/plan.d.ts +0 -74
- package/dist/import/plan.js +0 -190
- package/dist/import/plan.js.map +0 -1
- package/dist/import/prune.d.ts +0 -46
- package/dist/import/prune.js +0 -61
- package/dist/import/prune.js.map +0 -1
- package/dist/import/secrets.d.ts +0 -7
- package/dist/import/secrets.js +0 -77
- package/dist/import/secrets.js.map +0 -1
- package/dist/import/upload.d.ts +0 -21
- package/dist/import/upload.js +0 -99
- package/dist/import/upload.js.map +0 -1
- package/dist/install.d.ts +0 -81
- package/dist/install.js +0 -168
- package/dist/install.js.map +0 -1
- package/dist/instructions.d.ts +0 -4
- package/dist/instructions.js +0 -67
- package/dist/instructions.js.map +0 -1
- package/dist/memoryPath.d.ts +0 -1
- package/dist/memoryPath.js +0 -13
- package/dist/memoryPath.js.map +0 -1
- package/dist/memorySync.d.ts +0 -28
- package/dist/memorySync.js +0 -56
- package/dist/memorySync.js.map +0 -1
- package/dist/paths.d.ts +0 -16
- package/dist/paths.js +0 -58
- package/dist/paths.js.map +0 -1
- package/dist/registry/caveats.d.ts +0 -17
- package/dist/registry/caveats.js +0 -74
- package/dist/registry/caveats.js.map +0 -1
- package/dist/registry/clients.d.ts +0 -17
- package/dist/registry/clients.js +0 -402
- package/dist/registry/clients.js.map +0 -1
- package/dist/registry/index.d.ts +0 -6
- package/dist/registry/index.js +0 -7
- package/dist/registry/index.js.map +0 -1
- package/dist/registry/serverInfo.d.ts +0 -25
- package/dist/registry/serverInfo.js +0 -33
- package/dist/registry/serverInfo.js.map +0 -1
- package/dist/registry/servers.d.ts +0 -58
- package/dist/registry/servers.js +0 -259
- package/dist/registry/servers.js.map +0 -1
- package/dist/registry/toolHints.d.ts +0 -29
- package/dist/registry/toolHints.js +0 -41
- package/dist/registry/toolHints.js.map +0 -1
- package/dist/registry/types.d.ts +0 -71
- package/dist/registry/types.js +0 -6
- package/dist/registry/types.js.map +0 -1
- package/dist/skillFilePath.d.ts +0 -1
- package/dist/skillFilePath.js +0 -29
- package/dist/skillFilePath.js.map +0 -1
- package/dist/stubs.d.ts +0 -7
- package/dist/stubs.js +0 -159
- package/dist/stubs.js.map +0 -1
- package/dist/tools/connections.d.ts +0 -154
- package/dist/tools/connections.js +0 -164
- package/dist/tools/connections.js.map +0 -1
- package/dist/tools/getAgentDefinition.d.ts +0 -26
- package/dist/tools/getAgentDefinition.js +0 -29
- package/dist/tools/getAgentDefinition.js.map +0 -1
- package/dist/tools/getSkill.d.ts +0 -26
- package/dist/tools/getSkill.js +0 -26
- package/dist/tools/getSkill.js.map +0 -1
- package/dist/tools/getSkillFile.d.ts +0 -27
- package/dist/tools/getSkillFile.js +0 -39
- package/dist/tools/getSkillFile.js.map +0 -1
- package/dist/tools/index.d.ts +0 -263
- package/dist/tools/index.js +0 -69
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/lists.d.ts +0 -38
- package/dist/tools/lists.js +0 -39
- package/dist/tools/lists.js.map +0 -1
- package/dist/tools/memoryGraph.d.ts +0 -69
- package/dist/tools/memoryGraph.js +0 -128
- package/dist/tools/memoryGraph.js.map +0 -1
- package/dist/tools/memoryRead.d.ts +0 -25
- package/dist/tools/memoryRead.js +0 -33
- package/dist/tools/memoryRead.js.map +0 -1
- package/dist/tools/memoryWrite.d.ts +0 -27
- package/dist/tools/memoryWrite.js +0 -34
- package/dist/tools/memoryWrite.js.map +0 -1
- package/dist/tools/write.d.ts +0 -166
- package/dist/tools/write.js +0 -193
- package/dist/tools/write.js.map +0 -1
package/dist/bin/cli.js
CHANGED
|
@@ -1,565 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// open() nimmt jede Zeichenkette entgegen, auch file:// oder ein voellig
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
program
|
|
49
|
-
.command('login')
|
|
50
|
-
.description('Connect this device to OnlyMCP')
|
|
51
|
-
.action(async () => {
|
|
52
|
-
intro('OnlyMCP');
|
|
53
|
-
// Einmal ehrlich sagen, was mitgeht, BEVOR es mitgeht. Die beiden Werte
|
|
54
|
-
// stehen nachher in der Geraeteliste dieses Kontos, sonst nirgends - aber
|
|
55
|
-
// wer sie nicht schicken will, soll das erfahren, solange er noch abbrechen
|
|
56
|
-
// kann.
|
|
57
|
-
log.info(`This device will register itself as: ${await deviceLabel()}`);
|
|
58
|
-
const res = await fetch(`${baseUrl}/api/auth/device/code`, {
|
|
59
|
-
method: 'POST',
|
|
60
|
-
headers: { 'content-type': 'application/json' },
|
|
61
|
-
body: JSON.stringify({ client_id: 'onlymcp-cli' }),
|
|
62
|
-
});
|
|
63
|
-
const d = await res.json();
|
|
64
|
-
log.info(`Code: ${d.user_code}`);
|
|
65
|
-
log.info(d.verification_uri);
|
|
66
|
-
console.log(await QRCode.toString(d.verification_uri_complete, { type: 'terminal', small: true }));
|
|
67
|
-
// Geoeffnet wird die vollstaendige Adresse, nicht die kurze: sie traegt den
|
|
68
|
-
// Code mit, die Seite fuellt ihn dann selbst ein. Abgetippt und im QR-Code
|
|
69
|
-
// steht weiterhin beides, fuer den Fall, dass kein Browser aufgeht.
|
|
70
|
-
if (safeOpenUrl(d.verification_uri_complete, new URL(baseUrl).origin)) {
|
|
71
|
-
await open(d.verification_uri_complete).catch(() => { });
|
|
72
|
-
}
|
|
73
|
-
const s = spinner();
|
|
74
|
-
s.start('Waiting for confirmation');
|
|
75
|
-
const token = await pollForToken(baseUrl, d.device_code, d.interval ?? 5);
|
|
76
|
-
Keyring.save(token);
|
|
77
|
-
s.stop('Connected');
|
|
78
|
-
const api = new ApiClient(baseUrl, () => Keyring.read());
|
|
79
|
-
const counts = await writeStubsEverywhere(api);
|
|
80
|
-
outro(`${counts.skills} skills, ${counts.agents} agents, ${counts.files} files ready`);
|
|
81
|
-
});
|
|
82
|
-
/**
|
|
83
|
-
* Stubs in jeden Ordner, den ein Client auf diesem Geraet liest.
|
|
84
|
-
*
|
|
85
|
-
* Gezaehlt wird ueber alle Ordner zusammen, nicht der letzte: bis Aufgabe 12
|
|
86
|
-
* ueberschrieb die Schleife ihr Ergebnis bei jedem Durchlauf und meldete
|
|
87
|
-
* deshalb die Zahlen des zuletzt beschriebenen Ordners als Gesamtzahl.
|
|
88
|
-
*/
|
|
89
|
-
async function writeStubsEverywhere(api) {
|
|
90
|
-
const total = { skills: 0, agents: 0, files: 0, kept: [] };
|
|
91
|
-
for (const dir of stubDirs()) {
|
|
92
|
-
const c = await writeStubs(api, dir);
|
|
93
|
-
total.skills += c.skills;
|
|
94
|
-
total.agents += c.agents;
|
|
95
|
-
total.files += c.files;
|
|
96
|
-
for (const k of c.kept)
|
|
97
|
-
total.kept.push(`${dir}/${k}`);
|
|
98
|
-
}
|
|
99
|
-
return total;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Reports a finished import or sync, for the "last import / last sync" line on the devices page.
|
|
103
|
-
* A report that fails (an older server answering 404, no network, a timeout) prints nothing and
|
|
104
|
-
* changes nothing: the command already succeeded.
|
|
105
|
-
*/
|
|
106
|
-
async function reportRun(api, kind, files) {
|
|
107
|
-
try {
|
|
108
|
-
await api.reportRun(kind, files);
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
/* deliberately silent, see above */
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Ends this device's session on the server (next round A6). Clearing only the keyring left the
|
|
116
|
-
* session alive there, where it kept counting as a device until it expired. Best effort: null when
|
|
117
|
-
* the session ended, else what went wrong, and the logout goes on on this machine either way.
|
|
118
|
-
*/
|
|
119
|
-
async function endServerSession(token) {
|
|
120
|
-
try {
|
|
121
|
-
await new ApiClient(baseUrl, () => token).signOut();
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
catch (e) {
|
|
125
|
-
if (e instanceof ApiError && e.status > 0)
|
|
126
|
-
return `HTTP ${e.status}`;
|
|
127
|
-
return e instanceof Error ? e.message : String(e);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
program
|
|
131
|
-
.command('logout')
|
|
132
|
-
.description("End this device's session, then remove its login and cache")
|
|
133
|
-
.action(async () => {
|
|
134
|
-
// The server first, while the token is still here to name the session by.
|
|
135
|
-
const token = Keyring.read();
|
|
136
|
-
const failure = token ? await endServerSession(token) : null;
|
|
137
|
-
Keyring.clear();
|
|
138
|
-
await new TtlCache().clear();
|
|
139
|
-
if (failure) {
|
|
140
|
-
log.warn(`The session on the server could not be ended (${failure}). It counts as a device until it ` +
|
|
141
|
-
'expires, or until you remove it in the dashboard under Sessions and devices.');
|
|
142
|
-
log.success('Logged out on this device');
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
log.success('Logged out');
|
|
146
|
-
});
|
|
147
|
-
program
|
|
148
|
-
.command('status')
|
|
149
|
-
.description('Show whether this device is logged in')
|
|
150
|
-
.action(() => {
|
|
151
|
-
log.info(Keyring.read() ? 'logged in' : 'not logged in');
|
|
152
|
-
});
|
|
153
|
-
program
|
|
154
|
-
.command('sync')
|
|
155
|
-
.description('Rewrite the stub files')
|
|
156
|
-
.option('--memory <dir>', 'Also write memory as files into this folder')
|
|
157
|
-
.action(async (opts) => {
|
|
158
|
-
const api = new ApiClient(baseUrl, () => Keyring.read());
|
|
159
|
-
const counts = await writeStubsEverywhere(api);
|
|
160
|
-
log.success(`${counts.skills} skills, ${counts.agents} agents, ${counts.files} files`);
|
|
161
|
-
if (counts.kept.length > 0) {
|
|
162
|
-
// Stubs liegen in denselben Ordnern, aus denen importiert wird. Was
|
|
163
|
-
// dort schon stand und kein Stub war, bleibt stehen - aber nicht
|
|
164
|
-
// stillschweigend, sonst sucht jemand einen Stub, der nie geschrieben
|
|
165
|
-
// wurde. Gekuerzt, weil ein Geraet mit 270 eigenen Agent-Dateien sonst
|
|
166
|
-
// 270 Zeilen bekaeme und die Erfolgsmeldung aus dem Bild schoebe.
|
|
167
|
-
log.warn([
|
|
168
|
-
`Left untouched, because something of your own is already there (${counts.kept.length})`,
|
|
169
|
-
...counts.kept.slice(0, 10).map((k) => ` ${k}`),
|
|
170
|
-
...(counts.kept.length > 10 ? [` ... and ${counts.kept.length - 10} more`] : []),
|
|
171
|
-
].join('\n'));
|
|
172
|
-
}
|
|
173
|
-
// Every file this run wrote: the stubs, the skills' companion files, and (below) memory.
|
|
174
|
-
const stubFiles = counts.skills + counts.agents + counts.files;
|
|
175
|
-
if (!opts.memory) {
|
|
176
|
-
await reportRun(api, 'sync', stubFiles);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
const m = await syncMemoryToDisk(api, opts.memory);
|
|
180
|
-
log.success(`${m.written} memory files to ${opts.memory}`);
|
|
181
|
-
if (m.collisions.length > 0) {
|
|
182
|
-
log.warn([
|
|
183
|
-
'Team files with a path an existing file of yours already has',
|
|
184
|
-
...m.collisions.map((p) => ` ${p}`),
|
|
185
|
-
].join('\n'));
|
|
186
|
-
}
|
|
187
|
-
if (m.refused.length > 0) {
|
|
188
|
-
log.warn(['Paths refused', ...m.refused.map((p) => ` ${p}`)].join('\n'));
|
|
189
|
-
}
|
|
190
|
-
if (m.failures.length > 0) {
|
|
191
|
-
log.error([`${m.failures.length} failed`, ...m.failures.map((f) => ` ${f.what} ${f.code}`)].join('\n'));
|
|
192
|
-
process.exitCode = 1;
|
|
193
|
-
}
|
|
194
|
-
// A sync with failures is not a successful one: nothing is reported for it.
|
|
195
|
-
if (m.failures.length === 0)
|
|
196
|
-
await reportRun(api, 'sync', stubFiles + m.written);
|
|
197
|
-
});
|
|
198
|
-
program
|
|
199
|
-
.command('install')
|
|
200
|
-
.description('Register OnlyMCP in an MCP client')
|
|
201
|
-
.option('--client <id>', 'claude-code, codex or antigravity (aliases: claude, gemini, gemini-cli)')
|
|
202
|
-
.option('--all', 'Write every config file that already exists on this machine')
|
|
203
|
-
.option('--print', 'Only print, write nothing')
|
|
204
|
-
.action((opts) => {
|
|
205
|
-
// ONLYMCP_HOME ist dieselbe Testschleuse wie in paths.ts: damit trifft
|
|
206
|
-
// kein Testlauf jemals den echten ~/.codex- oder ~/.claude-Ordner.
|
|
207
|
-
const home = process.env.ONLYMCP_HOME ?? homedir();
|
|
208
|
-
const outcome = planInstall(home, opts);
|
|
209
|
-
/** Writes one target, or only prints it with --print. Shared by --client and --all. */
|
|
210
|
-
function apply(t) {
|
|
211
|
-
if (opts.print) {
|
|
212
|
-
log.info(`${t.name} -> ${t.configPath}`);
|
|
213
|
-
console.log(t.snippet);
|
|
214
|
-
// Bewusst keine zweite Zeile mit ONLYMCP_STUB_DIRS: die Variable steht
|
|
215
|
-
// seit install.ts im Schnipsel selbst. Zwei Wege, dasselbe zu setzen,
|
|
216
|
-
// laden dazu ein, beide zu gehen.
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
const result = writeInstallTarget(t);
|
|
221
|
-
if (result === 'already-present') {
|
|
222
|
-
log.info(`${t.name} is already set up: ${t.configPath}`);
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
log.success(`${t.name} set up: ${t.configPath}`);
|
|
226
|
-
}
|
|
227
|
-
if (t.id === 'claude-code')
|
|
228
|
-
log.info(CLAUDE_CODE_NOTE);
|
|
229
|
-
}
|
|
230
|
-
catch (e) {
|
|
231
|
-
log.error(e instanceof Error ? e.message : String(e));
|
|
232
|
-
process.exitCode = 1;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
switch (outcome.kind) {
|
|
236
|
-
case 'conflicting-options':
|
|
237
|
-
log.error('Pass either --client <id> or --all, not both.');
|
|
238
|
-
process.exitCode = 2;
|
|
239
|
-
return;
|
|
240
|
-
case 'missing-client':
|
|
241
|
-
log.info(`Known clients: ${outcome.targets.map((t) => t.id).join(', ')}`);
|
|
242
|
-
log.info('Pass --client <id>, or --all.');
|
|
243
|
-
process.exitCode = 2;
|
|
244
|
-
return;
|
|
245
|
-
case 'unknown-client':
|
|
246
|
-
// Kein Raten. Ein falsch geratener Pfad ueberschreibt sonst eine
|
|
247
|
-
// fremde Konfigurationsdatei.
|
|
248
|
-
log.error(`Unknown client. Known: ${outcome.targets.map((t) => t.id).join(', ')}`);
|
|
249
|
-
process.exitCode = 1;
|
|
250
|
-
return;
|
|
251
|
-
case 'none-present-for-all':
|
|
252
|
-
log.error('No known client is set up on this machine yet.');
|
|
253
|
-
log.info(`Pass --client <id> to write its config anyway. Known: ${outcome.targets.map((t) => t.id).join(', ')}.`);
|
|
254
|
-
process.exitCode = 2;
|
|
255
|
-
return;
|
|
256
|
-
case 'single':
|
|
257
|
-
apply(outcome.target);
|
|
258
|
-
return;
|
|
259
|
-
case 'all':
|
|
260
|
-
for (const t of outcome.present)
|
|
261
|
-
apply(t);
|
|
262
|
-
if (outcome.skipped.length > 0) {
|
|
263
|
-
log.info(`Skipped, not set up on this machine: ${outcome.skipped.map((t) => t.id).join(', ')}`);
|
|
264
|
-
}
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
function formatBytes(bytes) {
|
|
269
|
-
if (bytes < 1024)
|
|
270
|
-
return `${bytes} B`;
|
|
271
|
-
if (bytes < 1024 * 1024)
|
|
272
|
-
return `${Math.round(bytes / 1024)} KB`;
|
|
273
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
274
|
-
}
|
|
275
|
-
function sumBytes(items) {
|
|
276
|
-
return items.reduce((total, x) => total + x.bytes, 0);
|
|
277
|
-
}
|
|
278
|
-
/** Der Plan, ausgeschrieben. Steht immer, auch bei --yes: wer zuschaut, soll sehen. */
|
|
279
|
-
function printPlan(plan, roots) {
|
|
280
|
-
const files = plan.skills.reduce((n, s) => n + s.files.length, 0);
|
|
281
|
-
const withFiles = plan.skills.filter((s) => s.files.length > 0).length;
|
|
282
|
-
const fileBytes = plan.skills.reduce((n, s) => n + sumBytes(s.files), 0);
|
|
283
|
-
if (roots.skills) {
|
|
284
|
-
log.message(`Skills ${String(plan.skills.length).padStart(4)} from ${roots.skills}\n` +
|
|
285
|
-
` ${String(files).padStart(4)} more files across ${withFiles} skills ` +
|
|
286
|
-
`${formatBytes(sumBytes(plan.skills) + fileBytes)}`);
|
|
287
|
-
}
|
|
288
|
-
if (roots.agents) {
|
|
289
|
-
log.message(`Agents ${String(plan.agents.length).padStart(4)} from ${roots.agents} ${formatBytes(sumBytes(plan.agents))}`);
|
|
290
|
-
}
|
|
291
|
-
if (roots.memory) {
|
|
292
|
-
log.message(`Memory ${String(plan.memory.length).padStart(4)} from ${roots.memory} ${formatBytes(sumBytes(plan.memory))}`);
|
|
293
|
-
}
|
|
294
|
-
if (plan.skipped.length > 0) {
|
|
295
|
-
const byReason = new Map();
|
|
296
|
-
for (const s of plan.skipped)
|
|
297
|
-
byReason.set(s.reason, (byReason.get(s.reason) ?? 0) + 1);
|
|
298
|
-
log.message(['Skipped', ...[...byReason].map(([reason, n]) => ` ${String(n).padStart(4)} ${reason}`)].join('\n'));
|
|
299
|
-
}
|
|
300
|
-
if (plan.noDescription.length > 0) {
|
|
301
|
-
// Geht trotzdem hoch. Gemeldet, weil die Liste im Dashboard und die
|
|
302
|
-
// Stubs auf jedem Geraet von der Beschreibung leben und ein Skill ohne
|
|
303
|
-
// sie dort als blosser Name steht.
|
|
304
|
-
log.warn(['Missing a description in the SKILL.md header', ...plan.noDescription.map((n) => ` ${n}`)].join('\n'));
|
|
305
|
-
}
|
|
306
|
-
if (plan.flagged.length > 0) {
|
|
307
|
-
log.warn([
|
|
308
|
-
'Held back over possible credentials (none of it left this machine)',
|
|
309
|
-
...plan.flagged.flatMap((f) => [
|
|
310
|
-
` ${f.relative}`,
|
|
311
|
-
...f.hits.map((h) => ` line ${h.line} rule ${h.rule} ${h.excerpt}`),
|
|
312
|
-
]),
|
|
313
|
-
].join('\n'));
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
program
|
|
317
|
-
.command('import')
|
|
318
|
-
.description('Bring existing local skills, agents and memory into OnlyMCP')
|
|
319
|
-
.option('--skills <dir>', 'Folder with skill subfolders, or a plugin cache')
|
|
320
|
-
.option('--agents <dir>', 'Folder with agent files')
|
|
321
|
-
// --memory hat mit Absicht keine Vorgabe. Skills und Agents liegen an einem
|
|
322
|
-
// bekannten Pfad, an dem nur Skills und Agents liegen. Ein Memory-Ordner
|
|
323
|
-
// nicht: eine Vorgabe auf irgendjemandes Notizen zu richten und die
|
|
324
|
-
// hochzuladen waere das Schlimmste, was dieser Befehl tun koennte.
|
|
325
|
-
.option('--memory <dir>', 'Folder with memory files (no default)')
|
|
326
|
-
.option('--dry-run', 'Only show what would be uploaded, then stop')
|
|
327
|
-
.option('--yes', 'Upload without asking first')
|
|
328
|
-
// --prune loescht im Konto. Es hat deshalb bewusst KEINE Abkuerzung ueber
|
|
329
|
-
// --yes: die Liste wird immer gezeigt und immer einzeln bestaetigt, auch
|
|
330
|
-
// wenn der Upload selbst unbeaufsichtigt laeuft.
|
|
331
|
-
.option('--prune', 'Delete, after confirmation, what the account holds and the source no longer has')
|
|
332
|
-
.action(async (opts) => {
|
|
333
|
-
// Dieselbe Testschleuse wie in install.ts und paths.ts.
|
|
334
|
-
const home = process.env.ONLYMCP_HOME ?? homedir();
|
|
335
|
-
const roots = {
|
|
336
|
-
skills: opts.skills ?? join(home, '.claude', 'skills'),
|
|
337
|
-
agents: opts.agents ?? join(home, '.claude', 'agents'),
|
|
338
|
-
memory: opts.memory,
|
|
339
|
-
};
|
|
340
|
-
intro('OnlyMCP Import');
|
|
341
|
-
const s = spinner();
|
|
342
|
-
s.start('Reading the folders');
|
|
343
|
-
let plan;
|
|
344
|
-
try {
|
|
345
|
-
const [skills, agents, memory] = await Promise.all([
|
|
346
|
-
existsSync(roots.skills) ? collect(roots.skills) : Promise.resolve([]),
|
|
347
|
-
existsSync(roots.agents) ? collect(roots.agents) : Promise.resolve([]),
|
|
348
|
-
roots.memory && existsSync(roots.memory) ? collect(roots.memory) : Promise.resolve([]),
|
|
349
|
-
]);
|
|
350
|
-
plan = buildImportPlan({ skills, agents, memory }, { skillsRoot: roots.skills });
|
|
351
|
-
}
|
|
352
|
-
catch (e) {
|
|
353
|
-
s.stop('Aborted');
|
|
354
|
-
log.error(e instanceof Error ? e.message : String(e));
|
|
355
|
-
process.exitCode = 1;
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
s.stop('Read');
|
|
359
|
-
printPlan(plan, {
|
|
360
|
-
skills: existsSync(roots.skills) ? roots.skills : undefined,
|
|
361
|
-
agents: existsSync(roots.agents) ? roots.agents : undefined,
|
|
362
|
-
memory: roots.memory && existsSync(roots.memory) ? roots.memory : undefined,
|
|
363
|
-
});
|
|
364
|
-
const total = countPlan(plan);
|
|
365
|
-
// Mit --prune gibt es auch dann etwas zu tun, wenn nichts hochgeht: genau
|
|
366
|
-
// ein leerer Quellordner ist der Fall, in dem am meisten geloescht wuerde.
|
|
367
|
-
if (total === 0 && !opts.prune) {
|
|
368
|
-
outro('Nothing to do.');
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
if (opts.dryRun && !opts.prune) {
|
|
372
|
-
outro('Nothing uploaded (--dry-run).');
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
const api = new ApiClient(baseUrl, () => Keyring.read());
|
|
376
|
-
// Ins falsche Konto zu laden ist ein leicht gemachter und unangenehm zu
|
|
377
|
-
// behebender Fehler. Die Gegenmassnahme ist eine Zeile Ausgabe.
|
|
378
|
-
try {
|
|
379
|
-
const me = await api.getMe();
|
|
380
|
-
log.info(`Target ${baseUrl} as ${me.email} plan ${me.plan.name}`);
|
|
381
|
-
}
|
|
382
|
-
catch (e) {
|
|
383
|
-
log.error(`Not logged in, or the server is unreachable (${e instanceof Error ? e.message : String(e)}).`);
|
|
384
|
-
log.info('First: npx onlymcp login');
|
|
385
|
-
process.exitCode = 1;
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
if (total > 0 && !opts.yes && !opts.dryRun) {
|
|
389
|
-
if (!process.stdin.isTTY) {
|
|
390
|
-
// Ohne Terminal gibt es keine Rueckfrage, also auch keinen Upload.
|
|
391
|
-
// Wer das automatisieren will, sagt --yes und hat den Plan vorher mit
|
|
392
|
-
// --dry-run gesehen.
|
|
393
|
-
log.error('No terminal for the prompt. Run --dry-run first, then --yes.');
|
|
394
|
-
process.exitCode = 1;
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
const go = await confirm({ message: `Upload ${total} entries?`, initialValue: false });
|
|
398
|
-
if (isCancel(go) || !go) {
|
|
399
|
-
cancel('Cancelled. Nothing uploaded.');
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
if (plan.flagged.length > 0) {
|
|
403
|
-
const release = await multiselect({
|
|
404
|
-
message: 'Send the held-back files anyway?',
|
|
405
|
-
options: plan.flagged.map((f) => ({ value: f.relative, label: f.relative })),
|
|
406
|
-
required: false,
|
|
407
|
-
initialValues: [],
|
|
408
|
-
});
|
|
409
|
-
if (isCancel(release)) {
|
|
410
|
-
cancel('Cancelled. Nothing uploaded.');
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
if (release.length > 0) {
|
|
414
|
-
// Freigeben heisst: die Datei noch einmal einsammeln, diesmal ohne
|
|
415
|
-
// ihre Funde, und den Plan daraus neu bauen. Kein zweiter Weg in den
|
|
416
|
-
// Upload, der die Pruefung umgehen koennte.
|
|
417
|
-
const freed = new Set(release);
|
|
418
|
-
const clear = (items) => items.map((c) => (freed.has(c.relative) ? { ...c, hits: [] } : c));
|
|
419
|
-
const [skills, agents, memory] = await Promise.all([
|
|
420
|
-
existsSync(roots.skills) ? collect(roots.skills) : Promise.resolve([]),
|
|
421
|
-
existsSync(roots.agents) ? collect(roots.agents) : Promise.resolve([]),
|
|
422
|
-
roots.memory && existsSync(roots.memory) ? collect(roots.memory) : Promise.resolve([]),
|
|
423
|
-
]);
|
|
424
|
-
plan = buildImportPlan({
|
|
425
|
-
skills: clear(skills),
|
|
426
|
-
agents: clear(agents),
|
|
427
|
-
memory: clear(memory),
|
|
428
|
-
}, { skillsRoot: roots.skills });
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
if (total > 0 && !opts.dryRun) {
|
|
433
|
-
const up = spinner();
|
|
434
|
-
up.start('Uploading');
|
|
435
|
-
const result = await runUpload(api, plan, (done, all, label) => {
|
|
436
|
-
up.message(`${done}/${all} ${label}`);
|
|
437
|
-
});
|
|
438
|
-
up.stop('Done');
|
|
439
|
-
log.success(`${result.skills} skills, ${result.skillFiles} skill files, ` +
|
|
440
|
-
`${result.agents} agents, ${result.memory} memory files`);
|
|
441
|
-
if (result.failures.length > 0) {
|
|
442
|
-
log.error([`${result.failures.length} failed`, ...result.failures.map((f) => ` ${f.what} ${f.code}`)].join('\n'));
|
|
443
|
-
process.exitCode = 1;
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
await reportRun(api, 'import', result.skills + result.skillFiles + result.agents + result.memory);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (opts.prune) {
|
|
450
|
-
// Verglichen wird nur, was auch gelesen wurde. Ohne --memory sagt ein
|
|
451
|
-
// Import gar nichts ueber Memory aus, und "steht im Konto, fehlt in der
|
|
452
|
-
// Quelle" waere dort keine Aussage, sondern ein Unfall.
|
|
453
|
-
const covered = {
|
|
454
|
-
skills: existsSync(roots.skills),
|
|
455
|
-
agents: existsSync(roots.agents),
|
|
456
|
-
memory: Boolean(roots.memory && existsSync(roots.memory)),
|
|
457
|
-
};
|
|
458
|
-
const targets = await planPrune(api, plan, covered);
|
|
459
|
-
if (isEmpty(targets)) {
|
|
460
|
-
log.info('Nothing to delete: the account holds nothing the source does not know.');
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
// Vollstaendig aufgezaehlt, nicht gezaehlt. Eine Zahl zu bestaetigen
|
|
464
|
-
// heisst, etwas zu bestaetigen, das man nicht gesehen hat.
|
|
465
|
-
log.warn([
|
|
466
|
-
'Would delete (only your own, nothing from a team, nothing archived)',
|
|
467
|
-
...targets.skills.map((n) => ` Skill ${n}`),
|
|
468
|
-
...targets.agents.map((n) => ` Agent ${n}`),
|
|
469
|
-
...targets.memory.map((p) => ` Memory ${p}`),
|
|
470
|
-
].join('\n'));
|
|
471
|
-
if (opts.dryRun) {
|
|
472
|
-
log.info('Nothing deleted (--dry-run).');
|
|
473
|
-
}
|
|
474
|
-
else if (!process.stdin.isTTY) {
|
|
475
|
-
// Kein --yes-Durchgriff: geloescht wird nur von Hand.
|
|
476
|
-
log.error('No terminal for the prompt. Nothing deleted.');
|
|
477
|
-
process.exitCode = 1;
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
const go = await confirm({
|
|
481
|
-
message: `Delete ${countTargets(targets)} entries in the account?`,
|
|
482
|
-
initialValue: false,
|
|
483
|
-
});
|
|
484
|
-
if (isCancel(go) || !go) {
|
|
485
|
-
log.info('Nothing deleted.');
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
const pruned = await runPrune(api, targets);
|
|
489
|
-
log.success(`${pruned.deleted} deleted`);
|
|
490
|
-
if (pruned.failures.length > 0) {
|
|
491
|
-
log.error([
|
|
492
|
-
`${pruned.failures.length} failed`,
|
|
493
|
-
...pruned.failures.map((f) => ` ${f.what} ${f.code}`),
|
|
494
|
-
].join('\n'));
|
|
495
|
-
process.exitCode = 1;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
outro(opts.dryRun ? 'Nothing changed (--dry-run).' : 'Then: npx onlymcp sync on every device.');
|
|
502
|
-
});
|
|
503
|
-
program
|
|
504
|
-
.command('export')
|
|
505
|
-
.description('Get everything out of the account: memory, skills, agents, connections')
|
|
506
|
-
.option('--out <dir>', 'Unpack the archive into this folder')
|
|
507
|
-
.option('--zip <file>', 'Keep the archive as a file')
|
|
508
|
-
.option('--scope <scope>', 'personal, all or team:<id>', 'all')
|
|
509
|
-
.option('--types <list>', 'memory,skills,agents,connections')
|
|
510
|
-
.action(async (opts) => {
|
|
511
|
-
// Genau eines von beiden. Beides hiesse zweimal herunterladen, keines
|
|
512
|
-
// hiesse, die teuerste Leseoperation des Produkts ins Nichts zu schicken.
|
|
513
|
-
if ((opts.out ? 1 : 0) + (opts.zip ? 1 : 0) !== 1) {
|
|
514
|
-
log.error('Either --out <folder> or --zip <file>, not both and not neither.');
|
|
515
|
-
process.exitCode = 1;
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
intro('OnlyMCP Export');
|
|
519
|
-
const api = new ApiClient(baseUrl, () => Keyring.read());
|
|
520
|
-
const s = spinner();
|
|
521
|
-
s.start('Fetching the archive');
|
|
522
|
-
let res;
|
|
523
|
-
try {
|
|
524
|
-
res = await api.exportArchive({
|
|
525
|
-
scope: opts.scope,
|
|
526
|
-
types: opts.types?.split(',').map((t) => t.trim()).filter((t) => t !== ''),
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
catch (e) {
|
|
530
|
-
s.stop('Aborted');
|
|
531
|
-
log.error(e instanceof Error ? e.message : String(e));
|
|
532
|
-
log.info('Not logged in? First: npx onlymcp login');
|
|
533
|
-
process.exitCode = 1;
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
if (opts.zip) {
|
|
537
|
-
// In die Datei geschrieben, waehrend es ankommt. Ein Konto mit zwei
|
|
538
|
-
// Gigabyte soll nicht erst zwei Gigabyte Arbeitsspeicher kosten.
|
|
539
|
-
mkdirSync(dirname(opts.zip), { recursive: true });
|
|
540
|
-
await pipeline(Readable.fromWeb(res.body), createWriteStream(opts.zip));
|
|
541
|
-
s.stop('Done');
|
|
542
|
-
outro(`Archive: ${opts.zip}`);
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
546
|
-
const result = await unpackArchive(bytes, opts.out);
|
|
547
|
-
s.stop('Done');
|
|
548
|
-
log.success(`${result.written} files in ${opts.out}`);
|
|
549
|
-
if (result.refused.length > 0) {
|
|
550
|
-
log.warn(['Not written, path in the archive is not allowed', ...result.refused.map((p) => ` ${p}`)].join('\n'));
|
|
551
|
-
}
|
|
552
|
-
if (result.failed.length > 0) {
|
|
553
|
-
// Auf Windows der Normalfall fuer einen Skill mit Namensraum: der
|
|
554
|
-
// Doppelpunkt ist dort kein zulaessiges Zeichen in einem Ordnernamen.
|
|
555
|
-
log.warn([
|
|
556
|
-
"Not written, the filename doesn't work on this system",
|
|
557
|
-
...result.failed.map((f) => ` ${f.path} ${f.reason}`),
|
|
558
|
-
'With --zip <file> the archive stays unchanged.',
|
|
559
|
-
].join('\n'));
|
|
560
|
-
process.exitCode = 1;
|
|
561
|
-
}
|
|
562
|
-
outro(`Restore: npx onlymcp import --skills ${opts.out}/skills --agents ${opts.out}/agents --memory ${opts.out}/memory`);
|
|
563
|
-
});
|
|
564
|
-
await program.parseAsync();
|
|
565
|
-
//# sourceMappingURL=cli.js.map
|
|
2
|
+
import{cancel as dt,confirm as ut,intro as he,isCancel as me,log as d,multiselect as Qn,outro as I,spinner as Z}from"@clack/prompts";import{Command as ei}from"commander";import ti from"open";import ni from"qrcode";import{homedir as mt}from"node:os";import{createWriteStream as ii,existsSync as k,mkdirSync as ri}from"node:fs";import{pipeline as oi}from"node:stream/promises";import{Readable as si}from"node:stream";import{dirname as ai,join as pt}from"node:path";import{hostname as Lt}from"node:os";import{execFile as At}from"node:child_process";import{hostname as Ot}from"node:os";import{readFile as It}from"node:fs/promises";import{promisify as _t}from"node:util";import{randomUUID as Se}from"node:crypto";import{link as yt,mkdir as bt,readFile as vt,rename as wt,rm as kt,writeFile as Ce}from"node:fs/promises";import{basename as St,dirname as Pe,join as Ct}from"node:path";import{existsSync as ht}from"node:fs";import{homedir as be}from"node:os";import{join as A}from"node:path";function N(){return process.env.ONLYMCP_HOME??A(be(),".claude","onlymcp")}function ye(){return process.env.ONLYMCP_HOME??be()}function ve(){return A(N(),"device-id")}function we(){return A(N(),"cache")}function L(){return A(N(),"stub-ignore")}function ke(){let e=[];process.env.CLAUDE_PLUGIN_ROOT&&e.push(process.env.CLAUDE_PLUGIN_ROOT);for(let t of(process.env.ONLYMCP_STUB_DIRS??"").split(":"))t&&e.push(t);for(let t of[A(ye(),".claude"),A(ye(),".codex")])ht(t)&&!e.includes(t)&&e.push(t);return e.length>0?e:[A(N(),"plugin")]}var xt=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,xe;function $e(){return xe??=Pt(),xe}async function ee(e){try{let t=(await vt(e,"utf8")).trim().toLowerCase();return xt.test(t)?t:null}catch{return null}}async function Pt(){let e=ve(),t=await ee(e);if(t)return t;try{return await bt(Pe(e),{recursive:!0}),await Et(e)}catch{return null}}var $t=new Set(["EPERM","ENOTSUP","EOPNOTSUPP","ENOSYS","EXDEV"]);async function Et(e){let t=Se(),n=Ct(Pe(e),`.${St(e)}.${process.pid}.${Se()}.tmp`);try{await Ce(n,`${t}
|
|
3
|
+
`,{flag:"wx",mode:384});try{return await yt(n,e),t}catch(r){let o=r.code;if(o!=="EEXIST"&&!$t.has(o??""))throw r;if(o!=="EEXIST")try{return await Ce(e,`${t}
|
|
4
|
+
`,{flag:"wx",mode:384}),t}catch(s){if(s.code!=="EEXIST")throw s}}let i=await ee(e);return i||(await wt(n,e),await ee(e)??t)}finally{await kt(n,{force:!0}).catch(()=>{})}}var U="0.19.0";var Rt=_t(At),Tt=`onlymcp-cli/${U} (${process.platform} ${process.arch}; node ${process.versions.node})`,Ee=200;async function Ae(e,t){let n;try{return await Promise.race([e,new Promise(i=>{n=setTimeout(()=>i(null),t),n.unref?.()})])}catch{return null}finally{n&&clearTimeout(n)}}async function Nt(){try{if(process.platform==="linux"){let e=It("/sys/class/dmi/id/product_version","utf8"),n=(await Ae(e,Ee))?.trim();return!n||/^(to be filled|default string|system version|none|n\/a)/i.test(n)?null:n}if(process.platform==="darwin"){let e=Rt("sysctl",["-n","hw.model"]);return(await Ae(e,Ee))?.stdout.trim()||null}}catch{}return null}async function te(){let e="unknown";try{e=Ot()||"unknown"}catch{}let t=await Nt();return(t&&t!==e?`${e} (${t})`:e).slice(0,60)}var Oe=null;async function _(){Oe??=await te();let e=await $e();return{"user-agent":Tt,"x-onlymcp-device":Oe,...e?{"x-onlymcp-device-id":e}:{}}}var h=class extends Error{constructor(n,i={},r=0){super(n);this.code=n;this.params=i;this.status=r;this.name="ApiError"}code;params;status},x=class{constructor(t,n){this.baseUrl=t;this.token=n}baseUrl;token;async call(t,n={}){let i=this.token();if(!i)throw new h("errors.NOT_LOGGED_IN");let r=await fetch(`${this.baseUrl}${t}`,{...n,headers:{...await _(),...n.headers??{},authorization:`Bearer ${i}`,"content-type":"application/json"}});if(r.status===204)return;let o=await r.json().catch(()=>({}));if(!r.ok)throw new h(o.code??"errors.UNKNOWN",o.params??{},r.status);return o}getMe(){return this.call("/api/me")}reportRun(t,n){return this.call("/api/devices/current/run",{method:"POST",body:JSON.stringify({kind:t,files:n}),signal:AbortSignal.timeout(5e3)})}signOut(){return this.call("/api/auth/sign-out",{method:"POST",body:"{}",signal:AbortSignal.timeout(5e3)})}listSkills(){return this.call("/api/skills")}getSkill(t){return this.call(`/api/skills/${encodeURIComponent(t)}`)}putSkill(t,n,i={}){return this.call(`/api/skills/${encodeURIComponent(t)}`,{method:"PUT",body:JSON.stringify({body:n,...i})})}skillFileUrl(t,n){let i=n.split("/").map(encodeURIComponent).join("/");return`/api/skills/${encodeURIComponent(t)}/files/${i}`}getSkillFile(t,n){return this.call(this.skillFileUrl(t,n))}putSkillFile(t,n,i,r="utf8"){return this.call(this.skillFileUrl(t,n),{method:"PUT",body:JSON.stringify({content:i,encoding:r})})}listAgents(){return this.call("/api/agents")}putAgent(t,n){return this.call(`/api/agents/${encodeURIComponent(t)}`,{method:"PUT",body:JSON.stringify(n)})}getAgent(t,{use:n=!1}={}){return this.call(`/api/agents/${encodeURIComponent(t)}${n?"?use=1":""}`)}readMemory(t){return this.call(this.memoryUrl(t))}writeMemory(t,n,i){return this.call(this.memoryUrl(t),{method:"PUT",body:JSON.stringify({content:n,deviceLabel:i})})}memoryUrl(t){return`/api/memory/${t.split("/").map(encodeURIComponent).join("/")}`}listMemory(){return this.call("/api/memory")}searchContent(t,n={}){let i=new URLSearchParams({q:t});return n.types?.length&&i.set("types",n.types.join(",")),n.limit&&i.set("limit",String(n.limit)),this.call(`/api/search?${i.toString()}`)}memoryTree(t="all"){return this.call(`/api/memory/tree?scope=${encodeURIComponent(t)}`)}moveMemory(t,n,i=!0,r=Lt()){return this.call("/api/memory/move",{method:"POST",body:JSON.stringify({from:t,to:n,updateLinks:i,deviceLabel:r})})}async exportArchive(t={}){let n=this.token();if(!n)throw new h("errors.NOT_LOGGED_IN");let i=new URLSearchParams;t.scope&&i.set("scope",t.scope),t.types?.length&&i.set("types",t.types.join(","));let r=i.size>0?`?${i.toString()}`:"",o=await fetch(`${this.baseUrl}/api/export${r}`,{headers:{...await _(),authorization:`Bearer ${n}`}});if(!o.ok){let s=await o.json().catch(()=>({}));throw new h(s.code??"errors.UNKNOWN",s.params??{},o.status)}return o}deleteMemory(t){return this.call(this.memoryUrl(t),{method:"DELETE"})}deleteSkill(t){return this.call(`/api/skills/${encodeURIComponent(t)}`,{method:"DELETE"})}deleteSkillFile(t,n){return this.call(this.skillFileUrl(t,n),{method:"DELETE"})}deleteAgent(t){return this.call(`/api/agents/${encodeURIComponent(t)}`,{method:"DELETE"})}listConnections(){return this.call("/api/mcp-connections")}createConnection(t){return this.call("/api/mcp-connections",{method:"POST",body:JSON.stringify(t)})}updateConnection(t,n){return this.call(`/api/mcp-connections/${t}`,{method:"PATCH",body:JSON.stringify(n)})}deleteConnection(t){return this.call(`/api/mcp-connections/${t}`,{method:"DELETE"})}listCapabilities(t){return this.call(`/api/mcp-connections/${t}/capabilities`)}toggleCapability(t,n,i){return this.call(`/api/mcp-connections/${t}/capabilities/${n}`,{method:"PATCH",body:JSON.stringify({enabled:i})})}};import{Entry as Ut}from"@napi-rs/keyring";function Mt(e,t){let n=()=>new Ut(e,t);return{save(i){n().setPassword(i)},read(){try{return n().getPassword()}catch{return null}},clear(){try{n().deletePassword()}catch{}}}}var S=Mt("onlymcp","session-token");var Dt={sleep:e=>new Promise(t=>setTimeout(t,e))};async function Ie(e,t,n,i=Dt){let r=n;for(;;){await i.sleep(r*1e3);let s=await(await fetch(`${e}/api/auth/device/token`,{method:"POST",headers:{...await _(),"content-type":"application/json"},body:JSON.stringify({grant_type:"urn:ietf:params:oauth:grant-type:device_code",device_code:t,client_id:"onlymcp-cli"})})).json().catch(()=>({}));if(s.access_token)return s.access_token;switch(s.error){case"authorization_pending":break;case"slow_down":r+=5;break;case"access_denied":throw new h("errors.DEVICE_ACCESS_DENIED");case"expired_token":throw new h("errors.DEVICE_CODE_EXPIRED");default:throw new h("errors.DEVICE_LOGIN_FAILED",{error:s.error})}}}import M from"cacache";var zt=Number(process.env.ONLYMCP_TTL_SECONDS??360*60),D=class{constructor(t=we()){this.dir=t}dir;async get(t){try{let n=await M.get(this.dir,t),i=n.metadata;return!i||i.expiresAt<=Date.now()?(await M.rm.entry(this.dir,t),null):n.data.toString("utf8")}catch{return null}}async set(t,n,i=zt){await M.put(this.dir,t,n,{metadata:{expiresAt:Date.now()+i*1e3}})}async clear(){await M.rm.all(this.dir)}};import{appendFileSync as nn,existsSync as j,mkdirSync as Re,readFileSync as Ne,writeFileSync as rn}from"node:fs";import{dirname as Le,join as on}from"node:path";function _e(e){return{language:"bash",text:e}}function P(e,t){return{language:"json",text:JSON.stringify(e,null,2),path:t}}var jt={id:"claude-code",name:"Claude Code",category:"coding",brand:"claudecode",methods:["remote-header","remote-oauth","stdio"],docsUrl:"https://code.claude.com/docs/en/mcp-quickstart",verifiedOn:"2026-09-25",caveats:["clients.caveat.claudeCodeConnectorSynced"],install:{configPath:"~/.claude.json",format:"json",stubDir:null},remote:e=>[_e(`claude mcp add --transport http --scope user onlymcp ${e.endpoint} --header "Authorization: Bearer ${e.token}"`)]},Ft={id:"claude-app",name:"Claude",category:"chat",brand:"claude",methods:["remote-oauth"],docsUrl:"https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp",verifiedOn:"2026-09-25",caveats:["clients.caveat.claudeHeaderBeta"],install:null,oauthRedirectHosts:["claude.ai","claude.com"],remote:()=>null},Bt={id:"chatgpt",name:"ChatGPT",category:"chat",brand:"openai",methods:["remote-oauth"],docsUrl:"https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt",verifiedOn:"2026-09-25",caveats:["clients.caveat.chatgptDeveloperMode"],install:null,oauthRedirectHosts:["chatgpt.com"],remote:()=>null},Ht={id:"codex",name:"Codex",category:"coding",brand:"openai",methods:["remote-header","remote-oauth","stdio"],docsUrl:"https://learn.chatgpt.com/docs/extend/mcp?surface=cli",verifiedOn:"2026-09-25",caveats:["clients.caveat.codexTokenEnv"],install:{configPath:"~/.codex/config.toml",format:"toml",stubDir:"~/.codex/skills"},remote:e=>[_e([`export ONLYMCP_TOKEN=${e.token}`,`codex mcp add onlymcp --url ${e.endpoint} --bearer-token-env-var ONLYMCP_TOKEN`].join(`
|
|
5
|
+
`))]},Gt={id:"antigravity",aliases:["gemini-cli"],name:"Antigravity",category:"coding",brand:"antigravity",methods:["remote-oauth","remote-header","stdio"],docsUrl:"https://antigravity.google/docs/mcp",verifiedOn:"2026-10-17",caveats:[],install:{configPath:"~/.gemini/config/mcp_config.json",format:"json",stubDir:null},oauthRedirectHosts:["antigravity.google"],remote:e=>[P({mcpServers:{onlymcp:{serverUrl:e.endpoint,headers:{Authorization:`Bearer ${e.token}`}}}},"~/.gemini/config/mcp_config.json")]},Wt={id:"gemini-app",name:"Gemini",category:"chat",brand:"googlegemini",methods:["remote-oauth"],docsUrl:"https://support.google.com/gemini/answer/17209137",verifiedOn:"2026-10-10",caveats:["clients.caveat.geminiAppSpark"],install:null,remote:()=>null},Jt={id:"grok",name:"Grok",category:"chat",brand:"grok",methods:["remote-oauth"],docsUrl:"https://docs.x.ai/grok/connectors",verifiedOn:"2026-10-17",caveats:["clients.caveat.grokConnectors","clients.caveat.grokBusinessAdmin"],install:null,oauthRedirectHosts:["grok.com"],remote:()=>null},Vt={id:"copilot-vscode",name:"GitHub Copilot (VS Code)",category:"ide",brand:"githubcopilot",methods:["remote-header","remote-oauth","stdio"],docsUrl:"https://code.visualstudio.com/docs/agents/reference/mcp-configuration",verifiedOn:"2026-09-25",caveats:["clients.caveat.copilotPolicy"],install:null,remote:e=>[P({servers:{onlymcp:{type:"http",url:e.endpoint,headers:{Authorization:`Bearer ${e.token}`}}}},".vscode/mcp.json")]},Kt={id:"cursor",name:"Cursor",category:"ide",brand:"cursor",methods:["remote-header","remote-oauth","stdio"],docsUrl:"https://cursor.com/docs/mcp",verifiedOn:"2026-09-25",caveats:[],install:null,remote:e=>[P({mcpServers:{onlymcp:{url:e.endpoint,headers:{Authorization:`Bearer ${e.token}`}}}},"~/.cursor/mcp.json")]},Yt={id:"windsurf",name:"Windsurf",category:"ide",brand:"windsurf",methods:["remote-header","stdio"],docsUrl:"https://docs.windsurf.com/windsurf/cascade/mcp",verifiedOn:"2026-09-25",caveats:["clients.caveat.windsurfAllowlist"],install:null,remote:e=>[P({mcpServers:{onlymcp:{serverUrl:e.endpoint,headers:{Authorization:`Bearer ${e.token}`}}}},"~/.codeium/windsurf/mcp_config.json")]},qt={id:"cline",name:"Cline",category:"ide",brand:"cline",methods:["remote-header","stdio"],docsUrl:"https://docs.cline.bot/mcp/mcp-overview",verifiedOn:"2026-09-25",caveats:[],install:null,remote:e=>[P({mcpServers:{onlymcp:{type:"streamableHttp",url:e.endpoint,headers:{Authorization:`Bearer ${e.token}`}}}},"~/.cline/mcp.json")]},Zt={id:"zed",name:"Zed",category:"ide",brand:"zedindustries",methods:["remote-header","remote-oauth","stdio"],docsUrl:"https://zed.dev/docs/assistant/model-context-protocol",verifiedOn:"2026-09-25",caveats:[],install:null,remote:e=>[P({context_servers:{onlymcp:{url:e.endpoint,headers:{Authorization:`Bearer ${e.token}`}}}},"settings.json")]},Xt={id:"continue",name:"Continue",category:"ide",brand:null,methods:["remote-header","stdio"],docsUrl:"https://docs.continue.dev/customize/deep-dives/mcp",verifiedOn:"2026-09-25",caveats:[],install:null,remote:e=>[{language:"yaml",path:"config.yaml",text:["mcpServers:"," - name: onlymcp"," type: streamable-http",` url: ${e.endpoint}`," requestOptions:"," headers:",` Authorization: Bearer ${e.token}`].join(`
|
|
6
|
+
`)}]},Qt={id:"opencode",name:"opencode",category:"coding",brand:"opencode",methods:["remote-header","remote-oauth","stdio"],docsUrl:"https://opencode.ai/docs/mcp-servers/",verifiedOn:"2026-09-25",caveats:[],install:null,remote:e=>[P({mcp:{onlymcp:{type:"remote",url:e.endpoint,headers:{Authorization:`Bearer ${e.token}`}}}},"opencode.json")]},en={id:"gitlab-duo",name:"GitLab Duo",category:"ide",brand:"gitlab",methods:["remote-oauth"],docsUrl:"https://docs.gitlab.com/user/gitlab_duo/model_context_protocol/mcp_clients",verifiedOn:"2026-10-18",caveats:["clients.caveat.gitlabDuoUserFile"],install:null,remote:e=>[P({mcpServers:{onlymcp:{type:"http",url:e.endpoint}}},"~/.gitlab/duo/mcp.json")]},z=[Ft,Bt,Wt,Jt,jt,Ht,Gt,Qt,Vt,Kt,Yt,qt,Zt,Xt,en];function ne(e){return z.find(t=>t.id===e||t.aliases?.includes(e))}var v=e=>({title:e,annotations:{readOnlyHint:!0,openWorldHint:!1}}),w=(e,t)=>({title:e,annotations:{readOnlyHint:!1,destructiveHint:t.destructive,idempotentHint:t.idempotent,openWorldHint:t.openWorld??!1}}),tn={list_memory:v("List memory files"),memory_read:v("Read a memory file"),memory_write:w("Write a memory file",{destructive:!0,idempotent:!0}),memory_delete:w("Delete a memory file",{destructive:!0,idempotent:!1}),memory_search:v("Search memory"),memory_tree:v("Show the memory tree"),memory_move:w("Move a memory file or folder",{destructive:!1,idempotent:!1}),memory_links:v("Show memory links"),list_skills:v("List skills"),get_skill:v("Load a skill"),skill_write:w("Write a skill",{destructive:!0,idempotent:!0}),skill_delete:w("Delete a skill",{destructive:!0,idempotent:!1}),list_skill_files:v("List skill files"),get_skill_file:v("Read a skill file"),skill_file_write:w("Write a skill file",{destructive:!0,idempotent:!0}),skill_file_delete:w("Delete a skill file",{destructive:!0,idempotent:!1}),list_agents:v("List agents"),get_agent_definition:v("Load an agent definition"),agent_write:w("Write an agent",{destructive:!0,idempotent:!0}),agent_delete:w("Delete an agent",{destructive:!0,idempotent:!1}),list_connections:v("List connections"),connection_create:w("Add a connection",{destructive:!1,idempotent:!1,openWorld:!0}),connection_update:w("Update a connection",{destructive:!1,idempotent:!0}),connection_delete:w("Delete a connection",{destructive:!0,idempotent:!1}),connection_tools:v("List a connection's tools"),connection_tool_toggle:w("Switch a connection tool on or off",{destructive:!1,idempotent:!0}),connection_tool_schema:v("Show a connection tool's schema"),connection_call:w("Call a connection tool",{destructive:!0,idempotent:!1,openWorld:!0})};var sn="onlymcp",re="npx",oe=["-y","-p",sn,"onlymcp-mcp"],Ue='With the claude.ai connector synced into Claude Code, or the OnlyMCP plugin installed, you need nothing else. This local process is only for skill files on disk: it replaces the remote "onlymcp" entry in ~/.claude.json, and next to the connector or the plugin it would list every tool twice.';function ie(e){return`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}function an(e){return JSON.stringify({mcpServers:{onlymcp:e?{command:re,args:oe,env:e}:{command:re,args:oe}}},null,2)}function ln(e){let t=["[mcp_servers.onlymcp]",`command = ${ie(re)}`,`args = [${oe.map(ie).join(", ")}]`];for(let[n,i]of Object.entries(e??{}))t.push(`env = { ${n} = ${ie(i)} }`);return`${t.join(`
|
|
7
|
+
`)}
|
|
8
|
+
`}var cn={claude:"claude-code",gemini:"antigravity"};function Me(){return z.filter(e=>e.install!==null)}var dn=new Set(Me().map(e=>e.id));function un(e){let t=cn[e]??ne(e)?.id;return t&&dn.has(t)?t:void 0}function Te(e,t){return e.startsWith("~/")?on(t,e.slice(2)):e}function pn(e){return Me().map(t=>{let n=Te(t.install.configPath,e),i=t.install.stubDir?Te(t.install.stubDir,e):null,r=i?Le(i):null,o=t.install.format==="toml"?ln(r?{ONLYMCP_STUB_DIRS:r}:void 0):an();return{id:t.id,name:t.name,configPath:n,format:t.install.format,stubDir:i,stubRoot:r,snippet:o}})}function mn(e,t,n){let i={};if(j(e)){let o=Ne(e,"utf8").trim();if(o)try{i=JSON.parse(o)}catch{throw new Error(`${e} contains invalid JSON. Nothing written.`)}}let r=i.mcpServers??{};i.mcpServers={...r,[t]:n},rn(e,`${JSON.stringify(i,null,2)}
|
|
9
|
+
`,"utf8")}function gn(e,t){return j(e)&&Ne(e,"utf8").includes("[mcp_servers.onlymcp]")?"already-present":(nn(e,`
|
|
10
|
+
${t}`),"written")}function De(e,t){let n=pn(e);if(t.all){if(t.client)return{kind:"conflicting-options"};let o=n.filter(p=>j(p.configPath)),s=n.filter(p=>!j(p.configPath));return o.length===0?{kind:"none-present-for-all",targets:n}:{kind:"all",present:o,skipped:s}}if(!t.client)return{kind:"missing-client",targets:n};let i=un(t.client),r=i?n.find(o=>o.id===i):void 0;return r?{kind:"single",target:r}:{kind:"unknown-client",targets:n}}function ze(e){Re(Le(e.configPath),{recursive:!0});let t=e.format==="toml"?gn(e.configPath,e.snippet):(()=>{let n=JSON.parse(e.snippet),i=Object.entries(n.mcpServers)[0];if(!i)throw new Error(`${e.name}: empty mcpServers snippet.`);return mn(e.configPath,i[0],i[1]),"written"})();return e.stubDir&&Re(e.stubDir,{recursive:!0}),t}import{access as vn,mkdir as ae,readFile as wn,rm as We,writeFile as B}from"node:fs/promises";import{dirname as kn,join as T}from"node:path";function R(e){if(!e.startsWith(`---
|
|
11
|
+
`))return null;let t=e.indexOf(`
|
|
12
|
+
---`,3);return t<0?null:e.slice(4,t)}function $(e,t){if(!/^[a-zA-Z0-9_-]+$/.test(t))return;let n=new RegExp(`^${t}:[ \\t]*(.*)$`,"m").exec(e);if(!n)return;let r=n[1].trim().replace(/^(['"])(.*)\1$/,"$2").trim();return r.length>0?r:void 0}function je(e){let t=R(e);if(t===null)return null;let n=$(t,"description");if(!n)return null;let i=$(t,"tools"),r=i?i.replace(/^\[(.*)\]$/,"$1").split(",").map(s=>s.trim()).filter(s=>s.length>0):[],o=$(t,"model");return o?{description:n,model:o,tools:r}:{description:n,tools:r}}function Fe(e){let t=R(e);return(t===null?void 0:$(t,"description"))??""}function Be(e){let t=R(e);return t===null?!1:$(t,"x-onlymcp-stub")==="true"}function He(e){if(!e.startsWith(`---
|
|
13
|
+
`))return e;let t=e.indexOf(`
|
|
14
|
+
---`,3);if(t<0)return e;let n=e.indexOf(`
|
|
15
|
+
`,t+4);return n<0?"":e.slice(n+1)}import{readFile as fn}from"node:fs/promises";var se=()=>!1;function hn(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function yn(e){let t=e.split(/\r?\n/).map(n=>n.trim()).filter(n=>n!==""&&!n.startsWith("#")).map(n=>new RegExp(`^${n.split("*").map(hn).join(".*")}$`));return n=>t.some(i=>i.test(n))}async function Ge(e=L()){try{return yn(await fn(e,"utf8"))}catch(t){if(t.code==="ENOENT")return se;throw t}}var bn=/^[A-Za-z0-9][A-Za-z0-9._-]*$/;function F(e){if(e.length===0||e.length>200||e==="SKILL.md")return!1;let t=e.split("/");return t.length>8?!1:t.every(n=>bn.test(n)&&n!==".."&&n!==".")}var Sn=/^[a-z0-9]+(-[a-z0-9]+)*$/,Cn=/^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?$/,le="x-onlymcp-stub";function H(e){return e.replace(/:/g,"-")}function G(e){return(e??"").replace(/[\r\n]+/g," ").trim()}async function ce(e,t){let n;try{n=await wn(e,"utf8")}catch{return!0}let i=R(n);if(i===null||$(i,le)!=="true")return!1;let r=$(i,"x-onlymcp-name");return r===void 0||r===t}async function Je(e,t){return await vn(e).then(()=>!0,()=>!1)&&await ce(e,t)}async function Ve(e,t,n={}){let i=n.ignore??se,[{skills:r},{agents:o}]=await Promise.all([e.listSkills(),e.listAgents()]),s=0,p=0,a=[],l=[],c=0;for(let u of r){if(!Cn.test(u.name))continue;let f=T(t,"skills",H(u.name)),y=T(f,"SKILL.md");if(i(u.name)||i(H(u.name))){l.push(u.name),await Je(y,u.name)&&(await We(f,{recursive:!0,force:!0}),c+=1);continue}if(!await ce(y,u.name)){a.push(`skills/${H(u.name)}`);continue}await ae(f,{recursive:!0}),await B(y,`---
|
|
16
|
+
name: ${H(u.name)}
|
|
17
|
+
description: ${G(u.description)||"Synchronisiert ueber OnlyMCP. Inhalt wird bei Bedarf geladen."}
|
|
18
|
+
${le}: true
|
|
19
|
+
x-onlymcp-name: ${u.name}
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
Call \`get_skill({ name: "${u.name}" })\` on the OnlyMCP server and follow whatever comes back.
|
|
23
|
+
`,"utf8"),p+=1;try{let m=await e.getSkill(u.name);for(let b of m.files??[]){if(!F(b.path))continue;let X=await e.getSkillFile(u.name,b.path),Q=T(f,...b.path.split("/"));await ae(kn(Q),{recursive:!0}),X.encoding==="base64"?await B(Q,Buffer.from(X.content,"base64")):await B(Q,X.content,"utf8"),s+=1}}catch{}}await ae(T(t,"agents"),{recursive:!0});let g=0;for(let u of o){if(!Sn.test(u.name))continue;let f=T(t,"agents",`${u.name}.md`);if(i(u.name)){l.push(u.name),await Je(f,u.name)&&(await We(f),c+=1);continue}if(!await ce(f,u.name)){a.push(`agents/${u.name}.md`);continue}let y=G(u.description),m=u.model?G(u.model):"",b=u.tools.map(G).join(", ");await B(f,`---
|
|
24
|
+
name: ${u.name}
|
|
25
|
+
description: ${y}
|
|
26
|
+
${m?`model: ${m}
|
|
27
|
+
`:""}tools: ${b}
|
|
28
|
+
${le}: true
|
|
29
|
+
x-onlymcp-name: ${u.name}
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
Call \`get_agent_definition({ name: "${u.name}" })\` on the OnlyMCP server and follow whatever comes back.
|
|
33
|
+
`,"utf8"),g+=1}return{skills:p,agents:g,files:s,kept:a,ignored:l,removed:c}}import{readdir as En,readFile as An,realpath as qe,stat as On}from"node:fs/promises";import{join as In,relative as _n,resolve as Rn,sep as Ze}from"node:path";var xn=[/^\.env(\..+)?$/i,/^\.credentials\.json$/i,/^id_(rsa|dsa|ecdsa|ed25519)(\.pub)?$/i,/\.(pem|key|p12|pfx|jks|keystore)$/i,/^\.(npmrc|netrc|pgpass|htpasswd)$/i,/^\.git-credentials$/i];function Ke(e){return xn.some(t=>t.test(e))}var Pn=[{rule:"anthropic",re:/\bsk-ant-[A-Za-z0-9_-]{20,}/},{rule:"openai",re:/\bsk-(?!ant-)[A-Za-z0-9_-]{20,}/},{rule:"github",re:/\bgh[pousr]_[A-Za-z0-9]{36,}/},{rule:"aws",re:/\bAKIA[0-9A-Z]{16}\b/},{rule:"slack",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}/},{rule:"google",re:/\bAIza[0-9A-Za-z_-]{35}\b/},{rule:"jwt",re:/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\./},{rule:"private-key",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{rule:"url-credentials",re:/\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]{6,}@/i},{rule:"assignment",re:/\b(api[_-]?key|secret|passwd|password|token|credential)\b\s*[:=]\s*['"][^'"\s]{16,}['"]/i}];function $n(e,t){let n=e.indexOf(t);return`${e.slice(Math.max(0,n-24),n)}\u2026${t.slice(0,4)}\u2026 (${t.length} characters)`}function Ye(e){let t=[],n=e.split(`
|
|
34
|
+
`);for(let i=0;i<n.length;i+=1){let r=n[i];for(let{rule:o,re:s}of Pn){let p=s.exec(r);if(p){t.push({rule:o,line:i+1,excerpt:$n(r,p[0])});break}}}return t}var Tn=new Set([".git","node_modules","__pycache__",".venv","venv",".pytest_cache",".mypy_cache","dist","build",".next",".cache"]),Nn=2*1024*1024;function Ln(e){return e.subarray(0,8192).includes(0)}async function O(e,t={}){let n=t.maxBytes??Nn,i=await qe(Rn(e)),r=[];async function o(s){let p=await En(s,{withFileTypes:!0});for(let a of p){let l=In(s,a.name);if(a.isDirectory()){if(Tn.has(a.name))continue;await o(l);continue}let c;try{c=await qe(l)}catch{continue}if(c!==i&&!c.startsWith(i+Ze))continue;let g=_n(i,c).split(Ze).join("/");if(Ke(a.name)){r.push({absolute:c,relative:g,bytes:0,binary:!1,hits:[],skip:"denied-name",description:null});continue}let u=await On(c);if(!u.isFile())continue;if(u.size>n){r.push({absolute:c,relative:g,bytes:u.size,binary:!1,hits:[],skip:"too-large",description:null});continue}let f=await An(c),y=Ln(f),m=!y&&g.toLowerCase().endsWith(".md")?f.toString("utf8"):null;r.push({absolute:c,relative:g,bytes:u.size,binary:y,hits:y?[]:Ye(f.toString("utf8")),skip:m!==null&&Be(m)?"stub":null,description:m===null?null:Fe(m)||null})}}return await o(i),r.sort((s,p)=>s.relative.localeCompare(p.relative)),r}import{basename as Mn}from"node:path";var Un=/^[a-z0-9][a-z0-9._-]*(\/[a-z0-9][a-z0-9._-]*)*\.md$/i;function W(e){return Un.test(e)&&!e.split("/").includes("..")}var Dn=/^[a-z0-9]+(-[a-z0-9]+)*$/,zn=/^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?$/,Xe=/^[a-z0-9][a-z0-9._-]*$/i;function jn(e){let t=e.split("/"),n=t.indexOf("skills");return n>=4&&t[n-4]==="cache"&&n+1<t.length-1?{prefix:t.slice(0,n+2).join("/"),name:`${t[n-2]}:${t[n+1]}`,plugin:t[n-2],path:t.slice(n+2).join("/")}:t.length<2?null:{prefix:t[0],name:t[0],plugin:null,path:t.slice(1).join("/")}}function J(e){return e.skip===null&&e.hits.length===0}function V(e){return e.skip??(e.hits.length>0?"flagged":"unusable")}function Fn(e,t={}){let n=[],i=[],r=[],o=new Map,s=t.root?Mn(t.root.replace(/[/\\]+$/,"")):"",p=Xe.test(s)?{key:s,kind:"directory",label:s}:null;for(let a of e){let l=jn(a.relative);if(!l){i.push({relative:a.relative,reason:"not-in-a-skill-folder"});continue}let c=o.get(l.prefix);c?c.entries.push(a):o.set(l.prefix,{at:l,entries:[a]})}for(let[a,{at:l,entries:c}]of o){let g=l.name;if(!zn.test(g)){i.push({relative:a,reason:"invalid-name"});continue}let u=c.find(m=>m.relative===`${a}/SKILL.md`);if(!u){i.push({relative:a,reason:"no-skill-md"});continue}if(!J(u)){i.push({relative:`${a}/SKILL.md`,reason:V(u)});continue}let f=[];for(let m of c){if(m===u)continue;if(!J(m)){i.push({relative:m.relative,reason:V(m)});continue}let b=m.relative.slice(a.length+1);if(!F(b)){i.push({relative:m.relative,reason:"invalid-path"});continue}f.push({path:b,absolute:m.absolute,bytes:m.bytes,binary:m.binary})}f.sort((m,b)=>m.path.localeCompare(b.path));let y=u.description??"";y||r.push(g),n.push({name:g,source:u.absolute,sourcePath:u.relative,description:y,collection:l.plugin?Xe.test(l.plugin)?{key:l.plugin,kind:"plugin",label:l.plugin}:null:p,bytes:u.bytes,files:f})}return n.sort((a,l)=>a.name.localeCompare(l.name)),r.sort(),{plans:n,skipped:i,noDescription:r}}function Bn(e){let t=[],n=[];for(let i of e){if(i.relative.includes("/")){n.push({relative:i.relative,reason:"not-at-top-level"});continue}if(!i.relative.endsWith(".md")){n.push({relative:i.relative,reason:"not-markdown"});continue}let r=i.relative.slice(0,-3);if(!Dn.test(r)){n.push({relative:i.relative,reason:"invalid-name"});continue}if(!J(i)){n.push({relative:i.relative,reason:V(i)});continue}t.push({name:r,source:i.absolute,bytes:i.bytes})}return t.sort((i,r)=>i.name.localeCompare(r.name)),{plans:t,skipped:n}}function Hn(e){let t=[],n=[];for(let i of e){if(!W(i.relative)){n.push({relative:i.relative,reason:"invalid-path"});continue}if(!J(i)){n.push({relative:i.relative,reason:V(i)});continue}t.push({path:i.relative,source:i.absolute,bytes:i.bytes})}return t.sort((i,r)=>i.path.localeCompare(r.path)),{plans:t,skipped:n}}function de(e,t={}){let n=Fn(e.skills??[],{root:t.skillsRoot}),i=Bn(e.agents??[]),r=Hn(e.memory??[]),o=[...e.skills??[],...e.agents??[],...e.memory??[]];return{skills:n.plans,agents:i.plans,memory:r.plans,flagged:o.filter(s=>s.skip===null&&s.hits.length>0),skipped:[...n.skipped,...i.skipped,...r.skipped],noDescription:n.noDescription}}import{readFile as K}from"node:fs/promises";import{hostname as Gn}from"node:os";function Y(e){return e instanceof h?e.code:e instanceof Error?e.message:String(e)}function ue(e){return e.skills.length+e.skills.reduce((t,n)=>t+n.files.length,0)+e.agents.length+e.memory.length}async function Qe(e,t,n){let i={skills:0,skillFiles:0,agents:0,memory:0,failures:[]},r=ue(t),o=0,s=a=>n(o+=1,r,a);for(let a of t.skills){try{await e.putSkill(a.name,await K(a.source,"utf8"),{...a.description?{description:a.description}:{},sourcePath:a.sourcePath,...a.collection?{collection:a.collection}:{}}),i.skills+=1}catch(l){i.failures.push({what:`skill ${a.name}`,code:Y(l)}),o+=a.files.length,s(a.name);continue}s(a.name);for(let l of a.files){try{let c=await K(l.absolute);await e.putSkillFile(a.name,l.path,l.binary?c.toString("base64"):c.toString("utf8"),l.binary?"base64":"utf8"),i.skillFiles+=1}catch(c){i.failures.push({what:`${a.name}/${l.path}`,code:Y(c)})}s(`${a.name}/${l.path}`)}}for(let a of t.agents){try{let l=await K(a.source,"utf8"),c=je(l);if(!c){i.failures.push({what:`agent ${a.name}`,code:"no-frontmatter"}),s(a.name);continue}await e.putAgent(a.name,{...c,body:He(l)}),i.agents+=1}catch(l){i.failures.push({what:`agent ${a.name}`,code:Y(l)})}s(a.name)}let p=Gn();for(let a of t.memory){try{await e.writeMemory(a.path,await K(a.source,"utf8"),p),i.memory+=1}catch(l){i.failures.push({what:a.path,code:Y(l)})}s(a.path)}return i}function et(e){return e.skills.length===0&&e.agents.length===0&&e.memory.length===0}function tt(e){return e.skills.length+e.agents.length+e.memory.length}function pe(e){return e.scope==="personal"&&e.isOwn&&e.archivedAt===null}async function nt(e,t,n){let i={skills:[],agents:[],memory:[]};if(n.skills){let r=new Set(t.skills.map(s=>s.name)),{skills:o}=await e.listSkills();i.skills=o.filter(s=>pe(s)&&!r.has(s.name)).map(s=>s.name)}if(n.agents){let r=new Set(t.agents.map(s=>s.name)),{agents:o}=await e.listAgents();i.agents=o.filter(s=>pe(s)&&!r.has(s.name)).map(s=>s.name)}if(n.memory){let r=new Set(t.memory.map(s=>s.path)),{files:o}=await e.listMemory();i.memory=o.filter(s=>pe(s)&&!r.has(s.path)).map(s=>s.path)}return i.skills.sort(),i.agents.sort(),i.memory.sort(),i}function Wn(e){return e instanceof h?e.code:e instanceof Error?e.message:String(e)}async function it(e,t){let n={deleted:0,failures:[]},i=async(r,o,s)=>{for(let p of r)try{await s(p),n.deleted+=1}catch(a){n.failures.push({what:o(p),code:Wn(a)})}};return await i(t.skills,r=>`skill ${r}`,r=>e.deleteSkill(r)),await i(t.agents,r=>`agent ${r}`,r=>e.deleteAgent(r)),await i(t.memory,r=>r,r=>e.deleteMemory(r)),n}import{mkdir as rt,writeFile as Jn}from"node:fs/promises";import{dirname as Vn,resolve as ot,sep as Kn}from"node:path";async function st(e,t){let n=ot(t);await rt(n,{recursive:!0});let i={written:0,refused:[],collisions:[],failures:[]},{files:r}=await e.listMemory(),o=[...r].sort((p,a)=>p.scope===a.scope?p.path.localeCompare(a.path):p.scope==="personal"?-1:1),s=new Set;for(let p of o){if(!W(p.path)){i.refused.push(p.path);continue}let a=ot(n,...p.path.split("/"));if(!a.startsWith(n+Kn)){i.refused.push(p.path);continue}if(s.has(a)){i.collisions.push(p.path);continue}try{let l=await e.readMemory(p.path);await rt(Vn(a),{recursive:!0}),await Jn(a,l.content,"utf8"),s.add(a),i.written+=1}catch(l){i.failures.push({what:p.path,code:l instanceof Error?l.message:String(l)})}}return i}import{mkdir as at,writeFile as Yn}from"node:fs/promises";import{dirname as qn,resolve as lt,sep as Zn}from"node:path";import{unzipSync as Xn}from"fflate";async function ct(e,t){let n=lt(t);await at(n,{recursive:!0});let i={written:0,refused:[],failed:[]},r=Xn(e);for(let[o,s]of Object.entries(r)){if(o.endsWith("/"))continue;let p=o.split("/");if(o.startsWith("/")||/^[a-zA-Z]:/.test(o)||o.includes("\\")||p.some(c=>c===""||c==="."||c==="..")){i.refused.push(o);continue}let l=lt(n,...p);if(!l.startsWith(n+Zn)){i.refused.push(o);continue}try{await at(qn(l),{recursive:!0}),await Yn(l,s),i.written+=1}catch(c){i.failed.push({path:o,reason:c instanceof Error?c.message:String(c)})}}return i}var C=process.env.ONLYMCP_API_URL??"https://only-mcp.com",E=new ei().name("onlymcp").version(U);function li(e,t){try{let n=new URL(e);return(n.protocol==="https:"||n.protocol==="http:")&&n.origin===t}catch{return!1}}E.command("login").description("Connect this device to OnlyMCP").action(async()=>{he("OnlyMCP"),d.info(`This device will register itself as: ${await te()}`);let t=await(await fetch(`${C}/api/auth/device/code`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({client_id:"onlymcp-cli"})})).json();d.info(`Code: ${t.user_code}`),d.info(t.verification_uri),console.log(await ni.toString(t.verification_uri_complete,{type:"terminal",small:!0})),li(t.verification_uri_complete,new URL(C).origin)&&await ti(t.verification_uri_complete).catch(()=>{});let n=Z();n.start("Waiting for confirmation");let i=await Ie(C,t.device_code,t.interval??5);S.save(i),n.stop("Connected");let r=new x(C,()=>S.read()),o=await gt(r),s=ft(o);s&&d.info(s),I(`${o.skills} skills, ${o.agents} agents, ${o.files} files ready`)});async function gt(e){let t=await Ge(),n={skills:0,agents:0,files:0,kept:[],ignored:new Set,removed:0};for(let i of ke()){let r=await Ve(e,i,{ignore:t});n.skills+=r.skills,n.agents+=r.agents,n.files+=r.files;for(let o of r.kept)n.kept.push(`${i}/${o}`);for(let o of r.ignored)n.ignored.add(o);n.removed+=r.removed}return n}function ft(e){if(e.ignored.size===0)return null;let t=e.removed>0?`, ${e.removed} earlier stubs removed`:"";return`${e.ignored.size} left out by ${L()}${t}`}async function fe(e,t,n){try{await e.reportRun(t,n)}catch{}}async function ci(e){try{return await new x(C,()=>e).signOut(),null}catch(t){return t instanceof h&&t.status>0?`HTTP ${t.status}`:t instanceof Error?t.message:String(t)}}E.command("logout").description("End this device's session, then remove its login and cache").action(async()=>{let e=S.read(),t=e?await ci(e):null;if(S.clear(),await new D().clear(),t){d.warn(`The session on the server could not be ended (${t}). It counts as a device until it expires, or until you remove it in the dashboard under Sessions and devices.`),d.success("Logged out on this device");return}d.success("Logged out")});E.command("status").description("Show whether this device is logged in").action(()=>{d.info(S.read()?"logged in":"not logged in")});E.command("sync").description("Rewrite the stub files").option("--memory <dir>","Also write memory as files into this folder").action(async e=>{let t=new x(C,()=>S.read()),n=await gt(t);d.success(`${n.skills} skills, ${n.agents} agents, ${n.files} files`);let i=ft(n);i&&d.info(i),n.kept.length>0&&d.warn([`Left untouched, because something of your own is already there (${n.kept.length})`,...n.kept.slice(0,10).map(s=>` ${s}`),...n.kept.length>10?[` ... and ${n.kept.length-10} more`]:[]].join(`
|
|
35
|
+
`));let r=n.skills+n.agents+n.files;if(!e.memory){await fe(t,"sync",r);return}let o=await st(t,e.memory);d.success(`${o.written} memory files to ${e.memory}`),o.collisions.length>0&&d.warn(["Team files with a path an existing file of yours already has",...o.collisions.map(s=>` ${s}`)].join(`
|
|
36
|
+
`)),o.refused.length>0&&d.warn(["Paths refused",...o.refused.map(s=>` ${s}`)].join(`
|
|
37
|
+
`)),o.failures.length>0&&(d.error([`${o.failures.length} failed`,...o.failures.map(s=>` ${s.what} ${s.code}`)].join(`
|
|
38
|
+
`)),process.exitCode=1),o.failures.length===0&&await fe(t,"sync",r+o.written)});E.command("install").description("Register OnlyMCP in an MCP client").option("--client <id>","claude-code, codex or antigravity (aliases: claude, gemini, gemini-cli)").option("--all","Write every config file that already exists on this machine").option("--print","Only print, write nothing").action(e=>{let t=process.env.ONLYMCP_HOME??mt(),n=De(t,e);function i(r){if(e.print){d.info(`${r.name} -> ${r.configPath}`),console.log(r.snippet);return}try{ze(r)==="already-present"?d.info(`${r.name} is already set up: ${r.configPath}`):d.success(`${r.name} set up: ${r.configPath}`),r.id==="claude-code"&&d.info(Ue)}catch(o){d.error(o instanceof Error?o.message:String(o)),process.exitCode=1}}switch(n.kind){case"conflicting-options":d.error("Pass either --client <id> or --all, not both."),process.exitCode=2;return;case"missing-client":d.info(`Known clients: ${n.targets.map(r=>r.id).join(", ")}`),d.info("Pass --client <id>, or --all."),process.exitCode=2;return;case"unknown-client":d.error(`Unknown client. Known: ${n.targets.map(r=>r.id).join(", ")}`),process.exitCode=1;return;case"none-present-for-all":d.error("No known client is set up on this machine yet."),d.info(`Pass --client <id> to write its config anyway. Known: ${n.targets.map(r=>r.id).join(", ")}.`),process.exitCode=2;return;case"single":i(n.target);return;case"all":for(let r of n.present)i(r);n.skipped.length>0&&d.info(`Skipped, not set up on this machine: ${n.skipped.map(r=>r.id).join(", ")}`);return}});function ge(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function q(e){return e.reduce((t,n)=>t+n.bytes,0)}function di(e,t){let n=e.skills.reduce((o,s)=>o+s.files.length,0),i=e.skills.filter(o=>o.files.length>0).length,r=e.skills.reduce((o,s)=>o+q(s.files),0);if(t.skills&&d.message(`Skills ${String(e.skills.length).padStart(4)} from ${t.skills}
|
|
39
|
+
${String(n).padStart(4)} more files across ${i} skills ${ge(q(e.skills)+r)}`),t.agents&&d.message(`Agents ${String(e.agents.length).padStart(4)} from ${t.agents} ${ge(q(e.agents))}`),t.memory&&d.message(`Memory ${String(e.memory.length).padStart(4)} from ${t.memory} ${ge(q(e.memory))}`),e.skipped.length>0){let o=new Map;for(let s of e.skipped)o.set(s.reason,(o.get(s.reason)??0)+1);d.message(["Skipped",...[...o].map(([s,p])=>` ${String(p).padStart(4)} ${s}`)].join(`
|
|
40
|
+
`))}e.noDescription.length>0&&d.warn(["Missing a description in the SKILL.md header",...e.noDescription.map(o=>` ${o}`)].join(`
|
|
41
|
+
`)),e.flagged.length>0&&d.warn(["Held back over possible credentials (none of it left this machine)",...e.flagged.flatMap(o=>[` ${o.relative}`,...o.hits.map(s=>` line ${s.line} rule ${s.rule} ${s.excerpt}`)])].join(`
|
|
42
|
+
`))}E.command("import").description("Bring existing local skills, agents and memory into OnlyMCP").option("--skills <dir>","Folder with skill subfolders, or a plugin cache (~/.claude/skills when no folder is named)").option("--agents <dir>","Folder with agent files (~/.claude/agents when no folder is named)").option("--memory <dir>","Folder with memory files (no default)").option("--dry-run","Only show what would be uploaded, then stop").option("--yes","Upload without asking first").option("--prune","Delete, after confirmation, what the account holds and the source no longer has").action(async e=>{let t=process.env.ONLYMCP_HOME??mt(),n=!!(e.skills||e.agents||e.memory),i={skills:n?e.skills:pt(t,".claude","skills"),agents:n?e.agents:pt(t,".claude","agents"),memory:e.memory};he("OnlyMCP Import");let r=Z();r.start("Reading the folders");let o;try{let[a,l,c]=await Promise.all([i.skills&&k(i.skills)?O(i.skills):Promise.resolve([]),i.agents&&k(i.agents)?O(i.agents):Promise.resolve([]),i.memory&&k(i.memory)?O(i.memory):Promise.resolve([])]);o=de({skills:a,agents:l,memory:c},{skillsRoot:i.skills})}catch(a){r.stop("Aborted"),d.error(a instanceof Error?a.message:String(a)),process.exitCode=1;return}r.stop("Read"),di(o,{skills:i.skills&&k(i.skills)?i.skills:void 0,agents:i.agents&&k(i.agents)?i.agents:void 0,memory:i.memory&&k(i.memory)?i.memory:void 0});let s=ue(o);if(s===0&&!e.prune){I("Nothing to do.");return}if(e.dryRun&&!e.prune){I("Nothing uploaded (--dry-run).");return}let p=new x(C,()=>S.read());try{let a=await p.getMe();d.info(`Target ${C} as ${a.email} plan ${a.plan.name}`)}catch(a){d.error(`Not logged in, or the server is unreachable (${a instanceof Error?a.message:String(a)}).`),d.info("First: npx onlymcp login"),process.exitCode=1;return}if(s>0&&!e.yes&&!e.dryRun){if(!process.stdin.isTTY){d.error("No terminal for the prompt. Run --dry-run first, then --yes."),process.exitCode=1;return}let a=await ut({message:`Upload ${s} entries?`,initialValue:!1});if(me(a)||!a){dt("Cancelled. Nothing uploaded.");return}if(o.flagged.length>0){let l=await Qn({message:"Send the held-back files anyway?",options:o.flagged.map(c=>({value:c.relative,label:c.relative})),required:!1,initialValues:[]});if(me(l)){dt("Cancelled. Nothing uploaded.");return}if(l.length>0){let c=new Set(l),g=m=>m.map(b=>c.has(b.relative)?{...b,hits:[]}:b),[u,f,y]=await Promise.all([i.skills&&k(i.skills)?O(i.skills):Promise.resolve([]),i.agents&&k(i.agents)?O(i.agents):Promise.resolve([]),i.memory&&k(i.memory)?O(i.memory):Promise.resolve([])]);o=de({skills:g(u),agents:g(f),memory:g(y)},{skillsRoot:i.skills})}}}if(s>0&&!e.dryRun){let a=Z();a.start("Uploading");let l=await Qe(p,o,(c,g,u)=>{a.message(`${c}/${g} ${u}`)});a.stop("Done"),d.success(`${l.skills} skills, ${l.skillFiles} skill files, ${l.agents} agents, ${l.memory} memory files`),l.failures.length>0?(d.error([`${l.failures.length} failed`,...l.failures.map(c=>` ${c.what} ${c.code}`)].join(`
|
|
43
|
+
`)),process.exitCode=1):await fe(p,"import",l.skills+l.skillFiles+l.agents+l.memory)}if(e.prune){let a={skills:!!(i.skills&&k(i.skills)),agents:!!(i.agents&&k(i.agents)),memory:!!(i.memory&&k(i.memory))},l=await nt(p,o,a);if(et(l))d.info("Nothing to delete: the account holds nothing the source does not know.");else if(d.warn(["Would delete (only your own, nothing from a team, nothing archived)",...l.skills.map(c=>` Skill ${c}`),...l.agents.map(c=>` Agent ${c}`),...l.memory.map(c=>` Memory ${c}`)].join(`
|
|
44
|
+
`)),e.dryRun)d.info("Nothing deleted (--dry-run).");else if(!process.stdin.isTTY)d.error("No terminal for the prompt. Nothing deleted."),process.exitCode=1;else{let c=await ut({message:`Delete ${tt(l)} entries in the account?`,initialValue:!1});if(me(c)||!c)d.info("Nothing deleted.");else{let g=await it(p,l);d.success(`${g.deleted} deleted`),g.failures.length>0&&(d.error([`${g.failures.length} failed`,...g.failures.map(u=>` ${u.what} ${u.code}`)].join(`
|
|
45
|
+
`)),process.exitCode=1)}}}I(e.dryRun?"Nothing changed (--dry-run).":"Then: npx onlymcp sync on every device.")});E.command("export").description("Get everything out of the account: memory, skills, agents, connections").option("--out <dir>","Unpack the archive into this folder").option("--zip <file>","Keep the archive as a file").option("--scope <scope>","personal, all or team:<id>","all").option("--types <list>","memory,skills,agents,connections").action(async e=>{if((e.out?1:0)+(e.zip?1:0)!=1){d.error("Either --out <folder> or --zip <file>, not both and not neither."),process.exitCode=1;return}he("OnlyMCP Export");let t=new x(C,()=>S.read()),n=Z();n.start("Fetching the archive");let i;try{i=await t.exportArchive({scope:e.scope,types:e.types?.split(",").map(s=>s.trim()).filter(s=>s!=="")})}catch(s){n.stop("Aborted"),d.error(s instanceof Error?s.message:String(s)),d.info("Not logged in? First: npx onlymcp login"),process.exitCode=1;return}if(e.zip){ri(ai(e.zip),{recursive:!0}),await oi(si.fromWeb(i.body),ii(e.zip)),n.stop("Done"),I(`Archive: ${e.zip}`);return}let r=new Uint8Array(await i.arrayBuffer()),o=await ct(r,e.out);n.stop("Done"),d.success(`${o.written} files in ${e.out}`),o.refused.length>0&&d.warn(["Not written, path in the archive is not allowed",...o.refused.map(s=>` ${s}`)].join(`
|
|
46
|
+
`)),o.failed.length>0&&(d.warn(["Not written, the filename doesn't work on this system",...o.failed.map(s=>` ${s.path} ${s.reason}`),"With --zip <file> the archive stays unchanged."].join(`
|
|
47
|
+
`)),process.exitCode=1),I(`Restore: npx onlymcp import --skills ${e.out}/skills --agents ${e.out}/agents --memory ${e.out}/memory`)});await E.parseAsync();
|