glove-foundry 0.0.0 → 0.2.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/{chunk-UNHGCCSA.js → chunk-P65RT7H5.js} +486 -49
- package/dist/cli.js +536 -352
- package/dist/index.js +1 -1
- package/docs/building-with-foundry.md +39 -2
- package/docs/inspector.md +35 -2
- package/package.json +7 -6
- package/templates/minimal/README.md +58 -0
- package/templates/minimal/agents/assistant/agent.ts +28 -0
- package/templates/minimal/agents/assistant/tools/current-time.tool.ts +17 -0
- package/templates/minimal/env.example +2 -0
- package/templates/minimal/foundry.application.ts +12 -0
- package/templates/minimal/foundry.config.ts +6 -0
- package/templates/minimal/gitignore +3 -0
- package/templates/travel-concierge/README.md +203 -0
- package/templates/travel-concierge/agents/concierge/actions/reply.action.ts +6 -0
- package/templates/travel-concierge/agents/concierge/agent.ts +106 -0
- package/templates/travel-concierge/agents/concierge/apps/calendar.app.ts +67 -0
- package/templates/travel-concierge/agents/concierge/composition.ts +18 -0
- package/templates/travel-concierge/agents/concierge/events/message-received.event.ts +7 -0
- package/templates/travel-concierge/agents/concierge/events/message-sent.event.ts +7 -0
- package/templates/travel-concierge/agents/concierge/layers/trip-context.layer.ts +23 -0
- package/templates/travel-concierge/agents/concierge/memory/traveller.memory.ts +19 -0
- package/templates/travel-concierge/agents/concierge/predicates/mentions-trip.predicate.ts +17 -0
- package/templates/travel-concierge/agents/concierge/schedules/trip-countdown.ts +16 -0
- package/templates/travel-concierge/agents/concierge/subscribers/usage.subscriber.ts +16 -0
- package/templates/travel-concierge/agents/concierge/tools/find-flights.tool.ts +49 -0
- package/templates/travel-concierge/agents/concierge/topology.ts +35 -0
- package/templates/travel-concierge/agents/concierge/transmissions/messaging.transmission.ts +57 -0
- package/templates/travel-concierge/agents/concierge/workbench.ts +26 -0
- package/templates/travel-concierge/env.example +3 -0
- package/templates/travel-concierge/foundry.application.ts +18 -0
- package/templates/travel-concierge/foundry.config.ts +9 -0
- package/templates/travel-concierge/gitignore +3 -0
- package/templates/travel-concierge/lib/demo-model.ts +72 -0
- package/templates/travel-concierge/src/client.ts +22 -0
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
FoundryRuntime,
|
|
4
4
|
FoundryServer,
|
|
5
5
|
writeGeneratedTypes
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-P65RT7H5.js";
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_FOUNDRY_CONFIG
|
|
9
9
|
} from "./chunk-ZFNMFE3T.js";
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
import { watch } from "node:fs";
|
|
17
17
|
import { access as access2 } from "node:fs/promises";
|
|
18
18
|
import { spawn } from "node:child_process";
|
|
19
|
-
import { relative, resolve as
|
|
20
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
|
+
import { relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
20
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
21
21
|
import process2 from "node:process";
|
|
22
22
|
|
|
23
23
|
// src/env.ts
|
|
@@ -52,8 +52,97 @@ async function loadEnvFile(path) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// src/scaffold.ts
|
|
55
|
-
import { access, mkdir, readdir, writeFile } from "node:fs/promises";
|
|
56
|
-
import { basename, dirname, resolve } from "node:path";
|
|
55
|
+
import { access, mkdir, readFile as readFile3, readdir, writeFile } from "node:fs/promises";
|
|
56
|
+
import { basename, dirname as dirname2, join, relative, resolve as resolve2, sep } from "node:path";
|
|
57
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
58
|
+
|
|
59
|
+
// src/scaffold-versions.ts
|
|
60
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
61
|
+
import { dirname, resolve } from "node:path";
|
|
62
|
+
import { fileURLToPath } from "node:url";
|
|
63
|
+
var TEMPLATE_DEPENDENCIES = [
|
|
64
|
+
"effect",
|
|
65
|
+
"glove-core",
|
|
66
|
+
"glove-js",
|
|
67
|
+
"glove-lisp",
|
|
68
|
+
"glove-mcp",
|
|
69
|
+
"glove-memory",
|
|
70
|
+
"glove-python",
|
|
71
|
+
"glove-working-environment",
|
|
72
|
+
"zod"
|
|
73
|
+
];
|
|
74
|
+
var FALLBACK_RANGES = Object.freeze({
|
|
75
|
+
effect: "^3.22.1",
|
|
76
|
+
"glove-core": "^3.6.0",
|
|
77
|
+
"glove-js": "^0.4.0",
|
|
78
|
+
"glove-lisp": "^0.4.0",
|
|
79
|
+
"glove-mcp": "^1.1.0",
|
|
80
|
+
"glove-memory": "^1.1.0",
|
|
81
|
+
"glove-python": "^0.3.0",
|
|
82
|
+
"glove-working-environment": "^0.6.0",
|
|
83
|
+
zod: "^4.3.6"
|
|
84
|
+
});
|
|
85
|
+
var FALLBACK_FOUNDRY_RANGE = "^0.1.0";
|
|
86
|
+
function toRange(value) {
|
|
87
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
88
|
+
if (value.startsWith("workspace:") || value.startsWith("link:") || value.startsWith("file:")) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (/^[\^~><=]|^\d+\.x|\|\||\s-\s/.test(value)) return value;
|
|
92
|
+
return `^${value}`;
|
|
93
|
+
}
|
|
94
|
+
async function readOwnManifest() {
|
|
95
|
+
const path = resolve(dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(await readFile2(path, "utf8"));
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function resolveTemplateVersions() {
|
|
103
|
+
const manifest = await readOwnManifest();
|
|
104
|
+
const declared = manifest?.dependencies ?? {};
|
|
105
|
+
const dependencies = { ...FALLBACK_RANGES };
|
|
106
|
+
const fellBack = [];
|
|
107
|
+
for (const name of TEMPLATE_DEPENDENCIES) {
|
|
108
|
+
const range = toRange(declared[name]);
|
|
109
|
+
if (range) dependencies[name] = range;
|
|
110
|
+
else fellBack.push(name);
|
|
111
|
+
}
|
|
112
|
+
const own = toRange(manifest?.version);
|
|
113
|
+
const foundry = own && own !== "^0.0.0" ? own : FALLBACK_FOUNDRY_RANGE;
|
|
114
|
+
return Object.freeze({
|
|
115
|
+
foundry,
|
|
116
|
+
dependencies: Object.freeze(dependencies),
|
|
117
|
+
resolved: fellBack.length === 0,
|
|
118
|
+
fellBack: Object.freeze(fellBack)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/scaffold.ts
|
|
123
|
+
var FOUNDRY_TEMPLATES = [
|
|
124
|
+
"travel-concierge",
|
|
125
|
+
"minimal"
|
|
126
|
+
];
|
|
127
|
+
var TEMPLATE_IMPORTS = Object.freeze({
|
|
128
|
+
"travel-concierge": [
|
|
129
|
+
"effect",
|
|
130
|
+
"glove-core",
|
|
131
|
+
"glove-js",
|
|
132
|
+
"glove-memory",
|
|
133
|
+
"glove-working-environment",
|
|
134
|
+
"zod"
|
|
135
|
+
],
|
|
136
|
+
minimal: ["effect", "glove-core", "zod"]
|
|
137
|
+
});
|
|
138
|
+
var TEMPLATE_AGENT_ROUTE = Object.freeze({
|
|
139
|
+
"travel-concierge": "concierge",
|
|
140
|
+
minimal: "assistant"
|
|
141
|
+
});
|
|
142
|
+
var RENAMED_ON_COPY = Object.freeze({
|
|
143
|
+
gitignore: ".gitignore",
|
|
144
|
+
"env.example": ".env.example"
|
|
145
|
+
});
|
|
57
146
|
async function exists(path) {
|
|
58
147
|
try {
|
|
59
148
|
await access(path);
|
|
@@ -62,18 +151,172 @@ async function exists(path) {
|
|
|
62
151
|
return false;
|
|
63
152
|
}
|
|
64
153
|
}
|
|
154
|
+
function templatesRoot() {
|
|
155
|
+
return resolve2(dirname2(fileURLToPath2(import.meta.url)), "../templates");
|
|
156
|
+
}
|
|
157
|
+
async function walk(root, prefix = "") {
|
|
158
|
+
const entries = await readdir(resolve2(root, prefix), { withFileTypes: true });
|
|
159
|
+
const files = [];
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
162
|
+
if (entry.isDirectory()) files.push(...await walk(root, relativePath));
|
|
163
|
+
else files.push(relativePath);
|
|
164
|
+
}
|
|
165
|
+
return files.sort();
|
|
166
|
+
}
|
|
167
|
+
function projectNameFrom(rootDir) {
|
|
168
|
+
return basename(rootDir).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "") || "glove-foundry-app";
|
|
169
|
+
}
|
|
170
|
+
function runner(packageManager, script) {
|
|
171
|
+
if (packageManager === "npm") return `npm run ${script}`;
|
|
172
|
+
return `${packageManager} ${script}`;
|
|
173
|
+
}
|
|
174
|
+
function installCommand(packageManager) {
|
|
175
|
+
return packageManager === "yarn" ? "yarn" : `${packageManager} install`;
|
|
176
|
+
}
|
|
177
|
+
async function detectScaffoldTarget(rootDir) {
|
|
178
|
+
for (const config of [
|
|
179
|
+
"next.config.js",
|
|
180
|
+
"next.config.mjs",
|
|
181
|
+
"next.config.ts",
|
|
182
|
+
"next.config.cjs"
|
|
183
|
+
]) {
|
|
184
|
+
if (await exists(resolve2(rootDir, config))) return "nextjs";
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const manifest = JSON.parse(
|
|
188
|
+
await readFile3(resolve2(rootDir, "package.json"), "utf8")
|
|
189
|
+
);
|
|
190
|
+
if (manifest.dependencies?.next ?? manifest.devDependencies?.next) return "nextjs";
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
return "standalone";
|
|
194
|
+
}
|
|
195
|
+
async function detectPackageManager(rootDir) {
|
|
196
|
+
if (await exists(resolve2(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
197
|
+
if (await exists(resolve2(rootDir, "yarn.lock"))) return "yarn";
|
|
198
|
+
if (await exists(resolve2(rootDir, "bun.lockb"))) return "bun";
|
|
199
|
+
if (await exists(resolve2(rootDir, "package-lock.json"))) return "npm";
|
|
200
|
+
const agent = process.env.npm_config_user_agent ?? "";
|
|
201
|
+
if (agent.startsWith("yarn")) return "yarn";
|
|
202
|
+
if (agent.startsWith("bun")) return "bun";
|
|
203
|
+
if (agent.startsWith("npm")) return "npm";
|
|
204
|
+
return "pnpm";
|
|
205
|
+
}
|
|
206
|
+
function fill(content, values) {
|
|
207
|
+
return content.replace(
|
|
208
|
+
/\{\{(\w+)\}\}/g,
|
|
209
|
+
(match, key) => Object.hasOwn(values, key) ? values[key] : match
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
function dependenciesFor(template, versions) {
|
|
213
|
+
const dependencies = { "glove-foundry": versions.foundry };
|
|
214
|
+
for (const name of TEMPLATE_IMPORTS[template]) {
|
|
215
|
+
dependencies[name] = versions.dependencies[name];
|
|
216
|
+
}
|
|
217
|
+
return Object.fromEntries(
|
|
218
|
+
Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b))
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
var DEV_DEPENDENCIES = Object.freeze({
|
|
222
|
+
"@types/node": "^25.2.3",
|
|
223
|
+
eslint: "^9.39.2",
|
|
224
|
+
typescript: "^5.9.3",
|
|
225
|
+
"typescript-eslint": "^8.54.0"
|
|
226
|
+
});
|
|
227
|
+
function routesDeclaration(agentsDir, route) {
|
|
228
|
+
return `// Generated by Glove Foundry. Do not edit.
|
|
229
|
+
export type FoundryRoutes = {
|
|
230
|
+
readonly "${route}": typeof import("../${agentsDir}/${route}/agent.js").default;
|
|
231
|
+
};
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
65
234
|
async function scaffoldFoundryProject(options) {
|
|
66
|
-
const rootDir =
|
|
67
|
-
|
|
235
|
+
const rootDir = resolve2(options.directory);
|
|
236
|
+
const template = options.template ?? "travel-concierge";
|
|
237
|
+
if (!FOUNDRY_TEMPLATES.includes(template)) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`Unknown Foundry template "${template}". Available: ${FOUNDRY_TEMPLATES.join(", ")}.`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
const templateDir = resolve2(templatesRoot(), template);
|
|
243
|
+
if (!await exists(templateDir)) {
|
|
244
|
+
throw new Error(`Foundry template "${template}" is missing from this install.`);
|
|
245
|
+
}
|
|
246
|
+
const target = options.target ?? await detectScaffoldTarget(rootDir);
|
|
247
|
+
const packageManager = options.packageManager ?? await detectPackageManager(rootDir);
|
|
248
|
+
if (target === "standalone" && await exists(rootDir)) {
|
|
68
249
|
const entries = await readdir(rootDir);
|
|
69
250
|
if (entries.length > 0) {
|
|
70
|
-
throw new Error(
|
|
251
|
+
throw new Error(
|
|
252
|
+
`Cannot scaffold into non-empty directory ${rootDir}. Pass --target nextjs to add Foundry to an existing project.`
|
|
253
|
+
);
|
|
71
254
|
}
|
|
72
255
|
}
|
|
73
256
|
await mkdir(rootDir, { recursive: true });
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
257
|
+
const versions = await resolveTemplateVersions();
|
|
258
|
+
const projectName = projectNameFrom(rootDir);
|
|
259
|
+
const agentRoute = TEMPLATE_AGENT_ROUTE[template];
|
|
260
|
+
const foundryDir = target === "nextjs" ? "foundry" : "";
|
|
261
|
+
const agentsDir = foundryDir ? `${foundryDir}/agents` : "agents";
|
|
262
|
+
const devScript = target === "nextjs" ? "foundry:dev" : "dev";
|
|
263
|
+
const startScript = target === "nextjs" ? "foundry:start" : "start";
|
|
264
|
+
const placeholders = {
|
|
265
|
+
projectName,
|
|
266
|
+
installCommand: installCommand(packageManager),
|
|
267
|
+
devCommand: runner(packageManager, devScript),
|
|
268
|
+
startCommand: runner(packageManager, startScript),
|
|
269
|
+
lintCommand: runner(packageManager, "lint"),
|
|
270
|
+
typecheckCommand: runner(packageManager, "typecheck")
|
|
271
|
+
};
|
|
272
|
+
const files = /* @__PURE__ */ new Map();
|
|
273
|
+
for (const relativePath of await walk(templateDir)) {
|
|
274
|
+
const source = await readFile3(resolve2(templateDir, relativePath), "utf8");
|
|
275
|
+
const name = basename(relativePath);
|
|
276
|
+
const renamed = RENAMED_ON_COPY[name];
|
|
277
|
+
let destination = renamed ? join(dirname2(relativePath), renamed).split(sep).join("/") : relativePath;
|
|
278
|
+
if (target === "nextjs") {
|
|
279
|
+
if (destination === "README.md" || destination === ".gitignore") continue;
|
|
280
|
+
if (destination.startsWith("src/")) continue;
|
|
281
|
+
if (destination === "foundry.config.ts") continue;
|
|
282
|
+
if (destination === ".env.example") destination = "foundry.env.example";
|
|
283
|
+
else destination = `${foundryDir}/${destination}`;
|
|
284
|
+
}
|
|
285
|
+
files.set(destination, fill(source, placeholders));
|
|
286
|
+
}
|
|
287
|
+
if (target === "nextjs") {
|
|
288
|
+
files.set(
|
|
289
|
+
"foundry.config.mts",
|
|
290
|
+
`import { defineConfig } from "glove-foundry/config";
|
|
291
|
+
|
|
292
|
+
export default defineConfig({
|
|
293
|
+
agentsDir: "${agentsDir}",
|
|
294
|
+
applicationFile: "${foundryDir}/foundry.application.ts",
|
|
295
|
+
server: { port: 4141 },
|
|
296
|
+
execution: { pollIntervalMs: 100, maxConcurrent: 4 },
|
|
297
|
+
});
|
|
298
|
+
`
|
|
299
|
+
);
|
|
300
|
+
files.set(
|
|
301
|
+
`${foundryDir}/package.json`,
|
|
302
|
+
`${JSON.stringify(
|
|
303
|
+
{
|
|
304
|
+
name: `${projectName}-foundry`,
|
|
305
|
+
private: true,
|
|
306
|
+
// Foundry and the Glove packages are ESM. This scopes that to the
|
|
307
|
+
// agents, so the surrounding app keeps its own module system.
|
|
308
|
+
type: "module"
|
|
309
|
+
},
|
|
310
|
+
null,
|
|
311
|
+
2
|
|
312
|
+
)}
|
|
313
|
+
`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
files.set(".foundry/routes.d.ts", routesDeclaration(agentsDir, agentRoute));
|
|
317
|
+
const dependencies = dependenciesFor(template, versions);
|
|
318
|
+
if (target === "standalone") {
|
|
319
|
+
files.set(
|
|
77
320
|
"package.json",
|
|
78
321
|
`${JSON.stringify(
|
|
79
322
|
{
|
|
@@ -87,29 +330,15 @@ async function scaffoldFoundryProject(options) {
|
|
|
87
330
|
lint: "eslint .",
|
|
88
331
|
typecheck: "tsc --noEmit"
|
|
89
332
|
},
|
|
90
|
-
dependencies
|
|
91
|
-
|
|
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
|
-
}
|
|
333
|
+
dependencies,
|
|
334
|
+
devDependencies: DEV_DEPENDENCIES
|
|
106
335
|
},
|
|
107
336
|
null,
|
|
108
337
|
2
|
|
109
338
|
)}
|
|
110
339
|
`
|
|
111
|
-
|
|
112
|
-
|
|
340
|
+
);
|
|
341
|
+
files.set(
|
|
113
342
|
"tsconfig.json",
|
|
114
343
|
`${JSON.stringify(
|
|
115
344
|
{
|
|
@@ -122,324 +351,207 @@ async function scaffoldFoundryProject(options) {
|
|
|
122
351
|
noEmit: true,
|
|
123
352
|
skipLibCheck: true
|
|
124
353
|
},
|
|
125
|
-
include: [
|
|
354
|
+
include: [
|
|
355
|
+
"agents",
|
|
356
|
+
"lib",
|
|
357
|
+
"src",
|
|
358
|
+
"foundry.application.ts",
|
|
359
|
+
"foundry.config.ts",
|
|
360
|
+
".foundry/routes.d.ts"
|
|
361
|
+
]
|
|
126
362
|
},
|
|
127
363
|
null,
|
|
128
364
|
2
|
|
129
365
|
)}
|
|
130
366
|
`
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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 });
|
|
367
|
+
);
|
|
368
|
+
files.set(
|
|
369
|
+
"eslint.config.js",
|
|
370
|
+
'import tseslint from "typescript-eslint";\nimport foundry from "glove-foundry/eslint";\n\nexport default [...tseslint.configs.recommended, foundry];\n'
|
|
371
|
+
);
|
|
372
|
+
} else {
|
|
373
|
+
const [clientPath, clientSource] = nextClientModule(agentRoute, foundryDir);
|
|
374
|
+
files.set(clientPath, clientSource);
|
|
375
|
+
const [handlerPath, handlerSource] = nextRouteHandler(agentRoute);
|
|
376
|
+
files.set(handlerPath, handlerSource);
|
|
377
|
+
files.set(`${foundryDir}/README.md`, nextReadme(projectName, agentRoute, placeholders));
|
|
378
|
+
}
|
|
379
|
+
for (const [relativePath, content] of files) {
|
|
380
|
+
const path = resolve2(rootDir, relativePath);
|
|
381
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
382
|
+
if (target === "nextjs" && await exists(path)) continue;
|
|
383
|
+
await writeFile(path, content, { encoding: "utf8", flag: "wx" });
|
|
384
|
+
}
|
|
385
|
+
if (target === "nextjs") {
|
|
386
|
+
await mergeNextManifest(rootDir, dependencies, files);
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
rootDir,
|
|
390
|
+
files: [...files.keys()].sort(),
|
|
391
|
+
template,
|
|
392
|
+
target,
|
|
393
|
+
packageManager,
|
|
394
|
+
agentsDir,
|
|
395
|
+
versions,
|
|
396
|
+
agentRoute
|
|
397
|
+
};
|
|
225
398
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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();
|
|
399
|
+
function nextClientModule(route, foundryDir) {
|
|
400
|
+
return [
|
|
401
|
+
"lib/foundry.ts",
|
|
402
|
+
`/**
|
|
403
|
+
* The Next.js app talks to the Foundry runtime over HTTP, so nothing from
|
|
404
|
+
* the agent graph is bundled into your app. \`FoundryRoutes\` is a type-only
|
|
405
|
+
* import, erased at build time, and it is regenerated on every dev run \u2014 so
|
|
406
|
+
* agent("${route}") is checked against the files that actually exist.
|
|
407
|
+
*/
|
|
408
|
+
import { createFoundryClient } from "glove-foundry/client";
|
|
409
|
+
import type { FoundryRoutes } from "../.foundry/routes.js";
|
|
269
410
|
|
|
270
|
-
export
|
|
271
|
-
|
|
272
|
-
data,
|
|
273
|
-
accounts: [],
|
|
274
|
-
routes: [],
|
|
275
|
-
bindings: [],
|
|
411
|
+
export const foundry = createFoundryClient<FoundryRoutes>({
|
|
412
|
+
baseUrl: process.env.FOUNDRY_URL ?? "http://127.0.0.1:4141",
|
|
276
413
|
});
|
|
277
|
-
`
|
|
278
|
-
],
|
|
279
|
-
[
|
|
280
|
-
"agents/assistant/tools/current-time.tool.ts",
|
|
281
|
-
`import { defineSharedTool } from "glove-foundry";
|
|
282
|
-
import { z } from "zod";
|
|
283
414
|
|
|
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
415
|
|
|
296
|
-
|
|
416
|
+
// The agents live in ${foundryDir}/agents. Start the runtime with
|
|
417
|
+
// \`foundry:dev\`; it serves the inspector at http://127.0.0.1:4141.
|
|
297
418
|
`
|
|
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 [];
|
|
419
|
+
];
|
|
334
420
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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.
|
|
421
|
+
function nextRouteHandler(route) {
|
|
422
|
+
return [
|
|
423
|
+
`app/api/${route}/route.ts`,
|
|
424
|
+
`/**
|
|
425
|
+
* POST /api/${route} \u2014 send a message to the agent and wait for its result.
|
|
426
|
+
*
|
|
427
|
+
* This runs on the server, so the Foundry runtime never has to be public.
|
|
428
|
+
* For a streaming UI, subscribe to the runtime's /api/events instead of
|
|
429
|
+
* awaiting run.wait().
|
|
430
|
+
*/
|
|
431
|
+
import { foundry } from "../../../lib/foundry.js";
|
|
432
|
+
|
|
433
|
+
export async function POST(request: Request) {
|
|
434
|
+
const { message, workspaceId = "default" } = (await request.json()) as {
|
|
435
|
+
message: string;
|
|
436
|
+
workspaceId?: string;
|
|
437
|
+
};
|
|
409
438
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
439
|
+
// A real app reuses one instance per user rather than creating one per call.
|
|
440
|
+
const agent = await foundry.agent("${route}").create({ workspaceId });
|
|
441
|
+
const conversation = await foundry.createConversation(agent.id);
|
|
442
|
+
const run = await foundry.send(agent.id, conversation.id, message);
|
|
443
|
+
const completed = await run.wait();
|
|
414
444
|
|
|
415
|
-
|
|
445
|
+
return Response.json({ runId: run.id, output: completed.output });
|
|
446
|
+
}
|
|
416
447
|
`
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
448
|
+
];
|
|
449
|
+
}
|
|
450
|
+
function nextReadme(projectName, route, placeholders) {
|
|
451
|
+
return [
|
|
452
|
+
`# Foundry agents for ${projectName}`,
|
|
453
|
+
"",
|
|
454
|
+
"The agents live here; the Next.js app calls them over HTTP through",
|
|
455
|
+
"`lib/foundry.ts`. Nothing in this folder is bundled into your app.",
|
|
456
|
+
"",
|
|
457
|
+
"## Running",
|
|
458
|
+
"",
|
|
459
|
+
"Two processes, because they have different lifecycles \u2014 the runtime keeps",
|
|
460
|
+
"durable state and should not restart when a React component changes:",
|
|
461
|
+
"",
|
|
462
|
+
"```bash",
|
|
463
|
+
placeholders.devCommand + " # Foundry runtime + inspector on :4141",
|
|
464
|
+
placeholders.installCommand.split(" ")[0] + " dev # Next.js on :3000",
|
|
465
|
+
"```",
|
|
466
|
+
"",
|
|
467
|
+
"Then POST to your own route:",
|
|
468
|
+
"",
|
|
469
|
+
"```bash",
|
|
470
|
+
`curl -X POST http://localhost:3000/api/${route} \\`,
|
|
471
|
+
" -H 'content-type: application/json' \\",
|
|
472
|
+
` -d '{"message":"I want to fly from Lisbon to Nairobi in April."}'`,
|
|
473
|
+
"```",
|
|
474
|
+
"",
|
|
475
|
+
"## Layout",
|
|
476
|
+
"",
|
|
477
|
+
"| Path | What it is |",
|
|
478
|
+
"| --- | --- |",
|
|
479
|
+
`| \`foundry/agents/${route}/agent.ts\` | The agent. The file path is its id. |`,
|
|
480
|
+
"| `foundry/foundry.application.ts` | Data adapter, accounts, routes |",
|
|
481
|
+
"| `foundry.config.ts` | Points the runtime at `foundry/agents` |",
|
|
482
|
+
"| `lib/foundry.ts` | Typed HTTP client for your app to import |",
|
|
483
|
+
`| \`app/api/${route}/route.ts\` | An example Next.js route handler |`,
|
|
484
|
+
"| `.foundry/routes.d.ts` | Generated. Do not edit |",
|
|
485
|
+
"",
|
|
486
|
+
"## Deploying",
|
|
487
|
+
"",
|
|
488
|
+
"The runtime is a normal Node process. Deploy it wherever you run servers,",
|
|
489
|
+
"set `FOUNDRY_URL` in your Next.js environment to point at it, and keep it",
|
|
490
|
+
"private to your network \u2014 the inspector is a development surface.",
|
|
491
|
+
"",
|
|
492
|
+
"See the [full Foundry guide](https://github.com/porkytheblack/glove/blob/main/packages/glove-foundry/docs/building-with-foundry.md).",
|
|
493
|
+
""
|
|
494
|
+
].join("\n");
|
|
495
|
+
}
|
|
496
|
+
async function mergeNextManifest(rootDir, dependencies, files) {
|
|
497
|
+
const path = resolve2(rootDir, "package.json");
|
|
498
|
+
let manifest = {};
|
|
499
|
+
if (await exists(path)) {
|
|
500
|
+
manifest = JSON.parse(await readFile3(path, "utf8"));
|
|
423
501
|
}
|
|
424
|
-
|
|
502
|
+
const scripts = { ...manifest.scripts };
|
|
503
|
+
scripts["foundry:dev"] ??= "glove foundry dev";
|
|
504
|
+
scripts["foundry:start"] ??= "glove foundry start";
|
|
505
|
+
const merged = {
|
|
506
|
+
...manifest,
|
|
507
|
+
scripts,
|
|
508
|
+
dependencies: Object.fromEntries(
|
|
509
|
+
Object.entries({
|
|
510
|
+
...manifest.dependencies,
|
|
511
|
+
...dependencies
|
|
512
|
+
}).sort(([a], [b]) => a.localeCompare(b))
|
|
513
|
+
)
|
|
514
|
+
};
|
|
515
|
+
await writeFile(path, `${JSON.stringify(merged, null, 2)}
|
|
516
|
+
`, "utf8");
|
|
517
|
+
files.set("package.json", "(merged)");
|
|
518
|
+
}
|
|
519
|
+
function displayPath(rootDir) {
|
|
520
|
+
return relative(process.cwd(), rootDir) || ".";
|
|
425
521
|
}
|
|
426
522
|
|
|
427
523
|
// src/cli.ts
|
|
428
524
|
var HELP = `Glove Foundry \u2014 a file-routed runtime for agents
|
|
429
525
|
|
|
430
526
|
Usage:
|
|
431
|
-
glove foundry [directory]
|
|
432
|
-
glove foundry dev [options] Run the development runtime and
|
|
527
|
+
glove foundry init [directory] Create a Foundry application
|
|
528
|
+
glove foundry dev [options] Run the development runtime and inspector
|
|
433
529
|
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
530
|
|
|
438
|
-
|
|
531
|
+
The bare form still works: glove foundry my-app
|
|
532
|
+
|
|
533
|
+
Create options:
|
|
534
|
+
--template <name> travel-concierge (default) or minimal
|
|
535
|
+
travel-concierge a worked example: flights, a
|
|
536
|
+
calendar app, chat transport,
|
|
537
|
+
memory, a schedule, a REPL
|
|
538
|
+
minimal one agent and one tool
|
|
539
|
+
--target <name> standalone (default) or nextjs
|
|
540
|
+
Detected automatically: a directory holding a
|
|
541
|
+
Next.js app gets the nextjs target, which adds
|
|
542
|
+
agents under foundry/ and leaves the app alone.
|
|
543
|
+
--package-manager <name> pnpm, npm, yarn, or bun (detected by lockfile)
|
|
544
|
+
|
|
545
|
+
Run options:
|
|
439
546
|
--root <directory> Project root (default: current directory)
|
|
440
547
|
--port <number> Override the configured port
|
|
441
548
|
--host <address> Override the configured host
|
|
442
549
|
--no-watch Disable agent/config hot restart
|
|
550
|
+
|
|
551
|
+
Examples:
|
|
552
|
+
glove foundry init my-agents
|
|
553
|
+
glove foundry init my-agents --template minimal
|
|
554
|
+
glove foundry init . --target nextjs Add agents to the app in this directory
|
|
443
555
|
`;
|
|
444
556
|
function parseArgs(raw) {
|
|
445
557
|
const args = [...raw];
|
|
@@ -476,7 +588,7 @@ async function pathExists(path) {
|
|
|
476
588
|
async function loadConfig(rootDir) {
|
|
477
589
|
const candidates = ["foundry.config.ts", "foundry.config.mts", "foundry.config.js", "foundry.config.mjs"];
|
|
478
590
|
for (const candidate of candidates) {
|
|
479
|
-
const path =
|
|
591
|
+
const path = resolve3(rootDir, candidate);
|
|
480
592
|
if (!await pathExists(path)) continue;
|
|
481
593
|
const url = pathToFileURL(path);
|
|
482
594
|
url.searchParams.set("t", String(Date.now()));
|
|
@@ -490,7 +602,7 @@ async function loadConfig(rootDir) {
|
|
|
490
602
|
}
|
|
491
603
|
async function loadApplication(rootDir, config) {
|
|
492
604
|
const relativePath = config.applicationFile ?? DEFAULT_FOUNDRY_CONFIG.applicationFile;
|
|
493
|
-
const path =
|
|
605
|
+
const path = resolve3(rootDir, relativePath);
|
|
494
606
|
if (!await pathExists(path)) return EMPTY_FOUNDRY_APPLICATION;
|
|
495
607
|
const url = pathToFileURL(path);
|
|
496
608
|
url.searchParams.set("t", String(Date.now()));
|
|
@@ -501,18 +613,18 @@ async function loadApplication(rootDir, config) {
|
|
|
501
613
|
return imported.default;
|
|
502
614
|
}
|
|
503
615
|
async function runWorker(parsed) {
|
|
504
|
-
const rootDir =
|
|
616
|
+
const rootDir = resolve3(
|
|
505
617
|
typeof parsed.flags.root === "string" ? parsed.flags.root : process2.cwd()
|
|
506
618
|
);
|
|
507
|
-
await loadEnvFile(
|
|
508
|
-
await loadEnvFile(
|
|
619
|
+
await loadEnvFile(resolve3(rootDir, ".env"));
|
|
620
|
+
await loadEnvFile(resolve3(rootDir, ".env.local"));
|
|
509
621
|
const config = await loadConfig(rootDir);
|
|
510
622
|
const application = await loadApplication(rootDir, config);
|
|
511
|
-
const applicationFilePath =
|
|
623
|
+
const applicationFilePath = resolve3(
|
|
512
624
|
rootDir,
|
|
513
625
|
config.applicationFile ?? DEFAULT_FOUNDRY_CONFIG.applicationFile
|
|
514
626
|
);
|
|
515
|
-
const agentsDir =
|
|
627
|
+
const agentsDir = resolve3(
|
|
516
628
|
rootDir,
|
|
517
629
|
config.agentsDir ?? DEFAULT_FOUNDRY_CONFIG.agentsDir
|
|
518
630
|
);
|
|
@@ -558,16 +670,17 @@ async function runWorker(parsed) {
|
|
|
558
670
|
process2.once("SIGTERM", () => void close());
|
|
559
671
|
process2.once("SIGINT", () => void close());
|
|
560
672
|
}
|
|
561
|
-
function shouldRestart(rootDir, changed) {
|
|
673
|
+
function shouldRestart(rootDir, changed, agentsDir) {
|
|
562
674
|
if (!changed) return false;
|
|
563
|
-
const normalized =
|
|
564
|
-
return normalized.startsWith(
|
|
675
|
+
const normalized = relative2(rootDir, resolve3(rootDir, changed)).split(sep2).join("/");
|
|
676
|
+
return normalized.startsWith(`${agentsDir}/`) || normalized === ".env" || normalized === ".env.local" || /^(?:[\w.-]+\/)*foundry\.application\.(?:ts|mts|js|mjs)$/.test(normalized) || /^foundry\.config\.(?:ts|mts|js|mjs)$/.test(normalized);
|
|
565
677
|
}
|
|
566
678
|
async function superviseDev(parsed) {
|
|
567
|
-
const rootDir =
|
|
679
|
+
const rootDir = resolve3(
|
|
568
680
|
typeof parsed.flags.root === "string" ? parsed.flags.root : process2.cwd()
|
|
569
681
|
);
|
|
570
|
-
const
|
|
682
|
+
const agentsDir = (await loadConfig(rootDir)).agentsDir ?? DEFAULT_FOUNDRY_CONFIG.agentsDir;
|
|
683
|
+
const cliPath = fileURLToPath3(import.meta.url);
|
|
571
684
|
const tsxImport = import.meta.resolve("tsx");
|
|
572
685
|
let child = null;
|
|
573
686
|
const watchers = [];
|
|
@@ -618,7 +731,7 @@ async function superviseDev(parsed) {
|
|
|
618
731
|
if (!parsed.flags["no-watch"]) {
|
|
619
732
|
try {
|
|
620
733
|
const onChange = (filename) => {
|
|
621
|
-
if (!shouldRestart(rootDir, filename?.toString() ?? null)) return;
|
|
734
|
+
if (!shouldRestart(rootDir, filename?.toString() ?? null, agentsDir)) return;
|
|
622
735
|
if (debounce) clearTimeout(debounce);
|
|
623
736
|
debounce = setTimeout(restart, 120);
|
|
624
737
|
};
|
|
@@ -629,14 +742,14 @@ async function superviseDev(parsed) {
|
|
|
629
742
|
(_event, filename) => onChange(filename?.toString() ?? null)
|
|
630
743
|
)
|
|
631
744
|
);
|
|
632
|
-
const
|
|
633
|
-
if (await pathExists(
|
|
745
|
+
const watchedAgentsDir = resolve3(rootDir, agentsDir);
|
|
746
|
+
if (await pathExists(watchedAgentsDir)) {
|
|
634
747
|
watchers.push(
|
|
635
748
|
watch(
|
|
636
|
-
|
|
749
|
+
watchedAgentsDir,
|
|
637
750
|
{ recursive: true },
|
|
638
751
|
(_event, filename) => onChange(
|
|
639
|
-
filename ?
|
|
752
|
+
filename ? `${agentsDir}/${filename.toString()}` : agentsDir
|
|
640
753
|
)
|
|
641
754
|
)
|
|
642
755
|
);
|
|
@@ -648,7 +761,7 @@ async function superviseDev(parsed) {
|
|
|
648
761
|
"memory",
|
|
649
762
|
"inboxes"
|
|
650
763
|
]) {
|
|
651
|
-
const watched =
|
|
764
|
+
const watched = resolve3(rootDir, directory);
|
|
652
765
|
if (await pathExists(watched)) {
|
|
653
766
|
watchers.push(
|
|
654
767
|
watch(
|
|
@@ -678,7 +791,7 @@ async function superviseDev(parsed) {
|
|
|
678
791
|
process2.once("SIGTERM", stop);
|
|
679
792
|
}
|
|
680
793
|
async function superviseStart(parsed) {
|
|
681
|
-
const rootDir =
|
|
794
|
+
const rootDir = resolve3(
|
|
682
795
|
typeof parsed.flags.root === "string" ? parsed.flags.root : process2.cwd()
|
|
683
796
|
);
|
|
684
797
|
const child = spawn(
|
|
@@ -686,7 +799,7 @@ async function superviseStart(parsed) {
|
|
|
686
799
|
[
|
|
687
800
|
"--import",
|
|
688
801
|
import.meta.resolve("tsx"),
|
|
689
|
-
|
|
802
|
+
fileURLToPath3(import.meta.url),
|
|
690
803
|
"__start-worker",
|
|
691
804
|
"--root",
|
|
692
805
|
rootDir,
|
|
@@ -716,6 +829,77 @@ async function superviseStart(parsed) {
|
|
|
716
829
|
throw new Error(`Foundry worker exited with code ${exitCode}.`);
|
|
717
830
|
}
|
|
718
831
|
}
|
|
832
|
+
var UsageError = class extends Error {
|
|
833
|
+
};
|
|
834
|
+
function flagValue(parsed, name, allowed) {
|
|
835
|
+
const raw = parsed.flags[name];
|
|
836
|
+
if (raw === void 0) return void 0;
|
|
837
|
+
if (typeof raw !== "string" || !allowed.includes(raw)) {
|
|
838
|
+
throw new UsageError(
|
|
839
|
+
`--${name} must be one of: ${allowed.join(", ")}.` + (typeof raw === "string" ? ` Received "${raw}".` : "")
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
return raw;
|
|
843
|
+
}
|
|
844
|
+
async function create(directory, parsed) {
|
|
845
|
+
const template = flagValue(parsed, "template", FOUNDRY_TEMPLATES);
|
|
846
|
+
const target = flagValue(parsed, "target", ["standalone", "nextjs"]);
|
|
847
|
+
const packageManager = flagValue(
|
|
848
|
+
parsed,
|
|
849
|
+
"package-manager",
|
|
850
|
+
["pnpm", "npm", "yarn", "bun"]
|
|
851
|
+
);
|
|
852
|
+
const result = await scaffoldFoundryProject({
|
|
853
|
+
directory,
|
|
854
|
+
...template ? { template } : {},
|
|
855
|
+
...target ? { target } : {},
|
|
856
|
+
...packageManager ? { packageManager } : {}
|
|
857
|
+
});
|
|
858
|
+
const where = displayPath(result.rootDir);
|
|
859
|
+
const pm = result.packageManager;
|
|
860
|
+
const run = (script) => pm === "npm" ? `npm run ${script}` : `${pm} ${script}`;
|
|
861
|
+
const install = pm === "yarn" ? "yarn" : `${pm} install`;
|
|
862
|
+
const lines = [""];
|
|
863
|
+
if (result.target === "nextjs") {
|
|
864
|
+
lines.push(` Added Glove Foundry to the app in ${result.rootDir}`);
|
|
865
|
+
lines.push("");
|
|
866
|
+
lines.push(` Agents: ${result.agentsDir}/${result.agentRoute}/agent.ts`);
|
|
867
|
+
lines.push(` Client: lib/foundry.ts`);
|
|
868
|
+
lines.push(` Route: app/api/${result.agentRoute}/route.ts`);
|
|
869
|
+
lines.push(` Guide: foundry/README.md`);
|
|
870
|
+
lines.push("");
|
|
871
|
+
lines.push(" Next:");
|
|
872
|
+
if (where !== ".") lines.push(` cd ${where}`);
|
|
873
|
+
lines.push(` ${install}`);
|
|
874
|
+
lines.push(` ${run("foundry:dev")} # runtime + inspector on :4141`);
|
|
875
|
+
lines.push(` ${run("dev")} # your Next.js app`);
|
|
876
|
+
} else {
|
|
877
|
+
lines.push(` Created a Glove Foundry app in ${result.rootDir}`);
|
|
878
|
+
lines.push("");
|
|
879
|
+
lines.push(` Template: ${result.template}`);
|
|
880
|
+
lines.push(` Agent: ${result.agentsDir}/${result.agentRoute}/agent.ts`);
|
|
881
|
+
lines.push(` Guide: README.md`);
|
|
882
|
+
lines.push("");
|
|
883
|
+
lines.push(" Next:");
|
|
884
|
+
if (where !== ".") lines.push(` cd ${where}`);
|
|
885
|
+
lines.push(" cp .env.example .env.local");
|
|
886
|
+
lines.push(` ${install}`);
|
|
887
|
+
lines.push(` ${run("dev")}`);
|
|
888
|
+
lines.push("");
|
|
889
|
+
lines.push(" Then open http://127.0.0.1:4141 and press Start a run.");
|
|
890
|
+
if (result.template === "travel-concierge") {
|
|
891
|
+
lines.push(" It works without an API key \u2014 a demo model answers until you set one.");
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (!result.versions.resolved) {
|
|
895
|
+
lines.push("");
|
|
896
|
+
lines.push(" Note: some Glove versions came from built-in defaults rather than");
|
|
897
|
+
lines.push(" this install's own manifest. Check before installing:");
|
|
898
|
+
lines.push(` ${result.versions.fellBack.join(", ")}`);
|
|
899
|
+
}
|
|
900
|
+
lines.push("");
|
|
901
|
+
process2.stdout.write(lines.join("\n"));
|
|
902
|
+
}
|
|
719
903
|
async function main() {
|
|
720
904
|
const parsed = parseArgs(process2.argv.slice(2));
|
|
721
905
|
if (parsed.command === "__dev-worker" || parsed.command === "__start-worker") {
|
|
@@ -729,18 +913,7 @@ async function main() {
|
|
|
729
913
|
const createByGloveSyntax = parsed.gloveSyntax && parsed.command !== "dev" && parsed.command !== "start";
|
|
730
914
|
if (parsed.command === "init" || createByGloveSyntax || parsed.gloveSyntax && !parsed.command) {
|
|
731
915
|
const directory = parsed.command === "init" ? parsed.positional[0] ?? "glove-foundry-app" : parsed.command ?? "glove-foundry-app";
|
|
732
|
-
|
|
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
|
-
`);
|
|
916
|
+
await create(directory, parsed);
|
|
744
917
|
return;
|
|
745
918
|
}
|
|
746
919
|
if (parsed.command === "dev") {
|
|
@@ -754,7 +927,18 @@ async function main() {
|
|
|
754
927
|
process2.stdout.write(HELP);
|
|
755
928
|
}
|
|
756
929
|
main().catch((error) => {
|
|
757
|
-
|
|
930
|
+
if (error instanceof UsageError) {
|
|
931
|
+
process2.stderr.write(`
|
|
932
|
+
${error.message}
|
|
933
|
+
|
|
934
|
+
Run "glove foundry help" for usage.
|
|
935
|
+
|
|
758
936
|
`);
|
|
937
|
+
} else {
|
|
938
|
+
process2.stderr.write(
|
|
939
|
+
`${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
940
|
+
`
|
|
941
|
+
);
|
|
942
|
+
}
|
|
759
943
|
process2.exitCode = 1;
|
|
760
944
|
});
|