@intent-systems/nexus 2026.1.5-3 → 2026.1.5-5

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.
Files changed (144) hide show
  1. package/dist/agents/agent-id.js +41 -0
  2. package/dist/agents/auth-profiles.js +114 -25
  3. package/dist/agents/identity-state.js +79 -0
  4. package/dist/agents/model-auth.js +1 -0
  5. package/dist/agents/model-fallback.js +15 -9
  6. package/dist/agents/model-selection.js +1 -1
  7. package/dist/agents/models-config.js +17 -11
  8. package/dist/agents/pi-embedded-runner.js +101 -9
  9. package/dist/agents/sandbox.js +12 -3
  10. package/dist/agents/skill-runner.js +29 -4
  11. package/dist/agents/skill-usage.js +114 -11
  12. package/dist/agents/skills-status.js +4 -4
  13. package/dist/agents/skills.js +18 -7
  14. package/dist/agents/subagent-registry.js +25 -11
  15. package/dist/agents/system-prompt.js +16 -0
  16. package/dist/agents/tool-policy.js +19 -3
  17. package/dist/agents/tools/browser-tool.js +5 -2
  18. package/dist/agents/tools/image-tool.js +93 -8
  19. package/dist/agents/tools/sessions-announce-target.js +5 -1
  20. package/dist/agents/workspace.js +55 -46
  21. package/dist/auto-reply/command-detection.js +2 -1
  22. package/dist/auto-reply/reply/directive-handling.js +153 -28
  23. package/dist/auto-reply/reply/directives.js +17 -2
  24. package/dist/auto-reply/reply/model-selection.js +8 -3
  25. package/dist/auto-reply/reply/queue.js +2 -2
  26. package/dist/auto-reply/reply.js +1 -1
  27. package/dist/auto-reply/thinking.js +15 -0
  28. package/dist/browser/chrome.js +1 -1
  29. package/dist/browser/client.js +2 -0
  30. package/dist/browser/config.js +6 -2
  31. package/dist/browser/pw-tools-core.js +3 -0
  32. package/dist/browser/routes/agent.js +14 -0
  33. package/dist/canvas-host/server.js +1 -1
  34. package/dist/capabilities/detector.js +245 -0
  35. package/dist/capabilities/registry.js +99 -0
  36. package/dist/channels/location.js +44 -0
  37. package/dist/channels/web/index.js +2 -0
  38. package/dist/cli/cloud-cli.js +12 -7
  39. package/dist/cli/credential-cli.js +139 -17
  40. package/dist/cli/gateway-cli.js +1 -1
  41. package/dist/cli/log-cli.js +25 -0
  42. package/dist/cli/pairing-cli.js +1 -1
  43. package/dist/cli/program.js +58 -6
  44. package/dist/cli/run-main.js +1 -1
  45. package/dist/cli/skills-cli.js +144 -21
  46. package/dist/cli/skills-hub-cli.js +59 -29
  47. package/dist/cli/tool-connector-cli.js +99 -24
  48. package/dist/cli/upstream-sync-cli.js +253 -96
  49. package/dist/cli/usage-cli.js +14 -0
  50. package/dist/commands/auth-choice-options.js +6 -1
  51. package/dist/commands/auth-choice.js +157 -5
  52. package/dist/commands/bootstrap-preset.js +10 -6
  53. package/dist/commands/capabilities.js +33 -6
  54. package/dist/commands/claude-md.js +3 -2
  55. package/dist/commands/config-view.js +1 -1
  56. package/dist/commands/configure.js +4 -4
  57. package/dist/commands/credential.js +497 -36
  58. package/dist/commands/cursor-rules.js +39 -19
  59. package/dist/commands/doctor.js +5 -4
  60. package/dist/commands/identity.js +28 -31
  61. package/dist/commands/init.js +15 -18
  62. package/dist/commands/log.js +134 -0
  63. package/dist/commands/models/fallbacks.js +1 -1
  64. package/dist/commands/models/image-fallbacks.js +1 -1
  65. package/dist/commands/models/list.js +1 -1
  66. package/dist/commands/models/scan.js +1 -1
  67. package/dist/commands/onboard-auth.js +27 -2
  68. package/dist/commands/onboard-eve-identity.js +7 -8
  69. package/dist/commands/onboard-non-interactive.js +4 -2
  70. package/dist/commands/onboard-quickstart.js +18 -11
  71. package/dist/commands/quest-state.js +271 -0
  72. package/dist/commands/quest.js +53 -13
  73. package/dist/commands/reset.js +1 -1
  74. package/dist/commands/sessions-ingest.js +5 -4
  75. package/dist/commands/setup.js +4 -2
  76. package/dist/commands/skills-manifest.js +2 -2
  77. package/dist/commands/status.js +179 -61
  78. package/dist/commands/suggestions.js +1 -1
  79. package/dist/commands/usage-tracking.js +32 -0
  80. package/dist/commands/usage-upload.js +6 -1
  81. package/dist/config/defaults.js +1 -3
  82. package/dist/config/includes.js +5 -7
  83. package/dist/config/io.js +88 -16
  84. package/dist/config/legacy.js +4 -2
  85. package/dist/config/paths.js +16 -0
  86. package/dist/config/sessions.js +9 -5
  87. package/dist/config/zod-schema.js +4 -3
  88. package/dist/control-plane/broker/broker.js +1022 -0
  89. package/dist/control-plane/compaction.js +282 -0
  90. package/dist/control-plane/factory.js +31 -0
  91. package/dist/control-plane/index.js +10 -0
  92. package/dist/control-plane/odu/agents.js +192 -0
  93. package/dist/control-plane/odu/interaction-tools.js +208 -0
  94. package/dist/control-plane/odu/prompt-loader.js +95 -0
  95. package/dist/control-plane/odu/runtime.js +479 -0
  96. package/dist/control-plane/odu/types.js +6 -0
  97. package/dist/control-plane/odu-control-plane.js +316 -0
  98. package/dist/control-plane/single-agent.js +249 -0
  99. package/dist/control-plane/types.js +11 -0
  100. package/dist/credentials/store.js +449 -0
  101. package/dist/gateway/server-browser.js +5 -4
  102. package/dist/gateway/server-methods/cron.js +11 -1
  103. package/dist/gateway/server.js +14 -7
  104. package/dist/infra/bonjour.js +1 -1
  105. package/dist/infra/event-log.js +8 -2
  106. package/dist/infra/path-env.js +1 -2
  107. package/dist/infra/provider-usage.auth.js +5 -3
  108. package/dist/infra/provider-usage.fetch.claude.js +16 -6
  109. package/dist/infra/provider-usage.fetch.minimax.js +8 -3
  110. package/dist/infra/provider-usage.js +9 -5
  111. package/dist/infra/restart.js +2 -2
  112. package/dist/infra/usage-settings.js +78 -0
  113. package/dist/infra/usage-suggestions.js +17 -5
  114. package/dist/infra/usage-upload.js +38 -1
  115. package/dist/infra/voicewake.js +2 -2
  116. package/dist/logging/redact.js +109 -0
  117. package/dist/markdown/fences.js +58 -0
  118. package/dist/media/image-ops.js +3 -1
  119. package/dist/memory/embeddings.js +146 -0
  120. package/dist/memory/index.js +3 -0
  121. package/dist/memory/internal.js +163 -0
  122. package/dist/pairing/pairing-store.js +218 -0
  123. package/dist/plugins/cli.js +42 -0
  124. package/dist/plugins/discovery.js +253 -0
  125. package/dist/plugins/install.js +181 -0
  126. package/dist/plugins/loader.js +290 -0
  127. package/dist/plugins/registry.js +105 -0
  128. package/dist/plugins/status.js +29 -0
  129. package/dist/plugins/tools.js +39 -0
  130. package/dist/plugins/types.js +1 -0
  131. package/dist/providers/github-copilot-auth.js +1 -1
  132. package/dist/routing/resolve-route.js +144 -0
  133. package/dist/routing/session-key.js +65 -0
  134. package/dist/sessions/send-policy.js +5 -5
  135. package/dist/slack/monitor.js +22 -1
  136. package/dist/telegram/reaction-level.js +2 -1
  137. package/dist/utils/provider-utils.js +28 -0
  138. package/dist/utils.js +4 -3
  139. package/dist/wizard/onboarding.js +29 -7
  140. package/package.json +4 -29
  141. package/patches/@mariozechner__pi-ai.patch +215 -0
  142. package/patches/playwright-core@1.57.0.patch +13 -0
  143. package/patches/qrcode-terminal.patch +12 -0
  144. package/scripts/postinstall.js +202 -0
