pennyrouter 0.3.12 → 0.3.14
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/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/launch.js +14 -8
- package/src/launch.test.js +19 -2
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -174,7 +174,7 @@ async function profiles(flags) {
|
|
|
174
174
|
}
|
|
175
175
|
if (requested) {
|
|
176
176
|
// Resolve before saving so an unknown name fails without changing anything.
|
|
177
|
-
const selected = resolvePennyProfile(config, requested);
|
|
177
|
+
const selected = resolvePennyProfile(config, requested, { requireExisting: true });
|
|
178
178
|
await savePennyConfig({ ...selected, active_profile: requested });
|
|
179
179
|
console.log(`Active profile is now "${requested}" (${selected.gateway_base_url}).`);
|
|
180
180
|
return;
|
package/src/launch.js
CHANGED
|
@@ -151,16 +151,19 @@ export function activePennyProfile(config) {
|
|
|
151
151
|
* `gateway_base_url`. Every consumer reads those two fields, so resolving here keeps the
|
|
152
152
|
* profile lookup in one place rather than threading a profile through each caller.
|
|
153
153
|
*
|
|
154
|
-
*
|
|
155
|
-
* `install` has to
|
|
154
|
+
* A name that does not exist yet resolves to a profile with no credentials rather than
|
|
155
|
+
* throwing, because `install --profile private` has to read the profile it is about to
|
|
156
|
+
* create. Only an explicit `requireExisting` lookup — selecting a profile to launch or
|
|
157
|
+
* switch to — treats an unknown name as an error.
|
|
156
158
|
*/
|
|
157
|
-
export function resolvePennyProfile(config, name = null) {
|
|
159
|
+
export function resolvePennyProfile(config, name = null, { requireExisting = false } = {}) {
|
|
158
160
|
const requested = name || activePennyProfile(config);
|
|
159
161
|
const profiles = config.profiles || {};
|
|
160
162
|
const entry = profiles[requested];
|
|
161
163
|
if (!entry) {
|
|
162
|
-
if (
|
|
163
|
-
const
|
|
164
|
+
if (!requireExisting) return { ...config, profile: requested };
|
|
165
|
+
const names = Object.keys(profiles);
|
|
166
|
+
const known = names.length ? names.join(", ") : "none configured";
|
|
164
167
|
throw new Error(`Unknown PennyRouter profile "${requested}". Available: ${known}.`);
|
|
165
168
|
}
|
|
166
169
|
return {
|
|
@@ -183,13 +186,13 @@ export function setPennyProfile(config, name, { apiKey, gatewayBaseUrl }) {
|
|
|
183
186
|
};
|
|
184
187
|
}
|
|
185
188
|
|
|
186
|
-
export async function loadPennyConfig({ profile = null } = {}) {
|
|
189
|
+
export async function loadPennyConfig({ profile = null, requireExisting = false } = {}) {
|
|
187
190
|
const config = (await readJsonFile(pennyConfigPath(), null)) || {
|
|
188
191
|
version: CONFIG_VERSION,
|
|
189
192
|
profiles: {},
|
|
190
193
|
harnesses: {},
|
|
191
194
|
};
|
|
192
|
-
return resolvePennyProfile(config, profile);
|
|
195
|
+
return resolvePennyProfile(config, profile, { requireExisting });
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
/**
|
|
@@ -1039,7 +1042,10 @@ export async function runPenny(argv = process.argv.slice(2)) {
|
|
|
1039
1042
|
}
|
|
1040
1043
|
const selection = extractProfileArgs(argv.slice(1));
|
|
1041
1044
|
argv = [harness, ...selection.args];
|
|
1042
|
-
const config = await loadPennyConfig({
|
|
1045
|
+
const config = await loadPennyConfig({
|
|
1046
|
+
profile: selection.profile,
|
|
1047
|
+
requireExisting: Boolean(selection.profile),
|
|
1048
|
+
});
|
|
1043
1049
|
if (harness === "claude" && argv[1]?.toLowerCase() === "desktop") {
|
|
1044
1050
|
const option = argv[2]?.toLowerCase();
|
|
1045
1051
|
if (option === "--restore" || option === "restore") {
|
package/src/launch.test.js
CHANGED
|
@@ -284,6 +284,10 @@ async function main() {
|
|
|
284
284
|
});
|
|
285
285
|
assert.ok(paid.includes('env_key = "PENNYROUTER_API_KEY"'));
|
|
286
286
|
assert.ok(!paid.includes("requires_openai_auth"));
|
|
287
|
+
|
|
288
|
+
// Inside the fixture: testProfiles persists a config, which must land in the
|
|
289
|
+
// temporary HOME and never in the developer's real ~/.config/pennyrouter.
|
|
290
|
+
await testProfiles();
|
|
287
291
|
} finally {
|
|
288
292
|
for (const key of ENV_KEYS) {
|
|
289
293
|
if (previous[key] === undefined) delete process.env[key];
|
|
@@ -292,7 +296,6 @@ async function main() {
|
|
|
292
296
|
fs.rmSync(root, { recursive: true, force: true });
|
|
293
297
|
}
|
|
294
298
|
|
|
295
|
-
await testProfiles();
|
|
296
299
|
console.log("launch.test.js: all checks passed");
|
|
297
300
|
}
|
|
298
301
|
|
|
@@ -333,7 +336,21 @@ async function testProfiles() {
|
|
|
333
336
|
resolvePennyProfile(config, "cloud").gateway_base_url,
|
|
334
337
|
"https://api.pennyrouter.com",
|
|
335
338
|
);
|
|
336
|
-
|
|
339
|
+
// A not-yet-created profile resolves empty: `install --profile private` must be able to
|
|
340
|
+
// read the profile it is about to write, even when other profiles already exist.
|
|
341
|
+
const pending = resolvePennyProfile(config, "brand-new");
|
|
342
|
+
assert.equal(pending.profile, "brand-new");
|
|
343
|
+
assert.equal(pending.api_key, undefined);
|
|
344
|
+
// Selecting one to launch or switch to is the opposite: an unknown name must fail loudly
|
|
345
|
+
// rather than silently launching with no credentials.
|
|
346
|
+
assert.throws(
|
|
347
|
+
() => resolvePennyProfile(config, "nope", { requireExisting: true }),
|
|
348
|
+
/Unknown PennyRouter profile "nope"\. Available: cloud, private\./,
|
|
349
|
+
);
|
|
350
|
+
await assert.rejects(
|
|
351
|
+
loadPennyConfig({ profile: "nope", requireExisting: true }),
|
|
352
|
+
/Unknown PennyRouter profile/,
|
|
353
|
+
);
|
|
337
354
|
|
|
338
355
|
// A resolved config must not persist one profile's credentials at the top level,
|
|
339
356
|
// where they would shadow whichever profile is selected on the next load.
|