@intent-systems/nexus 2026.1.5-4 → 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 (123) 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 +46 -15
  35. package/dist/capabilities/registry.js +2 -1
  36. package/dist/cli/cloud-cli.js +12 -7
  37. package/dist/cli/credential-cli.js +139 -17
  38. package/dist/cli/gateway-cli.js +1 -1
  39. package/dist/cli/log-cli.js +25 -0
  40. package/dist/cli/pairing-cli.js +1 -1
  41. package/dist/cli/program.js +58 -6
  42. package/dist/cli/run-main.js +1 -1
  43. package/dist/cli/skills-cli.js +144 -21
  44. package/dist/cli/skills-hub-cli.js +59 -29
  45. package/dist/cli/tool-connector-cli.js +99 -24
  46. package/dist/cli/upstream-sync-cli.js +253 -96
  47. package/dist/cli/usage-cli.js +14 -0
  48. package/dist/commands/auth-choice-options.js +6 -1
  49. package/dist/commands/auth-choice.js +157 -5
  50. package/dist/commands/bootstrap-preset.js +10 -6
  51. package/dist/commands/capabilities.js +33 -6
  52. package/dist/commands/claude-md.js +3 -2
  53. package/dist/commands/config-view.js +1 -1
  54. package/dist/commands/configure.js +4 -4
  55. package/dist/commands/credential.js +497 -36
  56. package/dist/commands/cursor-rules.js +39 -19
  57. package/dist/commands/doctor.js +5 -4
  58. package/dist/commands/identity.js +28 -31
  59. package/dist/commands/init.js +15 -18
  60. package/dist/commands/log.js +134 -0
  61. package/dist/commands/models/fallbacks.js +1 -1
  62. package/dist/commands/models/image-fallbacks.js +1 -1
  63. package/dist/commands/models/list.js +1 -1
  64. package/dist/commands/models/scan.js +1 -1
  65. package/dist/commands/onboard-auth.js +27 -2
  66. package/dist/commands/onboard-eve-identity.js +7 -8
  67. package/dist/commands/onboard-non-interactive.js +4 -2
  68. package/dist/commands/onboard-quickstart.js +18 -11
  69. package/dist/commands/quest-state.js +271 -0
  70. package/dist/commands/quest.js +53 -13
  71. package/dist/commands/reset.js +1 -1
  72. package/dist/commands/sessions-ingest.js +5 -4
  73. package/dist/commands/setup.js +4 -2
  74. package/dist/commands/skills-manifest.js +2 -2
  75. package/dist/commands/status.js +179 -61
  76. package/dist/commands/suggestions.js +1 -1
  77. package/dist/commands/usage-tracking.js +32 -0
  78. package/dist/commands/usage-upload.js +6 -1
  79. package/dist/config/defaults.js +1 -3
  80. package/dist/config/includes.js +5 -7
  81. package/dist/config/io.js +88 -16
  82. package/dist/config/legacy.js +4 -2
  83. package/dist/config/paths.js +16 -0
  84. package/dist/config/sessions.js +9 -5
  85. package/dist/config/zod-schema.js +4 -3
  86. package/dist/control-plane/broker/broker.js +131 -78
  87. package/dist/control-plane/compaction.js +3 -5
  88. package/dist/control-plane/factory.js +2 -2
  89. package/dist/control-plane/index.js +2 -2
  90. package/dist/control-plane/odu/agents.js +28 -23
  91. package/dist/control-plane/odu/interaction-tools.js +62 -50
  92. package/dist/control-plane/odu/prompt-loader.js +8 -8
  93. package/dist/control-plane/odu/runtime.js +87 -75
  94. package/dist/control-plane/odu-control-plane.js +14 -12
  95. package/dist/control-plane/single-agent.js +13 -13
  96. package/dist/credentials/store.js +133 -7
  97. package/dist/gateway/server-browser.js +5 -4
  98. package/dist/gateway/server-methods/cron.js +11 -1
  99. package/dist/gateway/server.js +14 -7
  100. package/dist/infra/bonjour.js +1 -1
  101. package/dist/infra/event-log.js +8 -2
  102. package/dist/infra/path-env.js +1 -2
  103. package/dist/infra/provider-usage.auth.js +5 -3
  104. package/dist/infra/provider-usage.fetch.claude.js +16 -6
  105. package/dist/infra/provider-usage.fetch.minimax.js +8 -3
  106. package/dist/infra/provider-usage.js +9 -5
  107. package/dist/infra/restart.js +2 -2
  108. package/dist/infra/usage-settings.js +78 -0
  109. package/dist/infra/usage-suggestions.js +17 -5
  110. package/dist/infra/usage-upload.js +38 -1
  111. package/dist/infra/voicewake.js +2 -2
  112. package/dist/media/image-ops.js +3 -1
  113. package/dist/memory/index.js +2 -381
  114. package/dist/pairing/pairing-store.js +24 -0
  115. package/dist/providers/github-copilot-auth.js +1 -1
  116. package/dist/routing/resolve-route.js +6 -6
  117. package/dist/routing/session-key.js +3 -1
  118. package/dist/sessions/send-policy.js +5 -5
  119. package/dist/slack/monitor.js +22 -1
  120. package/dist/telegram/reaction-level.js +2 -1
  121. package/dist/utils.js +4 -3
  122. package/dist/wizard/onboarding.js +29 -7
  123. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import fsp from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { flattenManifest, generateSkillManifest, readSkillManifest, } from "./skills-manifest.js";
