@developerz.ai/aitm 0.0.4 → 0.0.5
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 +12 -2
- package/dist/cli/args.d.ts +29 -1
- package/dist/cli/args.js +113 -0
- package/dist/cli/cli.js +23 -2
- package/dist/cli/commands.d.ts +7 -0
- package/dist/cli/commands.js +93 -4
- package/dist/config/config-loader.d.ts +2 -0
- package/dist/config/config-loader.js +46 -7
- package/dist/config/config-writer.js +9 -0
- package/dist/config/profiles.d.ts +30 -0
- package/dist/config/profiles.js +224 -0
- package/dist/config/provider-presets.d.ts +5 -0
- package/dist/config/provider-presets.js +18 -0
- package/dist/config/schema.d.ts +26 -1
- package/dist/config/schema.js +10 -0
- package/dist/credentials/credentials.d.ts +4 -0
- package/dist/credentials/credentials.js +7 -1
- package/dist/orchestrator/orchestrator.js +16 -4
- package/dist/subagents/planner.d.ts +2 -4
- package/dist/subagents/planner.js +11 -10
- package/dist/subagents/reviewer.d.ts +2 -4
- package/dist/subagents/reviewer.js +16 -12
- package/dist/subagents/worker.d.ts +2 -4
- package/dist/subagents/worker.js +12 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -76,8 +76,18 @@ aitm config set autoMerge true --project
|
|
|
76
76
|
aitm config list
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
- **Provider**:
|
|
80
|
-
|
|
79
|
+
- **Provider**: any OpenAI-compatible endpoint via one credential — OpenRouter by
|
|
80
|
+
default, or set `baseURL` to run on z.ai GLM, a self-hosted gateway, etc. No
|
|
81
|
+
Anthropic SDK. **Profiles** switch the whole provider in one command:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
aitm profile add z.ai --preset zai --api-key "<your z.ai key>"
|
|
85
|
+
aitm profile use z.ai # ✅ verified end-to-end on z.ai GLM (glm-5.2 / glm-5-turbo)
|
|
86
|
+
aitm profile use openrouter
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
See [providers](https://github.com/developerz-ai/ai-task-master/blob/main/packages/aitm/docs/providers.md)
|
|
90
|
+
and [`aitm profile`](https://github.com/developerz-ai/ai-task-master/blob/main/packages/aitm/docs/commands/profile.md).
|
|
81
91
|
- **Coding style**: `aitm` reads your repo's `CLAUDE.md` / `AGENTS.md` and feeds
|
|
82
92
|
it to subagents as a style signal (the provider stays OpenRouter).
|
|
83
93
|
- **MCP**: `aitm` is an MCP **client** — declare `mcpServers` in config and their
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PresetName } from '../config/provider-presets.ts';
|
|
1
2
|
export type StartArgs = {
|
|
2
3
|
kind: 'start';
|
|
3
4
|
goal: string;
|
|
@@ -31,7 +32,34 @@ export type ConfigArgs = {
|
|
|
31
32
|
kind: 'config-list';
|
|
32
33
|
scope: 'global' | 'project';
|
|
33
34
|
};
|
|
34
|
-
export type
|
|
35
|
+
export type ProfileArgs = {
|
|
36
|
+
kind: 'profile-list';
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'profile-use';
|
|
39
|
+
name: string;
|
|
40
|
+
} | {
|
|
41
|
+
kind: 'profile-add';
|
|
42
|
+
name: string;
|
|
43
|
+
preset?: PresetName;
|
|
44
|
+
baseURL?: string;
|
|
45
|
+
apiKey?: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'profile-set';
|
|
48
|
+
name: string;
|
|
49
|
+
key: string;
|
|
50
|
+
value: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'profile-get';
|
|
53
|
+
name: string;
|
|
54
|
+
key: string;
|
|
55
|
+
} | {
|
|
56
|
+
kind: 'profile-remove';
|
|
57
|
+
name: string;
|
|
58
|
+
} | {
|
|
59
|
+
kind: 'profile-show';
|
|
60
|
+
name?: string;
|
|
61
|
+
};
|
|
62
|
+
export type ParsedArgs = StartArgs | MergePrArgs | ConfigArgs | ProfileArgs | {
|
|
35
63
|
kind: 'help';
|
|
36
64
|
};
|
|
37
65
|
export declare function parseArgs(argv: ReadonlyArray<string>): ParsedArgs;
|
package/dist/cli/args.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPresetName } from "../config/provider-presets.js";
|
|
1
2
|
const HELP = { kind: 'help' };
|
|
2
3
|
export function parseArgs(argv) {
|
|
3
4
|
const [command, ...rest] = argv;
|
|
@@ -12,6 +13,8 @@ export function parseArgs(argv) {
|
|
|
12
13
|
return parseMergePr(rest);
|
|
13
14
|
case 'config':
|
|
14
15
|
return parseConfig(rest);
|
|
16
|
+
case 'profile':
|
|
17
|
+
return parseProfile(rest);
|
|
15
18
|
default:
|
|
16
19
|
return HELP;
|
|
17
20
|
}
|
|
@@ -194,6 +197,116 @@ function parseConfig(args) {
|
|
|
194
197
|
return HELP;
|
|
195
198
|
}
|
|
196
199
|
}
|
|
200
|
+
function parseProfile(args) {
|
|
201
|
+
const sub = args[0];
|
|
202
|
+
if (sub === undefined)
|
|
203
|
+
return HELP;
|
|
204
|
+
const tail = args.slice(1);
|
|
205
|
+
switch (sub) {
|
|
206
|
+
case 'list':
|
|
207
|
+
return tail.length === 0 ? { kind: 'profile-list' } : HELP;
|
|
208
|
+
case 'use': {
|
|
209
|
+
const name = onlyName(tail);
|
|
210
|
+
return name === null ? HELP : { kind: 'profile-use', name };
|
|
211
|
+
}
|
|
212
|
+
case 'remove': {
|
|
213
|
+
const name = onlyName(tail);
|
|
214
|
+
return name === null ? HELP : { kind: 'profile-remove', name };
|
|
215
|
+
}
|
|
216
|
+
case 'show': {
|
|
217
|
+
if (tail.length === 0)
|
|
218
|
+
return { kind: 'profile-show' };
|
|
219
|
+
const name = onlyName(tail);
|
|
220
|
+
return name === null ? HELP : { kind: 'profile-show', name };
|
|
221
|
+
}
|
|
222
|
+
case 'get': {
|
|
223
|
+
if (tail.length !== 2)
|
|
224
|
+
return HELP;
|
|
225
|
+
const [name, key] = tail;
|
|
226
|
+
if (name === undefined ||
|
|
227
|
+
key === undefined ||
|
|
228
|
+
name.startsWith('--') ||
|
|
229
|
+
key.startsWith('--')) {
|
|
230
|
+
return HELP;
|
|
231
|
+
}
|
|
232
|
+
return { kind: 'profile-get', name, key };
|
|
233
|
+
}
|
|
234
|
+
case 'set': {
|
|
235
|
+
if (tail.length !== 3)
|
|
236
|
+
return HELP;
|
|
237
|
+
const [name, key, value] = tail;
|
|
238
|
+
if (name === undefined || key === undefined || value === undefined)
|
|
239
|
+
return HELP;
|
|
240
|
+
if (name.startsWith('--') || key.startsWith('--'))
|
|
241
|
+
return HELP;
|
|
242
|
+
return { kind: 'profile-set', name, key, value };
|
|
243
|
+
}
|
|
244
|
+
case 'add':
|
|
245
|
+
return parseProfileAdd(tail);
|
|
246
|
+
default:
|
|
247
|
+
return HELP;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function parseProfileAdd(tail) {
|
|
251
|
+
const positionals = [];
|
|
252
|
+
let preset;
|
|
253
|
+
let baseURL;
|
|
254
|
+
let apiKey;
|
|
255
|
+
let i = 0;
|
|
256
|
+
while (i < tail.length) {
|
|
257
|
+
const raw = tail[i];
|
|
258
|
+
if (raw === undefined)
|
|
259
|
+
break;
|
|
260
|
+
const { flag, inlineValue, consumed } = splitFlag(raw);
|
|
261
|
+
if (flag === '--preset') {
|
|
262
|
+
const v = takeValue(tail, i, inlineValue);
|
|
263
|
+
if (v === null || !isPresetName(v))
|
|
264
|
+
return HELP;
|
|
265
|
+
preset = v;
|
|
266
|
+
i += consumed(inlineValue !== null);
|
|
267
|
+
}
|
|
268
|
+
else if (flag === '--base-url') {
|
|
269
|
+
const v = takeValue(tail, i, inlineValue);
|
|
270
|
+
if (v === null || (inlineValue === null && v.startsWith('--')))
|
|
271
|
+
return HELP;
|
|
272
|
+
baseURL = v;
|
|
273
|
+
i += consumed(inlineValue !== null);
|
|
274
|
+
}
|
|
275
|
+
else if (flag === '--api-key') {
|
|
276
|
+
const v = takeValue(tail, i, inlineValue);
|
|
277
|
+
if (v === null || (inlineValue === null && v.startsWith('--')))
|
|
278
|
+
return HELP;
|
|
279
|
+
apiKey = v;
|
|
280
|
+
i += consumed(inlineValue !== null);
|
|
281
|
+
}
|
|
282
|
+
else if (raw.startsWith('--')) {
|
|
283
|
+
return HELP;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
positionals.push(raw);
|
|
287
|
+
i += 1;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const name = positionals[0];
|
|
291
|
+
if (name === undefined || positionals.length > 1)
|
|
292
|
+
return HELP;
|
|
293
|
+
const out = { kind: 'profile-add', name };
|
|
294
|
+
if (preset !== undefined)
|
|
295
|
+
out.preset = preset;
|
|
296
|
+
if (baseURL !== undefined)
|
|
297
|
+
out.baseURL = baseURL;
|
|
298
|
+
if (apiKey !== undefined)
|
|
299
|
+
out.apiKey = apiKey;
|
|
300
|
+
return out;
|
|
301
|
+
}
|
|
302
|
+
function onlyName(tail) {
|
|
303
|
+
if (tail.length !== 1)
|
|
304
|
+
return null;
|
|
305
|
+
const name = tail[0];
|
|
306
|
+
if (name === undefined || name.startsWith('--'))
|
|
307
|
+
return null;
|
|
308
|
+
return name;
|
|
309
|
+
}
|
|
197
310
|
function parseNonNegativeInt(s) {
|
|
198
311
|
if (s === undefined || s === null)
|
|
199
312
|
return null;
|
package/dist/cli/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { realpathSync } from 'node:fs';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { parseArgs } from "./args.js";
|
|
5
|
-
import { runConfig, runMergePr, runStart } from "./commands.js";
|
|
5
|
+
import { runConfig, runMergePr, runProfile, runStart } from "./commands.js";
|
|
6
6
|
export async function main(argv, ctx = {}) {
|
|
7
7
|
const stdout = ctx.stdout ?? ((chunk) => process.stdout.write(chunk));
|
|
8
8
|
const stderr = ctx.stderr ?? ((chunk) => process.stderr.write(chunk));
|
|
@@ -17,6 +17,14 @@ export async function main(argv, ctx = {}) {
|
|
|
17
17
|
case 'config-get':
|
|
18
18
|
case 'config-list':
|
|
19
19
|
return emit(await runConfig(parsed, buildConfigCtx(ctx, stdout)), stderr);
|
|
20
|
+
case 'profile-list':
|
|
21
|
+
case 'profile-use':
|
|
22
|
+
case 'profile-add':
|
|
23
|
+
case 'profile-set':
|
|
24
|
+
case 'profile-get':
|
|
25
|
+
case 'profile-remove':
|
|
26
|
+
case 'profile-show':
|
|
27
|
+
return emit(await runProfile(parsed, buildProfileCtx(ctx, stdout)), stderr);
|
|
20
28
|
case 'help':
|
|
21
29
|
stdout(`${HELP_TEXT}\n`);
|
|
22
30
|
return 0;
|
|
@@ -60,6 +68,12 @@ function buildConfigCtx(ctx, stdout) {
|
|
|
60
68
|
out.homeDir = ctx.homeDir;
|
|
61
69
|
return out;
|
|
62
70
|
}
|
|
71
|
+
function buildProfileCtx(ctx, stdout) {
|
|
72
|
+
const out = { stdout };
|
|
73
|
+
if (ctx.homeDir !== undefined)
|
|
74
|
+
out.homeDir = ctx.homeDir;
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
63
77
|
function emit(exit, stderr) {
|
|
64
78
|
if (exit.message !== undefined && exit.message !== '')
|
|
65
79
|
stderr(`${exit.message}\n`);
|
|
@@ -76,6 +90,13 @@ Usage:
|
|
|
76
90
|
aitm config unset <key> [--project]
|
|
77
91
|
aitm config get <key> [--project]
|
|
78
92
|
aitm config list [--project]
|
|
93
|
+
aitm profile list
|
|
94
|
+
aitm profile use <name>
|
|
95
|
+
aitm profile add <name> [--preset openrouter|zai] [--base-url <url>] [--api-key <key>]
|
|
96
|
+
aitm profile set <name> <key> <value>
|
|
97
|
+
aitm profile get <name> <key>
|
|
98
|
+
aitm profile remove <name>
|
|
99
|
+
aitm profile show [<name>]
|
|
79
100
|
aitm help | --help | -h
|
|
80
101
|
|
|
81
102
|
Exit codes:
|
|
@@ -83,7 +104,7 @@ Exit codes:
|
|
|
83
104
|
1 precondition failure or run blocked
|
|
84
105
|
2 cancelled
|
|
85
106
|
|
|
86
|
-
Docs: docs/commands/start.md, docs/commands/merge-pr.md, docs/commands/config.md`;
|
|
107
|
+
Docs: docs/commands/start.md, docs/commands/merge-pr.md, docs/commands/config.md, docs/commands/profile.md`;
|
|
87
108
|
if (isEntrypoint(import.meta.url, process.argv[1])) {
|
|
88
109
|
main(process.argv.slice(2)).then((code) => {
|
|
89
110
|
process.exit(code);
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -72,6 +72,10 @@ export type ConfigCtx = {
|
|
|
72
72
|
homeDir?: string;
|
|
73
73
|
stdout?: (chunk: string) => void;
|
|
74
74
|
};
|
|
75
|
+
export type ProfileCtx = {
|
|
76
|
+
homeDir?: string;
|
|
77
|
+
stdout?: (chunk: string) => void;
|
|
78
|
+
};
|
|
75
79
|
export declare function runStart(args: Extract<ParsedArgs, {
|
|
76
80
|
kind: 'start';
|
|
77
81
|
}>, ctx?: StartCtx): Promise<CommandExit>;
|
|
@@ -81,3 +85,6 @@ export declare function runMergePr(args: Extract<ParsedArgs, {
|
|
|
81
85
|
export declare function runConfig(args: Extract<ParsedArgs, {
|
|
82
86
|
kind: `config-${string}`;
|
|
83
87
|
}>, ctx?: ConfigCtx): Promise<CommandExit>;
|
|
88
|
+
export declare function runProfile(args: Extract<ParsedArgs, {
|
|
89
|
+
kind: `profile-${string}`;
|
|
90
|
+
}>, ctx?: ProfileCtx): Promise<CommandExit>;
|
package/dist/cli/commands.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join, resolve as resolvePath } from 'node:path';
|
|
|
3
3
|
import { AgentConfigDetector } from "../agent-config/agent-config-detector.js";
|
|
4
4
|
import { ConfigLoader } from "../config/config-loader.js";
|
|
5
5
|
import { ConfigWriter } from "../config/config-writer.js";
|
|
6
|
+
import { ProfileManager } from "../config/profiles.js";
|
|
6
7
|
import { Credentials } from "../credentials/credentials.js";
|
|
7
8
|
import { DEFAULT_MODELS } from "../credentials/defaults.js";
|
|
8
9
|
import { GitHubClient } from "../github/github-client.js";
|
|
@@ -257,10 +258,7 @@ export async function runConfig(args, ctx = {}) {
|
|
|
257
258
|
}
|
|
258
259
|
case 'config-list': {
|
|
259
260
|
const file = await writer.list(args.scope);
|
|
260
|
-
|
|
261
|
-
? { ...file, openrouterApiKey: maskSecret(file.openrouterApiKey) }
|
|
262
|
-
: file;
|
|
263
|
-
stdout(`${JSON.stringify(safe, null, 2)}\n`);
|
|
261
|
+
stdout(`${JSON.stringify(redactConfigKeys(file), null, 2)}\n`);
|
|
264
262
|
return { code: 0 };
|
|
265
263
|
}
|
|
266
264
|
default:
|
|
@@ -274,6 +272,65 @@ export async function runConfig(args, ctx = {}) {
|
|
|
274
272
|
return { code: 1, message: errMsg(err) };
|
|
275
273
|
}
|
|
276
274
|
}
|
|
275
|
+
export async function runProfile(args, ctx = {}) {
|
|
276
|
+
const homeDir = ctx.homeDir ?? homedir();
|
|
277
|
+
const stdout = ctx.stdout ?? ((chunk) => process.stdout.write(chunk));
|
|
278
|
+
const manager = new ProfileManager(homeDir);
|
|
279
|
+
try {
|
|
280
|
+
switch (args.kind) {
|
|
281
|
+
case 'profile-list': {
|
|
282
|
+
const listing = await manager.list();
|
|
283
|
+
stdout(formatProfileList(listing.activeProfile, listing.profiles));
|
|
284
|
+
return { code: 0 };
|
|
285
|
+
}
|
|
286
|
+
case 'profile-use': {
|
|
287
|
+
await manager.use(args.name);
|
|
288
|
+
stdout(`Active profile is now "${args.name}".\n`);
|
|
289
|
+
return { code: 0 };
|
|
290
|
+
}
|
|
291
|
+
case 'profile-add': {
|
|
292
|
+
const input = {};
|
|
293
|
+
if (args.preset !== undefined)
|
|
294
|
+
input.preset = args.preset;
|
|
295
|
+
if (args.baseURL !== undefined)
|
|
296
|
+
input.baseURL = args.baseURL;
|
|
297
|
+
if (args.apiKey !== undefined)
|
|
298
|
+
input.apiKey = args.apiKey;
|
|
299
|
+
await manager.add(args.name, input);
|
|
300
|
+
const activated = (await manager.list()).activeProfile === args.name;
|
|
301
|
+
stdout(activated
|
|
302
|
+
? `Created and activated profile "${args.name}".\n`
|
|
303
|
+
: `Created profile "${args.name}". Run \`aitm profile use ${args.name}\` to activate it.\n`);
|
|
304
|
+
return { code: 0 };
|
|
305
|
+
}
|
|
306
|
+
case 'profile-set':
|
|
307
|
+
await manager.set(args.name, args.key, args.value);
|
|
308
|
+
return { code: 0 };
|
|
309
|
+
case 'profile-get': {
|
|
310
|
+
const value = await manager.get(args.name, args.key);
|
|
311
|
+
stdout(`${formatConfigValue(value)}\n`);
|
|
312
|
+
return { code: 0 };
|
|
313
|
+
}
|
|
314
|
+
case 'profile-remove':
|
|
315
|
+
await manager.remove(args.name);
|
|
316
|
+
stdout(`Removed profile "${args.name}".\n`);
|
|
317
|
+
return { code: 0 };
|
|
318
|
+
case 'profile-show': {
|
|
319
|
+
const { name, profile } = await manager.show(args.name);
|
|
320
|
+
stdout(`${name}\n${JSON.stringify(redactProfile(profile), null, 2)}\n`);
|
|
321
|
+
return { code: 0 };
|
|
322
|
+
}
|
|
323
|
+
default:
|
|
324
|
+
return {
|
|
325
|
+
code: 1,
|
|
326
|
+
message: `Unknown profile subcommand: ${args.kind}`,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
return { code: 1, message: errMsg(err) };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
277
334
|
function toCliOverrides(args) {
|
|
278
335
|
const out = {};
|
|
279
336
|
if (args.maxPrs !== undefined)
|
|
@@ -362,6 +419,38 @@ function formatConfigValue(value) {
|
|
|
362
419
|
return value;
|
|
363
420
|
return JSON.stringify(value, null, 2);
|
|
364
421
|
}
|
|
422
|
+
function formatProfileList(active, profiles) {
|
|
423
|
+
const names = Object.keys(profiles).sort();
|
|
424
|
+
if (names.length === 0) {
|
|
425
|
+
return 'No profiles configured. Create one with `aitm profile add <name> --preset zai`.\n';
|
|
426
|
+
}
|
|
427
|
+
const lines = names.map((name) => {
|
|
428
|
+
const p = profiles[name] ?? {};
|
|
429
|
+
const marker = name === active ? '*' : ' ';
|
|
430
|
+
const base = p.baseURL ?? '(provider default)';
|
|
431
|
+
const key = p.openrouterApiKey ? maskSecret(p.openrouterApiKey) : '(no key)';
|
|
432
|
+
return `${marker} ${name}\t${base}\t${key}`;
|
|
433
|
+
});
|
|
434
|
+
return `${lines.join('\n')}\n`;
|
|
435
|
+
}
|
|
436
|
+
function redactProfile(profile) {
|
|
437
|
+
return profile.openrouterApiKey
|
|
438
|
+
? { ...profile, openrouterApiKey: maskSecret(profile.openrouterApiKey) }
|
|
439
|
+
: profile;
|
|
440
|
+
}
|
|
441
|
+
function redactConfigKeys(file) {
|
|
442
|
+
const out = file.openrouterApiKey
|
|
443
|
+
? { ...file, openrouterApiKey: maskSecret(file.openrouterApiKey) }
|
|
444
|
+
: { ...file };
|
|
445
|
+
if (out.profiles) {
|
|
446
|
+
const profiles = {};
|
|
447
|
+
for (const [name, profile] of Object.entries(out.profiles)) {
|
|
448
|
+
profiles[name] = redactProfile(profile);
|
|
449
|
+
}
|
|
450
|
+
out.profiles = profiles;
|
|
451
|
+
}
|
|
452
|
+
return out;
|
|
453
|
+
}
|
|
365
454
|
const defaultAuthStatus = (cwd) => new GitHubClient(cwd).authStatus();
|
|
366
455
|
async function defaultRunLoop(input) {
|
|
367
456
|
return runLoopAdapter(input);
|
|
@@ -13,6 +13,9 @@ const CLAUDE_PROJECT_MCP_FILE = '.mcp.json';
|
|
|
13
13
|
const CLAUDE_USER_FILE = '.claude.json';
|
|
14
14
|
const KNOWN_KEYS = new Set([
|
|
15
15
|
'openrouterApiKey',
|
|
16
|
+
'activeProfile',
|
|
17
|
+
'profiles',
|
|
18
|
+
'baseURL',
|
|
16
19
|
'models',
|
|
17
20
|
'maxPrs',
|
|
18
21
|
'maxSessions',
|
|
@@ -50,10 +53,13 @@ export class ConfigLoader {
|
|
|
50
53
|
const project = await this.readProject();
|
|
51
54
|
const claudeUser = await this.readClaudeUserMcp();
|
|
52
55
|
const claudeProject = await this.readClaudeProjectMcp();
|
|
53
|
-
const
|
|
56
|
+
const active = this.resolveActiveProfile(global);
|
|
57
|
+
const profile = active?.profile;
|
|
58
|
+
const { apiKey, apiKeySource } = this.resolveApiKey(global, project, profile);
|
|
54
59
|
if (apiKey === undefined || apiKeySource === undefined) {
|
|
55
|
-
throw new Error('No OpenRouter API key found. Set OPENROUTER_API_KEY env,
|
|
56
|
-
'"openrouterApiKey" to ~/.aitm.json or ./.ai-task-master/config.json
|
|
60
|
+
throw new Error('No OpenRouter API key found. Set OPENROUTER_API_KEY env, add ' +
|
|
61
|
+
'"openrouterApiKey" to ~/.aitm.json or ./.ai-task-master/config.json, or ' +
|
|
62
|
+
'create a profile with `aitm profile add <name> --api-key <key>`.');
|
|
57
63
|
}
|
|
58
64
|
const { mcpServers, mcpServerSources } = this.resolveMcpServers({
|
|
59
65
|
aitmGlobal: global?.mcpServers,
|
|
@@ -64,7 +70,9 @@ export class ConfigLoader {
|
|
|
64
70
|
return {
|
|
65
71
|
openrouterApiKey: apiKey,
|
|
66
72
|
apiKeySource,
|
|
67
|
-
|
|
73
|
+
...(active ? { activeProfile: active.name } : {}),
|
|
74
|
+
baseURL: this.resolveBaseURL(global, project, profile),
|
|
75
|
+
models: this.resolveModels(global, project, profile, cliOverrides),
|
|
68
76
|
maxPrs: pick(cliOverrides.maxPrs, project?.maxPrs, global?.maxPrs, DEFAULTS.maxPrs),
|
|
69
77
|
maxSessions: pickNullable(cliOverrides.maxSessions, project?.maxSessions, global?.maxSessions, DEFAULTS.maxSessions),
|
|
70
78
|
autoMerge: pick(cliOverrides.autoMerge, project?.autoMerge, global?.autoMerge, DEFAULTS.autoMerge),
|
|
@@ -178,27 +186,58 @@ export class ConfigLoader {
|
|
|
178
186
|
}
|
|
179
187
|
return validated;
|
|
180
188
|
}
|
|
181
|
-
|
|
189
|
+
resolveActiveProfile(global) {
|
|
190
|
+
const name = global?.activeProfile;
|
|
191
|
+
if (!name)
|
|
192
|
+
return undefined;
|
|
193
|
+
const profile = global?.profiles?.[name];
|
|
194
|
+
if (!profile) {
|
|
195
|
+
this.warn(`activeProfile "${name}" is set in ~/.aitm.json but no such profile exists — ignoring it. ` +
|
|
196
|
+
'Run `aitm profile list` to see available profiles.');
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
return { name, profile };
|
|
200
|
+
}
|
|
201
|
+
resolveBaseURL(global, project, profile) {
|
|
202
|
+
if (project?.baseURL)
|
|
203
|
+
return project.baseURL;
|
|
204
|
+
if (global?.baseURL)
|
|
205
|
+
return global.baseURL;
|
|
206
|
+
if (profile?.baseURL)
|
|
207
|
+
return profile.baseURL;
|
|
208
|
+
const env = this.env.OPENROUTER_BASE_URL?.trim();
|
|
209
|
+
if (!env)
|
|
210
|
+
return undefined;
|
|
211
|
+
const parsed = z.url().safeParse(env);
|
|
212
|
+
if (!parsed.success) {
|
|
213
|
+
throw new Error(`OPENROUTER_BASE_URL is not a valid URL: ${JSON.stringify(env)}`);
|
|
214
|
+
}
|
|
215
|
+
return parsed.data;
|
|
216
|
+
}
|
|
217
|
+
resolveApiKey(global, project, profile) {
|
|
182
218
|
if (project?.openrouterApiKey) {
|
|
183
219
|
return { apiKey: project.openrouterApiKey, apiKeySource: 'project' };
|
|
184
220
|
}
|
|
185
221
|
if (global?.openrouterApiKey) {
|
|
186
222
|
return { apiKey: global.openrouterApiKey, apiKeySource: 'global' };
|
|
187
223
|
}
|
|
224
|
+
if (profile?.openrouterApiKey) {
|
|
225
|
+
return { apiKey: profile.openrouterApiKey, apiKeySource: 'profile' };
|
|
226
|
+
}
|
|
188
227
|
const envKey = this.env.OPENROUTER_API_KEY;
|
|
189
228
|
if (envKey) {
|
|
190
229
|
return { apiKey: envKey, apiKeySource: 'env' };
|
|
191
230
|
}
|
|
192
231
|
return { apiKey: undefined, apiKeySource: undefined };
|
|
193
232
|
}
|
|
194
|
-
resolveModels(global, project, cliOverrides) {
|
|
233
|
+
resolveModels(global, project, profile, cliOverrides) {
|
|
195
234
|
const merged = {
|
|
196
235
|
generic: DEFAULT_MODELS.generic,
|
|
197
236
|
smart: DEFAULT_MODELS.smart,
|
|
198
237
|
coding: DEFAULT_MODELS.coding,
|
|
199
238
|
fast: DEFAULT_MODELS.fast,
|
|
200
239
|
};
|
|
201
|
-
for (const src of [global?.models, project?.models]) {
|
|
240
|
+
for (const src of [profile?.models, global?.models, project?.models]) {
|
|
202
241
|
if (!src)
|
|
203
242
|
continue;
|
|
204
243
|
if (src.generic)
|
|
@@ -6,8 +6,10 @@ import { ConfigFileSchema } from "./schema.js";
|
|
|
6
6
|
const GLOBAL_FILE = '.aitm.json';
|
|
7
7
|
const PROJECT_DIR = '.ai-task-master';
|
|
8
8
|
const PROJECT_FILE = 'config.json';
|
|
9
|
+
const PROFILE_MANAGED_KEYS = new Set(['activeProfile', 'profiles']);
|
|
9
10
|
const KNOWN_KEYS = new Set([
|
|
10
11
|
'openrouterApiKey',
|
|
12
|
+
'baseURL',
|
|
11
13
|
'models',
|
|
12
14
|
'maxPrs',
|
|
13
15
|
'maxSessions',
|
|
@@ -28,6 +30,7 @@ export class ConfigWriter {
|
|
|
28
30
|
async set(scope, key, value) {
|
|
29
31
|
const parts = splitKey(key);
|
|
30
32
|
const top = parts[0];
|
|
33
|
+
assertNotProfileManaged(top);
|
|
31
34
|
if (!KNOWN_KEYS.has(top)) {
|
|
32
35
|
throw new Error(unknownKeyMessage(top));
|
|
33
36
|
}
|
|
@@ -37,6 +40,7 @@ export class ConfigWriter {
|
|
|
37
40
|
}
|
|
38
41
|
async unset(scope, key) {
|
|
39
42
|
const parts = splitKey(key);
|
|
43
|
+
assertNotProfileManaged(parts[0]);
|
|
40
44
|
const file = await this.readRaw(scope);
|
|
41
45
|
unsetDottedKey(file, parts);
|
|
42
46
|
return this.validateAndPersist(scope, file);
|
|
@@ -158,6 +162,11 @@ function getDottedKey(obj, parts) {
|
|
|
158
162
|
}
|
|
159
163
|
return cur;
|
|
160
164
|
}
|
|
165
|
+
function assertNotProfileManaged(top) {
|
|
166
|
+
if (PROFILE_MANAGED_KEYS.has(top)) {
|
|
167
|
+
throw new Error(`"${top}" is managed by \`aitm profile …\`. Use the profile commands instead.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
161
170
|
function unknownKeyMessage(top) {
|
|
162
171
|
const allowed = [...KNOWN_KEYS].sort().join(', ');
|
|
163
172
|
return `Unknown config key "${top}". Allowed top-level keys: ${allowed}`;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type PresetName } from './provider-presets.ts';
|
|
2
|
+
import { type Profile } from './schema.ts';
|
|
3
|
+
export type AddProfileInput = {
|
|
4
|
+
preset?: PresetName;
|
|
5
|
+
baseURL?: string;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
};
|
|
8
|
+
export type ProfileListing = {
|
|
9
|
+
activeProfile: string | undefined;
|
|
10
|
+
profiles: Record<string, Profile>;
|
|
11
|
+
};
|
|
12
|
+
export declare class ProfileManager {
|
|
13
|
+
private readonly homeDir;
|
|
14
|
+
constructor(homeDir: string);
|
|
15
|
+
list(): Promise<ProfileListing>;
|
|
16
|
+
use(name: string): Promise<void>;
|
|
17
|
+
add(name: string, input?: AddProfileInput): Promise<Profile>;
|
|
18
|
+
set(name: string, key: string, value: unknown): Promise<Profile>;
|
|
19
|
+
get(name: string, key: string): Promise<unknown>;
|
|
20
|
+
remove(name: string): Promise<void>;
|
|
21
|
+
show(name?: string): Promise<{
|
|
22
|
+
name: string;
|
|
23
|
+
profile: Profile;
|
|
24
|
+
}>;
|
|
25
|
+
private filePath;
|
|
26
|
+
private readRaw;
|
|
27
|
+
private readValidated;
|
|
28
|
+
private validate;
|
|
29
|
+
private persist;
|
|
30
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { ZodError } from 'zod';
|
|
4
|
+
import { atomicWrite } from "../fs/atomic-write.js";
|
|
5
|
+
import { PROVIDER_PRESETS } from "./provider-presets.js";
|
|
6
|
+
import { ConfigFileSchema } from "./schema.js";
|
|
7
|
+
const GLOBAL_FILE = '.aitm.json';
|
|
8
|
+
export class ProfileManager {
|
|
9
|
+
homeDir;
|
|
10
|
+
constructor(homeDir) {
|
|
11
|
+
this.homeDir = homeDir;
|
|
12
|
+
}
|
|
13
|
+
async list() {
|
|
14
|
+
const validated = await this.readValidated();
|
|
15
|
+
return {
|
|
16
|
+
activeProfile: validated.activeProfile,
|
|
17
|
+
profiles: validated.profiles ?? {},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async use(name) {
|
|
21
|
+
const file = await this.readRaw();
|
|
22
|
+
const profiles = asObject(file.profiles);
|
|
23
|
+
if (!(name in profiles)) {
|
|
24
|
+
throw new Error(unknownProfileMessage(name, Object.keys(profiles)));
|
|
25
|
+
}
|
|
26
|
+
file.activeProfile = name;
|
|
27
|
+
await this.persist(file);
|
|
28
|
+
}
|
|
29
|
+
async add(name, input = {}) {
|
|
30
|
+
if (name.trim() === '')
|
|
31
|
+
throw new Error('Profile name must be non-empty.');
|
|
32
|
+
const file = await this.readRaw();
|
|
33
|
+
const profiles = ensureObject(file, 'profiles');
|
|
34
|
+
if (name in profiles) {
|
|
35
|
+
throw new Error(`Profile "${name}" already exists. Use \`aitm profile set ${name} <key> <value>\` to modify it.`);
|
|
36
|
+
}
|
|
37
|
+
const profile = input.preset
|
|
38
|
+
? jsonClone(PROVIDER_PRESETS[input.preset])
|
|
39
|
+
: {};
|
|
40
|
+
if (input.baseURL !== undefined)
|
|
41
|
+
profile.baseURL = input.baseURL;
|
|
42
|
+
if (input.apiKey !== undefined)
|
|
43
|
+
profile.openrouterApiKey = input.apiKey;
|
|
44
|
+
profiles[name] = profile;
|
|
45
|
+
if (file.activeProfile === undefined)
|
|
46
|
+
file.activeProfile = name;
|
|
47
|
+
const validated = await this.persist(file);
|
|
48
|
+
return validated.profiles?.[name] ?? {};
|
|
49
|
+
}
|
|
50
|
+
async set(name, key, value) {
|
|
51
|
+
const file = await this.readRaw();
|
|
52
|
+
const profiles = asObject(file.profiles);
|
|
53
|
+
if (!(name in profiles)) {
|
|
54
|
+
throw new Error(unknownProfileMessage(name, Object.keys(profiles)));
|
|
55
|
+
}
|
|
56
|
+
const profile = asObject(profiles[name]);
|
|
57
|
+
setDotted(profile, splitKey(key), parseValue(value));
|
|
58
|
+
profiles[name] = profile;
|
|
59
|
+
file.profiles = profiles;
|
|
60
|
+
const validated = await this.persist(file);
|
|
61
|
+
return validated.profiles?.[name] ?? {};
|
|
62
|
+
}
|
|
63
|
+
async get(name, key) {
|
|
64
|
+
const { profiles } = await this.list();
|
|
65
|
+
const profile = profiles[name];
|
|
66
|
+
if (profile === undefined) {
|
|
67
|
+
throw new Error(unknownProfileMessage(name, Object.keys(profiles)));
|
|
68
|
+
}
|
|
69
|
+
return getDotted(profile, splitKey(key));
|
|
70
|
+
}
|
|
71
|
+
async remove(name) {
|
|
72
|
+
const file = await this.readRaw();
|
|
73
|
+
const profiles = asObject(file.profiles);
|
|
74
|
+
if (!(name in profiles)) {
|
|
75
|
+
throw new Error(unknownProfileMessage(name, Object.keys(profiles)));
|
|
76
|
+
}
|
|
77
|
+
delete profiles[name];
|
|
78
|
+
if (file.activeProfile === name)
|
|
79
|
+
delete file.activeProfile;
|
|
80
|
+
await this.persist(file);
|
|
81
|
+
}
|
|
82
|
+
async show(name) {
|
|
83
|
+
const { activeProfile, profiles } = await this.list();
|
|
84
|
+
const target = name ?? activeProfile;
|
|
85
|
+
if (target === undefined) {
|
|
86
|
+
throw new Error('No profile specified and no active profile set. Pass a name or run `aitm profile use <name>`.');
|
|
87
|
+
}
|
|
88
|
+
const profile = profiles[target];
|
|
89
|
+
if (profile === undefined) {
|
|
90
|
+
throw new Error(unknownProfileMessage(target, Object.keys(profiles)));
|
|
91
|
+
}
|
|
92
|
+
return { name: target, profile };
|
|
93
|
+
}
|
|
94
|
+
filePath() {
|
|
95
|
+
return join(this.homeDir, GLOBAL_FILE);
|
|
96
|
+
}
|
|
97
|
+
async readRaw() {
|
|
98
|
+
const path = this.filePath();
|
|
99
|
+
let raw;
|
|
100
|
+
try {
|
|
101
|
+
raw = await readFile(path, 'utf8');
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (isNotFound(err))
|
|
105
|
+
return {};
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
let parsed;
|
|
109
|
+
try {
|
|
110
|
+
parsed = JSON.parse(raw);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
114
|
+
throw new Error(`${path}: invalid JSON — ${msg}`);
|
|
115
|
+
}
|
|
116
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
117
|
+
throw new Error(`${path}: expected a JSON object at the top level`);
|
|
118
|
+
}
|
|
119
|
+
return parsed;
|
|
120
|
+
}
|
|
121
|
+
async readValidated() {
|
|
122
|
+
return this.validate(await this.readRaw());
|
|
123
|
+
}
|
|
124
|
+
validate(file) {
|
|
125
|
+
try {
|
|
126
|
+
return ConfigFileSchema.parse(file);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (err instanceof ZodError)
|
|
130
|
+
throw new Error(`${this.filePath()}: ${formatZodError(err)}`);
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async persist(file) {
|
|
135
|
+
const validated = this.validate(file);
|
|
136
|
+
await atomicWrite(this.filePath(), `${JSON.stringify(validated, null, 2)}\n`);
|
|
137
|
+
return validated;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function asObject(value) {
|
|
141
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
142
|
+
? value
|
|
143
|
+
: {};
|
|
144
|
+
}
|
|
145
|
+
function ensureObject(parent, key) {
|
|
146
|
+
const next = asObject(parent[key]);
|
|
147
|
+
parent[key] = next;
|
|
148
|
+
return next;
|
|
149
|
+
}
|
|
150
|
+
function jsonClone(value) {
|
|
151
|
+
return JSON.parse(JSON.stringify(value));
|
|
152
|
+
}
|
|
153
|
+
const ALLOWED_PROFILE_ROOT_KEYS = new Set([
|
|
154
|
+
'openrouterApiKey',
|
|
155
|
+
'baseURL',
|
|
156
|
+
'models',
|
|
157
|
+
]);
|
|
158
|
+
const FORBIDDEN_KEY_SEGMENTS = new Set([
|
|
159
|
+
'__proto__',
|
|
160
|
+
'prototype',
|
|
161
|
+
'constructor',
|
|
162
|
+
]);
|
|
163
|
+
const KEY_SURFACE_HINT = 'Allowed keys: openrouterApiKey, baseURL, models.<tier>.';
|
|
164
|
+
function splitKey(key) {
|
|
165
|
+
const parts = key.split('.');
|
|
166
|
+
if (parts.length === 0 || parts.some((p) => p === '')) {
|
|
167
|
+
throw new Error(`Invalid profile key: "${key}". ${KEY_SURFACE_HINT}`);
|
|
168
|
+
}
|
|
169
|
+
if (parts.some((p) => FORBIDDEN_KEY_SEGMENTS.has(p))) {
|
|
170
|
+
throw new Error(`Invalid profile key: "${key}" — reserved segment. ${KEY_SURFACE_HINT}`);
|
|
171
|
+
}
|
|
172
|
+
const [first, ...rest] = parts;
|
|
173
|
+
if (first === undefined || !ALLOWED_PROFILE_ROOT_KEYS.has(first)) {
|
|
174
|
+
throw new Error(`Invalid profile key: "${key}". ${KEY_SURFACE_HINT}`);
|
|
175
|
+
}
|
|
176
|
+
if (first === 'models' ? rest.length !== 1 : rest.length !== 0) {
|
|
177
|
+
throw new Error(`Invalid profile key: "${key}". ${KEY_SURFACE_HINT}`);
|
|
178
|
+
}
|
|
179
|
+
return [first, ...rest];
|
|
180
|
+
}
|
|
181
|
+
function setDotted(obj, parts, value) {
|
|
182
|
+
const [first, ...rest] = parts;
|
|
183
|
+
if (first === undefined)
|
|
184
|
+
return;
|
|
185
|
+
if (rest.length === 0) {
|
|
186
|
+
obj[first] = value;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const sub = asObject(obj[first]);
|
|
190
|
+
obj[first] = sub;
|
|
191
|
+
setDotted(sub, rest, value);
|
|
192
|
+
}
|
|
193
|
+
function getDotted(obj, parts) {
|
|
194
|
+
let cur = obj;
|
|
195
|
+
for (const p of parts) {
|
|
196
|
+
if (cur === null || typeof cur !== 'object' || Array.isArray(cur))
|
|
197
|
+
return undefined;
|
|
198
|
+
cur = cur[p];
|
|
199
|
+
}
|
|
200
|
+
return cur;
|
|
201
|
+
}
|
|
202
|
+
function parseValue(v) {
|
|
203
|
+
if (typeof v !== 'string')
|
|
204
|
+
return v;
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(v);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return v;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function unknownProfileMessage(name, available) {
|
|
213
|
+
const list = available.length > 0 ? available.slice().sort().join(', ') : '(none)';
|
|
214
|
+
return `Unknown profile "${name}". Available: ${list}. Create it with \`aitm profile add ${name}\`.`;
|
|
215
|
+
}
|
|
216
|
+
function isNotFound(err) {
|
|
217
|
+
return (typeof err === 'object' &&
|
|
218
|
+
err !== null &&
|
|
219
|
+
'code' in err &&
|
|
220
|
+
err.code === 'ENOENT');
|
|
221
|
+
}
|
|
222
|
+
function formatZodError(err) {
|
|
223
|
+
return err.issues.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`).join('; ');
|
|
224
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Profile } from './schema.ts';
|
|
2
|
+
export type PresetName = 'openrouter' | 'zai';
|
|
3
|
+
export declare const PROVIDER_PRESETS: Readonly<Record<PresetName, Profile>>;
|
|
4
|
+
export declare const PRESET_NAMES: readonly PresetName[];
|
|
5
|
+
export declare function isPresetName(s: string): s is PresetName;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const PROVIDER_PRESETS = {
|
|
2
|
+
openrouter: {
|
|
3
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
|
4
|
+
},
|
|
5
|
+
zai: {
|
|
6
|
+
baseURL: 'https://api.z.ai/api/coding/paas/v4',
|
|
7
|
+
models: {
|
|
8
|
+
generic: 'glm-5.2',
|
|
9
|
+
smart: 'glm-5.2',
|
|
10
|
+
coding: 'glm-5.2',
|
|
11
|
+
fast: 'glm-5-turbo',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export const PRESET_NAMES = Object.keys(PROVIDER_PRESETS);
|
|
16
|
+
export function isPresetName(s) {
|
|
17
|
+
return Object.hasOwn(PROVIDER_PRESETS, s);
|
|
18
|
+
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -18,8 +18,31 @@ export declare const MergeMethodSchema: z.ZodEnum<{
|
|
|
18
18
|
merge: "merge";
|
|
19
19
|
rebase: "rebase";
|
|
20
20
|
}>;
|
|
21
|
+
export declare const ProfileSchema: z.ZodObject<{
|
|
22
|
+
openrouterApiKey: z.ZodOptional<z.ZodString>;
|
|
23
|
+
baseURL: z.ZodOptional<z.ZodURL>;
|
|
24
|
+
models: z.ZodOptional<z.ZodObject<{
|
|
25
|
+
generic: z.ZodOptional<z.ZodString>;
|
|
26
|
+
smart: z.ZodOptional<z.ZodString>;
|
|
27
|
+
coding: z.ZodOptional<z.ZodString>;
|
|
28
|
+
fast: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, z.core.$loose>>;
|
|
30
|
+
}, z.core.$loose>;
|
|
31
|
+
export type Profile = z.infer<typeof ProfileSchema>;
|
|
21
32
|
export declare const ConfigFileSchema: z.ZodObject<{
|
|
22
33
|
openrouterApiKey: z.ZodOptional<z.ZodString>;
|
|
34
|
+
activeProfile: z.ZodOptional<z.ZodString>;
|
|
35
|
+
profiles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
36
|
+
openrouterApiKey: z.ZodOptional<z.ZodString>;
|
|
37
|
+
baseURL: z.ZodOptional<z.ZodURL>;
|
|
38
|
+
models: z.ZodOptional<z.ZodObject<{
|
|
39
|
+
generic: z.ZodOptional<z.ZodString>;
|
|
40
|
+
smart: z.ZodOptional<z.ZodString>;
|
|
41
|
+
coding: z.ZodOptional<z.ZodString>;
|
|
42
|
+
fast: z.ZodOptional<z.ZodString>;
|
|
43
|
+
}, z.core.$loose>>;
|
|
44
|
+
}, z.core.$loose>>>;
|
|
45
|
+
baseURL: z.ZodOptional<z.ZodURL>;
|
|
23
46
|
models: z.ZodOptional<z.ZodObject<{
|
|
24
47
|
generic: z.ZodOptional<z.ZodString>;
|
|
25
48
|
smart: z.ZodOptional<z.ZodString>;
|
|
@@ -71,7 +94,9 @@ export type CliOverrides = {
|
|
|
71
94
|
};
|
|
72
95
|
export type ResolvedConfig = {
|
|
73
96
|
openrouterApiKey: string;
|
|
74
|
-
apiKeySource: 'project' | 'global' | 'env';
|
|
97
|
+
apiKeySource: 'project' | 'global' | 'env' | 'profile';
|
|
98
|
+
activeProfile?: string | undefined;
|
|
99
|
+
baseURL?: string | undefined;
|
|
75
100
|
models: Required<Pick<CapabilityModels, 'generic' | 'smart' | 'coding' | 'fast'>>;
|
|
76
101
|
maxPrs: number;
|
|
77
102
|
maxSessions: number | null;
|
package/dist/config/schema.js
CHANGED
|
@@ -10,9 +10,19 @@ export const CapabilityModelsSchema = z
|
|
|
10
10
|
.passthrough();
|
|
11
11
|
export const LogLevelSchema = z.enum(['debug', 'info', 'warn', 'error']);
|
|
12
12
|
export const MergeMethodSchema = z.enum(['squash', 'merge', 'rebase']);
|
|
13
|
+
export const ProfileSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
openrouterApiKey: z.string().optional(),
|
|
16
|
+
baseURL: z.url().optional(),
|
|
17
|
+
models: CapabilityModelsSchema.optional(),
|
|
18
|
+
})
|
|
19
|
+
.passthrough();
|
|
13
20
|
export const ConfigFileSchema = z
|
|
14
21
|
.object({
|
|
15
22
|
openrouterApiKey: z.string().optional(),
|
|
23
|
+
activeProfile: z.string().optional(),
|
|
24
|
+
profiles: z.record(z.string(), ProfileSchema).optional(),
|
|
25
|
+
baseURL: z.url().optional(),
|
|
16
26
|
models: CapabilityModelsSchema.optional(),
|
|
17
27
|
maxPrs: z.number().int().positive().optional(),
|
|
18
28
|
maxSessions: z.number().int().positive().nullable().optional(),
|
|
@@ -3,6 +3,10 @@ import type { Capability, ResolvedConfig } from '../config/schema.ts';
|
|
|
3
3
|
export type Role = 'planner' | 'worker' | 'reviewer' | 'orchestrator';
|
|
4
4
|
export declare const ROLE_CAPABILITY: Readonly<Record<Role, Capability>>;
|
|
5
5
|
export type ModelHandles = Record<Role, LanguageModel>;
|
|
6
|
+
export declare function providerSettings(resolved: ResolvedConfig): {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
baseURL?: string;
|
|
9
|
+
};
|
|
6
10
|
export declare class Credentials {
|
|
7
11
|
private readonly resolved;
|
|
8
12
|
private providerInstance;
|
|
@@ -6,6 +6,12 @@ export const ROLE_CAPABILITY = {
|
|
|
6
6
|
reviewer: 'smart',
|
|
7
7
|
orchestrator: 'fast',
|
|
8
8
|
};
|
|
9
|
+
export function providerSettings(resolved) {
|
|
10
|
+
return {
|
|
11
|
+
apiKey: resolved.openrouterApiKey,
|
|
12
|
+
...(resolved.baseURL ? { baseURL: resolved.baseURL } : {}),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
9
15
|
export class Credentials {
|
|
10
16
|
resolved;
|
|
11
17
|
providerInstance;
|
|
@@ -37,7 +43,7 @@ export class Credentials {
|
|
|
37
43
|
provider() {
|
|
38
44
|
if (!this.providerInstance) {
|
|
39
45
|
Credentials.assertApiKeyPresent(this.resolved);
|
|
40
|
-
this.providerInstance = createOpenRouter(
|
|
46
|
+
this.providerInstance = createOpenRouter(providerSettings(this.resolved));
|
|
41
47
|
}
|
|
42
48
|
return this.providerInstance;
|
|
43
49
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { submittedOutput } from '@developerz.ai/ai-claude-compat';
|
|
2
|
+
import { generateText, hasToolCall, stepCountIs, ToolLoopAgent, tool } from 'ai';
|
|
2
3
|
import { ExecaError, execa } from 'execa';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
import { makePlannerTool, makeReviewerTool, makeWorkerTool, } from "./subagent-tools.js";
|
|
@@ -133,15 +134,26 @@ export class Orchestrator {
|
|
|
133
134
|
const result = await generateText({
|
|
134
135
|
model: this.init.credentials.modelFor('orchestrator'),
|
|
135
136
|
prompt: this.buildPrPrompt(group, delivery),
|
|
136
|
-
|
|
137
|
+
tools: {
|
|
138
|
+
submit: tool({
|
|
139
|
+
description: 'Submit the composed pull-request title and body (the PrComposition schema).',
|
|
140
|
+
inputSchema: PrCompositionSchema,
|
|
141
|
+
execute: async (composition) => composition,
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
144
|
+
toolChoice: { type: 'tool', toolName: 'submit' },
|
|
137
145
|
});
|
|
138
|
-
|
|
146
|
+
const out = submittedOutput(result, PrCompositionSchema);
|
|
147
|
+
if (!out) {
|
|
148
|
+
throw new Error('orchestrator did not submit a PR composition');
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
139
151
|
}
|
|
140
152
|
buildPrPrompt(group, delivery) {
|
|
141
153
|
return [
|
|
142
154
|
this.buildSystemPrompt(),
|
|
143
155
|
'',
|
|
144
|
-
'Compose the pull-request title and body for this PR group
|
|
156
|
+
'Compose the pull-request title and body for this PR group, then call the submit tool with it.',
|
|
145
157
|
'- title: conventional-commit style, ≤72 chars',
|
|
146
158
|
'- body: short summary + bulleted file changes + relevant rolling context',
|
|
147
159
|
'',
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { GlobInput, GlobOutput, GrepInput, GrepOutput, ReadFileInput, ReadFileOutput } from '@developerz.ai/ai-claude-compat';
|
|
2
|
-
import { type
|
|
2
|
+
import { type Tool, type ToolLoopAgent } from 'ai';
|
|
3
3
|
import { type Plan } from '../plan/schema.ts';
|
|
4
4
|
import type { SubagentInit } from './factory.ts';
|
|
5
|
-
type
|
|
6
|
-
export type PlannerAgent = ToolLoopAgent<never, PlannerTools, PlannerOutput>;
|
|
5
|
+
export type PlannerAgent = ToolLoopAgent<never, PlannerTools>;
|
|
7
6
|
export type PlannerTools = {
|
|
8
7
|
readFile: Tool<ReadFileInput, ReadFileOutput>;
|
|
9
8
|
grep: Tool<GrepInput, GrepOutput>;
|
|
@@ -28,4 +27,3 @@ export type PlannerResult = {
|
|
|
28
27
|
export declare const PLANNER_SYSTEM_PREFIX: string;
|
|
29
28
|
export declare function createPlannerAgent(init: SubagentInit<PlannerTools>): PlannerAgent;
|
|
30
29
|
export declare function runPlanner(agent: PlannerAgent, input: PlannerInput): Promise<PlannerResult>;
|
|
31
|
-
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createSubagent } from '@developerz.ai/ai-claude-compat';
|
|
2
|
-
import {
|
|
1
|
+
import { createSubagent, submittedOutput } from '@developerz.ai/ai-claude-compat';
|
|
2
|
+
import { tool } from 'ai';
|
|
3
3
|
import { PlanSchema } from "../plan/schema.js";
|
|
4
4
|
export const PLANNER_SYSTEM_PREFIX = [
|
|
5
5
|
'',
|
|
@@ -15,14 +15,18 @@ export const PLANNER_SYSTEM_PREFIX = [
|
|
|
15
15
|
'- Prefer parallelizable siblings over a single linear chain.',
|
|
16
16
|
'- Do not invent files. Do not propose work outside the repo.',
|
|
17
17
|
'',
|
|
18
|
-
'
|
|
18
|
+
'When the plan is ready, call the `submit` tool exactly once with the Plan (matching the Plan schema).',
|
|
19
19
|
].join('\n');
|
|
20
20
|
export function createPlannerAgent(init) {
|
|
21
21
|
return createSubagent({
|
|
22
22
|
model: init.model,
|
|
23
23
|
tools: init.tools,
|
|
24
24
|
systemPrompt: init.systemPrompt,
|
|
25
|
-
|
|
25
|
+
submit: tool({
|
|
26
|
+
description: 'Submit the finished plan as an ordered list of PR groups (the Plan schema).',
|
|
27
|
+
inputSchema: PlanSchema,
|
|
28
|
+
execute: async (plan) => plan,
|
|
29
|
+
}),
|
|
26
30
|
...(init.maxSteps !== undefined ? { maxSteps: init.maxSteps } : {}),
|
|
27
31
|
}, 20);
|
|
28
32
|
}
|
|
@@ -32,8 +36,8 @@ export async function runPlanner(agent, input) {
|
|
|
32
36
|
}
|
|
33
37
|
try {
|
|
34
38
|
const result = await agent.generate({ prompt: buildUserPrompt(input) });
|
|
35
|
-
const raw = result
|
|
36
|
-
if (!raw
|
|
39
|
+
const raw = submittedOutput(result, PlanSchema);
|
|
40
|
+
if (!raw || raw.groups.length === 0) {
|
|
37
41
|
return { kind: 'blocked', reason: 'planner returned an empty group list' };
|
|
38
42
|
}
|
|
39
43
|
return { kind: 'ok', plan: capGroups(raw, input.maxPrs) };
|
|
@@ -42,16 +46,13 @@ export async function runPlanner(agent, input) {
|
|
|
42
46
|
return { kind: 'error', error: err instanceof Error ? err.message : String(err) };
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
|
-
function plannerOutput() {
|
|
46
|
-
return Output.object({ schema: PlanSchema, name: 'Plan' });
|
|
47
|
-
}
|
|
48
49
|
function buildUserPrompt(input) {
|
|
49
50
|
const lines = [`Goal: ${input.goal}`];
|
|
50
51
|
if (input.criteria?.trim()) {
|
|
51
52
|
lines.push(`Acceptance criteria: ${input.criteria}`);
|
|
52
53
|
}
|
|
53
54
|
lines.push(`maxPrs: ${input.maxPrs}`);
|
|
54
|
-
lines.push('Survey the repo with the read-only tools, then
|
|
55
|
+
lines.push('Survey the repo with the read-only tools, then call submit with the Plan.');
|
|
55
56
|
return lines.join('\n');
|
|
56
57
|
}
|
|
57
58
|
function capGroups(plan, maxPrs) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Tool, type ToolLoopAgent } from 'ai';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type { ReviewThread } from '../github/schema.ts';
|
|
4
4
|
import type { SubagentInit } from './factory.ts';
|
|
@@ -24,8 +24,7 @@ export declare const ThreadResolutionOutputSchema: z.ZodObject<{
|
|
|
24
24
|
reason: z.ZodOptional<z.ZodString>;
|
|
25
25
|
}, z.core.$strip>;
|
|
26
26
|
export type ThreadResolutionOutput = z.infer<typeof ThreadResolutionOutputSchema>;
|
|
27
|
-
type
|
|
28
|
-
export type ReviewerAgent = ToolLoopAgent<never, ReviewerTools, ReviewerAgentOutput>;
|
|
27
|
+
export type ReviewerAgent = ToolLoopAgent<never, ReviewerTools>;
|
|
29
28
|
export type ReviewerInput = {
|
|
30
29
|
pr: number;
|
|
31
30
|
threads: ReviewThread[];
|
|
@@ -57,4 +56,3 @@ export type ReviewerResult = {
|
|
|
57
56
|
export declare const REVIEWER_SYSTEM_PREFIX: string;
|
|
58
57
|
export declare function createReviewerAgent(init: SubagentInit<ReviewerTools>): ReviewerAgent;
|
|
59
58
|
export declare function runReviewer(agent: ReviewerAgent, input: ReviewerInput): Promise<ReviewerResult>;
|
|
60
|
-
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createSubagent } from '@developerz.ai/ai-claude-compat';
|
|
2
|
-
import {
|
|
1
|
+
import { createSubagent, submittedOutput, } from '@developerz.ai/ai-claude-compat';
|
|
2
|
+
import { tool } from 'ai';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
export const ThreadResolutionOutputSchema = z.object({
|
|
5
5
|
kind: z.enum(['fixed', 'replied', 'wontfix']),
|
|
@@ -9,24 +9,24 @@ export const ThreadResolutionOutputSchema = z.object({
|
|
|
9
9
|
export const REVIEWER_SYSTEM_PREFIX = [
|
|
10
10
|
'',
|
|
11
11
|
'You are the Reviewer subagent. You receive ONE unresolved PR review thread at a time and',
|
|
12
|
-
'decide between three outcomes,
|
|
12
|
+
'decide between three outcomes, then call the `submit` tool with a ThreadResolutionOutput naming the choice.',
|
|
13
13
|
'',
|
|
14
14
|
'- "fixed": the reviewer is right and a code change is needed. Use your tools (grep/glob/',
|
|
15
15
|
' readFile to locate, editFile/multiEdit to change, writeFile to rewrite, bash for the rest)',
|
|
16
16
|
' to make the fix inside the worktree. DO NOT run `git commit` yourself — the runner commits',
|
|
17
17
|
' every staged change after you finish. Reply on the thread via the github tool explaining',
|
|
18
|
-
' the fix and resolve the thread, then
|
|
18
|
+
' the fix and resolve the thread, then submit { kind: "fixed", commitMessage } where',
|
|
19
19
|
' commitMessage is the subject line the runner will pass to `git commit`.',
|
|
20
20
|
'- "replied": the comment is a question or clarification request and no code change is needed.',
|
|
21
|
-
' Answer it via github.replyToThread. Do not edit code.
|
|
21
|
+
' Answer it via github.replyToThread. Do not edit code. Submit { kind: "replied" }.',
|
|
22
22
|
'- "wontfix": the suggestion is stale, out of scope, or you disagree. Reply with the reason',
|
|
23
|
-
' via github.replyToThread, resolve the thread via github.resolveThread, and
|
|
23
|
+
' via github.replyToThread, resolve the thread via github.resolveThread, and submit',
|
|
24
24
|
' { kind: "wontfix", reason }.',
|
|
25
25
|
'',
|
|
26
26
|
'Rules:',
|
|
27
27
|
'- Stay inside the worktree. No work outside the repo.',
|
|
28
28
|
'- Resolve the thread for "fixed" and "wontfix" outcomes; "replied" leaves it open.',
|
|
29
|
-
'-
|
|
29
|
+
'- When done, call `submit` once with a value matching the ThreadResolutionOutput schema.',
|
|
30
30
|
].join('\n');
|
|
31
31
|
const reviewerInitRegistry = new WeakMap();
|
|
32
32
|
export function createReviewerAgent(init) {
|
|
@@ -34,9 +34,10 @@ export function createReviewerAgent(init) {
|
|
|
34
34
|
model: init.model,
|
|
35
35
|
tools: init.tools,
|
|
36
36
|
systemPrompt: init.systemPrompt,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
submit: tool({
|
|
38
|
+
description: 'Submit the resolution for this review thread (the ThreadResolutionOutput schema).',
|
|
39
|
+
inputSchema: ThreadResolutionOutputSchema,
|
|
40
|
+
execute: async (resolution) => resolution,
|
|
40
41
|
}),
|
|
41
42
|
...(init.maxSteps !== undefined ? { maxSteps: init.maxSteps } : {}),
|
|
42
43
|
}, 20);
|
|
@@ -67,7 +68,10 @@ export async function runReviewer(agent, input) {
|
|
|
67
68
|
}
|
|
68
69
|
async function resolveOneThread(agent, init, input, thread) {
|
|
69
70
|
const result = await agent.generate({ prompt: buildThreadPrompt(input, thread) });
|
|
70
|
-
const out = result
|
|
71
|
+
const out = submittedOutput(result, ThreadResolutionOutputSchema);
|
|
72
|
+
if (!out) {
|
|
73
|
+
return { threadId: thread.id, kind: 'wontfix', reason: 'reviewer did not submit a resolution' };
|
|
74
|
+
}
|
|
71
75
|
switch (out.kind) {
|
|
72
76
|
case 'fixed': {
|
|
73
77
|
const message = out.commitMessage?.trim() || `fix: address review thread ${thread.id}`;
|
|
@@ -92,7 +96,7 @@ function buildThreadPrompt(input, thread) {
|
|
|
92
96
|
for (const c of thread.comments) {
|
|
93
97
|
lines.push(` @${c.author}: ${c.body}`);
|
|
94
98
|
}
|
|
95
|
-
lines.push('', 'Decide the outcome, take the action, then
|
|
99
|
+
lines.push('', 'Decide the outcome, take the action, then call submit with the ThreadResolutionOutput.');
|
|
96
100
|
return lines.join('\n');
|
|
97
101
|
}
|
|
98
102
|
async function commitFix(bash, worktreePath, message) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BashInput, BashOutput, EditFileInput, EditFileOutput, GlobInput, GlobOutput, GrepInput, GrepOutput, MultiBashInput, MultiBashOutput, MultiEditInput, MultiEditOutput, ReadFileInput, ReadFileOutput, WriteFileInput, WriteFileOutput } from '@developerz.ai/ai-claude-compat';
|
|
2
|
-
import { type
|
|
2
|
+
import { type Tool, type ToolLoopAgent } from 'ai';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import type { PrGroup } from '../state/schema.ts';
|
|
5
5
|
import type { SubagentInit } from './factory.ts';
|
|
@@ -36,8 +36,7 @@ export declare const FileManifestSchema: z.ZodObject<{
|
|
|
36
36
|
draftCommitMessage: z.ZodString;
|
|
37
37
|
}, z.core.$strip>;
|
|
38
38
|
export type FileManifest = z.infer<typeof FileManifestSchema>;
|
|
39
|
-
type
|
|
40
|
-
export type WorkerAgent = ToolLoopAgent<never, WorkerTools, WorkerOutput>;
|
|
39
|
+
export type WorkerAgent = ToolLoopAgent<never, WorkerTools>;
|
|
41
40
|
export type WorkerInput = {
|
|
42
41
|
group: PrGroup;
|
|
43
42
|
worktreePath: string;
|
|
@@ -70,4 +69,3 @@ export type WorkerResult = {
|
|
|
70
69
|
export declare const WORKER_SYSTEM_PREFIX: string;
|
|
71
70
|
export declare function createWorkerAgent(init: SubagentInit<WorkerTools>): WorkerAgent;
|
|
72
71
|
export declare function runWorker(agent: WorkerAgent, input: WorkerInput): Promise<WorkerResult>;
|
|
73
|
-
export {};
|
package/dist/subagents/worker.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { composeSystemPrompt, createSubagent } from '@developerz.ai/ai-claude-compat';
|
|
2
|
-
import { generateText,
|
|
1
|
+
import { composeSystemPrompt, createSubagent, submittedOutput, } from '@developerz.ai/ai-claude-compat';
|
|
2
|
+
import { generateText, stepCountIs, tool } from 'ai';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
export const FileManifestEntrySchema = z.object({
|
|
5
5
|
path: z.string().min(1),
|
|
@@ -16,8 +16,8 @@ export const WORKER_SYSTEM_PREFIX = [
|
|
|
16
16
|
'land in a single pull request on a dedicated branch. Work in two phases.',
|
|
17
17
|
'',
|
|
18
18
|
'Phase 1 — manifest. Use your read-only tools (readFile with optional offset/limit, grep,',
|
|
19
|
-
'glob) to ground yourself in the existing code, then
|
|
20
|
-
'file to create/modify/delete plus a one-line draft commit message. Do not edit yet.',
|
|
19
|
+
'glob) to ground yourself in the existing code, then call the `submit` tool with a FileManifest',
|
|
20
|
+
'listing every file to create/modify/delete plus a one-line draft commit message. Do not edit yet.',
|
|
21
21
|
'',
|
|
22
22
|
'Phase 2 — edits. Each manifest entry is handed to a dedicated editor subagent in',
|
|
23
23
|
'parallel by the runtime; you do not execute Phase 2 yourself.',
|
|
@@ -26,7 +26,7 @@ export const WORKER_SYSTEM_PREFIX = [
|
|
|
26
26
|
'- Stay inside the worktree provided. No work outside the repo.',
|
|
27
27
|
'- One responsibility per file. If a file has multiple unrelated edits, split it.',
|
|
28
28
|
'- draftCommitMessage is a hint to the Orchestrator; keep the subject under 72 chars.',
|
|
29
|
-
'-
|
|
29
|
+
'- When the manifest is complete, call `submit` once with the FileManifest (matching the schema).',
|
|
30
30
|
].join('\n');
|
|
31
31
|
const EDITOR_SYSTEM_PREFIX = [
|
|
32
32
|
'',
|
|
@@ -50,7 +50,11 @@ export function createWorkerAgent(init) {
|
|
|
50
50
|
model: init.model,
|
|
51
51
|
tools: init.tools,
|
|
52
52
|
systemPrompt: init.systemPrompt,
|
|
53
|
-
|
|
53
|
+
submit: tool({
|
|
54
|
+
description: 'Submit the file manifest (the FileManifest schema) for this PR group.',
|
|
55
|
+
inputSchema: FileManifestSchema,
|
|
56
|
+
execute: async (manifest) => manifest,
|
|
57
|
+
}),
|
|
54
58
|
...(init.maxSteps !== undefined ? { maxSteps: init.maxSteps } : {}),
|
|
55
59
|
}, 30);
|
|
56
60
|
workerInitRegistry.set(agent, init);
|
|
@@ -91,7 +95,7 @@ export async function runWorker(agent, input) {
|
|
|
91
95
|
}
|
|
92
96
|
async function planManifest(agent, input) {
|
|
93
97
|
const result = await agent.generate({ prompt: buildManifestPrompt(input) });
|
|
94
|
-
return result
|
|
98
|
+
return submittedOutput(result, FileManifestSchema) ?? { files: [], draftCommitMessage: '' };
|
|
95
99
|
}
|
|
96
100
|
function buildManifestPrompt(input) {
|
|
97
101
|
const lines = [
|
|
@@ -106,7 +110,7 @@ function buildManifestPrompt(input) {
|
|
|
106
110
|
if (input.rollingContext.trim()) {
|
|
107
111
|
lines.push('', 'Rolling context from prior PRs:', input.rollingContext);
|
|
108
112
|
}
|
|
109
|
-
lines.push('', 'Survey the repo, then
|
|
113
|
+
lines.push('', 'Survey the repo, then call submit with the FileManifest.');
|
|
110
114
|
return lines.join('\n');
|
|
111
115
|
}
|
|
112
116
|
async function runEditor(init, file, input) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@developerz.ai/aitm",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Autonomous task orchestrator. Goal in, merged PRs out.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@ai-sdk/mcp": "^1.0.42",
|
|
45
|
-
"@developerz.ai/ai-claude-compat": "0.0.
|
|
45
|
+
"@developerz.ai/ai-claude-compat": "0.0.5",
|
|
46
46
|
"@openrouter/ai-sdk-provider": "^2.9.0",
|
|
47
47
|
"ai": "^6.0.182",
|
|
48
48
|
"execa": "^9.6.1",
|