@qlucent/fishi 0.6.0 → 0.8.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.
Files changed (2) hide show
  1. package/dist/index.js +270 -4
  2. 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 chalk8 from "chalk";
5
+ import chalk11 from "chalk";
6
6
 
7
7
  // src/commands/init.ts
8
8
  import chalk from "chalk";
@@ -10,7 +10,7 @@ import ora from "ora";
10
10
  import inquirer from "inquirer";
11
11
  import path3 from "path";
12
12
  import fs3 from "fs";
13
- import { detectConflicts, createBackup } from "@qlucent/fishi-core";
13
+ import { detectConflicts, createBackup, detectDocker } from "@qlucent/fishi-core";
14
14
 
15
15
  // src/analyzers/detector.ts
16
16
  import fs from "fs";
@@ -1005,6 +1005,23 @@ async function initCommand(description, options) {
1005
1005
  } else {
1006
1006
  initOptions = await runWizard(options);
1007
1007
  }
1008
+ const dockerAvailable = detectDocker();
1009
+ let sandboxMode = "process";
1010
+ if (options.interactive !== false) {
1011
+ if (dockerAvailable) {
1012
+ const { useSandbox } = await inquirer.prompt([{
1013
+ type: "confirm",
1014
+ name: "useSandbox",
1015
+ message: "Docker detected. Use Docker sandbox for agent isolation? (Recommended)",
1016
+ default: true
1017
+ }]);
1018
+ sandboxMode = useSandbox ? "docker" : "process";
1019
+ } else {
1020
+ console.log(chalk.yellow(" Docker not found. Using process-level sandbox (limited isolation)."));
1021
+ console.log(chalk.gray(" Install Docker for full agent isolation: https://docs.docker.com/get-docker/"));
1022
+ console.log("");
1023
+ }
1024
+ }
1008
1025
  let brownfieldAnalysis = null;
