noiton 2.0.1 → 2.0.2
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/README.md +2 -12
- package/package.json +2 -7
- package/src/index.js +0 -5
- package/src/lib/agents/hermes.js +20 -1
- package/src/lib/agents/index.js +42 -12
- package/src/lib/agents/opencode.js +19 -1
- package/src/lib/detect.js +47 -107
- package/src/lib/install.js +0 -2
- package/src/lib/agents/aider.js +0 -141
- package/src/lib/agents/cline.js +0 -128
- package/src/lib/agents/kilo.js +0 -140
- package/src/lib/agents/kimi.js +0 -149
- package/src/lib/agents/openclaw.js +0 -155
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npx noiton
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
The octopus will:
|
|
14
|
-
1. 🔍 Detect which AI agents are installed (OpenCode, Hermes
|
|
14
|
+
1. 🔍 Detect which AI agents are installed (OpenCode, Hermes)
|
|
15
15
|
2. 📦 If none found, offer to install OpenCode or Hermes based on your use case
|
|
16
16
|
3. 🔄 If OpenCode is outdated, offer to update it
|
|
17
17
|
4. 🔑 Ask for your NOITON API key (one key serves all agents)
|
|
@@ -25,11 +25,6 @@ The octopus will:
|
|
|
25
25
|
|-------|------|---------------|-----------------|
|
|
26
26
|
| **OpenCode** | CLI | JSON (`opencode.json`) | ✅ |
|
|
27
27
|
| **Hermes** | CLI | YAML (`config.yaml` + `.env`) | Single default |
|
|
28
|
-
| **OpenClaw** | CLI | JSON5 (`openclaw.json`) | ✅ |
|
|
29
|
-
| **Cline** | VS Code ext | JSON (VS Code settings) | Single |
|
|
30
|
-
| **Aider** | CLI | YAML (`.aider.conf.yml` + `.env`) | Single |
|
|
31
|
-
| **Kilo Code** | CLI + VS Code | JSONC (`kilo.jsonc`) | ✅ |
|
|
32
|
-
| **Kimi Code** | CLI + VS Code | TOML (`config.toml`) | Single |
|
|
33
28
|
|
|
34
29
|
## Commands
|
|
35
30
|
|
|
@@ -45,15 +40,10 @@ npx noiton --version # Show version
|
|
|
45
40
|
|
|
46
41
|
## How It Works
|
|
47
42
|
|
|
48
|
-
NOITON detects your installed agents
|
|
43
|
+
NOITON detects your installed agents by checking if the CLI binary is on PATH, then writes to each one's native config format:
|
|
49
44
|
|
|
50
45
|
- **OpenCode** → `~/.config/opencode/opencode.json` (provider block)
|
|
51
46
|
- **Hermes** → `~/.hermes/config.yaml` (model block) + `~/.hermes/.env` (API key)
|
|
52
|
-
- **OpenClaw** → `~/.openclaw/openclaw.json` (models.providers block)
|
|
53
|
-
- **Cline** → VS Code `settings.json` (cline.* keys)
|
|
54
|
-
- **Aider** → `~/.aider.conf.yml` (openai-api-base) + `~/.env` (OPENAI_API_KEY)
|
|
55
|
-
- **Kilo Code** → `~/.config/kilo/kilo.jsonc` (provider.openai-compatible block)
|
|
56
|
-
- **Kimi Code** → `~/.kimi-code/config.toml` (providers + models blocks)
|
|
57
47
|
|
|
58
48
|
Existing configurations are preserved — NOITON only adds/removes its own entries.
|
|
59
49
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "noiton",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "🐙 Universal AI Agent Connector - one key, many agents (OpenCode, Hermes
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "🐙 Universal AI Agent Connector - one key, many agents (OpenCode, Hermes)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"noiton",
|
|
7
7
|
"opencode",
|
|
8
8
|
"hermes",
|
|
9
|
-
"openclaw",
|
|
10
|
-
"cline",
|
|
11
|
-
"aider",
|
|
12
|
-
"kilo",
|
|
13
|
-
"kimi",
|
|
14
9
|
"ai",
|
|
15
10
|
"cli",
|
|
16
11
|
"agent",
|
package/src/index.js
CHANGED
|
@@ -23,11 +23,6 @@ ${c.bold("USAGE")}
|
|
|
23
23
|
${c.bold("AGENTES SUPORTADOS")}
|
|
24
24
|
${c.cyan("•")} OpenCode Programador IA (CLI)
|
|
25
25
|
${c.cyan("•")} Hermes Assistente de tarefas (CLI)
|
|
26
|
-
${c.cyan("•")} OpenClaw Agente open-source (CLI)
|
|
27
|
-
${c.cyan("•")} Cline Extensão VS Code
|
|
28
|
-
${c.cyan("•")} Aider Pair programmer de terminal
|
|
29
|
-
${c.cyan("•")} Kilo Code CLI + extensão VS Code
|
|
30
|
-
${c.cyan("•")} Kimi Code CLI + extensão VS Code
|
|
31
26
|
|
|
32
27
|
${c.bold("EXEMPLOS")}
|
|
33
28
|
npx noiton # configura pela primeira vez
|
package/src/lib/agents/hermes.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from "node:fs";
|
|
15
15
|
import { join, dirname } from "node:path";
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
|
+
import { execSync } from "node:child_process";
|
|
17
18
|
import YAML from "yaml";
|
|
18
19
|
import { BASE_URL } from "../../constants.js";
|
|
19
20
|
|
|
@@ -22,6 +23,19 @@ const CONFIG_PATH = join(HOME, ".hermes", "config.yaml");
|
|
|
22
23
|
const ENV_PATH = join(HOME, ".hermes", ".env");
|
|
23
24
|
const ENV_KEY = "NOITON_API_KEY";
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Check if hermes binary exists on PATH.
|
|
28
|
+
* @returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
function hasHermesBin() {
|
|
31
|
+
try {
|
|
32
|
+
execSync(`command -v hermes 2>/dev/null`, { stdio: "ignore" });
|
|
33
|
+
return true;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
function readYaml() {
|
|
26
40
|
if (!existsSync(CONFIG_PATH)) return { config: {}, path: CONFIG_PATH };
|
|
27
41
|
try {
|
|
@@ -75,8 +89,13 @@ export const hermesAgent = {
|
|
|
75
89
|
name: "Hermes",
|
|
76
90
|
supportsMultipleModels: false,
|
|
77
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Detect if Hermes is really installed.
|
|
94
|
+
* Requires the binary on PATH - config files alone aren't enough
|
|
95
|
+
* (could be leftover from uninstalled tools).
|
|
96
|
+
*/
|
|
78
97
|
detect() {
|
|
79
|
-
return
|
|
98
|
+
return hasHermesBin();
|
|
80
99
|
},
|
|
81
100
|
|
|
82
101
|
isConfigured() {
|
package/src/lib/agents/index.js
CHANGED
|
@@ -6,24 +6,38 @@
|
|
|
6
6
|
* configure, changeKey, refresh, remove }
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
9
11
|
import { opencodeAgent } from "./opencode.js";
|
|
10
12
|
import { hermesAgent } from "./hermes.js";
|
|
11
|
-
import { openclawAgent } from "./openclaw.js";
|
|
12
|
-
import { clineAgent } from "./cline.js";
|
|
13
|
-
import { aiderAgent } from "./aider.js";
|
|
14
|
-
import { kiloAgent } from "./kilo.js";
|
|
15
|
-
import { kimiAgent } from "./kimi.js";
|
|
16
13
|
|
|
17
14
|
export const REGISTRY = [
|
|
18
15
|
opencodeAgent,
|
|
19
16
|
hermesAgent,
|
|
20
|
-
openclawAgent,
|
|
21
|
-
clineAgent,
|
|
22
|
-
aiderAgent,
|
|
23
|
-
kiloAgent,
|
|
24
|
-
kimiAgent,
|
|
25
17
|
];
|
|
26
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Get version of a CLI binary (best-effort).
|
|
21
|
+
* @param {string} bin
|
|
22
|
+
* @returns {string|null}
|
|
23
|
+
*/
|
|
24
|
+
function getBinVersion(bin) {
|
|
25
|
+
for (const flag of ["--version", "-v"]) {
|
|
26
|
+
try {
|
|
27
|
+
const out = execSync(`${bin} ${flag} 2>/dev/null`, {
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
timeout: 5000,
|
|
30
|
+
}).trim();
|
|
31
|
+
const match = out.match(/(\d+\.\d+(?:\.\d+)?(?:-\w+)?)/);
|
|
32
|
+
if (match) return match[1];
|
|
33
|
+
return out.split("\n")[0];
|
|
34
|
+
} catch {
|
|
35
|
+
// try next flag
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
/**
|
|
28
42
|
* Get an agent adapter by ID.
|
|
29
43
|
* @param {string} id
|
|
@@ -35,10 +49,26 @@ export function getAgent(id) {
|
|
|
35
49
|
|
|
36
50
|
/**
|
|
37
51
|
* Get all agents that are detected as installed.
|
|
52
|
+
* Enriches each adapter with version and configFound info.
|
|
38
53
|
* @returns {Array}
|
|
39
54
|
*/
|
|
40
55
|
export function getDetectedAgents() {
|
|
41
|
-
return REGISTRY.filter((a) => a.detect())
|
|
56
|
+
return REGISTRY.filter((a) => a.detect()).map((a) => {
|
|
57
|
+
// Try to get version from binary
|
|
58
|
+
let version = null;
|
|
59
|
+
const bins = { opencode: "opencode", hermes: "hermes" };
|
|
60
|
+
const bin = bins[a.id];
|
|
61
|
+
if (bin) {
|
|
62
|
+
version = getBinVersion(bin);
|
|
63
|
+
}
|
|
64
|
+
// Check if config file exists
|
|
65
|
+
let configFound = false;
|
|
66
|
+
try {
|
|
67
|
+
const status = a.getStatus();
|
|
68
|
+
configFound = Boolean(status?.path && existsSync(status.path));
|
|
69
|
+
} catch {}
|
|
70
|
+
return { ...a, version, configFound };
|
|
71
|
+
});
|
|
42
72
|
}
|
|
43
73
|
|
|
44
74
|
/**
|
|
@@ -56,7 +86,7 @@ export function getConfiguredAgents() {
|
|
|
56
86
|
}
|
|
57
87
|
|
|
58
88
|
/**
|
|
59
|
-
* Get status of all
|
|
89
|
+
* Get status of all agents.
|
|
60
90
|
* @returns {Array}
|
|
61
91
|
*/
|
|
62
92
|
export function getAllStatuses() {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
9
9
|
import { join, dirname } from "node:path";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
11
12
|
import {
|
|
12
13
|
PROVIDER_ID,
|
|
13
14
|
PROVIDER_NAME,
|
|
@@ -18,6 +19,19 @@ import {
|
|
|
18
19
|
const HOME = homedir();
|
|
19
20
|
const XDG = process.env.XDG_CONFIG_HOME || join(HOME, ".config");
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Check if opencode binary exists on PATH.
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
function hasOpencodeBin() {
|
|
27
|
+
try {
|
|
28
|
+
execSync(`command -v opencode 2>/dev/null`, { stdio: "ignore" });
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
/** @returns {string} config path */
|
|
22
36
|
export function getConfigPath() {
|
|
23
37
|
if (process.env.OPENCODE_CONFIG) return process.env.OPENCODE_CONFIG;
|
|
@@ -116,8 +130,12 @@ export const opencodeAgent = {
|
|
|
116
130
|
name: "OpenCode",
|
|
117
131
|
supportsMultipleModels: true,
|
|
118
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Detect if OpenCode is really installed.
|
|
135
|
+
* Requires the binary on PATH - config files alone aren't enough.
|
|
136
|
+
*/
|
|
119
137
|
detect() {
|
|
120
|
-
return
|
|
138
|
+
return hasOpencodeBin();
|
|
121
139
|
},
|
|
122
140
|
|
|
123
141
|
isConfigured() {
|
package/src/lib/detect.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NOITON Agent Detection
|
|
3
3
|
*
|
|
4
|
-
* Detects which AI coding agents are installed on the system
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Returns a list of detected agents with metadata.
|
|
4
|
+
* Detects which AI coding agents are installed on the system.
|
|
5
|
+
* Detection requires the CLI binary to be on PATH (not just config files,
|
|
6
|
+
* which can be leftover from uninstalled tools).
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
9
|
import { existsSync } from "node:fs";
|
|
12
10
|
import { join } from "node:path";
|
|
13
11
|
import { homedir } from "node:os";
|
|
14
12
|
import { execSync } from "node:child_process";
|
|
15
|
-
import { c } from "../constants.js";
|
|
16
13
|
|
|
17
14
|
const HOME = homedir();
|
|
18
15
|
const XDG = process.env.XDG_CONFIG_HOME || join(HOME, ".config");
|
|
@@ -24,7 +21,7 @@ const XDG = process.env.XDG_CONFIG_HOME || join(HOME, ".config");
|
|
|
24
21
|
*/
|
|
25
22
|
function hasBin(bin) {
|
|
26
23
|
try {
|
|
27
|
-
execSync(`
|
|
24
|
+
execSync(`command -v ${bin} 2>/dev/null`, { stdio: "ignore" });
|
|
28
25
|
return true;
|
|
29
26
|
} catch {
|
|
30
27
|
return false;
|
|
@@ -44,7 +41,6 @@ function getBinVersion(bin) {
|
|
|
44
41
|
encoding: "utf8",
|
|
45
42
|
timeout: 5000,
|
|
46
43
|
}).trim();
|
|
47
|
-
// Extract first version-like token
|
|
48
44
|
const match = out.match(/(\d+\.\d+(?:\.\d+)?(?:-\w+)?)/);
|
|
49
45
|
if (match) return match[1];
|
|
50
46
|
return out.split("\n")[0];
|
|
@@ -76,8 +72,6 @@ function getNpmLatest(pkg) {
|
|
|
76
72
|
/**
|
|
77
73
|
* Compare two semver-like version strings.
|
|
78
74
|
* Returns: 1 if a > b, -1 if a < b, 0 if equal.
|
|
79
|
-
* @param {string} a
|
|
80
|
-
* @param {string} b
|
|
81
75
|
*/
|
|
82
76
|
function compareVersions(a, b) {
|
|
83
77
|
const pa = a.split(".").map(Number);
|
|
@@ -93,10 +87,6 @@ function compareVersions(a, b) {
|
|
|
93
87
|
|
|
94
88
|
// ============= AGENT DEFINITIONS =============
|
|
95
89
|
|
|
96
|
-
/**
|
|
97
|
-
* Registry of all supported agents.
|
|
98
|
-
* Each entry describes how to detect the agent and where its config lives.
|
|
99
|
-
*/
|
|
100
90
|
export const AGENT_REGISTRY = [
|
|
101
91
|
{
|
|
102
92
|
id: "opencode",
|
|
@@ -125,109 +115,42 @@ export const AGENT_REGISTRY = [
|
|
|
125
115
|
description: "Assistente que faz tarefas e se integra com apps",
|
|
126
116
|
useCase: "assistant",
|
|
127
117
|
},
|
|
128
|
-
{
|
|
129
|
-
id: "openclaw",
|
|
130
|
-
name: "OpenClaw",
|
|
131
|
-
type: "CLI",
|
|
132
|
-
bin: "openclaw",
|
|
133
|
-
npmPkg: null,
|
|
134
|
-
installMethod: null,
|
|
135
|
-
installCmd: null,
|
|
136
|
-
configPath: join(HOME, ".openclaw", "openclaw.json"),
|
|
137
|
-
supportsMultipleModels: true,
|
|
138
|
-
description: "Agente de IA open-source com providers customizáveis",
|
|
139
|
-
useCase: "programming",
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
id: "cline",
|
|
143
|
-
name: "Cline",
|
|
144
|
-
type: "VSCode Extension",
|
|
145
|
-
bin: null,
|
|
146
|
-
npmPkg: null,
|
|
147
|
-
installMethod: null,
|
|
148
|
-
installCmd: null,
|
|
149
|
-
configPath: join(XDG, "Code", "User", "settings.json"),
|
|
150
|
-
supportsMultipleModels: true,
|
|
151
|
-
description: "Extensão VS Code para programação com IA",
|
|
152
|
-
useCase: "programming",
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: "aider",
|
|
156
|
-
name: "Aider",
|
|
157
|
-
type: "CLI",
|
|
158
|
-
bin: "aider",
|
|
159
|
-
npmPkg: null,
|
|
160
|
-
installMethod: "pip",
|
|
161
|
-
installCmd: "pip install aider-chat",
|
|
162
|
-
configPath: join(HOME, ".aider.conf.yml"),
|
|
163
|
-
supportsMultipleModels: false,
|
|
164
|
-
description: "Pair programmer de terminal que edita código com você",
|
|
165
|
-
useCase: "programming",
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
id: "kilo",
|
|
169
|
-
name: "Kilo Code",
|
|
170
|
-
type: "CLI + VSCode",
|
|
171
|
-
bin: "kilo",
|
|
172
|
-
npmPkg: "@kilocode/cli",
|
|
173
|
-
installMethod: "npm",
|
|
174
|
-
installCmd: "npm install -g @kilocode/cli",
|
|
175
|
-
configPath: join(XDG, "kilo", "kilo.jsonc"),
|
|
176
|
-
supportsMultipleModels: true,
|
|
177
|
-
description: "Agente de IA com CLI e extensão VS Code",
|
|
178
|
-
useCase: "programming",
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
id: "kimi",
|
|
182
|
-
name: "Kimi Code",
|
|
183
|
-
type: "CLI + VSCode",
|
|
184
|
-
bin: "kimi",
|
|
185
|
-
npmPkg: null,
|
|
186
|
-
installMethod: null,
|
|
187
|
-
installCmd: null,
|
|
188
|
-
configPath: join(HOME, ".kimi-code", "config.toml"),
|
|
189
|
-
supportsMultipleModels: false,
|
|
190
|
-
description: "Agente de IA da Moonshot com CLI e VS Code",
|
|
191
|
-
useCase: "programming",
|
|
192
|
-
},
|
|
193
118
|
];
|
|
194
119
|
|
|
195
120
|
/**
|
|
196
121
|
* Detect all installed agents.
|
|
122
|
+
* An agent is "installed" only if its binary is on PATH.
|
|
123
|
+
* Config files alone are NOT enough (could be leftover from uninstalled tools).
|
|
197
124
|
* @returns {Array} detected agents with status info
|
|
198
125
|
*/
|
|
199
126
|
export function detectAgents() {
|
|
200
127
|
const detected = [];
|
|
201
128
|
for (const agent of AGENT_REGISTRY) {
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
215
|
-
}
|
|
129
|
+
if (!agent.bin) continue;
|
|
130
|
+
const binFound = hasBin(agent.bin);
|
|
131
|
+
if (!binFound) continue;
|
|
132
|
+
|
|
133
|
+
const version = getBinVersion(agent.bin);
|
|
134
|
+
detected.push({
|
|
135
|
+
...agent,
|
|
136
|
+
installed: true,
|
|
137
|
+
version,
|
|
138
|
+
binFound: true,
|
|
139
|
+
configFound: existsSync(agent.configPath),
|
|
140
|
+
});
|
|
216
141
|
}
|
|
217
142
|
return detected;
|
|
218
143
|
}
|
|
219
144
|
|
|
220
145
|
/**
|
|
221
|
-
* Check if a specific agent is installed.
|
|
146
|
+
* Check if a specific agent is installed (binary on PATH).
|
|
222
147
|
* @param {string} id
|
|
223
148
|
* @returns {boolean}
|
|
224
149
|
*/
|
|
225
150
|
export function isAgentInstalled(id) {
|
|
226
151
|
const agent = AGENT_REGISTRY.find((a) => a.id === id);
|
|
227
|
-
if (!agent) return false;
|
|
228
|
-
|
|
229
|
-
const configFound = existsSync(agent.configPath);
|
|
230
|
-
return binFound || configFound;
|
|
152
|
+
if (!agent || !agent.bin) return false;
|
|
153
|
+
return hasBin(agent.bin);
|
|
231
154
|
}
|
|
232
155
|
|
|
233
156
|
/**
|
|
@@ -240,13 +163,10 @@ export function getOpenCodeStatus() {
|
|
|
240
163
|
if (!agent) return null;
|
|
241
164
|
|
|
242
165
|
const binFound = hasBin("opencode");
|
|
243
|
-
|
|
244
|
-
const installed = binFound || configFound;
|
|
166
|
+
if (!binFound) return null;
|
|
245
167
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const version = binFound ? getBinVersion("opencode") : null;
|
|
249
|
-
const latest = binFound ? getNpmLatest("opencode-ai") : null;
|
|
168
|
+
const version = getBinVersion("opencode");
|
|
169
|
+
const latest = getNpmLatest("opencode-ai");
|
|
250
170
|
const updateAvailable =
|
|
251
171
|
version && latest ? compareVersions(latest, version) > 0 : false;
|
|
252
172
|
|
|
@@ -256,8 +176,29 @@ export function getOpenCodeStatus() {
|
|
|
256
176
|
version,
|
|
257
177
|
latest,
|
|
258
178
|
updateAvailable,
|
|
259
|
-
binFound,
|
|
260
|
-
configFound,
|
|
179
|
+
binFound: true,
|
|
180
|
+
configFound: existsSync(agent.configPath),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get the Hermes agent status.
|
|
186
|
+
* @returns {object|null}
|
|
187
|
+
*/
|
|
188
|
+
export function getHermesStatus() {
|
|
189
|
+
const agent = AGENT_REGISTRY.find((a) => a.id === "hermes");
|
|
190
|
+
if (!agent) return null;
|
|
191
|
+
|
|
192
|
+
const binFound = hasBin("hermes");
|
|
193
|
+
if (!binFound) return null;
|
|
194
|
+
|
|
195
|
+
const version = getBinVersion("hermes");
|
|
196
|
+
return {
|
|
197
|
+
...agent,
|
|
198
|
+
installed: true,
|
|
199
|
+
version,
|
|
200
|
+
binFound: true,
|
|
201
|
+
configFound: existsSync(agent.configPath),
|
|
261
202
|
};
|
|
262
203
|
}
|
|
263
204
|
|
|
@@ -275,7 +216,6 @@ export function getInstallableAgents() {
|
|
|
275
216
|
* @returns {object|null}
|
|
276
217
|
*/
|
|
277
218
|
export function getRecommendedAgent(useCase) {
|
|
278
|
-
// Prefer OpenCode for programming, Hermes for assistant
|
|
279
219
|
if (useCase === "programming") {
|
|
280
220
|
return AGENT_REGISTRY.find((a) => a.id === "opencode");
|
|
281
221
|
}
|
package/src/lib/install.js
CHANGED
|
@@ -80,8 +80,6 @@ export async function updateAgent(agentId) {
|
|
|
80
80
|
|
|
81
81
|
const updateCmds = {
|
|
82
82
|
opencode: "curl -fsSL https://opencode.ai/install | bash",
|
|
83
|
-
kilo: "npm update -g @kilocode/cli",
|
|
84
|
-
aider: "pip install --upgrade aider-chat",
|
|
85
83
|
hermes: "pip install --upgrade nous-hermes",
|
|
86
84
|
};
|
|
87
85
|
|
package/src/lib/agents/aider.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aider Agent Adapter
|
|
3
|
-
*
|
|
4
|
-
* Config: ~/.aider.conf.yml (YAML) + env vars
|
|
5
|
-
* Format:
|
|
6
|
-
* openai-api-base: <BASE_URL>
|
|
7
|
-
* model: <model-id>
|
|
8
|
-
* .env: OPENAI_API_KEY=<key>
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
12
|
-
import { join, dirname } from "node:path";
|
|
13
|
-
import { homedir } from "node:os";
|
|
14
|
-
import YAML from "yaml";
|
|
15
|
-
import { BASE_URL } from "../../constants.js";
|
|
16
|
-
|
|
17
|
-
const HOME = homedir();
|
|
18
|
-
const CONFIG_PATH = join(HOME, ".aider.conf.yml");
|
|
19
|
-
const ENV_PATH = join(HOME, ".env");
|
|
20
|
-
|
|
21
|
-
function readYaml() {
|
|
22
|
-
if (!existsSync(CONFIG_PATH)) return { config: {}, path: CONFIG_PATH };
|
|
23
|
-
try {
|
|
24
|
-
const raw = readFileSync(CONFIG_PATH, "utf8");
|
|
25
|
-
return { config: YAML.parse(raw) || {}, path: CONFIG_PATH };
|
|
26
|
-
} catch {
|
|
27
|
-
return { config: {}, path: CONFIG_PATH };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function writeYaml(config) {
|
|
32
|
-
if (!existsSync(dirname(CONFIG_PATH))) {
|
|
33
|
-
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
34
|
-
}
|
|
35
|
-
writeFileSync(CONFIG_PATH, YAML.stringify(config, { indent: 2 }), "utf8");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function readEnvFile() {
|
|
39
|
-
if (!existsSync(ENV_PATH)) return {};
|
|
40
|
-
const raw = readFileSync(ENV_PATH, "utf8");
|
|
41
|
-
const env = {};
|
|
42
|
-
for (const line of raw.split("\n")) {
|
|
43
|
-
const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
44
|
-
if (m) env[m[1]] = m[2];
|
|
45
|
-
}
|
|
46
|
-
return env;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function writeEnvFile(env) {
|
|
50
|
-
const lines = Object.entries(env).map(([k, v]) => `${k}=${v}`);
|
|
51
|
-
writeFileSync(ENV_PATH, lines.join("\n") + "\n", "utf8");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ============= ADAPTER INTERFACE =============
|
|
55
|
-
|
|
56
|
-
export const aiderAgent = {
|
|
57
|
-
id: "aider",
|
|
58
|
-
name: "Aider",
|
|
59
|
-
supportsMultipleModels: false,
|
|
60
|
-
|
|
61
|
-
detect() {
|
|
62
|
-
return existsSync(CONFIG_PATH);
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
isConfigured() {
|
|
66
|
-
const { config } = readYaml();
|
|
67
|
-
return Boolean(config?.["openai-api-base"] === BASE_URL);
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
getStatus() {
|
|
71
|
-
const { config, path } = readYaml();
|
|
72
|
-
const env = readEnvFile();
|
|
73
|
-
const apiKey = env.OPENAI_API_KEY;
|
|
74
|
-
const configured = config?.["openai-api-base"] === BASE_URL;
|
|
75
|
-
const models = config?.model ? [config.model] : [];
|
|
76
|
-
return {
|
|
77
|
-
configured,
|
|
78
|
-
path,
|
|
79
|
-
models,
|
|
80
|
-
apiKey: apiKey ? `${apiKey.slice(0, 6)}...${apiKey.slice(-4)}` : null,
|
|
81
|
-
envPath: ENV_PATH,
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
configure({ apiKey, models, primaryModel }) {
|
|
86
|
-
const { config } = readYaml();
|
|
87
|
-
config["openai-api-base"] = `${BASE_URL}/v1`;
|
|
88
|
-
config.model = primaryModel || models[0]?.id || "gpt-4";
|
|
89
|
-
config["weak-model"] = primaryModel || models[0]?.id || "gpt-4";
|
|
90
|
-
writeYaml(config);
|
|
91
|
-
|
|
92
|
-
// Set API key in .env
|
|
93
|
-
const env = readEnvFile();
|
|
94
|
-
env.OPENAI_API_KEY = apiKey;
|
|
95
|
-
writeEnvFile(env);
|
|
96
|
-
|
|
97
|
-
return { path: CONFIG_PATH, envPath: ENV_PATH, modelsCount: 1 };
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
changeKey(apiKey) {
|
|
101
|
-
if (!this.isConfigured()) {
|
|
102
|
-
throw new Error("NOITON não está configurado no Aider");
|
|
103
|
-
}
|
|
104
|
-
const env = readEnvFile();
|
|
105
|
-
env.OPENAI_API_KEY = apiKey;
|
|
106
|
-
writeEnvFile(env);
|
|
107
|
-
return { path: ENV_PATH };
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
refresh(models) {
|
|
111
|
-
const { config } = readYaml();
|
|
112
|
-
if (!config?.["openai-api-base"]) {
|
|
113
|
-
throw new Error("NOITON não está configurado no Aider");
|
|
114
|
-
}
|
|
115
|
-
if (models.length > 0) {
|
|
116
|
-
config.model = models[0].id;
|
|
117
|
-
config["weak-model"] = models[0].id;
|
|
118
|
-
}
|
|
119
|
-
writeYaml(config);
|
|
120
|
-
return { path: CONFIG_PATH, modelsCount: 1 };
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
remove() {
|
|
124
|
-
const { config } = readYaml();
|
|
125
|
-
let removed = false;
|
|
126
|
-
if (config?.["openai-api-base"] === BASE_URL) {
|
|
127
|
-
delete config["openai-api-base"];
|
|
128
|
-
delete config.model;
|
|
129
|
-
delete config["weak-model"];
|
|
130
|
-
removed = true;
|
|
131
|
-
writeYaml(config);
|
|
132
|
-
}
|
|
133
|
-
const env = readEnvFile();
|
|
134
|
-
if (env.OPENAI_API_KEY) {
|
|
135
|
-
delete env.OPENAI_API_KEY;
|
|
136
|
-
writeEnvFile(env);
|
|
137
|
-
removed = true;
|
|
138
|
-
}
|
|
139
|
-
return { path: CONFIG_PATH, envPath: ENV_PATH, removed };
|
|
140
|
-
},
|
|
141
|
-
};
|
package/src/lib/agents/cline.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cline Agent Adapter
|
|
3
|
-
*
|
|
4
|
-
* Config: VS Code settings.json with cline config block.
|
|
5
|
-
* Format (under `cline` key):
|
|
6
|
-
* "cline.provider": "openai",
|
|
7
|
-
* "cline.openAiBaseUrl": "<BASE_URL>",
|
|
8
|
-
* "cline.openAiApiKey": "<key>",
|
|
9
|
-
* "cline.openAiModelId": "<model>"
|
|
10
|
-
*
|
|
11
|
-
* VS Code stores multiple providers via cline.provider entries.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
15
|
-
import { join, dirname } from "node:path";
|
|
16
|
-
import { homedir } from "node:os";
|
|
17
|
-
import JSON5 from "json5";
|
|
18
|
-
import { BASE_URL, PROVIDER_ID } from "../../constants.js";
|
|
19
|
-
|
|
20
|
-
const HOME = homedir();
|
|
21
|
-
const XDG = process.env.XDG_CONFIG_HOME || join(HOME, ".config");
|
|
22
|
-
const CONFIG_PATH = join(XDG, "Code", "User", "settings.json");
|
|
23
|
-
|
|
24
|
-
function readConfig() {
|
|
25
|
-
if (!existsSync(CONFIG_PATH)) return { config: {}, path: CONFIG_PATH };
|
|
26
|
-
try {
|
|
27
|
-
// VS Code settings support JSON with comments (JSONC)
|
|
28
|
-
const raw = readFileSync(CONFIG_PATH, "utf8");
|
|
29
|
-
const cleaned = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
30
|
-
return { config: JSON5.parse(cleaned), path: CONFIG_PATH };
|
|
31
|
-
} catch {
|
|
32
|
-
return { config: {}, path: CONFIG_PATH };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function writeConfig(config) {
|
|
37
|
-
if (!existsSync(dirname(CONFIG_PATH))) {
|
|
38
|
-
mkdirSync(dirname(dirname(CONFIG_PATH)), { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ============= ADAPTER INTERFACE =============
|
|
44
|
-
|
|
45
|
-
export const clineAgent = {
|
|
46
|
-
id: "cline",
|
|
47
|
-
name: "Cline",
|
|
48
|
-
supportsMultipleModels: true,
|
|
49
|
-
|
|
50
|
-
detect() {
|
|
51
|
-
return existsSync(CONFIG_PATH);
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
isConfigured() {
|
|
55
|
-
const { config } = readConfig();
|
|
56
|
-
const result = Boolean(
|
|
57
|
-
config?.["cline.openAiBaseUrl"] === BASE_URL ||
|
|
58
|
-
config?.["cline.apiProvider"] === "openai" && config?.["cline.openAiApiKey"]
|
|
59
|
-
);
|
|
60
|
-
return result;
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
getStatus() {
|
|
64
|
-
const { config, path } = readConfig();
|
|
65
|
-
const baseUrl = config?.["cline.openAiBaseUrl"];
|
|
66
|
-
const apiKey = config?.["cline.openAiApiKey"];
|
|
67
|
-
const modelId = config?.["cline.openAiModelId"];
|
|
68
|
-
const configured = Boolean(baseUrl === BASE_URL || (apiKey && baseUrl));
|
|
69
|
-
|
|
70
|
-
const models = modelId ? [modelId] : [];
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
configured,
|
|
74
|
-
path,
|
|
75
|
-
models,
|
|
76
|
-
apiKey: apiKey ? `${apiKey.slice(0, 6)}...${apiKey.slice(-4)}` : null,
|
|
77
|
-
};
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
configure({ apiKey, models, primaryModel }) {
|
|
81
|
-
const { config } = readConfig();
|
|
82
|
-
config["cline.apiProvider"] = "openai";
|
|
83
|
-
config["cline.openAiBaseUrl"] = `${BASE_URL}/v1`;
|
|
84
|
-
config["cline.openAiApiKey"] = apiKey;
|
|
85
|
-
config["cline.openAiModelId"] = primaryModel || models[0]?.id;
|
|
86
|
-
// Save list of available models as a JSON array string
|
|
87
|
-
config["cline.noitonModels"] = JSON.stringify(models.map((m) => m.id));
|
|
88
|
-
writeConfig(config);
|
|
89
|
-
return { path: CONFIG_PATH, modelsCount: 1 };
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
changeKey(apiKey) {
|
|
93
|
-
const { config } = readConfig();
|
|
94
|
-
if (!config?.["cline.openAiApiKey"]) {
|
|
95
|
-
throw new Error("NOITON não está configurado no Cline");
|
|
96
|
-
}
|
|
97
|
-
config["cline.openAiApiKey"] = apiKey;
|
|
98
|
-
writeConfig(config);
|
|
99
|
-
return { path: CONFIG_PATH };
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
refresh(models) {
|
|
103
|
-
const { config } = readConfig();
|
|
104
|
-
if (!config?.["cline.openAiBaseUrl"]) {
|
|
105
|
-
throw new Error("NOITON não está configurado no Cline");
|
|
106
|
-
}
|
|
107
|
-
config["cline.openAiModelId"] = models[0]?.id || config["cline.openAiModelId"];
|
|
108
|
-
config["cline.noitonModels"] = JSON.stringify(models.map((m) => m.id));
|
|
109
|
-
writeConfig(config);
|
|
110
|
-
return { path: CONFIG_PATH, modelsCount: 1 };
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
remove() {
|
|
114
|
-
const { config } = readConfig();
|
|
115
|
-
let removed = false;
|
|
116
|
-
if (config?.["cline.openAiBaseUrl"] === BASE_URL ||
|
|
117
|
-
config?.["cline.openAiApiKey"]) {
|
|
118
|
-
delete config["cline.apiProvider"];
|
|
119
|
-
delete config["cline.openAiBaseUrl"];
|
|
120
|
-
delete config["cline.openAiApiKey"];
|
|
121
|
-
delete config["cline.openAiModelId"];
|
|
122
|
-
delete config["cline.noitonModels"];
|
|
123
|
-
removed = true;
|
|
124
|
-
}
|
|
125
|
-
if (removed) writeConfig(config);
|
|
126
|
-
return { path: CONFIG_PATH, removed };
|
|
127
|
-
},
|
|
128
|
-
};
|
package/src/lib/agents/kilo.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Kilo Code Agent Adapter
|
|
3
|
-
*
|
|
4
|
-
* Config: ~/.config/kilo/kilo.jsonc (JSONC)
|
|
5
|
-
* Format (under `provider.openai-compatible`):
|
|
6
|
-
* model: openai-compatible/<model-id>
|
|
7
|
-
* provider.openai-compatible.options.apiKey
|
|
8
|
-
* provider.openai-compatible.options.baseURL
|
|
9
|
-
* provider.openai-compatible.models.<id>.limit.context/output
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
13
|
-
import { join, dirname } from "node:path";
|
|
14
|
-
import { homedir } from "node:os";
|
|
15
|
-
import JSON5 from "json5";
|
|
16
|
-
import { BASE_URL } from "../../constants.js";
|
|
17
|
-
|
|
18
|
-
const HOME = homedir();
|
|
19
|
-
const XDG = process.env.XDG_CONFIG_HOME || join(HOME, ".config");
|
|
20
|
-
const CONFIG_PATH = join(XDG, "kilo", "kilo.jsonc");
|
|
21
|
-
const PROVIDER_ID = "openai-compatible";
|
|
22
|
-
|
|
23
|
-
function readConfig() {
|
|
24
|
-
if (!existsSync(CONFIG_PATH)) return { config: {}, path: CONFIG_PATH };
|
|
25
|
-
try {
|
|
26
|
-
const raw = readFileSync(CONFIG_PATH, "utf8");
|
|
27
|
-
// Strip comments (JSONC)
|
|
28
|
-
const cleaned = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
29
|
-
return { config: JSON5.parse(cleaned), path: CONFIG_PATH };
|
|
30
|
-
} catch {
|
|
31
|
-
return { config: {}, path: CONFIG_PATH };
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function writeConfig(config) {
|
|
36
|
-
if (!existsSync(dirname(CONFIG_PATH))) {
|
|
37
|
-
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
38
|
-
}
|
|
39
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function buildModels(models) {
|
|
43
|
-
const obj = {};
|
|
44
|
-
for (const m of models) {
|
|
45
|
-
if (!m.id) continue;
|
|
46
|
-
obj[m.id] = {
|
|
47
|
-
name: prettifyName(m.id),
|
|
48
|
-
tool_call: true,
|
|
49
|
-
limit: { context: m.context_window || 32768, output: 4096 },
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return obj;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function prettifyName(id) {
|
|
56
|
-
return id
|
|
57
|
-
.replace(/[@/]/g, " ")
|
|
58
|
-
.split(/[-_]/)
|
|
59
|
-
.filter(Boolean)
|
|
60
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
61
|
-
.join(" ");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ============= ADAPTER INTERFACE =============
|
|
65
|
-
|
|
66
|
-
export const kiloAgent = {
|
|
67
|
-
id: "kilo",
|
|
68
|
-
name: "Kilo Code",
|
|
69
|
-
supportsMultipleModels: true,
|
|
70
|
-
|
|
71
|
-
detect() {
|
|
72
|
-
return existsSync(CONFIG_PATH);
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
isConfigured() {
|
|
76
|
-
const { config } = readConfig();
|
|
77
|
-
return Boolean(config?.provider?.[PROVIDER_ID]?.options?.baseURL === BASE_URL);
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
getStatus() {
|
|
81
|
-
const { config, path } = readConfig();
|
|
82
|
-
const provider = config?.provider?.[PROVIDER_ID];
|
|
83
|
-
if (!provider) return { configured: false, path, models: [], apiKey: null };
|
|
84
|
-
const apiKey = provider.options?.apiKey;
|
|
85
|
-
const models = provider.models ? Object.keys(provider.models) : [];
|
|
86
|
-
return {
|
|
87
|
-
configured: provider.options?.baseURL === BASE_URL,
|
|
88
|
-
path,
|
|
89
|
-
models,
|
|
90
|
-
apiKey: apiKey ? (apiKey.startsWith("{env:") ? "(env var)" : `${apiKey.slice(0, 6)}...${apiKey.slice(-4)}`) : null,
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
configure({ apiKey, models, primaryModel }) {
|
|
95
|
-
const { config } = readConfig();
|
|
96
|
-
config.model = `${PROVIDER_ID}/${primaryModel || models[0]?.id}`;
|
|
97
|
-
config.provider = config.provider || {};
|
|
98
|
-
config.provider[PROVIDER_ID] = {
|
|
99
|
-
options: {
|
|
100
|
-
apiKey,
|
|
101
|
-
baseURL: `${BASE_URL}/v1`,
|
|
102
|
-
},
|
|
103
|
-
models: buildModels(models),
|
|
104
|
-
};
|
|
105
|
-
writeConfig(config);
|
|
106
|
-
return { path: CONFIG_PATH, modelsCount: models.length };
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
changeKey(apiKey) {
|
|
110
|
-
const { config } = readConfig();
|
|
111
|
-
if (!config?.provider?.[PROVIDER_ID]) {
|
|
112
|
-
throw new Error("NOITON não está configurado no Kilo Code");
|
|
113
|
-
}
|
|
114
|
-
config.provider[PROVIDER_ID].options.apiKey = apiKey;
|
|
115
|
-
writeConfig(config);
|
|
116
|
-
return { path: CONFIG_PATH };
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
refresh(models) {
|
|
120
|
-
const { config } = readConfig();
|
|
121
|
-
if (!config?.provider?.[PROVIDER_ID]) {
|
|
122
|
-
throw new Error("NOITON não está configurado no Kilo Code");
|
|
123
|
-
}
|
|
124
|
-
config.provider[PROVIDER_ID].models = buildModels(models);
|
|
125
|
-
writeConfig(config);
|
|
126
|
-
return { path: CONFIG_PATH, modelsCount: models.length };
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
remove() {
|
|
130
|
-
const { config } = readConfig();
|
|
131
|
-
let removed = false;
|
|
132
|
-
if (config?.provider?.[PROVIDER_ID]) {
|
|
133
|
-
delete config.provider[PROVIDER_ID];
|
|
134
|
-
delete config.model;
|
|
135
|
-
removed = true;
|
|
136
|
-
writeConfig(config);
|
|
137
|
-
}
|
|
138
|
-
return { path: CONFIG_PATH, removed };
|
|
139
|
-
},
|
|
140
|
-
};
|
package/src/lib/agents/kimi.js
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Kimi Code Agent Adapter
|
|
3
|
-
*
|
|
4
|
-
* Config: ~/.kimi-code/config.toml (TOML)
|
|
5
|
-
* Format:
|
|
6
|
-
* [providers.noiton]
|
|
7
|
-
* type = "openai"
|
|
8
|
-
* base_url = "<BASE_URL>/v1"
|
|
9
|
-
* api_key = "<key>"
|
|
10
|
-
*
|
|
11
|
-
* [models."<id>"]
|
|
12
|
-
* provider = "noiton"
|
|
13
|
-
* model = "<id>"
|
|
14
|
-
* max_context_size = <ctx>
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
18
|
-
import { join, dirname } from "node:path";
|
|
19
|
-
import { homedir } from "node:os";
|
|
20
|
-
import * as TOML from "@iarna/toml";
|
|
21
|
-
import { BASE_URL, PROVIDER_ID } from "../../constants.js";
|
|
22
|
-
|
|
23
|
-
const HOME = homedir();
|
|
24
|
-
const CONFIG_PATH = join(HOME, ".kimi-code", "config.toml");
|
|
25
|
-
|
|
26
|
-
function readConfig() {
|
|
27
|
-
if (!existsSync(CONFIG_PATH)) return { config: {}, path: CONFIG_PATH };
|
|
28
|
-
try {
|
|
29
|
-
const raw = readFileSync(CONFIG_PATH, "utf8");
|
|
30
|
-
return { config: TOML.parse(raw), path: CONFIG_PATH };
|
|
31
|
-
} catch {
|
|
32
|
-
return { config: {}, path: CONFIG_PATH };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function writeConfig(config) {
|
|
37
|
-
if (!existsSync(dirname(CONFIG_PATH))) {
|
|
38
|
-
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
writeFileSync(CONFIG_PATH, TOML.stringify(config), "utf8");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ============= ADAPTER INTERFACE =============
|
|
44
|
-
|
|
45
|
-
export const kimiAgent = {
|
|
46
|
-
id: "kimi",
|
|
47
|
-
name: "Kimi Code",
|
|
48
|
-
supportsMultipleModels: false,
|
|
49
|
-
|
|
50
|
-
detect() {
|
|
51
|
-
return existsSync(CONFIG_PATH);
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
isConfigured() {
|
|
55
|
-
const { config } = readConfig();
|
|
56
|
-
return Boolean(config?.providers?.[PROVIDER_ID]);
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
getStatus() {
|
|
60
|
-
const { config, path } = readConfig();
|
|
61
|
-
const provider = config?.providers?.[PROVIDER_ID];
|
|
62
|
-
if (!provider) return { configured: false, path, models: [], apiKey: null };
|
|
63
|
-
const apiKey = provider.api_key;
|
|
64
|
-
const models = config.models
|
|
65
|
-
? Object.entries(config.models)
|
|
66
|
-
.filter(([, v]) => v.provider === PROVIDER_ID)
|
|
67
|
-
.map(([k]) => k)
|
|
68
|
-
: [];
|
|
69
|
-
return {
|
|
70
|
-
configured: true,
|
|
71
|
-
path,
|
|
72
|
-
models,
|
|
73
|
-
apiKey: apiKey ? `${apiKey.slice(0, 6)}...${apiKey.slice(-4)}` : null,
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
configure({ apiKey, models, primaryModel }) {
|
|
78
|
-
const { config } = readConfig();
|
|
79
|
-
config.providers = config.providers || {};
|
|
80
|
-
config.providers[PROVIDER_ID] = {
|
|
81
|
-
type: "openai",
|
|
82
|
-
base_url: `${BASE_URL}/v1`,
|
|
83
|
-
api_key: apiKey,
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
config.models = config.models || {};
|
|
87
|
-
const modelId = primaryModel || models[0]?.id || "glm-5.2";
|
|
88
|
-
config.models[modelId] = {
|
|
89
|
-
provider: PROVIDER_ID,
|
|
90
|
-
model: modelId,
|
|
91
|
-
max_context_size: models[0]?.context_window || 32768,
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
writeConfig(config);
|
|
95
|
-
return { path: CONFIG_PATH, modelsCount: 1 };
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
changeKey(apiKey) {
|
|
99
|
-
const { config } = readConfig();
|
|
100
|
-
if (!config?.providers?.[PROVIDER_ID]) {
|
|
101
|
-
throw new Error("NOITON não está configurado no Kimi Code");
|
|
102
|
-
}
|
|
103
|
-
config.providers[PROVIDER_ID].api_key = apiKey;
|
|
104
|
-
writeConfig(config);
|
|
105
|
-
return { path: CONFIG_PATH };
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
refresh(models) {
|
|
109
|
-
const { config } = readConfig();
|
|
110
|
-
if (!config?.providers?.[PROVIDER_ID]) {
|
|
111
|
-
throw new Error("NOITON não está configurado no Kimi Code");
|
|
112
|
-
}
|
|
113
|
-
// Update context size if model exists
|
|
114
|
-
const modelId = Object.keys(config.models || {}).find(
|
|
115
|
-
(k) => config.models[k]?.provider === PROVIDER_ID
|
|
116
|
-
);
|
|
117
|
-
if (modelId && models[0]?.context_window) {
|
|
118
|
-
config.models[modelId].max_context_size = models[0].context_window;
|
|
119
|
-
}
|
|
120
|
-
writeConfig(config);
|
|
121
|
-
return { path: CONFIG_PATH, modelsCount: 1 };
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
remove() {
|
|
125
|
-
const { config } = readConfig();
|
|
126
|
-
let removed = false;
|
|
127
|
-
if (config?.providers?.[PROVIDER_ID]) {
|
|
128
|
-
delete config.providers[PROVIDER_ID];
|
|
129
|
-
removed = true;
|
|
130
|
-
}
|
|
131
|
-
if (config?.models) {
|
|
132
|
-
for (const [k, v] of Object.entries(config.models)) {
|
|
133
|
-
if (v.provider === PROVIDER_ID) {
|
|
134
|
-
delete config.models[k];
|
|
135
|
-
removed = true;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// Clean up empty top-level tables
|
|
140
|
-
if (config.providers && Object.keys(config.providers).length === 0) {
|
|
141
|
-
delete config.providers;
|
|
142
|
-
}
|
|
143
|
-
if (config.models && Object.keys(config.models).length === 0) {
|
|
144
|
-
delete config.models;
|
|
145
|
-
}
|
|
146
|
-
if (removed) writeConfig(config);
|
|
147
|
-
return { path: CONFIG_PATH, removed };
|
|
148
|
-
},
|
|
149
|
-
};
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenClaw Agent Adapter
|
|
3
|
-
*
|
|
4
|
-
* Config: ~/.openclaw/openclaw.json (JSON5)
|
|
5
|
-
* Format:
|
|
6
|
-
* models.providers.noiton = {
|
|
7
|
-
* baseUrl, apiKey, api: "openai-completions", models: [...]
|
|
8
|
-
* }
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
12
|
-
import { join, dirname } from "node:path";
|
|
13
|
-
import { homedir } from "node:os";
|
|
14
|
-
import JSON5 from "json5";
|
|
15
|
-
import { BASE_URL, PROVIDER_ID, PROVIDER_NAME } from "../../constants.js";
|
|
16
|
-
|
|
17
|
-
const HOME = homedir();
|
|
18
|
-
const CONFIG_PATH = join(HOME, ".openclaw", "openclaw.json");
|
|
19
|
-
|
|
20
|
-
function readConfig() {
|
|
21
|
-
if (!existsSync(CONFIG_PATH)) return { config: {}, path: CONFIG_PATH };
|
|
22
|
-
try {
|
|
23
|
-
const raw = readFileSync(CONFIG_PATH, "utf8");
|
|
24
|
-
return { config: JSON5.parse(raw), path: CONFIG_PATH };
|
|
25
|
-
} catch {
|
|
26
|
-
return { config: {}, path: CONFIG_PATH };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function writeConfig(config) {
|
|
31
|
-
if (!existsSync(dirname(CONFIG_PATH))) {
|
|
32
|
-
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
33
|
-
}
|
|
34
|
-
// Write as standard JSON (OpenClaw accepts JSON5 which is a superset)
|
|
35
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ============= ADAPTER INTERFACE =============
|
|
39
|
-
|
|
40
|
-
export const openclawAgent = {
|
|
41
|
-
id: "openclaw",
|
|
42
|
-
name: "OpenClaw",
|
|
43
|
-
supportsMultipleModels: true,
|
|
44
|
-
|
|
45
|
-
detect() {
|
|
46
|
-
return existsSync(CONFIG_PATH);
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
isConfigured() {
|
|
50
|
-
const { config } = readConfig();
|
|
51
|
-
return Boolean(config?.models?.providers?.[PROVIDER_ID]);
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
getStatus() {
|
|
55
|
-
const { config, path } = readConfig();
|
|
56
|
-
const provider = config?.models?.providers?.[PROVIDER_ID];
|
|
57
|
-
if (!provider) return { configured: false, path, models: [], apiKey: null };
|
|
58
|
-
const models = (provider.models || []).map((m) => m.id);
|
|
59
|
-
return {
|
|
60
|
-
configured: true,
|
|
61
|
-
path,
|
|
62
|
-
models,
|
|
63
|
-
apiKey: provider.apiKey,
|
|
64
|
-
};
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
configure({ apiKey, models, primaryModel }) {
|
|
68
|
-
const { config } = readConfig();
|
|
69
|
-
config.models = config.models || {};
|
|
70
|
-
config.models.mode = config.models.mode || "merge";
|
|
71
|
-
config.models.providers = config.models.providers || {};
|
|
72
|
-
|
|
73
|
-
config.models.providers[PROVIDER_ID] = {
|
|
74
|
-
baseUrl: `${BASE_URL}/v1`,
|
|
75
|
-
apiKey: `\${NOITON_API_KEY}`,
|
|
76
|
-
api: "openai-completions",
|
|
77
|
-
models: models.map((m) => ({
|
|
78
|
-
id: m.id,
|
|
79
|
-
name: prettifyName(m.id),
|
|
80
|
-
reasoning: false,
|
|
81
|
-
input: ["text"],
|
|
82
|
-
contextWindow: m.context_window || 32768,
|
|
83
|
-
maxTokens: 4096,
|
|
84
|
-
})),
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Set env for apiKey substitution
|
|
88
|
-
config.env = config.env || {};
|
|
89
|
-
config.env.NOITON_API_KEY = apiKey;
|
|
90
|
-
|
|
91
|
-
// Set default model if missing
|
|
92
|
-
if (!config.agents) config.agents = {};
|
|
93
|
-
if (!config.agents.defaults) config.agents.defaults = {};
|
|
94
|
-
if (!config.agents.defaults.model) {
|
|
95
|
-
config.agents.defaults.model = {
|
|
96
|
-
primary: `${PROVIDER_ID}/${primaryModel || models[0]?.id}`,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
writeConfig(config);
|
|
101
|
-
return { path: CONFIG_PATH, modelsCount: models.length };
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
changeKey(apiKey) {
|
|
105
|
-
const { config } = readConfig();
|
|
106
|
-
if (!config?.models?.providers?.[PROVIDER_ID]) {
|
|
107
|
-
throw new Error("NOITON não está configurado no OpenClaw");
|
|
108
|
-
}
|
|
109
|
-
config.env = config.env || {};
|
|
110
|
-
config.env.NOITON_API_KEY = apiKey;
|
|
111
|
-
writeConfig(config);
|
|
112
|
-
return { path: CONFIG_PATH };
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
refresh(models) {
|
|
116
|
-
const { config } = readConfig();
|
|
117
|
-
if (!config?.models?.providers?.[PROVIDER_ID]) {
|
|
118
|
-
throw new Error("NOITON não está configurado no OpenClaw");
|
|
119
|
-
}
|
|
120
|
-
config.models.providers[PROVIDER_ID].models = models.map((m) => ({
|
|
121
|
-
id: m.id,
|
|
122
|
-
name: prettifyName(m.id),
|
|
123
|
-
reasoning: false,
|
|
124
|
-
input: ["text"],
|
|
125
|
-
contextWindow: m.context_window || 32768,
|
|
126
|
-
maxTokens: 4096,
|
|
127
|
-
}));
|
|
128
|
-
writeConfig(config);
|
|
129
|
-
return { path: CONFIG_PATH, modelsCount: models.length };
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
remove() {
|
|
133
|
-
const { config } = readConfig();
|
|
134
|
-
let removed = false;
|
|
135
|
-
if (config?.models?.providers?.[PROVIDER_ID]) {
|
|
136
|
-
delete config.models.providers[PROVIDER_ID];
|
|
137
|
-
removed = true;
|
|
138
|
-
}
|
|
139
|
-
if (config?.env?.NOITON_API_KEY) {
|
|
140
|
-
delete config.env.NOITON_API_KEY;
|
|
141
|
-
removed = true;
|
|
142
|
-
}
|
|
143
|
-
if (removed) writeConfig(config);
|
|
144
|
-
return { path: CONFIG_PATH, removed };
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
function prettifyName(id) {
|
|
149
|
-
return id
|
|
150
|
-
.replace(/[@/]/g, " ")
|
|
151
|
-
.split(/[-_]/)
|
|
152
|
-
.filter(Boolean)
|
|
153
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
154
|
-
.join(" ");
|
|
155
|
-
}
|