@ouro.bot/cli 0.1.0-alpha.54 → 0.1.0-alpha.56
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 +13 -0
- package/changelog.json +16 -0
- package/dist/heart/daemon/auth-flow.js +351 -0
- package/dist/heart/daemon/daemon-cli.js +44 -18
- package/dist/heart/daemon/hatch-flow.js +2 -63
- package/dist/heart/providers/anthropic.js +2 -2
- package/dist/heart/providers/openai-codex.js +2 -2
- package/dist/mind/memory.js +17 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,6 +96,17 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
|
|
|
96
96
|
- `running`
|
|
97
97
|
- `error`
|
|
98
98
|
|
|
99
|
+
When a model provider needs first-time setup, reauth, or an explicit switch, use:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
ouro auth --agent <name>
|
|
103
|
+
ouro auth --agent <name> --provider <provider>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The default form reauths the provider already selected in `agent.json`. The explicit
|
|
107
|
+
`--provider` form is for adding or switching providers, and it updates `agent.json`
|
|
108
|
+
to use the newly authenticated provider.
|
|
109
|
+
|
|
99
110
|
## Quickstart
|
|
100
111
|
|
|
101
112
|
### Use The Published Runtime
|
|
@@ -134,6 +145,8 @@ ouro up
|
|
|
134
145
|
ouro status
|
|
135
146
|
ouro logs
|
|
136
147
|
ouro stop
|
|
148
|
+
ouro auth --agent <name>
|
|
149
|
+
ouro auth --agent <name> --provider <provider>
|
|
137
150
|
ouro hatch
|
|
138
151
|
ouro chat <agent>
|
|
139
152
|
ouro msg --to <agent> [--session <id>] [--task <ref>] <message>
|
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.56",
|
|
6
|
+
"changes": [
|
|
7
|
+
"The installed runtime now supports `ouro auth --agent <name>` as the normal provider auth and reauth path, with `--provider <provider>` available for explicit provider add/switch flows.",
|
|
8
|
+
"Anthropic and OpenAI Codex hatch/signup now share the same runtime auth backbone as later reauth, so first-time setup and recovery follow one consistent mental model.",
|
|
9
|
+
"Provider auth failures now point at the supported `ouro auth` recovery path and tell you what to do after reauth, including retrying the failed `ouro` command or reconnecting the errored session."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.55",
|
|
14
|
+
"changes": [
|
|
15
|
+
"Memory fact dedup now catches paraphrased duplicates via cosine similarity on existing embeddings, so semantically equivalent facts no longer slip past the word-overlap check.",
|
|
16
|
+
"Semantic dedup gracefully handles corrupt JSONL entries with missing or undefined embeddings instead of crashing on bad data.",
|
|
17
|
+
"Cosine similarity is now imported from associative-recall instead of duplicated in the memory module."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"version": "0.1.0-alpha.54",
|
|
6
22
|
"changes": [
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readAgentConfigForAgent = readAgentConfigForAgent;
|
|
37
|
+
exports.writeAgentProviderSelection = writeAgentProviderSelection;
|
|
38
|
+
exports.loadAgentSecrets = loadAgentSecrets;
|
|
39
|
+
exports.writeProviderCredentials = writeProviderCredentials;
|
|
40
|
+
exports.collectRuntimeAuthCredentials = collectRuntimeAuthCredentials;
|
|
41
|
+
exports.resolveHatchCredentials = resolveHatchCredentials;
|
|
42
|
+
exports.runRuntimeAuthFlow = runRuntimeAuthFlow;
|
|
43
|
+
const child_process_1 = require("child_process");
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
48
|
+
const identity_1 = require("../identity");
|
|
49
|
+
const ANTHROPIC_SETUP_TOKEN_PREFIX = "sk-ant-oat01-";
|
|
50
|
+
const ANTHROPIC_SETUP_TOKEN_MIN_LENGTH = 80;
|
|
51
|
+
const DEFAULT_SECRETS_TEMPLATE = {
|
|
52
|
+
providers: {
|
|
53
|
+
azure: {
|
|
54
|
+
modelName: "gpt-4o-mini",
|
|
55
|
+
apiKey: "",
|
|
56
|
+
endpoint: "",
|
|
57
|
+
deployment: "",
|
|
58
|
+
apiVersion: "2025-04-01-preview",
|
|
59
|
+
},
|
|
60
|
+
minimax: {
|
|
61
|
+
model: "minimax-text-01",
|
|
62
|
+
apiKey: "",
|
|
63
|
+
},
|
|
64
|
+
anthropic: {
|
|
65
|
+
model: "claude-opus-4-6",
|
|
66
|
+
setupToken: "",
|
|
67
|
+
},
|
|
68
|
+
"openai-codex": {
|
|
69
|
+
model: "gpt-5.4",
|
|
70
|
+
oauthAccessToken: "",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
teams: {
|
|
74
|
+
clientId: "",
|
|
75
|
+
clientSecret: "",
|
|
76
|
+
tenantId: "",
|
|
77
|
+
},
|
|
78
|
+
oauth: {
|
|
79
|
+
graphConnectionName: "graph",
|
|
80
|
+
adoConnectionName: "ado",
|
|
81
|
+
githubConnectionName: "",
|
|
82
|
+
},
|
|
83
|
+
teamsChannel: {
|
|
84
|
+
skipConfirmation: true,
|
|
85
|
+
port: 3978,
|
|
86
|
+
},
|
|
87
|
+
integrations: {
|
|
88
|
+
perplexityApiKey: "",
|
|
89
|
+
openaiEmbeddingsApiKey: "",
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
function deepMerge(defaults, partial) {
|
|
93
|
+
const result = { ...defaults };
|
|
94
|
+
for (const key of Object.keys(partial)) {
|
|
95
|
+
const left = result[key];
|
|
96
|
+
const right = partial[key];
|
|
97
|
+
if (right !== null &&
|
|
98
|
+
typeof right === "object" &&
|
|
99
|
+
!Array.isArray(right) &&
|
|
100
|
+
left !== null &&
|
|
101
|
+
typeof left === "object" &&
|
|
102
|
+
!Array.isArray(left)) {
|
|
103
|
+
result[key] = deepMerge(left, right);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
result[key] = right;
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
function readJsonRecord(filePath, label) {
|
|
111
|
+
try {
|
|
112
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
113
|
+
const parsed = JSON.parse(raw);
|
|
114
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
115
|
+
throw new Error("expected object");
|
|
116
|
+
}
|
|
117
|
+
return parsed;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
throw new Error(`Failed to read ${label} at ${filePath}: ${String(error)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function readAgentConfigForAgent(agentName, bundlesRoot = (0, identity_1.getAgentBundlesRoot)()) {
|
|
124
|
+
const configPath = path.join(bundlesRoot, `${agentName}.ouro`, "agent.json");
|
|
125
|
+
const parsed = readJsonRecord(configPath, "agent config");
|
|
126
|
+
const provider = parsed.provider;
|
|
127
|
+
if (provider !== "azure" &&
|
|
128
|
+
provider !== "anthropic" &&
|
|
129
|
+
provider !== "minimax" &&
|
|
130
|
+
provider !== "openai-codex") {
|
|
131
|
+
throw new Error(`agent.json at ${configPath} has unsupported provider '${String(provider)}'`);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
configPath,
|
|
135
|
+
config: parsed,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function writeAgentProviderSelection(agentName, provider, bundlesRoot = (0, identity_1.getAgentBundlesRoot)()) {
|
|
139
|
+
const { configPath, config } = readAgentConfigForAgent(agentName, bundlesRoot);
|
|
140
|
+
const nextConfig = { ...config, provider };
|
|
141
|
+
fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
142
|
+
(0, runtime_1.emitNervesEvent)({
|
|
143
|
+
component: "daemon",
|
|
144
|
+
event: "daemon.auth_provider_selected",
|
|
145
|
+
message: "updated agent provider selection after auth flow",
|
|
146
|
+
meta: { agentName, provider, configPath },
|
|
147
|
+
});
|
|
148
|
+
return configPath;
|
|
149
|
+
}
|
|
150
|
+
function resolveAgentSecretsPath(agentName, deps = {}) {
|
|
151
|
+
if (deps.secretsRoot)
|
|
152
|
+
return path.join(deps.secretsRoot, agentName, "secrets.json");
|
|
153
|
+
const homeDir = deps.homeDir ?? os.homedir();
|
|
154
|
+
return (0, identity_1.getAgentSecretsPath)(agentName).replace(os.homedir(), homeDir);
|
|
155
|
+
}
|
|
156
|
+
function loadAgentSecrets(agentName, deps = {}) {
|
|
157
|
+
const secretsPath = resolveAgentSecretsPath(agentName, deps);
|
|
158
|
+
const secretsDir = path.dirname(secretsPath);
|
|
159
|
+
fs.mkdirSync(secretsDir, { recursive: true });
|
|
160
|
+
let onDisk = {};
|
|
161
|
+
try {
|
|
162
|
+
onDisk = readJsonRecord(secretsPath, "secrets config");
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const message = error.message;
|
|
166
|
+
if (!message.includes("ENOENT"))
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
secretsPath,
|
|
171
|
+
secrets: deepMerge(DEFAULT_SECRETS_TEMPLATE, onDisk),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function writeSecrets(secretsPath, secrets) {
|
|
175
|
+
fs.writeFileSync(secretsPath, `${JSON.stringify(secrets, null, 2)}\n`, "utf8");
|
|
176
|
+
}
|
|
177
|
+
function writeProviderCredentials(agentName, provider, credentials, deps = {}) {
|
|
178
|
+
const { secretsPath, secrets } = loadAgentSecrets(agentName, deps);
|
|
179
|
+
applyCredentials(secrets, provider, credentials);
|
|
180
|
+
writeSecrets(secretsPath, secrets);
|
|
181
|
+
return { secretsPath, secrets };
|
|
182
|
+
}
|
|
183
|
+
function readCodexAccessToken(homeDir) {
|
|
184
|
+
const authPath = path.join(homeDir, ".codex", "auth.json");
|
|
185
|
+
try {
|
|
186
|
+
const raw = fs.readFileSync(authPath, "utf8");
|
|
187
|
+
const parsed = JSON.parse(raw);
|
|
188
|
+
const token = parsed?.tokens?.access_token;
|
|
189
|
+
return typeof token === "string" ? token.trim() : "";
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return "";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function ensurePromptInput(promptInput, provider) {
|
|
196
|
+
if (promptInput)
|
|
197
|
+
return promptInput;
|
|
198
|
+
throw new Error(`No prompt input is available for ${provider} authentication.`);
|
|
199
|
+
}
|
|
200
|
+
function validateAnthropicToken(token) {
|
|
201
|
+
const trimmed = token.trim();
|
|
202
|
+
if (!trimmed) {
|
|
203
|
+
throw new Error("No Anthropic setup token was provided.");
|
|
204
|
+
}
|
|
205
|
+
if (!trimmed.startsWith(ANTHROPIC_SETUP_TOKEN_PREFIX)) {
|
|
206
|
+
throw new Error(`Invalid Anthropic setup token format. Expected prefix ${ANTHROPIC_SETUP_TOKEN_PREFIX}.`);
|
|
207
|
+
}
|
|
208
|
+
if (trimmed.length < ANTHROPIC_SETUP_TOKEN_MIN_LENGTH) {
|
|
209
|
+
throw new Error("Anthropic setup token looks too short.");
|
|
210
|
+
}
|
|
211
|
+
return trimmed;
|
|
212
|
+
}
|
|
213
|
+
async function collectRuntimeAuthCredentials(input, deps) {
|
|
214
|
+
const spawnSync = deps.spawnSync ?? child_process_1.spawnSync;
|
|
215
|
+
const homeDir = deps.homeDir ?? os.homedir();
|
|
216
|
+
if (input.provider === "openai-codex") {
|
|
217
|
+
let token = readCodexAccessToken(homeDir);
|
|
218
|
+
if (!token) {
|
|
219
|
+
(0, runtime_1.emitNervesEvent)({
|
|
220
|
+
component: "daemon",
|
|
221
|
+
event: "daemon.auth_codex_login_start",
|
|
222
|
+
message: "starting codex login for runtime auth",
|
|
223
|
+
meta: { agentName: input.agentName },
|
|
224
|
+
});
|
|
225
|
+
const result = spawnSync("codex", ["login"], { stdio: "inherit" });
|
|
226
|
+
if (result.error) {
|
|
227
|
+
throw new Error(`Failed to run 'codex login': ${result.error.message}`);
|
|
228
|
+
}
|
|
229
|
+
if (result.status !== 0) {
|
|
230
|
+
throw new Error(`'codex login' exited with status ${result.status}.`);
|
|
231
|
+
}
|
|
232
|
+
token = readCodexAccessToken(homeDir);
|
|
233
|
+
if (!token) {
|
|
234
|
+
throw new Error("Codex login completed but no token was found in ~/.codex/auth.json. Re-run `codex login` and try again.");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { oauthAccessToken: token };
|
|
238
|
+
}
|
|
239
|
+
if (input.provider === "anthropic") {
|
|
240
|
+
(0, runtime_1.emitNervesEvent)({
|
|
241
|
+
component: "daemon",
|
|
242
|
+
event: "daemon.auth_claude_setup_start",
|
|
243
|
+
message: "starting claude setup-token for runtime auth",
|
|
244
|
+
meta: { agentName: input.agentName },
|
|
245
|
+
});
|
|
246
|
+
const result = spawnSync("claude", ["setup-token"], { stdio: "inherit" });
|
|
247
|
+
if (result.error) {
|
|
248
|
+
throw new Error(`Failed to run 'claude setup-token': ${result.error.message}`);
|
|
249
|
+
}
|
|
250
|
+
if (result.status !== 0) {
|
|
251
|
+
throw new Error(`'claude setup-token' exited with status ${result.status}.`);
|
|
252
|
+
}
|
|
253
|
+
const prompt = ensurePromptInput(input.promptInput, input.provider);
|
|
254
|
+
const setupToken = validateAnthropicToken(await prompt("Paste the setup token from `claude setup-token`: "));
|
|
255
|
+
return { setupToken };
|
|
256
|
+
}
|
|
257
|
+
if (input.provider === "minimax") {
|
|
258
|
+
const prompt = ensurePromptInput(input.promptInput, input.provider);
|
|
259
|
+
const apiKey = (await prompt("MiniMax API key: ")).trim();
|
|
260
|
+
if (!apiKey)
|
|
261
|
+
throw new Error("MiniMax API key is required.");
|
|
262
|
+
return { apiKey };
|
|
263
|
+
}
|
|
264
|
+
const prompt = ensurePromptInput(input.promptInput, input.provider);
|
|
265
|
+
const apiKey = (await prompt("Azure API key: ")).trim();
|
|
266
|
+
const endpoint = (await prompt("Azure endpoint: ")).trim();
|
|
267
|
+
const deployment = (await prompt("Azure deployment: ")).trim();
|
|
268
|
+
if (!apiKey || !endpoint || !deployment) {
|
|
269
|
+
throw new Error("Azure API key, endpoint, and deployment are required.");
|
|
270
|
+
}
|
|
271
|
+
return { apiKey, endpoint, deployment };
|
|
272
|
+
}
|
|
273
|
+
async function resolveHatchCredentials(input) {
|
|
274
|
+
const prompt = input.promptInput;
|
|
275
|
+
const credentials = { ...(input.credentials ?? {}) };
|
|
276
|
+
if (input.provider === "anthropic" && !credentials.setupToken && input.runAuthFlow) {
|
|
277
|
+
const result = await input.runAuthFlow({
|
|
278
|
+
agentName: input.agentName,
|
|
279
|
+
provider: "anthropic",
|
|
280
|
+
promptInput: prompt,
|
|
281
|
+
});
|
|
282
|
+
Object.assign(credentials, result.credentials);
|
|
283
|
+
}
|
|
284
|
+
if (input.provider === "anthropic" && !credentials.setupToken && prompt) {
|
|
285
|
+
credentials.setupToken = await prompt("Anthropic setup-token: ");
|
|
286
|
+
}
|
|
287
|
+
if (input.provider === "openai-codex" && !credentials.oauthAccessToken && input.runAuthFlow) {
|
|
288
|
+
const result = await input.runAuthFlow({
|
|
289
|
+
agentName: input.agentName,
|
|
290
|
+
provider: "openai-codex",
|
|
291
|
+
promptInput: prompt,
|
|
292
|
+
});
|
|
293
|
+
Object.assign(credentials, result.credentials);
|
|
294
|
+
}
|
|
295
|
+
if (input.provider === "openai-codex" && !credentials.oauthAccessToken && prompt) {
|
|
296
|
+
credentials.oauthAccessToken = await prompt("OpenAI Codex OAuth token: ");
|
|
297
|
+
}
|
|
298
|
+
if (input.provider === "minimax" && !credentials.apiKey && prompt) {
|
|
299
|
+
credentials.apiKey = await prompt("MiniMax API key: ");
|
|
300
|
+
}
|
|
301
|
+
if (input.provider === "azure") {
|
|
302
|
+
if (!credentials.apiKey && prompt)
|
|
303
|
+
credentials.apiKey = await prompt("Azure API key: ");
|
|
304
|
+
if (!credentials.endpoint && prompt)
|
|
305
|
+
credentials.endpoint = await prompt("Azure endpoint: ");
|
|
306
|
+
if (!credentials.deployment && prompt)
|
|
307
|
+
credentials.deployment = await prompt("Azure deployment: ");
|
|
308
|
+
}
|
|
309
|
+
return credentials;
|
|
310
|
+
}
|
|
311
|
+
function applyCredentials(secrets, provider, credentials) {
|
|
312
|
+
if (provider === "anthropic") {
|
|
313
|
+
secrets.providers.anthropic.setupToken = credentials.setupToken.trim();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (provider === "openai-codex") {
|
|
317
|
+
secrets.providers["openai-codex"].oauthAccessToken = credentials.oauthAccessToken.trim();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (provider === "minimax") {
|
|
321
|
+
secrets.providers.minimax.apiKey = credentials.apiKey.trim();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
secrets.providers.azure.apiKey = credentials.apiKey.trim();
|
|
325
|
+
secrets.providers.azure.endpoint = credentials.endpoint.trim();
|
|
326
|
+
secrets.providers.azure.deployment = credentials.deployment.trim();
|
|
327
|
+
}
|
|
328
|
+
async function runRuntimeAuthFlow(input, deps = {}) {
|
|
329
|
+
(0, runtime_1.emitNervesEvent)({
|
|
330
|
+
component: "daemon",
|
|
331
|
+
event: "daemon.auth_flow_start",
|
|
332
|
+
message: "starting runtime auth flow",
|
|
333
|
+
meta: { agentName: input.agentName, provider: input.provider },
|
|
334
|
+
});
|
|
335
|
+
const homeDir = deps.homeDir ?? os.homedir();
|
|
336
|
+
const credentials = await collectRuntimeAuthCredentials(input, deps);
|
|
337
|
+
const { secretsPath } = writeProviderCredentials(input.agentName, input.provider, credentials, { homeDir });
|
|
338
|
+
(0, runtime_1.emitNervesEvent)({
|
|
339
|
+
component: "daemon",
|
|
340
|
+
event: "daemon.auth_flow_end",
|
|
341
|
+
message: "completed runtime auth flow",
|
|
342
|
+
meta: { agentName: input.agentName, provider: input.provider, secretsPath },
|
|
343
|
+
});
|
|
344
|
+
return {
|
|
345
|
+
agentName: input.agentName,
|
|
346
|
+
provider: input.provider,
|
|
347
|
+
secretsPath,
|
|
348
|
+
message: `authenticated ${input.agentName} with ${input.provider}`,
|
|
349
|
+
credentials,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
@@ -67,6 +67,7 @@ const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
|
|
|
67
67
|
const launchd_1 = require("./launchd");
|
|
68
68
|
const socket_client_1 = require("./socket-client");
|
|
69
69
|
const session_activity_1 = require("../session-activity");
|
|
70
|
+
const auth_flow_1 = require("./auth-flow");
|
|
70
71
|
function stringField(value) {
|
|
71
72
|
return typeof value === "string" ? value : null;
|
|
72
73
|
}
|
|
@@ -268,6 +269,7 @@ function usage() {
|
|
|
268
269
|
" ouro [up]",
|
|
269
270
|
" ouro stop|down|status|logs|hatch",
|
|
270
271
|
" ouro -v|--version",
|
|
272
|
+
" ouro auth --agent <name> [--provider <provider>]",
|
|
271
273
|
" ouro chat <agent>",
|
|
272
274
|
" ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
|
|
273
275
|
" ouro poke <agent> --task <task-id>",
|
|
@@ -536,6 +538,23 @@ function parseTaskCommand(args) {
|
|
|
536
538
|
return { kind: "task.sessions", ...(agent ? { agent } : {}) };
|
|
537
539
|
throw new Error(`Usage\n${usage()}`);
|
|
538
540
|
}
|
|
541
|
+
function parseAuthCommand(args) {
|
|
542
|
+
const { agent, rest } = extractAgentFlag(args);
|
|
543
|
+
let provider;
|
|
544
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
545
|
+
if (rest[i] === "--provider") {
|
|
546
|
+
const value = rest[i + 1];
|
|
547
|
+
if (!isAgentProvider(value))
|
|
548
|
+
throw new Error(`Usage\n${usage()}`);
|
|
549
|
+
provider = value;
|
|
550
|
+
i += 1;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (!agent)
|
|
555
|
+
throw new Error(`Usage\n${usage()}`);
|
|
556
|
+
return provider ? { kind: "auth.run", agent, provider } : { kind: "auth.run", agent };
|
|
557
|
+
}
|
|
539
558
|
function parseReminderCommand(args) {
|
|
540
559
|
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
541
560
|
const [sub, ...rest] = cleaned;
|
|
@@ -670,6 +689,8 @@ function parseOuroCommand(args) {
|
|
|
670
689
|
return { kind: "daemon.logs" };
|
|
671
690
|
if (head === "hatch")
|
|
672
691
|
return parseHatchCommand(args.slice(1));
|
|
692
|
+
if (head === "auth")
|
|
693
|
+
return parseAuthCommand(args.slice(1));
|
|
673
694
|
if (head === "task")
|
|
674
695
|
return parseTaskCommand(args.slice(1));
|
|
675
696
|
if (head === "reminder")
|
|
@@ -1059,6 +1080,7 @@ function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SO
|
|
|
1059
1080
|
runHatchFlow: hatch_flow_1.runHatchFlow,
|
|
1060
1081
|
promptInput: defaultPromptInput,
|
|
1061
1082
|
runAdoptionSpecialist: defaultRunAdoptionSpecialist,
|
|
1083
|
+
runAuthFlow: auth_flow_1.runRuntimeAuthFlow,
|
|
1062
1084
|
registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
|
|
1063
1085
|
installOuroCommand: ouro_path_installer_1.installOuroCommand,
|
|
1064
1086
|
syncGlobalOuroBotWrapper: ouro_bot_global_installer_1.syncGlobalOuroBotWrapper,
|
|
@@ -1095,24 +1117,13 @@ async function resolveHatchInput(command, deps) {
|
|
|
1095
1117
|
if (!agentName || !humanName || !isAgentProvider(providerRaw)) {
|
|
1096
1118
|
throw new Error(`Usage\n${usage()}`);
|
|
1097
1119
|
}
|
|
1098
|
-
const credentials =
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
1105
|
-
if (providerRaw === "minimax" && !credentials.apiKey && prompt) {
|
|
1106
|
-
credentials.apiKey = await prompt("MiniMax API key: ");
|
|
1107
|
-
}
|
|
1108
|
-
if (providerRaw === "azure") {
|
|
1109
|
-
if (!credentials.apiKey && prompt)
|
|
1110
|
-
credentials.apiKey = await prompt("Azure API key: ");
|
|
1111
|
-
if (!credentials.endpoint && prompt)
|
|
1112
|
-
credentials.endpoint = await prompt("Azure endpoint: ");
|
|
1113
|
-
if (!credentials.deployment && prompt)
|
|
1114
|
-
credentials.deployment = await prompt("Azure deployment: ");
|
|
1115
|
-
}
|
|
1120
|
+
const credentials = await (0, auth_flow_1.resolveHatchCredentials)({
|
|
1121
|
+
agentName,
|
|
1122
|
+
provider: providerRaw,
|
|
1123
|
+
credentials: command.credentials,
|
|
1124
|
+
promptInput: prompt,
|
|
1125
|
+
runAuthFlow: deps.runAuthFlow,
|
|
1126
|
+
});
|
|
1116
1127
|
return {
|
|
1117
1128
|
agentName,
|
|
1118
1129
|
humanName,
|
|
@@ -1512,6 +1523,21 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1512
1523
|
deps.writeStdout(message);
|
|
1513
1524
|
return message;
|
|
1514
1525
|
}
|
|
1526
|
+
// ── auth (local, no daemon socket needed) ──
|
|
1527
|
+
if (command.kind === "auth.run") {
|
|
1528
|
+
const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent).config.provider;
|
|
1529
|
+
const authRunner = deps.runAuthFlow ?? auth_flow_1.runRuntimeAuthFlow;
|
|
1530
|
+
const result = await authRunner({
|
|
1531
|
+
agentName: command.agent,
|
|
1532
|
+
provider,
|
|
1533
|
+
promptInput: deps.promptInput,
|
|
1534
|
+
});
|
|
1535
|
+
if (command.provider) {
|
|
1536
|
+
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, command.provider);
|
|
1537
|
+
}
|
|
1538
|
+
deps.writeStdout(result.message);
|
|
1539
|
+
return result.message;
|
|
1540
|
+
}
|
|
1515
1541
|
// ── whoami (local, no daemon socket needed) ──
|
|
1516
1542
|
if (command.kind === "whoami") {
|
|
1517
1543
|
if (command.agent) {
|
|
@@ -41,6 +41,7 @@ const path = __importStar(require("path"));
|
|
|
41
41
|
const identity_1 = require("../identity");
|
|
42
42
|
const config_1 = require("../config");
|
|
43
43
|
const runtime_1 = require("../../nerves/runtime");
|
|
44
|
+
const auth_flow_1 = require("./auth-flow");
|
|
44
45
|
const hatch_specialist_1 = require("./hatch-specialist");
|
|
45
46
|
function requiredCredentialKeys(provider) {
|
|
46
47
|
if (provider === "anthropic")
|
|
@@ -67,70 +68,8 @@ function validateCredentials(provider, credentials) {
|
|
|
67
68
|
throw new Error(`Missing required credentials for ${provider}: ${missing.join(", ")}`);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
|
-
function buildSecretsTemplate() {
|
|
71
|
-
return {
|
|
72
|
-
providers: {
|
|
73
|
-
azure: {
|
|
74
|
-
modelName: "gpt-4o-mini",
|
|
75
|
-
apiKey: "",
|
|
76
|
-
endpoint: "",
|
|
77
|
-
deployment: "",
|
|
78
|
-
apiVersion: "2025-04-01-preview",
|
|
79
|
-
},
|
|
80
|
-
minimax: {
|
|
81
|
-
model: "minimax-text-01",
|
|
82
|
-
apiKey: "",
|
|
83
|
-
},
|
|
84
|
-
anthropic: {
|
|
85
|
-
model: "claude-opus-4-6",
|
|
86
|
-
setupToken: "",
|
|
87
|
-
},
|
|
88
|
-
"openai-codex": {
|
|
89
|
-
model: "gpt-5.4",
|
|
90
|
-
oauthAccessToken: "",
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
teams: {
|
|
94
|
-
clientId: "",
|
|
95
|
-
clientSecret: "",
|
|
96
|
-
tenantId: "",
|
|
97
|
-
},
|
|
98
|
-
oauth: {
|
|
99
|
-
graphConnectionName: "graph",
|
|
100
|
-
adoConnectionName: "ado",
|
|
101
|
-
githubConnectionName: "",
|
|
102
|
-
},
|
|
103
|
-
teamsChannel: {
|
|
104
|
-
skipConfirmation: true,
|
|
105
|
-
port: 3978,
|
|
106
|
-
},
|
|
107
|
-
integrations: {
|
|
108
|
-
perplexityApiKey: "",
|
|
109
|
-
openaiEmbeddingsApiKey: "",
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
71
|
function writeSecretsFile(agentName, provider, credentials, secretsRoot) {
|
|
114
|
-
|
|
115
|
-
if (provider === "anthropic") {
|
|
116
|
-
secrets.providers.anthropic.setupToken = credentials.setupToken.trim();
|
|
117
|
-
}
|
|
118
|
-
else if (provider === "openai-codex") {
|
|
119
|
-
secrets.providers["openai-codex"].oauthAccessToken = credentials.oauthAccessToken.trim();
|
|
120
|
-
}
|
|
121
|
-
else if (provider === "minimax") {
|
|
122
|
-
secrets.providers.minimax.apiKey = credentials.apiKey.trim();
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
secrets.providers.azure.apiKey = credentials.apiKey.trim();
|
|
126
|
-
secrets.providers.azure.endpoint = credentials.endpoint.trim();
|
|
127
|
-
secrets.providers.azure.deployment = credentials.deployment.trim();
|
|
128
|
-
}
|
|
129
|
-
const secretsDir = path.join(secretsRoot, agentName);
|
|
130
|
-
fs.mkdirSync(secretsDir, { recursive: true });
|
|
131
|
-
const secretsPath = path.join(secretsDir, "secrets.json");
|
|
132
|
-
fs.writeFileSync(secretsPath, `${JSON.stringify(secrets, null, 2)}\n`, "utf-8");
|
|
133
|
-
return secretsPath;
|
|
72
|
+
return (0, auth_flow_1.writeProviderCredentials)(agentName, provider, credentials, { secretsRoot }).secretsPath;
|
|
134
73
|
}
|
|
135
74
|
function writeReadme(dir, purpose) {
|
|
136
75
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -24,10 +24,10 @@ function getAnthropicSetupTokenInstructions() {
|
|
|
24
24
|
const agentName = getAnthropicAgentNameForGuidance();
|
|
25
25
|
return [
|
|
26
26
|
"Fix:",
|
|
27
|
-
` 1. Run \`
|
|
28
|
-
" (or run `claude setup-token` and paste the token manually)",
|
|
27
|
+
` 1. Run \`ouro auth --agent ${agentName}\``,
|
|
29
28
|
` 2. Open ${getAnthropicSecretsPathForGuidance()}`,
|
|
30
29
|
" 3. Confirm providers.anthropic.setupToken is set",
|
|
30
|
+
" 4. After reauth, retry the failed ouro command or reconnect this session.",
|
|
31
31
|
].join("\n");
|
|
32
32
|
}
|
|
33
33
|
function getAnthropicReauthGuidance(reason) {
|
|
@@ -28,11 +28,11 @@ function getOpenAICodexOAuthInstructions() {
|
|
|
28
28
|
const agentName = getOpenAICodexAgentNameForGuidance();
|
|
29
29
|
return [
|
|
30
30
|
"Fix:",
|
|
31
|
-
` 1. Run \`
|
|
32
|
-
" (or run `codex login` and set the OAuth token manually)",
|
|
31
|
+
` 1. Run \`ouro auth --agent ${agentName}\``,
|
|
33
32
|
` 2. Open ${getOpenAICodexSecretsPathForGuidance()}`,
|
|
34
33
|
" 3. Confirm providers.openai-codex.oauthAccessToken is set",
|
|
35
34
|
" 4. This provider uses chatgpt.com/backend-api/codex/responses (not api.openai.com/responses).",
|
|
35
|
+
" 5. After reauth, retry the failed ouro command or reconnect this session.",
|
|
36
36
|
].join("\n");
|
|
37
37
|
}
|
|
38
38
|
function getOpenAICodexReauthGuidance(reason) {
|
package/dist/mind/memory.js
CHANGED
|
@@ -33,7 +33,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.__memoryTestUtils = void 0;
|
|
37
36
|
exports.ensureMemoryStorePaths = ensureMemoryStorePaths;
|
|
38
37
|
exports.appendFactsWithDedup = appendFactsWithDedup;
|
|
39
38
|
exports.readMemoryFacts = readMemoryFacts;
|
|
@@ -46,7 +45,9 @@ const crypto_1 = require("crypto");
|
|
|
46
45
|
const config_1 = require("../heart/config");
|
|
47
46
|
const identity_1 = require("../heart/identity");
|
|
48
47
|
const runtime_1 = require("../nerves/runtime");
|
|
48
|
+
const associative_recall_1 = require("./associative-recall");
|
|
49
49
|
const DEDUP_THRESHOLD = 0.6;
|
|
50
|
+
const SEMANTIC_DEDUP_THRESHOLD = 0.95;
|
|
50
51
|
const ENTITY_TOKEN = /[a-z0-9]+/g;
|
|
51
52
|
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
52
53
|
class OpenAIEmbeddingProvider {
|
|
@@ -177,13 +178,24 @@ function appendDailyFact(dailyDir, fact) {
|
|
|
177
178
|
const dayPath = path.join(dailyDir, `${day}.jsonl`);
|
|
178
179
|
fs.appendFileSync(dayPath, `${JSON.stringify(fact)}\n`, "utf8");
|
|
179
180
|
}
|
|
180
|
-
function appendFactsWithDedup(stores, incoming) {
|
|
181
|
+
function appendFactsWithDedup(stores, incoming, options) {
|
|
181
182
|
const existing = readExistingFacts(stores.factsPath);
|
|
182
183
|
const all = [...existing];
|
|
183
184
|
let added = 0;
|
|
184
185
|
let skipped = 0;
|
|
186
|
+
const semanticThreshold = options?.semanticThreshold;
|
|
185
187
|
for (const fact of incoming) {
|
|
186
|
-
const duplicate = all.some((prior) =>
|
|
188
|
+
const duplicate = all.some((prior) => {
|
|
189
|
+
if (overlapScore(prior.text, fact.text) > DEDUP_THRESHOLD)
|
|
190
|
+
return true;
|
|
191
|
+
if (semanticThreshold !== undefined &&
|
|
192
|
+
Array.isArray(fact.embedding) && fact.embedding.length > 0 &&
|
|
193
|
+
Array.isArray(prior.embedding) && prior.embedding.length > 0 &&
|
|
194
|
+
fact.embedding.length === prior.embedding.length) {
|
|
195
|
+
return (0, associative_recall_1.cosineSimilarity)(fact.embedding, prior.embedding) > semanticThreshold;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
});
|
|
187
199
|
if (duplicate) {
|
|
188
200
|
skipped++;
|
|
189
201
|
continue;
|
|
@@ -202,24 +214,6 @@ function appendFactsWithDedup(stores, incoming) {
|
|
|
202
214
|
});
|
|
203
215
|
return { added, skipped };
|
|
204
216
|
}
|
|
205
|
-
function cosineSimilarity(left, right) {
|
|
206
|
-
if (left.length === 0 || right.length === 0 || left.length !== right.length)
|
|
207
|
-
return 0;
|
|
208
|
-
let dot = 0;
|
|
209
|
-
let leftNorm = 0;
|
|
210
|
-
let rightNorm = 0;
|
|
211
|
-
for (let i = 0; i < left.length; i += 1) {
|
|
212
|
-
dot += left[i] * right[i];
|
|
213
|
-
leftNorm += left[i] * left[i];
|
|
214
|
-
rightNorm += right[i] * right[i];
|
|
215
|
-
}
|
|
216
|
-
if (leftNorm === 0 || rightNorm === 0)
|
|
217
|
-
return 0;
|
|
218
|
-
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
|
|
219
|
-
}
|
|
220
|
-
exports.__memoryTestUtils = {
|
|
221
|
-
cosineSimilarity,
|
|
222
|
-
};
|
|
223
217
|
function createDefaultEmbeddingProvider() {
|
|
224
218
|
const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
|
|
225
219
|
if (!apiKey)
|
|
@@ -271,7 +265,7 @@ async function saveMemoryFact(options) {
|
|
|
271
265
|
createdAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
272
266
|
embedding,
|
|
273
267
|
};
|
|
274
|
-
return appendFactsWithDedup(stores, [fact]);
|
|
268
|
+
return appendFactsWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
|
|
275
269
|
}
|
|
276
270
|
async function backfillEmbeddings(options) {
|
|
277
271
|
const memoryRoot = options?.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
|
|
@@ -372,7 +366,7 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
372
366
|
.filter((fact) => fact.embedding.length === queryEmbedding.length)
|
|
373
367
|
.map((fact) => ({
|
|
374
368
|
fact,
|
|
375
|
-
score: cosineSimilarity(queryEmbedding, fact.embedding),
|
|
369
|
+
score: (0, associative_recall_1.cosineSimilarity)(queryEmbedding, fact.embedding),
|
|
376
370
|
}))
|
|
377
371
|
.filter((entry) => entry.score > 0)
|
|
378
372
|
.sort((left, right) => right.score - left.score)
|