Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,6 @@ Cognotik uses a "Bring Your Own Key" (BYOK) model for all AI service integration

- OpenAI, Anthropic, and other AI providers for their powerful models
- JetBrains for the IntelliJ platform
- The open-source community for various libraries and tools used in the project
- The open-source community for various libraries and tools used in the project
## Improvements
This is a small improvement to the documentation.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,91 @@ import java.io.File
import java.io.InputStream
import java.nio.file.Path
import java.util.*
import kotlin.text.StringBuilder
import kotlin.io.path.name

class FileSelectionUtils {
companion object {
val log = LoggerFactory.getLogger(FileSelectionUtils::class.java)
/**
* Creates an ASCII-art formatted compact tree listing of files and directories
* starting from the given root file, subject to filtering and depth constraints.
*
* @param rootFile The starting file or directory.
* @param maxFilesPerDir The maximum number of entries to process in any single directory.
* @param fn A filter function that determines whether a file or directory should be included in the tree.
* @return A string representing the ASCII tree of matching files.
*/
fun filteredWalkAsciiTree(
rootFile: File,
maxFilesPerDir: Int = 20,
fn: (File) -> Boolean = { !isLLMIgnored(it.toPath()) }
): String {
val sb = StringBuilder()
if (!fn(rootFile)) {
log.debug("Skipping root file for tree: ${rootFile.absolutePath}")
return "" // Root itself doesn't match, so empty tree
}
sb.append(rootFile.name)
if (rootFile.isDirectory) {
sb.append("/")
}
sb.appendLine()
if (rootFile.isDirectory) {
val children = rootFile.listFiles()?.toList() ?: emptyList()
val entriesToConsider = children.take(maxFilesPerDir)
entriesToConsider.forEachIndexed { index, child ->
buildAsciiSubTree(
child,
"", // Initial parentContinuationPrefix for children of the root
index == entriesToConsider.size - 1,
maxFilesPerDir,
fn,
sb
)
}
}
return sb.toString()
}
private fun buildAsciiSubTree(
currentFile: File,
parentContinuationPrefix: String, // Prefix like "│ " or " "
isLastInSiblings: Boolean,
maxFilesPerDir: Int,
filterFn: (File) -> Boolean,
sb: StringBuilder
) {
if (!filterFn(currentFile)) {
// If the current file is filtered out, do not display it or its children.
// log.debug("Skipping in tree (sub): ${currentFile.absolutePath}") // Optional: for more verbose logging
return
}
sb.append(parentContinuationPrefix)
sb.append(if (isLastInSiblings) "└── " else "├── ")
sb.append(currentFile.name)
if (currentFile.isDirectory) {
sb.append("/")
}
sb.appendLine()
if (currentFile.isDirectory) {
val children = currentFile.listFiles()?.toList() ?: emptyList()
val entriesToConsider = children.take(maxFilesPerDir)
// The new continuation prefix for the children of currentFile
val childContinuationPrefix = parentContinuationPrefix + (if (isLastInSiblings) " " else "│ ")
entriesToConsider.forEachIndexed { index, child ->
buildAsciiSubTree(
child,
childContinuationPrefix,
index == entriesToConsider.size - 1,
maxFilesPerDir,
filterFn,
sb
)
}
}
}


fun filteredWalk(
file: File,
maxFilesPerDir: Int = 20,
Expand Down Expand Up @@ -139,6 +219,7 @@ class FileSelectionUtils {
path.name == "node_modules" -> return true
path.name == "target" -> return true
path.name == "build" -> return true
path.name == ".git" -> return true
}
var currentDir = path.toFile().parentFile
currentDir ?: return false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Enhanced on 2025-05-08 17:48:16 - Minor improvements
// New utility class for string manipulation
class StringManipulationUtils {

Expand Down Expand Up @@ -147,4 +148,4 @@ class StringManipulationUtils {
}
}

module.exports = StringManipulationUtils;
module.exports = StringManipulationUtils;
87 changes: 55 additions & 32 deletions desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
## Overview

The Cognotik Application Server is a modular platform for hosting AI-powered applications. It provides a daemon-based
architecture that allows applications to run in the background and be accessed via a web interface. The server includes
system tray integration, socket-based communication for remote control, and various AI-powered applications.
architecture that allows applications to run in the background and be accessed via a web interface or system tray.
The server includes system tray integration, socket-based communication for remote control, automatic updates,
and various AI-powered applications configurable through a setup wizard.

## Open Source & API Key Requirements

Expand All @@ -20,19 +21,18 @@ model for AI services:

### AppServer

The main server component that hosts web applications and provides the core functionality.
The main server component that hosts web applications, manages the system tray, handles updates, and provides the core functionality.

**Key Features:**

- Web server hosting multiple AI applications
- System tray integration for easy access
- Socket-based communication for remote control
- Authentication and authorization management
- Welcome wizard for initial setup and session configuration
- User settings management (API Keys, Local Tools) via web UI
- Socket server for communication with DaemonClient

**Usage:**

```
java -cp <classpath> com.simiacryptus.cognotik.AppServer server [options]
java -cp <classpath> com.simiacryptus.cognotik.AppServer [options]
```

**Options:**
Expand Down Expand Up @@ -69,22 +69,31 @@ Manages the system tray icon and menu for easy access to the applications.

**Key Features:**

- System tray icon with context menu
- Quick access to hosted applications
- Server shutdown option
- Quick access to hosted applications via browser
- Option to check for and install updates

### UpdateManager
Handles checking for new application versions, downloading updates, and launching the appropriate installer for the detected operating system (Windows, macOS, Linux).
**Key Features:**
- Checks GitHub releases for the latest version
- Compares latest version with the current running version
- Downloads platform-specific installers (MSI, DMG, DEB)
- Provides UI prompts for update confirmation and progress
- Manages installer execution (including uninstallation steps where needed)


## Included Applications

The server hosts several AI-powered applications:
The server hosts several AI-powered applications, primarily accessed through a central welcome wizard or the system tray:

1. **Basic Chat** (`/chat`): A simple chat interface for interacting with AI models.
1. **Basic Chat** (`/chat`): A simple, standalone chat interface accessible via a button in the welcome page header. Allows configuring model, temperature, and budget separately.

2. **Task Runner** (`/singleTask`): A single-task execution environment using the `SingleTaskMode` cognitive strategy.
2. **Task Chat** (`/taskChat`): Launched from the welcome wizard ("Chat" mode). An interactive environment for executing individual tasks using the `TaskChatMode` cognitive strategy. Configurable via the wizard.

3. **Auto Plan** (`/autoPlan`): An application that automatically plans and executes tasks using the `AutoPlanMode`
3. **Autonomous Mode** (`/autoPlan`): Launched from the welcome wizard ("Autonomous" mode). An application that automatically plans and executes tasks using the `AutoPlanMode`
cognitive strategy.

4. **Plan Ahead** (`/planAhead`): An application that plans ahead before executing tasks using the `PlanAheadMode`
4. **Plan Ahead Mode** (`/planAhead`): Launched from the welcome wizard ("Plan Ahead" mode). An application that plans ahead before executing tasks using the `PlanAheadMode`
cognitive strategy.

## Installation
Expand All @@ -106,11 +115,11 @@ Each package includes:
### Manual Installation

1. Build the project using Gradle:
```
```bash
./gradlew shadowJar
```

2. Run the application:
2. Run the application (starts the DaemonClient, which manages the AppServer):
```
java -jar build/libs/cognotik-<version>-all.jar
```
Expand All @@ -120,18 +129,18 @@ Each package includes:
### Building from Source

1. Clone the repository
2. Build using Gradle:
2. Build the project (including the shadow JAR):
```
./gradlew build
./gradlew shadowJar
```

### Running in Development Mode

```
./gradlew runServer
./gradlew run
```

### Creating Platform Packages
### Creating Platform-Specific Packages

```
./gradlew package
Expand All @@ -149,35 +158,49 @@ The application follows a modular architecture:

1. **DaemonClient**: Entry point that manages the server process
2. **AppServer**: Core server that hosts web applications
3. **SystemTrayManager**: UI integration via system tray
4. **Web Applications**: Individual applications hosted by the server
3. **SystemTrayManager**: UI integration via system tray (part of AppServer)
4. **UpdateManager**: Handles application updates (used by SystemTrayManager/AppServer)
5. **Web Applications**: Individual applications hosted by the server (e.g., BasicChatApp, UnifiedPlanApp)
6. **Welcome Wizard (`welcome.html`)**: Initial configuration and session launch UI.

Communication between components:

- Socket-based communication between DaemonClient and AppServer
- HTTP/WebSocket for web applications
- System tray for user interaction
- GitHub API for update checks

## Configuration

The server uses sensible defaults but can be configured via command-line options. It automatically finds available ports
The server uses sensible defaults but can be configured via command-line options (passed to `AppServer` via `DaemonClient`)
and a web-based welcome wizard. It automatically finds available ports
if the default ports are in use.

### API Key Configuration

You'll need to configure your API keys before using the AI features:

1. Launch the application
2. Access the settings via the system tray menu or web interface
3. Enter your API keys for the services you want to use (OpenAI, Anthropic, etc.)
4. Save your configuration
Your API keys are stored locally and are only used to authenticate with the respective services.
2. The welcome wizard (`/`) will load automatically.
3. Click the "User Settings" button in the top-right menu bar.
4. In the modal dialog:
- Go to the "API Keys" tab.
- Enter your API keys for the services you want to use (OpenAI, Anthropic, Groq, etc.).
- Go to the "Local Tools" tab (optional).
- Add paths to any local command-line tools you want the AI to be able to use.
- Click "Save Settings".
5. Your API keys and tool paths are stored locally and are only used to authenticate with the respective services or execute the specified tools.

### Session Configuration (via Welcome Wizard)

Before launching an AI session, the welcome wizard guides you through:
- **Choosing a Cognitive Mode:** Chat, Autonomous, or Plan Ahead.
- **Configuring Settings:** Selecting default and parsing AI models, setting the working directory, adjusting temperature, and enabling/disabling auto-fix.
- **Selecting Tasks:** Enabling specific capabilities like file modification, shell execution, web search, etc.

## Troubleshooting

- **Server not starting**: Check if another instance is already running
- **Port conflicts**: The server will automatically find an available port
- **System tray not showing**: Ensure your system supports system tray icons
To check for updates manually, use the "Update to..." option in the system tray menu (if an update is available).

To stop a running server:

Expand Down
39 changes: 29 additions & 10 deletions desktop/src/main/kotlin/com/simiacryptus/cognotik/AppServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.simiacryptus.cognotik.plan.PlanSettings
import com.simiacryptus.cognotik.plan.cognitive.AutoPlanMode
import com.simiacryptus.cognotik.plan.cognitive.PlanAheadMode
import com.simiacryptus.cognotik.plan.cognitive.TaskChatMode
import com.simiacryptus.cognotik.plan.cognitive.GoalOrientedMode
import com.simiacryptus.cognotik.platform.ApplicationServices
import com.simiacryptus.cognotik.platform.file.AuthorizationManager
import com.simiacryptus.cognotik.platform.model.AuthenticationInterface
Expand Down Expand Up @@ -76,14 +77,17 @@ open class AppServer(
}

private var server: AppServer? = null




private fun handleServer(vararg args: String) {
log.info("Parsing server options...")
val options = parseServerOptions(*args)
log.info("Configuring server with options: port=${options.port}, host=${options.host}, publicName=${options.publicName}")

var actualPort = options.port
try {

ServerSocket(actualPort).use {
log.debug("Port $actualPort is available")
}
Expand All @@ -94,8 +98,8 @@ open class AppServer(
log.info("Using alternative port $actualPort")
println("Using alternative port $actualPort")
}
scheduledExecutorService.scheduleAtFixedRate({checkUpdate()},
0, 7*24, TimeUnit.HOURS)
scheduledExecutorService.scheduleAtFixedRate({ checkUpdate() },
0, 7 * 24, TimeUnit.HOURS)
server = AppServer(
localName = options.host,
publicName = options.publicName,
Expand All @@ -104,12 +108,16 @@ open class AppServer(
server?.initSystemTray()
server?.startSocketServer(actualPort + 1)


Runtime.getRuntime().addShutdownHook(Thread {
log.info("Shutdown hook triggered, stopping server...")
server?.stopServer()
})
server?._main(*args)
// Call _main with NO server options (strip out --port/--host/--public-name and their values)
val filteredArgs = args.filterIndexed { i, arg ->
arg in listOf("--port", "--host", "--public-name") ||
(i > 0 && args[i - 1] in listOf("--port", "--host", "--public-name"))
}.toTypedArray()
server?._main(*filteredArgs)
}

private fun findAvailablePort(startPort: Int): Int {
Expand Down Expand Up @@ -250,7 +258,7 @@ open class AppServer(
workingDir = "."
)
listOf(
ChildWebApp("/chat", BasicChatApp(".".toFile(), model, parsingModel)),
ChildWebApp("/chat", BasicChatApp(File("."), model, parsingModel)),
ChildWebApp(
"/taskChat", UnifiedPlanApp(
path = "/taskChat",
Expand Down Expand Up @@ -286,6 +294,18 @@ open class AppServer(
cognitiveStrategy = PlanAheadMode,
describer = describer
)
),
ChildWebApp(
"/goalOriented", UnifiedPlanApp(
path = "/goalOriented",
applicationName = "Goal-Oriented",
planSettings = planSettings,
model = model,
parsingModel = parsingModel,
api2 = api2,
cognitiveStrategy = GoalOrientedMode,
describer = describer
)
)
)
}
Expand Down Expand Up @@ -402,14 +422,13 @@ open class AppServer(

}

private fun String.toFile(): File = File(this)



fun String?.urlEncode(): String {
return this?.let {
URLEncoder.encode(it, "UTF-8")
URLEncoder.encode(it, Charsets.UTF_8.name())
.replace("+", "%20")

.replace("%7E", "~")

} ?: ""
}
Loading