@flow-engineer/flow 0.2.3 → 0.3.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.
@@ -1,14 +1,19 @@
1
- // Pip in the playground: `npm start`, then open the printed link and pick a test person. With
2
- // FLOW_MESSAGING_KEY in .env (`npx @flow-engineer/flow link` puts it there), Pip is on Telegram
1
+ // The agent in the playground: `npm start`, then open the printed link and pick a test person. With
2
+ // FLOW_MESSAGING_KEY in .env (`npx @flow-engineer/flow link` puts it there), it is on Telegram
3
3
  // too (and WhatsApp and iMessage when .env names their senders), through Flow Messaging's event
4
- // stream: an outbound connection, so no public URL or tunnel.
4
+ // stream: an outbound connection, so no public URL or tunnel. It listens on PORT, else 8788 or the
5
+ // next free port above it.
5
6
 
7
+ import { readFileSync } from "node:fs";
8
+ import { choosePort, markRunning } from "../../../src/index.ts";
6
9
  import { flowMessaging } from "../../../flow-messaging/src/index.ts";
7
10
  import { simulators } from "../../../simulators/src/index.ts";
8
11
  import { buildAgent } from "./agent.ts";
9
12
  import { channelNames, flowChannels } from "./channels.ts";
10
13
 
11
- const play = simulators({ title: "Pip · Bean & Leaf" });
14
+ // What people call it: package.json's displayName (`npx @flow-engineer/flow link --display-name <name>` changes it).
15
+ const { displayName: name = "Pip" } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { displayName?: string };
16
+ const play = simulators({ title: `${name} · Bean & Leaf` });
12
17
  const fm = process.env.FLOW_MESSAGING_KEY ? flowMessaging() : undefined;
13
18
  const channels = fm ? flowChannels(fm) : [];
14
19
  let agent: ReturnType<typeof buildAgent>;
@@ -18,12 +23,22 @@ try {
18
23
  console.error((error as Error).message); // such as no model key in .env
19
24
  process.exit(1);
20
25
  }
21
- const port = Number(process.env.PORT ?? 8788);
22
- const { url } = await play.listen(agent, port);
23
- console.log(`Pip's playground: ${url}/`);
26
+ let url: string;
27
+ try {
28
+ const { port, note } = await choosePort("PORT", 8788, { command: "npm start" });
29
+ if (note) console.log(note);
30
+ ({ url } = await play.listen(agent, port));
31
+ } catch (error) {
32
+ console.error((error as Error).message); // such as the port PORT names being in use
33
+ process.exit(1);
34
+ }
35
+ const port = Number(new URL(url).port);
36
+ // `npx @flow-engineer/flow link` reads it (.flow/running.json) to say whether to start or restart this.
37
+ markRunning(url);
38
+ console.log(`${name}'s playground: ${url}/`);
24
39
  console.log(`Every channel side by side: ${url}/person`);
25
40
  if (fm) {
26
- // Events sent while Pip was not running come when it starts again (.flow/ keeps the last one handled).
41
+ // Events sent while it was not running come when it starts again (.flow/ keeps the last one handled).
27
42
  const live = fm.start(agent, { cursorFile: ".flow/flow-messaging-cursor", port: port + 1 });
28
43
  console.log(`On ${channelNames(channels)} through Flow Messaging (${live.mode}). The phone link: npx @flow-engineer/flow link`);
29
44
  live.stopped.then((why) => console.error(`Flow Messaging stopped: ${why.message} Then: npx @flow-engineer/flow setup`));
@@ -1,9 +1,11 @@
1
- // Pip's tools and skills as an MCP server, for Claude, ChatGPT or any MCP host: `npm run mcp`.
1
+ // The agent's tools and skills as an MCP server, for Claude, ChatGPT or any MCP host: `npm run mcp`.
2
2
  // The same tools the chat agent uses; no model key needed, since the host brings its own model.
3
3
 
4
+ import { readFileSync } from "node:fs";
4
5
  import { createServer } from "node:http";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import { mcpServer } from "../../../mcp/src/index.ts";
8
+ import { choosePort } from "../../../src/index.ts";
7
9
  import { tools } from "./tools.ts";
8
10
 
9
11
  export const mcp = mcpServer({
@@ -15,8 +17,24 @@ export const mcp = mcpServer({
15
17
  // auth: { issuer, resource, scopes, verify } (the chinwag-auth skill).
16
18
  });
17
19
 
18
- const port = Number(process.env.MCP_PORT ?? 8790);
19
- createServer((req, res) => void mcp.node(req, res)).listen(port, "127.0.0.1", () => {
20
- console.log(`Pip's MCP server: http://127.0.0.1:${port}/mcp`);
21
- console.log(`Try it in Claude Code: claude mcp add --transport http bean-and-leaf http://127.0.0.1:${port}/mcp`);
22
- });
20
+ // What people call it: package.json's displayName.
21
+ const { displayName: name = "Pip" } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { displayName?: string };
22
+ // MCP_PORT, else 8790 or the next free port above it.
23
+ let port: number;
24
+ try {
25
+ const chosen = await choosePort("MCP_PORT", 8790, { command: "npm run mcp" });
26
+ port = chosen.port;
27
+ if (chosen.note) console.log(`${chosen.note} The CLI then needs CAFE_SERVER=http://127.0.0.1:${port}/mcp.`);
28
+ } catch (error) {
29
+ console.error((error as Error).message);
30
+ process.exit(1);
31
+ }
32
+ createServer((req, res) => void mcp.node(req, res))
33
+ .once("error", (error) => {
34
+ console.error(`${error.message}. Run: MCP_PORT=<port> npm run mcp`);
35
+ process.exit(1);
36
+ })
37
+ .listen(port, "127.0.0.1", () => {
38
+ console.log(`${name}'s MCP server: http://127.0.0.1:${port}/mcp`);
39
+ console.log(`Try it in Claude Code: claude mcp add --transport http bean-and-leaf http://127.0.0.1:${port}/mcp`);
40
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flow-engineer/flow",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "An open-source runtime for chat agents that feel like a person texting back.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {