@qlucent/fishi 0.4.0 → 0.6.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.
- package/dist/index.js +254 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk8 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/commands/init.ts
|
|
8
8
|
import chalk from "chalk";
|
|
@@ -1649,11 +1649,260 @@ async function validateCommand() {
|
|
|
1649
1649
|
}
|
|
1650
1650
|
}
|
|
1651
1651
|
|
|
1652
|
+
// src/commands/monitor.ts
|
|
1653
|
+
import chalk6 from "chalk";
|
|
1654
|
+
import fs8 from "fs";
|
|
1655
|
+
import path8 from "path";
|
|
1656
|
+
import { parse as parseYaml4 } from "yaml";
|
|
1657
|
+
import { readMonitorState, getAgentSummary } from "@qlucent/fishi-core";
|
|
1658
|
+
var PHASES = ["init", "discovery", "planning", "development", "testing", "review", "deployed"];
|
|
1659
|
+
function formatNumber(n) {
|
|
1660
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1661
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
1662
|
+
return String(n);
|
|
1663
|
+
}
|
|
1664
|
+
function renderPhaseBar(currentPhase) {
|
|
1665
|
+
const idx = PHASES.indexOf(currentPhase);
|
|
1666
|
+
const parts = PHASES.map((p, i) => {
|
|
1667
|
+
if (i < idx) return chalk6.green(`[${p}]`);
|
|
1668
|
+
if (i === idx) return chalk6.cyan.bold(`[${p}]`);
|
|
1669
|
+
return chalk6.gray(`[${p}]`);
|
|
1670
|
+
});
|
|
1671
|
+
return " " + parts.join(chalk6.gray(" \u2192 "));
|
|
1672
|
+
}
|
|
1673
|
+
function renderDivider(label) {
|
|
1674
|
+
const line = "\u2500".repeat(60);
|
|
1675
|
+
return chalk6.gray(`
|
|
1676
|
+
${line}
|
|
1677
|
+
`) + chalk6.bold(label) + "\n";
|
|
1678
|
+
}
|
|
1679
|
+
function renderMonitor(projectDir) {
|
|
1680
|
+
const state = readMonitorState(projectDir);
|
|
1681
|
+
const agentSummary = getAgentSummary(projectDir);
|
|
1682
|
+
let currentPhase = "init";
|
|
1683
|
+
const projectYamlPath = path8.join(projectDir, ".fishi", "state", "project.yaml");
|
|
1684
|
+
if (fs8.existsSync(projectYamlPath)) {
|
|
1685
|
+
try {
|
|
1686
|
+
const projectState = parseYaml4(fs8.readFileSync(projectYamlPath, "utf-8"));
|
|
1687
|
+
currentPhase = projectState?.current_phase || "init";
|
|
1688
|
+
} catch {
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
let gates = [];
|
|
1692
|
+
const gatesYamlPath = path8.join(projectDir, ".fishi", "state", "gates.yaml");
|
|
1693
|
+
if (fs8.existsSync(gatesYamlPath)) {
|
|
1694
|
+
try {
|
|
1695
|
+
const gatesData = parseYaml4(fs8.readFileSync(gatesYamlPath, "utf-8"));
|
|
1696
|
+
gates = gatesData?.gates || [];
|
|
1697
|
+
} catch {
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
const { summary, dynamicAgents, events, lastUpdated } = state;
|
|
1701
|
+
console.log("");
|
|
1702
|
+
console.log(chalk6.cyan.bold(" FISHI Agent Monitor"));
|
|
1703
|
+
console.log(chalk6.gray(` Last updated: ${new Date(lastUpdated).toLocaleTimeString()}`));
|
|
1704
|
+
console.log(renderDivider("Phase Progress"));
|
|
1705
|
+
console.log(renderPhaseBar(currentPhase));
|
|
1706
|
+
console.log("");
|
|
1707
|
+
console.log(renderDivider("Summary"));
|
|
1708
|
+
console.log(
|
|
1709
|
+
` ${chalk6.bold("Completions:")} ${chalk6.cyan(summary.totalAgentCompletions)} ${chalk6.bold("Files Changed:")} ${chalk6.cyan(summary.totalFilesChanged)} ${chalk6.bold("Tokens:")} ${chalk6.cyan(formatNumber(summary.totalTokens))} ${chalk6.bold("Dynamic Agents:")} ${chalk6.cyan(summary.dynamicAgentsCreated)}`
|
|
1710
|
+
);
|
|
1711
|
+
console.log(renderDivider("Tokens by Model"));
|
|
1712
|
+
const modelEntries = Object.entries(summary.tokensByModel);
|
|
1713
|
+
if (modelEntries.length === 0) {
|
|
1714
|
+
console.log(chalk6.gray(" (none yet)"));
|
|
1715
|
+
} else {
|
|
1716
|
+
for (const [model, tokens] of modelEntries.sort((a, b) => b[1] - a[1])) {
|
|
1717
|
+
console.log(` ${chalk6.yellow(model.padEnd(30))} ${chalk6.cyan(formatNumber(tokens))}`);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
console.log(renderDivider("Tools Used"));
|
|
1721
|
+
const toolEntries = Object.entries(summary.toolsUsed);
|
|
1722
|
+
if (toolEntries.length === 0) {
|
|
1723
|
+
console.log(chalk6.gray(" (none yet)"));
|
|
1724
|
+
} else {
|
|
1725
|
+
for (const [tool, count] of toolEntries.sort((a, b) => b[1] - a[1]).slice(0, 10)) {
|
|
1726
|
+
console.log(` ${chalk6.yellow(tool.padEnd(30))} ${chalk6.cyan(count)}`);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
console.log(renderDivider("Agent Activity"));
|
|
1730
|
+
const agentEntries = Object.entries(agentSummary);
|
|
1731
|
+
if (agentEntries.length === 0) {
|
|
1732
|
+
console.log(chalk6.gray(" (no agent events yet)"));
|
|
1733
|
+
} else {
|
|
1734
|
+
for (const [agent, stats] of agentEntries.sort((a, b) => b[1].completions - a[1].completions)) {
|
|
1735
|
+
const failColor = stats.failures > 0 ? chalk6.red : chalk6.gray;
|
|
1736
|
+
console.log(
|
|
1737
|
+
` ${chalk6.magenta(agent.padEnd(28))} completions: ${chalk6.cyan(stats.completions)} failures: ${failColor(stats.failures)} files: ${chalk6.cyan(stats.filesChanged)}`
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
if (dynamicAgents.length > 0) {
|
|
1742
|
+
console.log(renderDivider("Dynamic Agents"));
|
|
1743
|
+
for (const da of dynamicAgents) {
|
|
1744
|
+
console.log(` ${chalk6.magenta(da.name)} ${chalk6.gray("\u2190")} ${chalk6.gray(da.coordinator)}`);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
if (gates.length > 0) {
|
|
1748
|
+
console.log(renderDivider("Gates"));
|
|
1749
|
+
for (const gate of gates) {
|
|
1750
|
+
const statusColor = gate.status === "passed" ? chalk6.green : gate.status === "failed" ? chalk6.red : chalk6.yellow;
|
|
1751
|
+
console.log(` ${statusColor("\u25CF")} ${gate.name.padEnd(30)} ${statusColor(gate.status)}`);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
console.log(renderDivider("Recent Events (last 10)"));
|
|
1755
|
+
const recentEvents = events.slice(-10).reverse();
|
|
1756
|
+
if (recentEvents.length === 0) {
|
|
1757
|
+
console.log(chalk6.gray(" (no events yet)"));
|
|
1758
|
+
} else {
|
|
1759
|
+
for (const ev of recentEvents) {
|
|
1760
|
+
const ts = new Date(ev.timestamp).toLocaleTimeString();
|
|
1761
|
+
console.log(
|
|
1762
|
+
` ${chalk6.gray(ts)} ${chalk6.cyan(ev.type.padEnd(20))} ${chalk6.magenta(ev.agent)}`
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
console.log("");
|
|
1767
|
+
}
|
|
1768
|
+
async function monitorCommand(options) {
|
|
1769
|
+
const projectDir = process.cwd();
|
|
1770
|
+
const fishiDir = path8.join(projectDir, ".fishi");
|
|
1771
|
+
if (!fs8.existsSync(fishiDir)) {
|
|
1772
|
+
console.log(chalk6.yellow("\n FISHI is not initialized in this directory."));
|
|
1773
|
+
console.log(chalk6.gray(" Run `fishi init` to get started.\n"));
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
if (!options.watch) {
|
|
1777
|
+
renderMonitor(projectDir);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
const monitorJsonPath = path8.join(fishiDir, "state", "monitor.json");
|
|
1781
|
+
let lastMtime = 0;
|
|
1782
|
+
const render = () => {
|
|
1783
|
+
process.stdout.write("\x1B[2J\x1B[0;0H");
|
|
1784
|
+
renderMonitor(projectDir);
|
|
1785
|
+
console.log(chalk6.gray(" [watch mode \u2014 polling every 1s, Ctrl+C to exit]"));
|
|
1786
|
+
};
|
|
1787
|
+
render();
|
|
1788
|
+
const interval = setInterval(() => {
|
|
1789
|
+
try {
|
|
1790
|
+
if (fs8.existsSync(monitorJsonPath)) {
|
|
1791
|
+
const mtime = fs8.statSync(monitorJsonPath).mtimeMs;
|
|
1792
|
+
if (mtime !== lastMtime) {
|
|
1793
|
+
lastMtime = mtime;
|
|
1794
|
+
render();
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
} catch {
|
|
1798
|
+
}
|
|
1799
|
+
}, 1e3);
|
|
1800
|
+
process.on("SIGINT", () => {
|
|
1801
|
+
clearInterval(interval);
|
|
1802
|
+
console.log(chalk6.gray("\n Monitor exited.\n"));
|
|
1803
|
+
process.exit(0);
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// src/commands/dashboard.ts
|
|
1808
|
+
import http from "http";
|
|
1809
|
+
import fs9 from "fs";
|
|
1810
|
+
import path9 from "path";
|
|
1811
|
+
import { parse as parseYaml5 } from "yaml";
|
|
1812
|
+
import chalk7 from "chalk";
|
|
1813
|
+
import { readMonitorState as readMonitorState2, getAgentSummary as getAgentSummary2, getDashboardHtml } from "@qlucent/fishi-core";
|
|
1814
|
+
var DEFAULT_PORT = 4269;
|
|
1815
|
+
async function dashboardCommand(options) {
|
|
1816
|
+
const projectDir = process.cwd();
|
|
1817
|
+
const fishiDir = path9.join(projectDir, ".fishi");
|
|
1818
|
+
if (!fs9.existsSync(fishiDir)) {
|
|
1819
|
+
console.log(chalk7.yellow("\n FISHI is not initialized in this directory."));
|
|
1820
|
+
console.log(chalk7.gray(" Run `fishi init` to get started.\n"));
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
const port = Number(options.port || DEFAULT_PORT);
|
|
1824
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
1825
|
+
console.error(chalk7.red(` Invalid port: ${options.port}`));
|
|
1826
|
+
process.exit(1);
|
|
1827
|
+
}
|
|
1828
|
+
const html = getDashboardHtml();
|
|
1829
|
+
const server = http.createServer((req, res) => {
|
|
1830
|
+
const url = req.url?.split("?")[0] ?? "/";
|
|
1831
|
+
if (url === "/") {
|
|
1832
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1833
|
+
res.end(html);
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
if (url === "/api/state") {
|
|
1837
|
+
try {
|
|
1838
|
+
const monitorState = readMonitorState2(projectDir);
|
|
1839
|
+
const agentSummary = getAgentSummary2(projectDir);
|
|
1840
|
+
let phase = "init";
|
|
1841
|
+
const projectYamlPath = path9.join(fishiDir, "state", "project.yaml");
|
|
1842
|
+
if (fs9.existsSync(projectYamlPath)) {
|
|
1843
|
+
try {
|
|
1844
|
+
const projectState = parseYaml5(fs9.readFileSync(projectYamlPath, "utf-8"));
|
|
1845
|
+
phase = projectState?.current_phase || "init";
|
|
1846
|
+
} catch {
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
let gates = [];
|
|
1850
|
+
const gatesYamlPath = path9.join(fishiDir, "state", "gates.yaml");
|
|
1851
|
+
if (fs9.existsSync(gatesYamlPath)) {
|
|
1852
|
+
try {
|
|
1853
|
+
const gatesData = parseYaml5(fs9.readFileSync(gatesYamlPath, "utf-8"));
|
|
1854
|
+
gates = gatesData?.gates || [];
|
|
1855
|
+
} catch {
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
const payload = {
|
|
1859
|
+
summary: monitorState.summary,
|
|
1860
|
+
events: monitorState.events,
|
|
1861
|
+
dynamicAgents: monitorState.dynamicAgents,
|
|
1862
|
+
lastUpdated: monitorState.lastUpdated,
|
|
1863
|
+
agentSummary,
|
|
1864
|
+
phase,
|
|
1865
|
+
gates
|
|
1866
|
+
};
|
|
1867
|
+
res.writeHead(200, {
|
|
1868
|
+
"Content-Type": "application/json",
|
|
1869
|
+
"Access-Control-Allow-Origin": "*"
|
|
1870
|
+
});
|
|
1871
|
+
res.end(JSON.stringify(payload));
|
|
1872
|
+
} catch (err) {
|
|
1873
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1874
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
1875
|
+
}
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1879
|
+
res.end("Not found");
|
|
1880
|
+
});
|
|
1881
|
+
server.listen(port, () => {
|
|
1882
|
+
console.log("");
|
|
1883
|
+
console.log(chalk7.cyan.bold(" FISHI Agent Dashboard"));
|
|
1884
|
+
console.log("");
|
|
1885
|
+
console.log(` ${chalk7.bold("URL:")} ${chalk7.cyan(`http://localhost:${port}`)}`);
|
|
1886
|
+
console.log(` ${chalk7.bold("API:")} ${chalk7.gray(`http://localhost:${port}/api/state`)}`);
|
|
1887
|
+
console.log("");
|
|
1888
|
+
console.log(chalk7.gray(" Press Ctrl+C to stop.\n"));
|
|
1889
|
+
});
|
|
1890
|
+
process.on("SIGINT", () => {
|
|
1891
|
+
server.close(() => {
|
|
1892
|
+
console.log(chalk7.gray("\n Dashboard stopped.\n"));
|
|
1893
|
+
process.exit(0);
|
|
1894
|
+
});
|
|
1895
|
+
});
|
|
1896
|
+
await new Promise((resolve) => {
|
|
1897
|
+
server.on("close", resolve);
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1652
1901
|
// src/index.ts
|
|
1653
1902
|
var program = new Command();
|
|
1654
1903
|
program.name("fishi").description(
|
|
1655
|
-
|
|
1656
|
-
).version("0.
|
|
1904
|
+
chalk8.cyan("\u{1F41F} FISHI") + " \u2014 Your AI Dev Team That Actually Ships\n Autonomous agent framework for Claude Code"
|
|
1905
|
+
).version("0.6.0");
|
|
1657
1906
|
program.command("init").description("Initialize FISHI in the current directory").argument("[description]", "Project description (skip wizard with zero-config)").option("-l, --language <lang>", "Primary language (e.g., typescript, python)").option("-f, --framework <framework>", "Framework (e.g., nextjs, express, django)").option(
|
|
1658
1907
|
"-c, --cost-mode <mode>",
|
|
1659
1908
|
"Cost mode: performance | balanced | economy",
|
|
@@ -1663,4 +1912,6 @@ program.command("status").description("Show project status, active agents, and T
|
|
|
1663
1912
|
program.command("mcp").description("Manage MCP server integrations").argument("<action>", "Action: add | list | remove").argument("[name]", "MCP server name").action(mcpCommand);
|
|
1664
1913
|
program.command("reset").description("Rollback to a previous checkpoint").argument("[checkpoint]", "Checkpoint ID (defaults to latest)").action(resetCommand);
|
|
1665
1914
|
program.command("validate").description("Validate scaffold integrity \u2014 checks files, frontmatter, cross-references, pipeline, and permissions").action(validateCommand);
|
|
1915
|
+
program.command("monitor").description("Agent observability \u2014 TUI dashboard showing agent activity, tokens, gates").option("-w, --watch", "Watch mode \u2014 auto-refresh on changes").action(monitorCommand);
|
|
1916
|
+
program.command("dashboard").description("Agent observability \u2014 web dashboard at http://localhost:4269").option("-p, --port <port>", "Port number", "4269").action(dashboardCommand);
|
|
1666
1917
|
program.parse();
|