@@ -0,0 +1,449 @@
1
+ import { exec, execFile } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import fsp from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { resolveCredentialsDir } from "../config/paths.js";
7
+ export { resolveCredentialsDir };
8
+ const execFileAsync = promisify(execFile);
9
+ const execAsync = promisify(exec);
10
+ export function resolveCredentialIndexPath() {
11
+ return path.join(resolveCredentialsDir(), "index.json");
12
+ }
13
+ export function resolveCredentialPath(service, account, authId) {
14
+ return path.join(resolveCredentialsDir(), service, "accounts", account, "auth", `${authId}.json`);
15
+ }
16
+ export function readCredentialRecordSync(filePath) {
17
+ try {
18
+ const raw = fs.readFileSync(filePath, "utf-8");
19
+ const parsed = JSON.parse(raw);
20
+ if (!parsed || typeof parsed !== "object")
21
+ return null;
22
+ if (!parsed.type || !parsed.storage)
23
+ return null;
24
+ return parsed;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ export async function readCredentialRecord(filePath) {
31
+ try {
32
+ const raw = await fsp.readFile(filePath, "utf-8");
33
+ const parsed = JSON.parse(raw);
34
+ if (!parsed || typeof parsed !== "object")
35
+ return null;
36
+ if (!parsed.type || !parsed.storage)
37
+ return null;
38
+ return parsed;
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ export function writeCredentialRecordSync(service, account, authId, record) {
45
+ const filePath = resolveCredentialPath(service, account, authId);
46
+ const dir = path.dirname(filePath);
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ fs.writeFileSync(filePath, `${JSON.stringify(record, null, 2)}\n`, "utf-8");
49
+ fs.chmodSync(filePath, 0o600);
50
+ }
51
+ export async function writeCredentialRecord(service, account, authId, record) {
52
+ const filePath = resolveCredentialPath(service, account, authId);
53
+ const dir = path.dirname(filePath);
54
+ await fsp.mkdir(dir, { recursive: true });
55
+ await fsp.writeFile(filePath, `${JSON.stringify(record, null, 2)}\n`, "utf-8");
56
+ }
57
+ function isDirentDirectory(entry) {
58
+ return typeof entry !== "string";
59
+ }
60
+ export function listCredentialEntriesSync() {
61
+ const root = resolveCredentialsDir();
62
+ if (!fs.existsSync(root))
63
+ return [];
64
+ const services = fs.readdirSync(root, { withFileTypes: true });
65
+ const entries = [];
66
+ for (const serviceEntry of services) {
67
+ if (!isDirentDirectory(serviceEntry) || !serviceEntry.isDirectory())
68
+ continue;
69
+ const service = serviceEntry.name;
70
+ const accountsDir = path.join(root, service, "accounts");
71
+ if (!fs.existsSync(accountsDir))
72
+ continue;
73
+ const accounts = fs.readdirSync(accountsDir, { withFileTypes: true });
74
+ for (const accountEntry of accounts) {
75
+ if (!isDirentDirectory(accountEntry) || !accountEntry.isDirectory())
76
+ continue;
77
+ const account = accountEntry.name;
78
+ const authDir = path.join(accountsDir, account, "auth");
79
+ if (!fs.existsSync(authDir))
80
+ continue;
81
+ const authFiles = fs.readdirSync(authDir, { withFileTypes: true });
82
+ for (const authEntry of authFiles) {
83
+ if (!isDirentDirectory(authEntry) || authEntry.isDirectory())
84
+ continue;
85
+ if (!authEntry.name.endsWith(".json"))
86
+ continue;
87
+ const authId = authEntry.name.replace(/\.json$/, "");
88
+ const filePath = path.join(authDir, authEntry.name);
89
+ const record = readCredentialRecordSync(filePath);
90
+ if (!record)
91
+ continue;
92
+ entries.push({ service, account, authId, filePath, record });
93
+ }
94
+ }
95
+ }
96
+ return entries;
97
+ }
98
+ export async function listCredentialEntries() {
99
+ const root = resolveCredentialsDir();
100
+ if (!fs.existsSync(root))
101
+ return [];
102
+ const services = await fsp.readdir(root, { withFileTypes: true });
103
+ const entries = [];
104
+ for (const serviceEntry of services) {
105
+ if (!isDirentDirectory(serviceEntry) || !serviceEntry.isDirectory())
106
+ continue;
107
+ const service = serviceEntry.name;
108
+ const accountsDir = path.join(root, service, "accounts");
109
+ if (!fs.existsSync(accountsDir))
110
+ continue;
111
+ const accounts = await fsp.readdir(accountsDir, { withFileTypes: true });
112
+ for (const accountEntry of accounts) {
113
+ if (!isDirentDirectory(accountEntry) || !accountEntry.isDirectory())
114
+ continue;
115
+ const account = accountEntry.name;
116
+ const authDir = path.join(accountsDir, account, "auth");
117
+ if (!fs.existsSync(authDir))
118
+ continue;
119
+ const authFiles = await fsp.readdir(authDir, { withFileTypes: true });
120
+ for (const authEntry of authFiles) {
121
+ if (!isDirentDirectory(authEntry) || authEntry.isDirectory())
122
+ continue;
123
+ if (!authEntry.name.endsWith(".json"))
124
+ continue;
125
+ const authId = authEntry.name.replace(/\.json$/, "");
126
+ const filePath = path.join(authDir, authEntry.name);
127
+ const record = await readCredentialRecord(filePath);
128
+ if (!record)
129
+ continue;
130
+ entries.push({ service, account, authId, filePath, record });
131
+ }
132
+ }
133
+ }
134
+ return entries;
135
+ }
136
+ export function buildCredentialIndex(entries) {
137
+ const services = {};
138
+ for (const entry of entries) {
139
+ const service = services[entry.service] ?? { accounts: [] };
140
+ const account = service.accounts.find((acc) => acc.id === entry.account);
141
+ const status = entry.record.lastError
142
+ ? "broken"
143
+ : entry.record.lastUsed
144
+ ? "active"
145
+ : "ready";
146
+ if (account) {
147
+ if (!account.auths.includes(entry.authId))
148
+ account.auths.push(entry.authId);
149
+ if (entry.record.lastError) {
150
+ account.status = "broken";
151
+ account.lastError = entry.record.lastError;
152
+ }
153
+ else if (account.status !== "broken" && entry.record.lastUsed) {
154
+ account.status = "active";
155
+ account.lastUsed = entry.record.lastUsed;
156
+ }
157
+ }
158
+ else {
159
+ service.accounts.push({
160
+ id: entry.account,
161
+ owner: entry.record.owner,
162
+ auths: [entry.authId],
163
+ status,
164
+ lastUsed: entry.record.lastUsed,
165
+ lastError: entry.record.lastError ?? null,
166
+ });
167
+ }
168
+ services[entry.service] = service;
169
+ }
170
+ return {
171
+ version: 1,
172
+ lastUpdated: new Date().toISOString(),
173
+ services,
174
+ };
175
+ }
176
+ export function readCredentialIndexSync() {
177
+ const indexPath = resolveCredentialIndexPath();
178
+ try {
179
+ const raw = fs.readFileSync(indexPath, "utf-8");
180
+ const parsed = JSON.parse(raw);
181
+ if (!parsed || typeof parsed !== "object")
182
+ return null;
183
+ if (!parsed.services || typeof parsed.services !== "object")
184
+ return null;
185
+ return parsed;
186
+ }
187
+ catch {
188
+ return null;
189
+ }
190
+ }
191
+ export async function readCredentialIndex() {
192
+ const indexPath = resolveCredentialIndexPath();
193
+ try {
194
+ const raw = await fsp.readFile(indexPath, "utf-8");
195
+ const parsed = JSON.parse(raw);
196
+ if (!parsed || typeof parsed !== "object")
197
+ return null;
198
+ if (!parsed.services || typeof parsed.services !== "object")
199
+ return null;
200
+ return parsed;
201
+ }
202
+ catch {
203
+ return null;
204
+ }
205
+ }
206
+ export function writeCredentialIndexSync(index) {
207
+ const indexPath = resolveCredentialIndexPath();
208
+ fs.mkdirSync(path.dirname(indexPath), { recursive: true });
209
+ fs.writeFileSync(indexPath, `${JSON.stringify(index, null, 2)}\n`, "utf-8");
210
+ }
211
+ export async function writeCredentialIndex(index) {
212
+ const indexPath = resolveCredentialIndexPath();
213
+ await fsp.mkdir(path.dirname(indexPath), { recursive: true });
214
+ await fsp.writeFile(indexPath, `${JSON.stringify(index, null, 2)}\n`, "utf-8");
215
+ }
216
+ export function ensureCredentialIndexSync() {
217
+ const existing = readCredentialIndexSync();
218
+ if (existing)
219
+ return existing;
220
+ const index = buildCredentialIndex(listCredentialEntriesSync());
221
+ writeCredentialIndexSync(index);
222
+ return index;
223
+ }
224
+ function resolveStorageField(storage, field) {
225
+ if (storage.provider !== "1password")
226
+ return null;
227
+ const exact = storage.fields?.[field];
228
+ if (exact)
229
+ return exact;
230
+ if (field === "key")
231
+ return storage.fields?.apiKey ?? storage.fields?.token ?? null;
232
+ if (field === "accessToken")
233
+ return storage.fields?.accessToken ?? storage.fields?.token ?? null;
234
+ return null;
235
+ }
236
+ async function readRawFromStorage(storage, field) {
237
+ if (storage.provider === "keychain") {
238
+ try {
239
+ const { stdout } = await execFileAsync("security", [
240
+ "find-generic-password",
241
+ "-s",
242
+ storage.service,
243
+ "-a",
244
+ storage.account,
245
+ "-w",
246
+ ]);
247
+ const trimmed = stdout.trim();
248
+ return trimmed ? trimmed : null;
249
+ }
250
+ catch {
251
+ return null;
252
+ }
253
+ }
254
+ if (storage.provider === "env") {
255
+ const envValue = process.env[storage.var];
256
+ if (typeof envValue !== "string")
257
+ return null;
258
+ const trimmed = envValue.trim();
259
+ return trimmed ? trimmed : null;
260
+ }
261
+ if (storage.provider === "1password") {
262
+ const fieldName = resolveStorageField(storage, field);
263
+ if (!fieldName)
264
+ return null;
265
+ const itemRef = `op://${storage.vault}/${storage.item}/${fieldName}`;
266
+ try {
267
+ const { stdout } = await execFileAsync("op", ["read", itemRef]);
268
+ const trimmed = stdout.trim();
269
+ return trimmed ? trimmed : null;
270
+ }
271
+ catch {
272
+ return null;
273
+ }
274
+ }
275
+ if (storage.provider === "external") {
276
+ try {
277
+ const command = storage.command ?? storage.syncCommand;
278
+ if (!command)
279
+ return null;
280
+ const { stdout } = await execAsync(command);
281
+ const trimmed = stdout.trim();
282
+ return trimmed ? trimmed : null;
283
+ }
284
+ catch {
285
+ return null;
286
+ }
287
+ }
288
+ return null;
289
+ }
290
+ async function readSecretFromStorage(record, field) {
291
+ const storage = record.storage;
292
+ const raw = await readRawFromStorage(storage, field);
293
+ if (!raw)
294
+ return null;
295
+ const wantsJson = storage.format === "json" || Boolean(storage.jsonPath);
296
+ if (!wantsJson)
297
+ return raw;
298
+ try {
299
+ const parsed = JSON.parse(raw);
300
+ const pathSpec = storage.jsonPath ?? field;
301
+ const resolved = resolveJsonPath(parsed, pathSpec);
302
+ if (typeof resolved === "string")
303
+ return resolved.trim() || null;
304
+ if (typeof resolved === "number")
305
+ return String(resolved);
306
+ return null;
307
+ }
308
+ catch {
309
+ return null;
310
+ }
311
+ }
312
+ function resolveJsonPath(value, pathSpec) {
313
+ const parts = pathSpec.split(".").filter(Boolean);
314
+ let current = value;
315
+ for (const part of parts) {
316
+ if (!current || typeof current !== "object")
317
+ return undefined;
318
+ const match = /^(.+?)\[(\d+)\]$/.exec(part);
319
+ if (match) {
320
+ const [, key, indexRaw] = match;
321
+ const index = Number.parseInt(indexRaw, 10);
322
+ if (Number.isNaN(index))
323
+ return undefined;
324
+ const next = current[key ?? ""];
325
+ if (!Array.isArray(next))
326
+ return undefined;
327
+ current = next[index];
328
+ continue;
329
+ }
330
+ current = current[part];
331
+ }
332
+ return current;
333
+ }
334
+ export function resolveDefaultEnvVar(params) {
335
+ const service = params.service.toLowerCase();
336
+ const type = params.type;
337
+ const known = {
338
+ anthropic: {
339
+ api_key: "ANTHROPIC_API_KEY",
340
+ token: "ANTHROPIC_OAUTH_TOKEN",
341
+ oauth: "ANTHROPIC_OAUTH_TOKEN",
342
+ },
343
+ openai: { api_key: "OPENAI_API_KEY" },
344
+ gemini: { api_key: "GEMINI_API_KEY" },
345
+ "brave-search": { api_key: "BRAVE_API_KEY" },
346
+ github: { token: "GITHUB_TOKEN" },
347
+ "github-copilot": { token: "COPILOT_GITHUB_TOKEN" },
348
+ discord: { token: "DISCORD_BOT_TOKEN" },
349
+ slack: { token: "SLACK_BOT_TOKEN" },
350
+ openrouter: { api_key: "OPENROUTER_API_KEY" },
351
+ zai: { api_key: "ZAI_API_KEY" },
352
+ minimax: { api_key: "MINIMAX_API_KEY", token: "MINIMAX_API_KEY" },
353
+ "openai-codex": { oauth: "NEXUS_OPENAI_CODEX_TOKEN" },
354
+ "google-antigravity": { oauth: "NEXUS_GOOGLE_ANTIGRAVITY_TOKEN" },
355
+ chutes: { oauth: "NEXUS_CHUTES_TOKEN" },
356
+ "nexus-cloud": { token: "NEXUS_CLOUD_TOKEN" },
357
+ "nexus-hub": { token: "NEXUS_HUB_TOKEN" },
358
+ };
359
+ const mapped = known[service]?.[type];
360
+ if (mapped)
361
+ return mapped;
362
+ const suffix = type === "api_key"
363
+ ? "API_KEY"
364
+ : type === "oauth"
365
+ ? "OAUTH_TOKEN"
366
+ : type === "token"
367
+ ? "TOKEN"
368
+ : "CONFIG";
369
+ const normalized = service.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
370
+ return `NEXUS_${normalized}_${suffix}`;
371
+ }
372
+ export async function resolveOAuthBundle(record) {
373
+ if (record.type !== "oauth")
374
+ return {};
375
+ const raw = await readRawFromStorage(record.storage, "accessToken");
376
+ if (!raw)
377
+ return {};
378
+ const wantsJson = record.storage.format === "json" || Boolean(record.storage.jsonPath);
379
+ if (!wantsJson) {
380
+ return { accessToken: raw, expiresAt: record.expiresAt };
381
+ }
382
+ try {
383
+ const parsed = JSON.parse(raw);
384
+ if (record.storage.jsonPath) {
385
+ const access = resolveJsonPath(parsed, record.storage.jsonPath);
386
+ if (typeof access === "string") {
387
+ return { accessToken: access };
388
+ }
389
+ }
390
+ const accessToken = typeof parsed.accessToken === "string"
391
+ ? parsed.accessToken
392
+ : typeof parsed.access_token === "string"
393
+ ? parsed.access_token
394
+ : undefined;
395
+ const refreshToken = typeof parsed.refreshToken === "string"
396
+ ? parsed.refreshToken
397
+ : typeof parsed.refresh_token === "string"
398
+ ? parsed.refresh_token
399
+ : undefined;
400
+ const expiresAt = typeof parsed.expiresAt === "number" || typeof parsed.expiresAt === "string"
401
+ ? parsed.expiresAt
402
+ : typeof parsed.expires_at === "number" || typeof parsed.expires_at === "string"
403
+ ? parsed.expires_at
404
+ : undefined;
405
+ return { accessToken, refreshToken, expiresAt };
406
+ }
407
+ catch {
408
+ return {};
409
+ }
410
+ }
411
+ export async function storeKeychainSecret(params) {
412
+ try {
413
+ await execFileAsync("security", [
414
+ "add-generic-password",
415
+ "-U",
416
+ "-s",
417
+ params.service,
418
+ "-a",
419
+ params.account,
420
+ "-w",
421
+ params.value,
422
+ ]);
423
+ return true;
424
+ }
425
+ catch {
426
+ return false;
427
+ }
428
+ }
429
+ export async function resolveCredentialValue(record) {
430
+ if (record.type === "api_key") {
431
+ const value = await readSecretFromStorage(record, "key");
432
+ if (!value)
433
+ return null;
434
+ return { value, field: "key" };
435
+ }
436
+ if (record.type === "token") {
437
+ const value = await readSecretFromStorage(record, "token");
438
+ if (!value)
439
+ return null;
440
+ return { value, field: "token" };
441
+ }
442
+ if (record.type === "oauth") {
443
+ const value = await readSecretFromStorage(record, "accessToken");
444
+ if (!value)
445
+ return null;
446
+ return { value, field: "accessToken" };
447
+ }
448
+ return null;
449
+ }
@@ -1,11 +1,12 @@
1
1
  export async function startBrowserControlServerIfEnabled() {
2
- if (process.env.NEXUS_SKIP_BROWSER_CONTROL_SERVER === "1" ||
3
- process.env.NEXUS_SKIP_BROWSER_CONTROL_SERVER === "1")
2
+ if (process.env.NEXUS_SKIP_BROWSER_CONTROL_SERVER === "1")
4
3
  return null;
5
4
  // Lazy import: keeps startup fast, but still bundles for the embedded
6
5
  // gateway (bun --compile) via the static specifier path.
7
- const override = (process.env.NEXUS_BROWSER_CONTROL_MODULE ?? process.env.NEXUS_BROWSER_CONTROL_MODULE)?.trim();
8
- const mod = override ? await import(override) : await import("../browser/server.js");
6
+ const override = process.env.NEXUS_BROWSER_CONTROL_MODULE?.trim();
7
+ const mod = override
8
+ ? await import(override)
9
+ : await import("../browser/server.js");
9
10
  await mod.startBrowserControlServerFromConfig();
10
11
  return { stop: mod.stopBrowserControlServer };
11
12
  }
@@ -41,9 +41,19 @@ export const cronHandlers = {
41
41
  },
42
42
  "cron.update": async ({ params, respond, context }) => {
43
43
  const normalizedPatch = normalizeCronJobPatch(params?.patch);
44
- const candidate = normalizedPatch && typeof params === "object" && params !== null
44
+ const withPatch = normalizedPatch && typeof params === "object" && params !== null
45
45
  ? { ...params, patch: normalizedPatch }
46
46
  : params;
47
+ const candidate = withPatch && typeof withPatch === "object" && withPatch !== null
48
+ ? (() => {
49
+ const entry = withPatch;
50
+ if (!entry.id && entry.jobId) {
51
+ const { jobId, ...rest } = entry;
52
+ return { ...rest, id: jobId };
53
+ }
54
+ return entry;
55
+ })()
56
+ : withPatch;
47
57
  if (!validateCronUpdateParams(candidate)) {
48
58
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid cron.update params: ${formatValidationErrors(validateCronUpdateParams.errors)}`));
49
59
  return;
@@ -2,10 +2,10 @@ import { randomUUID } from "node:crypto";
2
2
  import os from "node:os";
3
3
  import chalk from "chalk";
4
4
  import { WebSocketServer } from "ws";
5
- import { createSubagentRegistry } from "../agents/subagent-registry.js";
6
5
  import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
7
6
  import { loadModelCatalog, resetModelCatalogCacheForTest, } from "../agents/model-catalog.js";
8
7
  import { resolveConfiguredModelRef } from "../agents/model-selection.js";
8
+ import { createSubagentRegistry } from "../agents/subagent-registry.js";
9
9
  import { CANVAS_HOST_PATH } from "../canvas-host/a2ui.js";
10
10
  import { createCanvasHostHandler, startCanvasHost, } from "../canvas-host/server.js";
11
11
  import { createDefaultDeps } from "../cli/deps.js";
@@ -256,7 +256,8 @@ export async function startGatewayServer(port = 18789, opts = {}) {
256
256
  allowTailscale,
257
257
  };
258
258
  let hooksConfig = resolveHooksConfig(cfgAtStart);
259
- const canvasHostEnabled = process.env.NEXUS_SKIP_CANVAS_HOST !== "1" && cfgAtStart.canvasHost?.enabled !== false;
259
+ const canvasHostEnabled = process.env.NEXUS_SKIP_CANVAS_HOST !== "1" &&
260
+ cfgAtStart.canvasHost?.enabled !== false;
260
261
  assertGatewayAuthConfigured(resolvedAuth);
261
262
  if (tailscaleMode === "funnel" && authMode !== "password") {
262
263
  throw new Error("tailscale funnel requires gateway auth mode=password (set gateway.auth.password or NEXUS_GATEWAY_PASSWORD)");
@@ -464,7 +465,7 @@ export async function startGatewayServer(port = 18789, opts = {}) {
464
465
  return resolveMainSessionKey(cfg);
465
466
  return resolveAgentMainSessionKey({ cfg, agentId });
466
467
  };
467
- const enqueueCronSystemEvent = (text, opts) => {
468
+ const _enqueueCronSystemEvent = (text, opts) => {
468
469
  const sessionKey = resolveCronMainSessionKey(opts?.agentId);
469
470
  if (sessionKey) {
470
471
  enqueueSystemEvent(text, { sessionKey });
@@ -586,7 +587,9 @@ export async function startGatewayServer(port = 18789, opts = {}) {
586
587
  }
587
588
  if (process.env.NEXUS_BRIDGE_PORT !== undefined) {
588
589
  const parsed = Number.parseInt(process.env.NEXUS_BRIDGE_PORT, 10);
589
- return Number.isFinite(parsed) && parsed > 0 ? parsed : deriveDefaultBridgePort(port);
590
+ return Number.isFinite(parsed) && parsed > 0
591
+ ? parsed
592
+ : deriveDefaultBridgePort(port);
590
593
  }
591
594
  return deriveDefaultBridgePort(port);
592
595
  })();
@@ -1049,7 +1052,9 @@ export async function startGatewayServer(port = 18789, opts = {}) {
1049
1052
  type: "hello-ok",
1050
1053
  protocol: PROTOCOL_VERSION,
1051
1054
  server: {
1052
- version: process.env.NEXUS_VERSION ?? process.env.npm_package_version ?? "dev",
1055
+ version: process.env.NEXUS_VERSION ??
1056
+ process.env.npm_package_version ??
1057
+ "dev",
1053
1058
  commit: process.env.GIT_COMMIT,
1054
1059
  host: os.hostname(),
1055
1060
  connId,
@@ -1240,7 +1245,8 @@ export async function startGatewayServer(port = 18789, opts = {}) {
1240
1245
  }
1241
1246
  // Launch configured channels (WhatsApp Web, Discord, Slack, Telegram) so gateway replies via the
1242
1247
  // surface the message came from. Tests can opt out via NEXUS_SKIP_CHANNELS (or legacy _PROVIDERS).
1243
- const skipChannels = process.env.NEXUS_SKIP_CHANNELS === "1" || process.env.NEXUS_SKIP_PROVIDERS === "1";
1248
+ const skipChannels = process.env.NEXUS_SKIP_CHANNELS === "1" ||
1249
+ process.env.NEXUS_SKIP_PROVIDERS === "1";
1244
1250
  if (!skipChannels) {
1245
1251
  try {
1246
1252
  await startProviders();
@@ -1315,7 +1321,8 @@ export async function startGatewayServer(port = 18789, opts = {}) {
1315
1321
  }
1316
1322
  }
1317
1323
  if (plan.restartProviders.size > 0) {
1318
- const skipChannelReload = process.env.NEXUS_SKIP_CHANNELS === "1" || process.env.NEXUS_SKIP_PROVIDERS === "1";
1324
+ const skipChannelReload = process.env.NEXUS_SKIP_CHANNELS === "1" ||
1325
+ process.env.NEXUS_SKIP_PROVIDERS === "1";
1319
1326
  if (skipChannelReload) {
1320
1327
  logChannels.info("skipping channel reload (NEXUS_SKIP_CHANNELS=1)");
1321
1328
  }
@@ -2,7 +2,7 @@ import os from "node:os";
2
2
  import { logDebug, logWarn } from "../logger.js";
3
3
  import { getLogger } from "../logging.js";
4
4
  function isDisabledByEnv() {
5
- if (process.env.NEXUS_DISABLE_BONJOUR === "1" || process.env.NEXUS_DISABLE_BONJOUR === "1")
5
+ if (process.env.NEXUS_DISABLE_BONJOUR === "1")
6
6
  return true;
7
7
  if (process.env.NODE_ENV === "test")
8
8
  return true;
@@ -200,7 +200,11 @@ export function recordCliCommandFinish(status = "ok") {
200
200
  export function recordCliCommandFailure(error) {
201
201
  if (!cliSession?.activeCommand)
202
202
  return;
203
- const message = error instanceof Error ? error.message : typeof error === "string" ? error : "unknown error";
203
+ const message = error instanceof Error
204
+ ? error.message
205
+ : typeof error === "string"
206
+ ? error
207
+ : "unknown error";
204
208
  writeEvent({
205
209
  id: createEventId(),
206
210
  ts: Date.now(),
@@ -227,7 +231,9 @@ export function recordCliSessionEnd(params) {
227
231
  invocation_kind: cliSession.invocationKind,
228
232
  status: params.status,
229
233
  error: params.error,
230
- data: params.exitCode !== undefined ? { exit_code: params.exitCode } : undefined,
234
+ data: params.exitCode !== undefined
235
+ ? { exit_code: params.exitCode }
236
+ : undefined,
231
237
  });
232
238
  cliSession = null;
233
239
  }
@@ -79,10 +79,9 @@ function candidateBinDirs(opts) {
79
79
  * under launchd/minimal environments (and inside the macOS bun bundle).
80
80
  */
81
81
  export function ensureNexusCliOnPath(opts = {}) {
82
- if (process.env.NEXUS_PATH_BOOTSTRAPPED === "1" || process.env.NEXUS_PATH_BOOTSTRAPPED === "1")
82
+ if (process.env.NEXUS_PATH_BOOTSTRAPPED === "1")
83
83
  return;
84
84
  process.env.NEXUS_PATH_BOOTSTRAPPED = "1";
85
- process.env.NEXUS_PATH_BOOTSTRAPPED = "1"; // keep for backward compat
86
85
  const existing = opts.pathEnv ?? process.env.PATH ?? "";
87
86
  const prepend = candidateBinDirs(opts);
88
87
  if (prepend.length === 0)
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { CLAUDE_CLI_PROFILE_ID, ensureAuthProfileStore, listProfilesForProvider, resolveApiKeyForProfile, resolveAuthProfileOrder, } from "../agents/auth-profiles.js";
5
- import { getCustomProviderApiKey, resolveEnvApiKey } from "../agents/model-auth.js";
5
+ import { getCustomProviderApiKey, resolveEnvApiKey, } from "../agents/model-auth.js";
6
6
  import { normalizeProviderId } from "../agents/model-selection.js";
7
7
  import { loadConfig } from "../config/config.js";
8
8
  function parseGoogleToken(apiKey) {
@@ -53,7 +53,8 @@ function resolveZaiApiKey() {
53
53
  }
54
54
  }
55
55
  function resolveMinimaxApiKey() {
56
- const envDirect = process.env.MINIMAX_CODE_PLAN_KEY?.trim() || process.env.MINIMAX_API_KEY?.trim();
56
+ const envDirect = process.env.MINIMAX_CODE_PLAN_KEY?.trim() ||
57
+ process.env.MINIMAX_API_KEY?.trim();
57
58
  if (envDirect)
58
59
  return envDirect;
59
60
  const envResolved = resolveEnvApiKey("minimax");
@@ -112,7 +113,8 @@ async function resolveOAuthToken(params) {
112
113
  if (!resolved?.apiKey)
113
114
  continue;
114
115
  let token = resolved.apiKey;
115
- if (params.provider === "google-gemini-cli" || params.provider === "google-antigravity") {
116
+ if (params.provider === "google-gemini-cli" ||
117
+ params.provider === "google-antigravity") {
116
118
  const parsed = parseGoogleToken(resolved.apiKey);
117
119
  token = parsed?.token ?? resolved.apiKey;
118
120
  }