5
4
  import { resolveStateDir } from "../config/paths.js";
6
- import { MANAGED_SKILLS_DIR, NEXUS_ROOT, resolveUserPath } from "../utils.js";
7
5
  import { defaultRuntime } from "../runtime.js";
6
+ import { MANAGED_SKILLS_DIR, NEXUS_ROOT, resolveUserPath } from "../utils.js";
7
+ import { flattenManifest, generateSkillManifest, readSkillManifest, } from "./skills-manifest.js";
8
8
  /**
9
9
  * Generate and write `.cursor/rules` for a given workspace.
10
10
  *
@@ -35,28 +35,46 @@ function categorizeSkills(skills) {
35
35
  "Media & Content": [],
36
36
  "Smart Home & Devices": [],
37
37
  "Development & Tools": [],
38
- "Other": [],
38
+ Other: [],
39
39
  };
40
40
  const categoryPatterns = [
41
- ["Communication & Messaging", [/imsg|imessage|whatsapp|discord|slack|telegram|sms|message|chat/i]],
42
- ["Productivity & Notes", [/note|reminder|bear|obsidian|notion|things|trello|task|todo/i]],
43
- ["Google & Cloud Services", [/google|gmail|calendar|drive|sheets|docs|gemini|gog|places/i]],
44
- ["Media & Content", [/image|video|audio|camera|gif|pdf|whisper|speech|photo|screenshot/i]],
45
- ["Smart Home & Devices", [/hue|sonos|spotify|eight|sleep|smart|home|light|speaker/i]],
46
- ["Development & Tools", [/github|git|code|coding|terminal|bash|cli|api|mcp|1password|search/i]],
41
+ [
42
+ "Communication & Messaging",
43
+ [/imsg|imessage|whatsapp|discord|slack|telegram|sms|message|chat/i],
44
+ ],
45
+ [
46
+ "Productivity & Notes",
47
+ [/note|reminder|bear|obsidian|notion|things|trello|task|todo/i],
48
+ ],
49
+ [
50
+ "Google & Cloud Services",
51
+ [/google|gmail|calendar|drive|sheets|docs|gemini|gog|places/i],
52
+ ],
53
+ [
54
+ "Media & Content",
55
+ [/image|video|audio|camera|gif|pdf|whisper|speech|photo|screenshot/i],
56
+ ],
57
+ [
58
+ "Smart Home & Devices",
59
+ [/hue|sonos|spotify|eight|sleep|smart|home|light|speaker/i],
60
+ ],
61
+ [
62
+ "Development & Tools",
63
+ [/github|git|code|coding|terminal|bash|cli|api|mcp|1password|search/i],
64
+ ],
47
65
  ];
48
66
  for (const skill of skills) {
49
67
  const text = `${skill.name} ${skill.description || ""}`;
50
68
  let matched = false;
51
69
  for (const [category, patterns] of categoryPatterns) {
52
- if (patterns.some(p => p.test(text))) {
70
+ if (patterns.some((p) => p.test(text))) {
53
71
  categories[category].push(skill);
54
72
  matched = true;
55
73
  break;
56
74
  }
57
75
  }
58
76
  if (!matched) {
59
- categories["Other"].push(skill);
77
+ categories.Other.push(skill);
60
78
  }
61
79
  }
62
80
  return categories;
@@ -73,7 +91,8 @@ function formatSkillsSection(skills) {
73
91
  lines.push(`### ${category}`);
74
92
  for (const skill of categorySkills.sort((a, b) => a.name.localeCompare(b.name))) {
75
93
  const desc = skill.description
76
- ? skill.description.slice(0, 80) + (skill.description.length > 80 ? "..." : "")
94
+ ? skill.description.slice(0, 80) +
95
+ (skill.description.length > 80 ? "..." : "")
77
96
  : "(no description)";
78
97
  lines.push(`- **${skill.name}**: ${desc}`);
79
98
  }
@@ -105,7 +124,7 @@ async function loadAgentsMd(workspaceDir) {
105
124
  /**
106
125
  * Load bootstrap files (IDENTITY.md, PROFILE.md, SOUL.md) if they exist
107
126
  */
