mainflow 0.11.0 → 0.11.2
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/cli.js +2 -2
- package/dist/hooks.js +4 -3
- package/dist/{host-iTpj7MPD.js → host-C3MyDGB_.js} +58 -20
- package/dist/host.d.ts +6 -0
- package/dist/host.js +2 -2
- package/dist/{launch-BMuk7UvN.js → launch-T0sXbTgT.js} +26 -5
- package/dist/launch.d.ts +6 -0
- package/dist/launch.js +2 -2
- package/dist/{ui-DNLdjWwd.js → ui-BTLe6I--.js} +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { C as supersedeDecision, F as updateAction, G as createIssue, H as appendOps, I as sessionBriefing, J as setFindingArchived, K as planIssue, N as startSession, O as readSession, Q as loadHub, T as addAction, U as closeIssue, W as createFinding, X as setIssueArchived, Y as setFindingStatus, Z as HubError, _ as initHub, b as setCriterion, d as addHub, f as addRepo, g as dropChecklistItem, h as createTask, it as WriteError, j as setSessionStatus, k as setSessionArchived, l as migrateHub, m as createMainline, nt as TASK_FILE, p as createDecision, q as reopenIssue, st as findHubRoot, tt as MAINLINE_FILE, u as acceptDecision, v as setArchived, w as updateTask, x as setMainlineStatus, y as setChecklistItem } from "./host-C3MyDGB_.js";
|
|
3
3
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
4
4
|
import { COMMAND_SPECS, OPTIONS } from "./facts.js";
|
|
5
5
|
import { runHook } from "./hooks.js";
|
|
6
|
-
import {
|
|
6
|
+
import { l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-T0sXbTgT.js";
|
|
7
7
|
import { readFileSync, realpathSync } from "node:fs";
|
|
8
8
|
import { join, relative } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
package/dist/hooks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as setSessionInfo, B as headerLine, E as ensureSession, I as sessionBriefing, L as turnReminder, M as settleSession, O as readSession, Q as loadHub, R as decorateReply, V as mayDecorate, Z as HubError, at as DATA_DIR, ct as worktreesOf, i as hostName, j as setSessionStatus, ot as currentBranch, st as findHubRoot, z as endsWithMarker } from "./host-C3MyDGB_.js";
|
|
2
2
|
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
//#region src/hooks.ts
|
|
@@ -55,8 +55,9 @@ const settleOnEnd = (root, { payload }) => {
|
|
|
55
55
|
/** How a hub's interface is linked from a reply: through its /open route, which shows the address in the app. */
|
|
56
56
|
const openLink = (hub) => (path) => `https://${hostName(hub)}/open${path}`;
|
|
57
57
|
/**
|
|
58
|
-
* MessageDisplay: link the references in the lines about to be shown and expand
|
|
59
|
-
*
|
|
58
|
+
* MessageDisplay: link the references in the lines about to be shown and expand the trailing header line into
|
|
59
|
+
* the mainline › task › action its ids name, titles included, whatever state that action is in now. Display
|
|
60
|
+
* only, so the model keeps reading what it wrote. Text with nothing to change
|
|
60
61
|
* returns before the hub is loaded, because Claude Code holds every batch until the hook answers.
|
|
61
62
|
*/
|
|
62
63
|
const displayReply = (root, { payload }) => {
|
|
@@ -936,31 +936,54 @@ function linkMentions(text, context) {
|
|
|
936
936
|
return path ? `[${ref}](${context.link(path)})` : all;
|
|
937
937
|
})).join("");
|
|
938
938
|
}
|
|
939
|
-
/** `▸ m001 title › t001 title › a001 do
|
|
940
|
-
function
|
|
941
|
-
const action = context.session?.actions.find((a) => a.status === "doing");
|
|
942
|
-
if (!context.session || !action) return null;
|
|
939
|
+
/** `▸ m001 title › t001 title › a001 do` for the given ids, each linked; titles come from the hub and the session. */
|
|
940
|
+
function headerLineFor(ids, context) {
|
|
943
941
|
const index = refIndex(context.hub);
|
|
944
942
|
const parts = [];
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
const ref = `${served.mainline}/${served.task}`;
|
|
951
|
-
parts.push(`[${served.task}](${context.link(`/mainlines/${served.mainline}/tasks/${served.task}`)}) ${index.get(ref)?.title ?? ""}`.trimEnd());
|
|
943
|
+
if (ids.mainline) {
|
|
944
|
+
parts.push(`[${ids.mainline}](${context.link(`/mainlines/${ids.mainline}`)}) ${index.get(ids.mainline)?.title ?? ""}`.trimEnd());
|
|
945
|
+
if (ids.task) {
|
|
946
|
+
const ref = `${ids.mainline}/${ids.task}`;
|
|
947
|
+
parts.push(`[${ids.task}](${context.link(`/mainlines/${ids.mainline}/tasks/${ids.task}`)}) ${index.get(ref)?.title ?? ""}`.trimEnd());
|
|
952
948
|
}
|
|
953
949
|
}
|
|
954
|
-
|
|
950
|
+
const action = context.session?.actions.find((a) => a.id === ids.action);
|
|
951
|
+
parts.push(context.session ? `[${ids.action}](${context.link(sessionPath(context.session.id))}) ${action?.do ?? ""}`.trimEnd() : ids.action);
|
|
955
952
|
return `▸ ${parts.join(" › ")}`;
|
|
956
953
|
}
|
|
957
|
-
/** The
|
|
958
|
-
function
|
|
954
|
+
/** The ids of the session's action in progress; null when there is none. */
|
|
955
|
+
function currentIds(session) {
|
|
959
956
|
const action = session?.actions.find((a) => a.status === "doing");
|
|
960
957
|
if (!action) return null;
|
|
961
958
|
const served = action.serves.map((s) => parseRef(s)).find((r) => r !== null);
|
|
962
|
-
|
|
963
|
-
|
|
959
|
+
if (served && "mainline" in served) return {
|
|
960
|
+
mainline: served.mainline,
|
|
961
|
+
task: "task" in served ? served.task : void 0,
|
|
962
|
+
action: action.id
|
|
963
|
+
};
|
|
964
|
+
return { action: action.id };
|
|
965
|
+
}
|
|
966
|
+
/** The current action line for a bare marker; null when the session has no action in progress. */
|
|
967
|
+
function currentActionLine(context) {
|
|
968
|
+
const ids = currentIds(context.session);
|
|
969
|
+
return ids ? headerLineFor(ids, context) : null;
|
|
970
|
+
}
|
|
971
|
+
/** The header line the model writes at the end of a turn, e.g. `▸ m001/t001 · a001`; null without an action in progress. */
|
|
972
|
+
function headerLine(session) {
|
|
973
|
+
const ids = currentIds(session);
|
|
974
|
+
if (!ids) return null;
|
|
975
|
+
const where = ids.mainline ? ids.task ? `${ids.mainline}/${ids.task}` : ids.mainline : null;
|
|
976
|
+
return `▸ ${where ? `${where} · ` : ""}${ids.action}`;
|
|
977
|
+
}
|
|
978
|
+
/** The ids in a header line the model wrote, or null for a bare marker or a line that does not parse. */
|
|
979
|
+
const WRITTEN = new RegExp(`^\\s*▸\\s+(?:(m\\d{3})(?:\\/(t\\d{3}))?\\s*·\\s*)?(a\\d{3})\\s*$`);
|
|
980
|
+
function parseHeader(line) {
|
|
981
|
+
const m = WRITTEN.exec(line);
|
|
982
|
+
return m ? {
|
|
983
|
+
mainline: m[1],
|
|
984
|
+
task: m[2],
|
|
985
|
+
action: m[3]
|
|
986
|
+
} : null;
|
|
964
987
|
}
|
|
965
988
|
/** A line that is the header: the marker alone, or the marker followed by the ids the model copied. */
|
|
966
989
|
const HEADER = new RegExp(`^[ \\t]*▸(?:[ \\t].*)?$`);
|
|
@@ -973,14 +996,15 @@ const lastHeader = (text) => {
|
|
|
973
996
|
const endsWithMarker = (text) => lastHeader(text) !== null;
|
|
974
997
|
/**
|
|
975
998
|
* The text to display for a piece of a reply: mentions linked, and a trailing header line replaced with the
|
|
976
|
-
*
|
|
977
|
-
*
|
|
999
|
+
* mainline › task › action it names, titles and links included. A bare marker names the session's action in
|
|
1000
|
+
* progress and is dropped without one. Returns the input unchanged when there is nothing to do.
|
|
978
1001
|
*/
|
|
979
1002
|
function decorateReply(text, context) {
|
|
980
1003
|
const header = lastHeader(text);
|
|
981
1004
|
if (header === null) return linkMentions(text, context);
|
|
982
1005
|
const body = linkMentions(text.trimEnd().slice(0, -header.length).trimEnd(), context);
|
|
983
|
-
const
|
|
1006
|
+
const written = parseHeader(header);
|
|
1007
|
+
const line = written ? headerLineFor(written, context) : header.trim() === "▸" ? currentActionLine(context) : linkMentions(header.trim(), context);
|
|
984
1008
|
return line ? `${body}\n\n${line}\n` : `${body}\n`;
|
|
985
1009
|
}
|
|
986
1010
|
/** Cheap test for text that decorateReply could change, so a display hook can skip loading the hub. */
|
|
@@ -1818,6 +1842,20 @@ async function syncHost(root, hub, port, options = {}) {
|
|
|
1818
1842
|
};
|
|
1819
1843
|
return { url: `https://${hostName(hub)}` };
|
|
1820
1844
|
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Remove {hub}.mainflow.localhost when the hub rooted at `root` is gone, so a dangling link does not stop Caddy
|
|
1847
|
+
* from loading every other site. Only a link that points into `root` is removed; the reload failure, if any,
|
|
1848
|
+
* is returned for the caller to log.
|
|
1849
|
+
*/
|
|
1850
|
+
function removeHostLink(root, hub, reload = reloadCaddy) {
|
|
1851
|
+
const sites = caddySites();
|
|
1852
|
+
if (!sites) return null;
|
|
1853
|
+
const link = join(sites, `mainflow-${hub}.caddy`);
|
|
1854
|
+
if (!lstatSync(link, { throwIfNoEntry: false })?.isSymbolicLink()) return null;
|
|
1855
|
+
if (!resolve(dirname(link), readlinkSync(link)).startsWith(resolve(root) + "/")) return null;
|
|
1856
|
+
rmSync(link);
|
|
1857
|
+
return reload(sites);
|
|
1858
|
+
}
|
|
1821
1859
|
/** The macOS app's bundle identifier, from apps/macos/scripts/package.sh. */
|
|
1822
1860
|
const APP_BUNDLE_ID = "space.carlos.mainflow";
|
|
1823
1861
|
/** Close the tabs Safari and Chrome opened on this hub's /open route; each browser only when it is running. */
|
|
@@ -1850,4 +1888,4 @@ function openInApp(hub, target, closeAfterMs = 150) {
|
|
|
1850
1888
|
return true;
|
|
1851
1889
|
}
|
|
1852
1890
|
//#endregion
|
|
1853
|
-
export {
|
|
1891
|
+
export { linksInto as $, setSessionInfo as A, headerLine as B, supersedeDecision as C, loadSessions as D, ensureSession as E, updateAction as F, createIssue as G, appendOps as H, sessionBriefing as I, setFindingArchived as J, planIssue as K, turnReminder as L, settleSession as M, startSession as N, readSession as O, transcriptPath as P, loadHub as Q, decorateReply as R, setMainlineTitle as S, addAction as T, closeIssue as U, mayDecorate as V, createFinding as W, setIssueArchived as X, setFindingStatus as Y, HubError as Z, initHub as _, openInApp as a, DATA_DIR as at, setCriterion as b, syncHost as c, worktreesOf as ct, addHub as d, refIndex as et, addRepo as f, dropChecklistItem as g, createTask as h, hostName as i, WriteError as it, setSessionStatus as j, setSessionArchived as k, migrateHub as l, createMainline as m, DEFAULT_UI_PORT as n, TASK_FILE as nt, removeHostLink as o, currentBranch as ot, createDecision as p, reopenIssue as q, caddySites as r, ConflictError as rt, siteBlock as s, findHubRoot as st, APP_BUNDLE_ID as t, MAINLINE_FILE as tt, acceptDecision as u, setArchived as v, updateTask as w, setMainlineStatus as x, setChecklistItem as y, endsWithMarker as z };
|
package/dist/host.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export type HostResult = {
|
|
|
21
21
|
* different hub of the same name stops here: overwriting it would silently take that hub's host name.
|
|
22
22
|
*/
|
|
23
23
|
export declare function syncHost(root: string, hub: string, port: number, options?: HostOptions): Promise<HostResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Remove {hub}.mainflow.localhost when the hub rooted at `root` is gone, so a dangling link does not stop Caddy
|
|
26
|
+
* from loading every other site. Only a link that points into `root` is removed; the reload failure, if any,
|
|
27
|
+
* is returned for the caller to log.
|
|
28
|
+
*/
|
|
29
|
+
export declare function removeHostLink(root: string, hub: string, reload?: (sites: string) => string | null): string | null;
|
|
24
30
|
/** The macOS app's bundle identifier, from apps/macos/scripts/package.sh. */
|
|
25
31
|
export declare const APP_BUNDLE_ID = "space.carlos.mainflow";
|
|
26
32
|
/**
|
package/dist/host.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as openInApp, i as hostName, n as DEFAULT_UI_PORT, o as
|
|
2
|
-
export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, siteBlock, syncHost };
|
|
1
|
+
import { a as openInApp, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, r as caddySites, s as siteBlock, t as APP_BUNDLE_ID } from "./host-C3MyDGB_.js";
|
|
2
|
+
export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, removeHostLink, siteBlock, syncHost };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { at as DATA_DIR, c as syncHost, i as hostName, it as WriteError, n as DEFAULT_UI_PORT, o as removeHostLink } from "./host-C3MyDGB_.js";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
5
5
|
import { createServer } from "node:net";
|
|
@@ -111,7 +111,7 @@ async function choosePort(hub, candidates, probe = networkProbe) {
|
|
|
111
111
|
*/
|
|
112
112
|
function startUiInBackground(root) {
|
|
113
113
|
const cli = process.argv[1];
|
|
114
|
-
if (!cli) return;
|
|
114
|
+
if (!cli || process.env.MAINFLOW_NO_BACKGROUND_UI === "1") return;
|
|
115
115
|
spawn(process.execPath, [
|
|
116
116
|
cli,
|
|
117
117
|
"ui",
|
|
@@ -122,6 +122,20 @@ function startUiInBackground(root) {
|
|
|
122
122
|
stdio: "ignore"
|
|
123
123
|
}).on("error", () => {}).unref();
|
|
124
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Call `onGone` once the hub's config.yaml disappears, checking every `everyMs`. A server started for a hub in a
|
|
127
|
+
* directory that is later deleted, such as a test's, then stops instead of holding its port forever. The timer
|
|
128
|
+
* does not keep the process alive by itself.
|
|
129
|
+
*/
|
|
130
|
+
function watchHubGone(root, onGone, everyMs = 3e4) {
|
|
131
|
+
const timer = setInterval(() => {
|
|
132
|
+
if (existsSync(join(root, ".mainflow", "config.yaml"))) return;
|
|
133
|
+
clearInterval(timer);
|
|
134
|
+
onGone();
|
|
135
|
+
}, everyMs);
|
|
136
|
+
timer.unref();
|
|
137
|
+
return timer;
|
|
138
|
+
}
|
|
125
139
|
/** Open a URL in the default browser without waiting for it. */
|
|
126
140
|
function openUrl(url) {
|
|
127
141
|
const [command, ...args] = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? [
|
|
@@ -162,13 +176,20 @@ async function launchUi(root, hub, configured, options) {
|
|
|
162
176
|
}
|
|
163
177
|
let server;
|
|
164
178
|
if (!reuse) {
|
|
165
|
-
const { serveUi } = await import("./ui-
|
|
179
|
+
const { serveUi } = await import("./ui-BTLe6I--.js");
|
|
166
180
|
server = await serveUi(root, choice.port);
|
|
167
181
|
writeUiRecord(root, {
|
|
168
182
|
port: choice.port,
|
|
169
183
|
pid: process.pid,
|
|
170
184
|
version: CLI_VERSION
|
|
171
185
|
});
|
|
186
|
+
const started = server;
|
|
187
|
+
watchHubGone(root, () => {
|
|
188
|
+
const failure = removeHostLink(root, hub);
|
|
189
|
+
if (failure) log(`! caddy reload failed after removing ${hostName(hub)}: ${failure}`);
|
|
190
|
+
started.close();
|
|
191
|
+
process.exit(0);
|
|
192
|
+
});
|
|
172
193
|
}
|
|
173
194
|
const host = await syncHost(root, hub, choice.port, options.host);
|
|
174
195
|
if (host.url === null) log(`! host name not available: ${host.reason}`);
|
|
@@ -181,4 +202,4 @@ async function launchUi(root, hub, configured, options) {
|
|
|
181
202
|
};
|
|
182
203
|
}
|
|
183
204
|
//#endregion
|
|
184
|
-
export { portCandidates as a,
|
|
205
|
+
export { portCandidates as a, watchHubGone as c, networkProbe as i, CLI_VERSION as l, choosePort as n, readUiRecord as o, launchUi as r, startUiInBackground as s, PORT_SPAN as t };
|
package/dist/launch.d.ts
CHANGED
|
@@ -51,6 +51,12 @@ export declare function choosePort(hub: string, candidates: number[], probe?: Po
|
|
|
51
51
|
* pinned version serves its interface.
|
|
52
52
|
*/
|
|
53
53
|
export declare function startUiInBackground(root: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Call `onGone` once the hub's config.yaml disappears, checking every `everyMs`. A server started for a hub in a
|
|
56
|
+
* directory that is later deleted, such as a test's, then stops instead of holding its port forever. The timer
|
|
57
|
+
* does not keep the process alive by itself.
|
|
58
|
+
*/
|
|
59
|
+
export declare function watchHubGone(root: string, onGone: () => void, everyMs?: number): NodeJS.Timeout;
|
|
54
60
|
export interface LaunchOptions {
|
|
55
61
|
port?: string;
|
|
56
62
|
open: boolean;
|
package/dist/launch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as portCandidates, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-
|
|
2
|
-
export { PORT_SPAN, choosePort, launchUi, networkProbe, portCandidates, readUiRecord, startUiInBackground };
|
|
1
|
+
import { a as portCandidates, c as watchHubGone, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-T0sXbTgT.js";
|
|
2
|
+
export { PORT_SPAN, choosePort, launchUi, networkProbe, portCandidates, readUiRecord, startUiInBackground, watchHubGone };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as linksInto, D as loadSessions, J as setFindingArchived, O as readSession, P as transcriptPath, Q as loadHub, S as setMainlineTitle, X as setIssueArchived, a as openInApp, at as DATA_DIR, b as setCriterion, et as refIndex, it as WriteError, k as setSessionArchived, rt as ConflictError, v as setArchived, w as updateTask, x as setMainlineStatus, y as setChecklistItem } from "./host-C3MyDGB_.js";
|
|
2
2
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
3
|
-
import {
|
|
3
|
+
import { l as CLI_VERSION } from "./launch-T0sXbTgT.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { closeSync, existsSync, openSync, readFileSync, readSync, statSync, watch } from "node:fs";
|
|
6
6
|
import { basename, dirname, extname, join, normalize, relative, sep } from "node:path";
|