@agentic-surfaces/cli 0.1.29 → 0.1.30
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/index.js +243 -52
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
2
|
-
import { join, dirname, resolve } from "node:path";
|
|
1
|
+
import { readFileSync, readdirSync, existsSync, watch as watchFs, } from "node:fs";
|
|
2
|
+
import { join, dirname, resolve, relative, sep } from "node:path";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { loadWorkflow, runWorkflowOnce, Scheduler, loadProjectConfig, buildWorkflowRunner, defaultRegistry, parseAgentFile, FilePauseState } from "@agentic-surfaces/core";
|
|
4
|
+
import { loadWorkflow, runWorkflowOnce, Scheduler, loadProjectConfig, buildWorkflowRunner, defaultRegistry, parseAgentFile, FilePauseState, } 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";
|
|
@@ -19,7 +19,8 @@ function loadDotenv(dir = process.cwd()) {
|
|
|
19
19
|
if (!m)
|
|
20
20
|
continue; // skips blanks + `# comments`
|
|
21
21
|
let val = m[2];
|
|
22
|
-
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
22
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
23
|
+
(val.startsWith("'") && val.endsWith("'")))
|
|
23
24
|
val = val.slice(1, -1);
|
|
24
25
|
if (process.env[m[1]] === undefined)
|
|
25
26
|
process.env[m[1]] = val;
|
|
@@ -41,15 +42,63 @@ function discoverProjectDir(start) {
|
|
|
41
42
|
dir = parent;
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
//
|
|
45
|
+
// Recursively collect every workflow yaml under `dir` as absolute file paths.
|
|
46
|
+
// Subdirectories are walked so workflows can be organised into folders (the
|
|
47
|
+
// folder path becomes the sidebar group when a workflow omits an explicit
|
|
48
|
+
// `group:` field — see loadWorkflows).
|
|
49
|
+
function collectWorkflowFiles(dir) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
52
|
+
const full = join(dir, entry.name);
|
|
53
|
+
if (entry.isDirectory()) {
|
|
54
|
+
out.push(...collectWorkflowFiles(full));
|
|
55
|
+
}
|
|
56
|
+
else if (entry.isFile() &&
|
|
57
|
+
entry.name.endsWith(".yaml") &&
|
|
58
|
+
entry.name !== CONFIG) {
|
|
59
|
+
out.push(full);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
// Derive the fallback group for a workflow file from its subdirectory path
|
|
65
|
+
// relative to the workflows dir. `workflows/sentry/poll.yaml` → "sentry";
|
|
66
|
+
// `workflows/sentry/bugs/x.yaml` → "sentry/bugs"; a file at the root → undefined.
|
|
67
|
+
function groupFromPath(rootDir, file) {
|
|
68
|
+
const rel = relative(rootDir, dirname(file));
|
|
69
|
+
if (!rel || rel === "." || rel.startsWith(".."))
|
|
70
|
+
return undefined;
|
|
71
|
+
return rel.split(sep).join("/");
|
|
72
|
+
}
|
|
73
|
+
// Load every workflow yaml under a folder (recursively) into a name→Workflow map.
|
|
74
|
+
// Grouping precedence: an explicit top-level `group:` field on the workflow wins;
|
|
75
|
+
// otherwise the file's subdirectory path becomes the group; a workflow at the root
|
|
76
|
+
// with no `group:` stays ungrouped. The map is keyed by workflow `name`, which must
|
|
77
|
+
// stay globally unique across subdirs (sub-workflow `foreach`/run-workflow refs
|
|
78
|
+
// resolve by name) — a collision is a hard error rather than a silent overwrite.
|
|
45
79
|
function loadWorkflows(dir) {
|
|
46
80
|
const map = new Map();
|
|
47
|
-
|
|
81
|
+
const seenAt = new Map(); // name → file that defined it
|
|
82
|
+
for (const file of collectWorkflowFiles(dir)) {
|
|
83
|
+
let w;
|
|
48
84
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
85
|
+
w = loadWorkflow(readFileSync(file, "utf8"));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
continue; /* skip unparseable */
|
|
51
89
|
}
|
|
52
|
-
|
|
90
|
+
if (seenAt.has(w.name)) {
|
|
91
|
+
throw new Error(`duplicate workflow name "${w.name}" — defined in both ${relative(dir, seenAt.get(w.name))} and ${relative(dir, file)}. ` +
|
|
92
|
+
`Workflow names must be globally unique (they are how sub-workflows are referenced).`);
|
|
93
|
+
}
|
|
94
|
+
seenAt.set(w.name, file);
|
|
95
|
+
// Explicit `group:` field wins; otherwise fall back to the subdirectory path.
|
|
96
|
+
if (w.group === undefined) {
|
|
97
|
+
const g = groupFromPath(dir, file);
|
|
98
|
+
if (g)
|
|
99
|
+
w = { ...w, group: g };
|
|
100
|
+
}
|
|
101
|
+
map.set(w.name, w);
|
|
53
102
|
}
|
|
54
103
|
return map;
|
|
55
104
|
}
|
|
@@ -71,6 +120,74 @@ function loadAgents(projectDir) {
|
|
|
71
120
|
}
|
|
72
121
|
return map;
|
|
73
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Build the in-process hot-reload function for `start`/`ui`. Re-scans the workflows dir (recursively,
|
|
125
|
+
* same loader as startup) + the agents dir, rebuilds the shared maps IN PLACE (so the workflow runner's
|
|
126
|
+
* closure over `workflows` and `services.agents` sees the new set), and re-registers the scheduler's cron
|
|
127
|
+
* timers from the fresh workflow set. Returns the new workflow list for the server to serve.
|
|
128
|
+
*
|
|
129
|
+
* Mutating the existing Map instances (rather than replacing them) is deliberate: the runner built by
|
|
130
|
+
* `buildWorkflowRunner` and the services object both close over those Map references, so `foreach` /
|
|
131
|
+
* run-workflow sub-workflow resolution and agent lookups pick up edits without rebuilding the runner.
|
|
132
|
+
*
|
|
133
|
+
* A reload that fails to parse the dir (e.g. mid-write) is reported and skipped — the previous good set
|
|
134
|
+
* stays live rather than blanking the dashboard.
|
|
135
|
+
*/
|
|
136
|
+
function makeReloader(opts) {
|
|
137
|
+
return () => {
|
|
138
|
+
const next = loadWorkflows(opts.workflowsDir); // throws on duplicate names; caught by /api/reload
|
|
139
|
+
// Rebuild the shared workflow map in place.
|
|
140
|
+
opts.workflows.clear();
|
|
141
|
+
for (const [name, wf] of next)
|
|
142
|
+
opts.workflows.set(name, wf);
|
|
143
|
+
// Rebuild the shared agents map in place.
|
|
144
|
+
const nextAgents = loadAgents(opts.projectDir);
|
|
145
|
+
opts.agents.clear();
|
|
146
|
+
for (const [name, def] of nextAgents)
|
|
147
|
+
opts.agents.set(name, def);
|
|
148
|
+
// Re-register cron timers from the fresh set (cancels the old ones — no leaks, no double-fire).
|
|
149
|
+
opts.scheduler.reload([...next.values()]);
|
|
150
|
+
return [...next.values()];
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Watch the project dir (recursively) and hot-reload on workflow/agent edits. Editors fire several
|
|
155
|
+
* fs events per save, so the reload is debounced (~400ms). After a successful reload it emits a
|
|
156
|
+
* `workflows:changed` SSE event so the dashboard re-fetches /api/workflows without a refresh.
|
|
157
|
+
*
|
|
158
|
+
* Loop guard: a reload only reads files (the scheduler + maps are rebuilt from them) and never writes
|
|
159
|
+
* back into the watched dir, so a reload cannot retrigger itself; the debounce additionally collapses
|
|
160
|
+
* any burst into a single reload.
|
|
161
|
+
*
|
|
162
|
+
* This is the in-process successor to the old `--watch` (which exited for a supervisor restart). The
|
|
163
|
+
* launchd path that relied on `start --watch` still works — see the `start` command for the flag's
|
|
164
|
+
* retained behavior.
|
|
165
|
+
*/
|
|
166
|
+
function watchAndReload(dir, observer, reload) {
|
|
167
|
+
let pending;
|
|
168
|
+
try {
|
|
169
|
+
watchFs(dir, { recursive: true }, (_event, filename) => {
|
|
170
|
+
if (!filename || !/\.(yaml|md)$/.test(filename.toString()))
|
|
171
|
+
return;
|
|
172
|
+
clearTimeout(pending);
|
|
173
|
+
pending = setTimeout(() => {
|
|
174
|
+
try {
|
|
175
|
+
const next = reload();
|
|
176
|
+
observer.notifyWorkflowsChanged();
|
|
177
|
+
console.log(`hot-reload: ${next.length} workflow(s) (changed: ${filename})`);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
// Keep the previous good set live; just report (e.g. mid-write parse error or a name clash).
|
|
181
|
+
console.error("hot-reload skipped:", err instanceof Error ? err.message : String(err));
|
|
182
|
+
}
|
|
183
|
+
}, 400); // debounce editor multi-writes
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
// recursive watch isn't supported everywhere; degrade to manual reload (the Reload button).
|
|
188
|
+
console.error("hot-reload watch unavailable (use the Reload button):", err instanceof Error ? err.message : String(err));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
74
191
|
// Walk up to the directory that contains agentic-surfaces.config.yaml.
|
|
75
192
|
function findProjectDir(start) {
|
|
76
193
|
let dir = resolve(start);
|
|
@@ -107,7 +224,13 @@ function launchUi(opts) {
|
|
|
107
224
|
cache: opts.cache,
|
|
108
225
|
pause: opts.pause,
|
|
109
226
|
planUsage: opts.planUsage,
|
|
110
|
-
|
|
227
|
+
onReload: opts.onReload,
|
|
228
|
+
config: {
|
|
229
|
+
dryRun: opts.dryRun,
|
|
230
|
+
agent: opts.agentDefaults,
|
|
231
|
+
version: VERSION,
|
|
232
|
+
atlassianSite: opts.atlassianSite,
|
|
233
|
+
},
|
|
111
234
|
});
|
|
112
235
|
const url = `http://127.0.0.1:${p}`;
|
|
113
236
|
openBrowser(url);
|
|
@@ -116,8 +239,10 @@ function launchUi(opts) {
|
|
|
116
239
|
function openBrowser(url) {
|
|
117
240
|
if (process.env["FLOW_NO_OPEN"])
|
|
118
241
|
return; // headless / CI / don't hijack a browser
|
|
119
|
-
const cmd = process.platform === "darwin"
|
|
120
|
-
|
|
242
|
+
const cmd = process.platform === "darwin"
|
|
243
|
+
? "open"
|
|
244
|
+
: process.platform === "win32"
|
|
245
|
+
? "start"
|
|
121
246
|
: "xdg-open";
|
|
122
247
|
try {
|
|
123
248
|
spawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -164,8 +289,17 @@ export async function run(argv) {
|
|
|
164
289
|
const registry = defaultRegistry();
|
|
165
290
|
const observer = new StreamingObserver();
|
|
166
291
|
const services = buildServices({ projectConfig: pc, projectDir: dir });
|
|
167
|
-
const runWorkflowFn = buildWorkflowRunner({
|
|
168
|
-
|
|
292
|
+
const runWorkflowFn = buildWorkflowRunner({
|
|
293
|
+
workflows: allWorkflows,
|
|
294
|
+
registry,
|
|
295
|
+
services: { ...services, agents },
|
|
296
|
+
observer,
|
|
297
|
+
});
|
|
298
|
+
const servicesWithRunner = {
|
|
299
|
+
...services,
|
|
300
|
+
agents,
|
|
301
|
+
runWorkflow: runWorkflowFn,
|
|
302
|
+
};
|
|
169
303
|
// Platform-wide play/pause (file-backed so it survives a --watch restart).
|
|
170
304
|
const pause = new FilePauseState();
|
|
171
305
|
// Cron-triggered workflows fire on schedule too.
|
|
@@ -173,8 +307,17 @@ export async function run(argv) {
|
|
|
173
307
|
for (const [, w] of allWorkflows)
|
|
174
308
|
sched.add(w);
|
|
175
309
|
sched.start();
|
|
310
|
+
const reload = makeReloader({
|
|
311
|
+
workflowsDir,
|
|
312
|
+
projectDir: dir,
|
|
313
|
+
workflows: allWorkflows,
|
|
314
|
+
agents,
|
|
315
|
+
scheduler: sched,
|
|
316
|
+
});
|
|
176
317
|
const url = launchUi({
|
|
177
|
-
observer,
|
|
318
|
+
observer,
|
|
319
|
+
workflows: allWorkflows,
|
|
320
|
+
agents,
|
|
178
321
|
onRun: (name, payload) => runWorkflowFn(name, payload),
|
|
179
322
|
cache: services.cache,
|
|
180
323
|
pause,
|
|
@@ -182,9 +325,13 @@ export async function run(argv) {
|
|
|
182
325
|
dryRun: pc?.dryRun,
|
|
183
326
|
agentDefaults: { model: pc?.agent?.model, effort: pc?.agent?.effort },
|
|
184
327
|
atlassianSite: pc?.atlassianSite,
|
|
328
|
+
onReload: reload,
|
|
185
329
|
});
|
|
330
|
+
// Automatic hot reload: a debounced recursive watch on the project dir re-scans + re-registers
|
|
331
|
+
// the scheduler in place and pushes a `workflows:changed` SSE event so the dashboard re-fetches.
|
|
332
|
+
watchAndReload(dir, observer, reload);
|
|
186
333
|
console.log(`\n▶ agentic-surfaces ${VERSION} — ${url}`);
|
|
187
|
-
console.log(` project: ${dir} · ${allWorkflows.size} workflow(s) · Ctrl-C to exit`);
|
|
334
|
+
console.log(` project: ${dir} · ${allWorkflows.size} workflow(s) · hot-reload on · Ctrl-C to exit`);
|
|
188
335
|
await new Promise(() => { }); // serve until interrupted
|
|
189
336
|
return 0;
|
|
190
337
|
}
|
|
@@ -194,7 +341,7 @@ export async function run(argv) {
|
|
|
194
341
|
return 0;
|
|
195
342
|
}
|
|
196
343
|
if (cmd === "run-once" || cmd === "run") {
|
|
197
|
-
const file = rest.find(a => !a.startsWith("--"));
|
|
344
|
+
const file = rest.find((a) => !a.startsWith("--"));
|
|
198
345
|
if (!file) {
|
|
199
346
|
console.error("run: missing workflow file");
|
|
200
347
|
return 1;
|
|
@@ -203,36 +350,60 @@ export async function run(argv) {
|
|
|
203
350
|
const pc = findProjectConfig(dirname(file));
|
|
204
351
|
const wf = loadWorkflow(readFileSync(file, "utf8"));
|
|
205
352
|
const projectDir = findProjectDir(dirname(file)) ?? dirname(file);
|
|
206
|
-
const services = buildServices(fake
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const w = loadWorkflow(readFileSync(join(dir, f), "utf8"));
|
|
212
|
-
allWorkflows.set(w.name, w);
|
|
353
|
+
const services = buildServices(fake
|
|
354
|
+
? {
|
|
355
|
+
fakeAgent: [{ text: "fake reply" }],
|
|
356
|
+
projectConfig: pc,
|
|
357
|
+
projectDir,
|
|
213
358
|
}
|
|
214
|
-
|
|
215
|
-
|
|
359
|
+
: { projectConfig: pc, projectDir });
|
|
360
|
+
// Scan recursively from the workflows root (so sub-workflow refs resolve
|
|
361
|
+
// across subdir folders + groups come through), falling back to the file's
|
|
362
|
+
// own dir when no project config is present.
|
|
363
|
+
const scanDir = pc?.workflows
|
|
364
|
+
? join(projectDir, pc.workflows)
|
|
365
|
+
: dirname(file);
|
|
366
|
+
const allWorkflows = loadWorkflows(scanDir);
|
|
216
367
|
const agents = loadAgents(projectDir);
|
|
217
368
|
const registry = defaultRegistry();
|
|
218
369
|
const ui = rest.includes("--ui");
|
|
219
370
|
const observer = ui ? new StreamingObserver() : undefined;
|
|
220
|
-
const runWorkflowFn = buildWorkflowRunner({
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
371
|
+
const runWorkflowFn = buildWorkflowRunner({
|
|
372
|
+
workflows: allWorkflows,
|
|
373
|
+
registry,
|
|
374
|
+
services: { ...services, agents },
|
|
375
|
+
observer,
|
|
376
|
+
});
|
|
377
|
+
const servicesWithRunner = {
|
|
378
|
+
...services,
|
|
379
|
+
agents,
|
|
380
|
+
runWorkflow: runWorkflowFn,
|
|
381
|
+
};
|
|
382
|
+
const url = ui
|
|
383
|
+
? launchUi({
|
|
384
|
+
observer: observer,
|
|
385
|
+
workflows: allWorkflows,
|
|
386
|
+
agents,
|
|
387
|
+
onRun: (name, payload) => runWorkflowFn(name, payload),
|
|
388
|
+
cache: services.cache,
|
|
389
|
+
dryRun: pc?.dryRun,
|
|
390
|
+
agentDefaults: {
|
|
391
|
+
model: pc?.agent?.model,
|
|
392
|
+
effort: pc?.agent?.effort,
|
|
393
|
+
},
|
|
394
|
+
atlassianSite: pc?.atlassianSite,
|
|
395
|
+
})
|
|
396
|
+
: undefined;
|
|
230
397
|
// A run error must NOT tear down the --ui server: the failure is already
|
|
231
398
|
// streamed to the dashboard (the failed node + run), so keep serving so it
|
|
232
399
|
// stays inspectable. Without --ui, an error still exits non-zero.
|
|
233
400
|
let failed = false;
|
|
234
401
|
try {
|
|
235
|
-
const outputs = await runWorkflowOnce(wf, {
|
|
402
|
+
const outputs = await runWorkflowOnce(wf, {
|
|
403
|
+
registry,
|
|
404
|
+
services: servicesWithRunner,
|
|
405
|
+
observer,
|
|
406
|
+
});
|
|
236
407
|
console.log("ran", wf.name, "->", [...outputs.keys()].join(", "));
|
|
237
408
|
}
|
|
238
409
|
catch (err) {
|
|
@@ -246,34 +417,45 @@ export async function run(argv) {
|
|
|
246
417
|
return failed ? 1 : 0;
|
|
247
418
|
}
|
|
248
419
|
if (cmd === "start") {
|
|
249
|
-
const dir = rest.find(a => !a.startsWith("--")) ?? ".";
|
|
420
|
+
const dir = rest.find((a) => !a.startsWith("--")) ?? ".";
|
|
250
421
|
const pc = loadProjectConfig(dir);
|
|
251
422
|
const workflowsDir = pc?.workflows ? join(dir, pc.workflows) : dir;
|
|
252
423
|
const services = buildServices({ projectConfig: pc, projectDir: dir });
|
|
253
|
-
const allWorkflows =
|
|
254
|
-
const yamlFiles = readdirSync(workflowsDir).filter(f => f.endsWith(".yaml") && f !== "agentic-surfaces.config.yaml");
|
|
255
|
-
for (const f of yamlFiles) {
|
|
256
|
-
try {
|
|
257
|
-
const w = loadWorkflow(readFileSync(join(workflowsDir, f), "utf8"));
|
|
258
|
-
allWorkflows.set(w.name, w);
|
|
259
|
-
}
|
|
260
|
-
catch { /* skip unparseable */ }
|
|
261
|
-
}
|
|
424
|
+
const allWorkflows = loadWorkflows(workflowsDir);
|
|
262
425
|
const agents = loadAgents(dir);
|
|
263
426
|
const registry = defaultRegistry();
|
|
264
427
|
const ui = rest.includes("--ui");
|
|
265
428
|
const observer = ui ? new StreamingObserver() : undefined;
|
|
266
|
-
const runWorkflowFn = buildWorkflowRunner({
|
|
267
|
-
|
|
429
|
+
const runWorkflowFn = buildWorkflowRunner({
|
|
430
|
+
workflows: allWorkflows,
|
|
431
|
+
registry,
|
|
432
|
+
services: { ...services, agents },
|
|
433
|
+
observer,
|
|
434
|
+
});
|
|
435
|
+
const servicesWithRunner = {
|
|
436
|
+
...services,
|
|
437
|
+
agents,
|
|
438
|
+
runWorkflow: runWorkflowFn,
|
|
439
|
+
};
|
|
268
440
|
const pause = new FilePauseState();
|
|
269
441
|
const sched = new Scheduler(servicesWithRunner, registry, observer, pause);
|
|
270
442
|
for (const [, w] of allWorkflows)
|
|
271
443
|
sched.add(w);
|
|
272
444
|
sched.start();
|
|
445
|
+
const reload = makeReloader({
|
|
446
|
+
workflowsDir,
|
|
447
|
+
projectDir: dir,
|
|
448
|
+
workflows: allWorkflows,
|
|
449
|
+
agents,
|
|
450
|
+
scheduler: sched,
|
|
451
|
+
});
|
|
273
452
|
console.log(`scheduler started; watching ${workflowsDir}${pause.isPaused() ? " (PAUSED)" : ""}`);
|
|
453
|
+
const watchFlag = rest.includes("--watch");
|
|
274
454
|
if (ui) {
|
|
275
455
|
const url = launchUi({
|
|
276
|
-
observer: observer,
|
|
456
|
+
observer: observer,
|
|
457
|
+
workflows: allWorkflows,
|
|
458
|
+
agents,
|
|
277
459
|
onRun: (name, payload) => runWorkflowFn(name, payload),
|
|
278
460
|
cache: services.cache,
|
|
279
461
|
pause,
|
|
@@ -281,13 +463,22 @@ export async function run(argv) {
|
|
|
281
463
|
dryRun: pc?.dryRun,
|
|
282
464
|
agentDefaults: { model: pc?.agent?.model, effort: pc?.agent?.effort },
|
|
283
465
|
atlassianSite: pc?.atlassianSite,
|
|
466
|
+
onReload: reload,
|
|
284
467
|
});
|
|
285
468
|
console.log(`▶ agentic-surfaces ${VERSION} — ${url}`);
|
|
469
|
+
// With the dashboard up, prefer in-process hot reload (button + automatic) over the legacy
|
|
470
|
+
// process-exit watcher — unless --watch is explicitly passed (the supervisor-restart contract).
|
|
471
|
+
if (!watchFlag) {
|
|
472
|
+
watchAndReload(dir, observer, reload);
|
|
473
|
+
console.log("hot-reload on (Reload button + automatic file watch)");
|
|
474
|
+
}
|
|
286
475
|
}
|
|
287
476
|
// --watch: exit when a config file changes so a supervisor (launchd/pm2/systemd) restarts us
|
|
288
477
|
// with the fresh config. The seen-cache (.flow-cache.sqlite) persists across the restart, so
|
|
289
|
-
// dedup is preserved. Without a supervisor this just exits on the first edit.
|
|
290
|
-
|
|
478
|
+
// dedup is preserved. Without a supervisor this just exits on the first edit. RETAINED for the
|
|
479
|
+
// launchd path; the dashboard's in-process hot reload above is the lighter-weight default when
|
|
480
|
+
// --watch is omitted. When --watch is set it wins (a supervisor expects the process to exit).
|
|
481
|
+
if (watchFlag) {
|
|
291
482
|
const { watch } = await import("node:fs");
|
|
292
483
|
let pending;
|
|
293
484
|
watch(dir, { recursive: true }, (_event, filename) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentic-surfaces/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"README.md"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agentic-surfaces/
|
|
26
|
-
"@agentic-surfaces/
|
|
27
|
-
"@agentic-surfaces/server": "0.1.
|
|
25
|
+
"@agentic-surfaces/agent": "0.1.30",
|
|
26
|
+
"@agentic-surfaces/core": "0.1.30",
|
|
27
|
+
"@agentic-surfaces/server": "0.1.30"
|
|
28
28
|
},
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|