@swmansion/argent 0.5.2 → 0.6.0-next.1
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/README.md +21 -15
- package/bin/ax-service +0 -0
- package/bin/simulator-server +0 -0
- package/dist/bundled-paths.d.ts +2 -0
- package/dist/bundled-paths.js +9 -0
- package/dist/bundled-paths.js.map +1 -0
- package/dist/cli-cmds.mjs +729 -0
- package/dist/cli.d.ts +15 -6
- package/dist/cli.js +59 -12
- package/dist/cli.js.map +1 -1
- package/dist/installer.mjs +14730 -0
- package/dist/mcp-server.mjs +14352 -0
- package/dist/preview-ui/index.html +799 -0
- package/dist/tool-server.cjs +4243 -1176
- package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/libKeyboardPatch.dylib +0 -0
- package/dylibs/libNativeDevtoolsIos.dylib +0 -0
- package/package.json +16 -10
- package/skills/argent-react-native-profiler/SKILL.md +16 -12
- package/dist/auto-screenshot.d.ts +0 -22
- package/dist/auto-screenshot.js +0 -79
- package/dist/auto-screenshot.js.map +0 -1
- package/dist/cli/constants.d.ts +0 -6
- package/dist/cli/constants.js +0 -12
- package/dist/cli/constants.js.map +0 -1
- package/dist/cli/init.d.ts +0 -2
- package/dist/cli/init.js +0 -476
- package/dist/cli/init.js.map +0 -1
- package/dist/cli/mcp-configs.d.ts +0 -38
- package/dist/cli/mcp-configs.js +0 -724
- package/dist/cli/mcp-configs.js.map +0 -1
- package/dist/cli/uninstall.d.ts +0 -13
- package/dist/cli/uninstall.js +0 -389
- package/dist/cli/uninstall.js.map +0 -1
- package/dist/cli/update.d.ts +0 -1
- package/dist/cli/update.js +0 -126
- package/dist/cli/update.js.map +0 -1
- package/dist/cli/utils.d.ts +0 -27
- package/dist/cli/utils.js +0 -158
- package/dist/cli/utils.js.map +0 -1
- package/dist/content.d.ts +0 -31
- package/dist/content.js +0 -59
- package/dist/content.js.map +0 -1
- package/dist/launcher.d.ts +0 -8
- package/dist/launcher.js +0 -183
- package/dist/launcher.js.map +0 -1
- package/dist/mcp-server.d.ts +0 -1
- package/dist/mcp-server.js +0 -228
- package/dist/mcp-server.js.map +0 -1
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../argent-cli/src/run.ts
|
|
4
|
+
import * as fs2 from "node:fs";
|
|
5
|
+
import * as path2 from "node:path";
|
|
6
|
+
|
|
7
|
+
// ../argent-tools-client/src/launcher.ts
|
|
8
|
+
import * as net from "node:net";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import * as readline from "node:readline";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import { mkdir, writeFile, readFile, unlink } from "node:fs/promises";
|
|
15
|
+
var STATE_DIR = path.join(homedir(), ".argent");
|
|
16
|
+
var STATE_FILE = path.join(STATE_DIR, "tool-server.json");
|
|
17
|
+
var LOG_FILE = path.join(STATE_DIR, "tool-server.log");
|
|
18
|
+
function buildToolsServerEnv(paths, port, baseEnv = process.env) {
|
|
19
|
+
return {
|
|
20
|
+
...baseEnv,
|
|
21
|
+
PORT: String(port),
|
|
22
|
+
ARGENT_SIMULATOR_SERVER_DIR: paths.simulatorServerDir,
|
|
23
|
+
ARGENT_NATIVE_DEVTOOLS_DIR: paths.nativeDevtoolsDir
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function findFreePort() {
|
|
27
|
+
return new Promise((resolve2, reject) => {
|
|
28
|
+
const srv = net.createServer();
|
|
29
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
30
|
+
const addr = srv.address();
|
|
31
|
+
if (!addr || typeof addr !== "object") {
|
|
32
|
+
srv.close(() => reject(new Error("Could not bind to find free port")));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const port = addr.port;
|
|
36
|
+
srv.close((err) => {
|
|
37
|
+
if (err) reject(err);
|
|
38
|
+
else resolve2(port);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
srv.on("error", reject);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function isProcessAlive(pid) {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(pid, 0);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function isHealthy(port) {
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
const timer = setTimeout(() => controller.abort(), 2e3);
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetch(`http://127.0.0.1:${port}/tools`, {
|
|
57
|
+
signal: controller.signal
|
|
58
|
+
});
|
|
59
|
+
return res.ok;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
} finally {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function spawnToolsServer(paths, port) {
|
|
67
|
+
return new Promise((resolve2, reject) => {
|
|
68
|
+
let logFd;
|
|
69
|
+
try {
|
|
70
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
71
|
+
logFd = fs.openSync(LOG_FILE, "a");
|
|
72
|
+
} catch {
|
|
73
|
+
logFd = fs.openSync("/dev/null", "w");
|
|
74
|
+
}
|
|
75
|
+
const child = spawn("node", [paths.bundlePath, "start"], {
|
|
76
|
+
detached: true,
|
|
77
|
+
stdio: ["ignore", "pipe", logFd],
|
|
78
|
+
env: buildToolsServerEnv(paths, port)
|
|
79
|
+
});
|
|
80
|
+
child.unref();
|
|
81
|
+
const pid = child.pid;
|
|
82
|
+
if (!pid) {
|
|
83
|
+
reject(new Error("Failed to get PID of spawned tools server"));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let settled = false;
|
|
87
|
+
const settle = (fn) => {
|
|
88
|
+
if (settled) return;
|
|
89
|
+
settled = true;
|
|
90
|
+
fn();
|
|
91
|
+
};
|
|
92
|
+
const rl = readline.createInterface({ input: child.stdout });
|
|
93
|
+
rl.on("line", (line) => {
|
|
94
|
+
const match = line.match(/Tools server listening on http:\/\/127\.0\.0\.1:(\d+)/);
|
|
95
|
+
if (match) {
|
|
96
|
+
const actualPort = parseInt(match[1], 10);
|
|
97
|
+
rl.close();
|
|
98
|
+
child.stdout?.resume();
|
|
99
|
+
settle(() => resolve2({ port: actualPort, pid }));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
child.on("error", (err) => {
|
|
103
|
+
rl.close();
|
|
104
|
+
settle(() => reject(err));
|
|
105
|
+
});
|
|
106
|
+
child.on("exit", (code) => {
|
|
107
|
+
rl.close();
|
|
108
|
+
settle(() => reject(new Error(`tool-server exited with code ${code} before becoming ready`)));
|
|
109
|
+
});
|
|
110
|
+
const timer = setTimeout(() => {
|
|
111
|
+
rl.close();
|
|
112
|
+
settle(() => reject(new Error("Timed out waiting for tools server to become ready")));
|
|
113
|
+
}, 15e3);
|
|
114
|
+
rl.on("close", () => clearTimeout(timer));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function readState() {
|
|
118
|
+
try {
|
|
119
|
+
const raw = await readFile(STATE_FILE, "utf8");
|
|
120
|
+
return JSON.parse(raw);
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function writeState(state) {
|
|
126
|
+
await mkdir(STATE_DIR, { recursive: true });
|
|
127
|
+
await writeFile(STATE_FILE, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
128
|
+
}
|
|
129
|
+
async function clearState() {
|
|
130
|
+
try {
|
|
131
|
+
await unlink(STATE_FILE);
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function killToolServer() {
|
|
136
|
+
const state = await readState();
|
|
137
|
+
if (!state) return;
|
|
138
|
+
try {
|
|
139
|
+
process.kill(state.pid, "SIGTERM");
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
await clearState();
|
|
143
|
+
}
|
|
144
|
+
async function ensureToolsServer(paths) {
|
|
145
|
+
const state = await readState();
|
|
146
|
+
if (state) {
|
|
147
|
+
const alive = isProcessAlive(state.pid);
|
|
148
|
+
if (alive) {
|
|
149
|
+
const healthy = await isHealthy(state.port);
|
|
150
|
+
if (healthy) {
|
|
151
|
+
return `http://127.0.0.1:${state.port}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
await clearState();
|
|
155
|
+
}
|
|
156
|
+
const port = await findFreePort();
|
|
157
|
+
const { port: actualPort, pid } = await spawnToolsServer(paths, port);
|
|
158
|
+
await writeState({
|
|
159
|
+
port: actualPort,
|
|
160
|
+
pid,
|
|
161
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
162
|
+
bundlePath: paths.bundlePath
|
|
163
|
+
});
|
|
164
|
+
return `http://127.0.0.1:${actualPort}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ../argent-tools-client/src/tools-client.ts
|
|
168
|
+
function createToolsClient(options = {}) {
|
|
169
|
+
let cached = null;
|
|
170
|
+
async function baseUrl() {
|
|
171
|
+
if (process.env.ARGENT_TOOLS_URL) return process.env.ARGENT_TOOLS_URL;
|
|
172
|
+
if (cached) return cached;
|
|
173
|
+
if (!options.paths) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"tools-client: cannot spawn tool-server without `paths`; set ARGENT_TOOLS_URL or pass paths to createToolsClient()"
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
cached = await ensureToolsServer(options.paths);
|
|
179
|
+
return cached;
|
|
180
|
+
}
|
|
181
|
+
async function fetchTools() {
|
|
182
|
+
const url = await baseUrl();
|
|
183
|
+
const res = await fetch(`${url}/tools`);
|
|
184
|
+
if (!res.ok) throw new Error(`GET /tools failed: ${res.status} ${res.statusText}`);
|
|
185
|
+
const json = await res.json();
|
|
186
|
+
return json.tools;
|
|
187
|
+
}
|
|
188
|
+
async function fetchTool(name) {
|
|
189
|
+
const tools2 = await fetchTools();
|
|
190
|
+
return tools2.find((t) => t.name === name) ?? null;
|
|
191
|
+
}
|
|
192
|
+
async function callTool(name, args) {
|
|
193
|
+
const url = await baseUrl();
|
|
194
|
+
const res = await fetch(`${url}/tools/${encodeURIComponent(name)}`, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: { "Content-Type": "application/json" },
|
|
197
|
+
body: JSON.stringify(args ?? {})
|
|
198
|
+
});
|
|
199
|
+
const json = await res.json().catch(() => ({}));
|
|
200
|
+
if (!res.ok) {
|
|
201
|
+
throw new Error(json.error ?? json.message ?? `${res.status} ${res.statusText}`);
|
|
202
|
+
}
|
|
203
|
+
return { data: json.data, note: json.note };
|
|
204
|
+
}
|
|
205
|
+
return { fetchTools, fetchTool, callTool, baseUrl };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ../argent-cli/src/flag-parser.ts
|
|
209
|
+
var FlagParseException = class extends Error {
|
|
210
|
+
};
|
|
211
|
+
function isScalarType(type) {
|
|
212
|
+
return type === "string" || type === "number" || type === "integer" || type === "boolean";
|
|
213
|
+
}
|
|
214
|
+
function coerceScalar(raw, type, field) {
|
|
215
|
+
if (type === "number") {
|
|
216
|
+
const n = Number(raw);
|
|
217
|
+
if (Number.isNaN(n)) throw new FlagParseException(`--${field} expected a number, got "${raw}"`);
|
|
218
|
+
return n;
|
|
219
|
+
}
|
|
220
|
+
if (type === "integer") {
|
|
221
|
+
const n = Number(raw);
|
|
222
|
+
if (!Number.isInteger(n))
|
|
223
|
+
throw new FlagParseException(`--${field} expected an integer, got "${raw}"`);
|
|
224
|
+
return n;
|
|
225
|
+
}
|
|
226
|
+
if (type === "boolean") {
|
|
227
|
+
if (raw === "true" || raw === "1") return true;
|
|
228
|
+
if (raw === "false" || raw === "0") return false;
|
|
229
|
+
throw new FlagParseException(`--${field} expected true/false, got "${raw}"`);
|
|
230
|
+
}
|
|
231
|
+
return raw;
|
|
232
|
+
}
|
|
233
|
+
function parseJsonOrThrow(raw, label) {
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(raw);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
throw new FlagParseException(
|
|
238
|
+
`${label} could not be parsed as JSON: ${err instanceof Error ? err.message : err}`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function parseFlags(argv, schema) {
|
|
243
|
+
const properties = schema?.properties ?? {};
|
|
244
|
+
const args = {};
|
|
245
|
+
const positional = [];
|
|
246
|
+
let helpRequested = false;
|
|
247
|
+
let rawArgs = null;
|
|
248
|
+
const seenArrayFields = /* @__PURE__ */ new Set();
|
|
249
|
+
function takeNext(i, flag) {
|
|
250
|
+
if (i + 1 >= argv.length) {
|
|
251
|
+
throw new FlagParseException(`--${flag} requires a value`);
|
|
252
|
+
}
|
|
253
|
+
return { value: argv[i + 1], nextIndex: i + 1 };
|
|
254
|
+
}
|
|
255
|
+
for (let i = 0; i < argv.length; i++) {
|
|
256
|
+
const tok = argv[i];
|
|
257
|
+
if (tok === "--help" || tok === "-h") {
|
|
258
|
+
helpRequested = true;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (tok === "--") {
|
|
262
|
+
for (let j = i + 1; j < argv.length; j++) positional.push(argv[j]);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
if (!tok.startsWith("--")) {
|
|
266
|
+
positional.push(tok);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const eq = tok.indexOf("=");
|
|
270
|
+
let flag;
|
|
271
|
+
let inlineValue;
|
|
272
|
+
if (eq >= 0) {
|
|
273
|
+
flag = tok.slice(2, eq);
|
|
274
|
+
inlineValue = tok.slice(eq + 1);
|
|
275
|
+
} else {
|
|
276
|
+
flag = tok.slice(2);
|
|
277
|
+
}
|
|
278
|
+
if (flag === "args") {
|
|
279
|
+
const { value: value2, nextIndex: nextIndex2 } = inlineValue !== void 0 ? { value: inlineValue, nextIndex: i } : takeNext(i, "args");
|
|
280
|
+
rawArgs = value2;
|
|
281
|
+
i = nextIndex2;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (flag.endsWith("-json")) {
|
|
285
|
+
const fieldName = flag.slice(0, -"-json".length);
|
|
286
|
+
const { value: value2, nextIndex: nextIndex2 } = inlineValue !== void 0 ? { value: inlineValue, nextIndex: i } : takeNext(i, flag);
|
|
287
|
+
args[fieldName] = parseJsonOrThrow(value2, `--${flag}`);
|
|
288
|
+
i = nextIndex2;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (flag.startsWith("no-")) {
|
|
292
|
+
const fieldName = flag.slice(3);
|
|
293
|
+
const propSchema2 = properties[fieldName];
|
|
294
|
+
if (propSchema2?.type === "boolean") {
|
|
295
|
+
if (inlineValue !== void 0) {
|
|
296
|
+
throw new FlagParseException(`--no-${fieldName} does not take a value`);
|
|
297
|
+
}
|
|
298
|
+
args[fieldName] = false;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const propSchema = properties[flag];
|
|
303
|
+
if (propSchema?.type === "boolean") {
|
|
304
|
+
if (inlineValue !== void 0) {
|
|
305
|
+
args[flag] = coerceScalar(inlineValue, "boolean", flag);
|
|
306
|
+
} else {
|
|
307
|
+
args[flag] = true;
|
|
308
|
+
}
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (propSchema?.type === "array") {
|
|
312
|
+
const itemType = propSchema.items?.type;
|
|
313
|
+
if (!isScalarType(itemType)) {
|
|
314
|
+
throw new FlagParseException(
|
|
315
|
+
`--${flag} is an array of objects; pass it as --${flag}-json '<json>' or --args '<json>'`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
const { value: value2, nextIndex: nextIndex2 } = inlineValue !== void 0 ? { value: inlineValue, nextIndex: i } : takeNext(i, flag);
|
|
319
|
+
const coerced = coerceScalar(value2, itemType, flag);
|
|
320
|
+
if (!seenArrayFields.has(flag)) {
|
|
321
|
+
args[flag] = [coerced];
|
|
322
|
+
seenArrayFields.add(flag);
|
|
323
|
+
} else {
|
|
324
|
+
args[flag].push(coerced);
|
|
325
|
+
}
|
|
326
|
+
if (inlineValue === void 0) i = nextIndex2;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (propSchema?.type === "object") {
|
|
330
|
+
throw new FlagParseException(
|
|
331
|
+
`--${flag} is an object; pass it as --${flag}-json '<json>' or --args '<json>'`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
const { value, nextIndex } = inlineValue !== void 0 ? { value: inlineValue, nextIndex: i } : takeNext(i, flag);
|
|
335
|
+
args[flag] = coerceScalar(value, propSchema?.type, flag);
|
|
336
|
+
if (inlineValue === void 0) i = nextIndex;
|
|
337
|
+
}
|
|
338
|
+
return { args, positional, helpRequested, rawArgs };
|
|
339
|
+
}
|
|
340
|
+
function formatSchemaUsage(schema) {
|
|
341
|
+
if (!schema || !schema.properties) return " (no parameters)";
|
|
342
|
+
const required = new Set(schema.required ?? []);
|
|
343
|
+
const lines = [];
|
|
344
|
+
const entries = Object.entries(schema.properties);
|
|
345
|
+
if (entries.length === 0) return " (no parameters)";
|
|
346
|
+
let maxFlagLen = 0;
|
|
347
|
+
for (const [name, prop] of entries) {
|
|
348
|
+
const display = renderFlagName(name, prop);
|
|
349
|
+
if (display.length > maxFlagLen) maxFlagLen = display.length;
|
|
350
|
+
}
|
|
351
|
+
for (const [name, prop] of entries) {
|
|
352
|
+
const flag = renderFlagName(name, prop).padEnd(maxFlagLen, " ");
|
|
353
|
+
const typeLabel = renderType(prop);
|
|
354
|
+
const req = required.has(name) ? " (required)" : "";
|
|
355
|
+
const desc = prop.description ? ` ${prop.description}` : "";
|
|
356
|
+
lines.push(` ${flag} ${typeLabel}${req}${desc}`);
|
|
357
|
+
}
|
|
358
|
+
return lines.join("\n");
|
|
359
|
+
}
|
|
360
|
+
function renderFlagName(name, prop) {
|
|
361
|
+
if (prop.type === "object" || prop.type === "array" && !isScalarType(prop.items?.type)) {
|
|
362
|
+
return `--${name}-json <json>`;
|
|
363
|
+
}
|
|
364
|
+
if (prop.type === "boolean") return `--${name}`;
|
|
365
|
+
return `--${name} <value>`;
|
|
366
|
+
}
|
|
367
|
+
function renderType(prop) {
|
|
368
|
+
if (prop.enum && Array.isArray(prop.enum)) {
|
|
369
|
+
return `enum: ${prop.enum.map((v) => JSON.stringify(v)).join(" | ")}`;
|
|
370
|
+
}
|
|
371
|
+
if (prop.type === "array") {
|
|
372
|
+
const item = prop.items?.type ?? "any";
|
|
373
|
+
const suffix = isScalarType(prop.items?.type) ? " (repeatable)" : "";
|
|
374
|
+
return `array<${item}>${suffix}`;
|
|
375
|
+
}
|
|
376
|
+
return prop.type ?? "any";
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ../argent-cli/src/run.ts
|
|
380
|
+
function splitOptions(argv) {
|
|
381
|
+
let json = false;
|
|
382
|
+
let outPath = null;
|
|
383
|
+
const rest = [];
|
|
384
|
+
for (let i = 0; i < argv.length; i++) {
|
|
385
|
+
const tok = argv[i];
|
|
386
|
+
if (tok === "--json") {
|
|
387
|
+
json = true;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (tok === "--out") {
|
|
391
|
+
const v = argv[i + 1];
|
|
392
|
+
if (!v) throw new FlagParseException("--out requires a path");
|
|
393
|
+
outPath = v;
|
|
394
|
+
i += 1;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (tok.startsWith("--out=")) {
|
|
398
|
+
outPath = tok.slice("--out=".length);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
rest.push(tok);
|
|
402
|
+
}
|
|
403
|
+
return { json, outPath, argvForFlags: rest };
|
|
404
|
+
}
|
|
405
|
+
async function readStdin() {
|
|
406
|
+
return new Promise((resolve2, reject) => {
|
|
407
|
+
let data = "";
|
|
408
|
+
process.stdin.setEncoding("utf8");
|
|
409
|
+
process.stdin.on("data", (chunk) => {
|
|
410
|
+
data += chunk;
|
|
411
|
+
});
|
|
412
|
+
process.stdin.on("end", () => resolve2(data));
|
|
413
|
+
process.stdin.on("error", reject);
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
function printToolHelp(meta) {
|
|
417
|
+
const description = meta.description?.trim() ?? "";
|
|
418
|
+
console.log(`argent run ${meta.name} [flags]`);
|
|
419
|
+
if (description) console.log(`
|
|
420
|
+
${description}
|
|
421
|
+
`);
|
|
422
|
+
console.log("Flags:");
|
|
423
|
+
console.log(formatSchemaUsage(meta.inputSchema));
|
|
424
|
+
console.log("\nGlobal flags:");
|
|
425
|
+
console.log(" --args <json> Pass the entire payload as JSON (overrides flags)");
|
|
426
|
+
console.log(" --args - Read the entire payload as JSON from stdin");
|
|
427
|
+
console.log(" --<field>-json <json> Pass a single field as JSON (objects/nested arrays)");
|
|
428
|
+
console.log(" --json Print the raw JSON result");
|
|
429
|
+
console.log(" --out <path> For image results, save to <path> instead of fetching URL");
|
|
430
|
+
console.log(" --help, -h Show this help");
|
|
431
|
+
}
|
|
432
|
+
async function fetchImageToFile(result, outPath) {
|
|
433
|
+
const url = result.url;
|
|
434
|
+
if (!url) {
|
|
435
|
+
throw new Error("Tool result did not include a `url`; cannot save image");
|
|
436
|
+
}
|
|
437
|
+
const res = await fetch(url);
|
|
438
|
+
if (!res.ok) throw new Error(`Failed to download image: ${res.status} ${res.statusText}`);
|
|
439
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
440
|
+
fs2.mkdirSync(path2.dirname(path2.resolve(outPath)), { recursive: true });
|
|
441
|
+
fs2.writeFileSync(outPath, buf);
|
|
442
|
+
}
|
|
443
|
+
function renderResult(result, outputHint, json) {
|
|
444
|
+
if (json) return JSON.stringify(result, null, 2);
|
|
445
|
+
if (outputHint === "image" && result && typeof result === "object" && "path" in result && typeof result.path === "string") {
|
|
446
|
+
const r = result;
|
|
447
|
+
return `Saved screenshot: ${r.path}`;
|
|
448
|
+
}
|
|
449
|
+
if (typeof result === "string") return result;
|
|
450
|
+
return JSON.stringify(result, null, 2);
|
|
451
|
+
}
|
|
452
|
+
async function run(argv, options) {
|
|
453
|
+
const { fetchTool, callTool } = createToolsClient({ paths: options.paths });
|
|
454
|
+
const [toolName, ...rest] = argv;
|
|
455
|
+
if (!toolName || toolName === "--help" || toolName === "-h") {
|
|
456
|
+
console.log(`Usage: argent run <tool> [flags]
|
|
457
|
+
|
|
458
|
+
Invoke a tool exposed by the argent tool-server. Run \`argent tools\` to list
|
|
459
|
+
available tools, or \`argent tools describe <name>\` to see one tool's flags.
|
|
460
|
+
|
|
461
|
+
Examples:
|
|
462
|
+
argent run list-simulators
|
|
463
|
+
argent run gesture-tap --udid <UDID> --x 0.5 --y 0.5
|
|
464
|
+
argent run screenshot --udid <UDID> --out ./screen.png
|
|
465
|
+
argent run run-sequence --udid <UDID> --steps-json '[{"tool":"button","args":{"button":"home"}}]'
|
|
466
|
+
argent run gesture-tap --args '{"udid":"<UDID>","x":0.5,"y":0.5}'
|
|
467
|
+
`);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const { json, outPath, argvForFlags } = splitOptions(rest);
|
|
471
|
+
const meta = await fetchTool(toolName);
|
|
472
|
+
if (!meta) {
|
|
473
|
+
console.error(`Tool "${toolName}" not found. Run \`argent tools\` to list available tools.`);
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
let parsed;
|
|
477
|
+
try {
|
|
478
|
+
parsed = parseFlags(argvForFlags, meta.inputSchema);
|
|
479
|
+
} catch (err) {
|
|
480
|
+
if (err instanceof FlagParseException) {
|
|
481
|
+
console.error(`Error: ${err.message}
|
|
482
|
+
`);
|
|
483
|
+
printToolHelp(meta);
|
|
484
|
+
process.exit(2);
|
|
485
|
+
}
|
|
486
|
+
throw err;
|
|
487
|
+
}
|
|
488
|
+
if (parsed.helpRequested) {
|
|
489
|
+
printToolHelp(meta);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
let payload = {};
|
|
493
|
+
if (parsed.rawArgs !== null) {
|
|
494
|
+
let rawJson = parsed.rawArgs;
|
|
495
|
+
if (rawJson === "-") {
|
|
496
|
+
rawJson = await readStdin();
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
const parsedRaw = JSON.parse(rawJson);
|
|
500
|
+
if (parsedRaw === null || typeof parsedRaw !== "object" || Array.isArray(parsedRaw)) {
|
|
501
|
+
console.error("--args must be a JSON object");
|
|
502
|
+
process.exit(2);
|
|
503
|
+
}
|
|
504
|
+
payload = parsedRaw;
|
|
505
|
+
} catch (err) {
|
|
506
|
+
console.error(`--args is not valid JSON: ${err instanceof Error ? err.message : err}`);
|
|
507
|
+
process.exit(2);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
for (const [k, v] of Object.entries(parsed.args)) {
|
|
511
|
+
payload[k] = v;
|
|
512
|
+
}
|
|
513
|
+
let result;
|
|
514
|
+
let note;
|
|
515
|
+
try {
|
|
516
|
+
const resp = await callTool(toolName, payload);
|
|
517
|
+
result = resp.data;
|
|
518
|
+
note = resp.note;
|
|
519
|
+
} catch (err) {
|
|
520
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
if (outPath && meta.outputHint === "image" && result && typeof result === "object") {
|
|
524
|
+
try {
|
|
525
|
+
await fetchImageToFile(result, outPath);
|
|
526
|
+
} catch (err) {
|
|
527
|
+
console.error(`Failed to save image: ${err instanceof Error ? err.message : err}`);
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (note) console.error(note);
|
|
532
|
+
console.log(renderResult(result, meta.outputHint, json));
|
|
533
|
+
if (outPath && meta.outputHint === "image" && !json) {
|
|
534
|
+
console.log(`Wrote: ${outPath}`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ../argent-cli/src/tools.ts
|
|
539
|
+
function summarize(description, max = 80) {
|
|
540
|
+
if (!description) return "";
|
|
541
|
+
const firstLine = description.split("\n", 1)[0].trim();
|
|
542
|
+
if (firstLine.length <= max) return firstLine;
|
|
543
|
+
return firstLine.slice(0, max - 1).trimEnd() + "\u2026";
|
|
544
|
+
}
|
|
545
|
+
async function tools(argv, options) {
|
|
546
|
+
const { fetchTool, fetchTools } = createToolsClient({ paths: options.paths });
|
|
547
|
+
async function listTools(json2) {
|
|
548
|
+
const list = await fetchTools();
|
|
549
|
+
if (json2) {
|
|
550
|
+
console.log(JSON.stringify(list, null, 2));
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
if (list.length === 0) {
|
|
554
|
+
console.log("(no tools registered)");
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const sorted = [...list].sort((a, b) => a.name.localeCompare(b.name));
|
|
558
|
+
const maxName = sorted.reduce((m, t) => Math.max(m, t.name.length), 0);
|
|
559
|
+
for (const t of sorted) {
|
|
560
|
+
const summary = summarize(t.description);
|
|
561
|
+
console.log(` ${t.name.padEnd(maxName, " ")} ${summary}`);
|
|
562
|
+
}
|
|
563
|
+
console.log(`
|
|
564
|
+
${sorted.length} tools. Run \`argent tools describe <name>\` for details.`);
|
|
565
|
+
}
|
|
566
|
+
async function describeTool(name, json2) {
|
|
567
|
+
const meta = await fetchTool(name);
|
|
568
|
+
if (!meta) {
|
|
569
|
+
console.error(`Tool "${name}" not found. Run \`argent tools\` to list available tools.`);
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
if (json2) {
|
|
573
|
+
console.log(JSON.stringify(meta, null, 2));
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
console.log(`Tool: ${meta.name}
|
|
577
|
+
`);
|
|
578
|
+
if (meta.description) console.log(`${meta.description.trim()}
|
|
579
|
+
`);
|
|
580
|
+
console.log("Flags:");
|
|
581
|
+
console.log(formatSchemaUsage(meta.inputSchema));
|
|
582
|
+
if (meta.outputHint) console.log(`
|
|
583
|
+
Output hint: ${meta.outputHint}`);
|
|
584
|
+
}
|
|
585
|
+
const json = argv.includes("--json");
|
|
586
|
+
const positional = argv.filter((a) => !a.startsWith("--"));
|
|
587
|
+
const sub = positional[0];
|
|
588
|
+
if (!sub) {
|
|
589
|
+
await listTools(json);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (sub === "describe") {
|
|
593
|
+
const name = positional[1];
|
|
594
|
+
if (!name) {
|
|
595
|
+
console.error("Usage: argent tools describe <tool-name>");
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
await describeTool(name, json);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (sub === "--help" || sub === "-h") {
|
|
602
|
+
console.log(`Usage:
|
|
603
|
+
argent tools List available tools
|
|
604
|
+
argent tools describe <name> Show one tool's flags and description
|
|
605
|
+
|
|
606
|
+
Options:
|
|
607
|
+
--json Print machine-readable JSON
|
|
608
|
+
`);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
console.error(`Unknown subcommand: tools ${sub}`);
|
|
612
|
+
process.exit(1);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ../argent-cli/src/server.ts
|
|
616
|
+
import * as fs3 from "node:fs";
|
|
617
|
+
import * as path3 from "node:path";
|
|
618
|
+
import { homedir as homedir2 } from "node:os";
|
|
619
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
620
|
+
var STATE_DIR2 = path3.join(homedir2(), ".argent");
|
|
621
|
+
var STATE_FILE2 = path3.join(STATE_DIR2, "tool-server.json");
|
|
622
|
+
var LOG_FILE2 = path3.join(STATE_DIR2, "tool-server.log");
|
|
623
|
+
function readState2() {
|
|
624
|
+
try {
|
|
625
|
+
return JSON.parse(fs3.readFileSync(STATE_FILE2, "utf8"));
|
|
626
|
+
} catch {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function isProcessAlive2(pid) {
|
|
631
|
+
try {
|
|
632
|
+
process.kill(pid, 0);
|
|
633
|
+
return true;
|
|
634
|
+
} catch {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async function isHealthy2(port, timeoutMs = 2e3) {
|
|
639
|
+
const controller = new AbortController();
|
|
640
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
641
|
+
try {
|
|
642
|
+
const res = await fetch(`http://127.0.0.1:${port}/tools`, { signal: controller.signal });
|
|
643
|
+
return res.ok;
|
|
644
|
+
} catch {
|
|
645
|
+
return false;
|
|
646
|
+
} finally {
|
|
647
|
+
clearTimeout(timer);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
async function statusCmd(json) {
|
|
651
|
+
const state = readState2();
|
|
652
|
+
if (!state) {
|
|
653
|
+
if (json) {
|
|
654
|
+
console.log(JSON.stringify({ running: false }, null, 2));
|
|
655
|
+
} else {
|
|
656
|
+
console.log("tool-server: not running (no state file)");
|
|
657
|
+
}
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const alive = isProcessAlive2(state.pid);
|
|
661
|
+
const healthy = alive ? await isHealthy2(state.port) : false;
|
|
662
|
+
if (json) {
|
|
663
|
+
console.log(JSON.stringify({ running: alive && healthy, ...state, alive, healthy }, null, 2));
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
console.log(`tool-server:`);
|
|
667
|
+
console.log(` url: http://127.0.0.1:${state.port}`);
|
|
668
|
+
console.log(` pid: ${state.pid}`);
|
|
669
|
+
console.log(` startedAt: ${state.startedAt}`);
|
|
670
|
+
console.log(` process: ${alive ? "alive" : "dead"}`);
|
|
671
|
+
console.log(` health: ${healthy ? "ok" : "unreachable"}`);
|
|
672
|
+
if (!alive || !healthy) {
|
|
673
|
+
console.log(`
|
|
674
|
+
State file is stale; next \`argent\` invocation will respawn the server.`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async function stopCmd() {
|
|
678
|
+
const state = readState2();
|
|
679
|
+
if (!state) {
|
|
680
|
+
console.log("tool-server: not running");
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
await killToolServer();
|
|
684
|
+
console.log(`tool-server stopped (pid ${state.pid}).`);
|
|
685
|
+
}
|
|
686
|
+
function logsCmd(follow) {
|
|
687
|
+
if (!fs3.existsSync(LOG_FILE2)) {
|
|
688
|
+
console.log(`No log file at ${LOG_FILE2}`);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (!follow) {
|
|
692
|
+
process.stdout.write(fs3.readFileSync(LOG_FILE2, "utf8"));
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const child = spawn2("tail", ["-f", LOG_FILE2], { stdio: "inherit" });
|
|
696
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
697
|
+
}
|
|
698
|
+
async function server(argv) {
|
|
699
|
+
const sub = argv[0];
|
|
700
|
+
const json = argv.includes("--json");
|
|
701
|
+
const follow = argv.includes("-f") || argv.includes("--follow");
|
|
702
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
703
|
+
console.log(`Usage:
|
|
704
|
+
argent server status [--json] Show tool-server pid, port, and health
|
|
705
|
+
argent server stop Terminate the running tool-server
|
|
706
|
+
argent server logs [-f] Print (or follow) the tool-server log
|
|
707
|
+
`);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
switch (sub) {
|
|
711
|
+
case "status":
|
|
712
|
+
await statusCmd(json);
|
|
713
|
+
return;
|
|
714
|
+
case "stop":
|
|
715
|
+
await stopCmd();
|
|
716
|
+
return;
|
|
717
|
+
case "logs":
|
|
718
|
+
logsCmd(follow);
|
|
719
|
+
return;
|
|
720
|
+
default:
|
|
721
|
+
console.error(`Unknown subcommand: server ${sub}`);
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
export {
|
|
726
|
+
run,
|
|
727
|
+
server,
|
|
728
|
+
tools
|
|
729
|
+
};
|