@agentic-surfaces/cli 0.1.8 → 0.1.10

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.
Files changed (2) hide show
  1. package/dist/index.js +89 -29
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, readdirSync, existsSync } from "node:fs";
2
2
  import { join, dirname, resolve } from "node:path";
3
3
  import { spawn } from "node:child_process";
4
- import { loadWorkflow, runWorkflowOnce, Scheduler, loadProjectConfig, buildWorkflowRunner, defaultRegistry } from "@agentic-surfaces/core";
4
+ import { loadWorkflow, runWorkflowOnce, Scheduler, loadProjectConfig, buildWorkflowRunner, defaultRegistry, parseAgentFile } from "@agentic-surfaces/core";
5
5
  import { serve, StreamingObserver } from "@agentic-surfaces/server";
6
6
  import { buildServices } from "./services.js";
7
7
  const CONFIG = "agentic-surfaces.config.yaml";
@@ -33,14 +33,62 @@ function loadWorkflows(dir) {
33
33
  }
34
34
  return map;
35
35
  }
36
- // `--ui`: start the live dashboard (HTTP server + SSE) and return its
37
- // StreamingObserver so run lifecycle events stream to the browser.
38
- function startUi() {
36
+ // Load agents/<name>.md from the project into a name→AgentDefinition map.
37
+ // A bad agent file is reported and skipped (consistent with workflow loading).
38
+ function loadAgents(projectDir) {
39
+ const map = new Map();
40
+ const agentsDir = join(projectDir, "agents");
41
+ if (!existsSync(agentsDir))
42
+ return map;
43
+ for (const f of readdirSync(agentsDir).filter((f) => f.endsWith(".md"))) {
44
+ try {
45
+ const def = parseAgentFile(readFileSync(join(agentsDir, f), "utf8"), f.replace(/\.md$/, ""));
46
+ map.set(def.name, def);
47
+ }
48
+ catch (err) {
49
+ console.error(`skipping agent ${f}:`, err instanceof Error ? err.message : String(err));
50
+ }
51
+ }
52
+ return map;
53
+ }
54
+ // Walk up to the directory that contains agentic-surfaces.config.yaml.
55
+ function findProjectDir(start) {
56
+ let dir = resolve(start);
57
+ for (;;) {
58
+ if (existsSync(join(dir, CONFIG)))
59
+ return dir;
60
+ const parent = dirname(dir);
61
+ if (parent === dir)
62
+ return undefined;
63
+ dir = parent;
64
+ }
65
+ }
66
+ // This package's version, shown in the dashboard. npm always ships package.json
67
+ // in the tarball, so it resolves both from source and when installed via npx.
68
+ const VERSION = (() => {
69
+ try {
70
+ return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
71
+ }
72
+ catch {
73
+ return "unknown";
74
+ }
75
+ })();
76
+ // Launch THE dashboard — one full UI, used by every `--ui` path (bare `ui`,
77
+ // `run --ui`, `start --ui`). Always serves the workflow list, Run buttons,
78
+ // agents, config + version. There is no lite variant.
79
+ function launchUi(opts) {
39
80
  const port = parseInt(process.env["FLOW_UI_PORT"] ?? "4000", 10);
40
- const { observer, port: p } = serve({ port });
81
+ const { port: p } = serve({
82
+ port,
83
+ observer: opts.observer,
84
+ workflows: [...opts.workflows.values()],
85
+ agents: [...opts.agents.values()],
86
+ onRun: opts.onRun,
87
+ config: { dryRun: opts.dryRun, agent: opts.agentDefaults, version: VERSION },
88
+ });
41
89
  const url = `http://127.0.0.1:${p}`;
42
90
  openBrowser(url);
43
- return { observer, url };
91
+ return url;
44
92
  }
45
93
  function openBrowser(url) {
46
94
  if (process.env["FLOW_NO_OPEN"])
@@ -88,27 +136,24 @@ export async function run(argv) {
88
136
  const pc = loadProjectConfig(dir);
89
137
  const workflowsDir = pc?.workflows ? join(dir, pc.workflows) : dir;
90
138
  const allWorkflows = loadWorkflows(workflowsDir);
139
+ const agents = loadAgents(dir);
91
140
  const registry = defaultRegistry();
92
141
  const observer = new StreamingObserver();
93
142
  const services = buildServices({ projectConfig: pc });
94
- const runWorkflowFn = buildWorkflowRunner({ workflows: allWorkflows, registry, services, observer });
95
- const servicesWithRunner = { ...services, runWorkflow: runWorkflowFn };
143
+ const runWorkflowFn = buildWorkflowRunner({ workflows: allWorkflows, registry, services: { ...services, agents }, observer });
144
+ const servicesWithRunner = { ...services, agents, runWorkflow: runWorkflowFn };
96
145
  // Cron-triggered workflows fire on schedule too.
97
146
  const sched = new Scheduler(servicesWithRunner, registry, observer);
98
147
  for (const [, w] of allWorkflows)
99
148
  sched.add(w);
100
149
  sched.start();
101
- const port = parseInt(process.env["FLOW_UI_PORT"] ?? "4000", 10);
102
- const { port: actualPort } = serve({
103
- port,
104
- observer,
105
- workflows: [...allWorkflows.values()],
150
+ const url = launchUi({
151
+ observer, workflows: allWorkflows, agents,
106
152
  onRun: (name) => runWorkflowFn(name, undefined),
107
- config: { dryRun: pc?.dryRun, agent: { model: pc?.agent?.model, effort: pc?.agent?.effort } },
153
+ dryRun: pc?.dryRun,
154
+ agentDefaults: { model: pc?.agent?.model, effort: pc?.agent?.effort },
108
155
  });
109
- const url = `http://127.0.0.1:${actualPort}`;
110
- openBrowser(url);
111
- console.log(`\n▶ agentic-surfaces — ${url}`);
156
+ console.log(`\n▶ agentic-surfaces ${VERSION} — ${url}`);
112
157
  console.log(` project: ${dir} · ${allWorkflows.size} workflow(s) · Ctrl-C to exit`);
113
158
  await new Promise(() => { }); // serve until interrupted
114
159
  return 0;
@@ -137,11 +182,18 @@ export async function run(argv) {
137
182
  }
138
183
  catch { /* skip unparseable */ }
139
184
  }
185
+ const agents = loadAgents(findProjectDir(dirname(file)) ?? dir);
140
186
  const registry = defaultRegistry();
141
- const session = rest.includes("--ui") ? startUi() : undefined;
142
- const observer = session?.observer;
143
- const runWorkflowFn = buildWorkflowRunner({ workflows: allWorkflows, registry, services, observer });
144
- const servicesWithRunner = { ...services, runWorkflow: runWorkflowFn };
187
+ const ui = rest.includes("--ui");
188
+ const observer = ui ? new StreamingObserver() : undefined;
189
+ const runWorkflowFn = buildWorkflowRunner({ workflows: allWorkflows, registry, services: { ...services, agents }, observer });
190
+ const servicesWithRunner = { ...services, agents, runWorkflow: runWorkflowFn };
191
+ const url = ui ? launchUi({
192
+ observer: observer, workflows: allWorkflows, agents,
193
+ onRun: (name) => runWorkflowFn(name, undefined),
194
+ dryRun: pc?.dryRun,
195
+ agentDefaults: { model: pc?.agent?.model, effort: pc?.agent?.effort },
196
+ }) : undefined;
145
197
  // A run error must NOT tear down the --ui server: the failure is already
146
198
  // streamed to the dashboard (the failed node + run), so keep serving so it
147
199
  // stays inspectable. Without --ui, an error still exits non-zero.
@@ -154,8 +206,8 @@ export async function run(argv) {
154
206
  failed = true;
155
207
  console.error("run failed:", err instanceof Error ? err.message : String(err));
156
208
  }
157
- if (session) {
158
- console.log(`\n▶ Dashboard live at ${session.url} — Ctrl-C to exit${failed ? " (run failed — see the dashboard)" : ""}`);
209
+ if (url) {
210
+ console.log(`\n▶ agentic-surfaces ${VERSION} ${url} — Ctrl-C to exit${failed ? " (run failed — see the dashboard)" : ""}`);
159
211
  await new Promise(() => { }); // keep the server up so the run stays viewable
160
212
  }
161
213
  return failed ? 1 : 0;
@@ -174,18 +226,26 @@ export async function run(argv) {
174
226
  }
175
227
  catch { /* skip unparseable */ }
176
228
  }
229
+ const agents = loadAgents(dir);
177
230
  const registry = defaultRegistry();
178
- const session = rest.includes("--ui") ? startUi() : undefined;
179
- const observer = session?.observer;
180
- const runWorkflowFn = buildWorkflowRunner({ workflows: allWorkflows, registry, services, observer });
181
- const servicesWithRunner = { ...services, runWorkflow: runWorkflowFn };
231
+ const ui = rest.includes("--ui");
232
+ const observer = ui ? new StreamingObserver() : undefined;
233
+ const runWorkflowFn = buildWorkflowRunner({ workflows: allWorkflows, registry, services: { ...services, agents }, observer });
234
+ const servicesWithRunner = { ...services, agents, runWorkflow: runWorkflowFn };
182
235
  const sched = new Scheduler(servicesWithRunner, registry, observer);
183
236
  for (const [, w] of allWorkflows)
184
237
  sched.add(w);
185
238
  sched.start();
186
239
  console.log("scheduler started; watching", workflowsDir);
187
- if (session)
188
- console.log(`▶ Dashboard live at ${session.url}`);
240
+ if (ui) {
241
+ const url = launchUi({
242
+ observer: observer, workflows: allWorkflows, agents,
243
+ onRun: (name) => runWorkflowFn(name, undefined),
244
+ dryRun: pc?.dryRun,
245
+ agentDefaults: { model: pc?.agent?.model, effort: pc?.agent?.effort },
246
+ });
247
+ console.log(`▶ agentic-surfaces ${VERSION} — ${url}`);
248
+ }
189
249
  return 0;
190
250
  }
191
251
  console.error("usage: flow <validate|run|start> [workflow|projectDir] [--ui] [--fake]");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentic-surfaces/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -21,9 +21,9 @@
21
21
  "dist"
22
22
  ],
23
23
  "dependencies": {
24
- "@agentic-surfaces/core": "0.1.8",
25
- "@agentic-surfaces/agent": "0.1.8",
26
- "@agentic-surfaces/server": "0.1.8"
24
+ "@agentic-surfaces/core": "0.1.10",
25
+ "@agentic-surfaces/agent": "0.1.10",
26
+ "@agentic-surfaces/server": "0.1.10"
27
27
  },
28
28
  "scripts": {
29
29
  "build": "tsc -b",