108
- async function loadBootstrapFiles(workspaceDir) {
127
+ async function loadBootstrapFiles(_workspaceDir) {
109
128
  const files = {};
110
129
  const agentId = process.env.NEXUS_AGENT_ID?.trim() || "default";
111
130
  const stateDir = resolveStateDir();
@@ -137,6 +156,7 @@ export async function generateCursorRules(options = {}) {
137
156
  const workspaceDir = options.workspaceDir || process.cwd();
138
157
  const skillsDir = options.skillsDir || MANAGED_SKILLS_DIR;
139
158
  const userSkillsDir = options.userSkillsDir || path.join(NEXUS_ROOT, "home", "skills");
159
+ const managedSkillsDir = path.join(skillsDir, "managed");
140
160
  // Try to read existing manifest, or generate one
141
161
  let manifest = await readSkillManifest();
142
162
  if (!manifest) {
@@ -181,14 +201,14 @@ nexus cursor-rules --output .cursor/rules
181
201
 
182
202
  ## Available Skills (${skills.length} total)
183
203
 
184
- The following skills are available in \`~/nexus/skills/\`. Read the full \`SKILL.md\` when you need detailed usage instructions.
204
+ The following skills are available in \`${skillsDir}\`. Read the full \`SKILL.md\` when you need detailed usage instructions.
185
205
 
186
206
  ${skillsSection}
187
207
  ## How to Use Skills
188
208
 
189
209
  1. **When a user's request relates to a skill**, read the corresponding SKILL.md:
190
210
  \`\`\`bash
191
- cat ~/nexus/skills/<skill>/SKILL.md
211
+ cat ${path.join(skillsDir, "<skill>", "SKILL.md")}
192
212
  \`\`\`
193
213
 
194
214
  2. **Skills provide CLI tools** - use bash to run them:
@@ -207,13 +227,13 @@ ${skillsSection}
207
227
  ${agentsMdSection}${bootstrapSection}
208
228
  ## Skill Directory Structure
209
229
 
210
- - **Bundled**: \`~/nexus/skills/\` (${manifest.bundled.count} skills)
211
- - **Managed**: \`~/nexus/skills/managed/\` (${manifest.managed.count} skills)
212
- - **User**: \`~/nexus/home/skills/\` (${manifest.user.count} skills)
230
+ - **Bundled**: \`${skillsDir}\` (${manifest.bundled.count} skills)
231
+ - **Managed**: \`${managedSkillsDir}\` (${manifest.managed.count} skills)
232
+ - **User**: \`${userSkillsDir}\` (${manifest.user.count} skills)
213
233
 
214
234
  Precedence: User > Managed > Bundled
215
235
  `;
216
- return rules.trim() + "\n";
236
+ return `${rules.trim()}\n`;
217
237
  }
218
238
  /**
219
239
  * Write cursor rules to the specified output path
@@ -24,7 +24,7 @@ import { ensureSystemdUserLingerInteractive } from "./systemd-linger.js";
24
24
  function resolveMode(cfg) {
25
25
  return cfg.gateway?.mode === "remote" ? "remote" : "local";
26
26
  }
27
- function resolveLegacyConfigPath(env) {
27
+ function resolveLegacyConfigPath(_env) {
28
28
  return path.join(os.homedir(), ".nexus", "nexus.json");
29
29
  }
30
30
  async function noteSecurityWarnings(cfg) {
@@ -438,8 +438,7 @@ async function maybeMigrateLegacyConfigFile(runtime) {
438
438
  const gatewayBind = typeof legacySnapshot.parsed?.gateway?.bind === "string"
439
439
  ? legacySnapshot.parsed.gateway?.bind
440
440
  : undefined;
441
- const agentWorkspace = typeof legacySnapshot.parsed?.agent?.workspace ===
442
- "string"
441
+ const agentWorkspace = typeof legacySnapshot.parsed?.agent?.workspace === "string"
443
442
  ? legacySnapshot.parsed.agent?.workspace
444
443
  : undefined;
445
444
  note([
@@ -471,7 +470,9 @@ async function maybeMigrateLegacyGatewayService(cfg, runtime) {
471
470
  const legacyServices = await findLegacyGatewayServices(process.env);
472
471
  if (legacyServices.length === 0)
473
472
  return;
474
- note(legacyServices.map((svc) => `- ${svc.label} (${svc.platform}, ${svc.detail})`).join("\n"), "Legacy Nexus services detected");
473
+ note(legacyServices
474
+ .map((svc) => `- ${svc.label} (${svc.platform}, ${svc.detail})`)
475
+ .join("\n"), "Legacy Nexus services detected");
475
476
  const migrate = guardCancel(await confirm({
476
477
  message: "Migrate legacy Nexus services to Nexus now?",
477
478
  initialValue: true,
@@ -1,40 +1,37 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { resolveStateDir } from "../config/paths.js";
1
+ import { resolveIdentitySnapshot } from "../agents/identity-state.js";
4
2
  import { defaultRuntime } from "../runtime.js";
5
- function readField(pathname, label) {
6
- try {
7
- const raw = fs.readFileSync(pathname, "utf-8");
8
- const regex = new RegExp(`^[-*]?\\s*${label}\\s*:\\s*(.+)$`, "im");
9
- const match = raw.match(regex);
10
- return match?.[1]?.trim();
11
- }
12
- catch {
13
- return undefined;
14
- }
15
- }
16
3
  export async function identityCommand(opts, runtime = defaultRuntime) {
17
- const stateDir = resolveStateDir();
18
- const agentId = process.env.NEXUS_AGENT_ID?.trim() || "default";
19
- const agentIdentityDir = path.join(stateDir, "agents", agentId, "identity");
20
- const userIdentityDir = path.join(stateDir, "user", "identity");
21
- const agentIdentityPath = path.join(agentIdentityDir, "IDENTITY.md");
22
- const agentSoulPath = path.join(agentIdentityDir, "SOUL.md");
23
- const agentMemoryPath = path.join(agentIdentityDir, "MEMORY.md");
24
- const userProfilePath = path.join(userIdentityDir, "PROFILE.md");
4
+ const resolution = resolveIdentitySnapshot();
5
+ if (!resolution.ok) {
6
+ const payload = {
7
+ ok: false,
8
+ error: "multiple_agents",
9
+ agents: resolution.agentOptions,
10
+ };
11
+ if (opts.json) {
12
+ runtime.log(JSON.stringify(payload, null, 2));
13
+ }
14
+ else {
15
+ runtime.error("Multiple agents detected in state/agents.");
16
+ runtime.error(`Set NEXUS_AGENT_ID to one of: ${payload.agents.join(", ")}`);
17
+ }
18
+ runtime.exit(2);
19
+ return;
20
+ }
21
+ const identity = resolution.snapshot;
25
22
  const payload = {
26
23
  agent: {
27
- id: agentId,
28
- name: readField(agentIdentityPath, "Name"),
29
- identityPath: agentIdentityPath,
30
- soulPath: agentSoulPath,
31
- memoryPath: agentMemoryPath,
32
- exists: fs.existsSync(agentIdentityPath),
24
+ id: identity.agentId,
25
+ name: identity.agentName,
26
+ identityPath: identity.agentIdentityPath,
27
+ soulPath: identity.agentSoulPath,
28
+ memoryPath: identity.agentMemoryPath,
29
+ exists: identity.agentIdentityExists,
33
30
  },
34
31
  user: {
35
- name: readField(userProfilePath, "Name"),
36
- profilePath: userProfilePath,
37
- exists: fs.existsSync(userProfilePath),
32
+ name: identity.userName,
33
+ profilePath: identity.userProfilePath,
34
+ exists: identity.userProfileExists,
38
35
  },
39
36
  };
40
37
  if (opts.json) {
@@ -6,9 +6,10 @@ import { ensureAgentWorkspace } from "../agents/workspace.js";
6
6
  import { CONFIG_PATH_NEXUS, writeConfigFile } from "../config/config.js";
7
7
  import { resolveStateDir } from "../config/paths.js";
8
8
  import { defaultRuntime } from "../runtime.js";
9
- import { NEXUS_ROOT } from "../utils.js";
10
- import { copyBundledSkills, generateSkillManifest, writeSkillManifest } from "./skills-manifest.js";
9
+ import { MANAGED_SKILLS_DIR, NEXUS_ROOT } from "../utils.js";
10
+ import { scanCredentials } from "./credential.js";
11
11
  import { writeCursorRules } from "./cursor-rules.js";
12
+ import { copyBundledSkills, generateSkillManifest, writeSkillManifest, } from "./skills-manifest.js";
12
13
  const execFileAsync = promisify(execFile);
13
14
  /**
14
15
  * Checks if a directory is already a git repository.
@@ -84,30 +85,25 @@ build/
84
85
  * - ~/nexus/state/ (state directory)
85
86
  */
86
87
  export async function initCommand(opts, runtime = defaultRuntime) {
87
- const workspaceDir = opts?.workspace?.trim() || path.join(process.env.HOME || "~", "nexus", "home");
88
+ const workspaceDir = opts?.workspace?.trim() ||
89
+ path.join(process.env.HOME || "~", "nexus", "home");
90
+ const createHomeLayout = false;
88
91
  // 1. Create workspace directory and Nexus state scaffolding
89
92
  const workspace = await ensureAgentWorkspace({
90
93
  dir: workspaceDir,
91
94
  ensureBootstrapFiles: true,
95
+ bootstrapMode: "minimal",
96
+ createHomeLayout,
92
97
  });
93
98
  runtime.log(`✓ Created workspace: ${workspace.dir}`);
94
99
  if (workspace.agentsPath)
95
100
  runtime.log(` - ${path.basename(workspace.agentsPath)}`);
96
- if (workspace.toolsPath)
101
+ if (workspace.toolsPath && createHomeLayout)
97
102
  runtime.log(` - ${path.basename(workspace.toolsPath)}`);
98
103
  if (workspace.bootstrapPath)
99
104
  runtime.log(` - ${path.basename(workspace.bootstrapPath)}`);
100
- // 2. Create user skills/ directory (for personal custom skills)
105
+ // 2. Resolve user skills directory (may be created later by user)
101
106
  const userSkillsDir = path.join(workspace.dir, "skills");
102
- await fs.mkdir(userSkillsDir, { recursive: true });
103
- runtime.log(`✓ Created user skills directory: ${userSkillsDir}`);
104
- // 3. Create memory/ and projects/ directories
105
- const memoryDir = path.join(workspace.dir, "memory");
106
- await fs.mkdir(memoryDir, { recursive: true });
107
- const projectsDir = path.join(workspace.dir, "projects");
108
- await fs.mkdir(projectsDir, { recursive: true });
109
- runtime.log(`✓ Created memory directory: ${memoryDir}`);
110
- runtime.log(`✓ Created projects directory: ${projectsDir}`);
111
107
  // 4. Create state directory (~/nexus/state)
112
108
  const stateDir = resolveStateDir();
113
109
  await fs.mkdir(stateDir, { recursive: true });
@@ -131,8 +127,11 @@ export async function initCommand(opts, runtime = defaultRuntime) {
131
127
  };
132
128
  await fs.writeFile(workspaceMetaPath, JSON.stringify(workspaceMeta, null, 2), "utf-8");
133
129
  runtime.log(`✓ Created workspace metadata: ${workspaceMetaPath}`);
134
- // 6. Create main skills directory (~/nexus/skills) and copy bundled skills
135
- const skillsDir = path.join(NEXUS_ROOT, "skills");
130
+ // 5b. Ensure credential index
131
+ await scanCredentials();
132
+ runtime.log(`✓ Created credential index: ${path.join(stateDir, "credentials", "index.json")}`);
133
+ // 6. Create main skills directory (state/skills) and copy bundled skills
134
+ const skillsDir = MANAGED_SKILLS_DIR;
136
135
  await fs.mkdir(skillsDir, { recursive: true });
137
136
  runtime.log(`✓ Created skills directory: ${skillsDir}`);
138
137
  // Copy bundled skills from installed package to ~/nexus/skills/
@@ -160,8 +159,6 @@ export async function initCommand(opts, runtime = defaultRuntime) {
160
159
  includeBootstrap: false,
161
160
  });
162
161
  runtime.log(`✓ Created Cursor rules: ${cursorRulesPath}`);
163
- // 9. Initialize git repository for workspace
164
- await initGitRepo(workspace.dir, runtime);
165
162
  runtime.log("\nNexus initialization complete!");
166
163
  runtime.log(`\nNext steps:\n - Run 'nexus status' to orient\n - Open ${NEXUS_ROOT} in your editor`);
167
164
  }
@@ -0,0 +1,134 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parseDurationMs } from "../cli/parse-duration.js";
4
+ import { readSkillUsageEntries } from "../agents/skill-usage.js";
5
+ import { resolveEventLogDir } from "../infra/event-log.js";
6
+ import { defaultRuntime } from "../runtime.js";
7
+ function parseSince(raw) {
8
+ if (!raw)
9
+ return undefined;
10
+ const trimmed = raw.trim();
11
+ if (!trimmed)
12
+ return undefined;
13
+ const parsedDate = Date.parse(trimmed);
14
+ if (!Number.isNaN(parsedDate)) {
15
+ return parsedDate;
16
+ }
17
+ const ms = parseDurationMs(trimmed, { defaultUnit: "h" });
18
+ return Date.now() - ms;
19
+ }
20
+ function readEventFiles(dir) {
21
+ try {
22
+ return fs
23
+ .readdirSync(dir, { withFileTypes: true })
24
+ .filter((entry) => entry.isFile() &&
25
+ entry.name.startsWith("events-") &&
26
+ entry.name.endsWith(".jsonl"))
27
+ .map((entry) => entry.name)
28
+ .sort();
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ }
34
+ function parseEventLine(line) {
35
+ try {
36
+ const parsed = JSON.parse(line);
37
+ if (!parsed || typeof parsed !== "object")
38
+ return null;
39
+ if (typeof parsed.ts !== "number")
40
+ return null;
41
+ return parsed;
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ function loadEventLogEntries(opts) {
48
+ const dir = resolveEventLogDir();
49
+ const files = readEventFiles(dir);
50
+ const events = [];
51
+ for (const file of files) {
52
+ const filePath = path.join(dir, file);
53
+ let raw = "";
54
+ try {
55
+ raw = fs.readFileSync(filePath, "utf-8");
56
+ }
57
+ catch {
58
+ continue;
59
+ }
60
+ for (const line of raw.split(/\r?\n/)) {
61
+ if (!line.trim())
62
+ continue;
63
+ const event = parseEventLine(line);
64
+ if (!event)
65
+ continue;
66
+ if (opts.sinceMs && event.ts < opts.sinceMs)
67
+ continue;
68
+ if (opts.source && event.source !== opts.source)
69
+ continue;
70
+ if (opts.command && event.command_path !== opts.command)
71
+ continue;
72
+ if (opts.errors) {
73
+ const isError = event.status === "error" ||
74
+ event.event_type === "command_failed" ||
75
+ event.event_type === "cli_session_end";
76
+ if (!isError)
77
+ continue;
78
+ }
79
+ events.push(event);
80
+ }
81
+ }
82
+ if (opts.limit && opts.limit > 0 && events.length > opts.limit) {
83
+ return events.slice(-opts.limit);
84
+ }
85
+ return events;
86
+ }
87
+ export async function logCommand(opts, runtime = defaultRuntime) {
88
+ const sinceMs = parseSince(opts.since);
89
+ const limit = opts.limit && opts.limit > 0 ? opts.limit : undefined;
90
+ if (opts.skill) {
91
+ const entries = readSkillUsageEntries(opts.skill, {
92
+ sinceMs,
93
+ limit,
94
+ });
95
+ if (opts.json) {
96
+ runtime.log(JSON.stringify({ skill: opts.skill, entries }, null, 2));
97
+ return;
98
+ }
99
+ if (entries.length === 0) {
100
+ runtime.log("No skill usage found.");
101
+ return;
102
+ }
103
+ for (const entry of entries) {
104
+ const time = new Date(entry.ts).toISOString();
105
+ const status = entry.ok ? "ok" : "error";
106
+ const duration = typeof entry.durationMs === "number"
107
+ ? ` ${entry.durationMs}ms`
108
+ : "";
109
+ runtime.log(`${time} ${entry.event} ${status}${duration}`);
110
+ }
111
+ return;
112
+ }
113
+ const events = loadEventLogEntries({
114
+ sinceMs,
115
+ errors: opts.errors,
116
+ source: opts.source,
117
+ command: opts.command,
118
+ limit,
119
+ });
120
+ if (opts.json) {
121
+ runtime.log(JSON.stringify({ events }, null, 2));
122
+ return;
123
+ }
124
+ if (events.length === 0) {
125
+ runtime.log("No events found.");
126
+ return;
127
+ }
128
+ for (const event of events) {
129
+ const time = new Date(event.ts).toISOString();
130
+ const status = event.status ?? "-";
131
+ const command = event.command_path ?? event.event_type ?? "-";
132
+ runtime.log(`${time} ${event.source}:${command} ${status}`);
133
+ }
134
+ }
@@ -1,4 +1,4 @@
1
- import { buildModelAliasIndex, resolveModelRefFromString } from "../../agents/model-selection.js";
1
+ import { buildModelAliasIndex, resolveModelRefFromString, } from "../../agents/model-selection.js";
2
2
  import { CONFIG_PATH_NEXUS, loadConfig } from "../../config/config.js";
3
3
  import { DEFAULT_PROVIDER, ensureFlagCompatibility, modelKey, resolveModelTarget, updateConfig, } from "./shared.js";
4
4
  export async function modelsFallbacksListCommand(opts, runtime) {
@@ -1,4 +1,4 @@
1
- import { buildModelAliasIndex, resolveModelRefFromString } from "../../agents/model-selection.js";
1
+ import { buildModelAliasIndex, resolveModelRefFromString, } from "../../agents/model-selection.js";
2
2
  import { CONFIG_PATH_NEXUS, loadConfig } from "../../config/config.js";
3
3
  import { DEFAULT_PROVIDER, ensureFlagCompatibility, modelKey, resolveModelTarget, updateConfig, } from "./shared.js";
4
4
  export async function modelsImageFallbacksListCommand(opts, runtime) {
@@ -5,7 +5,7 @@ import { ensureAuthProfileStore, listProfilesForProvider, } from "../../agents/a
5
5
  import { getCustomProviderApiKey, resolveEnvApiKey, } from "../../agents/model-auth.js";
6
6
  import { buildModelAliasIndex, parseModelRef, resolveConfiguredModelRef, resolveModelRefFromString, } from "../../agents/model-selection.js";
7
7
  import { ensureNexusModelsJson } from "../../agents/models-config.js";
8
- import { CONFIG_PATH_NEXUS, loadConfig } from "../../config/config.js";
8
+ import { CONFIG_PATH_NEXUS, loadConfig, } from "../../config/config.js";
9
9
  import { info } from "../../globals.js";
10
10
  import { DEFAULT_MODEL, DEFAULT_PROVIDER, ensureFlagCompatibility, formatTokenK, modelKey, } from "./shared.js";
11
11
  const MODEL_PAD = 42;
@@ -1,6 +1,6 @@
1
1
  import { cancel, isCancel, multiselect } from "@clack/prompts";
2
2
  import { resolveApiKeyForProvider } from "../../agents/model-auth.js";
3
- import { scanOpenRouterModels } from "../../agents/model-scan.js";
3
+ import { scanOpenRouterModels, } from "../../agents/model-scan.js";
4
4
  import { CONFIG_PATH_NEXUS, loadConfig } from "../../config/config.js";
5
5
  import { formatMs, formatTokenK, updateConfig } from "./shared.js";
6
6
  const MODEL_PAD = 42;
@@ -1,11 +1,36 @@
1
1
  import { upsertAuthProfile } from "../agents/auth-profiles.js";
2
2
  export async function writeOAuthCredentials(provider, creds) {
3
+ const access = "accessToken" in creds && typeof creds.accessToken === "string"
4
+ ? creds.accessToken
5
+ : typeof creds.access === "string"
6
+ ? creds.access
7
+ : undefined;
8
+ const refresh = "refreshToken" in creds && typeof creds.refreshToken === "string"
9
+ ? creds.refreshToken
10
+ : typeof creds.refresh === "string"
11
+ ? creds.refresh
12
+ : undefined;
13
+ const rawExpires = "expiresAt" in creds
14
+ ? creds.expiresAt
15
+ : creds.expires;
16
+ const parsedExpires = typeof rawExpires === "number"
17
+ ? rawExpires
18
+ : typeof rawExpires === "string"
19
+ ? Number.parseInt(rawExpires, 10)
20
+ : undefined;
21
+ const expires = typeof parsedExpires === "number" && Number.isFinite(parsedExpires)
22
+ ? parsedExpires
23
+ : undefined;
24
+ const email = typeof creds.email === "string" ? creds.email : undefined;
3
25
  upsertAuthProfile({
4
- profileId: `${provider}:${creds.email ?? "default"}`,
26
+ profileId: `${provider}:${email ?? "default"}`,
5
27
  credential: {
6
28
  type: "oauth",
7
29
  provider,
8
- ...creds,
30
+ access,
31
+ refresh,
32
+ expires,
33
+ email,
9
34
  },
10
35
  });
11
36
  }
@@ -5,7 +5,10 @@ const execFileAsync = promisify(execFile);
5
5
  function coerceStringArray(input) {
6
6
  if (!Array.isArray(input))
7
7
  return [];
8
- return input.map((v) => String(v)).map((s) => s.trim()).filter(Boolean);
8
+ return input
9
+ .map((v) => String(v))
10
+ .map((s) => s.trim())
11
+ .filter(Boolean);
9
12
  }
10
13
  function parseEveWhoamiJson(stdout) {
11
14
  const raw = stdout.trim();
@@ -78,18 +81,14 @@ export async function maybeSetupIdentityFromEve(params) {
78
81
  await prompter.note(`Eve whoami failed: ${err instanceof Error ? err.message : String(err)}`, "Eve whoami");
79
82
  return;
80
83
  }
81
- if (!whoami?.name && (!whoami?.phones?.length && !whoami?.emails?.length)) {
84
+ if (!whoami?.name && !whoami?.phones?.length && !whoami?.emails?.length) {
82
85
  await prompter.note("Eve ran, but didn't return identity info in a readable format.", "Eve whoami");
83
86
  return;
84
87
  }
85
88
  const summaryLines = [
86
89
  whoami.name ? `Name: ${whoami.name}` : undefined,
87
- whoami.phones && whoami.phones.length
88
- ? `Phones: ${whoami.phones.join(", ")}`
89
- : undefined,
90
- whoami.emails && whoami.emails.length
91
- ? `Emails: ${whoami.emails.join(", ")}`
92
- : undefined,
90
+ whoami.phones?.length ? `Phones: ${whoami.phones.join(", ")}` : undefined,
91
+ whoami.emails?.length ? `Emails: ${whoami.emails.join(", ")}` : undefined,
93
92
  ].filter(Boolean);
94
93
  await prompter.note(summaryLines.join("\n"), "Detected identity (Eve)");
95
94
  // NOTE: We don't directly edit IDENTITY.md / PROFILE.md here yet; the bootstrap ritual
@@ -5,9 +5,9 @@ import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
5
5
  import { resolveGatewayService } from "../daemon/service.js";
6
6
  import { defaultRuntime } from "../runtime.js";
7
7
  import { resolveUserPath, sleep } from "../utils.js";
8
+ import { applyBootstrapPreset } from "./bootstrap-preset.js";
8
9
  import { healthCommand } from "./health.js";
9
10
  import { applyAuthProfileConfig, applyMinimaxConfig, setAnthropicApiKey, } from "./onboard-auth.js";
10
- import { applyBootstrapPreset } from "./bootstrap-preset.js";
11
11
  import { applyWizardMetadata, DEFAULT_WORKSPACE, ensureWorkspaceAndSessions, randomToken, } from "./onboard-helpers.js";
12
12
  import { ensureSystemdUserLingerNonInteractive } from "./systemd-linger.js";
13
13
  export async function runNonInteractiveOnboarding(opts, runtime = defaultRuntime) {
@@ -180,7 +180,9 @@ export async function runNonInteractiveOnboarding(opts, runtime = defaultRuntime
180
180
  skipBootstrap: Boolean(nextConfig.agent?.skipBootstrap),
181
181
  });
182
182
  // Scripted bootstrap (test harness / automation): fill identity files + delete BOOTSTRAP.md.
183
- if (opts.bootstrapPreset || opts.bootstrapAgentName || opts.bootstrapUserName) {
183
+ if (opts.bootstrapPreset ||
184
+ opts.bootstrapAgentName ||
185
+ opts.bootstrapUserName) {
184
186
  await applyBootstrapPreset({
185
187
  workspaceDir,
186
188
  runtime,
@@ -1,18 +1,18 @@
1
1
  import net from "node:net";
2
+ import { CLAUDE_CLI_PROFILE_ID, CODEX_CLI_PROFILE_ID, ensureAuthProfileStore, } from "../agents/auth-profiles.js";
2
3
  import { CONFIG_PATH_NEXUS, readConfigFileSnapshot, resolveGatewayPort, writeConfigFile, } from "../config/config.js";
3
- import { resolveGatewayService } from "../daemon/service.js";
4
- import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
5
4
  import { GATEWAY_LAUNCH_AGENT_LABEL } from "../daemon/constants.js";
5
+ import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
6
+ import { resolveGatewayService } from "../daemon/service.js";
6
7
  import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
7
8
  import { defaultRuntime } from "../runtime.js";
8
9
  import { resolveUserPath, sleep } from "../utils.js";
10
+ import { applyBootstrapPreset } from "./bootstrap-preset.js";
9
11
  import { healthCommand } from "./health.js";
10
12
  import { applyAuthProfileConfig, setAnthropicApiKey } from "./onboard-auth.js";
11
- import { applyBootstrapPreset } from "./bootstrap-preset.js";
12
13
  import { applyWizardMetadata, DEFAULT_WORKSPACE, detectBrowserOpenSupport, ensureWorkspaceAndSessions, openUrl, resolveControlUiLinks, } from "./onboard-helpers.js";
13
- import { CLAUDE_CLI_PROFILE_ID, CODEX_CLI_PROFILE_ID, ensureAuthProfileStore, } from "../agents/auth-profiles.js";
14
- import { ensureSystemdUserLingerNonInteractive } from "./systemd-linger.js";
15
14
  import { applyOpenAICodexModelDefault } from "./openai-codex-model-default.js";
15
+ import { ensureSystemdUserLingerNonInteractive } from "./systemd-linger.js";
16
16
  async function isPortFree(port) {
17
17
  try {
18
18
  await new Promise((resolve, reject) => {
@@ -51,7 +51,7 @@ async function pickGatewayPort(preferred) {
51
51
  });
52
52
  }
53
53
  function applyQuickstartAuth(cfg, runtime) {
54
- // Sync external credentials (Keychain on macOS, ~/.claude, ~/.codex) into the store.
54
+ // Quickstart only considers credentials already imported into the store.
55
55
  const store = ensureAuthProfileStore();
56
56
  // 1) Prefer OpenAI Codex credentials if present (works without API keys).
57
57
  if (store.profiles[CODEX_CLI_PROFILE_ID]) {
@@ -79,9 +79,9 @@ function applyQuickstartAuth(cfg, runtime) {
79
79
  // valid for general Anthropic API requests. We avoid auto-selecting them here.
80
80
  // Users can still opt in via `nexus onboard` interactive.
81
81
  if (store.profiles[CLAUDE_CLI_PROFILE_ID]) {
82
- runtime.log('Detected Claude Code credentials, but Nexus may require an Anthropic API key (or another provider) for API access. Run `nexus onboard` to choose billing/auth.');
82
+ runtime.log("Detected Claude Code credentials, but Nexus may require an Anthropic API key (or another provider) for API access. Run `nexus onboard` to choose billing/auth.");
83
83
  }
84
- runtime.log("No Claude/Codex credentials detected yet. You can set this later via `nexus onboard` or `nexus configure`.");
84
+ runtime.log("No Claude/Codex credentials detected yet. If you use the CLIs, run `nexus credential import claude-cli` or `nexus credential import codex-cli`.");
85
85
  return cfg;
86
86
  }
87
87
  export async function runQuickstartOnboarding(opts, runtime = defaultRuntime) {
@@ -113,14 +113,19 @@ export async function runQuickstartOnboarding(opts, runtime = defaultRuntime) {
113
113
  },
114
114
  };
115
115
  nextConfig = applyQuickstartAuth(nextConfig, runtime);
116
- nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode: "local" });
116
+ nextConfig = applyWizardMetadata(nextConfig, {
117
+ command: "onboard",
118
+ mode: "local",
119
+ });
117
120
  await writeConfigFile(nextConfig);
118
121
  runtime.log(`Updated ${CONFIG_PATH_NEXUS}`);
119
122
  await ensureWorkspaceAndSessions(workspaceDir, runtime, {
120
123
  skipBootstrap: Boolean(nextConfig.agent?.skipBootstrap),
121
124
  });
122
125
  // Scripted bootstrap (test harness / automation): fill identity files + delete BOOTSTRAP.md.
123
- if (opts.bootstrapPreset || opts.bootstrapAgentName || opts.bootstrapUserName) {
126
+ if (opts.bootstrapPreset ||
127
+ opts.bootstrapAgentName ||
128
+ opts.bootstrapUserName) {
124
129
  await applyBootstrapPreset({
125
130
  workspaceDir,
126
131
  runtime,
@@ -142,7 +147,9 @@ export async function runQuickstartOnboarding(opts, runtime = defaultRuntime) {
142
147
  const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({ port, dev: devMode });
143
148
  const environment = {
144
149
  PATH: process.env.PATH,
145
- NEXUS_LAUNCHD_LABEL: process.platform === "darwin" ? GATEWAY_LAUNCH_AGENT_LABEL : undefined,
150
+ NEXUS_LAUNCHD_LABEL: process.platform === "darwin"
151
+ ? GATEWAY_LAUNCH_AGENT_LABEL
152
+ : undefined,
146
153
  };
147
154
  await service.install({
148
155
  env: process.env,