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: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ Commands parse parameters by splitting on commas: `message.getTextParameters()`
Runtime config at `~/Library/Application Support/Jared/config.json`. Schema:
```json
{
"routes": { "routename": { "disabled": true } },
"disabledCommands": { "/ping": true },
"webhooks": [{ "url": "https://...", "routes": [] }],
"webServer": { "port": 3005 }
}
```
Route names in `config.routes` are lowercased keys matching `Route.name.lowercased()`.
`disabledCommands` keys are lowercased command names (e.g. `"/ping"`). Legacy `"routes": { "name": { "disabled": true } }` configs are decoded as aliases automatically.

## Entities & Recipients

Expand Down
6 changes: 2 additions & 4 deletions Documentation/config-sample.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"routes": {
"/remote": {
"disabled": true
}
"disabledCommands": {
"/remote": true
},
"webhooks": [
{
Expand Down
1 change: 0 additions & 1 deletion Documentation/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

Welcome to the Jared documentation hub. Here you'll find a glossary of Jared's API documentation.

+ [Routes](routes.md)
+ [Webhooks](webhooks.md)
+ [n8n Integration](n8n.md)
+ [REST API](restapi.md)
Expand Down
34 changes: 0 additions & 34 deletions Documentation/routes.md

This file was deleted.

16 changes: 16 additions & 0 deletions Documentation/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ Add webhook entries to the `webhooks` array in `config.json` (`~/Library/Applica
}
```

### Disabling built-in commands

Add a `disabledCommands` map to `config.json` to prevent specific slash commands from being routed:

```json
{
"disabledCommands": {
"/remote": true
}
}
```

Keys are lowercased command names (e.g. `"/ping"`, `"/send"`). A value of `true` disables that command; `false` or absent leaves it enabled.

**Legacy `routes` key** — configs using the old `"routes": { "name": { "disabled": true } }` format continue to load correctly. The decoder maps legacy entries to `disabledCommands` automatically.

### Backward compatibility

The simple format from Jared v1.6.x loads unchanged. All new fields have defaults:
Expand Down
28 changes: 18 additions & 10 deletions Jared/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,41 @@
import Foundation

struct ConfigurationFile: Decodable {
let routes: [String: RouteConfiguration]
let disabledCommands: [String: Bool]
let webhooks: [RichWebhook]
let webServer: WebserverConfiguration

init(routes: [String: RouteConfiguration] = [:],
init(disabledCommands: [String: Bool] = [:],
webhooks: [RichWebhook] = [],
webServer: WebserverConfiguration = WebserverConfiguration(port: 3000)) {
self.routes = routes
self.disabledCommands = disabledCommands
self.webhooks = webhooks
self.webServer = webServer
}

init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
routes = (try? c.decodeIfPresent([String: RouteConfiguration].self, forKey: .routes)) ?? [:]
webhooks = (try? c.decodeIfPresent([RichWebhook].self, forKey: .webhooks)) ?? []
webServer = (try? c.decodeIfPresent(WebserverConfiguration.self, forKey: .webServer)) ?? WebserverConfiguration(port: 3000)
// Accept new "disabledCommands" key; fall back to legacy "routes" map for existing configs.
if let newMap = try? c.decodeIfPresent([String: Bool].self, forKey: .disabledCommands) {
disabledCommands = newMap
} else if let legacyRoutes = try? c.decodeIfPresent([String: LegacyRouteConfiguration].self, forKey: .routes) {
disabledCommands = legacyRoutes.mapValues { $0.disabled }
} else {
disabledCommands = [:]
}
webhooks = (try? c.decodeIfPresent([RichWebhook].self, forKey: .webhooks)) ?? []
webServer = (try? c.decodeIfPresent(WebserverConfiguration.self, forKey: .webServer)) ?? WebserverConfiguration(port: 3000)
}

private enum CodingKeys: String, CodingKey {
case routes, webhooks, webServer
case disabledCommands, routes, webhooks, webServer
}
}

private struct LegacyRouteConfiguration: Decodable {
let disabled: Bool
}

struct WebserverConfiguration: Decodable {
let port: Int
let bearerToken: String?
Expand All @@ -56,6 +67,3 @@ struct WebserverConfiguration: Decodable {
}
}

struct RouteConfiguration: Decodable {
let disabled: Bool
}
6 changes: 1 addition & 5 deletions Jared/PluginManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,7 @@ class PluginManager: RouteProvider, PluginController, ConfigurationApplier {
}

func enabled(routeName: String) -> Bool {
if let routeConfig = config.routes[routeName.lowercased()] {
return !routeConfig.disabled
} else {
return true
}
return !(config.disabledCommands[routeName.lowercased()] ?? false)
}

func getAllModules() -> [RoutingModule] {
Expand Down
26 changes: 22 additions & 4 deletions JaredTests/ConfigurationWatcherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ConfigurationWatcherTests: XCTestCase {
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
let configURL = dir.appendingPathComponent("config.json")
let initial = """
{"routes":{},"webhooks":[],"webServer":{"port":3000}}
{"webhooks":[],"webServer":{"port":3000}}
"""
try initial.write(to: configURL, atomically: true, encoding: .utf8)

Expand All @@ -24,7 +24,7 @@ class ConfigurationWatcherTests: XCTestCase {
watcher.start()

let updated = """
{"routes":{"ping":{"disabled":true}},"webhooks":[],"webServer":{"port":3000}}
{"disabledCommands":{"/ping":true},"webhooks":[],"webServer":{"port":3000}}
"""
try updated.write(to: configURL, atomically: true, encoding: .utf8)

Expand All @@ -36,7 +36,7 @@ class ConfigurationWatcherTests: XCTestCase {
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
let configURL = dir.appendingPathComponent("config.json")
let initial = """
{"routes":{},"webhooks":[],"webServer":{"port":3000}}
{"webhooks":[],"webServer":{"port":3000}}
"""
try initial.write(to: configURL, atomically: true, encoding: .utf8)

Expand All @@ -53,13 +53,31 @@ class ConfigurationWatcherTests: XCTestCase {
watcher.start()

let updated = """
{"routes":{},"webhooks":[],"webServer":{"port":4000}}
{"webhooks":[],"webServer":{"port":4000}}
"""
try updated.write(to: configURL, atomically: true, encoding: .utf8)
wait(for: [expectation], timeout: 3.0)
XCTAssertEqual(applier.applied.last?.webServer.port, 4000)
}

func testDisabledCommandsNewKey() throws {
let json = """
{"disabledCommands":{"/ping":true,"/send":false},"webhooks":[],"webServer":{"port":3000}}
"""
let config = try JSONDecoder().decode(ConfigurationFile.self, from: Data(json.utf8))
XCTAssertTrue(config.disabledCommands["/ping"] == true)
XCTAssertTrue(config.disabledCommands["/send"] == false)
}

func testDisabledCommandsLegacyRoutesKey() throws {
let json = """
{"routes":{"/ping":{"disabled":true},"/send":{"disabled":false}},"webhooks":[],"webServer":{"port":3000}}
"""
let config = try JSONDecoder().decode(ConfigurationFile.self, from: Data(json.utf8))
XCTAssertTrue(config.disabledCommands["/ping"] == true)
XCTAssertTrue(config.disabledCommands["/send"] == false)
}

func testCallbackNotFiredBeforeStart() throws {
let dir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
Expand Down