@machines-cash/agent-skill 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/bin/cli.mjs +447 -0
- package/package.json +18 -0
- package/skill/SKILL.md +30 -0
- package/skill/agents/openai.yaml +3 -0
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { createServer } from "node:http";
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const MCP_URL = process.env.MACHINES_MCP_URL?.trim() || "https://mcp.machines.cash";
|
|
12
|
+
const MCP_SERVER_URL = `${MCP_URL.replace(/\/$/, "")}/mcp`;
|
|
13
|
+
const MCP_CONNECT_CLIENT_ID = process.env.MACHINES_MCP_CONNECT_CLIENT_ID?.trim() || "cli_native";
|
|
14
|
+
const SKILL_NAME = "machines-user-ops";
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const skillSourceDir = path.resolve(__dirname, "..", "skill");
|
|
18
|
+
|
|
19
|
+
function printHelp() {
|
|
20
|
+
console.log(`machines-agent-skill
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
machines-agent-skill install --host codex|claude|both
|
|
24
|
+
machines-agent-skill auth login [--manual-key]
|
|
25
|
+
machines-agent-skill doctor
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseFlag(args, flag) {
|
|
30
|
+
const index = args.indexOf(flag);
|
|
31
|
+
if (index < 0) return null;
|
|
32
|
+
return args[index + 1] ?? null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function hasFlag(args, flag) {
|
|
36
|
+
return args.includes(flag);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function pathExists(filePath) {
|
|
40
|
+
try {
|
|
41
|
+
await fs.access(filePath);
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function ensureDir(dirPath) {
|
|
49
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function copyDir(sourceDir, targetDir) {
|
|
53
|
+
await ensureDir(targetDir);
|
|
54
|
+
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
57
|
+
const targetPath = path.join(targetDir, entry.name);
|
|
58
|
+
if (entry.isDirectory()) {
|
|
59
|
+
await copyDir(sourcePath, targetPath);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function upsertCodexMcpSection(configToml) {
|
|
67
|
+
const sectionName = "[mcp_servers.machines]";
|
|
68
|
+
const sectionBody = `${sectionName}\nurl = \"${MCP_SERVER_URL}\"\n`;
|
|
69
|
+
const sectionRegex = /^\[mcp_servers\.machines\][\s\S]*?(?=^\[[^\]]+\]|\Z)/m;
|
|
70
|
+
|
|
71
|
+
if (sectionRegex.test(configToml)) {
|
|
72
|
+
return configToml.replace(sectionRegex, sectionBody);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const suffix = configToml.endsWith("\n") ? "" : "\n";
|
|
76
|
+
return `${configToml}${suffix}\n${sectionBody}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function installCodex() {
|
|
80
|
+
const home = os.homedir();
|
|
81
|
+
const codexSkillDir = path.join(home, ".codex", "skills", SKILL_NAME);
|
|
82
|
+
const codexConfigPath = path.join(home, ".codex", "config.toml");
|
|
83
|
+
|
|
84
|
+
await copyDir(skillSourceDir, codexSkillDir);
|
|
85
|
+
|
|
86
|
+
let configToml = "";
|
|
87
|
+
if (await pathExists(codexConfigPath)) {
|
|
88
|
+
configToml = await fs.readFile(codexConfigPath, "utf8");
|
|
89
|
+
}
|
|
90
|
+
const nextConfigToml = upsertCodexMcpSection(configToml);
|
|
91
|
+
await ensureDir(path.dirname(codexConfigPath));
|
|
92
|
+
await fs.writeFile(codexConfigPath, nextConfigToml, "utf8");
|
|
93
|
+
|
|
94
|
+
console.log(`codex configured: ${codexConfigPath}`);
|
|
95
|
+
console.log(`codex skill installed: ${codexSkillDir}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function installClaude() {
|
|
99
|
+
const home = os.homedir();
|
|
100
|
+
const claudeSkillDir = path.join(home, ".claude", "skills", SKILL_NAME);
|
|
101
|
+
const claudeSettingsPath = path.join(home, ".claude", "settings.json");
|
|
102
|
+
|
|
103
|
+
await copyDir(skillSourceDir, claudeSkillDir);
|
|
104
|
+
|
|
105
|
+
let settings = {};
|
|
106
|
+
if (await pathExists(claudeSettingsPath)) {
|
|
107
|
+
try {
|
|
108
|
+
settings = JSON.parse(await fs.readFile(claudeSettingsPath, "utf8"));
|
|
109
|
+
} catch {
|
|
110
|
+
settings = {};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) {
|
|
115
|
+
settings = {};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const root = settings;
|
|
119
|
+
if (!root.mcpServers || typeof root.mcpServers !== "object" || Array.isArray(root.mcpServers)) {
|
|
120
|
+
root.mcpServers = {};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
root.mcpServers.machines = {
|
|
124
|
+
transport: "http",
|
|
125
|
+
url: MCP_SERVER_URL,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
await ensureDir(path.dirname(claudeSettingsPath));
|
|
129
|
+
await fs.writeFile(claudeSettingsPath, `${JSON.stringify(root, null, 2)}\n`, "utf8");
|
|
130
|
+
|
|
131
|
+
console.log(`claude configured: ${claudeSettingsPath}`);
|
|
132
|
+
console.log(`claude skill installed: ${claudeSkillDir}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function installCommand(args) {
|
|
136
|
+
const host = (parseFlag(args, "--host") || "both").toLowerCase();
|
|
137
|
+
if (!["codex", "claude", "both"].includes(host)) {
|
|
138
|
+
throw new Error("--host must be codex, claude, or both");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (host === "codex" || host === "both") {
|
|
142
|
+
await installCodex();
|
|
143
|
+
}
|
|
144
|
+
if (host === "claude" || host === "both") {
|
|
145
|
+
await installClaude();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function createPkceVerifier() {
|
|
150
|
+
return randomBytes(48).toString("base64url");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function createPkceChallenge(verifier) {
|
|
154
|
+
return createHash("sha256").update(verifier).digest("base64url");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function createState() {
|
|
158
|
+
return randomBytes(24).toString("hex");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function openBrowser(url) {
|
|
162
|
+
const openArgsByPlatform = {
|
|
163
|
+
darwin: ["open", [url]],
|
|
164
|
+
win32: ["cmd", ["/c", "start", "", url]],
|
|
165
|
+
linux: ["xdg-open", [url]],
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const [command, args] = openArgsByPlatform[process.platform] || openArgsByPlatform.linux;
|
|
169
|
+
const child = spawn(command, args, {
|
|
170
|
+
detached: true,
|
|
171
|
+
stdio: "ignore",
|
|
172
|
+
});
|
|
173
|
+
child.unref();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function buildCallbackPageHtml(message) {
|
|
177
|
+
return `<!doctype html><html><head><meta charset=\"utf-8\"/><title>Machines Auth</title><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/></head><body style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0b0b0b;color:#f5f5f5;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;\"><div style=\"max-width:420px;padding:16px;border:1px solid #2a2a2a;border-radius:12px;background:#111\">${message}<div style=\"margin-top:8px;color:#9ca3af;font-size:13px\">You can close this window.</div></div></body></html>`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function exchangeOAuthCode(options) {
|
|
181
|
+
const body = new URLSearchParams({
|
|
182
|
+
grant_type: "authorization_code",
|
|
183
|
+
client_id: options.clientId,
|
|
184
|
+
code: options.code,
|
|
185
|
+
redirect_uri: options.redirectUri,
|
|
186
|
+
code_verifier: options.codeVerifier,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const response = await fetch(`${MCP_URL.replace(/\/$/, "")}/oauth/token`, {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: {
|
|
192
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
193
|
+
accept: "application/json",
|
|
194
|
+
},
|
|
195
|
+
body: body.toString(),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const text = await response.text();
|
|
199
|
+
let payload;
|
|
200
|
+
try {
|
|
201
|
+
payload = JSON.parse(text);
|
|
202
|
+
} catch {
|
|
203
|
+
payload = { raw: text };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
const errorDescription =
|
|
208
|
+
typeof payload?.error_description === "string"
|
|
209
|
+
? payload.error_description
|
|
210
|
+
: typeof payload?.error === "string"
|
|
211
|
+
? payload.error
|
|
212
|
+
: "token exchange failed";
|
|
213
|
+
throw new Error(errorDescription);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!payload || typeof payload !== "object" || typeof payload.access_token !== "string") {
|
|
217
|
+
throw new Error("invalid token response");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return payload;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function saveAuth(payload) {
|
|
224
|
+
const authDir = path.join(os.homedir(), ".machines", "agent-skill");
|
|
225
|
+
const authPath = path.join(authDir, "auth.json");
|
|
226
|
+
await ensureDir(authDir);
|
|
227
|
+
|
|
228
|
+
const out = {
|
|
229
|
+
mcpBaseUrl: MCP_URL,
|
|
230
|
+
mcpServerUrl: MCP_SERVER_URL,
|
|
231
|
+
clientId: MCP_CONNECT_CLIENT_ID,
|
|
232
|
+
accessToken: payload.access_token,
|
|
233
|
+
refreshToken: typeof payload.refresh_token === "string" ? payload.refresh_token : null,
|
|
234
|
+
expiresIn: typeof payload.expires_in === "number" ? payload.expires_in : null,
|
|
235
|
+
scope: typeof payload.scope === "string" ? payload.scope : "*",
|
|
236
|
+
tokenType: typeof payload.token_type === "string" ? payload.token_type : "Bearer",
|
|
237
|
+
obtainedAt: new Date().toISOString(),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
await fs.writeFile(authPath, `${JSON.stringify(out, null, 2)}\n`, {
|
|
241
|
+
encoding: "utf8",
|
|
242
|
+
mode: 0o600,
|
|
243
|
+
});
|
|
244
|
+
return authPath;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function loginCommand(args) {
|
|
248
|
+
const manualKey = hasFlag(args, "--manual-key");
|
|
249
|
+
const state = createState();
|
|
250
|
+
const codeVerifier = createPkceVerifier();
|
|
251
|
+
const codeChallenge = createPkceChallenge(codeVerifier);
|
|
252
|
+
const authResult = await new Promise((resolve, reject) => {
|
|
253
|
+
const timeoutMs = 5 * 60 * 1000;
|
|
254
|
+
let settled = false;
|
|
255
|
+
let timeout;
|
|
256
|
+
let redirectUri = "";
|
|
257
|
+
|
|
258
|
+
const closeServer = (server) =>
|
|
259
|
+
new Promise((closeResolve) => server.close(() => closeResolve()));
|
|
260
|
+
|
|
261
|
+
const server = createServer(async (req, res) => {
|
|
262
|
+
const requestUrl = new URL(req.url || "/", "http://127.0.0.1");
|
|
263
|
+
if (requestUrl.pathname !== "/callback") {
|
|
264
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
265
|
+
res.end("not found");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const incomingState = requestUrl.searchParams.get("state");
|
|
270
|
+
if (!incomingState || incomingState !== state) {
|
|
271
|
+
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
272
|
+
res.end(buildCallbackPageHtml("Invalid OAuth state."));
|
|
273
|
+
if (!settled) {
|
|
274
|
+
settled = true;
|
|
275
|
+
clearTimeout(timeout);
|
|
276
|
+
await closeServer(server);
|
|
277
|
+
reject(new Error("state mismatch during OAuth callback"));
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const error = requestUrl.searchParams.get("error");
|
|
283
|
+
const errorDescription = requestUrl.searchParams.get("error_description");
|
|
284
|
+
const code = requestUrl.searchParams.get("code");
|
|
285
|
+
|
|
286
|
+
if (error) {
|
|
287
|
+
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
288
|
+
res.end(buildCallbackPageHtml(`Authorization failed: ${errorDescription || error}`));
|
|
289
|
+
if (!settled) {
|
|
290
|
+
settled = true;
|
|
291
|
+
clearTimeout(timeout);
|
|
292
|
+
await closeServer(server);
|
|
293
|
+
reject(new Error(errorDescription || error));
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!code) {
|
|
299
|
+
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
300
|
+
res.end(buildCallbackPageHtml("Missing authorization code."));
|
|
301
|
+
if (!settled) {
|
|
302
|
+
settled = true;
|
|
303
|
+
clearTimeout(timeout);
|
|
304
|
+
await closeServer(server);
|
|
305
|
+
reject(new Error("authorization code missing"));
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
311
|
+
res.end(buildCallbackPageHtml("Authorization complete."));
|
|
312
|
+
if (!settled) {
|
|
313
|
+
settled = true;
|
|
314
|
+
clearTimeout(timeout);
|
|
315
|
+
await closeServer(server);
|
|
316
|
+
resolve({ code, redirectUri });
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
server.once("error", (error) => {
|
|
321
|
+
if (settled) return;
|
|
322
|
+
settled = true;
|
|
323
|
+
clearTimeout(timeout);
|
|
324
|
+
reject(error);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
server.listen(0, "127.0.0.1", () => {
|
|
328
|
+
const address = server.address();
|
|
329
|
+
const port = typeof address === "object" && address ? address.port : null;
|
|
330
|
+
if (!port) {
|
|
331
|
+
reject(new Error("failed to bind local callback server"));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
336
|
+
const authorizeUrl = new URL(`${MCP_URL.replace(/\/$/, "")}/oauth/authorize`);
|
|
337
|
+
authorizeUrl.searchParams.set("response_type", "code");
|
|
338
|
+
authorizeUrl.searchParams.set("client_id", MCP_CONNECT_CLIENT_ID);
|
|
339
|
+
authorizeUrl.searchParams.set("redirect_uri", redirectUri);
|
|
340
|
+
authorizeUrl.searchParams.set("state", state);
|
|
341
|
+
authorizeUrl.searchParams.set("scope", "*");
|
|
342
|
+
authorizeUrl.searchParams.set("code_challenge", codeChallenge);
|
|
343
|
+
authorizeUrl.searchParams.set("code_challenge_method", "S256");
|
|
344
|
+
if (manualKey) {
|
|
345
|
+
authorizeUrl.searchParams.set("auth_mode", "consumer_key");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
console.log(`opening browser for Machines login...`);
|
|
349
|
+
console.log(`if browser does not open, visit: ${authorizeUrl.toString()}`);
|
|
350
|
+
try {
|
|
351
|
+
openBrowser(authorizeUrl.toString());
|
|
352
|
+
} catch {
|
|
353
|
+
// User can open URL manually.
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
timeout = setTimeout(async () => {
|
|
357
|
+
if (settled) return;
|
|
358
|
+
settled = true;
|
|
359
|
+
await closeServer(server);
|
|
360
|
+
reject(new Error("oauth login timed out"));
|
|
361
|
+
}, timeoutMs);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const tokenPayload = await exchangeOAuthCode({
|
|
366
|
+
clientId: MCP_CONNECT_CLIENT_ID,
|
|
367
|
+
code: authResult.code,
|
|
368
|
+
redirectUri: authResult.redirectUri,
|
|
369
|
+
codeVerifier,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const authPath = await saveAuth(tokenPayload);
|
|
373
|
+
console.log(`login complete. token file: ${authPath}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function doctorCommand() {
|
|
377
|
+
const home = os.homedir();
|
|
378
|
+
const checks = [
|
|
379
|
+
{
|
|
380
|
+
label: "codex config",
|
|
381
|
+
path: path.join(home, ".codex", "config.toml"),
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
label: "codex skill",
|
|
385
|
+
path: path.join(home, ".codex", "skills", SKILL_NAME, "SKILL.md"),
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
label: "claude settings",
|
|
389
|
+
path: path.join(home, ".claude", "settings.json"),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
label: "claude skill",
|
|
393
|
+
path: path.join(home, ".claude", "skills", SKILL_NAME, "SKILL.md"),
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
label: "local auth cache",
|
|
397
|
+
path: path.join(home, ".machines", "agent-skill", "auth.json"),
|
|
398
|
+
},
|
|
399
|
+
];
|
|
400
|
+
|
|
401
|
+
for (const check of checks) {
|
|
402
|
+
const exists = await pathExists(check.path);
|
|
403
|
+
console.log(`${exists ? "ok" : "missing"} ${check.label}: ${check.path}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
const response = await fetch(`${MCP_URL.replace(/\/$/, "")}/.well-known/oauth-authorization-server`);
|
|
408
|
+
console.log(`${response.ok ? "ok" : "warn"} oauth metadata: ${response.status}`);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.log(`warn oauth metadata: ${(error && error.message) || "unreachable"}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function main() {
|
|
415
|
+
const args = process.argv.slice(2);
|
|
416
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
417
|
+
printHelp();
|
|
418
|
+
process.exit(0);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const command = args[0];
|
|
422
|
+
if (command === "install") {
|
|
423
|
+
await installCommand(args.slice(1));
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (command === "auth") {
|
|
428
|
+
const sub = args[1];
|
|
429
|
+
if (sub !== "login") {
|
|
430
|
+
throw new Error("auth subcommand must be: login");
|
|
431
|
+
}
|
|
432
|
+
await loginCommand(args.slice(2));
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (command === "doctor") {
|
|
437
|
+
await doctorCommand();
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
throw new Error(`unknown command: ${command}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
main().catch((error) => {
|
|
445
|
+
console.error(`machines-agent-skill error: ${error instanceof Error ? error.message : String(error)}`);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@machines-cash/agent-skill",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Installable Machines MCP user skill and auth helper for Codex and Claude",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"machines-agent-skill": "bin/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"skill"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "echo 'no build step'",
|
|
16
|
+
"test": "echo 'no tests'"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: machines-user-ops
|
|
3
|
+
description: Use Machines MCP user operations for cards, balances, deposits, withdrawals, and safe card-detail reveal. Trigger when users ask to manage their Machines account or spend flows.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Machines User Ops
|
|
7
|
+
|
|
8
|
+
Use the Machines MCP server at `https://mcp.machines.cash/mcp`.
|
|
9
|
+
|
|
10
|
+
## Defaults
|
|
11
|
+
- Prefer concise, end-user responses.
|
|
12
|
+
- For card lists, prefer `machines.user.cards.list`.
|
|
13
|
+
- For reads, prefer `machines.consumer.read` with end-user presentation.
|
|
14
|
+
- Never ask users for internal IDs when the tool can resolve them.
|
|
15
|
+
|
|
16
|
+
## Sensitive data
|
|
17
|
+
- Never print API keys, bearer tokens, or encrypted blobs.
|
|
18
|
+
- For full card details, use `machines.consumer.card_secrets.reveal` or `reveal_card_details`.
|
|
19
|
+
- Do not manually stitch secrets flows unless debugging.
|
|
20
|
+
|
|
21
|
+
## Common tasks
|
|
22
|
+
- Create card: `machines.consumer.write` (`group="cards"`, `method="POST"`, `path=""`).
|
|
23
|
+
- Update card limits/status: `machines.consumer.write` (`group="cards"`, `method="PATCH"`, `path="{cardId}"`).
|
|
24
|
+
- Balance: `machines.consumer.read` (`group="balances"`).
|
|
25
|
+
- Transactions: `machines.consumer.read` (`group="transactions"`).
|
|
26
|
+
- Scoped session mint (if needed): `machines.consumer.sessions.create`.
|
|
27
|
+
|
|
28
|
+
## Financial writes
|
|
29
|
+
- Include `idempotencyKey` for financial write groups.
|
|
30
|
+
- Surface short actionable errors; avoid internal route details.
|