3 Commits
Author SHA1 Message Date
Zacharias 209bde75e1 Added redirect to codeberg for issue tracking to README.md 2026-07-30 22:06:51 +02:00
Zacharias 5ae5920478 Moved versions of the "public facing" modules to the gradle.properties
Core:
- Fixed my promis of making Core work with multiple OllamaObjects
2026-07-30 21:24:06 +02:00
Zacharias b8280e6ec5 Refactored to Core 1.10.0
This includes chages to make less things static in the Core, instead using the Options Singelton instead
2026-07-30 20:49:06 +02:00
21 changed files with 202 additions and 169 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_26" default="true" project-jdk-name="26" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="graalvm-25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+1 -2
View File
@@ -5,7 +5,7 @@ plugins {
id 'io.spring.dependency-management' version '1.1.4'
}
version = '1.0-SNAPSHOT'
version = APIVersion
dependencies {
implementation project(":Core")
@@ -17,7 +17,6 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test:4.1.0-M4'
//implementation 'org.springframework.boot:spring-boot-starter-actuator'
testImplementation 'org.springframework.boot:spring-boot-starter-test:4.1.0-M4'
//runtimeOnly('org.springframework.boot:spring-boot-starter-web')
}
@@ -3,7 +3,6 @@ package me.neurodock.api;
import me.neurodock.api.payload.request.NewQurryResponceHook;
import me.neurodock.api.payload.request.NewToolRequest;
import me.neurodock.core.Core;
import me.neurodock.core.GlobalObjects;
import me.neurodock.core.PrintMessageHandler;
import me.neurodock.ollama.OllamaObject;
import org.springframework.boot.SpringApplication;
@@ -41,8 +40,9 @@ public class APIApplication {
instance = this;
if(GlobalObjects.getObject("core") instanceof Core coreInstance) {
this.core = coreInstance;
if(false) {
// TODO: This needs to be properly refactored.
//this.core = coreInstance;
} else {
this.core = new Core(new PrintMessageHandler() {
@Override
+3 -1
View File
@@ -2,11 +2,13 @@ plugins {
id 'java-library'
}
version = '1.9.10'
version = coreVersion
dependencies {
implementation project(":Plugin-API")
api "org.json:json:20250107"
implementation 'org.graalvm.polyglot:polyglot:25.1.3'
implementation 'org.graalvm.polyglot:js:25.1.3'
}
java {
+23 -93
View File
@@ -81,11 +81,6 @@ public class Core {
*/
private final PrintAdvanceMessageHandler printMessageHandler;
public static String DATA;
public static File DATA_DIR;
public static File PLUGIN_DIRECTORY;
public static File CACHE_DIRECTORY;
/**
* Creates a new instance of Core with the provided PrintMessageHandler,
* defaulting the Ollama backend to {@code localhost}.
@@ -116,13 +111,6 @@ public class Core {
initShutdownHook();
}
static {
// This should not be enforced like this, instead this should read a data field, or wait for an init to be called....
// Unsure of how to properly do this at this time, however.
// After looking at things, just run {@link Core.setDataDirectory(String)} somewhere else before initilising the Core object and seems to be fine
setDataDirectory("AI-Chat", false);
}
public static void setLogDirectory(String logDirectory)
{
logDir = new File(logDirectory);
@@ -133,55 +121,6 @@ public class Core {
logFile = new File(logDirectory, "latest.log");
}
/**
* Set the data directory in appropriate locations depending on the host OS, falling back to $WORKING_DIR/data
* @param dataDirectory the data directory to use
*/
public static void setDataDirectory(String dataDirectory, boolean fullDirectory) {
String data;
if(System.getenv("AI_CHAT_DEBUG") != null) {
data = "./data";
}
if(fullDirectory) {
data = dataDirectory;
}
else if(System.getProperty("os.name").toLowerCase().contains("windows")) {
String localappdata = System.getenv("LOCALAPPDATA");
if(localappdata == null) {
localappdata = System.getenv("APPDATA");
}
data = localappdata + "/"+ dataDirectory;
}
else if (System.getProperty("os.name").toLowerCase().contains("linux")) {
data = System.getenv("HOME") + "/.local/share/" + dataDirectory;
}
else if (System.getProperty("os.name").toLowerCase().contains("mac")) {
data = System.getProperty("user.home") + "/Library/Application Support/"+ dataDirectory;
}
else {
data = "./data";
}
DATA = data;
DATA_DIR = new File(DATA);
if(!DATA_DIR.exists()) {
DATA_DIR.mkdirs();
}
String pluginDir = DATA + "/plugins";
PLUGIN_DIRECTORY = new File(pluginDir);
if(!PLUGIN_DIRECTORY.exists()) {
PLUGIN_DIRECTORY.mkdirs();
}
CACHE_DIRECTORY = new File(DATA + "/cache");
if(!CACHE_DIRECTORY.exists()) {
CACHE_DIRECTORY.mkdirs();
}
}
/**
* Creates the base directories required by the application.
* <p>
@@ -190,7 +129,8 @@ public class Core {
*/
private void initDirectories() {
ensureDir(logDir.getAbsolutePath());
ensureDir(DATA_DIR.getAbsolutePath() + "/messages");
ensureDir(Options.getInstance().getDataDir() + "/messages");
Options.getInstance().initiateDirectories();
}
/**
@@ -308,7 +248,7 @@ public class Core {
/**
* Persists the current session's messages from the {@link OllamaObject} to two locations:
* a timestamped archive file under {@code ./messages/}, and a rolling {@code messages.json}
* under {@link #DATA_DIR} for resuming the session later.
* under {@link Options#getDataDir()} for resuming the session later.
*
* @see #buildMessagesArray()
* @see #writeMessagesTo(File, JSONArray)
@@ -317,8 +257,8 @@ public class Core {
JSONArray messages = buildMessagesArray();
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd_HH-mm-ss"));
writeMessagesTo(new File(DATA_DIR.getAbsolutePath()+"/messages/" + timestamp + ".json"), messages);
writeMessagesTo(new File(DATA_DIR, "messages.json"), messages);
writeMessagesTo(new File(Options.getInstance().getDataDir().getAbsolutePath()+"/messages/" + timestamp + ".json"), messages);
writeMessagesTo(new File(Options.getInstance().getDataDir(), "messages.json"), messages);
}
/**
@@ -386,25 +326,20 @@ public class Core {
* @param ollamaObject The OllamaObject to use
*/
public void setOllamaObject(OllamaObject ollamaObject) {
if(this.ollamaObject == null) {
this.ollamaObject = ollamaObject;
this.ollamaObject = ollamaObject;
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
{
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
}
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
{
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
}
}
addTool(new AddMemoryFunction(), Source.CORE);
addTool(new RemoveMemoryFunction(), Source.CORE);
addTool(new GetMemoryFunction(), Source.CORE);
addTool(new GetMemoriesFunction(), Source.CORE);
addTool(new GetMemoryIdentitiesFunction(), Source.CORE);
}
else {
throw new IllegalArgumentException("Ollama object is already set");
}
addTool(new AddMemoryFunction(), Source.CORE);
addTool(new RemoveMemoryFunction(), Source.CORE);
addTool(new GetMemoryFunction(), Source.CORE);
addTool(new GetMemoriesFunction(), Source.CORE);
addTool(new GetMemoryIdentitiesFunction(), Source.CORE);
}
/**
@@ -413,18 +348,13 @@ public class Core {
* @param ollamaObject The OllamaObject to use
*/
public void setOllamaObjectNoMemory(OllamaObject ollamaObject) {
if(this.ollamaObject == null) {
this.ollamaObject = ollamaObject;
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
{
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
}
this.ollamaObject = ollamaObject;
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
{
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
}
}
else {
throw new IllegalArgumentException("Ollama object is already set");
}
}
/**
@@ -869,7 +799,7 @@ public class Core {
@Override
public File getDataDictionary() {
return DATA_DIR;
return Options.getInstance().getDataDir();
}
@Override
@@ -879,7 +809,7 @@ public class Core {
@Override
public File getCacheDirectory() {
return CACHE_DIRECTORY;
return Options.getInstance().getCacheDirectory();
}
public void addPlugin(LoadedPlugin plugin)
@@ -1,29 +0,0 @@
package me.neurodock.core;
import java.util.HashMap;
import java.util.Map;
public class GlobalObjects {
private static final Map<String, Object> objects = new HashMap<>();
public static void addObject(String name, Object object) {
if (name == null || object == null) {
throw new IllegalArgumentException("Name and object cannot be null");
}
objects.put(name, object);
}
public static Object getObject(String name) {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
return objects.get(name);
}
public static boolean removeObject(String name) {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
return objects.remove(name) != null;
}
}
@@ -1,15 +1,12 @@
package me.neurodock.core;
import com.sun.jdi.connect.spi.TransportService;
import me.neurodock.ollama.*;
import org.json.JSONObject;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -448,7 +445,7 @@ public class LLMSystemPrompt {
* Loads {@link #behavior} from a text file, resolved in order against:
* <ol>
* <li>the classpath</li>
* <li>{@link Core#DATA_DIR}</li>
* <li>{@link Options#getDataDir()}</li>
* <li>the current runtime/working directory</li>
* <li>an exact file path match</li>
* </ol>
@@ -553,7 +550,7 @@ public class LLMSystemPrompt {
/**
* Resolves and reads a behavior file as UTF-8 text, checking the classpath,
* {@link Core#DATA_DIR}, the working directory, and an exact path match, in that order.
* {@link Options#getDataDir()}, the working directory, and an exact path match, in that order.
*
* @param path the file to look for
* @return the full file contents, with each line terminated by {@code \n}
@@ -566,7 +563,7 @@ public class LLMSystemPrompt {
if (classpathResourceExists("/"+path)) {
in = BehaviorLoader.class.getResourceAsStream("/"+path);
} else {
Path dataDirPath = Path.of(Core.DATA_DIR.getPath(), path);
Path dataDirPath = Path.of(Options.getInstance().getDataDir().getPath(), path);
Path relativePath = Path.of(path);
if (Files.exists(dataDirPath)) {
in = new FileInputStream(dataDirPath.toFile());
@@ -0,0 +1,123 @@
package me.neurodock.core;
import java.io.File;
import java.nio.file.Path;
public class Options {
/**
* The singleton options object
*/
private static Options instance = new Options();
/**
* Provides the singleton options object for modification to options
* @return the current {@link Options} singleton object
*/
public static Options getInstance()
{
return instance;
}
private Path data;
private File dataDir;
private File pluginDirectory;
private File cacheDirectory;
/**
* Sets a new singleton options object
* @param options the new singleton object
*/
public static void setInstance(Options options)
{
instance = options;
}
public Options setDataDir(Path dataDir, boolean fullDirectory)
{
Path data = Path.of("./data");
String os = System.getProperty("os.name").toLowerCase();
Path cache;
if(System.getenv("AI_CHAT_DEBUG") == null) {
if (fullDirectory) {
data = dataDir;
} else {
if (os.contains("windows")) {
String localappdata = System.getenv("LOCALAPPDATA");
if (localappdata == null) {
localappdata = System.getenv("APPDATA");
}
data = Path.of(localappdata, dataDir.toFile().getPath());
} else if (os.contains("linux")) {
data = Path.of(System.getenv("HOME"), ".local/share", dataDir.toFile().getPath());
} else if (os.contains("mac")) {
data = Path.of(System.getProperty("user.home"), "Library/Application Support", dataDir.toFile().getPath());
}
}
}
if (os.contains("win")) {
String localAppData = System.getenv("LOCALAPPDATA");
cache = Path.of(localAppData != null ? localAppData
: System.getProperty("user.home") + "\\AppData\\Local");
} else if (os.contains("mac")) {
cache = Path.of(System.getProperty("user.home"), "Library", "Caches");
} else {
// Linux / other unix
String xdgCache = System.getenv("XDG_CACHE_HOME");
cache = Path.of(xdgCache != null && !xdgCache.isBlank()
? xdgCache
: System.getProperty("user.home") + "/.cache");
}
this.data = data;
this.cacheDirectory = new File(cache.toFile(), data.getFileName().toFile().toString());
return this;
}
public Options initiateDirectories()
{
this.dataDir = this.data.toFile();
if(!this.dataDir.exists()) {
this.dataDir.mkdirs();
}
String pluginDir = this.data + "/plugins";
pluginDirectory = new File(pluginDir);
if(!pluginDirectory.exists()) {
pluginDirectory.mkdirs();
}
if(!cacheDirectory.exists()) {
cacheDirectory.mkdirs();
}
return this;
}
public Path getData() {
return data;
}
public File getDataDir() {
return dataDir;
}
public File getPluginDirectory() {
return pluginDirectory;
}
public File getCacheDirectory() {
return cacheDirectory;
}
public Path getFileHandlerDataLocation()
{
return Path.of(dataDir.toString(), "files");
}
}
@@ -1,11 +1,11 @@
package me.neurodock.core.files;
import me.neurodock.core.Core;
import me.neurodock.core.Options;
import me.neurodock.core.Pair;
import me.neurodock.core.files.tools.ReadFileTool;
import me.neurodock.core.files.tools.WriteFileTool;
import me.neurodock.ollama.OllamaTool;
import org.intellij.lang.annotations.MagicConstant;
import java.io.*;
import java.nio.file.Path;
@@ -27,11 +27,12 @@ public class FileHandler {
/**
* Creates a new instance as well as setting the {@link #instance} to this new one
* A good start is to use {@link Options#getFileHandlerDataLocation()} as it's located along all other files
* @param baseDirectory the directory to be used as base directory
*/
public FileHandler(@MagicConstant(valuesFromClass = FileHandlerLocation.class) String baseDirectory) {
public FileHandler(Path baseDirectory) {
try {
root = Path.of(baseDirectory).toAbsolutePath().normalize();
root = baseDirectory.toAbsolutePath().normalize();
if (!root.toFile().exists()) {
root.toFile().mkdirs();
}
@@ -1,7 +0,0 @@
package me.neurodock.core.files;
import me.neurodock.core.Core;
public class FileHandlerLocation {
public static final String DATA_FILES = Core.DATA+"/files";
}
@@ -1,6 +1,6 @@
package me.neurodock.core.memory;
import me.neurodock.core.Core;
import me.neurodock.core.Options;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -17,7 +17,7 @@ public class CoreMemory {
/**
* The singleton instance of CoreMemory.
*/
private static final CoreMemory instance = new CoreMemory(Core.DATA + "/CoreMemory.json");
private static final CoreMemory instance = new CoreMemory(Options.getInstance().getDataDir() + "/CoreMemory.json");
/**
* Memory type identifier for key-value mapped memory storage.
@@ -1,10 +1,6 @@
package me.neurodock.ollama;
import me.neurodock.core.Core;
import me.neurodock.core.LLMSystemPrompt;
import me.neurodock.core.LaunchOptions;
import me.neurodock.core.Pair;
import me.neurodock.core.files.FileHandlerLocation;
import me.neurodock.core.*;
import me.neurodock.core.files.FileHandler;
import org.intellij.lang.annotations.MagicConstant;
@@ -15,6 +11,7 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -116,7 +113,7 @@ public class OllamaObject {
LaunchOptions launchOptions = LaunchOptions.getInstance();
if(launchOptions.isLoadOld()) {
System.out.println("Loading old data...");
File f = new File(Core.DATA_DIR+"/messages.json");
File f = new File(Options.getInstance().getDataDir(), "messages.json");
if(f.exists()) {
try {
BufferedReader br = new BufferedReader(new FileReader(f));
@@ -584,10 +581,10 @@ public class OllamaObject {
* object construction. Calling this after {@link #build()} is technically possible
* but not intended.
* </p>
* @param baseDirectory the base directory for file access, see {@link FileHandlerLocation}
* @param baseDirectory the base directory for file access.
* @return The {@link OllamaObjectBuilder}
*/
public OllamaObjectBuilder addFileTools(@MagicConstant(valuesFromClass = FileHandlerLocation.class) String baseDirectory)
public OllamaObjectBuilder addFileTools(Path baseDirectory)
{
new FileHandler(baseDirectory);
+3 -1
View File
@@ -3,11 +3,13 @@ import me.neurodock.core.files.FileHandlerException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
public class FileTest {
@Test
void TestError()
{
FileHandler fileHandler = new FileHandler("./test/files");
FileHandler fileHandler = new FileHandler(Path.of("./test/files"));
Assertions.assertThrowsExactly(FileHandlerException.class, () -> fileHandler.readFile("../build.gradle"));
}
}
+3 -1
View File
@@ -1,5 +1,6 @@
import me.neurodock.core.Core;
import me.neurodock.core.LaunchOptions;
import me.neurodock.core.Options;
import me.neurodock.core.PrintMessageHandler;
import me.neurodock.ollama.OllamaMessage;
import me.neurodock.ollama.OllamaMessageRole;
@@ -10,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.*;
import javax.imageio.ImageIO;
import java.awt.*;
import java.io.File;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicReference;
public class OllamaImages {
@@ -24,7 +26,7 @@ public class OllamaImages {
assertNotNull(i.get());
LaunchOptions.getInstance().setLoadOld(false);
Core.setDataDirectory("AI-Chat/Test", false);
Options.getInstance().setDataDir(Path.of("AI-Chat/Test"), false);
Core core = new Core(new PrintMessageHandler() {
@Override
@@ -1,15 +1,16 @@
package me.neurodock.display;
import me.neurodock.core.Core;
import me.neurodock.core.Options;
import me.neurodock.core.Pair;
import me.neurodock.core.PrintMessageHandler;
import me.neurodock.core.files.FileHandlerLocation;
import me.neurodock.core.memory.CoreMemory;
import me.neurodock.ollama.*;
import me.neurodock.ollama.utils.SystemMessage;
import org.json.JSONObject;
import java.io.*;
import java.nio.file.Path;
import java.util.*;
import static me.neurodock.core.Core.writeLog;
@@ -43,15 +44,17 @@ public class Display {
public Display()
{
Options.getInstance().setDataDir(Path.of("AI-Chat"), false);
core.setOllamaObject/*NoMemory*/(OllamaObject.builder()
.setModel("llama3.1:8b")
.keep_alive(10)
//.stream(false)
.addFileTools(FileHandlerLocation.DATA_FILES)
.addFileTools(Options.getInstance().getFileHandlerDataLocation())
.addMessages(new SystemMessage("You need to emulate feelings and emotions."))
.build());
core.enablePlugins(Core.PLUGIN_DIRECTORY);
core.enablePlugins(Options.getInstance().getPluginDirectory());
core.addTool(new TimeTool(), Core.Source.CTP);
// TODO: Well Docker failes when luanched.... Fuck
@@ -158,9 +161,9 @@ public class Display {
break;
case "working":
System.out.println("Working directories:\n" +
" Data: " + Core.DATA_DIR.getAbsolutePath() + "\n" +
" DateFiles: " + FileHandlerLocation.DATA_FILES + "\n" +
" Plugins: " + Core.PLUGIN_DIRECTORY.getAbsolutePath());
" Data: " + Options.getInstance().getDataDir().getAbsolutePath() + "\n" +
" DateFiles: " + Options.getInstance().getFileHandlerDataLocation() + "\n" +
" Plugins: " + Options.getInstance().getPluginDirectory().getAbsolutePath());
break;
default:
System.out.println("Unknown command: " + message);
@@ -7,6 +7,7 @@ import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
import me.neurodock.core.Core;
import me.neurodock.core.Options;
import me.neurodock.core.Pair;
import me.neurodock.ollama.OllamaFunctionArgument;
import me.neurodock.ollama.OllamaFunctionTool;
@@ -434,7 +435,7 @@ public class PythonRunner extends OllamaFunctionTool {
fullCmd.append(arg).append(" ");
}
File program = new File(Core.CACHE_DIRECTORY, "cmd.sh");
File program = new File(Options.getInstance().getCacheDirectory(), "cmd.sh");
if(program.exists()){
program.delete();
}
@@ -5,6 +5,7 @@ import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import me.neurodock.core.Core;
import me.neurodock.core.Options;
import me.neurodock.ollama.OllamaFunctionTool;
import me.neurodock.ollama.OllamaFunctionTools;
import org.json.JSONObject;
@@ -27,13 +28,13 @@ public class GeniusTools {
public final String Access_Token;
public final String BaseURL = "https://api.genius.com";
public final OllamaFunctionTools GeniusTools;
public final File CacheFile = new File(Core.DATA_DIR + "/genius_cache.json");
public final File CacheFile = new File(Options.getInstance().getDataDir(), "genius_cache.json");
public JSONObject CacheData;
public GeniusTools() {
super();
try {
JSONObject obj = new JSONObject(Files.readString(Path.of(Core.DATA_DIR + "/geniusapi.json")));
JSONObject obj = new JSONObject(Files.readString(Path.of(Options.getInstance().getDataDir().getPath(), "geniusapi.json")));
this.Client_ID = obj.getString("client_id");
this.Client_Secret = obj.getString("client_secret");
this.Access_Token = obj.getString("access_token");
@@ -5,6 +5,7 @@ import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import me.neurodock.core.Core;
import me.neurodock.core.Options;
import me.neurodock.ollama.OllamaFunctionTool;
import me.neurodock.ollama.OllamaFunctionTools;
import org.json.JSONObject;
@@ -30,7 +31,7 @@ public class MALAPITool {
public MALAPITool() {
super();
try {
JSONObject obj = new JSONObject(Files.readString(Path.of(Core.DATA_DIR + "/malapi.json")));
JSONObject obj = new JSONObject(Files.readString(Path.of(Options.getInstance().getDataDir().getPath(), "malapi.json")));
this.Client_ID = obj.getString("client_id");
this.Client_Secret = obj.getString("client_secret");
} catch (IOException e) {
+1 -1
View File
@@ -2,7 +2,7 @@ plugins {
id 'java-library'
}
version = '0.1.4.1'
version = pluginAPIVersion
repositories {
mavenCentral()
+6
View File
@@ -18,6 +18,12 @@ The author **does not endorse or encourage** scraping or any other use that viol
Use this software **at your own risk**. The author disclaims any liability for legal or technical consequences arising from its use.
## How to report issues?
This is a personal, self-hosted Gitea instance, and I haven't set up account
registration or figured out permissions for letting new accounts only open
issues — and honestly, I'd rather not have random accounts created here anyway.<br>
All issues for NeuroDock should be filed on the **[Codeberg mirror](https://codeberg.org/alienfromdia/NeuroDock/issues)**. Thanks for understanding.
## API
The documentation for the API is available at the gitea wiki under [API docs](https://git.server.4zellen.se/neurodock/NeuroDock/wiki/API-Docs)
+4
View File
@@ -1,3 +1,7 @@
# This is used in sub-project that dosent explicitly require the API sub-project to only include if thay shuld be included project wide.
useAPI = true
javaVersion = 25
coreVersion = 1.10.1
pluginAPIVersion = 0.1.4.1
APIVersion = 1.0-SNAPSHOT