@agentic-surfaces/cli 0.1.4 → 0.1.6

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 +85 -9
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,9 +1,38 @@
1
- import { readFileSync, readdirSync } from "node:fs";
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
4
  import { loadWorkflow, runWorkflowOnce, Scheduler, loadProjectConfig, buildWorkflowRunner, defaultRegistry } from "@agentic-surfaces/core";
5
- import { serve } from "@agentic-surfaces/server";
5
+ import { serve, StreamingObserver } from "@agentic-surfaces/server";
6
6
  import { buildServices } from "./services.js";
7
+ const CONFIG = "agentic-surfaces.config.yaml";
8
+ // Locate a project folder when none is given: check the dir, its ./agentic-surfaces
9
+ // subfolder (the repo-native convention), then walk up. Lets `agentic-surfaces`
10
+ // be run bare from a repo root and still find the config.
11
+ function discoverProjectDir(start) {
12
+ let dir = resolve(start);
13
+ for (;;) {
14
+ if (existsSync(join(dir, CONFIG)))
15
+ return dir;
16
+ if (existsSync(join(dir, "agentic-surfaces", CONFIG)))
17
+ return join(dir, "agentic-surfaces");
18
+ const parent = dirname(dir);
19
+ if (parent === dir)
20
+ return undefined;
21
+ dir = parent;
22
+ }
23
+ }
24
+ // Load every workflow yaml in a folder into a name→Workflow map.
25
+ function loadWorkflows(dir) {
26
+ const map = new Map();
27
+ for (const f of readdirSync(dir).filter((f) => f.endsWith(".yaml") && f !== CONFIG)) {
28
+ try {
29
+ const w = loadWorkflow(readFileSync(join(dir, f), "utf8"));
30
+ map.set(w.name, w);
31
+ }
32
+ catch { /* skip unparseable */ }
33
+ }
34
+ return map;
35
+ }
7
36
  // `--ui`: start the live dashboard (HTTP server + SSE) and return its
8
37
  // StreamingObserver so run lifecycle events stream to the browser.
9
38
  function startUi() {
@@ -46,15 +75,52 @@ function findProjectConfig(startDir) {
46
75
  export async function run(argv) {
47
76
  const [cmd, ...rest] = argv;
48
77
  try {
78
+ // Default / `ui`: launch the visual control surface. `agentic-surfaces`
79
+ // (no args) discovers the project from the cwd; `ui <dir>` is explicit.
80
+ // Workflows are listed + Run-able from the browser; cron ones also fire.
81
+ if (cmd === undefined || cmd === "ui") {
82
+ const arg = rest.find((a) => !a.startsWith("--"));
83
+ const dir = arg ? resolve(arg) : discoverProjectDir(process.cwd());
84
+ if (!dir) {
85
+ console.error(`no ${CONFIG} found (looked in . and ./agentic-surfaces, then up).\n pass a folder: agentic-surfaces ui <projectDir>`);
86
+ return 1;
87
+ }
88
+ const pc = loadProjectConfig(dir);
89
+ const workflowsDir = pc?.workflows ? join(dir, pc.workflows) : dir;
90
+ const allWorkflows = loadWorkflows(workflowsDir);
91
+ const registry = defaultRegistry();
92
+ const observer = new StreamingObserver();
93
+ const services = buildServices({ projectConfig: pc });
94
+ const runWorkflowFn = buildWorkflowRunner({ workflows: allWorkflows, registry, services, observer });
95
+ const servicesWithRunner = { ...services, runWorkflow: runWorkflowFn };
96
+ // Cron-triggered workflows fire on schedule too.
97
+ const sched = new Scheduler(servicesWithRunner, registry, observer);
98
+ for (const [, w] of allWorkflows)
99
+ sched.add(w);
100
+ 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()],
106
+ onRun: (name) => runWorkflowFn(name, undefined),
107
+ });
108
+ const url = `http://127.0.0.1:${actualPort}`;
109
+ openBrowser(url);
110
+ console.log(`\n▶ agentic-surfaces — ${url}`);
111
+ console.log(` project: ${dir} · ${allWorkflows.size} workflow(s) · Ctrl-C to exit`);
112
+ await new Promise(() => { }); // serve until interrupted
113
+ return 0;
114
+ }
49
115
  if (cmd === "validate") {
50
116
  loadWorkflow(readFileSync(rest[0], "utf8"));
51
117
  console.log("OK:", rest[0]);
52
118
  return 0;
53
119
  }
54
- if (cmd === "run-once") {
120
+ if (cmd === "run-once" || cmd === "run") {
55
121
  const file = rest.find(a => !a.startsWith("--"));
56
122
  if (!file) {
57
- console.error("run-once: missing workflow file");
123
+ console.error("run: missing workflow file");
58
124
  return 1;
59
125
  }
60
126
  const fake = rest.includes("--fake");
@@ -75,13 +141,23 @@ export async function run(argv) {
75
141
  const observer = session?.observer;
76
142
  const runWorkflowFn = buildWorkflowRunner({ workflows: allWorkflows, registry, services, observer });
77
143
  const servicesWithRunner = { ...services, runWorkflow: runWorkflowFn };
78
- const outputs = await runWorkflowOnce(wf, { registry, services: servicesWithRunner, observer });
79
- console.log("ran", wf.name, "->", [...outputs.keys()].join(", "));
144
+ // A run error must NOT tear down the --ui server: the failure is already
145
+ // streamed to the dashboard (the failed node + run), so keep serving so it
146
+ // stays inspectable. Without --ui, an error still exits non-zero.
147
+ let failed = false;
148
+ try {
149
+ const outputs = await runWorkflowOnce(wf, { registry, services: servicesWithRunner, observer });
150
+ console.log("ran", wf.name, "->", [...outputs.keys()].join(", "));
151
+ }
152
+ catch (err) {
153
+ failed = true;
154
+ console.error("run failed:", err instanceof Error ? err.message : String(err));
155
+ }
80
156
  if (session) {
81
- console.log(`\n▶ Dashboard live at ${session.url} — Ctrl-C to exit`);
157
+ console.log(`\n▶ Dashboard live at ${session.url} — Ctrl-C to exit${failed ? " (run failed — see the dashboard)" : ""}`);
82
158
  await new Promise(() => { }); // keep the server up so the run stays viewable
83
159
  }
84
- return 0;
160
+ return failed ? 1 : 0;
85
161
  }
86
162
  if (cmd === "start") {
87
163
  const dir = rest.find(a => !a.startsWith("--")) ?? ".";
@@ -111,7 +187,7 @@ export async function run(argv) {
111
187
  console.log(`▶ Dashboard live at ${session.url}`);
112
188
  return 0;
113
189
  }
114
- console.error("usage: flow <validate|run-once|start> ...");
190
+ console.error("usage: flow <validate|run|start> [workflow|projectDir] [--ui] [--fake]");
115
191
  return 1;
116
192
  }
117
193
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentic-surfaces/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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.4",
25
- "@agentic-surfaces/agent": "0.1.4",
26
- "@agentic-surfaces/server": "0.1.4"
24
+ "@agentic-surfaces/agent": "0.1.6",
25
+ "@agentic-surfaces/core": "0.1.6",
26
+ "@agentic-surfaces/server": "0.1.6"
27
27
  },
28
28
  "scripts": {
29
29
  "build": "tsc -b",