@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 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 = { ...(command.credentials ?? {}) };
1099
- if (providerRaw === "anthropic" && !credentials.setupToken && prompt) {
1100
- credentials.setupToken = await prompt("Anthropic setup-token: ");
1101
- }
1102
- if (providerRaw === "openai-codex" && !credentials.oauthAccessToken && prompt) {
1103
- credentials.oauthAccessToken = await prompt("OpenAI Codex OAuth token: ");
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
- const secrets = buildSecretsTemplate();
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 \`npm run auth:claude-setup-token -- --agent ${agentName}\``,
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 \`npm run auth:openai-codex -- --agent ${agentName}\``,
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) {
@@ -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) => overlapScore(prior.text, fact.text) > DEDUP_THRESHOLD);
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.54",
3
+ "version": "0.1.0-alpha.56",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",