klaudiak 2.1.88 → 2.1.89
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/Makefile +22 -1
- package/README.md +35 -0
- package/klaudiak-help.md +14 -0
- package/klaudiak.js +394 -21
- package/package.json +1 -1
- package/user-manual.md +0 -331
package/Makefile
CHANGED
|
@@ -160,4 +160,25 @@ publish:
|
|
|
160
160
|
trap 'rm -f "$$tmp_npmrc"' EXIT; \
|
|
161
161
|
printf '//registry.npmjs.org/:_authToken=%s\n' "$$token" > "$$tmp_npmrc"; \
|
|
162
162
|
printf 'always-auth=true\n' >> "$$tmp_npmrc"; \
|
|
163
|
-
AUTHORIZED=1 NPM_CONFIG_USERCONFIG="$$tmp_npmrc" npm publish --access public
|
|
163
|
+
AUTHORIZED=1 NPM_CONFIG_USERCONFIG="$$tmp_npmrc" npm publish --access public
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
.PHONY: publish-update
|
|
167
|
+
publish-update:
|
|
168
|
+
@set -euo pipefail; \
|
|
169
|
+
npm version patch --no-git-tag-version; \
|
|
170
|
+
$(MAKE) publish
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
.PHONY: publish-update-minor
|
|
174
|
+
publish-update-minor:
|
|
175
|
+
@set -euo pipefail; \
|
|
176
|
+
npm version minor --no-git-tag-version; \
|
|
177
|
+
$(MAKE) publish
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
.PHONY: publish-update-major
|
|
181
|
+
publish-update-major:
|
|
182
|
+
@set -euo pipefail; \
|
|
183
|
+
npm version major --no-git-tag-version; \
|
|
184
|
+
$(MAKE) publish
|
package/README.md
CHANGED
|
@@ -28,12 +28,16 @@ npx klaudiak
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
klaudiak
|
|
31
|
+
klaudiak --profile=<name>
|
|
31
32
|
klaudiak --port=<port>
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
- `klaudiak` starts the coding agent.
|
|
36
|
+
- `--profile=<name>` loads settings from `~/.klaudiak` profile `<name>`.
|
|
35
37
|
- `--port=<port>` sets `ANTHROPIC_BASE_URL=http://localhost:<port>` for this run when not already set.
|
|
36
38
|
|
|
39
|
+
When no profile flag is passed, Klaudiak always uses the `default` profile.
|
|
40
|
+
|
|
37
41
|
### Start proxy
|
|
38
42
|
|
|
39
43
|
```bash
|
|
@@ -50,6 +54,37 @@ klaudiak proxy openai/gpt-4o-mini key sk-or-v1-... --port=9090
|
|
|
50
54
|
- `<apiKey>` is passed to `OPENROUTER_API_KEY`.
|
|
51
55
|
- `--port=<port>` is passed to `PROXY_PORT`.
|
|
52
56
|
|
|
57
|
+
You can also run proxy from a profile (no model/key arguments) when profile `type` is `proxy` and includes `openrouterModel` + `openrouterApiKey`:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
klaudiak proxy --profile=<name>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Global defaults profiles
|
|
64
|
+
|
|
65
|
+
Klaudiak stores global profile defaults in `~/.klaudiak` (JSON). The file is auto-created whenever `klaudiak` runs.
|
|
66
|
+
|
|
67
|
+
Common commands:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
klaudiak defaults list
|
|
71
|
+
klaudiak defaults create gemini
|
|
72
|
+
klaudiak defaults set gemini type proxy
|
|
73
|
+
klaudiak defaults set gemini openrouterModel google/gemini-2.5-pro
|
|
74
|
+
klaudiak defaults set gemini openrouterApiKey sk-or-v1-...
|
|
75
|
+
klaudiak defaults show gemini
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Supported fields:
|
|
79
|
+
|
|
80
|
+
- `type` (`agent` or `proxy`)
|
|
81
|
+
- `anthropicBaseUrl`
|
|
82
|
+
- `anthropicApiKey`
|
|
83
|
+
- `anthropicModel`
|
|
84
|
+
- `proxyPort`
|
|
85
|
+
- `openrouterModel`
|
|
86
|
+
- `openrouterApiKey`
|
|
87
|
+
|
|
53
88
|
## How It Works
|
|
54
89
|
|
|
55
90
|
```text
|
package/klaudiak-help.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Klaudia Kode CLI
|
|
2
|
+
|
|
3
|
+
install using
|
|
4
|
+
$ npm install -g klaudiak
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
klaudiak
|
|
8
|
+
klaudiak --port=<port>
|
|
9
|
+
klaudiak proxy <model> key <apiKey> --port=<port>
|
|
10
|
+
|
|
11
|
+
Examples:
|
|
12
|
+
klaudiak
|
|
13
|
+
klaudiak --port=10000
|
|
14
|
+
klaudiak proxy deepseek/deepseek-chat-v3-0324 key sk-or-v1-... --port=10000
|
package/klaudiak.js
CHANGED
|
@@ -1,43 +1,215 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
5
|
+
import { homedir } from 'os';
|
|
4
6
|
import { fileURLToPath } from 'url';
|
|
5
7
|
import { dirname, join } from 'path';
|
|
6
8
|
|
|
7
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
10
|
const __dirname = dirname(__filename);
|
|
11
|
+
const DEFAULTS_PATH = join(homedir(), '.klaudiak');
|
|
12
|
+
const DEFAULT_PROFILE_NAME = 'default';
|
|
13
|
+
const PROFILE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
14
|
+
const PROFILE_FIELDS = new Set([
|
|
15
|
+
'type',
|
|
16
|
+
'anthropicBaseUrl',
|
|
17
|
+
'anthropicApiKey',
|
|
18
|
+
'anthropicModel',
|
|
19
|
+
'proxyPort',
|
|
20
|
+
'openrouterModel',
|
|
21
|
+
'openrouterApiKey',
|
|
22
|
+
]);
|
|
9
23
|
|
|
10
24
|
const args = process.argv.slice(2);
|
|
11
25
|
|
|
26
|
+
function defaultProfile() {
|
|
27
|
+
return {
|
|
28
|
+
type: 'agent',
|
|
29
|
+
anthropicBaseUrl: 'http://localhost:9090',
|
|
30
|
+
anthropicApiKey: '',
|
|
31
|
+
anthropicModel: '',
|
|
32
|
+
proxyPort: 9090,
|
|
33
|
+
openrouterModel: '',
|
|
34
|
+
openrouterApiKey: '',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function defaultDefaultsConfig() {
|
|
39
|
+
return {
|
|
40
|
+
version: 1,
|
|
41
|
+
profiles: {
|
|
42
|
+
[DEFAULT_PROFILE_NAME]: defaultProfile(),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function redactedProfile(profile) {
|
|
48
|
+
return {
|
|
49
|
+
...profile,
|
|
50
|
+
anthropicApiKey: profile.anthropicApiKey ? '***' : '',
|
|
51
|
+
openrouterApiKey: profile.openrouterApiKey ? '***' : '',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parsePortValue(raw, flagName = '--port') {
|
|
56
|
+
const port = Number(raw);
|
|
57
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
58
|
+
console.error(`Error: invalid ${flagName} value "${raw}". Expected integer in range 1-65535.`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
return port;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseTypedValue(field, value) {
|
|
65
|
+
if (field === 'proxyPort') {
|
|
66
|
+
return parsePortValue(value, 'proxyPort');
|
|
67
|
+
}
|
|
68
|
+
if (field === 'type') {
|
|
69
|
+
const normalized = String(value).toLowerCase();
|
|
70
|
+
if (normalized !== 'agent' && normalized !== 'proxy') {
|
|
71
|
+
console.error('Error: field "type" must be one of: agent, proxy');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
return normalized;
|
|
75
|
+
}
|
|
76
|
+
return String(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function sanitizeProfile(raw) {
|
|
80
|
+
const profile = defaultProfile();
|
|
81
|
+
if (!raw || typeof raw !== 'object') return profile;
|
|
82
|
+
|
|
83
|
+
for (const field of PROFILE_FIELDS) {
|
|
84
|
+
if (raw[field] === undefined || raw[field] === null) continue;
|
|
85
|
+
if (field === 'proxyPort') {
|
|
86
|
+
profile.proxyPort = parsePortValue(String(raw.proxyPort), 'proxyPort');
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (field === 'type') {
|
|
90
|
+
profile.type = parseTypedValue('type', raw.type);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
profile[field] = String(raw[field]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return profile;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function ensureDefaultsFile() {
|
|
100
|
+
if (!existsSync(DEFAULTS_PATH)) {
|
|
101
|
+
writeFileSync(DEFAULTS_PATH, `${JSON.stringify(defaultDefaultsConfig(), null, 2)}\n`, 'utf8');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function loadDefaultsConfig() {
|
|
106
|
+
ensureDefaultsFile();
|
|
107
|
+
try {
|
|
108
|
+
const raw = JSON.parse(readFileSync(DEFAULTS_PATH, 'utf8'));
|
|
109
|
+
if (!raw || typeof raw !== 'object') throw new Error('invalid root object');
|
|
110
|
+
|
|
111
|
+
const profiles = raw.profiles && typeof raw.profiles === 'object' ? raw.profiles : {};
|
|
112
|
+
const normalizedProfiles = {};
|
|
113
|
+
for (const [name, profile] of Object.entries(profiles)) {
|
|
114
|
+
if (!PROFILE_NAME_RE.test(name)) continue;
|
|
115
|
+
normalizedProfiles[name] = sanitizeProfile(profile);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!normalizedProfiles[DEFAULT_PROFILE_NAME]) {
|
|
119
|
+
normalizedProfiles[DEFAULT_PROFILE_NAME] = defaultProfile();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
version: 1,
|
|
124
|
+
profiles: normalizedProfiles,
|
|
125
|
+
};
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error(`Warning: invalid ~/.klaudiak JSON (${err.message}). Recreating with defaults.`);
|
|
128
|
+
const fallback = defaultDefaultsConfig();
|
|
129
|
+
saveDefaultsConfig(fallback);
|
|
130
|
+
return fallback;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function saveDefaultsConfig(config) {
|
|
135
|
+
writeFileSync(DEFAULTS_PATH, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function resolveProfileNameAndArgs(argv) {
|
|
139
|
+
let profileName = DEFAULT_PROFILE_NAME;
|
|
140
|
+
const filtered = [];
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < argv.length; i++) {
|
|
143
|
+
const arg = argv[i];
|
|
144
|
+
if (arg === '--profile' || arg === '--defaults') {
|
|
145
|
+
const value = argv[i + 1];
|
|
146
|
+
if (!value) {
|
|
147
|
+
console.error(`Error: ${arg} requires a value.`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
profileName = value.trim();
|
|
151
|
+
i++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (arg.startsWith('--profile=')) {
|
|
155
|
+
profileName = arg.slice('--profile='.length).trim();
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (arg.startsWith('--defaults=')) {
|
|
159
|
+
profileName = arg.slice('--defaults='.length).trim();
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
filtered.push(arg);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!PROFILE_NAME_RE.test(profileName)) {
|
|
166
|
+
console.error(`Error: invalid profile name "${profileName}".`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { profileName, args: filtered };
|
|
171
|
+
}
|
|
172
|
+
|
|
12
173
|
function printUsage() {
|
|
13
174
|
console.log([
|
|
14
175
|
'Klaudia Kode CLI',
|
|
15
176
|
'',
|
|
16
177
|
'Usage:',
|
|
17
178
|
' klaudiak',
|
|
179
|
+
' klaudiak --profile=<name>',
|
|
18
180
|
' klaudiak --port=<port>',
|
|
181
|
+
' klaudiak defaults <list|show|create|delete|set|unset>',
|
|
19
182
|
' klaudiak proxy <model> key <apiKey> --port=<port>',
|
|
20
183
|
'',
|
|
21
184
|
'Examples:',
|
|
22
185
|
' klaudiak',
|
|
186
|
+
' klaudiak --profile=gemini',
|
|
23
187
|
' klaudiak --port=10000',
|
|
188
|
+
' klaudiak defaults create gemini',
|
|
189
|
+
' klaudiak defaults set gemini type proxy',
|
|
190
|
+
' klaudiak defaults set gemini openrouterModel google/gemini-2.5-pro',
|
|
191
|
+
' klaudiak defaults set gemini openrouterApiKey sk-or-v1-...',
|
|
24
192
|
' klaudiak proxy deepseek/deepseek-chat-v3-0324 key sk-or-v1-... --port=10000',
|
|
25
193
|
].join('\n'));
|
|
26
194
|
}
|
|
27
195
|
|
|
28
196
|
function parsePort(argv) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
197
|
+
for (let i = 0; i < argv.length; i++) {
|
|
198
|
+
const arg = argv[i];
|
|
199
|
+
if (arg === '--port') {
|
|
200
|
+
const raw = argv[i + 1];
|
|
201
|
+
if (!raw) {
|
|
202
|
+
console.error('Error: --port requires a value.');
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
return parsePortValue(String(raw).trim(), '--port');
|
|
206
|
+
}
|
|
207
|
+
if (arg.startsWith('--port=')) {
|
|
208
|
+
const raw = arg.slice('--port='.length).trim();
|
|
209
|
+
return parsePortValue(raw, '--port');
|
|
210
|
+
}
|
|
38
211
|
}
|
|
39
|
-
|
|
40
|
-
return port;
|
|
212
|
+
return null;
|
|
41
213
|
}
|
|
42
214
|
|
|
43
215
|
function spawnNode(scriptPath, scriptArgs, env) {
|
|
@@ -56,28 +228,80 @@ function spawnNode(scriptPath, scriptArgs, env) {
|
|
|
56
228
|
}
|
|
57
229
|
|
|
58
230
|
function runAgent(argv) {
|
|
231
|
+
const { profileName, args: filteredArgs } = resolveProfileNameAndArgs(argv);
|
|
232
|
+
const defaults = loadDefaultsConfig();
|
|
233
|
+
const profile = defaults.profiles[profileName];
|
|
234
|
+
if (!profile) {
|
|
235
|
+
console.error(`Error: profile "${profileName}" was not found in ~/.klaudiak`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
59
239
|
const port = parsePort(argv);
|
|
60
|
-
const forwardArgs =
|
|
240
|
+
const forwardArgs = [];
|
|
241
|
+
for (let i = 0; i < filteredArgs.length; i++) {
|
|
242
|
+
const arg = filteredArgs[i];
|
|
243
|
+
if (arg === '--port') {
|
|
244
|
+
i++;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (arg.startsWith('--port=')) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
forwardArgs.push(arg);
|
|
251
|
+
}
|
|
61
252
|
const env = { ...process.env };
|
|
62
253
|
|
|
254
|
+
if (profile.anthropicApiKey && !env.ANTHROPIC_API_KEY) {
|
|
255
|
+
env.ANTHROPIC_API_KEY = profile.anthropicApiKey;
|
|
256
|
+
}
|
|
257
|
+
if (profile.anthropicModel && !env.ANTHROPIC_MODEL) {
|
|
258
|
+
env.ANTHROPIC_MODEL = profile.anthropicModel;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (profile.anthropicBaseUrl && !env.ANTHROPIC_BASE_URL) {
|
|
262
|
+
env.ANTHROPIC_BASE_URL = profile.anthropicBaseUrl;
|
|
263
|
+
}
|
|
264
|
+
|
|
63
265
|
if (port !== null) {
|
|
64
266
|
env.ANTHROPIC_BASE_URL = `http://localhost:${port}`;
|
|
65
267
|
} else if (!env.ANTHROPIC_BASE_URL) {
|
|
66
|
-
env.ANTHROPIC_BASE_URL = 'http://localhost:9090';
|
|
268
|
+
env.ANTHROPIC_BASE_URL = profile.anthropicBaseUrl || 'http://localhost:9090';
|
|
67
269
|
}
|
|
68
270
|
|
|
69
271
|
spawnNode(join(__dirname, 'cli.js'), forwardArgs, env);
|
|
70
272
|
}
|
|
71
273
|
|
|
72
274
|
function runProxy(argv) {
|
|
73
|
-
|
|
275
|
+
const { profileName, args: filteredArgs } = resolveProfileNameAndArgs(argv);
|
|
276
|
+
const defaults = loadDefaultsConfig();
|
|
277
|
+
const profile = defaults.profiles[profileName];
|
|
278
|
+
if (!profile) {
|
|
279
|
+
console.error(`Error: profile "${profileName}" was not found in ~/.klaudiak`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const effectiveArgs = filteredArgs;
|
|
284
|
+
if (effectiveArgs.length < 3 || effectiveArgs[1] !== 'key') {
|
|
285
|
+
if (effectiveArgs.length === 0 && profile.type === 'proxy' && profile.openrouterModel && profile.openrouterApiKey) {
|
|
286
|
+
const env = {
|
|
287
|
+
...process.env,
|
|
288
|
+
OPENROUTER_MODEL: profile.openrouterModel,
|
|
289
|
+
OPENROUTER_API_KEY: profile.openrouterApiKey,
|
|
290
|
+
};
|
|
291
|
+
if (profile.proxyPort) {
|
|
292
|
+
env.PROXY_PORT = String(profile.proxyPort);
|
|
293
|
+
}
|
|
294
|
+
spawnNode(join(__dirname, 'openrouter-proxy.mjs'), [], env);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
74
298
|
console.error('Error: invalid proxy command syntax.');
|
|
75
299
|
printUsage();
|
|
76
300
|
process.exit(1);
|
|
77
301
|
}
|
|
78
302
|
|
|
79
|
-
const model =
|
|
80
|
-
const apiKey =
|
|
303
|
+
const model = effectiveArgs[0]?.trim();
|
|
304
|
+
const apiKey = effectiveArgs[2]?.trim();
|
|
81
305
|
|
|
82
306
|
if (!model) {
|
|
83
307
|
console.error('Error: missing model.');
|
|
@@ -89,14 +313,30 @@ function runProxy(argv) {
|
|
|
89
313
|
process.exit(1);
|
|
90
314
|
}
|
|
91
315
|
|
|
92
|
-
const tail =
|
|
93
|
-
|
|
94
|
-
|
|
316
|
+
const tail = effectiveArgs.slice(3);
|
|
317
|
+
const validTail = [];
|
|
318
|
+
for (let i = 0; i < tail.length; i++) {
|
|
319
|
+
const arg = tail[i];
|
|
320
|
+
if (arg === '--port') {
|
|
321
|
+
const value = tail[i + 1];
|
|
322
|
+
if (!value) {
|
|
323
|
+
console.error('Error: --port requires a value.');
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
validTail.push(arg, value);
|
|
327
|
+
i++;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (arg.startsWith('--port=')) {
|
|
331
|
+
validTail.push(arg);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
console.error('Error: unexpected arguments. Only --port=<port> or --port <port> is supported after API key.');
|
|
95
335
|
printUsage();
|
|
96
336
|
process.exit(1);
|
|
97
337
|
}
|
|
98
338
|
|
|
99
|
-
const port = parsePort(
|
|
339
|
+
const port = parsePort(validTail);
|
|
100
340
|
const env = {
|
|
101
341
|
...process.env,
|
|
102
342
|
OPENROUTER_MODEL: model,
|
|
@@ -110,12 +350,145 @@ function runProxy(argv) {
|
|
|
110
350
|
spawnNode(join(__dirname, 'openrouter-proxy.mjs'), [], env);
|
|
111
351
|
}
|
|
112
352
|
|
|
353
|
+
function runDefaults(argv) {
|
|
354
|
+
const defaults = loadDefaultsConfig();
|
|
355
|
+
const [subcommand, ...rest] = argv;
|
|
356
|
+
|
|
357
|
+
if (!subcommand || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
358
|
+
console.log([
|
|
359
|
+
'Manage ~/.klaudiak profiles',
|
|
360
|
+
'',
|
|
361
|
+
'Usage:',
|
|
362
|
+
' klaudiak defaults list',
|
|
363
|
+
' klaudiak defaults show <name>',
|
|
364
|
+
' klaudiak defaults create <name>',
|
|
365
|
+
' klaudiak defaults delete <name>',
|
|
366
|
+
' klaudiak defaults set <name> <field> <value>',
|
|
367
|
+
' klaudiak defaults unset <name> <field>',
|
|
368
|
+
'',
|
|
369
|
+
`Fields: ${Array.from(PROFILE_FIELDS).join(', ')}`,
|
|
370
|
+
].join('\n'));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (subcommand === 'list') {
|
|
375
|
+
const names = Object.keys(defaults.profiles).sort();
|
|
376
|
+
for (const name of names) {
|
|
377
|
+
const type = defaults.profiles[name].type || 'agent';
|
|
378
|
+
console.log(`${name} (${type})`);
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (subcommand === 'show') {
|
|
384
|
+
const name = rest[0];
|
|
385
|
+
if (!name) {
|
|
386
|
+
console.error('Error: missing profile name.');
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
const profile = defaults.profiles[name];
|
|
390
|
+
if (!profile) {
|
|
391
|
+
console.error(`Error: profile "${name}" was not found.`);
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
console.log(JSON.stringify(redactedProfile(profile), null, 2));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (subcommand === 'create') {
|
|
399
|
+
const name = rest[0];
|
|
400
|
+
if (!name) {
|
|
401
|
+
console.error('Error: missing profile name.');
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
if (!PROFILE_NAME_RE.test(name)) {
|
|
405
|
+
console.error(`Error: invalid profile name "${name}".`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
if (defaults.profiles[name]) {
|
|
409
|
+
console.error(`Error: profile "${name}" already exists.`);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
defaults.profiles[name] = defaultProfile();
|
|
413
|
+
saveDefaultsConfig(defaults);
|
|
414
|
+
console.log(`Created profile "${name}".`);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (subcommand === 'delete') {
|
|
419
|
+
const name = rest[0];
|
|
420
|
+
if (!name) {
|
|
421
|
+
console.error('Error: missing profile name.');
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
if (name === DEFAULT_PROFILE_NAME) {
|
|
425
|
+
console.error('Error: cannot delete the default profile.');
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
if (!defaults.profiles[name]) {
|
|
429
|
+
console.error(`Error: profile "${name}" was not found.`);
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
delete defaults.profiles[name];
|
|
433
|
+
saveDefaultsConfig(defaults);
|
|
434
|
+
console.log(`Deleted profile "${name}".`);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (subcommand === 'set') {
|
|
439
|
+
const [name, field, ...valueParts] = rest;
|
|
440
|
+
if (!name || !field || valueParts.length === 0) {
|
|
441
|
+
console.error('Error: usage: klaudiak defaults set <name> <field> <value>');
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
if (!defaults.profiles[name]) {
|
|
445
|
+
console.error(`Error: profile "${name}" was not found.`);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
if (!PROFILE_FIELDS.has(field)) {
|
|
449
|
+
console.error(`Error: unknown field "${field}".`);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
defaults.profiles[name][field] = parseTypedValue(field, valueParts.join(' '));
|
|
453
|
+
saveDefaultsConfig(defaults);
|
|
454
|
+
console.log(`Set ${name}.${field}`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (subcommand === 'unset') {
|
|
459
|
+
const [name, field] = rest;
|
|
460
|
+
if (!name || !field) {
|
|
461
|
+
console.error('Error: usage: klaudiak defaults unset <name> <field>');
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
if (!defaults.profiles[name]) {
|
|
465
|
+
console.error(`Error: profile "${name}" was not found.`);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
if (!PROFILE_FIELDS.has(field)) {
|
|
469
|
+
console.error(`Error: unknown field "${field}".`);
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
defaults.profiles[name][field] = defaultProfile()[field];
|
|
473
|
+
saveDefaultsConfig(defaults);
|
|
474
|
+
console.log(`Unset ${name}.${field}`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
console.error(`Error: unknown defaults subcommand "${subcommand}".`);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
ensureDefaultsFile();
|
|
483
|
+
|
|
113
484
|
if (args.includes('--help') || args.includes('-h')) {
|
|
114
485
|
printUsage();
|
|
115
486
|
process.exit(0);
|
|
116
487
|
}
|
|
117
488
|
|
|
118
|
-
if (args.length > 0 && args[0] === '
|
|
489
|
+
if (args.length > 0 && args[0] === 'defaults') {
|
|
490
|
+
runDefaults(args.slice(1));
|
|
491
|
+
} else if (args.length > 0 && args[0] === 'proxy') {
|
|
119
492
|
runProxy(args.slice(1));
|
|
120
493
|
} else {
|
|
121
494
|
runAgent(args);
|
package/package.json
CHANGED
package/user-manual.md
DELETED
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
# claude-code-anymodel User Manual
|
|
2
|
-
|
|
3
|
-
This manual is for users who want to run this AI coding agent at full power.
|
|
4
|
-
|
|
5
|
-
## 1) What This Project Is
|
|
6
|
-
|
|
7
|
-
This project is an AnyModel-flavored Claude Code client:
|
|
8
|
-
|
|
9
|
-
- Interactive coding agent in your terminal
|
|
10
|
-
- Works with Anthropic-compatible endpoints
|
|
11
|
-
- Can route through AnyModel proxy to OpenRouter or Ollama
|
|
12
|
-
- Supports one-shot automation and long interactive sessions
|
|
13
|
-
|
|
14
|
-
Primary references in this repo:
|
|
15
|
-
|
|
16
|
-
- Runtime options and subcommands: [main.tsx](main.tsx)
|
|
17
|
-
- Slash-command registry: [commands.ts](commands.ts)
|
|
18
|
-
- Quickstart and proxy framing: [README.md](README.md)
|
|
19
|
-
- Workflow recipes: [Makefile](Makefile)
|
|
20
|
-
|
|
21
|
-
## 2) Prerequisites
|
|
22
|
-
|
|
23
|
-
- Node.js 18+ (required by [package.json](package.json))
|
|
24
|
-
- A shell environment where `npx` is available
|
|
25
|
-
- Optional for proxy workflows:
|
|
26
|
-
- OpenRouter API key (`OPENROUTER_API_KEY`)
|
|
27
|
-
- Ollama installed and running locally
|
|
28
|
-
|
|
29
|
-
Quick check:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
make env-init
|
|
33
|
-
make check-node
|
|
34
|
-
make help-cli
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Optional but recommended: use a local `.env`
|
|
38
|
-
|
|
39
|
-
This repo now supports a no-friction env workflow:
|
|
40
|
-
|
|
41
|
-
- `make env-init` creates `.env` from [\.env.example](.env.example)
|
|
42
|
-
- [Makefile](Makefile) auto-loads `.env` for all targets
|
|
43
|
-
- [openrouter-proxy.mjs](openrouter-proxy.mjs) also reads `.env` directly
|
|
44
|
-
|
|
45
|
-
That means you can set keys once in `.env` and stop re-exporting each session.
|
|
46
|
-
|
|
47
|
-
## 3) Fastest Start
|
|
48
|
-
|
|
49
|
-
### Anthropic/compatible direct flow
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
make run
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### OpenRouter flow (2 terminals)
|
|
56
|
-
|
|
57
|
-
Terminal A:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
export OPENROUTER_API_KEY=sk-or-v1-...
|
|
61
|
-
make proxy-openrouter
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Terminal B:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
make run-openrouter
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Ollama flow (3 terminals)
|
|
71
|
-
|
|
72
|
-
Terminal A:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
ollama serve
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Terminal B:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
make proxy-ollama OLLAMA_MODEL=llama3.1
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Terminal C:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
make run-ollama OLLAMA_MODEL=llama3.1
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## 4) Core Run Modes You Should Master
|
|
91
|
-
|
|
92
|
-
### Interactive mode (default)
|
|
93
|
-
|
|
94
|
-
Best for complex coding tasks, repo exploration, refactoring loops.
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
make run
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### Bare mode
|
|
101
|
-
|
|
102
|
-
Use this for minimal runtime overhead and fewer moving pieces.
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
make bare
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### One-shot print mode
|
|
109
|
-
|
|
110
|
-
Great for scripts, CI-style invocation, and deterministic automation.
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
make print PROMPT='summarize this repository architecture'
|
|
114
|
-
make print-json PROMPT='return a JSON checklist for onboarding'
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Safe automation mode
|
|
118
|
-
|
|
119
|
-
Adds guardrails: max turns + budget cap.
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
make safe-print PROMPT='write unit tests for changed files'
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## 5) Session Control (High Leverage)
|
|
126
|
-
|
|
127
|
-
### Continue latest session in current working directory
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
make continue
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### Resume by ID or search text
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
make resume SESSION='a3f0c2...'
|
|
137
|
-
make resume SESSION='feature login review'
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
These map to CLI flags in [main.tsx](main.tsx):
|
|
141
|
-
|
|
142
|
-
- `--continue`
|
|
143
|
-
- `--resume [value]`
|
|
144
|
-
|
|
145
|
-
## 6) Quality, Diagnostics, and Setup
|
|
146
|
-
|
|
147
|
-
### Health and setup
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
make doctor
|
|
151
|
-
make init
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
These run slash commands in session:
|
|
155
|
-
|
|
156
|
-
- `/doctor` for diagnostics
|
|
157
|
-
- `/init` for project onboarding/instructions
|
|
158
|
-
|
|
159
|
-
Both are registered in [commands.ts](commands.ts).
|
|
160
|
-
|
|
161
|
-
### Authentication status
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
make auth-status
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### Debug session
|
|
168
|
-
|
|
169
|
-
```bash
|
|
170
|
-
make debug-run
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
## 7) MCP and Plugin Operations
|
|
174
|
-
|
|
175
|
-
### Run local MCP server
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
make mcp-serve
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### List installed plugins
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
make plugins
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
For full plugin and marketplace lifecycle, use CLI subcommands from [main.tsx](main.tsx), such as:
|
|
188
|
-
|
|
189
|
-
- `plugin list`
|
|
190
|
-
- `plugin install`
|
|
191
|
-
- `plugin uninstall`
|
|
192
|
-
- `marketplace add`
|
|
193
|
-
- `marketplace list`
|
|
194
|
-
- `marketplace update`
|
|
195
|
-
|
|
196
|
-
## 8) Power User Workflows
|
|
197
|
-
|
|
198
|
-
### A) Tight implementation loop
|
|
199
|
-
|
|
200
|
-
1. Launch interactive session:
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
make run
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
2. Ask agent to implement feature in small increments.
|
|
207
|
-
3. Ask for tests and a final verification pass.
|
|
208
|
-
4. Use `make continue` for next session to preserve momentum.
|
|
209
|
-
|
|
210
|
-
### B) Scriptable review helper
|
|
211
|
-
|
|
212
|
-
```bash
|
|
213
|
-
make safe-print PROMPT='review staged changes and output a risk checklist'
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### C) Cost-aware exploration
|
|
217
|
-
|
|
218
|
-
Use short one-shot prompts first:
|
|
219
|
-
|
|
220
|
-
```bash
|
|
221
|
-
make print PROMPT='list only top 5 refactor opportunities'
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Then switch to full interactive only when needed.
|
|
225
|
-
|
|
226
|
-
### D) Worktree-isolated tasks
|
|
227
|
-
|
|
228
|
-
```bash
|
|
229
|
-
make worktree NAME=feature-auth-hardening
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
This maps to `--worktree` (see [main.tsx](main.tsx)).
|
|
233
|
-
|
|
234
|
-
## 9) Recommended Prompt Patterns
|
|
235
|
-
|
|
236
|
-
### For architecture understanding
|
|
237
|
-
|
|
238
|
-
- "Map the runtime flow from CLI entry to tool execution."
|
|
239
|
-
- "Explain how command registration and feature gates interact."
|
|
240
|
-
|
|
241
|
-
### For changes with fewer regressions
|
|
242
|
-
|
|
243
|
-
- "Propose changes first as a concise plan, then implement and run checks."
|
|
244
|
-
- "Keep edits minimal and avoid unrelated formatting changes."
|
|
245
|
-
|
|
246
|
-
### For safer automation
|
|
247
|
-
|
|
248
|
-
- "Stop if cost exceeds budget and return what remains."
|
|
249
|
-
- "Prefer read-only analysis first, then patch in one pass."
|
|
250
|
-
|
|
251
|
-
## 10) Cost and Performance Playbook
|
|
252
|
-
|
|
253
|
-
1. Prefer `--print` for focused tasks.
|
|
254
|
-
2. Use `--max-turns` and `--max-budget-usd` for bounded runs.
|
|
255
|
-
3. Keep prompts scoped to explicit files/components.
|
|
256
|
-
4. Use bare mode for lower startup complexity.
|
|
257
|
-
5. Resume sessions instead of re-explaining context from scratch.
|
|
258
|
-
|
|
259
|
-
## 11) Security and Safety Notes
|
|
260
|
-
|
|
261
|
-
- Avoid `--dangerously-skip-permissions` unless you truly control the environment.
|
|
262
|
-
- Keep API keys in environment variables, not committed files.
|
|
263
|
-
- Use isolated worktrees for risky changes.
|
|
264
|
-
- Review generated diffs before commit/push.
|
|
265
|
-
|
|
266
|
-
## 12) Commands You Will Use Most
|
|
267
|
-
|
|
268
|
-
```bash
|
|
269
|
-
make help
|
|
270
|
-
make run
|
|
271
|
-
make bare
|
|
272
|
-
make print PROMPT='...'
|
|
273
|
-
make print-json PROMPT='...'
|
|
274
|
-
make safe-print PROMPT='...'
|
|
275
|
-
make continue
|
|
276
|
-
make resume SESSION='...'
|
|
277
|
-
make doctor
|
|
278
|
-
make init
|
|
279
|
-
make proxy-openrouter
|
|
280
|
-
make run-openrouter
|
|
281
|
-
make proxy-ollama
|
|
282
|
-
make run-ollama
|
|
283
|
-
make mcp-serve
|
|
284
|
-
make plugins
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## 13) Internal/Feature-Gated Items
|
|
288
|
-
|
|
289
|
-
This codebase contains internal or feature-gated capabilities (for example some ant-only, bridge, or daemon paths in [commands.ts](commands.ts) and [main.tsx](main.tsx)).
|
|
290
|
-
|
|
291
|
-
Treat them as non-default unless your environment explicitly enables them.
|
|
292
|
-
|
|
293
|
-
## 14) Troubleshooting
|
|
294
|
-
|
|
295
|
-
### "Command not found: anymodel"
|
|
296
|
-
|
|
297
|
-
- Use `npx anymodel ...` (already baked into [Makefile](Makefile)).
|
|
298
|
-
- Ensure Node/npm are installed and available in PATH.
|
|
299
|
-
|
|
300
|
-
### "No auth / request failed"
|
|
301
|
-
|
|
302
|
-
- Run `make auth-status`.
|
|
303
|
-
- Ensure your selected provider credentials are present.
|
|
304
|
-
|
|
305
|
-
### "Proxy started but client cannot connect"
|
|
306
|
-
|
|
307
|
-
- Verify `ANTHROPIC_BASE_URL=http://localhost:9090` path via `make run-openrouter` or `make run-ollama`.
|
|
308
|
-
- Make sure proxy terminal is still running.
|
|
309
|
-
|
|
310
|
-
### "Ollama errors"
|
|
311
|
-
|
|
312
|
-
- Start Ollama daemon first: `ollama serve`.
|
|
313
|
-
- Ensure selected model exists locally.
|
|
314
|
-
|
|
315
|
-
### "Session resume not finding what I expect"
|
|
316
|
-
|
|
317
|
-
- Use explicit ID with `make resume SESSION='...'`.
|
|
318
|
-
- Use `make continue` from the same working directory.
|
|
319
|
-
|
|
320
|
-
## 15) Daily Best-Practice Checklist
|
|
321
|
-
|
|
322
|
-
1. Start with `make print` for small tasks.
|
|
323
|
-
2. Move to `make run` for multi-step changes.
|
|
324
|
-
3. Keep a cost cap for unattended runs (`make safe-print`).
|
|
325
|
-
4. Use worktrees for larger features.
|
|
326
|
-
5. Reuse context with `make continue`.
|
|
327
|
-
6. Use `make doctor` whenever behavior is odd.
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
If you want an even more opinionated setup, customize [Makefile](Makefile) defaults (model, budget, turns, proxy model) to match your preferred daily workflow.
|