agentsw 0.6.1 → 0.7.0
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 +22 -0
- package/README.zh-CN.md +21 -0
- package/dist/commands.js +30 -40
- package/dist/config-transaction.js +138 -0
- package/dist/envfile.js +27 -0
- package/dist/i18n.js +24 -3
- package/dist/import.js +35 -21
- package/dist/index.js +16 -3
- package/dist/jsonc.js +111 -0
- package/dist/menu.js +90 -9
- package/dist/provider-actions.js +37 -0
- package/dist/remove.js +651 -0
- package/dist/rename.js +458 -0
- package/dist/slug.js +17 -8
- package/dist/sources/ccswitch.js +4 -2
- package/dist/targets/claudecode.js +4 -3
- package/dist/targets/omp.js +52 -13
- package/dist/targets/pistyle.js +57 -24
- package/dist/targets/workbuddy.js +11 -10
- package/package.json +2 -1
package/dist/targets/pistyle.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { backupFile, home,
|
|
3
|
+
import { backupFile, home, writeFileAtomic } from "../fsutil.js";
|
|
4
|
+
import { editJsoncObject, isJsonObject, readJsoncObject } from "../jsonc.js";
|
|
4
5
|
import { looksLikeEnvName } from "../slug.js";
|
|
5
6
|
import { apiValue, classifyApi, entryApi, mergeModels, sdkBaseUrl, stripConflictingOverrides } from "./wire.js";
|
|
6
7
|
/** Per-model keys this adapter writes; one that stops being emitted is cleared, not inherited. */
|
|
@@ -14,6 +15,28 @@ const OWNED_MODEL_KEYS = [
|
|
|
14
15
|
"maxTokens",
|
|
15
16
|
"cost",
|
|
16
17
|
];
|
|
18
|
+
function readModels(file) {
|
|
19
|
+
const document = readJsoncObject(file);
|
|
20
|
+
const providers = document.value.providers;
|
|
21
|
+
if (providers !== undefined) {
|
|
22
|
+
if (!isJsonObject(providers))
|
|
23
|
+
throw new Error(`${file}: expected providers to be a JSON object`);
|
|
24
|
+
for (const entry of Object.values(providers)) {
|
|
25
|
+
if (!isJsonObject(entry))
|
|
26
|
+
throw new Error(`${file}: expected every provider entry to be a JSON object`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { ...document, value: document.value };
|
|
30
|
+
}
|
|
31
|
+
function readSettings(file) {
|
|
32
|
+
const document = readJsoncObject(file);
|
|
33
|
+
for (const key of ["defaultProvider", "defaultModel"]) {
|
|
34
|
+
if (document.value[key] !== undefined && typeof document.value[key] !== "string") {
|
|
35
|
+
throw new Error(`${file}: expected ${key} to be a string`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { ...document, value: document.value };
|
|
39
|
+
}
|
|
17
40
|
/**
|
|
18
41
|
* pi-family agents (pi, prime-agent) share the same config layout:
|
|
19
42
|
* <dir>/models.json — { providers: { <id>: { baseUrl, apiKey, api, models[] } } }
|
|
@@ -50,14 +73,14 @@ export function piStyleTarget(opts) {
|
|
|
50
73
|
const modelsFile = path.join(dir, "models.json");
|
|
51
74
|
const settingsFile = path.join(dir, "settings.json");
|
|
52
75
|
const notes = [];
|
|
53
|
-
|
|
76
|
+
// Validate both files before preparing any writes, including a malformed settings file.
|
|
77
|
+
const modelsDocument = readModels(modelsFile);
|
|
78
|
+
const settingsDocument = readSettings(settingsFile);
|
|
79
|
+
const modelsConfig = { ...modelsDocument.value };
|
|
54
80
|
const providers = { ...modelsConfig.providers };
|
|
55
81
|
// Keys agentsw does not model (headers, authHeader, oauth, ...) and per-model extras
|
|
56
82
|
// are the user's; a re-sync overwrites only the fields it owns.
|
|
57
|
-
const
|
|
58
|
-
const prev = (existing && typeof existing === "object" && !Array.isArray(existing)
|
|
59
|
-
? existing
|
|
60
|
-
: {});
|
|
83
|
+
const prev = providers[provider.id] ?? {};
|
|
61
84
|
const api = apiValue(provider.protocol, provider.openaiApi, entryApi(prev));
|
|
62
85
|
const baseUrl = sdkBaseUrl(provider.protocol, provider.baseUrl);
|
|
63
86
|
const models = mergeModels(prev.models, provider.models.map((m) => ({
|
|
@@ -93,60 +116,70 @@ export function piStyleTarget(opts) {
|
|
|
93
116
|
models,
|
|
94
117
|
};
|
|
95
118
|
modelsConfig.providers = providers;
|
|
119
|
+
const settings = {
|
|
120
|
+
...settingsDocument.value,
|
|
121
|
+
defaultProvider: provider.id,
|
|
122
|
+
defaultModel: provider.defaultModel,
|
|
123
|
+
};
|
|
124
|
+
const modelsText = editJsoncObject(modelsDocument, modelsConfig);
|
|
125
|
+
const settingsText = editJsoncObject(settingsDocument, settings);
|
|
96
126
|
const modelsBackup = backupFile(modelsFile);
|
|
97
127
|
if (modelsBackup)
|
|
98
128
|
notes.push(`backup: ${modelsBackup}`);
|
|
99
|
-
writeFileAtomic(modelsFile,
|
|
100
|
-
const settings = readJsonIfExists(settingsFile) ?? {};
|
|
101
|
-
settings.defaultProvider = provider.id;
|
|
102
|
-
settings.defaultModel = provider.defaultModel;
|
|
129
|
+
writeFileAtomic(modelsFile, modelsText);
|
|
103
130
|
const settingsBackup = backupFile(settingsFile);
|
|
104
131
|
if (settingsBackup)
|
|
105
132
|
notes.push(`backup: ${settingsBackup}`);
|
|
106
|
-
writeFileAtomic(settingsFile,
|
|
133
|
+
writeFileAtomic(settingsFile, settingsText);
|
|
107
134
|
return { app: opts.id, changed: [modelsFile, settingsFile], notes };
|
|
108
135
|
},
|
|
109
136
|
async prune(provider) {
|
|
110
137
|
const dir = resolveDir();
|
|
111
138
|
const modelsFile = path.join(dir, "models.json");
|
|
112
139
|
const settingsFile = path.join(dir, "settings.json");
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
140
|
+
const modelsDocument = readModels(modelsFile);
|
|
141
|
+
const settingsDocument = readSettings(settingsFile);
|
|
142
|
+
const providers = { ...modelsDocument.value.providers };
|
|
143
|
+
if (!Object.hasOwn(providers, provider.id)) {
|
|
116
144
|
return { app: opts.id, changed: [], notes: [], skipped: `no providers.${provider.id} entry` };
|
|
117
145
|
}
|
|
118
146
|
delete providers[provider.id];
|
|
147
|
+
const settings = { ...settingsDocument.value };
|
|
148
|
+
const resetDefault = settings.defaultProvider === provider.id;
|
|
149
|
+
if (resetDefault) {
|
|
150
|
+
delete settings.defaultProvider;
|
|
151
|
+
delete settings.defaultModel;
|
|
152
|
+
}
|
|
153
|
+
const modelsText = editJsoncObject(modelsDocument, { ...modelsDocument.value, providers });
|
|
154
|
+
const settingsText = resetDefault ? editJsoncObject(settingsDocument, settings) : undefined;
|
|
119
155
|
const notes = [];
|
|
120
156
|
const changed = [modelsFile];
|
|
121
157
|
const modelsBackup = backupFile(modelsFile);
|
|
122
158
|
if (modelsBackup)
|
|
123
159
|
notes.push(`backup: ${modelsBackup}`);
|
|
124
|
-
writeFileAtomic(modelsFile,
|
|
125
|
-
|
|
126
|
-
if (settings?.defaultProvider === provider.id) {
|
|
127
|
-
delete settings.defaultProvider;
|
|
128
|
-
delete settings.defaultModel;
|
|
160
|
+
writeFileAtomic(modelsFile, modelsText);
|
|
161
|
+
if (settingsText !== undefined) {
|
|
129
162
|
const settingsBackup = backupFile(settingsFile);
|
|
130
163
|
if (settingsBackup)
|
|
131
164
|
notes.push(`backup: ${settingsBackup}`);
|
|
132
|
-
writeFileAtomic(settingsFile,
|
|
165
|
+
writeFileAtomic(settingsFile, settingsText);
|
|
133
166
|
changed.push(settingsFile);
|
|
134
167
|
notes.push("default model reset (was pointing at this provider)");
|
|
135
168
|
}
|
|
136
169
|
return { app: opts.id, changed, notes };
|
|
137
170
|
},
|
|
138
171
|
current() {
|
|
139
|
-
const settings =
|
|
172
|
+
const settings = readSettings(path.join(resolveDir(), "settings.json")).value;
|
|
140
173
|
if (!settings?.defaultProvider)
|
|
141
174
|
return undefined;
|
|
142
175
|
return `${settings.defaultProvider} · ${settings.defaultModel ?? "?"}`;
|
|
143
176
|
},
|
|
144
177
|
candidates() {
|
|
145
178
|
const dir = resolveDir();
|
|
146
|
-
const mc =
|
|
147
|
-
|
|
179
|
+
const mc = readModels(path.join(dir, "models.json")).value;
|
|
180
|
+
const settings = readSettings(path.join(dir, "settings.json")).value;
|
|
181
|
+
if (!mc.providers)
|
|
148
182
|
return [];
|
|
149
|
-
const settings = readJsonIfExists(path.join(dir, "settings.json"));
|
|
150
183
|
const self = opts.id;
|
|
151
184
|
return Object.entries(mc.providers).flatMap(([id, entry]) => {
|
|
152
185
|
if (!entry || typeof entry.baseUrl !== "string")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { appDataDir, backupFile, home, readJsonIfExists, writeFileAtomic } from "../fsutil.js";
|
|
4
|
-
import {
|
|
4
|
+
import { providerIdFromBaseUrl, providerNameFromBaseUrl } from "../slug.js";
|
|
5
5
|
function configDir() {
|
|
6
6
|
return process.env.WORKBUDDY_CONFIG_DIR?.trim() ?? process.env.CODEBUDDY_CONFIG_DIR?.trim() ??
|
|
7
7
|
(process.platform === "win32" ? appDataDir("workbuddy") : path.join(home, ".workbuddy"));
|
|
@@ -109,7 +109,7 @@ export const workbuddy = {
|
|
|
109
109
|
return settings?.model;
|
|
110
110
|
},
|
|
111
111
|
candidates() {
|
|
112
|
-
//
|
|
112
|
+
// Each model has its own endpoint and credential; different accounts must stay separate.
|
|
113
113
|
const { models } = readWorkbuddyModels(path.join(configDir(), "models.json"));
|
|
114
114
|
if (models.length === 0)
|
|
115
115
|
return [];
|
|
@@ -122,23 +122,24 @@ export const workbuddy = {
|
|
|
122
122
|
const baseUrl = row.url.replace(/\/+$/, "").replace(/\/chat\/completions$/, "");
|
|
123
123
|
if (!/^https?:\/\//.test(baseUrl))
|
|
124
124
|
continue;
|
|
125
|
-
|
|
125
|
+
const apiKey = typeof row.apiKey === "string" && row.apiKey ? row.apiKey : undefined;
|
|
126
|
+
const key = JSON.stringify([baseUrl, apiKey ?? null]);
|
|
127
|
+
let g = groups.get(key);
|
|
126
128
|
if (!g) {
|
|
127
|
-
g = { ids: [] };
|
|
128
|
-
groups.set(
|
|
129
|
+
g = { baseUrl, apiKey, ids: [] };
|
|
130
|
+
groups.set(key, g);
|
|
129
131
|
}
|
|
130
132
|
if (!g.ids.includes(m.id))
|
|
131
133
|
g.ids.push(m.id);
|
|
132
|
-
if (!g.apiKey && typeof row.apiKey === "string")
|
|
133
|
-
g.apiKey = row.apiKey;
|
|
134
134
|
if (!g.vendor && typeof row.vendor === "string")
|
|
135
135
|
g.vendor = row.vendor;
|
|
136
136
|
}
|
|
137
137
|
const self = this.id;
|
|
138
|
-
return [...groups.
|
|
139
|
-
const
|
|
138
|
+
return [...groups.values()].map((g) => {
|
|
139
|
+
const { baseUrl } = g;
|
|
140
|
+
const id = providerIdFromBaseUrl(baseUrl, "openai");
|
|
140
141
|
const defaultModel = settings?.model && g.ids.includes(settings.model) ? settings.model : undefined;
|
|
141
|
-
return { id, name: g.vendor ??
|
|
142
|
+
return { id, generatedId: true, name: g.vendor ?? providerNameFromBaseUrl(baseUrl, "openai"), protocol: "openai", baseUrl, apiKey: g.apiKey, models: g.ids, defaultModel, source: self };
|
|
142
143
|
});
|
|
143
144
|
},
|
|
144
145
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentsw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "CLI provider switcher for coding agents: manage OpenAI/Anthropic-protocol providers in one place and sync them, with models.dev metadata, into Claude Code, Codex, omp, pi, prime-agent, opencode, Hermes, WorkBuddy and DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"commander": "^15.0.0",
|
|
44
|
+
"jsonc-parser": "^3.3.1",
|
|
44
45
|
"picocolors": "^1.1.1",
|
|
45
46
|
"prompts": "^2.4.2",
|
|
46
47
|
"smol-toml": "^1.8.0",
|