@almyty/agents 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/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @almyty/agents
2
+
3
+ List, run, and manage almyty agents from the command line.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ $ npx @almyty/auth login
9
+ $ npx @almyty/agents list
10
+ $ npx @almyty/agents run my-agent --input '{"text": "hello"}' --watch
11
+ ```
12
+
13
+ ## Commands
14
+
15
+ | Command | Description |
16
+ |---------|-------------|
17
+ | `list [--json]` | List all agents in your organization |
18
+ | `get <name\|id>` | Show details for one agent |
19
+ | `run <name\|id> [options]` | Invoke a workflow or start an autonomous run |
20
+ | `runs <name\|id>` | List recent runs for an agent |
21
+ | `cancel <name\|id> <runId>` | Cancel an in-flight run |
22
+
23
+ `<name|id>` accepts an agent name (case-insensitive) or UUID.
24
+
25
+ ## Run options
26
+
27
+ | Flag | Description |
28
+ |------|-------------|
29
+ | `--input '<json>'` | Input payload (object for workflow, string or object for autonomous) |
30
+ | `--watch` | Stream steps as they arrive |
31
+ | `--json` | Print raw JSON output |
32
+
33
+ ## Environment variables
34
+
35
+ | Variable | Description |
36
+ |----------|-------------|
37
+ | `ALMYTY_TOKEN` | Auth token override |
38
+ | `ALMYTY_URL` | API URL override |
39
+
40
+ ## Authentication
41
+
42
+ Requires `npx @almyty/auth login` first. Reads credentials from `~/.almyty/credentials.json`.
43
+
44
+ ## Docs
45
+
46
+ https://almyty.com/docs
47
+
48
+ ## License
49
+
50
+ BSL-1.1
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Tiny HTTP client for the almyty REST API, scoped to the surface
3
+ * needed by @almyty/agents (list / get / invoke / runs).
4
+ */
5
+ export interface AgentInfo {
6
+ id: string;
7
+ name: string;
8
+ description?: string;
9
+ mode?: 'workflow' | 'autonomous';
10
+ status?: string;
11
+ }
12
+ export interface AgentRun {
13
+ id: string;
14
+ status: string;
15
+ output?: any;
16
+ error?: string;
17
+ steps?: any[];
18
+ totalCost?: number;
19
+ totalTokens?: number;
20
+ }
21
+ export declare class AlmytyClient {
22
+ private readonly baseUrl;
23
+ private readonly token;
24
+ constructor(baseUrl: string, token: string);
25
+ private request;
26
+ listAgents(): Promise<AgentInfo[]>;
27
+ findAgentByNameOrId(nameOrId: string): Promise<AgentInfo | null>;
28
+ invokeAgent(agentId: string, input: Record<string, any>): Promise<any>;
29
+ startAgentRun(agentId: string, input: any, limits?: {
30
+ maxSteps?: number;
31
+ maxCostCents?: number;
32
+ maxDurationMs?: number;
33
+ }): Promise<AgentRun>;
34
+ getRun(agentId: string, runId: string): Promise<AgentRun>;
35
+ listRuns(agentId: string, page?: number, limit?: number): Promise<{
36
+ data: AgentRun[];
37
+ total: number;
38
+ }>;
39
+ sendRunInput(agentId: string, runId: string, input: string): Promise<void>;
40
+ cancelRun(agentId: string, runId: string): Promise<void>;
41
+ /**
42
+ * Poll a run until it reaches a terminal status (or waiting_input).
43
+ * Calls onStep whenever new steps appear so callers can stream output.
44
+ */
45
+ pollRun(agentId: string, runId: string, options?: {
46
+ intervalMs?: number;
47
+ timeoutMs?: number;
48
+ onStep?: (run: AgentRun) => void;
49
+ }): Promise<AgentRun>;
50
+ }
package/dist/client.js ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Tiny HTTP client for the almyty REST API, scoped to the surface
3
+ * needed by @almyty/agents (list / get / invoke / runs).
4
+ */
5
+ const TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled', 'timeout']);
6
+ export class AlmytyClient {
7
+ baseUrl;
8
+ token;
9
+ constructor(baseUrl, token) {
10
+ this.baseUrl = baseUrl.replace(/\/$/, '');
11
+ this.token = token;
12
+ }
13
+ async request(path, init = {}) {
14
+ const url = `${this.baseUrl}${path}`;
15
+ const headers = {
16
+ Authorization: `Bearer ${this.token}`,
17
+ 'Content-Type': 'application/json',
18
+ ...(init.headers || {}),
19
+ };
20
+ const res = await fetch(url, { ...init, headers });
21
+ if (!res.ok) {
22
+ if (res.status === 401) {
23
+ throw new Error('Authentication failed. Run: npx @almyty/auth login');
24
+ }
25
+ const text = await res.text().catch(() => '');
26
+ throw new Error(`API error ${res.status}: ${text}`);
27
+ }
28
+ if (res.status === 204)
29
+ return null;
30
+ return res.json();
31
+ }
32
+ async listAgents() {
33
+ const data = await this.request('/agents');
34
+ const agents = data?.data?.data || data?.data || data || [];
35
+ return agents.map((a) => ({
36
+ id: a.id,
37
+ name: a.name,
38
+ description: a.description,
39
+ mode: a.mode,
40
+ status: a.status,
41
+ }));
42
+ }
43
+ async findAgentByNameOrId(nameOrId) {
44
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
45
+ if (uuidRegex.test(nameOrId)) {
46
+ try {
47
+ const data = await this.request(`/agents/${nameOrId}`);
48
+ const a = data?.data ?? data;
49
+ if (a?.id) {
50
+ return { id: a.id, name: a.name, description: a.description, mode: a.mode, status: a.status };
51
+ }
52
+ }
53
+ catch {
54
+ // fall through to name search
55
+ }
56
+ }
57
+ const all = await this.listAgents();
58
+ const lower = nameOrId.toLowerCase();
59
+ return (all.find((a) => a.name === nameOrId) ||
60
+ all.find((a) => a.name.toLowerCase() === lower) ||
61
+ all.find((a) => a.name.toLowerCase().replace(/\s+/g, '-') === lower) ||
62
+ null);
63
+ }
64
+ async invokeAgent(agentId, input) {
65
+ const data = await this.request(`/agents/${agentId}/invoke`, {
66
+ method: 'POST',
67
+ body: JSON.stringify({ input }),
68
+ });
69
+ return data?.data ?? data;
70
+ }
71
+ async startAgentRun(agentId, input, limits) {
72
+ const data = await this.request(`/agents/${agentId}/runs`, {
73
+ method: 'POST',
74
+ body: JSON.stringify({ input, ...(limits || {}) }),
75
+ });
76
+ const run = data?.data ?? data;
77
+ return { id: run.id, status: run.status };
78
+ }
79
+ async getRun(agentId, runId) {
80
+ const data = await this.request(`/agents/${agentId}/runs/${runId}`);
81
+ return (data?.data ?? data);
82
+ }
83
+ async listRuns(agentId, page = 1, limit = 20) {
84
+ const data = await this.request(`/agents/${agentId}/runs?page=${page}&limit=${limit}`);
85
+ return {
86
+ data: data?.data ?? [],
87
+ total: data?.pagination?.total ?? 0,
88
+ };
89
+ }
90
+ async sendRunInput(agentId, runId, input) {
91
+ await this.request(`/agents/${agentId}/runs/${runId}/input`, {
92
+ method: 'POST',
93
+ body: JSON.stringify({ input }),
94
+ });
95
+ }
96
+ async cancelRun(agentId, runId) {
97
+ await this.request(`/agents/${agentId}/runs/${runId}/cancel`, { method: 'POST' });
98
+ }
99
+ /**
100
+ * Poll a run until it reaches a terminal status (or waiting_input).
101
+ * Calls onStep whenever new steps appear so callers can stream output.
102
+ */
103
+ async pollRun(agentId, runId, options = {}) {
104
+ const intervalMs = options.intervalMs ?? 1500;
105
+ const timeoutMs = options.timeoutMs ?? 5 * 60_000;
106
+ const deadline = Date.now() + timeoutMs;
107
+ let lastStepCount = -1;
108
+ while (Date.now() < deadline) {
109
+ const run = await this.getRun(agentId, runId);
110
+ if (Array.isArray(run.steps) && run.steps.length !== lastStepCount) {
111
+ lastStepCount = run.steps.length;
112
+ options.onStep?.(run);
113
+ }
114
+ if (run.status && (TERMINAL_STATUSES.has(run.status) || run.status === 'waiting_input')) {
115
+ return run;
116
+ }
117
+ await new Promise((r) => setTimeout(r, intervalMs));
118
+ }
119
+ throw new Error(`Run ${runId} did not finish within ${Math.round(timeoutMs / 1000)}s`);
120
+ }
121
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Read shared almyty credentials from ~/.almyty/credentials.json.
3
+ *
4
+ * Identical to packages/auth-cli/src/credentials.ts (intentional copy
5
+ * to keep each CLI package self-contained — these few helpers don't
6
+ * justify a workspace dependency).
7
+ */
8
+ export declare const CREDENTIALS_FILE: string;
9
+ export interface StoredCredentials {
10
+ url: string;
11
+ token: string;
12
+ email?: string;
13
+ frontendUrl?: string;
14
+ }
15
+ export declare function loadCredentials(): StoredCredentials | null;
16
+ export declare function resolveCredentialsOrExit(): StoredCredentials;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Read shared almyty credentials from ~/.almyty/credentials.json.
3
+ *
4
+ * Identical to packages/auth-cli/src/credentials.ts (intentional copy
5
+ * to keep each CLI package self-contained — these few helpers don't
6
+ * justify a workspace dependency).
7
+ */
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { homedir } from 'os';
10
+ import { join } from 'path';
11
+ export const CREDENTIALS_FILE = join(homedir(), '.almyty', 'credentials.json');
12
+ export function loadCredentials() {
13
+ try {
14
+ if (!existsSync(CREDENTIALS_FILE))
15
+ return null;
16
+ return JSON.parse(readFileSync(CREDENTIALS_FILE, 'utf-8'));
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ export function resolveCredentialsOrExit() {
23
+ const envToken = process.env.ALMYTY_TOKEN;
24
+ const envUrl = process.env.ALMYTY_URL || 'https://api.almyty.com';
25
+ if (envToken)
26
+ return { url: envUrl, token: envToken };
27
+ const stored = loadCredentials();
28
+ if (stored?.token)
29
+ return stored;
30
+ console.error('Not authenticated. Run one of:');
31
+ console.error(' npx @almyty/auth login');
32
+ console.error(' export ALMYTY_TOKEN=<your-token>');
33
+ process.exit(1);
34
+ }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @almyty/agents — list, get, run, and inspect almyty agents.
4
+ *
5
+ * npx @almyty/agents list
6
+ * npx @almyty/agents get <name|id>
7
+ * npx @almyty/agents run <name|id> [--input '<json>']
8
+ * npx @almyty/agents runs <name|id>
9
+ * npx @almyty/agents cancel <name|id> <runId>
10
+ *
11
+ * Reads credentials from ~/.almyty/credentials.json (created by
12
+ * `npx @almyty/auth login`).
13
+ */
14
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @almyty/agents — list, get, run, and inspect almyty agents.
4
+ *
5
+ * npx @almyty/agents list
6
+ * npx @almyty/agents get <name|id>
7
+ * npx @almyty/agents run <name|id> [--input '<json>']
8
+ * npx @almyty/agents runs <name|id>
9
+ * npx @almyty/agents cancel <name|id> <runId>
10
+ *
11
+ * Reads credentials from ~/.almyty/credentials.json (created by
12
+ * `npx @almyty/auth login`).
13
+ */
14
+ import { AlmytyClient } from './client.js';
15
+ import { resolveCredentialsOrExit } from './credentials.js';
16
+ const VERSION = '0.1.0';
17
+ function printHelp() {
18
+ console.log(`
19
+ @almyty/agents v${VERSION}
20
+
21
+ Usage:
22
+ npx @almyty/agents <command> [options]
23
+
24
+ Commands:
25
+ list List all agents in your organization
26
+ get <name|id> Show details for one agent
27
+ run <name|id> Invoke a workflow agent or start an autonomous run
28
+ runs <name|id> List recent runs for an agent
29
+ cancel <name|id> <runId> Cancel a running autonomous run
30
+ help Show this help
31
+
32
+ Run options:
33
+ --input '<json>' Input payload (workflow: object; autonomous: string or object)
34
+ --max-steps <n> Autonomous: max steps (default 50)
35
+ --max-cost-cents <n> Autonomous: max cost in cents (default 100)
36
+ --max-duration-ms <ms> Autonomous: max wall time in ms (default 3600000)
37
+ --watch Autonomous: stream steps as they arrive
38
+ --json Print raw JSON instead of pretty output
39
+
40
+ Environment:
41
+ ALMYTY_TOKEN Token override (skips ~/.almyty/credentials.json)
42
+ ALMYTY_URL API URL override
43
+
44
+ Login:
45
+ npx @almyty/auth login Browser-based login (writes ~/.almyty/credentials.json)
46
+ `);
47
+ }
48
+ function parseArgs(argv) {
49
+ const result = { positional: [], flags: {} };
50
+ let i = 0;
51
+ while (i < argv.length) {
52
+ const arg = argv[i];
53
+ if (arg === '--help' || arg === '-h') {
54
+ result.flags.help = true;
55
+ }
56
+ else if (arg === '--version' || arg === '-v') {
57
+ result.flags.version = true;
58
+ }
59
+ else if (arg === '--watch') {
60
+ result.flags.watch = true;
61
+ }
62
+ else if (arg === '--json') {
63
+ result.flags.json = true;
64
+ }
65
+ else if (arg.startsWith('--')) {
66
+ const key = arg.slice(2);
67
+ const next = argv[i + 1];
68
+ if (next && !next.startsWith('--')) {
69
+ result.flags[key] = next;
70
+ i++;
71
+ }
72
+ else {
73
+ result.flags[key] = true;
74
+ }
75
+ }
76
+ else if (!result.command) {
77
+ result.command = arg;
78
+ }
79
+ else {
80
+ result.positional.push(arg);
81
+ }
82
+ i++;
83
+ }
84
+ return result;
85
+ }
86
+ function newClient() {
87
+ const creds = resolveCredentialsOrExit();
88
+ return new AlmytyClient(creds.url, creds.token);
89
+ }
90
+ function formatAgent(a, prefix = '') {
91
+ const mode = a.mode ? ` [${a.mode}]` : '';
92
+ const status = a.status ? ` (${a.status})` : '';
93
+ const desc = a.description ? `\n${prefix} ${a.description}` : '';
94
+ return `${prefix}${a.name}${mode}${status}${desc}`;
95
+ }
96
+ async function cmdList(args) {
97
+ const client = newClient();
98
+ const agents = await client.listAgents();
99
+ if (args.flags.json) {
100
+ console.log(JSON.stringify(agents, null, 2));
101
+ return;
102
+ }
103
+ if (agents.length === 0) {
104
+ console.log('No agents found. Create one at https://app.almyty.com/agents');
105
+ return;
106
+ }
107
+ console.log(`\n${agents.length} agent(s):\n`);
108
+ for (const a of agents) {
109
+ console.log(' ' + formatAgent(a, ' ').trimStart());
110
+ console.log('');
111
+ }
112
+ }
113
+ async function cmdGet(args) {
114
+ const ref = args.positional[0];
115
+ if (!ref) {
116
+ console.error('Usage: npx @almyty/agents get <name|id>');
117
+ process.exit(1);
118
+ }
119
+ const client = newClient();
120
+ const agent = await client.findAgentByNameOrId(ref);
121
+ if (!agent) {
122
+ console.error(`Agent not found: ${ref}`);
123
+ process.exit(1);
124
+ }
125
+ if (args.flags.json) {
126
+ console.log(JSON.stringify(agent, null, 2));
127
+ return;
128
+ }
129
+ console.log('');
130
+ console.log(` ${agent.name}`);
131
+ console.log(` id: ${agent.id}`);
132
+ if (agent.mode)
133
+ console.log(` mode: ${agent.mode}`);
134
+ if (agent.status)
135
+ console.log(` status: ${agent.status}`);
136
+ if (agent.description)
137
+ console.log(` desc: ${agent.description}`);
138
+ console.log('');
139
+ }
140
+ function parseInputFlag(value) {
141
+ if (value === undefined || value === true || value === false)
142
+ return undefined;
143
+ // Try JSON first; fall back to raw string.
144
+ try {
145
+ return JSON.parse(value);
146
+ }
147
+ catch {
148
+ return value;
149
+ }
150
+ }
151
+ function formatStep(step) {
152
+ if (!step)
153
+ return null;
154
+ switch (step.type) {
155
+ case 'llm_call':
156
+ if (step.output?.content) {
157
+ return ` ${step.output.content}`;
158
+ }
159
+ return ` · llm_call (${step.tokens?.input ?? 0}↓ / ${step.tokens?.output ?? 0}↑)`;
160
+ case 'tool_call':
161
+ return ` · tool_call → ${step.input?.tool ?? '?'}`;
162
+ case 'sub_agent_call':
163
+ return ` · sub_agent_call → ${step.input?.agentId ?? '?'}`;
164
+ case 'error':
165
+ return ` ! error: ${step.error}`;
166
+ default:
167
+ return null;
168
+ }
169
+ }
170
+ async function cmdRun(args) {
171
+ const ref = args.positional[0];
172
+ if (!ref) {
173
+ console.error('Usage: npx @almyty/agents run <name|id> [--input <json>]');
174
+ process.exit(1);
175
+ }
176
+ const client = newClient();
177
+ const agent = await client.findAgentByNameOrId(ref);
178
+ if (!agent) {
179
+ console.error(`Agent not found: ${ref}`);
180
+ process.exit(1);
181
+ }
182
+ const rawInput = parseInputFlag(args.flags.input);
183
+ if (agent.mode === 'autonomous') {
184
+ const limits = {};
185
+ if (args.flags['max-steps'])
186
+ limits.maxSteps = Number(args.flags['max-steps']);
187
+ if (args.flags['max-cost-cents'])
188
+ limits.maxCostCents = Number(args.flags['max-cost-cents']);
189
+ if (args.flags['max-duration-ms'])
190
+ limits.maxDurationMs = Number(args.flags['max-duration-ms']);
191
+ const runStub = await client.startAgentRun(agent.id, rawInput ?? '', limits);
192
+ if (!args.flags.watch) {
193
+ if (args.flags.json) {
194
+ console.log(JSON.stringify(runStub, null, 2));
195
+ }
196
+ else {
197
+ console.log(`Started run ${runStub.id}`);
198
+ console.log(`Watch: npx @almyty/agents runs ${ref}`);
199
+ console.log(`Cancel: npx @almyty/agents cancel ${ref} ${runStub.id}`);
200
+ }
201
+ return;
202
+ }
203
+ let printed = 0;
204
+ const final = await client.pollRun(agent.id, runStub.id, {
205
+ onStep: (run) => {
206
+ const newSteps = (run.steps ?? []).slice(printed);
207
+ for (const step of newSteps) {
208
+ const line = formatStep(step);
209
+ if (line)
210
+ console.log(line);
211
+ }
212
+ printed = run.steps?.length ?? printed;
213
+ },
214
+ });
215
+ if (args.flags.json) {
216
+ console.log(JSON.stringify(final, null, 2));
217
+ return;
218
+ }
219
+ console.log('');
220
+ console.log(`Run ${final.status}.`);
221
+ if (final.output != null) {
222
+ console.log('');
223
+ console.log(typeof final.output === 'string' ? final.output : JSON.stringify(final.output, null, 2));
224
+ }
225
+ if (final.error)
226
+ console.error(`Error: ${final.error}`);
227
+ return;
228
+ }
229
+ // workflow mode
230
+ const result = await client.invokeAgent(agent.id, rawInput ?? {});
231
+ if (args.flags.json) {
232
+ console.log(JSON.stringify(result, null, 2));
233
+ return;
234
+ }
235
+ const output = result?.output ?? result;
236
+ if (output == null) {
237
+ console.log('(no output)');
238
+ }
239
+ else if (typeof output === 'string') {
240
+ console.log(output);
241
+ }
242
+ else {
243
+ console.log(JSON.stringify(output, null, 2));
244
+ }
245
+ }
246
+ async function cmdRuns(args) {
247
+ const ref = args.positional[0];
248
+ if (!ref) {
249
+ console.error('Usage: npx @almyty/agents runs <name|id>');
250
+ process.exit(1);
251
+ }
252
+ const client = newClient();
253
+ const agent = await client.findAgentByNameOrId(ref);
254
+ if (!agent) {
255
+ console.error(`Agent not found: ${ref}`);
256
+ process.exit(1);
257
+ }
258
+ const { data, total } = await client.listRuns(agent.id);
259
+ if (args.flags.json) {
260
+ console.log(JSON.stringify({ total, data }, null, 2));
261
+ return;
262
+ }
263
+ if (data.length === 0) {
264
+ console.log('No runs yet.');
265
+ return;
266
+ }
267
+ console.log(`\n${total} run(s):\n`);
268
+ for (const run of data) {
269
+ console.log(` ${run.id} ${run.status}`);
270
+ }
271
+ }
272
+ async function cmdCancel(args) {
273
+ const ref = args.positional[0];
274
+ const runId = args.positional[1];
275
+ if (!ref || !runId) {
276
+ console.error('Usage: npx @almyty/agents cancel <name|id> <runId>');
277
+ process.exit(1);
278
+ }
279
+ const client = newClient();
280
+ const agent = await client.findAgentByNameOrId(ref);
281
+ if (!agent) {
282
+ console.error(`Agent not found: ${ref}`);
283
+ process.exit(1);
284
+ }
285
+ await client.cancelRun(agent.id, runId);
286
+ console.log(`Cancelled ${runId}`);
287
+ }
288
+ async function main() {
289
+ const args = parseArgs(process.argv.slice(2));
290
+ if (args.flags.version) {
291
+ console.log(VERSION);
292
+ return;
293
+ }
294
+ if (args.flags.help || !args.command || args.command === 'help') {
295
+ printHelp();
296
+ return;
297
+ }
298
+ switch (args.command) {
299
+ case 'list':
300
+ await cmdList(args);
301
+ return;
302
+ case 'get':
303
+ await cmdGet(args);
304
+ return;
305
+ case 'run':
306
+ await cmdRun(args);
307
+ return;
308
+ case 'runs':
309
+ await cmdRuns(args);
310
+ return;
311
+ case 'cancel':
312
+ await cmdCancel(args);
313
+ return;
314
+ default:
315
+ console.error(`Unknown command: ${args.command}`);
316
+ printHelp();
317
+ process.exit(1);
318
+ }
319
+ }
320
+ main().catch((err) => {
321
+ console.error(`Error: ${err.message}`);
322
+ process.exit(1);
323
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@almyty/agents",
3
+ "version": "0.1.0",
4
+ "description": "List, run, and manage almyty agents from the command line",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "almyty-agents": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsx src/index.ts",
16
+ "test": "vitest run",
17
+ "test:watch": "vitest",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "almyty",
22
+ "agents",
23
+ "cli"
24
+ ],
25
+ "author": "almyty",
26
+ "license": "BSL-1.1",
27
+ "devDependencies": {
28
+ "@types/node": "^25.4.0",
29
+ "tsx": "^4.7.0",
30
+ "typescript": "^5.3.0",
31
+ "vitest": "^4.1.0"
32
+ }
33
+ }