@ouro.bot/cli 0.1.0-alpha.70 → 0.1.0-alpha.72
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,22 @@
|
|
|
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.72",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Fix: Compound shell commands (&&, ;, |, ||) are now checked per-subcommand for untrusted users instead of blanket-blocked. Safe compound commands like `ls && pwd` are allowed; only commands containing an untrusted subcommand are denied.",
|
|
8
|
+
"New `ouro config model --agent <name> <model-name>` command: change the active model for any provider. Reads provider from agent.json, updates the model field in secrets.json. Gated at friend trust level.",
|
|
9
|
+
"New `ouro friend update <id> --trust <level>` command: change an existing friend's trust level (stranger, acquaintance, friend, family). Gated at family trust level."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.71",
|
|
14
|
+
"changes": [
|
|
15
|
+
"Fix: `ouro auth switch` now works with both `ouro auth switch --agent X --provider Y` and `ouro auth --switch --agent X --provider Y` syntax.",
|
|
16
|
+
"Fix: `ouro auth verify` now makes a real API call for github-copilot (GET copilot_internal/user) instead of only checking token format.",
|
|
17
|
+
"Fix: `ouro auth --verify` flag form also accepted alongside positional `verify` subcommand."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"version": "0.1.0-alpha.70",
|
|
6
22
|
"changes": [
|
|
@@ -37,6 +37,7 @@ exports.readAgentConfigForAgent = readAgentConfigForAgent;
|
|
|
37
37
|
exports.writeAgentProviderSelection = writeAgentProviderSelection;
|
|
38
38
|
exports.loadAgentSecrets = loadAgentSecrets;
|
|
39
39
|
exports.writeProviderCredentials = writeProviderCredentials;
|
|
40
|
+
exports.writeAgentModel = writeAgentModel;
|
|
40
41
|
exports.collectRuntimeAuthCredentials = collectRuntimeAuthCredentials;
|
|
41
42
|
exports.resolveHatchCredentials = resolveHatchCredentials;
|
|
42
43
|
exports.runRuntimeAuthFlow = runRuntimeAuthFlow;
|
|
@@ -186,6 +187,26 @@ function writeProviderCredentials(agentName, provider, credentials, deps = {}) {
|
|
|
186
187
|
writeSecrets(secretsPath, secrets);
|
|
187
188
|
return { secretsPath, secrets };
|
|
188
189
|
}
|
|
190
|
+
const MODEL_FIELD = {
|
|
191
|
+
azure: "modelName",
|
|
192
|
+
minimax: "model",
|
|
193
|
+
anthropic: "model",
|
|
194
|
+
"openai-codex": "model",
|
|
195
|
+
"github-copilot": "model",
|
|
196
|
+
};
|
|
197
|
+
function writeAgentModel(agentName, modelName, deps = {}) {
|
|
198
|
+
const { config } = readAgentConfigForAgent(agentName, deps.bundlesRoot);
|
|
199
|
+
const provider = config.provider;
|
|
200
|
+
const { secretsPath, secrets } = loadAgentSecrets(agentName, deps);
|
|
201
|
+
const providerSecrets = secrets.providers[provider];
|
|
202
|
+
/* v8 ignore next -- fallback: all known providers are in MODEL_FIELD @preserve */
|
|
203
|
+
const fieldName = MODEL_FIELD[provider] ?? "model";
|
|
204
|
+
/* v8 ignore next -- defensive: fieldName always exists in template @preserve */
|
|
205
|
+
const previousModel = providerSecrets[fieldName] ?? "";
|
|
206
|
+
providerSecrets[fieldName] = modelName;
|
|
207
|
+
writeSecrets(secretsPath, secrets);
|
|
208
|
+
return { secretsPath, provider, previousModel };
|
|
209
|
+
}
|
|
189
210
|
function readCodexAccessToken(homeDir) {
|
|
190
211
|
const authPath = path.join(homeDir, ".codex", "auth.json");
|
|
191
212
|
try {
|
|
@@ -269,6 +269,7 @@ function usage() {
|
|
|
269
269
|
" ouro [up]",
|
|
270
270
|
" ouro stop|down|status|logs|hatch",
|
|
271
271
|
" ouro -v|--version",
|
|
272
|
+
" ouro config model --agent <name> <model-name>",
|
|
272
273
|
" ouro auth --agent <name> [--provider <provider>]",
|
|
273
274
|
" ouro auth verify --agent <name> [--provider <provider>]",
|
|
274
275
|
" ouro auth switch --agent <name> --provider <provider>",
|
|
@@ -285,6 +286,7 @@ function usage() {
|
|
|
285
286
|
" ouro friend list [--agent <name>]",
|
|
286
287
|
" ouro friend show <id> [--agent <name>]",
|
|
287
288
|
" ouro friend create --name <name> [--trust <level>] [--agent <name>]",
|
|
289
|
+
" ouro friend update <id> --trust <level> [--agent <name>]",
|
|
288
290
|
" ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
|
|
289
291
|
" ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
290
292
|
" ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
@@ -443,7 +445,7 @@ function hasStoredCredentials(provider, providerSecrets) {
|
|
|
443
445
|
}
|
|
444
446
|
/* v8 ignore stop */
|
|
445
447
|
/* v8 ignore start -- verifyProviderCredentials: per-provider branches tested via auth verify tests @preserve */
|
|
446
|
-
function verifyProviderCredentials(provider, providers) {
|
|
448
|
+
async function verifyProviderCredentials(provider, providers, fetchImpl = fetch) {
|
|
447
449
|
const p = providers[provider];
|
|
448
450
|
if (!p)
|
|
449
451
|
return "not configured";
|
|
@@ -461,7 +463,17 @@ function verifyProviderCredentials(provider, providers) {
|
|
|
461
463
|
}
|
|
462
464
|
if (provider === "github-copilot") {
|
|
463
465
|
const token = p.githubToken || "";
|
|
464
|
-
|
|
466
|
+
if (!token)
|
|
467
|
+
return "failed (no token)";
|
|
468
|
+
try {
|
|
469
|
+
const response = await fetchImpl("https://api.github.com/copilot_internal/user", {
|
|
470
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
471
|
+
});
|
|
472
|
+
return response.ok ? "ok" : `failed (HTTP ${response.status})`;
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
return `failed (${error.message})`;
|
|
476
|
+
}
|
|
465
477
|
}
|
|
466
478
|
if (provider === "minimax") {
|
|
467
479
|
const apiKey = p.apiKey || "";
|
|
@@ -593,7 +605,9 @@ function parseTaskCommand(args) {
|
|
|
593
605
|
}
|
|
594
606
|
function parseAuthCommand(args) {
|
|
595
607
|
const first = args[0];
|
|
596
|
-
|
|
608
|
+
// Support both positional (`auth switch`) and flag (`auth --switch`) forms
|
|
609
|
+
if (first === "verify" || first === "switch" || first === "--verify" || first === "--switch") {
|
|
610
|
+
const subcommand = first.replace(/^--/, "");
|
|
597
611
|
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
598
612
|
let provider;
|
|
599
613
|
/* v8 ignore start -- provider flag parsing: branches tested via CLI parsing tests @preserve */
|
|
@@ -611,7 +625,7 @@ function parseAuthCommand(args) {
|
|
|
611
625
|
/* v8 ignore next -- defensive: agent always provided in tests @preserve */
|
|
612
626
|
if (!agent)
|
|
613
627
|
throw new Error(`Usage\n${usage()}`);
|
|
614
|
-
if (
|
|
628
|
+
if (subcommand === "switch") {
|
|
615
629
|
if (!provider)
|
|
616
630
|
throw new Error(`auth switch requires --provider.\n${usage()}`);
|
|
617
631
|
return { kind: "auth.switch", agent, provider };
|
|
@@ -748,12 +762,51 @@ function parseFriendCommand(args) {
|
|
|
748
762
|
...(agent ? { agent } : {}),
|
|
749
763
|
};
|
|
750
764
|
}
|
|
765
|
+
if (sub === "update") {
|
|
766
|
+
const friendId = rest[0];
|
|
767
|
+
if (!friendId)
|
|
768
|
+
throw new Error(`Usage: ouro friend update <id> --trust <level>`);
|
|
769
|
+
let trustLevel;
|
|
770
|
+
/* v8 ignore start -- flag parsing loop: tested via CLI parsing tests @preserve */
|
|
771
|
+
for (let i = 1; i < rest.length; i++) {
|
|
772
|
+
if (rest[i] === "--trust" && rest[i + 1]) {
|
|
773
|
+
trustLevel = rest[i + 1];
|
|
774
|
+
i += 1;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
/* v8 ignore stop */
|
|
778
|
+
const VALID_TRUST_LEVELS = new Set(["stranger", "acquaintance", "friend", "family"]);
|
|
779
|
+
if (!trustLevel || !VALID_TRUST_LEVELS.has(trustLevel)) {
|
|
780
|
+
throw new Error(`Usage: ouro friend update <id> --trust <stranger|acquaintance|friend|family>`);
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
kind: "friend.update",
|
|
784
|
+
friendId,
|
|
785
|
+
trustLevel: trustLevel,
|
|
786
|
+
...(agent ? { agent } : {}),
|
|
787
|
+
};
|
|
788
|
+
}
|
|
751
789
|
if (sub === "link")
|
|
752
790
|
return parseLinkCommand(rest, "friend.link");
|
|
753
791
|
if (sub === "unlink")
|
|
754
792
|
return parseLinkCommand(rest, "friend.unlink");
|
|
755
793
|
throw new Error(`Usage\n${usage()}`);
|
|
756
794
|
}
|
|
795
|
+
function parseConfigCommand(args) {
|
|
796
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
797
|
+
const [sub, ...rest] = cleaned;
|
|
798
|
+
if (!sub)
|
|
799
|
+
throw new Error(`Usage\n${usage()}`);
|
|
800
|
+
if (sub === "model") {
|
|
801
|
+
if (!agent)
|
|
802
|
+
throw new Error("--agent is required for config model");
|
|
803
|
+
const modelName = rest[0];
|
|
804
|
+
if (!modelName)
|
|
805
|
+
throw new Error(`Usage: ouro config model --agent <name> <model-name>`);
|
|
806
|
+
return { kind: "config.model", agent, modelName };
|
|
807
|
+
}
|
|
808
|
+
throw new Error(`Usage\n${usage()}`);
|
|
809
|
+
}
|
|
757
810
|
function parseMcpCommand(args) {
|
|
758
811
|
const [sub, ...rest] = args;
|
|
759
812
|
if (!sub)
|
|
@@ -796,6 +849,8 @@ function parseOuroCommand(args) {
|
|
|
796
849
|
return parseReminderCommand(args.slice(1));
|
|
797
850
|
if (head === "friend")
|
|
798
851
|
return parseFriendCommand(args.slice(1));
|
|
852
|
+
if (head === "config")
|
|
853
|
+
return parseConfigCommand(args.slice(1));
|
|
799
854
|
if (head === "mcp")
|
|
800
855
|
return parseMcpCommand(args.slice(1));
|
|
801
856
|
if (head === "whoami") {
|
|
@@ -1435,6 +1490,19 @@ async function executeFriendCommand(command, store) {
|
|
|
1435
1490
|
});
|
|
1436
1491
|
return `created: ${id} (${command.name}, ${trustLevel})`;
|
|
1437
1492
|
}
|
|
1493
|
+
if (command.kind === "friend.update") {
|
|
1494
|
+
const current = await store.get(command.friendId);
|
|
1495
|
+
if (!current)
|
|
1496
|
+
return `friend not found: ${command.friendId}`;
|
|
1497
|
+
const now = new Date().toISOString();
|
|
1498
|
+
await store.put(command.friendId, {
|
|
1499
|
+
...current,
|
|
1500
|
+
trustLevel: command.trustLevel,
|
|
1501
|
+
role: command.trustLevel,
|
|
1502
|
+
updatedAt: now,
|
|
1503
|
+
});
|
|
1504
|
+
return `updated: ${command.friendId} → trust=${command.trustLevel}`;
|
|
1505
|
+
}
|
|
1438
1506
|
if (command.kind === "friend.link") {
|
|
1439
1507
|
const current = await store.get(command.friendId);
|
|
1440
1508
|
if (!current)
|
|
@@ -1661,7 +1729,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1661
1729
|
}
|
|
1662
1730
|
// ── friend subcommands (local, no daemon socket needed) ──
|
|
1663
1731
|
if (command.kind === "friend.list" || command.kind === "friend.show" || command.kind === "friend.create" ||
|
|
1664
|
-
command.kind === "friend.link" || command.kind === "friend.unlink") {
|
|
1732
|
+
command.kind === "friend.update" || command.kind === "friend.link" || command.kind === "friend.unlink") {
|
|
1665
1733
|
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1666
1734
|
let store = deps.friendStore;
|
|
1667
1735
|
if (!store) {
|
|
@@ -1697,15 +1765,16 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1697
1765
|
if (command.kind === "auth.verify") {
|
|
1698
1766
|
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent);
|
|
1699
1767
|
const providers = secrets.providers;
|
|
1768
|
+
const fetchFn = deps.fetchImpl ?? fetch;
|
|
1700
1769
|
if (command.provider) {
|
|
1701
|
-
const status = verifyProviderCredentials(command.provider, providers);
|
|
1770
|
+
const status = await verifyProviderCredentials(command.provider, providers, fetchFn);
|
|
1702
1771
|
const message = `${command.provider}: ${status}`;
|
|
1703
1772
|
deps.writeStdout(message);
|
|
1704
1773
|
return message;
|
|
1705
1774
|
}
|
|
1706
1775
|
const lines = [];
|
|
1707
1776
|
for (const p of Object.keys(providers)) {
|
|
1708
|
-
const status = verifyProviderCredentials(p, providers);
|
|
1777
|
+
const status = await verifyProviderCredentials(p, providers, fetchFn);
|
|
1709
1778
|
lines.push(`${p}: ${status}`);
|
|
1710
1779
|
}
|
|
1711
1780
|
const message = lines.join("\n");
|
|
@@ -1727,6 +1796,17 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1727
1796
|
return message;
|
|
1728
1797
|
}
|
|
1729
1798
|
/* v8 ignore stop */
|
|
1799
|
+
// ── config model (local, no daemon socket needed) ──
|
|
1800
|
+
/* v8 ignore start -- config model: tested via daemon-cli.test.ts @preserve */
|
|
1801
|
+
if (command.kind === "config.model") {
|
|
1802
|
+
const { provider, previousModel } = (0, auth_flow_1.writeAgentModel)(command.agent, command.modelName);
|
|
1803
|
+
const message = previousModel
|
|
1804
|
+
? `updated ${command.agent} model on ${provider}: ${previousModel} → ${command.modelName}`
|
|
1805
|
+
: `set ${command.agent} model on ${provider}: ${command.modelName}`;
|
|
1806
|
+
deps.writeStdout(message);
|
|
1807
|
+
return message;
|
|
1808
|
+
}
|
|
1809
|
+
/* v8 ignore stop */
|
|
1730
1810
|
// ── whoami (local, no daemon socket needed) ──
|
|
1731
1811
|
if (command.kind === "whoami") {
|
|
1732
1812
|
if (command.agent) {
|
package/dist/mind/prompt.js
CHANGED
|
@@ -178,8 +178,10 @@ my bones give me the \`ouro\` cli. always pass \`--agent ${agentName}\`:
|
|
|
178
178
|
ouro task update --agent ${agentName} <id> <status>
|
|
179
179
|
ouro friend list --agent ${agentName}
|
|
180
180
|
ouro friend show --agent ${agentName} <id>
|
|
181
|
+
ouro friend update --agent ${agentName} <id> --trust <level>
|
|
181
182
|
ouro session list --agent ${agentName}
|
|
182
183
|
ouro reminder create --agent ${agentName} <title> --body <body>
|
|
184
|
+
ouro config model --agent ${agentName} <model-name>
|
|
183
185
|
ouro auth --agent ${agentName} --provider <provider>
|
|
184
186
|
ouro auth verify --agent ${agentName} [--provider <provider>]
|
|
185
187
|
ouro auth switch --agent ${agentName} --provider <provider>
|
|
@@ -48,7 +48,6 @@ const REASONS = {
|
|
|
48
48
|
readBeforeOverwrite: "i need to read that file first before i can overwrite it.",
|
|
49
49
|
protectedPath: "that path is protected — i can read it but not modify it.",
|
|
50
50
|
destructiveCommand: "that command is too dangerous to run — it could cause irreversible damage.",
|
|
51
|
-
compoundCommand: "i can only run simple commands for you — no chaining with && or ;",
|
|
52
51
|
// Trust reasons (vary by relationship)
|
|
53
52
|
needsTrust: "i'd need a closer friend to vouch for you before i can do that.",
|
|
54
53
|
needsTrustForWrite: "i'd need a closer friend to vouch for you before i can write files outside my home.",
|
|
@@ -90,9 +89,6 @@ function splitShellCommands(command) {
|
|
|
90
89
|
return [command];
|
|
91
90
|
return command.split(COMPOUND_SEPARATORS).filter(Boolean);
|
|
92
91
|
}
|
|
93
|
-
function isCompoundCommand(command) {
|
|
94
|
-
return SUBSHELL_PATTERN.test(command) || splitShellCommands(command).length > 1;
|
|
95
|
-
}
|
|
96
92
|
// --- shell commands that write to protected paths ---
|
|
97
93
|
function shellWritesToProtectedPath(command) {
|
|
98
94
|
const redirectMatch = command.match(/>\s*(\S+)/);
|
|
@@ -169,7 +165,9 @@ exports.OURO_CLI_TRUST_MANIFEST = {
|
|
|
169
165
|
"friend list": "friend",
|
|
170
166
|
"friend show": "friend",
|
|
171
167
|
"friend create": "friend",
|
|
168
|
+
"friend update": "family",
|
|
172
169
|
"reminder create": "friend",
|
|
170
|
+
"config model": "friend",
|
|
173
171
|
"mcp list": "acquaintance",
|
|
174
172
|
"mcp call": "friend",
|
|
175
173
|
auth: "family",
|
|
@@ -234,11 +232,21 @@ function checkSingleShellCommandTrust(command, trustLevel) {
|
|
|
234
232
|
return deny(REASONS.needsTrust);
|
|
235
233
|
}
|
|
236
234
|
function checkShellTrustGuardrails(command, trustLevel) {
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
return
|
|
241
|
-
|
|
235
|
+
// Subshell patterns ($(), backticks) can't be reliably split — check as single command
|
|
236
|
+
/* v8 ignore next -- subshell branch: tested via guardrails.test.ts @preserve */
|
|
237
|
+
if (SUBSHELL_PATTERN.test(command)) {
|
|
238
|
+
return checkSingleShellCommandTrust(command, trustLevel);
|
|
239
|
+
}
|
|
240
|
+
// Compound commands: check each subcommand individually
|
|
241
|
+
const subcommands = splitShellCommands(command);
|
|
242
|
+
if (subcommands.length === 0)
|
|
243
|
+
return checkSingleShellCommandTrust(command, trustLevel);
|
|
244
|
+
for (const sub of subcommands) {
|
|
245
|
+
const result = checkSingleShellCommandTrust(sub, trustLevel);
|
|
246
|
+
if (!result.allowed)
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
return allow;
|
|
242
250
|
}
|
|
243
251
|
function checkWriteTrustGuardrails(toolName, args, context) {
|
|
244
252
|
if (toolName !== "write_file" && toolName !== "edit_file")
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -103,7 +103,7 @@ async function execTool(name, args, ctx) {
|
|
|
103
103
|
event: "tool.start",
|
|
104
104
|
component: "tools",
|
|
105
105
|
message: "tool execution started",
|
|
106
|
-
meta: { name },
|
|
106
|
+
meta: { name, ...(name === "shell" && args.command ? { command: args.command } : {}) },
|
|
107
107
|
});
|
|
108
108
|
// Look up from combined registry
|
|
109
109
|
const def = allDefinitions.find((d) => d.tool.function.name === name);
|