@ouro.bot/cli 0.1.0-alpha.348 → 0.1.0-alpha.349
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/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.349",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`ouro use`, `ouro check`, provider-scoped `ouro status --agent`, and machine-pool `ouro auth` now operate on machine-local provider state and credentials, with safe provenance, explicit lane/provider/model repair guidance, and legacy `auth switch`/`config model` compatibility routed through `state/providers.json` instead of mutating synced `agent.json`."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.348",
|
|
6
12
|
"changes": [
|
|
@@ -63,6 +63,11 @@ const tasks_1 = require("../../repertoire/tasks");
|
|
|
63
63
|
const thoughts_1 = require("./thoughts");
|
|
64
64
|
const launchd_1 = require("./launchd");
|
|
65
65
|
const auth_flow_1 = require("../auth/auth-flow");
|
|
66
|
+
const provider_credential_pool_1 = require("../provider-credential-pool");
|
|
67
|
+
const provider_binding_resolver_1 = require("../provider-binding-resolver");
|
|
68
|
+
const provider_state_1 = require("../provider-state");
|
|
69
|
+
const machine_identity_1 = require("../machine-identity");
|
|
70
|
+
const provider_models_1 = require("../provider-models");
|
|
66
71
|
const ouro_version_manager_1 = require("../versioning/ouro-version-manager");
|
|
67
72
|
const cli_parse_1 = require("./cli-parse");
|
|
68
73
|
const cli_parse_2 = require("./cli-parse");
|
|
@@ -411,10 +416,6 @@ async function listGithubCopilotModels(baseUrl, token, fetchImpl = fetch) {
|
|
|
411
416
|
/* v8 ignore stop */
|
|
412
417
|
}
|
|
413
418
|
// ── Provider credential verification ──
|
|
414
|
-
/* v8 ignore next 3 -- only called from auth.switch inside integration block @preserve */
|
|
415
|
-
function hasStoredCredentials(provider, providerSecrets) {
|
|
416
|
-
return identity_1.PROVIDER_CREDENTIALS[provider].required.every((key) => !!providerSecrets[key]);
|
|
417
|
-
}
|
|
418
419
|
/* v8 ignore start -- verifyProviderCredentials: delegates to pingProvider @preserve */
|
|
419
420
|
async function verifyProviderCredentials(provider, providers) {
|
|
420
421
|
const config = providers[provider];
|
|
@@ -458,6 +459,382 @@ async function resolveHatchInput(command, deps) {
|
|
|
458
459
|
migrationPath: command.migrationPath,
|
|
459
460
|
};
|
|
460
461
|
}
|
|
462
|
+
// ── Provider state CLI helpers ──
|
|
463
|
+
function providerCliHomeDir(deps) {
|
|
464
|
+
if (!deps.secretsRoot)
|
|
465
|
+
return os.homedir();
|
|
466
|
+
return path.basename(deps.secretsRoot) === ".agentsecrets"
|
|
467
|
+
? path.dirname(deps.secretsRoot)
|
|
468
|
+
: deps.secretsRoot;
|
|
469
|
+
}
|
|
470
|
+
function providerCliAgentRoot(command, deps) {
|
|
471
|
+
return path.join(deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
472
|
+
}
|
|
473
|
+
function providerCliNow(deps) {
|
|
474
|
+
return new Date((deps.now ?? Date.now)());
|
|
475
|
+
}
|
|
476
|
+
function readOrBootstrapProviderState(agentName, deps) {
|
|
477
|
+
const agentRoot = providerCliAgentRoot({ agent: agentName }, deps);
|
|
478
|
+
const readResult = (0, provider_state_1.readProviderState)(agentRoot);
|
|
479
|
+
if (readResult.ok)
|
|
480
|
+
return { agentRoot, state: readResult.state };
|
|
481
|
+
if (readResult.reason === "invalid") {
|
|
482
|
+
throw new Error(`provider state for ${agentName} is invalid at ${readResult.statePath}: ${readResult.error}`);
|
|
483
|
+
}
|
|
484
|
+
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(agentName, deps.bundlesRoot);
|
|
485
|
+
const homeDir = providerCliHomeDir(deps);
|
|
486
|
+
const machine = (0, machine_identity_1.loadOrCreateMachineIdentity)({
|
|
487
|
+
homeDir,
|
|
488
|
+
now: () => providerCliNow(deps),
|
|
489
|
+
});
|
|
490
|
+
const state = (0, provider_state_1.bootstrapProviderStateFromAgentConfig)({
|
|
491
|
+
machineId: machine.machineId,
|
|
492
|
+
now: providerCliNow(deps),
|
|
493
|
+
agentConfig: {
|
|
494
|
+
humanFacing: {
|
|
495
|
+
provider: config.humanFacing.provider,
|
|
496
|
+
model: config.humanFacing.model || (0, provider_models_1.getDefaultModelForProvider)(config.humanFacing.provider),
|
|
497
|
+
},
|
|
498
|
+
agentFacing: {
|
|
499
|
+
provider: config.agentFacing.provider,
|
|
500
|
+
model: config.agentFacing.model || (0, provider_models_1.getDefaultModelForProvider)(config.agentFacing.provider),
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
(0, provider_state_1.writeProviderState)(agentRoot, state);
|
|
505
|
+
(0, runtime_1.emitNervesEvent)({
|
|
506
|
+
component: "daemon",
|
|
507
|
+
event: "daemon.provider_state_bootstrapped",
|
|
508
|
+
message: "bootstrapped local provider state from agent config",
|
|
509
|
+
meta: { agent: agentName, agentRoot },
|
|
510
|
+
});
|
|
511
|
+
return { agentRoot, state };
|
|
512
|
+
}
|
|
513
|
+
function credentialPingConfig(record) {
|
|
514
|
+
return {
|
|
515
|
+
...record.credentials,
|
|
516
|
+
...record.config,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function pingAttemptCount(result) {
|
|
520
|
+
if (typeof result.attempts === "number")
|
|
521
|
+
return result.attempts;
|
|
522
|
+
if (Array.isArray(result.attempts))
|
|
523
|
+
return result.attempts.length;
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
function providerCliLegacyRecord(agent, provider, deps) {
|
|
527
|
+
try {
|
|
528
|
+
const legacyCandidates = (0, provider_credential_pool_1.readLegacyAgentProviderCredentials)({
|
|
529
|
+
homeDir: providerCliHomeDir(deps),
|
|
530
|
+
agentName: agent,
|
|
531
|
+
});
|
|
532
|
+
const candidate = legacyCandidates.find((entry) => entry.provider === provider);
|
|
533
|
+
if (candidate)
|
|
534
|
+
return { credentials: candidate.credentials, config: candidate.config };
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// Fall through to the injected/secretsRoot-aware legacy reader below.
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(agent, { secretsRoot: deps.secretsRoot });
|
|
541
|
+
const providerSecrets = secrets.providers[provider];
|
|
542
|
+
const split = (0, provider_credential_pool_1.splitProviderCredentialFields)(provider, providerSecrets);
|
|
543
|
+
if (Object.keys(split.credentials).length === 0 && Object.keys(split.config).length === 0)
|
|
544
|
+
return null;
|
|
545
|
+
return split;
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function readProviderCredentialRecord(agent, provider, deps) {
|
|
552
|
+
const homeDir = providerCliHomeDir(deps);
|
|
553
|
+
const poolResult = (0, provider_credential_pool_1.readProviderCredentialPool)(homeDir);
|
|
554
|
+
if (poolResult.ok) {
|
|
555
|
+
const existing = poolResult.pool.providers[provider];
|
|
556
|
+
if (existing)
|
|
557
|
+
return { ok: true, record: existing };
|
|
558
|
+
}
|
|
559
|
+
else if (poolResult.reason === "invalid") {
|
|
560
|
+
return { ok: false, reason: "invalid", poolPath: poolResult.poolPath, error: poolResult.error };
|
|
561
|
+
}
|
|
562
|
+
const legacy = providerCliLegacyRecord(agent, provider, deps);
|
|
563
|
+
if (legacy) {
|
|
564
|
+
const record = (0, provider_credential_pool_1.upsertProviderCredential)({
|
|
565
|
+
homeDir,
|
|
566
|
+
provider,
|
|
567
|
+
credentials: legacy.credentials,
|
|
568
|
+
config: legacy.config,
|
|
569
|
+
provenance: {
|
|
570
|
+
source: "legacy-agent-secrets",
|
|
571
|
+
contributedByAgent: agent,
|
|
572
|
+
},
|
|
573
|
+
now: providerCliNow(deps),
|
|
574
|
+
});
|
|
575
|
+
return { ok: true, record };
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
ok: false,
|
|
579
|
+
reason: "missing",
|
|
580
|
+
poolPath: poolResult.poolPath,
|
|
581
|
+
error: `no credentials stored for ${provider}`,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function writeProviderBinding(input) {
|
|
585
|
+
const updatedAt = providerCliNow(input.deps).toISOString();
|
|
586
|
+
input.state.updatedAt = updatedAt;
|
|
587
|
+
input.state.lanes[input.lane] = {
|
|
588
|
+
provider: input.provider,
|
|
589
|
+
model: input.model,
|
|
590
|
+
source: "local",
|
|
591
|
+
updatedAt,
|
|
592
|
+
};
|
|
593
|
+
input.state.readiness[input.lane] = {
|
|
594
|
+
status: input.status,
|
|
595
|
+
provider: input.provider,
|
|
596
|
+
model: input.model,
|
|
597
|
+
checkedAt: updatedAt,
|
|
598
|
+
...(input.credentialRevision ? { credentialRevision: input.credentialRevision } : {}),
|
|
599
|
+
...(input.error ? { error: input.error } : {}),
|
|
600
|
+
...(input.attempts !== undefined ? { attempts: input.attempts } : {}),
|
|
601
|
+
};
|
|
602
|
+
(0, provider_state_1.writeProviderState)(input.agentRoot, input.state);
|
|
603
|
+
}
|
|
604
|
+
function writeProviderReadiness(input) {
|
|
605
|
+
const checkedAt = providerCliNow(input.deps).toISOString();
|
|
606
|
+
input.state.updatedAt = checkedAt;
|
|
607
|
+
input.state.readiness[input.lane] = {
|
|
608
|
+
status: input.status,
|
|
609
|
+
provider: input.provider,
|
|
610
|
+
model: input.model,
|
|
611
|
+
checkedAt,
|
|
612
|
+
credentialRevision: input.credentialRevision,
|
|
613
|
+
...(input.error ? { error: input.error } : {}),
|
|
614
|
+
...(input.attempts !== undefined ? { attempts: input.attempts } : {}),
|
|
615
|
+
};
|
|
616
|
+
(0, provider_state_1.writeProviderState)(input.agentRoot, input.state);
|
|
617
|
+
}
|
|
618
|
+
async function executeProviderUse(command, deps, options = {}) {
|
|
619
|
+
const writeMessage = (message) => {
|
|
620
|
+
if (options.writeStdout !== false)
|
|
621
|
+
deps.writeStdout(message);
|
|
622
|
+
return message;
|
|
623
|
+
};
|
|
624
|
+
const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
|
|
625
|
+
const credential = readProviderCredentialRecord(command.agent, command.provider, deps);
|
|
626
|
+
if (!credential.ok) {
|
|
627
|
+
if (!command.force) {
|
|
628
|
+
const message = [
|
|
629
|
+
`no credentials stored for ${command.provider}.`,
|
|
630
|
+
`Run \`ouro auth --agent ${command.agent} --provider ${command.provider}\` first.`,
|
|
631
|
+
].join("\n");
|
|
632
|
+
return writeMessage(message);
|
|
633
|
+
}
|
|
634
|
+
writeProviderBinding({
|
|
635
|
+
agentRoot,
|
|
636
|
+
state,
|
|
637
|
+
lane: command.lane,
|
|
638
|
+
provider: command.provider,
|
|
639
|
+
model: command.model,
|
|
640
|
+
deps,
|
|
641
|
+
status: "failed",
|
|
642
|
+
error: credential.error,
|
|
643
|
+
});
|
|
644
|
+
const message = `forced ${command.agent} ${command.lane} to ${command.provider} / ${command.model}: failed (${credential.error})`;
|
|
645
|
+
return writeMessage(message);
|
|
646
|
+
}
|
|
647
|
+
const pingResult = await (0, provider_ping_1.pingProvider)(command.provider, credentialPingConfig(credential.record), {
|
|
648
|
+
model: command.model,
|
|
649
|
+
attemptPolicy: { baseDelayMs: 0 },
|
|
650
|
+
sleep: deps.sleep,
|
|
651
|
+
});
|
|
652
|
+
const attempts = pingAttemptCount(pingResult);
|
|
653
|
+
if (!pingResult.ok && !command.force) {
|
|
654
|
+
const message = [
|
|
655
|
+
`${command.agent} ${command.lane} ${command.provider} / ${command.model}: failed (${pingResult.message})`,
|
|
656
|
+
`Fix credentials with \`ouro auth --agent ${command.agent} --provider ${command.provider}\` or force the local binding with \`ouro use --agent ${command.agent} --lane ${command.lane} --provider ${command.provider} --model ${command.model} --force\`.`,
|
|
657
|
+
].join("\n");
|
|
658
|
+
return writeMessage(message);
|
|
659
|
+
}
|
|
660
|
+
writeProviderBinding({
|
|
661
|
+
agentRoot,
|
|
662
|
+
state,
|
|
663
|
+
lane: command.lane,
|
|
664
|
+
provider: command.provider,
|
|
665
|
+
model: command.model,
|
|
666
|
+
deps,
|
|
667
|
+
status: pingResult.ok ? "ready" : "failed",
|
|
668
|
+
credentialRevision: credential.record.revision,
|
|
669
|
+
...(!pingResult.ok ? { error: pingResult.message } : {}),
|
|
670
|
+
...(attempts !== undefined ? { attempts } : {}),
|
|
671
|
+
});
|
|
672
|
+
const status = pingResult.ok ? "ready" : `failed (${pingResult.message})`;
|
|
673
|
+
const message = `${command.force ? "forced " : ""}${command.agent} ${command.lane} ${command.provider} / ${command.model}: ${status}`;
|
|
674
|
+
(0, runtime_1.emitNervesEvent)({
|
|
675
|
+
component: "daemon",
|
|
676
|
+
event: "daemon.provider_use_completed",
|
|
677
|
+
message: "provider use command completed",
|
|
678
|
+
meta: { agent: command.agent, lane: command.lane, provider: command.provider, model: command.model, status: pingResult.ok ? "ready" : "failed" },
|
|
679
|
+
});
|
|
680
|
+
return writeMessage(message);
|
|
681
|
+
}
|
|
682
|
+
async function executeProviderCheck(command, deps) {
|
|
683
|
+
const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
|
|
684
|
+
const binding = state.lanes[command.lane];
|
|
685
|
+
const credential = readProviderCredentialRecord(command.agent, binding.provider, deps);
|
|
686
|
+
if (!credential.ok) {
|
|
687
|
+
const message = [
|
|
688
|
+
`${command.agent} ${command.lane} ${binding.provider} / ${binding.model}: unknown (${credential.error})`,
|
|
689
|
+
`Run \`ouro auth --agent ${command.agent} --provider ${binding.provider}\` first.`,
|
|
690
|
+
].join("\n");
|
|
691
|
+
deps.writeStdout(message);
|
|
692
|
+
return message;
|
|
693
|
+
}
|
|
694
|
+
const pingResult = await (0, provider_ping_1.pingProvider)(binding.provider, credentialPingConfig(credential.record), {
|
|
695
|
+
model: binding.model,
|
|
696
|
+
attemptPolicy: { baseDelayMs: 0 },
|
|
697
|
+
sleep: deps.sleep,
|
|
698
|
+
});
|
|
699
|
+
const attempts = pingAttemptCount(pingResult);
|
|
700
|
+
writeProviderReadiness({
|
|
701
|
+
agentRoot,
|
|
702
|
+
state,
|
|
703
|
+
lane: command.lane,
|
|
704
|
+
provider: binding.provider,
|
|
705
|
+
model: binding.model,
|
|
706
|
+
deps,
|
|
707
|
+
status: pingResult.ok ? "ready" : "failed",
|
|
708
|
+
credentialRevision: credential.record.revision,
|
|
709
|
+
...(!pingResult.ok ? { error: pingResult.message } : {}),
|
|
710
|
+
...(attempts !== undefined ? { attempts } : {}),
|
|
711
|
+
});
|
|
712
|
+
const status = pingResult.ok ? "ready" : `failed (${pingResult.message})`;
|
|
713
|
+
const message = `${command.agent} ${command.lane} ${binding.provider} / ${binding.model}: ${status}`;
|
|
714
|
+
deps.writeStdout(message);
|
|
715
|
+
(0, runtime_1.emitNervesEvent)({
|
|
716
|
+
component: "daemon",
|
|
717
|
+
event: "daemon.provider_check_completed",
|
|
718
|
+
message: "provider check command completed",
|
|
719
|
+
meta: { agent: command.agent, lane: command.lane, provider: binding.provider, model: binding.model, status: pingResult.ok ? "ready" : "failed" },
|
|
720
|
+
});
|
|
721
|
+
return message;
|
|
722
|
+
}
|
|
723
|
+
function renderProviderCredentialLine(credential) {
|
|
724
|
+
if (credential.status === "present") {
|
|
725
|
+
const contributor = credential.contributedByAgent ? ` by ${credential.contributedByAgent}` : "";
|
|
726
|
+
const credentialFields = credential.credentialFields.length > 0 ? ` credentials: ${credential.credentialFields.join(", ")}` : " credentials: none";
|
|
727
|
+
const configFields = credential.configFields.length > 0 ? ` config: ${credential.configFields.join(", ")}` : " config: none";
|
|
728
|
+
return `credentials: present (${credential.source}${contributor}; ${credential.revision};${credentialFields};${configFields})`;
|
|
729
|
+
}
|
|
730
|
+
if (credential.status === "invalid-pool") {
|
|
731
|
+
return `credentials: invalid pool (${credential.error}); repair: ${credential.repair.command}`;
|
|
732
|
+
}
|
|
733
|
+
return `credentials: missing; repair: ${credential.repair.command}`;
|
|
734
|
+
}
|
|
735
|
+
function executeProviderStatus(command, deps) {
|
|
736
|
+
const agentRoot = providerCliAgentRoot(command, deps);
|
|
737
|
+
const homeDir = providerCliHomeDir(deps);
|
|
738
|
+
const lines = [`provider status: ${command.agent}`];
|
|
739
|
+
for (const lane of ["outward", "inner"]) {
|
|
740
|
+
const resolved = (0, provider_binding_resolver_1.resolveEffectiveProviderBinding)({
|
|
741
|
+
agentName: command.agent,
|
|
742
|
+
agentRoot,
|
|
743
|
+
homeDir,
|
|
744
|
+
lane,
|
|
745
|
+
});
|
|
746
|
+
if (!resolved.ok) {
|
|
747
|
+
lines.push(` ${lane}: unavailable`);
|
|
748
|
+
lines.push(` ${resolved.reason}: ${resolved.repair.command}`);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const binding = resolved.binding;
|
|
752
|
+
lines.push(` ${lane}: ${binding.provider} / ${binding.model} (${binding.source})`);
|
|
753
|
+
lines.push(` readiness: ${binding.readiness.status}${binding.readiness.error ? ` (${binding.readiness.error})` : ""}`);
|
|
754
|
+
lines.push(` ${renderProviderCredentialLine(binding.credential)}`);
|
|
755
|
+
for (const warning of binding.warnings) {
|
|
756
|
+
lines.push(` warning: ${warning.message}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const message = lines.join("\n");
|
|
760
|
+
deps.writeStdout(message);
|
|
761
|
+
return message;
|
|
762
|
+
}
|
|
763
|
+
async function executeLegacyAuthSwitch(command, deps) {
|
|
764
|
+
const { state } = readOrBootstrapProviderState(command.agent, deps);
|
|
765
|
+
const lanes = command.facing
|
|
766
|
+
? [command.facing === "human" ? "outward" : "inner"]
|
|
767
|
+
: ["outward", "inner"];
|
|
768
|
+
const messages = [];
|
|
769
|
+
for (const lane of lanes) {
|
|
770
|
+
const model = state.lanes[lane].model;
|
|
771
|
+
messages.push(await executeProviderUse({
|
|
772
|
+
kind: "provider.use",
|
|
773
|
+
agent: command.agent,
|
|
774
|
+
lane,
|
|
775
|
+
provider: command.provider,
|
|
776
|
+
model,
|
|
777
|
+
legacyFacing: command.facing,
|
|
778
|
+
}, deps, { writeStdout: false }));
|
|
779
|
+
}
|
|
780
|
+
const message = [
|
|
781
|
+
`deprecated: switched this machine's local provider binding. \`ouro auth switch\` no longer edits agent.json.`,
|
|
782
|
+
...messages,
|
|
783
|
+
`Use \`ouro use --agent ${command.agent} --lane <outward|inner> --provider ${command.provider} --model <model>\` for explicit provider/model selection.`,
|
|
784
|
+
].join("\n");
|
|
785
|
+
deps.writeStdout(message);
|
|
786
|
+
return message;
|
|
787
|
+
}
|
|
788
|
+
async function executeLegacyConfigModel(command, deps) {
|
|
789
|
+
const lane = command.facing === "agent" ? "inner" : "outward";
|
|
790
|
+
const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
|
|
791
|
+
const binding = state.lanes[lane];
|
|
792
|
+
if (binding.provider === "github-copilot") {
|
|
793
|
+
const credential = readProviderCredentialRecord(command.agent, "github-copilot", deps);
|
|
794
|
+
if (credential.ok) {
|
|
795
|
+
const ghConfig = {
|
|
796
|
+
...credential.record.config,
|
|
797
|
+
...credential.record.credentials,
|
|
798
|
+
};
|
|
799
|
+
const githubToken = ghConfig.githubToken;
|
|
800
|
+
const baseUrl = ghConfig.baseUrl;
|
|
801
|
+
if (typeof githubToken === "string" && typeof baseUrl === "string") {
|
|
802
|
+
const fetchFn = deps.fetchImpl ?? fetch;
|
|
803
|
+
try {
|
|
804
|
+
const models = await listGithubCopilotModels(baseUrl, githubToken, fetchFn);
|
|
805
|
+
const available = models.map((m) => m.id);
|
|
806
|
+
if (available.length > 0 && !available.includes(command.modelName)) {
|
|
807
|
+
const message = `model '${command.modelName}' not found. available models:\n${available.map((id) => ` ${id}`).join("\n")}`;
|
|
808
|
+
deps.writeStdout(message);
|
|
809
|
+
return message;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
// Catalog validation failed; the live ping below gives the actionable result.
|
|
814
|
+
}
|
|
815
|
+
const pingResult = await (0, provider_ping_1.pingGithubCopilotModel)(baseUrl, githubToken, command.modelName, fetchFn);
|
|
816
|
+
if (!pingResult.ok) {
|
|
817
|
+
const message = `model '${command.modelName}' ping failed: ${pingResult.error}\nrun \`ouro config models --agent ${command.agent}\` to see available models.`;
|
|
818
|
+
deps.writeStdout(message);
|
|
819
|
+
return message;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
const updatedAt = providerCliNow(deps).toISOString();
|
|
825
|
+
state.updatedAt = updatedAt;
|
|
826
|
+
state.lanes[lane] = {
|
|
827
|
+
...binding,
|
|
828
|
+
model: command.modelName,
|
|
829
|
+
source: "local",
|
|
830
|
+
updatedAt,
|
|
831
|
+
};
|
|
832
|
+
delete state.readiness[lane];
|
|
833
|
+
(0, provider_state_1.writeProviderState)(agentRoot, state);
|
|
834
|
+
const message = `deprecated: updated ${command.agent} model on ${lane}/${binding.provider}: ${binding.model} -> ${command.modelName}\nUse \`ouro use --agent ${command.agent} --lane ${lane} --provider ${binding.provider} --model ${command.modelName}\` next time.`;
|
|
835
|
+
deps.writeStdout(message);
|
|
836
|
+
return message;
|
|
837
|
+
}
|
|
461
838
|
// ── System setup ──
|
|
462
839
|
async function registerOuroBundleTypeNonBlocking(deps) {
|
|
463
840
|
const registerOuroBundleType = deps.registerOuroBundleType;
|
|
@@ -1731,6 +2108,16 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1731
2108
|
deps.writeStdout(message);
|
|
1732
2109
|
return message;
|
|
1733
2110
|
}
|
|
2111
|
+
// ── provider state commands (local, no daemon socket needed) ──
|
|
2112
|
+
if (command.kind === "provider.use") {
|
|
2113
|
+
return executeProviderUse(command, deps);
|
|
2114
|
+
}
|
|
2115
|
+
if (command.kind === "provider.check") {
|
|
2116
|
+
return executeProviderCheck(command, deps);
|
|
2117
|
+
}
|
|
2118
|
+
if (command.kind === "provider.status") {
|
|
2119
|
+
return executeProviderStatus(command, deps);
|
|
2120
|
+
}
|
|
1734
2121
|
// ── auth (local, no daemon socket needed) ──
|
|
1735
2122
|
if (command.kind === "auth.run") {
|
|
1736
2123
|
const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot).config.humanFacing.provider;
|
|
@@ -1741,6 +2128,21 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1741
2128
|
provider,
|
|
1742
2129
|
promptInput: deps.promptInput,
|
|
1743
2130
|
});
|
|
2131
|
+
const credentials = (result.credentials ?? {});
|
|
2132
|
+
const split = (0, provider_credential_pool_1.splitProviderCredentialFields)(provider, credentials);
|
|
2133
|
+
if (Object.keys(split.credentials).length > 0 || Object.keys(split.config).length > 0) {
|
|
2134
|
+
(0, provider_credential_pool_1.upsertProviderCredential)({
|
|
2135
|
+
homeDir: providerCliHomeDir(deps),
|
|
2136
|
+
provider,
|
|
2137
|
+
credentials: split.credentials,
|
|
2138
|
+
config: split.config,
|
|
2139
|
+
provenance: {
|
|
2140
|
+
source: "auth-flow",
|
|
2141
|
+
contributedByAgent: command.agent,
|
|
2142
|
+
},
|
|
2143
|
+
now: providerCliNow(deps),
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
1744
2146
|
// Behavior: ouro auth stores credentials only — does NOT switch provider.
|
|
1745
2147
|
// Use `ouro auth switch` to change the active provider.
|
|
1746
2148
|
deps.writeStdout(result.message);
|
|
@@ -1779,39 +2181,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1779
2181
|
}
|
|
1780
2182
|
// ── auth switch (local, no daemon socket needed) ──
|
|
1781
2183
|
if (command.kind === "auth.switch") {
|
|
1782
|
-
|
|
1783
|
-
const providerSecrets = secrets.providers[command.provider];
|
|
1784
|
-
if (!providerSecrets || !hasStoredCredentials(command.provider, providerSecrets)) {
|
|
1785
|
-
const message = `no credentials stored for ${command.provider}. Run \`ouro auth --agent ${command.agent} --provider ${command.provider}\` first.`;
|
|
1786
|
-
deps.writeStdout(message);
|
|
1787
|
-
return message;
|
|
1788
|
-
}
|
|
1789
|
-
// Verify credentials actually work before switching
|
|
1790
|
-
const status = await verifyProviderCredentials(command.provider, secrets.providers);
|
|
1791
|
-
if (!status.startsWith("ok")) {
|
|
1792
|
-
const message = `${command.provider}: ${status}. fix credentials with \`ouro auth --agent ${command.agent} --provider ${command.provider}\` before switching.`;
|
|
1793
|
-
deps.writeStdout(message);
|
|
1794
|
-
return message;
|
|
1795
|
-
}
|
|
1796
|
-
if (command.facing) {
|
|
1797
|
-
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, command.facing, command.provider, deps.bundlesRoot);
|
|
1798
|
-
}
|
|
1799
|
-
else {
|
|
1800
|
-
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, "human", command.provider, deps.bundlesRoot);
|
|
1801
|
-
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, "agent", command.provider, deps.bundlesRoot);
|
|
1802
|
-
}
|
|
1803
|
-
const { config: updatedConfig } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
1804
|
-
const facingSummary = command.facing
|
|
1805
|
-
? (() => {
|
|
1806
|
-
const facingConfig = command.facing === "human" ? updatedConfig.humanFacing : updatedConfig.agentFacing;
|
|
1807
|
-
return `${command.facing} model: ${facingConfig.model}`;
|
|
1808
|
-
})()
|
|
1809
|
-
: updatedConfig.humanFacing.model === updatedConfig.agentFacing.model
|
|
1810
|
-
? `model: ${updatedConfig.humanFacing.model}`
|
|
1811
|
-
: `human model: ${updatedConfig.humanFacing.model}; agent model: ${updatedConfig.agentFacing.model}`;
|
|
1812
|
-
const message = `switched ${command.agent} to ${command.provider} (${facingSummary}; verified working)`;
|
|
1813
|
-
deps.writeStdout(message);
|
|
1814
|
-
return message;
|
|
2184
|
+
return executeLegacyAuthSwitch(command, deps);
|
|
1815
2185
|
}
|
|
1816
2186
|
/* v8 ignore stop */
|
|
1817
2187
|
// ── config models (local, no daemon socket needed) ──
|
|
@@ -1849,42 +2219,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1849
2219
|
// ── config model (local, no daemon socket needed) ──
|
|
1850
2220
|
/* v8 ignore start -- config model: tested via daemon-cli.test.ts @preserve */
|
|
1851
2221
|
if (command.kind === "config.model") {
|
|
1852
|
-
|
|
1853
|
-
// Validate model availability for github-copilot before writing
|
|
1854
|
-
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
1855
|
-
const facingConfig = facing === "human" ? config.humanFacing : config.agentFacing;
|
|
1856
|
-
if (facingConfig.provider === "github-copilot") {
|
|
1857
|
-
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
|
|
1858
|
-
const ghConfig = secrets.providers["github-copilot"];
|
|
1859
|
-
if (ghConfig.githubToken && ghConfig.baseUrl) {
|
|
1860
|
-
const fetchFn = deps.fetchImpl ?? fetch;
|
|
1861
|
-
try {
|
|
1862
|
-
const models = await listGithubCopilotModels(ghConfig.baseUrl, ghConfig.githubToken, fetchFn);
|
|
1863
|
-
const available = models.map((m) => m.id);
|
|
1864
|
-
if (available.length > 0 && !available.includes(command.modelName)) {
|
|
1865
|
-
const message = `model '${command.modelName}' not found. available models:\n${available.map((id) => ` ${id}`).join("\n")}`;
|
|
1866
|
-
deps.writeStdout(message);
|
|
1867
|
-
return message;
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
catch {
|
|
1871
|
-
// Catalog validation failed — fall through to ping test
|
|
1872
|
-
}
|
|
1873
|
-
// Ping test: verify the model actually works before switching
|
|
1874
|
-
const pingResult = await (0, provider_ping_1.pingGithubCopilotModel)(ghConfig.baseUrl, ghConfig.githubToken, command.modelName, fetchFn);
|
|
1875
|
-
if (!pingResult.ok) {
|
|
1876
|
-
const message = `model '${command.modelName}' ping failed: ${pingResult.error}\nrun \`ouro config models --agent ${command.agent}\` to see available models.`;
|
|
1877
|
-
deps.writeStdout(message);
|
|
1878
|
-
return message;
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
const { provider, previousModel } = (0, auth_flow_1.writeAgentModel)(command.agent, facing, command.modelName, { bundlesRoot: deps.bundlesRoot });
|
|
1883
|
-
const message = previousModel
|
|
1884
|
-
? `updated ${command.agent} model on ${provider}: ${previousModel} → ${command.modelName}`
|
|
1885
|
-
: `set ${command.agent} model on ${provider}: ${command.modelName}`;
|
|
1886
|
-
deps.writeStdout(message);
|
|
1887
|
-
return message;
|
|
2222
|
+
return executeLegacyConfigModel(command, deps);
|
|
1888
2223
|
}
|
|
1889
2224
|
/* v8 ignore stop */
|
|
1890
2225
|
// ── whoami (local, no daemon socket needed) ──
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.extractAgentFlag = extractAgentFlag;
|
|
10
10
|
exports.extractFacingFlag = extractFacingFlag;
|
|
11
|
+
exports.facingToProviderLane = facingToProviderLane;
|
|
11
12
|
exports.isAgentProvider = isAgentProvider;
|
|
12
13
|
exports.usage = usage;
|
|
13
14
|
exports.parseMcpServeCommand = parseMcpServeCommand;
|
|
@@ -34,6 +35,23 @@ function extractFacingFlag(args) {
|
|
|
34
35
|
const rest = [...args.slice(0, idx), ...args.slice(idx + 2)];
|
|
35
36
|
return { facing: value, rest };
|
|
36
37
|
}
|
|
38
|
+
function facingToProviderLane(facing) {
|
|
39
|
+
return facing === "human" ? "outward" : "inner";
|
|
40
|
+
}
|
|
41
|
+
function isProviderLane(value) {
|
|
42
|
+
return value === "outward" || value === "inner";
|
|
43
|
+
}
|
|
44
|
+
function extractLaneFlag(args) {
|
|
45
|
+
const idx = args.indexOf("--lane");
|
|
46
|
+
if (idx === -1 || idx + 1 >= args.length)
|
|
47
|
+
return { rest: args };
|
|
48
|
+
const value = args[idx + 1];
|
|
49
|
+
if (!isProviderLane(value)) {
|
|
50
|
+
throw new Error("--lane must be 'outward' or 'inner'");
|
|
51
|
+
}
|
|
52
|
+
const rest = [...args.slice(0, idx), ...args.slice(idx + 2)];
|
|
53
|
+
return { lane: value, rest };
|
|
54
|
+
}
|
|
37
55
|
function isAgentProvider(value) {
|
|
38
56
|
return value === "azure" || value === "anthropic" || value === "minimax" || value === "openai-codex" || value === "github-copilot";
|
|
39
57
|
}
|
|
@@ -43,6 +61,9 @@ function usage() {
|
|
|
43
61
|
" ouro [up] [--no-repair]",
|
|
44
62
|
" ouro dev [--repo-path <path>] [--clone [--clone-path <path>]]",
|
|
45
63
|
" ouro stop|down|status|logs|hatch",
|
|
64
|
+
" ouro status --agent <name>",
|
|
65
|
+
" ouro use --agent <name> --lane outward|inner --provider <provider> --model <model> [--force]",
|
|
66
|
+
" ouro check --agent <name> --lane outward|inner",
|
|
46
67
|
" ouro outlook [--json]",
|
|
47
68
|
" ouro -v|--version",
|
|
48
69
|
" ouro config model --agent <name> <model-name>",
|
|
@@ -403,6 +424,63 @@ function parseAuthCommand(args) {
|
|
|
403
424
|
}
|
|
404
425
|
return provider ? { kind: "auth.run", agent, provider } : { kind: "auth.run", agent };
|
|
405
426
|
}
|
|
427
|
+
function parseProviderUseCommand(args) {
|
|
428
|
+
const { agent, rest: afterAgent } = extractAgentFlag(args);
|
|
429
|
+
const { facing, rest: afterFacing } = extractFacingFlag(afterAgent);
|
|
430
|
+
const { lane, rest } = extractLaneFlag(afterFacing);
|
|
431
|
+
let provider;
|
|
432
|
+
let model;
|
|
433
|
+
let force = false;
|
|
434
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
435
|
+
const token = rest[i];
|
|
436
|
+
if (token === "--provider") {
|
|
437
|
+
const value = rest[i + 1];
|
|
438
|
+
if (!isAgentProvider(value))
|
|
439
|
+
throw new Error(`Usage: ouro use --agent <name> --lane outward|inner --provider <provider> --model <model>`);
|
|
440
|
+
provider = value;
|
|
441
|
+
i += 1;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (token === "--model") {
|
|
445
|
+
model = rest[i + 1];
|
|
446
|
+
i += 1;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (token === "--force") {
|
|
450
|
+
force = true;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
throw new Error("Usage: ouro use --agent <name> --lane outward|inner --provider <provider> --model <model> [--force]");
|
|
454
|
+
}
|
|
455
|
+
const resolvedLane = lane ?? (facing ? facingToProviderLane(facing) : undefined);
|
|
456
|
+
if (!agent || !resolvedLane || !provider || !model) {
|
|
457
|
+
throw new Error("Usage: ouro use --agent <name> --lane outward|inner --provider <provider> --model <model> [--force]");
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
kind: "provider.use",
|
|
461
|
+
agent,
|
|
462
|
+
lane: resolvedLane,
|
|
463
|
+
provider,
|
|
464
|
+
model,
|
|
465
|
+
...(force ? { force: true } : {}),
|
|
466
|
+
...(facing ? { legacyFacing: facing } : {}),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function parseProviderCheckCommand(args) {
|
|
470
|
+
const { agent, rest: afterAgent } = extractAgentFlag(args);
|
|
471
|
+
const { facing, rest: afterFacing } = extractFacingFlag(afterAgent);
|
|
472
|
+
const { lane, rest } = extractLaneFlag(afterFacing);
|
|
473
|
+
const resolvedLane = lane ?? (facing ? facingToProviderLane(facing) : undefined);
|
|
474
|
+
if (!agent || !resolvedLane || rest.length > 0) {
|
|
475
|
+
throw new Error("Usage: ouro check --agent <name> --lane outward|inner");
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
kind: "provider.check",
|
|
479
|
+
agent,
|
|
480
|
+
lane: resolvedLane,
|
|
481
|
+
...(facing ? { legacyFacing: facing } : {}),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
406
484
|
function parseReminderCommand(args) {
|
|
407
485
|
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
408
486
|
const [sub, ...rest] = cleaned;
|
|
@@ -750,8 +828,19 @@ function parseOuroCommand(args) {
|
|
|
750
828
|
return { kind: "versions" };
|
|
751
829
|
if (head === "stop" || head === "down")
|
|
752
830
|
return { kind: "daemon.stop" };
|
|
753
|
-
if (head === "status")
|
|
831
|
+
if (head === "status") {
|
|
832
|
+
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
833
|
+
if (agent) {
|
|
834
|
+
if (rest.length > 0)
|
|
835
|
+
throw new Error("Usage: ouro status --agent <name>");
|
|
836
|
+
return { kind: "provider.status", agent };
|
|
837
|
+
}
|
|
754
838
|
return { kind: "daemon.status" };
|
|
839
|
+
}
|
|
840
|
+
if (head === "use")
|
|
841
|
+
return parseProviderUseCommand(args.slice(1));
|
|
842
|
+
if (head === "check")
|
|
843
|
+
return parseProviderCheckCommand(args.slice(1));
|
|
755
844
|
if (head === "logs") {
|
|
756
845
|
if (second === "prune")
|
|
757
846
|
return { kind: "daemon.logs.prune" };
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.splitProviderCredentialFields = splitProviderCredentialFields;
|
|
36
37
|
exports.getProviderCredentialPoolPath = getProviderCredentialPoolPath;
|
|
37
38
|
exports.validateProviderCredentialPool = validateProviderCredentialPool;
|
|
38
39
|
exports.readProviderCredentialPool = readProviderCredentialPool;
|
|
@@ -105,7 +106,7 @@ function copyKnownFields(source, fields) {
|
|
|
105
106
|
}
|
|
106
107
|
return result;
|
|
107
108
|
}
|
|
108
|
-
function
|
|
109
|
+
function splitProviderCredentialFields(provider, rawConfig) {
|
|
109
110
|
const fields = PROVIDER_FIELD_SPLITS[provider];
|
|
110
111
|
return {
|
|
111
112
|
credentials: copyKnownFields(rawConfig, fields.credentials),
|
|
@@ -336,7 +337,7 @@ function readLegacyAgentProviderCredentials(input) {
|
|
|
336
337
|
if (!legacyProviderHasCredentialData(providerKey, rawProviderConfig)) {
|
|
337
338
|
continue;
|
|
338
339
|
}
|
|
339
|
-
const { credentials, config } =
|
|
340
|
+
const { credentials, config } = splitProviderCredentialFields(providerKey, rawProviderConfig);
|
|
340
341
|
candidates.push({
|
|
341
342
|
provider: providerKey,
|
|
342
343
|
credentials,
|
|
@@ -104,6 +104,10 @@ function enforceTrustGate(input) {
|
|
|
104
104
|
return { allowed: true };
|
|
105
105
|
}
|
|
106
106
|
// Open senses (BlueBubbles/iMessage) — enforce trust rules
|
|
107
|
+
// Group chat with a family member present — allow regardless of trust level
|
|
108
|
+
if (input.isGroupChat && input.groupHasFamilyMember) {
|
|
109
|
+
return { allowed: true };
|
|
110
|
+
}
|
|
107
111
|
const trustLevel = input.friend.trustLevel ?? "friend";
|
|
108
112
|
// Family and friend — always allow on open
|
|
109
113
|
if ((0, types_1.isTrustedLevel)(trustLevel)) {
|
|
@@ -119,11 +123,7 @@ function enforceTrustGate(input) {
|
|
|
119
123
|
return handleStranger(input, bundleRoot, nowIso);
|
|
120
124
|
}
|
|
121
125
|
function handleAcquaintance(input, bundleRoot, nowIso) {
|
|
122
|
-
const { isGroupChat,
|
|
123
|
-
// Group chat with family member present — allow
|
|
124
|
-
if (isGroupChat && groupHasFamilyMember) {
|
|
125
|
-
return { allowed: true };
|
|
126
|
-
}
|
|
126
|
+
const { isGroupChat, hasExistingGroupWithFamily } = input;
|
|
127
127
|
let result;
|
|
128
128
|
let noticeDetail;
|
|
129
129
|
if (isGroupChat) {
|