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 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
@@ -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
- const portArg = argv.find(a => a.startsWith('--port='));
30
- if (!portArg) return null;
31
-
32
- const raw = portArg.slice('--port='.length).trim();
33
- const port = Number(raw);
34
-
35
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
36
- console.error(`Error: invalid --port value "${raw}". Expected integer in range 1-65535.`);
37
- process.exit(1);
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 = argv.filter(a => !a.startsWith('--port='));
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
- if (argv.length < 4 || argv[1] !== 'key') {
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 = argv[0]?.trim();
80
- const apiKey = argv[2]?.trim();
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 = argv.slice(3);
93
- if (tail.some(a => !a.startsWith('--port='))) {
94
- console.error('Error: unexpected arguments. Only --port=<port> is supported after API key.');
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(tail);
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] === 'proxy') {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klaudiak",
3
- "version": "2.1.88",
3
+ "version": "2.1.89",
4
4
  "bin": {
5
5
  "klaudiak": "klaudiak.js"
6
6
  },
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.