glove-foundry 0.3.3 → 0.4.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.
@@ -9,7 +9,7 @@ async function readResponse(response) {
9
9
  function routePath(route) {
10
10
  return route.split("/").map(encodeURIComponent).join("/");
11
11
  }
12
- var FoundryRunHandle = class {
12
+ var FoundryRunHandle = class _FoundryRunHandle {
13
13
  id;
14
14
  initial;
15
15
  client;
@@ -24,6 +24,14 @@ var FoundryRunHandle = class {
24
24
  cancel() {
25
25
  return this.client.cancelRun(this.id);
26
26
  }
27
+ async steer(message) {
28
+ const result = await this.client.steerRun(this.id, message);
29
+ return {
30
+ fromRunId: result.fromRunId,
31
+ interrupted: result.interrupted,
32
+ run: new _FoundryRunHandle(result.run.id, result.run, this.client)
33
+ };
34
+ }
27
35
  events() {
28
36
  return this.client.getEvents({ runId: this.id });
29
37
  }
@@ -62,7 +70,16 @@ var FoundryClient = class {
62
70
  /\/$/,
63
71
  ""
64
72
  );
65
- this.fetcher = options.fetch ?? globalThis.fetch;
73
+ const fetcher = options.fetch ?? globalThis.fetch;
74
+ this.fetcher = options.authorization ? async (input, init = {}) => {
75
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
76
+ const method = init.method ?? (input instanceof Request ? input.method : "GET");
77
+ const headers = new Headers(input instanceof Request ? input.headers : void 0);
78
+ new Headers(init.headers).forEach((value, key) => headers.set(key, value));
79
+ const authorized = new Headers(await options.authorization.headers({ url, method }));
80
+ authorized.forEach((value, key) => headers.set(key, value));
81
+ return fetcher(input, { ...init, headers });
82
+ } : fetcher;
66
83
  }
67
84
  async health() {
68
85
  return readResponse(await this.fetcher(`${this.baseUrl}/health`));
@@ -119,6 +136,24 @@ var FoundryClient = class {
119
136
  async conversations(agentId) {
120
137
  return readResponse(await this.fetcher(`${this.baseUrl}/api/conversations?agent=${encodeURIComponent(agentId)}`));
121
138
  }
139
+ async updateConversation(agentId, conversationId, options) {
140
+ return readResponse(await this.fetcher(
141
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversationId)}`,
142
+ {
143
+ method: "PATCH",
144
+ headers: { "content-type": "application/json" },
145
+ body: JSON.stringify({ agentId, ...options })
146
+ }
147
+ ));
148
+ }
149
+ async conversationTranscript(agentId, conversationId, options = {}) {
150
+ const query = new URLSearchParams({ agent: agentId });
151
+ if (options.offset !== void 0) query.set("offset", String(options.offset));
152
+ if (options.limit !== void 0) query.set("limit", String(options.limit));
153
+ return readResponse(await this.fetcher(
154
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversationId)}/messages?${query}`
155
+ ));
156
+ }
122
157
  async workspaceEntries(workspaceId) {
123
158
  return readResponse(await this.fetcher(`${this.baseUrl}/api/workspaces/${encodeURIComponent(workspaceId)}/entries`));
124
159
  }
@@ -252,6 +287,18 @@ var FoundryClient = class {
252
287
  const payload = await readResponse(response);
253
288
  return payload.cancelled;
254
289
  }
290
+ async steerRun(runId, message) {
291
+ return readResponse(
292
+ await this.fetcher(
293
+ `${this.baseUrl}/api/runs/${encodeURIComponent(runId)}/steer`,
294
+ {
295
+ method: "POST",
296
+ headers: { "content-type": "application/json" },
297
+ body: JSON.stringify({ message })
298
+ }
299
+ )
300
+ );
301
+ }
255
302
  async getEvents(filter) {
256
303
  const query = new URLSearchParams();
257
304
  if (filter?.runId) query.set("runId", filter.runId);
@@ -265,6 +312,23 @@ var FoundryClient = class {
265
312
  );
266
313
  return readResponse(response);
267
314
  }
315
+ async approvals(filter = {}) {
316
+ const query = new URLSearchParams();
317
+ if (filter.runId) query.set("runId", filter.runId);
318
+ if (filter.status) query.set("status", filter.status);
319
+ const suffix = query.size ? `?${query}` : "";
320
+ return readResponse(await this.fetcher(`${this.baseUrl}/api/approvals${suffix}`));
321
+ }
322
+ async resolveApproval(approvalId, decision) {
323
+ return readResponse(await this.fetcher(
324
+ `${this.baseUrl}/api/approvals/${encodeURIComponent(approvalId)}`,
325
+ {
326
+ method: "POST",
327
+ headers: { "content-type": "application/json" },
328
+ body: JSON.stringify({ decision })
329
+ }
330
+ ));
331
+ }
268
332
  async manifest() {
269
333
  return readResponse(
270
334
  await this.fetcher(`${this.baseUrl}/api/manifest`)
package/dist/cli.js CHANGED
@@ -3,20 +3,20 @@ import {
3
3
  FoundryRuntime,
4
4
  FoundryServer,
5
5
  writeGeneratedTypes
6
- } from "./chunk-P65RT7H5.js";
6
+ } from "./chunk-25BGNRJN.js";
7
7
  import {
8
8
  DEFAULT_FOUNDRY_CONFIG
9
9
  } from "./chunk-ZFNMFE3T.js";
10
10
  import {
11
11
  EMPTY_FOUNDRY_APPLICATION,
12
12
  isFoundryApplication
13
- } from "./chunk-CRWY7M66.js";
13
+ } from "./chunk-5LDW2N2E.js";
14
14
 
15
15
  // src/cli.ts
16
16
  import { watch } from "node:fs";
17
17
  import { access as access2 } from "node:fs/promises";
18
- import { spawn } from "node:child_process";
19
- import { relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
18
+ import { spawn as spawn2 } from "node:child_process";
19
+ import { relative as relative2, resolve as resolve4, sep as sep2 } from "node:path";
20
20
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
21
21
  import process2 from "node:process";
22
22
 
@@ -73,16 +73,16 @@ var TEMPLATE_DEPENDENCIES = [
73
73
  ];
74
74
  var FALLBACK_RANGES = Object.freeze({
75
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",
76
+ "glove-core": "^4.0.0",
77
+ "glove-js": "^0.4.3",
78
+ "glove-lisp": "^0.4.3",
79
+ "glove-mcp": "^1.1.3",
80
+ "glove-memory": "^2.0.0",
81
+ "glove-python": "^0.3.3",
82
+ "glove-working-environment": "^0.6.1",
83
83
  zod: "^4.3.6"
84
84
  });
85
- var FALLBACK_FOUNDRY_RANGE = "^0.1.0";
85
+ var FALLBACK_FOUNDRY_RANGE = "^0.3.3";
86
86
  function toRange(value) {
87
87
  if (typeof value !== "string" || value.length === 0) return null;
88
88
  if (value.startsWith("workspace:") || value.startsWith("link:") || value.startsWith("file:")) {
@@ -105,7 +105,19 @@ async function resolveTemplateVersions() {
105
105
  const dependencies = { ...FALLBACK_RANGES };
106
106
  const fellBack = [];
107
107
  for (const name of TEMPLATE_DEPENDENCIES) {
108
- const range = toRange(declared[name]);
108
+ let range = toRange(declared[name]);
109
+ if (!range && typeof declared[name] === "string" && declared[name].startsWith("workspace:")) {
110
+ try {
111
+ const dependency = JSON.parse(await readFile2(resolve(
112
+ dirname(fileURLToPath(import.meta.url)),
113
+ "../node_modules",
114
+ name,
115
+ "package.json"
116
+ ), "utf8"));
117
+ range = toRange(dependency.version);
118
+ } catch {
119
+ }
120
+ }
109
121
  if (range) dependencies[name] = range;
110
122
  else fellBack.push(name);
111
123
  }
@@ -196,6 +208,7 @@ async function detectPackageManager(rootDir) {
196
208
  if (await exists(resolve2(rootDir, "pnpm-lock.yaml"))) return "pnpm";
197
209
  if (await exists(resolve2(rootDir, "yarn.lock"))) return "yarn";
198
210
  if (await exists(resolve2(rootDir, "bun.lockb"))) return "bun";
211
+ if (await exists(resolve2(rootDir, "bun.lock"))) return "bun";
199
212
  if (await exists(resolve2(rootDir, "package-lock.json"))) return "npm";
200
213
  const agent = process.env.npm_config_user_agent ?? "";
201
214
  if (agent.startsWith("yarn")) return "yarn";
@@ -520,6 +533,85 @@ function displayPath(rootDir) {
520
533
  return relative(process.cwd(), rootDir) || ".";
521
534
  }
522
535
 
536
+ // src/init.ts
537
+ import { spawn } from "node:child_process";
538
+ import { resolve as resolve3 } from "node:path";
539
+ var InitCancelled = class extends Error {
540
+ };
541
+ async function promptInit(options) {
542
+ const ui = await import("@clack/prompts");
543
+ ui.intro("Glove Foundry \xB7 Build an agent application");
544
+ const answer = (value) => {
545
+ if (ui.isCancel(value)) {
546
+ ui.cancel("Setup cancelled. No project files were created.");
547
+ throw new InitCancelled();
548
+ }
549
+ return value;
550
+ };
551
+ const directory = options.directory ?? answer(await ui.text({
552
+ message: "Where should the project live?",
553
+ placeholder: "my-agent-app",
554
+ defaultValue: "my-agent-app",
555
+ validate: (value) => !value?.trim() ? "Enter a directory, or . for this directory." : void 0
556
+ }));
557
+ const detected = await detectScaffoldTarget(resolve3(directory));
558
+ const target = options.target ?? answer(await ui.select({
559
+ message: "How will you use Foundry?",
560
+ initialValue: detected,
561
+ options: [
562
+ { value: "standalone", label: "Standalone agent application", hint: "runtime + inspector; connect any frontend" },
563
+ { value: "nextjs", label: "Add to a Next.js app", hint: "colocated agents; preserve existing app files" }
564
+ ]
565
+ }));
566
+ const template = options.template ?? answer(await ui.select({
567
+ message: "Choose your starting point",
568
+ initialValue: "travel-concierge",
569
+ options: [
570
+ { value: "travel-concierge", label: "Guided example", hint: "recommended \xB7 keyless demo, tools, apps, memory, schedules, VFS + REPL" },
571
+ { value: "minimal", label: "Minimal agent", hint: "one agent and one tool; bring an OpenRouter key" }
572
+ ]
573
+ }));
574
+ const packageManager = options.packageManager ?? answer(await ui.select({
575
+ message: "Which package manager do you use?",
576
+ initialValue: await detectPackageManager(resolve3(directory)),
577
+ options: ["pnpm", "npm", "yarn", "bun"].map((value) => ({
578
+ value,
579
+ label: value
580
+ }))
581
+ }));
582
+ const install = options.install ?? answer(await ui.confirm({
583
+ message: "Install dependencies after creating the project?",
584
+ initialValue: true
585
+ }));
586
+ ui.note([
587
+ `Directory: ${resolve3(directory)}`,
588
+ `Project: ${target === "nextjs" ? "Existing Next.js app" : "Standalone"}`,
589
+ `Starter: ${template === "minimal" ? "Minimal agent" : "Guided travel concierge"}`,
590
+ `Packages: ${packageManager}${install ? " \xB7 install now" : " \xB7 install later"}`,
591
+ "Templates use disposable demo storage. The README explains production persistence.",
592
+ "No API keys are requested or stored by this wizard."
593
+ ].join("\n"), "Review your project");
594
+ if (!answer(await ui.confirm({ message: "Create this project?", initialValue: true }))) {
595
+ ui.cancel("Setup cancelled. No project files were created.");
596
+ throw new InitCancelled();
597
+ }
598
+ return { directory: directory.trim(), target, template, packageManager, install };
599
+ }
600
+ async function installProject(directory, manager) {
601
+ await new Promise((done, reject) => {
602
+ const command = process.platform === "win32" ? "cmd.exe" : manager;
603
+ const args = process.platform === "win32" ? ["/d", "/s", "/c", `${manager} install`] : ["install"];
604
+ const child = spawn(command, args, { cwd: directory, stdio: "inherit", shell: false });
605
+ child.once("error", reject);
606
+ child.once("exit", (code) => code === 0 ? done() : reject(new Error(`Dependency installation exited with code ${code ?? "signal"}.`)));
607
+ });
608
+ }
609
+ function useInteractiveInit(flags, terminal) {
610
+ if (flags.interactive && (flags.yes || flags["no-interactive"])) throw new Error("--interactive cannot be combined with --yes or --no-interactive.");
611
+ if (flags.interactive && !terminal) throw new Error("Interactive setup needs a terminal. Use --yes with explicit options in scripts.");
612
+ return terminal && !flags.yes && !flags["no-interactive"];
613
+ }
614
+
523
615
  // src/cli.ts
524
616
  var HELP = `Glove Foundry \u2014 a file-routed runtime for agents
525
617
 
@@ -541,6 +633,10 @@ Create options:
541
633
  Next.js app gets the nextjs target, which adds
542
634
  agents under foundry/ and leaves the app alone.
543
635
  --package-manager <name> pnpm, npm, yarn, or bun (detected by lockfile)
636
+ --yes Use defaults without interactive prompts
637
+ --no-interactive Same non-interactive behavior, suitable for CI
638
+ --interactive Require an interactive terminal
639
+ --install / --no-install Install dependencies now / leave installation to you
544
640
 
545
641
  Run options:
546
642
  --root <directory> Project root (default: current directory)
@@ -557,13 +653,17 @@ function parseArgs(raw) {
557
653
  const args = [...raw];
558
654
  const gloveSyntax = args[0] === "foundry";
559
655
  if (gloveSyntax) args.shift();
560
- const command = args[0];
656
+ const command = args[0]?.startsWith("--") ? void 0 : args[0];
561
657
  const positional = [];
562
658
  const flags = {};
563
- for (let index = 1; index < args.length; index++) {
659
+ for (let index = command ? 1 : 0; index < args.length; index++) {
564
660
  const value = args[index];
565
661
  if (value.startsWith("--")) {
566
662
  const name = value.slice(2);
663
+ if (["yes", "no-interactive", "interactive", "install", "no-install", "help", "no-watch"].includes(name)) {
664
+ flags[name] = true;
665
+ continue;
666
+ }
567
667
  const next = args[index + 1];
568
668
  if (next && !next.startsWith("--")) {
569
669
  flags[name] = next;
@@ -588,7 +688,7 @@ async function pathExists(path) {
588
688
  async function loadConfig(rootDir) {
589
689
  const candidates = ["foundry.config.ts", "foundry.config.mts", "foundry.config.js", "foundry.config.mjs"];
590
690
  for (const candidate of candidates) {
591
- const path = resolve3(rootDir, candidate);
691
+ const path = resolve4(rootDir, candidate);
592
692
  if (!await pathExists(path)) continue;
593
693
  const url = pathToFileURL(path);
594
694
  url.searchParams.set("t", String(Date.now()));
@@ -602,7 +702,7 @@ async function loadConfig(rootDir) {
602
702
  }
603
703
  async function loadApplication(rootDir, config) {
604
704
  const relativePath = config.applicationFile ?? DEFAULT_FOUNDRY_CONFIG.applicationFile;
605
- const path = resolve3(rootDir, relativePath);
705
+ const path = resolve4(rootDir, relativePath);
606
706
  if (!await pathExists(path)) return EMPTY_FOUNDRY_APPLICATION;
607
707
  const url = pathToFileURL(path);
608
708
  url.searchParams.set("t", String(Date.now()));
@@ -613,18 +713,18 @@ async function loadApplication(rootDir, config) {
613
713
  return imported.default;
614
714
  }
615
715
  async function runWorker(parsed) {
616
- const rootDir = resolve3(
716
+ const rootDir = resolve4(
617
717
  typeof parsed.flags.root === "string" ? parsed.flags.root : process2.cwd()
618
718
  );
619
- await loadEnvFile(resolve3(rootDir, ".env"));
620
- await loadEnvFile(resolve3(rootDir, ".env.local"));
719
+ await loadEnvFile(resolve4(rootDir, ".env"));
720
+ await loadEnvFile(resolve4(rootDir, ".env.local"));
621
721
  const config = await loadConfig(rootDir);
622
722
  const application = await loadApplication(rootDir, config);
623
- const applicationFilePath = resolve3(
723
+ const applicationFilePath = resolve4(
624
724
  rootDir,
625
725
  config.applicationFile ?? DEFAULT_FOUNDRY_CONFIG.applicationFile
626
726
  );
627
- const agentsDir = resolve3(
727
+ const agentsDir = resolve4(
628
728
  rootDir,
629
729
  config.agentsDir ?? DEFAULT_FOUNDRY_CONFIG.agentsDir
630
730
  );
@@ -643,7 +743,9 @@ async function runWorker(parsed) {
643
743
  await runtime.start();
644
744
  const server = new FoundryServer(runtime, {
645
745
  host: typeof parsed.flags.host === "string" ? parsed.flags.host : config.server?.host ?? DEFAULT_FOUNDRY_CONFIG.server.host,
646
- port: typeof parsed.flags.port === "string" ? Number(parsed.flags.port) : config.server?.port ?? DEFAULT_FOUNDRY_CONFIG.server.port
746
+ port: typeof parsed.flags.port === "string" ? Number(parsed.flags.port) : config.server?.port ?? DEFAULT_FOUNDRY_CONFIG.server.port,
747
+ branding: config.branding,
748
+ messageBodyBytes: config.server?.messageBodyBytes
647
749
  });
648
750
  const listening = await server.listen();
649
751
  await runtime.health();
@@ -672,11 +774,11 @@ async function runWorker(parsed) {
672
774
  }
673
775
  function shouldRestart(rootDir, changed, agentsDir) {
674
776
  if (!changed) return false;
675
- const normalized = relative2(rootDir, resolve3(rootDir, changed)).split(sep2).join("/");
777
+ const normalized = relative2(rootDir, resolve4(rootDir, changed)).split(sep2).join("/");
676
778
  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);
677
779
  }
678
780
  async function superviseDev(parsed) {
679
- const rootDir = resolve3(
781
+ const rootDir = resolve4(
680
782
  typeof parsed.flags.root === "string" ? parsed.flags.root : process2.cwd()
681
783
  );
682
784
  const agentsDir = (await loadConfig(rootDir)).agentsDir ?? DEFAULT_FOUNDRY_CONFIG.agentsDir;
@@ -698,7 +800,7 @@ async function superviseDev(parsed) {
698
800
  ...typeof parsed.flags.host === "string" ? ["--host", parsed.flags.host] : []
699
801
  ];
700
802
  const start = () => {
701
- child = spawn(process2.execPath, workerArgs, {
803
+ child = spawn2(process2.execPath, workerArgs, {
702
804
  cwd: rootDir,
703
805
  env: process2.env,
704
806
  stdio: "inherit"
@@ -742,7 +844,7 @@ async function superviseDev(parsed) {
742
844
  (_event, filename) => onChange(filename?.toString() ?? null)
743
845
  )
744
846
  );
745
- const watchedAgentsDir = resolve3(rootDir, agentsDir);
847
+ const watchedAgentsDir = resolve4(rootDir, agentsDir);
746
848
  if (await pathExists(watchedAgentsDir)) {
747
849
  watchers.push(
748
850
  watch(
@@ -761,7 +863,7 @@ async function superviseDev(parsed) {
761
863
  "memory",
762
864
  "inboxes"
763
865
  ]) {
764
- const watched = resolve3(rootDir, directory);
866
+ const watched = resolve4(rootDir, directory);
765
867
  if (await pathExists(watched)) {
766
868
  watchers.push(
767
869
  watch(
@@ -791,10 +893,10 @@ async function superviseDev(parsed) {
791
893
  process2.once("SIGTERM", stop);
792
894
  }
793
895
  async function superviseStart(parsed) {
794
- const rootDir = resolve3(
896
+ const rootDir = resolve4(
795
897
  typeof parsed.flags.root === "string" ? parsed.flags.root : process2.cwd()
796
898
  );
797
- const child = spawn(
899
+ const child = spawn2(
798
900
  process2.execPath,
799
901
  [
800
902
  "--import",
@@ -842,6 +944,10 @@ function flagValue(parsed, name, allowed) {
842
944
  return raw;
843
945
  }
844
946
  async function create(directory, parsed) {
947
+ const supported = /* @__PURE__ */ new Set(["template", "target", "package-manager", "yes", "no-interactive", "interactive", "install", "no-install"]);
948
+ for (const flag of Object.keys(parsed.flags)) if (!supported.has(flag)) throw new UsageError(`Unknown create option --${flag}.`);
949
+ if (parsed.positional.length > 1) throw new UsageError("Provide only one project directory.");
950
+ if (parsed.flags.install && parsed.flags["no-install"]) throw new UsageError("Choose either --install or --no-install.");
845
951
  const template = flagValue(parsed, "template", FOUNDRY_TEMPLATES);
846
952
  const target = flagValue(parsed, "target", ["standalone", "nextjs"]);
847
953
  const packageManager = flagValue(
@@ -849,12 +955,45 @@ async function create(directory, parsed) {
849
955
  "package-manager",
850
956
  ["pnpm", "npm", "yarn", "bun"]
851
957
  );
852
- const result = await scaffoldFoundryProject({
853
- directory,
958
+ let interactive;
959
+ try {
960
+ interactive = useInteractiveInit(parsed.flags, Boolean(process2.stdin.isTTY && process2.stdout.isTTY));
961
+ } catch (error) {
962
+ throw new UsageError(error instanceof Error ? error.message : String(error));
963
+ }
964
+ const selected = {
965
+ ...directory ? { directory } : {},
854
966
  ...template ? { template } : {},
855
967
  ...target ? { target } : {},
856
- ...packageManager ? { packageManager } : {}
857
- });
968
+ ...packageManager ? { packageManager } : {},
969
+ ...parsed.flags.install ? { install: true } : parsed.flags["no-install"] ? { install: false } : {}
970
+ };
971
+ const options = interactive ? await promptInit(selected) : { ...selected, directory: directory ?? "glove-foundry-app", install: selected.install ?? false };
972
+ const ui = interactive ? await import("@clack/prompts") : void 0;
973
+ const progress = ui?.spinner();
974
+ progress?.start("Creating your agent application");
975
+ let result;
976
+ try {
977
+ result = await scaffoldFoundryProject(options);
978
+ } catch (error) {
979
+ progress?.stop("Project was not created");
980
+ throw error;
981
+ }
982
+ progress?.stop("Project files are ready");
983
+ let installed = false;
984
+ if (options.install) {
985
+ ui?.log.step(`Installing dependencies with ${result.packageManager}`);
986
+ try {
987
+ await installProject(result.rootDir, result.packageManager);
988
+ installed = true;
989
+ } catch (error) {
990
+ process2.exitCode = 1;
991
+ const message = `Project files are safe. Installation failed: ${error instanceof Error ? error.message : String(error)} Run ${result.packageManager} install in the project to retry.`;
992
+ if (ui) ui.log.warn(message);
993
+ else process2.stderr.write(`${message}
994
+ `);
995
+ }
996
+ }
858
997
  const where = displayPath(result.rootDir);
859
998
  const pm = result.packageManager;
860
999
  const run = (script) => pm === "npm" ? `npm run ${script}` : `${pm} ${script}`;
@@ -870,7 +1009,7 @@ async function create(directory, parsed) {
870
1009
  lines.push("");
871
1010
  lines.push(" Next:");
872
1011
  if (where !== ".") lines.push(` cd ${where}`);
873
- lines.push(` ${install}`);
1012
+ if (!installed) lines.push(` ${install}`);
874
1013
  lines.push(` ${run("foundry:dev")} # runtime + inspector on :4141`);
875
1014
  lines.push(` ${run("dev")} # your Next.js app`);
876
1015
  } else {
@@ -883,7 +1022,7 @@ async function create(directory, parsed) {
883
1022
  lines.push(" Next:");
884
1023
  if (where !== ".") lines.push(` cd ${where}`);
885
1024
  lines.push(" cp .env.example .env.local");
886
- lines.push(` ${install}`);
1025
+ if (!installed) lines.push(` ${install}`);
887
1026
  lines.push(` ${run("dev")}`);
888
1027
  lines.push("");
889
1028
  lines.push(" Then open http://127.0.0.1:4141 and press Start a run.");
@@ -898,7 +1037,10 @@ async function create(directory, parsed) {
898
1037
  lines.push(` ${result.versions.fellBack.join(", ")}`);
899
1038
  }
900
1039
  lines.push("");
901
- process2.stdout.write(lines.join("\n"));
1040
+ if (ui) {
1041
+ ui.note(lines.join("\n").trim(), "Next steps");
1042
+ ui.outro(process2.exitCode ? "Project created. Retry dependency installation before starting." : "Your agent application is ready to explore. Open the README to begin.");
1043
+ } else process2.stdout.write(lines.join("\n"));
902
1044
  }
903
1045
  async function main() {
904
1046
  const parsed = parseArgs(process2.argv.slice(2));
@@ -906,13 +1048,13 @@ async function main() {
906
1048
  await runWorker(parsed);
907
1049
  return;
908
1050
  }
909
- if (["help", "--help", "-h"].includes(parsed.command ?? "")) {
1051
+ if (parsed.flags.help || ["help", "--help", "-h"].includes(parsed.command ?? "")) {
910
1052
  process2.stdout.write(HELP);
911
1053
  return;
912
1054
  }
913
1055
  const createByGloveSyntax = parsed.gloveSyntax && parsed.command !== "dev" && parsed.command !== "start";
914
- if (parsed.command === "init" || createByGloveSyntax || parsed.gloveSyntax && !parsed.command) {
915
- const directory = parsed.command === "init" ? parsed.positional[0] ?? "glove-foundry-app" : parsed.command ?? "glove-foundry-app";
1056
+ if (parsed.command === "init" || createByGloveSyntax || !parsed.command && (process2.stdin.isTTY || parsed.flags.yes)) {
1057
+ const directory = parsed.command === "init" ? parsed.positional[0] : parsed.command ?? parsed.positional[0];
916
1058
  await create(directory, parsed);
917
1059
  return;
918
1060
  }
@@ -927,6 +1069,10 @@ async function main() {
927
1069
  process2.stdout.write(HELP);
928
1070
  }
929
1071
  main().catch((error) => {
1072
+ if (error instanceof InitCancelled) {
1073
+ process2.exitCode = 130;
1074
+ return;
1075
+ }
930
1076
  if (error instanceof UsageError) {
931
1077
  process2.stderr.write(`
932
1078
  ${error.message}