1009
1026
  if (detection.type === "brownfield" || detection.type === "hybrid") {
1010
1027
  console.log("");
@@ -1148,6 +1165,21 @@ async function initCommand(description, options) {
1148
1165
  const report = generateBrownfieldReport(brownfieldAnalysis);
1149
1166
  fs3.writeFileSync(reportPath, report, "utf-8");
1150
1167
  }
1168
+ const sandboxYaml = `
1169
+ sandbox:
1170
+ mode: ${sandboxMode}
1171
+ docker_available: ${dockerAvailable}
1172
+ `;
1173
+ const fishiYamlPath = path3.join(targetDir, ".fishi", "fishi.yaml");
1174
+ if (fs3.existsSync(fishiYamlPath)) {
1175
+ fs3.appendFileSync(fishiYamlPath, sandboxYaml, "utf-8");
1176
+ }
1177
+ const { getSandboxPolicyTemplate, getDockerfileTemplate } = await import("@qlucent/fishi-core");
1178
+ fs3.writeFileSync(path3.join(targetDir, ".fishi", "sandbox-policy.yaml"), getSandboxPolicyTemplate(), "utf-8");
1179
+ if (sandboxMode === "docker") {
1180
+ fs3.mkdirSync(path3.join(targetDir, ".fishi", "docker"), { recursive: true });
1181
+ fs3.writeFileSync(path3.join(targetDir, ".fishi", "docker", "Dockerfile"), getDockerfileTemplate(), "utf-8");
1182
+ }
1151
1183
  scaffoldSpinner.succeed("FISHI framework scaffolded successfully!");
1152
1184
  console.log("");
1153
1185
  console.log(chalk.bold(" Created:"));
@@ -1158,6 +1190,7 @@ async function initCommand(description, options) {
1158
1190
  console.log(chalk.gray(` \u{1F4C4} .claude/CLAUDE.md \u2014 Project instructions`));
1159
1191
  console.log(chalk.gray(` \u{1F4C4} .claude/settings.json \u2014 Hooks & permissions`));
1160
1192
  console.log(chalk.gray(` \u{1F4C4} .mcp.json \u2014 MCP server config`));
1193
+ console.log(chalk.gray(` \u{1F512} Sandbox: ${sandboxMode} mode${sandboxMode === "docker" ? " (full isolation)" : " (limited isolation)"}`));
1161
1194
  if (brownfieldAnalysis) {
1162
1195
  console.log(chalk.gray(` \u{1F4C4} .fishi/memory/brownfield-analysis.md \u2014 Codebase analysis report`));
1163
1196
  console.log("");
@@ -1898,11 +1931,241 @@ async function dashboardCommand(options) {
1898
1931
  });
1899
1932
  }
1900
1933
 
1934
+ // src/commands/sandbox.ts
1935
+ import chalk8 from "chalk";
1936
+ import fs10 from "fs";
1937
+ import path10 from "path";
1938
+ import { detectDocker as detectDocker2, readSandboxConfig, readSandboxPolicy } from "@qlucent/fishi-core";
1939
+ async function sandboxCommand(action) {
1940
+ const targetDir = process.cwd();
1941
+ if (!fs10.existsSync(path10.join(targetDir, ".fishi"))) {
1942
+ console.log(chalk8.yellow(" No FISHI project found. Run `fishi init` first."));
1943
+ return;
1944
+ }
1945
+ if (action === "status") {
1946
+ const config = readSandboxConfig(targetDir);
1947
+ const dockerNow = detectDocker2();
1948
+ console.log("");
1949
+ console.log(chalk8.cyan.bold(" FISHI Sandbox Status"));
1950
+ console.log("");
1951
+ console.log(chalk8.white(" Mode: ") + (config.mode === "docker" ? chalk8.green("Docker") : chalk8.yellow("Process")));
1952
+ console.log(chalk8.white(" Docker available: ") + (dockerNow ? chalk8.green("yes") : chalk8.red("no")));
1953
+ if (config.mode === "docker" && !dockerNow) {
1954
+ console.log(chalk8.red(" Warning: Docker mode configured but Docker not available!"));
1955
+ }
1956
+ console.log("");
1957
+ const policy = readSandboxPolicy(targetDir);
1958
+ console.log(chalk8.white.bold(" Policy"));
1959
+ console.log(chalk8.gray(` Timeout: ${policy.timeout}s`));
1960
+ console.log(chalk8.gray(` Memory limit: ${policy.memory}`));
1961
+ console.log(chalk8.gray(` CPU limit: ${policy.cpus}`));
1962
+ console.log(chalk8.gray(` Network allow: ${policy.networkAllow.join(", ")}`));
1963
+ console.log(chalk8.gray(` Env passthrough: ${policy.envPassthrough.length > 0 ? policy.envPassthrough.join(", ") : "(none)"}`));
1964
+ console.log("");
1965
+ } else if (action === "policy") {
1966
+ const policy = readSandboxPolicy(targetDir);
1967
+ console.log(JSON.stringify(policy, null, 2));
1968
+ } else {
1969
+ console.log(chalk8.yellow(` Unknown action: ${action}. Use: status, policy`));
1970
+ }
1971
+ }
1972
+
1973
+ // src/commands/quickstart.ts
1974
+ import chalk9 from "chalk";
1975
+ import ora2 from "ora";
1976
+ import path11 from "path";
1977
+ import fs11 from "fs";
1978
+ import { detectDevServer, startDevServer, getVibeModeConfig } from "@qlucent/fishi-core";
1979
+ import { detectConflicts as detectConflicts2, createBackup as createBackup2 } from "@qlucent/fishi-core";
1980
+ import { detectDocker as detectDocker3 } from "@qlucent/fishi-core";
1981
+ async function quickstartCommand(description, options) {
1982
+ const targetDir = process.cwd();
1983
+ const projectName = path11.basename(targetDir);
1984
+ console.log("");
1985
+ console.log(chalk9.cyan.bold(" FISHI \u2014 Vibe Mode"));
1986
+ console.log(chalk9.gray(" Skip the ceremony, start shipping"));
1987
+ console.log("");
1988
+ if (fs11.existsSync(path11.join(targetDir, ".fishi"))) {
1989
+ console.log(chalk9.yellow(" FISHI already initialized. Starting dev server only..."));
1990
+ const serverConfig2 = detectDevServer(targetDir, options.devCmd);
1991
+ if (serverConfig2.detected) {
1992
+ console.log(chalk9.green(` Starting ${serverConfig2.framework} dev server on :${serverConfig2.port}...`));
1993
+ const child = startDevServer(targetDir, serverConfig2);
1994
+ child.stdout?.on("data", (d) => process.stdout.write(d));
1995
+ child.stderr?.on("data", (d) => process.stderr.write(d));
1996
+ child.on("close", (code) => process.exit(code || 0));
1997
+ return;
1998
+ }
1999
+ console.log(chalk9.yellow(" Could not detect dev server. Use --dev-cmd to specify."));
2000
+ return;
2001
+ }
2002
+ const spinner = ora2("Analyzing project...").start();
2003
+ const detection = await detectProjectType(targetDir);
2004
+ spinner.succeed(`Project type: ${chalk9.bold(detection.type)}`);
2005
+ let brownfieldAnalysis = null;
2006
+ if (detection.type === "brownfield" || detection.type === "hybrid") {
2007
+ const analysisSpinner = ora2("Running brownfield analysis...").start();
2008
+ brownfieldAnalysis = await runBrownfieldAnalysis(targetDir);
2009
+ analysisSpinner.succeed("Brownfield analysis complete");
2010
+ }
2011
+ const conflictResult = detectConflicts2(targetDir);
2012
+ let resolutions;
2013
+ if (conflictResult.hasConflicts) {
2014
+ console.log(chalk9.yellow(` Auto-merging ${conflictResult.totalConflicts} conflicting files (vibe mode)...`));
2015
+ const allPaths = conflictResult.categories.flatMap((c) => c.conflicts.map((f) => f.path));
2016
+ await createBackup2(targetDir, allPaths);
2017
+ resolutions = { categories: {}, files: {} };
2018
+ for (const cat of conflictResult.categories) {
2019
+ if (cat.conflicts.length > 0) {
2020
+ const noMerge = ["agents", "skills", "commands"];
2021
+ resolutions.categories[cat.name] = noMerge.includes(cat.name) ? "skip" : "merge";
2022
+ }
2023
+ }
2024
+ }
2025
+ const initOptions = {
2026
+ description: description || `${projectName} project`,
2027
+ interactive: false,
2028
+ costMode: options.costMode || "balanced",
2029
+ language: options.language || brownfieldAnalysis?.language,
2030
+ framework: options.framework || brownfieldAnalysis?.framework
2031
+ };
2032
+ let brownfieldData = void 0;
2033
+ if (brownfieldAnalysis) {
2034
+ brownfieldData = {
2035
+ language: brownfieldAnalysis.language,
2036
+ framework: brownfieldAnalysis.framework,
2037
+ testFramework: brownfieldAnalysis.testFramework,
2038
+ packageManager: brownfieldAnalysis.packageManager,
2039
+ linter: brownfieldAnalysis.linter,
2040
+ formatter: brownfieldAnalysis.formatter,
2041
+ cssFramework: brownfieldAnalysis.cssFramework,
2042
+ orm: brownfieldAnalysis.orm,
2043
+ database: brownfieldAnalysis.database,
2044
+ authProvider: brownfieldAnalysis.authProvider,
2045
+ apiStyle: brownfieldAnalysis.apiStyle,
2046
+ monorepo: brownfieldAnalysis.monorepo,
2047
+ conventions: brownfieldAnalysis.conventions,
2048
+ codePatterns: brownfieldAnalysis.codePatterns,
2049
+ fileStats: {
2050
+ totalFiles: brownfieldAnalysis.fileStats.totalFiles,
2051
+ codeFiles: brownfieldAnalysis.fileStats.codeFiles,
2052
+ testFiles: brownfieldAnalysis.fileStats.testFiles
2053
+ }
2054
+ };
2055
+ }
2056
+ const scaffoldSpinner = ora2("Scaffolding FISHI...").start();
2057
+ try {
2058
+ const result = await scaffold(targetDir, {
2059
+ ...initOptions,
2060
+ projectName,
2061
+ projectType: detection.type,
2062
+ brownfieldAnalysis: brownfieldData,
2063
+ resolutions,
2064
+ docsReadmeExists: conflictResult.docsReadmeExists,
2065
+ rootClaudeMdExists: conflictResult.rootClaudeMdExists
2066
+ });
2067
+ scaffoldSpinner.succeed(`Scaffolded: ${result.agentCount} agents, ${result.skillCount} skills, ${result.commandCount} commands`);
2068
+ } catch (error) {
2069
+ scaffoldSpinner.fail("Scaffolding failed");
2070
+ console.error(chalk9.red(` Error: ${error instanceof Error ? error.message : error}`));
2071
+ process.exit(1);
2072
+ }
2073
+ const fishiYamlPath = path11.join(targetDir, ".fishi", "fishi.yaml");
2074
+ if (fs11.existsSync(fishiYamlPath)) {
2075
+ fs11.appendFileSync(fishiYamlPath, getVibeModeConfig(true), "utf-8");
2076
+ }
2077
+ const dockerAvailable = detectDocker3();
2078
+ const sandboxMode = dockerAvailable ? "docker" : "process";
2079
+ fs11.appendFileSync(fishiYamlPath, `
2080
+ sandbox:
2081
+ mode: ${sandboxMode}
2082
+ docker_available: ${dockerAvailable}
2083
+ `, "utf-8");
2084
+ const projectYamlPath = path11.join(targetDir, ".fishi", "state", "project.yaml");
2085
+ if (fs11.existsSync(projectYamlPath)) {
2086
+ let content = fs11.readFileSync(projectYamlPath, "utf-8");
2087
+ content = content.replace(/^phase:\s*.+$/m, "phase: development");
2088
+ fs11.writeFileSync(projectYamlPath, content, "utf-8");
2089
+ }
2090
+ const serverConfig = detectDevServer(targetDir, options.devCmd);
2091
+ console.log("");
2092
+ console.log(chalk9.cyan.bold(" Vibe Mode Active"));
2093
+ console.log(chalk9.gray(` Phase: ${chalk9.green("development")} (gates auto-skipped)`));
2094
+ console.log(chalk9.gray(` Sandbox: ${sandboxMode}`));
2095
+ if (serverConfig.detected) {
2096
+ const port = options.port ? parseInt(options.port, 10) : serverConfig.port;
2097
+ console.log(chalk9.gray(` Dev server: ${serverConfig.framework} on :${port}`));
2098
+ console.log("");
2099
+ console.log(chalk9.green.bold(` Preview: http://localhost:${port}`));
2100
+ console.log("");
2101
+ console.log(chalk9.gray(" Starting dev server..."));
2102
+ console.log("");
2103
+ const child = startDevServer(targetDir, { ...serverConfig, port });
2104
+ child.stdout?.on("data", (d) => process.stdout.write(d));
2105
+ child.stderr?.on("data", (d) => process.stderr.write(d));
2106
+ child.on("close", (code) => {
2107
+ console.log(chalk9.gray(`
2108
+ Dev server stopped (exit ${code})`));
2109
+ });
2110
+ process.on("SIGINT", () => {
2111
+ child.kill();
2112
+ console.log(chalk9.gray("\n Dev server stopped."));
2113
+ process.exit(0);
2114
+ });
2115
+ } else {
2116
+ console.log("");
2117
+ console.log(chalk9.yellow(" No dev server detected. Run your dev server manually."));
2118
+ console.log(chalk9.gray(' Or use: fishi quickstart --dev-cmd "npm run dev"'));
2119
+ console.log("");
2120
+ console.log(chalk9.cyan.bold(" Ready to ship!"));
2121
+ console.log(chalk9.gray(" Run `claude` to start working with your AI dev team."));
2122
+ console.log("");
2123
+ }
2124
+ }
2125
+
2126
+ // src/commands/preview.ts
2127
+ import chalk10 from "chalk";
2128
+ import fs12 from "fs";
2129
+ import path12 from "path";
2130
+ import { detectDevServer as detectDevServer2, startDevServer as startDevServer2 } from "@qlucent/fishi-core";
2131
+ async function previewCommand(options) {
2132
+ const targetDir = process.cwd();
2133
+ if (!fs12.existsSync(path12.join(targetDir, ".fishi"))) {
2134
+ console.log(chalk10.yellow(" No FISHI project found. Run `fishi init` or `fishi quickstart` first."));
2135
+ return;
2136
+ }
2137
+ const serverConfig = detectDevServer2(targetDir, options.devCmd);
2138
+ if (!serverConfig.detected) {
2139
+ console.log(chalk10.yellow(" Could not detect dev server."));
2140
+ console.log(chalk10.gray(' Use --dev-cmd to specify: fishi preview --dev-cmd "npm run dev"'));
2141
+ return;
2142
+ }
2143
+ const port = options.port ? parseInt(options.port, 10) : serverConfig.port;
2144
+ console.log("");
2145
+ console.log(chalk10.cyan.bold(" FISHI Live Preview"));
2146
+ console.log(chalk10.gray(` Framework: ${serverConfig.framework}`));
2147
+ console.log(chalk10.green.bold(` URL: http://localhost:${port}`));
2148
+ console.log(chalk10.gray(" Press Ctrl+C to stop"));
2149
+ console.log("");
2150
+ const child = startDevServer2(targetDir, { ...serverConfig, port });
2151
+ child.stdout?.on("data", (d) => process.stdout.write(d));
2152
+ child.stderr?.on("data", (d) => process.stderr.write(d));
2153
+ child.on("close", (code) => {
2154
+ console.log(chalk10.gray(`
2155
+ Dev server stopped (exit ${code})`));
2156
+ });
2157
+ process.on("SIGINT", () => {
2158
+ child.kill();
2159
+ console.log(chalk10.gray("\n Dev server stopped."));
2160
+ process.exit(0);
2161
+ });
2162
+ }
2163
+
1901
2164
  // src/index.ts
1902
2165
  var program = new Command();
1903
2166
  program.name("fishi").description(
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");
2167
+ chalk11.cyan("\u{1F41F} FISHI") + " \u2014 Your AI Dev Team That Actually Ships\n Autonomous agent framework for Claude Code"
2168
+ ).version("0.8.0");
1906
2169
  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(
1907
2170
  "-c, --cost-mode <mode>",
1908
2171
  "Cost mode: performance | balanced | economy",
@@ -1914,4 +2177,7 @@ program.command("reset").description("Rollback to a previous checkpoint").argume
1914
2177
  program.command("validate").description("Validate scaffold integrity \u2014 checks files, frontmatter, cross-references, pipeline, and permissions").action(validateCommand);
1915
2178
  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
2179
  program.command("dashboard").description("Agent observability \u2014 web dashboard at http://localhost:4269").option("-p, --port <port>", "Port number", "4269").action(dashboardCommand);
2180
+ program.command("sandbox").description("Sandbox status and policy management").argument("<action>", "Action: status | policy").action(sandboxCommand);
2181
+ program.command("quickstart").description("Vibe mode \u2014 skip gates, scaffold + start dev server immediately").argument("[description]", "What are you building?").option("-l, --language <lang>", "Primary language").option("-f, --framework <framework>", "Framework").option("-c, --cost-mode <mode>", "Cost mode", "balanced").option("--dev-cmd <cmd>", "Custom dev server command").option("--port <port>", "Dev server port").action(quickstartCommand);
2182
+ program.command("preview").description("Start live preview dev server").option("--dev-cmd <cmd>", "Custom dev server command").option("--port <port>", "Dev server port").action(previewCommand);
1917
2183
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qlucent/fishi",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "FISHI — Your AI Dev Team That Actually Ships. Autonomous agent framework for Claude Code.",
5
5
  "license": "MIT",
6
6
  "type": "module",