glove-foundry 0.0.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/dist/cli.js ADDED
@@ -0,0 +1,760 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ FoundryRuntime,
4
+ FoundryServer,
5
+ writeGeneratedTypes
6
+ } from "./chunk-UNHGCCSA.js";
7
+ import {
8
+ DEFAULT_FOUNDRY_CONFIG
9
+ } from "./chunk-ZFNMFE3T.js";
10
+ import {
11
+ EMPTY_FOUNDRY_APPLICATION,
12
+ isFoundryApplication
13
+ } from "./chunk-CRWY7M66.js";
14
+
15
+ // src/cli.ts
16
+ import { watch } from "node:fs";
17
+ import { access as access2 } from "node:fs/promises";
18
+ import { spawn } from "node:child_process";
19
+ import { relative, resolve as resolve2, sep } from "node:path";
20
+ import { fileURLToPath, pathToFileURL } from "node:url";
21
+ import process2 from "node:process";
22
+
23
+ // src/env.ts
24
+ import { readFile } from "node:fs/promises";
25
+ function unquote(value) {
26
+ const trimmed = value.trim();
27
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
28
+ return trimmed.slice(1, -1);
29
+ }
30
+ const comment = trimmed.indexOf(" #");
31
+ return comment >= 0 ? trimmed.slice(0, comment).trim() : trimmed;
32
+ }
33
+ async function loadEnvFile(path) {
34
+ let source;
35
+ try {
36
+ source = await readFile(path, "utf8");
37
+ } catch {
38
+ return;
39
+ }
40
+ for (const rawLine of source.split(/\r?\n/)) {
41
+ const line = rawLine.trim();
42
+ if (!line || line.startsWith("#")) continue;
43
+ const normalized = line.startsWith("export ") ? line.slice(7) : line;
44
+ const equals = normalized.indexOf("=");
45
+ if (equals <= 0) continue;
46
+ const name = normalized.slice(0, equals).trim();
47
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) continue;
48
+ if (process.env[name] === void 0) {
49
+ process.env[name] = unquote(normalized.slice(equals + 1));
50
+ }
51
+ }
52
+ }
53
+
54
+ // src/scaffold.ts
55
+ import { access, mkdir, readdir, writeFile } from "node:fs/promises";
56
+ import { basename, dirname, resolve } from "node:path";
57
+ async function exists(path) {
58
+ try {
59
+ await access(path);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ async function scaffoldFoundryProject(options) {
66
+ const rootDir = resolve(options.directory);
67
+ if (await exists(rootDir)) {
68
+ const entries = await readdir(rootDir);
69
+ if (entries.length > 0) {
70
+ throw new Error(`Cannot scaffold into non-empty directory ${rootDir}.`);
71
+ }
72
+ }
73
+ await mkdir(rootDir, { recursive: true });
74
+ const projectName = basename(rootDir).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "") || "glove-foundry-app";
75
+ const files = /* @__PURE__ */ new Map([
76
+ [
77
+ "package.json",
78
+ `${JSON.stringify(
79
+ {
80
+ name: projectName,
81
+ version: "0.1.0",
82
+ private: true,
83
+ type: "module",
84
+ scripts: {
85
+ dev: "glove foundry dev",
86
+ start: "glove foundry start",
87
+ lint: "eslint .",
88
+ typecheck: "tsc --noEmit"
89
+ },
90
+ dependencies: {
91
+ effect: "^3.22.1",
92
+ "glove-core": "^3.5.0",
93
+ "glove-foundry": "^0.1.0",
94
+ "glove-js": "^0.3.0",
95
+ "glove-mcp": "^1.0.1",
96
+ "glove-memory": "^1.0.2",
97
+ "glove-working-environment": "^0.5.0",
98
+ zod: "^4.3.6"
99
+ },
100
+ devDependencies: {
101
+ "@types/node": "^25.2.3",
102
+ eslint: "^9.39.2",
103
+ "typescript-eslint": "^8.54.0",
104
+ typescript: "^5.9.3"
105
+ }
106
+ },
107
+ null,
108
+ 2
109
+ )}
110
+ `
111
+ ],
112
+ [
113
+ "tsconfig.json",
114
+ `${JSON.stringify(
115
+ {
116
+ compilerOptions: {
117
+ target: "ES2022",
118
+ lib: ["ES2022", "DOM"],
119
+ module: "ESNext",
120
+ moduleResolution: "Bundler",
121
+ strict: true,
122
+ noEmit: true,
123
+ skipLibCheck: true
124
+ },
125
+ include: ["agents", "src", "foundry.application.ts", "foundry.config.ts", ".foundry/routes.d.ts"]
126
+ },
127
+ null,
128
+ 2
129
+ )}
130
+ `
131
+ ],
132
+ [
133
+ "foundry.config.ts",
134
+ `import { defineConfig } from "glove-foundry/config";
135
+
136
+ export default defineConfig({
137
+ server: { port: 4141 },
138
+ execution: {
139
+ pollIntervalMs: 100,
140
+ maxConcurrent: 4,
141
+ },
142
+ });
143
+ `
144
+ ],
145
+ [
146
+ "agents/assistant/composition.ts",
147
+ `import { composeAgent } from "glove-foundry";
148
+ import notes from "./apps/notes.app.js";
149
+ import requestContext from "./layers/request-context.layer.js";
150
+ import notion from "./mcp/notion.mcp.js";
151
+ import personalMemory from "./memory/personal.memory.js";
152
+ import usage from "./subscribers/usage.subscriber.js";
153
+ import currentTime from "./tools/current-time.tool.js";
154
+
155
+ export const components = composeAgent(
156
+ notes,
157
+ requestContext,
158
+ notion,
159
+ personalMemory,
160
+ usage,
161
+ currentTime,
162
+ );
163
+ `
164
+ ],
165
+ [
166
+ "agents/assistant/agent.ts",
167
+ `import { MemoryStore } from "glove-core";
168
+ import { createAdapter } from "glove-core/models/providers";
169
+ import { defineAgent } from "glove-foundry";
170
+ import { components } from "./composition.js";
171
+ import { loadDefaultInbox } from "./inboxes/default.inbox.js";
172
+ import requestContext from "./layers/request-context.layer.js";
173
+ import personalMemory from "./memory/personal.memory.js";
174
+ import usage from "./subscribers/usage.subscriber.js";
175
+ import { assistantRepl, assistantWorkspace } from "./workbench.js";
176
+
177
+ export default defineAgent({
178
+ description: "A general-purpose Glove assistant",
179
+ components,
180
+ memory: [personalMemory],
181
+ inboxes: (_agent, context) => loadDefaultInbox(context),
182
+ workingEnvironment: assistantWorkspace,
183
+ repl: (_agent, context) => assistantRepl(context.agentId, context.messageText),
184
+ store: ({ conversationId }) => new MemoryStore(\`foundry:\${conversationId}\`),
185
+ model: createAdapter({
186
+ provider: "openrouter",
187
+ model: process.env.OPENROUTER_MODEL ?? "openai/gpt-4.1-mini",
188
+ stream: true,
189
+ }),
190
+ systemPrompt: (_agent, { message, history }) =>
191
+ [
192
+ "You are a precise, practical assistant.",
193
+ \`Current request: \${message.text}\`,
194
+ \`Prior messages: \${history.length}\`,
195
+ ].join("\\n"),
196
+ compactionInstructions: () =>
197
+ "Preserve decisions, open work, and important context.",
198
+ compactionLimit: (_agent, { messageText }) =>
199
+ messageText.length > 2_000 ? 80_000 : 40_000,
200
+ layers: [requestContext],
201
+ subscribers: [usage],
202
+ });
203
+ `
204
+ ],
205
+ [
206
+ "agents/assistant/workbench.ts",
207
+ `import { JsSession, defineFn } from "glove-js";
208
+ import { defineRepl, defineWorkingEnvironment } from "glove-foundry";
209
+ import { z } from "zod";
210
+
211
+ export const assistantWorkspace = defineWorkingEnvironment({
212
+ options: { limits: { maxVfsBytes: 32 * 1024 * 1024 } },
213
+ });
214
+
215
+ export function assistantRepl(actor: string, message: string) {
216
+ const session = JsSession.create({ actor });
217
+ session.register(defineFn({
218
+ name: "request__current",
219
+ description: "Read the current request inside the REPL",
220
+ input: z.object({}),
221
+ readOnlyHint: true,
222
+ handler: () => ({ text: message, length: message.length }),
223
+ }));
224
+ return defineRepl({ language: "javascript", session });
225
+ }
226
+ `
227
+ ],
228
+ [
229
+ "agents/assistant/layers/request-context.layer.ts",
230
+ `import { Effect } from "effect";
231
+ import { defineLayer } from "glove-foundry";
232
+
233
+ const requestContext = defineLayer({
234
+ description: "Expose Foundry request identity as a native Glove skill",
235
+ setup: ({ glove, agentId, runId, message, history }) => Effect.sync(() => {
236
+ glove.defineSkill({
237
+ name: "request-context",
238
+ description: "Read the current Foundry agent and run ids",
239
+ exposeToAgent: true,
240
+ async handler() { return \`agent=\${agentId} run=\${runId} message=\${message.text} prior=\${history.length}\`; },
241
+ });
242
+ }),
243
+ });
244
+
245
+ export default requestContext;
246
+ `
247
+ ],
248
+ [
249
+ "agents/assistant/subscribers/usage.subscriber.ts",
250
+ `import { defineSubscriber } from "glove-foundry";
251
+
252
+ const usage = defineSubscriber({
253
+ description: "Observe token consumption without changing the agent",
254
+ create: {
255
+ async record(type, data) {
256
+ if (type === "token_consumption") console.log("[tokens]", data);
257
+ },
258
+ },
259
+ });
260
+
261
+ export default usage;
262
+ `
263
+ ],
264
+ [
265
+ "foundry.application.ts",
266
+ `import { MemoryFoundryDataAdapter, defineApplication } from "glove-foundry";
267
+
268
+ export const data = new MemoryFoundryDataAdapter();
269
+
270
+ export default defineApplication({
271
+ name: "${projectName}",
272
+ data,
273
+ accounts: [],
274
+ routes: [],
275
+ bindings: [],
276
+ });
277
+ `
278
+ ],
279
+ [
280
+ "agents/assistant/tools/current-time.tool.ts",
281
+ `import { defineSharedTool } from "glove-foundry";
282
+ import { z } from "zod";
283
+
284
+ const currentTime = defineSharedTool({
285
+ description: "Return the current ISO timestamp",
286
+ tool: {
287
+ name: "current_time",
288
+ description: "Return the current ISO timestamp",
289
+ inputSchema: z.object({}),
290
+ async do() {
291
+ return { status: "success", data: new Date().toISOString() };
292
+ },
293
+ },
294
+ });
295
+
296
+ export default currentTime;
297
+ `
298
+ ],
299
+ [
300
+ "agents/assistant/apps/notes.app.ts",
301
+ `import { Effect } from "effect";
302
+ import { defineApp } from "glove-foundry";
303
+ import { z } from "zod";
304
+
305
+ const notes = defineApp({
306
+ description: "An example application installed only when selected",
307
+ config: z.object({ namespace: z.string().default("default") }),
308
+ inbound: [],
309
+ outbound: [],
310
+ install: ({ config }) => Effect.sync(() => {
311
+ return { tools: [{
312
+ name: "notes_namespace",
313
+ description: "Return the installed notes namespace",
314
+ inputSchema: z.object({}),
315
+ async do() { return { status: "success", data: config.namespace }; },
316
+ }] };
317
+ }),
318
+ });
319
+
320
+ export default notes;
321
+ `
322
+ ],
323
+ [
324
+ "agents/assistant/inboxes/default.inbox.ts",
325
+ `import type { InboxItem } from "glove-core";
326
+ import type { AgentAssemblyContext } from "glove-foundry";
327
+
328
+ export async function loadDefaultInbox(
329
+ context: AgentAssemblyContext,
330
+ ): Promise<ReadonlyArray<InboxItem>> {
331
+ // Load this conversation's inbox from your own adapter or service.
332
+ void \`\${context.agentId}:\${context.conversationId}\`;
333
+ return [];
334
+ }
335
+ `
336
+ ],
337
+ [
338
+ "agents/assistant/mcp/notion.mcp.ts",
339
+ `import { defineMcp } from "glove-foundry";
340
+
341
+ // This remains disconnected until an agent instance installs it.
342
+ const notion = defineMcp({
343
+ entry: {
344
+ name: "Notion",
345
+ description: "Search and update a Notion workspace",
346
+ url: "https://mcp.notion.com/mcp",
347
+ tags: ["notes", "workspace"],
348
+ },
349
+ });
350
+
351
+ export default notion;
352
+ `
353
+ ],
354
+ [
355
+ "agents/assistant/memory/personal.memory.ts",
356
+ `import { Effect } from "effect";
357
+ import { defineMemory } from "glove-foundry";
358
+ import { MemorySchema } from "glove-memory/core";
359
+ import { InMemoryContextAdapter } from "glove-memory/in-memory";
360
+
361
+ const schema = new MemorySchema();
362
+ const context = new InMemoryContextAdapter({ schema });
363
+
364
+ const personalMemory = defineMemory({
365
+ description: "Ambient user context backed by a replaceable memory adapter",
366
+ context: { adapter: () => Effect.succeed(context) },
367
+ });
368
+
369
+ export default personalMemory;
370
+ `
371
+ ],
372
+ [
373
+ "src/client.ts",
374
+ `import { createFoundryClient } from "glove-foundry/client";
375
+ import type { FoundryRoutes } from "../.foundry/routes.js";
376
+
377
+ export const foundry = createFoundryClient<FoundryRoutes>();
378
+
379
+ const agent = await foundry.agent("assistant").create({ workspaceId: "default" });
380
+ const conversation = await foundry.createConversation(agent.id);
381
+ const run = await foundry.send(agent.id, conversation.id, "Hello");
382
+ const completed = await run.wait();
383
+ console.log(completed.output);
384
+ `
385
+ ],
386
+ [
387
+ ".foundry/routes.d.ts",
388
+ `// Generated by Glove Foundry. Do not edit.
389
+ export type FoundryRoutes = {
390
+ readonly "assistant": typeof import("../agents/assistant/agent.js").default;
391
+ };
392
+ `
393
+ ],
394
+ [".env.example", "OPENROUTER_API_KEY=\nOPENROUTER_MODEL=openai/gpt-4.1-mini\n"],
395
+ [
396
+ "eslint.config.js",
397
+ `import tseslint from "typescript-eslint";
398
+ import foundry from "glove-foundry/eslint";
399
+
400
+ export default [...tseslint.configs.recommended, foundry];
401
+ `
402
+ ],
403
+ [".gitignore", "node_modules\n.env.local\n.foundry/manifest.json\n"],
404
+ [
405
+ "README.md",
406
+ `# ${projectName}
407
+
408
+ A file-routed Glove Foundry application.
409
+
410
+ 1. Copy \`.env.example\` to \`.env.local\` and add your API key.
411
+ 2. Install dependencies with \`pnpm install\`.
412
+ 3. Run \`pnpm dev\`.
413
+ 4. Open http://127.0.0.1:4141.
414
+
415
+ Add agents under \`agents/<route>/agent.ts\`; the file path is the route. Keep that agent's applications, transmissions, tools, MCPs, memory, inboxes, layers, subscribers, and workbench beside it, then combine reusable capabilities with \`composeAgent\`. Code-authored references use imported definition values; Foundry normalizes them to ids only when instance data is persisted. Applications and MCPs remain inert until an agent instance installs them. The scaffold mounts a sandboxed VFS/script environment and a request-aware JavaScript REPL; change or remove them in \`workbench.ts\`. Agents create future and recurring work only through Foundry's scheduling and sleep tools.
416
+ `
417
+ ]
418
+ ]);
419
+ for (const [relativePath, content] of files) {
420
+ const path = resolve(rootDir, relativePath);
421
+ await mkdir(dirname(path), { recursive: true });
422
+ await writeFile(path, content, { encoding: "utf8", flag: "wx" });
423
+ }
424
+ return { rootDir, files: [...files.keys()] };
425
+ }
426
+
427
+ // src/cli.ts
428
+ var HELP = `Glove Foundry \u2014 a file-routed runtime for agents
429
+
430
+ Usage:
431
+ glove foundry [directory] Create a Foundry application
432
+ glove foundry dev [options] Run the development runtime and DevTools
433
+ glove foundry start [options] Run without file watching
434
+ glove-foundry init [directory] Create a Foundry application
435
+ glove-foundry dev [options] Run the development runtime and DevTools
436
+ glove-foundry start [options] Run without file watching
437
+
438
+ Options:
439
+ --root <directory> Project root (default: current directory)
440
+ --port <number> Override the configured port
441
+ --host <address> Override the configured host
442
+ --no-watch Disable agent/config hot restart
443
+ `;
444
+ function parseArgs(raw) {
445
+ const args = [...raw];
446
+ const gloveSyntax = args[0] === "foundry";
447
+ if (gloveSyntax) args.shift();
448
+ const command = args[0];
449
+ const positional = [];
450
+ const flags = {};
451
+ for (let index = 1; index < args.length; index++) {
452
+ const value = args[index];
453
+ if (value.startsWith("--")) {
454
+ const name = value.slice(2);
455
+ const next = args[index + 1];
456
+ if (next && !next.startsWith("--")) {
457
+ flags[name] = next;
458
+ index++;
459
+ } else {
460
+ flags[name] = true;
461
+ }
462
+ } else {
463
+ positional.push(value);
464
+ }
465
+ }
466
+ return { command, positional, flags, gloveSyntax };
467
+ }
468
+ async function pathExists(path) {
469
+ try {
470
+ await access2(path);
471
+ return true;
472
+ } catch {
473
+ return false;
474
+ }
475
+ }
476
+ async function loadConfig(rootDir) {
477
+ const candidates = ["foundry.config.ts", "foundry.config.mts", "foundry.config.js", "foundry.config.mjs"];
478
+ for (const candidate of candidates) {
479
+ const path = resolve2(rootDir, candidate);
480
+ if (!await pathExists(path)) continue;
481
+ const url = pathToFileURL(path);
482
+ url.searchParams.set("t", String(Date.now()));
483
+ const imported = await import(url.href);
484
+ if (!imported.default) {
485
+ throw new Error(`${candidate} must have a default export.`);
486
+ }
487
+ return imported.default;
488
+ }
489
+ return {};
490
+ }
491
+ async function loadApplication(rootDir, config) {
492
+ const relativePath = config.applicationFile ?? DEFAULT_FOUNDRY_CONFIG.applicationFile;
493
+ const path = resolve2(rootDir, relativePath);
494
+ if (!await pathExists(path)) return EMPTY_FOUNDRY_APPLICATION;
495
+ const url = pathToFileURL(path);
496
+ url.searchParams.set("t", String(Date.now()));
497
+ const imported = await import(url.href);
498
+ if (!isFoundryApplication(imported.default)) {
499
+ throw new Error(`${relativePath} must default-export defineApplication(...).`);
500
+ }
501
+ return imported.default;
502
+ }
503
+ async function runWorker(parsed) {
504
+ const rootDir = resolve2(
505
+ typeof parsed.flags.root === "string" ? parsed.flags.root : process2.cwd()
506
+ );
507
+ await loadEnvFile(resolve2(rootDir, ".env"));
508
+ await loadEnvFile(resolve2(rootDir, ".env.local"));
509
+ const config = await loadConfig(rootDir);
510
+ const application = await loadApplication(rootDir, config);
511
+ const applicationFilePath = resolve2(
512
+ rootDir,
513
+ config.applicationFile ?? DEFAULT_FOUNDRY_CONFIG.applicationFile
514
+ );
515
+ const agentsDir = resolve2(
516
+ rootDir,
517
+ config.agentsDir ?? DEFAULT_FOUNDRY_CONFIG.agentsDir
518
+ );
519
+ const runtime = await FoundryRuntime.discover({
520
+ rootDir,
521
+ agentsDir,
522
+ application,
523
+ ...await pathExists(applicationFilePath) ? { applicationFilePath } : {},
524
+ config
525
+ });
526
+ await writeGeneratedTypes({
527
+ rootDir,
528
+ agents: runtime.agents,
529
+ manifest: runtime.manifest
530
+ });
531
+ await runtime.start();
532
+ const server = new FoundryServer(runtime, {
533
+ host: typeof parsed.flags.host === "string" ? parsed.flags.host : config.server?.host ?? DEFAULT_FOUNDRY_CONFIG.server.host,
534
+ port: typeof parsed.flags.port === "string" ? Number(parsed.flags.port) : config.server?.port ?? DEFAULT_FOUNDRY_CONFIG.server.port
535
+ });
536
+ const listening = await server.listen();
537
+ await runtime.health();
538
+ process2.stdout.write(`
539
+ Glove Foundry
540
+
541
+ `);
542
+ process2.stdout.write(` Local: ${listening.url}
543
+ `);
544
+ process2.stdout.write(` Agents: ${runtime.agents.length}
545
+ `);
546
+ process2.stdout.write(` Runtime: ready
547
+ `);
548
+ process2.stdout.write(` Types: .foundry/routes.d.ts
549
+
550
+ `);
551
+ let closing = false;
552
+ const close = async () => {
553
+ if (closing) return;
554
+ closing = true;
555
+ await server.close();
556
+ await runtime.stop();
557
+ };
558
+ process2.once("SIGTERM", () => void close());
559
+ process2.once("SIGINT", () => void close());
560
+ }
561
+ function shouldRestart(rootDir, changed) {
562
+ if (!changed) return false;
563
+ const normalized = relative(rootDir, resolve2(rootDir, changed)).split(sep).join("/");
564
+ return normalized.startsWith("agents/") || normalized === ".env" || normalized === ".env.local" || /^foundry\.application\.(?:ts|mts|js|mjs)$/.test(normalized) || /^foundry\.config\.(?:ts|mts|js|mjs)$/.test(normalized);
565
+ }
566
+ async function superviseDev(parsed) {
567
+ const rootDir = resolve2(
568
+ typeof parsed.flags.root === "string" ? parsed.flags.root : process2.cwd()
569
+ );
570
+ const cliPath = fileURLToPath(import.meta.url);
571
+ const tsxImport = import.meta.resolve("tsx");
572
+ let child = null;
573
+ const watchers = [];
574
+ let restarting = false;
575
+ let stopping = false;
576
+ let debounce = null;
577
+ const workerArgs = [
578
+ "--import",
579
+ tsxImport,
580
+ cliPath,
581
+ "__dev-worker",
582
+ "--root",
583
+ rootDir,
584
+ ...typeof parsed.flags.port === "string" ? ["--port", parsed.flags.port] : [],
585
+ ...typeof parsed.flags.host === "string" ? ["--host", parsed.flags.host] : []
586
+ ];
587
+ const start = () => {
588
+ child = spawn(process2.execPath, workerArgs, {
589
+ cwd: rootDir,
590
+ env: process2.env,
591
+ stdio: "inherit"
592
+ });
593
+ child.once("exit", (code) => {
594
+ child = null;
595
+ if (!stopping && !restarting && code !== 0) {
596
+ process2.stderr.write(`Foundry worker exited with code ${code ?? "unknown"}.
597
+ `);
598
+ }
599
+ });
600
+ };
601
+ const restart = () => {
602
+ if (stopping || restarting) return;
603
+ restarting = true;
604
+ process2.stdout.write("\n Change detected \u2014 restarting Foundry\u2026\n");
605
+ const old = child;
606
+ if (!old) {
607
+ restarting = false;
608
+ start();
609
+ return;
610
+ }
611
+ old.once("exit", () => {
612
+ restarting = false;
613
+ if (!stopping) start();
614
+ });
615
+ old.kill("SIGTERM");
616
+ };
617
+ start();
618
+ if (!parsed.flags["no-watch"]) {
619
+ try {
620
+ const onChange = (filename) => {
621
+ if (!shouldRestart(rootDir, filename?.toString() ?? null)) return;
622
+ if (debounce) clearTimeout(debounce);
623
+ debounce = setTimeout(restart, 120);
624
+ };
625
+ watchers.push(
626
+ watch(
627
+ rootDir,
628
+ { recursive: false },
629
+ (_event, filename) => onChange(filename?.toString() ?? null)
630
+ )
631
+ );
632
+ const defaultAgentsDir = resolve2(rootDir, "agents");
633
+ if (await pathExists(defaultAgentsDir)) {
634
+ watchers.push(
635
+ watch(
636
+ defaultAgentsDir,
637
+ { recursive: true },
638
+ (_event, filename) => onChange(
639
+ filename ? `agents/${filename.toString()}` : "agents"
640
+ )
641
+ )
642
+ );
643
+ }
644
+ for (const directory of [
645
+ "tools",
646
+ "applications",
647
+ "mcp",
648
+ "memory",
649
+ "inboxes"
650
+ ]) {
651
+ const watched = resolve2(rootDir, directory);
652
+ if (await pathExists(watched)) {
653
+ watchers.push(
654
+ watch(
655
+ watched,
656
+ { recursive: true },
657
+ (_event, filename) => onChange(
658
+ filename ? `${directory}/${filename.toString()}` : directory
659
+ )
660
+ )
661
+ );
662
+ }
663
+ }
664
+ } catch (error) {
665
+ const message = error instanceof Error ? error.message : String(error);
666
+ process2.stderr.write(`Foundry file watching is unavailable: ${message}
667
+ `);
668
+ }
669
+ }
670
+ const stop = () => {
671
+ if (stopping) return;
672
+ stopping = true;
673
+ for (const watcher of watchers) watcher.close();
674
+ if (debounce) clearTimeout(debounce);
675
+ child?.kill("SIGTERM");
676
+ };
677
+ process2.once("SIGINT", stop);
678
+ process2.once("SIGTERM", stop);
679
+ }
680
+ async function superviseStart(parsed) {
681
+ const rootDir = resolve2(
682
+ typeof parsed.flags.root === "string" ? parsed.flags.root : process2.cwd()
683
+ );
684
+ const child = spawn(
685
+ process2.execPath,
686
+ [
687
+ "--import",
688
+ import.meta.resolve("tsx"),
689
+ fileURLToPath(import.meta.url),
690
+ "__start-worker",
691
+ "--root",
692
+ rootDir,
693
+ ...typeof parsed.flags.port === "string" ? ["--port", parsed.flags.port] : [],
694
+ ...typeof parsed.flags.host === "string" ? ["--host", parsed.flags.host] : []
695
+ ],
696
+ {
697
+ cwd: rootDir,
698
+ env: process2.env,
699
+ stdio: "inherit"
700
+ }
701
+ );
702
+ const forwardSignal = (signal) => {
703
+ if (!child.killed) child.kill(signal);
704
+ };
705
+ const interrupt = () => forwardSignal("SIGINT");
706
+ const terminate = () => forwardSignal("SIGTERM");
707
+ process2.once("SIGINT", interrupt);
708
+ process2.once("SIGTERM", terminate);
709
+ const exitCode = await new Promise((resolveExit, rejectExit) => {
710
+ child.once("error", rejectExit);
711
+ child.once("exit", resolveExit);
712
+ });
713
+ process2.removeListener("SIGINT", interrupt);
714
+ process2.removeListener("SIGTERM", terminate);
715
+ if (exitCode !== 0 && exitCode !== null) {
716
+ throw new Error(`Foundry worker exited with code ${exitCode}.`);
717
+ }
718
+ }
719
+ async function main() {
720
+ const parsed = parseArgs(process2.argv.slice(2));
721
+ if (parsed.command === "__dev-worker" || parsed.command === "__start-worker") {
722
+ await runWorker(parsed);
723
+ return;
724
+ }
725
+ if (["help", "--help", "-h"].includes(parsed.command ?? "")) {
726
+ process2.stdout.write(HELP);
727
+ return;
728
+ }
729
+ const createByGloveSyntax = parsed.gloveSyntax && parsed.command !== "dev" && parsed.command !== "start";
730
+ if (parsed.command === "init" || createByGloveSyntax || parsed.gloveSyntax && !parsed.command) {
731
+ const directory = parsed.command === "init" ? parsed.positional[0] ?? "glove-foundry-app" : parsed.command ?? "glove-foundry-app";
732
+ const result = await scaffoldFoundryProject({ directory });
733
+ process2.stdout.write(`Created Glove Foundry in ${result.rootDir}
734
+
735
+ `);
736
+ process2.stdout.write(` cd ${relative(process2.cwd(), result.rootDir) || "."}
737
+ `);
738
+ process2.stdout.write(` cp .env.example .env.local
739
+ `);
740
+ process2.stdout.write(` pnpm install
741
+ `);
742
+ process2.stdout.write(` pnpm dev
743
+ `);
744
+ return;
745
+ }
746
+ if (parsed.command === "dev") {
747
+ await superviseDev(parsed);
748
+ return;
749
+ }
750
+ if (parsed.command === "start") {
751
+ await superviseStart(parsed);
752
+ return;
753
+ }
754
+ process2.stdout.write(HELP);
755
+ }
756
+ main().catch((error) => {
757
+ process2.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}
758
+ `);
759
+ process2.exitCode = 1;
760
+ });