@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
@@ -0,0 +1,25 @@
1
+ import { logCommand } from "../commands/log.js";
2
+ import { defaultRuntime } from "../runtime.js";
3
+ export function registerLogCli(program) {
4
+ program
5
+ .command("log")
6
+ .description("Show Nexus event or skill usage logs")
7
+ .option("--json", "Output as JSON")
8
+ .option("--errors", "Only include error events")
9
+ .option("--since <time>", "Only include events after time (e.g. 24h)")
10
+ .option("--limit <n>", "Limit entries", (value) => Number.parseInt(value, 10))
11
+ .option("--skill <name>", "Show usage log for a skill")
12
+ .option("--source <source>", "Filter by event source")
13
+ .option("--command <path>", "Filter by command path")
14
+ .action(async (opts) => {
15
+ await logCommand({
16
+ json: Boolean(opts.json),
17
+ errors: Boolean(opts.errors),
18
+ since: typeof opts.since === "string" ? opts.since : undefined,
19
+ limit: Number.isFinite(opts.limit) ? opts.limit : undefined,
20
+ skill: typeof opts.skill === "string" ? opts.skill : undefined,
21
+ source: typeof opts.source === "string" ? opts.source : undefined,
22
+ command: typeof opts.command === "string" ? opts.command : undefined,
23
+ }, defaultRuntime);
24
+ });
25
+ }
@@ -15,7 +15,7 @@ const PROVIDERS = [
15
15
  "whatsapp",
16
16
  ];
17
17
  function parseProvider(raw) {
18
- const value = String(raw ?? "")
18
+ const value = (typeof raw === "string" || typeof raw === "number" ? String(raw) : "")
19
19
  .trim()
20
20
  .toLowerCase();
21
21
  if (PROVIDERS.includes(value))
@@ -4,22 +4,24 @@ import { configViewCommand } from "../commands/config-view.js";
4
4
  import { identityCommand } from "../commands/identity.js";
5
5
  import { initCommand } from "../commands/init.js";
6
6
  import { questCommand } from "../commands/quest.js";
7
- import { suggestionsCommand } from "../commands/suggestions.js";
8
7
  import { statusCommand } from "../commands/status.js";
8
+ import { suggestionsCommand } from "../commands/suggestions.js";
9
9
  import { updateCommand } from "../commands/update.js";
10
10
  import { isNixMode, migrateLegacyConfig, readConfigFileSnapshot, writeConfigFile, } from "../config/config.js";
11
11
  import { danger } from "../globals.js";
12
+ import { recordCliCommandFinish, recordCliCommandStart, } from "../infra/event-log.js";
12
13
  import { defaultRuntime } from "../runtime.js";
13
14
  import { VERSION } from "../version.js";
14
- import { recordCliCommandFinish, recordCliCommandStart } from "../infra/event-log.js";
15
15
  import { registerCloudCommand } from "./cloud-cli.js";
16
16
  import { registerCredentialCli } from "./credential-cli.js";
17
17
  import { registerDnsCli } from "./dns-cli.js";
18
18
  import { registerGatewayCli } from "./gateway-cli.js";
19
+ import { registerLogCli } from "./log-cli.js";
19
20
  import { registerSkillsCommand } from "./skills-cli.js";
20
21
  import { registerSkillsHubCommand } from "./skills-hub-cli.js";
21
22
  import { registerToolConnectorCli } from "./tool-connector-cli.js";
22
23
  import { registerUsageCli } from "./usage-cli.js";
24
+ import { resolveIdentitySnapshot } from "../agents/identity-state.js";
23
25
  function buildCommandPath(cmd) {
24
26
  const parts = [];
25
27
  let current = cmd;
@@ -51,6 +53,19 @@ export function buildProgram() {
51
53
  });
52
54
  if (actionCommand.name() === "init")
53
55
  return;
56
+ const skipIdentityCheck = process.env.NODE_ENV === "test" || Boolean(process.env.VITEST);
57
+ if (!skipIdentityCheck && actionCommand.name() !== "status") {
58
+ const identity = resolveIdentitySnapshot();
59
+ if (!identity.ok) {
60
+ defaultRuntime.error("Multiple agents detected in state/agents.");
61
+ defaultRuntime.error(`Set NEXUS_AGENT_ID to one of: ${identity.agentOptions.join(", ")}`);
62
+ process.exit(2);
63
+ }
64
+ if (!identity.snapshot.hasIdentity) {
65
+ await statusCommand({ brief: true }, defaultRuntime);
66
+ process.exit(2);
67
+ }
68
+ }
54
69
  const snapshot = await readConfigFileSnapshot();
55
70
  if (snapshot.legacyIssues.length === 0)
56
71
  return;
@@ -79,41 +94,77 @@ export function buildProgram() {
79
94
  .option("--brief", "Compact output")
80
95
  .option("--capabilities", "Focus on capabilities")
81
96
  .option("--credentials", "Focus on credentials")
97
+ .option("--usage", "Focus on usage statistics")
98
+ .option("--quiet", "Minimal output, exit codes only")
82
99
  .action(async (opts) => {
83
100
  await statusCommand({
84
101
  json: Boolean(opts.json),
85
102
  brief: Boolean(opts.brief),
86
103
  capabilities: Boolean(opts.capabilities),
87
104
  credentials: Boolean(opts.credentials),
105
+ usage: Boolean(opts.usage),
106
+ quiet: Boolean(opts.quiet),
88
107
  }, defaultRuntime);
89
108
  });
90
109
  program
91
110
  .command("capabilities")
92
111
  .description("Show full capabilities map")
93
112
  .option("--json", "Output as JSON")
113
+ .option("--category <name>", "Filter to a category")
114
+ .option("--status <status>", "Filter by status")
115
+ .option("--compact", "Compact output")
94
116
  .action(async (opts) => {
95
- await capabilitiesCommand({ json: Boolean(opts.json) }, defaultRuntime);
117
+ await capabilitiesCommand({
118
+ json: Boolean(opts.json),
119
+ category: opts.category ? String(opts.category) : undefined,
120
+ status: opts.status ? String(opts.status) : undefined,
121
+ compact: Boolean(opts.compact),
122
+ }, defaultRuntime);
96
123
  });
97
124
  program
98
125
  .command("map")
99
126
  .description("Alias for capabilities")
100
127
  .option("--json", "Output as JSON")
128
+ .option("--category <name>", "Filter to a category")
129
+ .option("--status <status>", "Filter by status")
130
+ .option("--compact", "Compact output")
101
131
  .action(async (opts) => {
102
- await capabilitiesCommand({ json: Boolean(opts.json) }, defaultRuntime);
132
+ await capabilitiesCommand({
133
+ json: Boolean(opts.json),
134
+ category: opts.category ? String(opts.category) : undefined,
135
+ status: opts.status ? String(opts.status) : undefined,
136
+ compact: Boolean(opts.compact),
137
+ }, defaultRuntime);
103
138
  });
104
139
  program
105
140
  .command("quest")
106
141
  .description("Show onboarding quests")
107
142
  .option("--json", "Output as JSON")
143
+ .option("--list", "List all quests")
144
+ .option("--progress", "Show quest progress summary")
145
+ .option("--start <quest>", "Mark a quest as started")
146
+ .option("--secrets", "Include secret quests")
147
+ .option("--power-path", "Only show power-path quests")
148
+ .option("--quick-wins", "Only show quick wins")
108
149
  .action(async (opts) => {
109
- await questCommand({ json: Boolean(opts.json) }, defaultRuntime);
150
+ await questCommand({
151
+ json: Boolean(opts.json),
152
+ list: Boolean(opts.list),
153
+ progress: Boolean(opts.progress),
154
+ start: opts.start ? String(opts.start) : undefined,
155
+ secrets: Boolean(opts.secrets),
156
+ powerPath: Boolean(opts.powerPath),
157
+ quickWins: Boolean(opts.quickWins),
158
+ }, defaultRuntime);
110
159
  });
111
160
  program
112
161
  .command("identity [target]")
113
162
  .description("Show identity files")
114
163
  .option("--json", "Output as JSON")
115
164
  .action(async (target, opts) => {
116
- const normalized = target && (target === "user" || target === "agent") ? target : undefined;
165
+ const normalized = target && (target === "user" || target === "agent")
166
+ ? target
167
+ : undefined;
117
168
  await identityCommand({ target: normalized, json: Boolean(opts.json) }, defaultRuntime);
118
169
  });
119
170
  program
@@ -156,6 +207,7 @@ export function buildProgram() {
156
207
  registerCredentialCli(program);
157
208
  registerDnsCli(program);
158
209
  registerGatewayCli(program);
210
+ registerLogCli(program);
159
211
  registerToolConnectorCli(program);
160
212
  registerUsageCli(program);
161
213
  return program;
@@ -2,10 +2,10 @@ import process from "node:process";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { loadDotEnv } from "../infra/dotenv.js";
4
4
  import { normalizeEnv } from "../infra/env.js";
5
+ import { initCliEventLogSession, recordCliCommandFailure, recordCliSessionEnd, registerAgentEventLogListener, } from "../infra/event-log.js";
5
6
  import { isMainModule } from "../infra/is-main.js";
6
7
  import { ensureNexusCliOnPath } from "../infra/path-env.js";
7
8
  import { assertSupportedRuntime } from "../infra/runtime-guard.js";
8
- import { initCliEventLogSession, recordCliCommandFailure, recordCliSessionEnd, registerAgentEventLogListener, } from "../infra/event-log.js";
9
9
  import { enableConsoleCapture } from "../logging.js";
10
10
  export async function runCli(argv = process.argv) {
11
11
  loadDotEnv({ quiet: true });
@@ -1,36 +1,129 @@
1
1
  import path from "node:path";
2
2
  import chalk from "chalk";
3
3
  import { getSkillInfo, runSkill, verifySkill } from "../agents/skill-runner.js";
4
- import { getAggregateStats, getSkillStats } from "../agents/skill-usage.js";
5
- import { flattenManifest, readSkillManifest } from "../commands/skills-manifest.js";
6
- import { MANAGED_SKILLS_DIR, NEXUS_ROOT } from "../utils.js";
4
+ import { getAggregateStats, getSkillStats, hasSkillUsage, recordSkillUsage, } from "../agents/skill-usage.js";
5
+ import { getSkillMetadata, loadWorkspaceSkillEntries } from "../agents/skills.js";
6
+ import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
7
+ import { DEFAULT_AGENT_WORKSPACE_DIR } from "../agents/workspace.js";
8
+ import { loadConfig } from "../config/config.js";
9
+ import { MANAGED_SKILLS_DIR, NEXUS_ROOT, resolveUserPath } from "../utils.js";
10
+ const STATUS_ICON = {
11
+ active: "✅",
12
+ ready: "⭐",
13
+ needs_setup: "🔧",
14
+ needs_install: "📥",
15
+ unavailable: "⛔",
16
+ };
17
+ function resolveSkillType(entry) {
18
+ const metadata = getSkillMetadata(entry);
19
+ if (metadata?.type)
20
+ return metadata.type;
21
+ const requires = metadata?.requires;
22
+ if (requires?.env?.length || requires?.config?.length)
23
+ return "connector";
24
+ if (requires?.bins?.length || requires?.anyBins?.length)
25
+ return "tool";
26
+ return "guide";
27
+ }
28
+ function resolveSkillStatus(statusEntry) {
29
+ if (statusEntry.disabled || statusEntry.blockedByAllowlist) {
30
+ return "unavailable";
31
+ }
32
+ if (statusEntry.missing.os.length > 0)
33
+ return "unavailable";
34
+ if (statusEntry.missing.bins.length > 0)
35
+ return "needs_install";
36
+ if (statusEntry.missing.env.length > 0 || statusEntry.missing.config.length > 0) {
37
+ return "needs_setup";
38
+ }
39
+ if (hasSkillUsage(statusEntry.name))
40
+ return "active";
41
+ return "ready";
42
+ }
7
43
  export function registerSkillsCommand(program) {
8
44
  const skill = program.command("skill").description("Use and inspect skills");
9
45
  skill
10
46
  .command("list")
11
47
  .description("List all available skills")
12
48
  .option("--json", "Output as JSON")
49
+ .option("--type <type>", "Filter by type (guide|tool|connector)")
50
+ .option("--active", "Only show active skills")
51
+ .option("--ready", "Only show ready skills")
52
+ .option("--needs-setup", "Only show skills needing setup")
53
+ .option("--needs-install", "Only show skills needing install")
54
+ .option("--unavailable", "Only show unavailable skills")
55
+ .option("--all", "Include unavailable skills")
13
56
  .action(async (opts) => {
14
- const manifest = await readSkillManifest();
15
- if (!manifest) {
16
- console.error("No skills manifest found. Run 'nexus init' to initialize your workspace.");
17
- process.exit(1);
18
- }
57
+ const config = loadConfig();
58
+ const workspaceDir = resolveUserPath(config.agent?.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR);
59
+ const entries = loadWorkspaceSkillEntries(workspaceDir, {
60
+ config,
61
+ managedSkillsDir: MANAGED_SKILLS_DIR,
62
+ });
63
+ const status = buildWorkspaceSkillStatus(workspaceDir, {
64
+ config,
65
+ managedSkillsDir: MANAGED_SKILLS_DIR,
66
+ entries,
67
+ });
68
+ const statusMap = new Map(status.skills.map((item) => [item.name.toLowerCase(), item]));
69
+ const items = entries.map((entry) => {
70
+ const statusEntry = statusMap.get(entry.skill.name.toLowerCase());
71
+ const statusValue = statusEntry
72
+ ? resolveSkillStatus(statusEntry)
73
+ : "unavailable";
74
+ return {
75
+ name: entry.skill.name,
76
+ description: entry.skill.description,
77
+ type: resolveSkillType(entry),
78
+ status: statusValue,
79
+ source: entry.skill.source,
80
+ filePath: entry.skill.filePath,
81
+ };
82
+ });
83
+ const typeFilter = opts.type
84
+ ? String(opts.type).toLowerCase()
85
+ : undefined;
86
+ const statusFilters = new Set();
87
+ if (opts.active)
88
+ statusFilters.add("active");
89
+ if (opts.ready)
90
+ statusFilters.add("ready");
91
+ if (opts.needsSetup)
92
+ statusFilters.add("needs_setup");
93
+ if (opts.needsInstall)
94
+ statusFilters.add("needs_install");
95
+ if (opts.unavailable)
96
+ statusFilters.add("unavailable");
97
+ const hasStatusFilter = statusFilters.size > 0;
98
+ const filtered = items.filter((item) => {
99
+ if (typeFilter && item.type !== typeFilter)
100
+ return false;
101
+ if (hasStatusFilter && !statusFilters.has(item.status))
102
+ return false;
103
+ if (!hasStatusFilter && !opts.all && item.status === "unavailable")
104
+ return false;
105
+ return true;
106
+ });
19
107
  if (opts.json) {
20
- console.log(JSON.stringify(manifest, null, 2));
108
+ console.log(JSON.stringify({
109
+ workspace: workspaceDir,
110
+ total: items.length,
111
+ filtered: filtered.length,
112
+ skills: filtered,
113
+ }, null, 2));
21
114
  return;
22
115
  }
23
- const allSkills = flattenManifest(manifest);
24
- if (allSkills.length === 0) {
25
- console.log("No skills installed.");
116
+ if (filtered.length === 0) {
117
+ console.log("No skills matched.");
26
118
  return;
27
119
  }
28
- console.log(`\n📚 Nexus Skills (${allSkills.length} total)\n`);
29
- for (const skillEntry of allSkills) {
30
- const desc = skillEntry.description
31
- ? ` - ${skillEntry.description.slice(0, 60)}${skillEntry.description.length > 60 ? "..." : ""}`
120
+ console.log(`\n📚 Nexus Skills (${filtered.length} shown)\n`);
121
+ for (const item of filtered) {
122
+ const desc = item.description
123
+ ? ` - ${item.description.slice(0, 60)}${item.description.length > 60 ? "..." : ""}`
32
124
  : "";
33
- console.log(` ${skillEntry.name}${desc}`);
125
+ const icon = STATUS_ICON[item.status] ?? "";
126
+ console.log(` ${icon} ${item.name}${desc}`);
34
127
  }
35
128
  console.log(`\nSkills directory: ${MANAGED_SKILLS_DIR}`);
36
129
  console.log(`User skills: ${path.join(NEXUS_ROOT, "home", "skills")}\n`);
@@ -42,6 +135,7 @@ export function registerSkillsCommand(program) {
42
135
  .option("--prompt", "Include the full SKILL.md prompt")
43
136
  .action(async (name, opts) => {
44
137
  const info = getSkillInfo(name);
138
+ const stats = await getSkillStats(name);
45
139
  if (!info.exists) {
46
140
  console.error(`Skill not found: ${name}`);
47
141
  console.error(`\nRun 'nexus skill list' to see available skills.`);
@@ -49,7 +143,8 @@ export function registerSkillsCommand(program) {
49
143
  }
50
144
  if (opts.json) {
51
145
  const output = opts.prompt ? info : { ...info, prompt: undefined };
52
- console.log(JSON.stringify(output, null, 2));
146
+ const payload = stats ? { ...output, usage: stats } : output;
147
+ console.log(JSON.stringify(payload, null, 2));
53
148
  return;
54
149
  }
55
150
  console.log(`\n${chalk.bold.cyan("Skill:")} ${info.name}`);
@@ -63,12 +158,16 @@ export function registerSkillsCommand(program) {
63
158
  const statusColor = info.configured ? chalk.green : chalk.yellow;
64
159
  const statusIcon = info.configured ? "✓" : "○";
65
160
  console.log(`${chalk.bold("Status:")} ${statusColor(`${statusIcon} ${info.configured ? "Configured" : "Not configured"}`)}`);
66
- if (info.lastUsed) {
67
- console.log(`${chalk.bold("Last used:")} ${info.lastUsed}`);
161
+ if (stats?.lastUsed ?? info.lastUsed) {
162
+ console.log(`${chalk.bold("Last used:")} ${stats?.lastUsed ?? info.lastUsed}`);
68
163
  }
69
164
  if (info.lastError) {
70
165
  console.log(`${chalk.bold("Last error:")} ${chalk.red(info.lastError)}`);
71
166
  }
167
+ if (stats) {
168
+ console.log(`${chalk.bold("Runs:")} ${stats.runs}`);
169
+ console.log(`${chalk.bold("Errors:")} ${stats.errors}`);
170
+ }
72
171
  if (opts.prompt && info.prompt) {
73
172
  console.log(`\n${chalk.bold.cyan("--- SKILL.md ---")}\n`);
74
173
  console.log(info.prompt);
@@ -76,6 +175,23 @@ export function registerSkillsCommand(program) {
76
175
  });
77
176
  skill
78
177
  .command("use <name>")
178
+ .description("Show the SKILL.md guide for a skill")
179
+ .action(async (name) => {
180
+ const info = getSkillInfo(name);
181
+ if (!info.exists) {
182
+ console.error(`Skill not found: ${name}`);
183
+ console.error(`\nRun 'nexus skill list' to see available skills.`);
184
+ process.exit(1);
185
+ }
186
+ if (!info.prompt) {
187
+ console.error(`No SKILL.md found for ${name}.`);
188
+ process.exit(1);
189
+ }
190
+ console.log(info.prompt.trimEnd());
191
+ recordSkillUsage(name, { ts: Date.now(), event: "use", ok: true });
192
+ });
193
+ skill
194
+ .command("run <name>")
79
195
  .description("Run a skill command with arguments")
80
196
  .allowUnknownOption()
81
197
  .action(async (name, _opts, command) => {
@@ -126,7 +242,14 @@ export function registerSkillsCommand(program) {
126
242
  }
127
243
  console.log(`Total skill runs: ${stats.totalRuns}`);
128
244
  console.log(`Active skills: ${stats.activeSkills}`);
129
- console.log(`Most used: ${stats.mostUsed?.name ?? "none"}`);
245
+ if (stats.topUsed && stats.topUsed.length > 0) {
246
+ console.log(`Most used: ${stats.topUsed
247
+ .map((item) => `${item.name} (${item.runs})`)
248
+ .join(", ")}`);
249
+ }
250
+ else {
251
+ console.log("Most used: none");
252
+ }
130
253
  console.log(`Ready but unused: ${stats.readyButUnused?.join(", ") || "none"}`);
131
254
  });
132
255
  }
@@ -1,17 +1,17 @@
1
+ import crypto from "node:crypto";
1
2
  import fs from "node:fs/promises";
2
- import path from "node:path";
3
3
  import os from "node:os";
4
- import crypto from "node:crypto";
4
+ import path from "node:path";
5
5
  import { Readable } from "node:stream";
6
6
  import { pipeline } from "node:stream/promises";
7
7
  import { fileTypeFromFile } from "file-type";
8
- import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
9
8
  import { getSkillMetadata, hasBinary, loadWorkspaceSkillEntries, } from "../agents/skills.js";
9
+ import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
10
10
  import { DEFAULT_AGENT_WORKSPACE_DIR } from "../agents/workspace.js";
11
- import { generateSkillManifest, writeSkillManifest } from "../commands/skills-manifest.js";
11
+ import { generateSkillManifest, writeSkillManifest, } from "../commands/skills-manifest.js";
12
12
  import { loadConfig } from "../config/config.js";
13
- import { runCommandWithTimeout } from "../process/exec.js";
14
13
  import { listCredentialEntries, resolveCredentialValue, } from "../credentials/store.js";
14
+ import { runCommandWithTimeout } from "../process/exec.js";
15
15
  import { MANAGED_SKILLS_DIR, resolveUserPath } from "../utils.js";
16
16
  function getHubBaseUrl() {
17
17
  return (process.env.NEXUS_HUB_URL ||
@@ -41,7 +41,9 @@ function normalizeStringList(value) {
41
41
  return [];
42
42
  }
43
43
  function normalizeHubDependencies(value) {
44
- const raw = value && typeof value === "object" ? value : {};
44
+ const raw = value && typeof value === "object"
45
+ ? value
46
+ : {};
45
47
  return {
46
48
  dependencies: normalizeStringList(raw.dependencies),
47
49
  tools: normalizeStringList(raw.tools),
@@ -142,16 +144,16 @@ function normalizeMetadata(value) {
142
144
  return {};
143
145
  }
144
146
  function resolveCapabilities(entry) {
147
+ const metadata = entry.metadata;
148
+ const metadataNexus = metadata && typeof metadata.nexus === "object"
149
+ ? metadata.nexus
150
+ : undefined;
145
151
  const candidates = [
146
152
  entry.capabilities,
147
153
  entry.provides,
148
- entry.metadata?.nexus
149
- ? entry.metadata.nexus.provides
150
- : undefined,
151
- entry.metadata?.nexus
152
- ? entry.metadata.nexus.capabilities
153
- : undefined,
154
- entry.metadata?.capabilities,
154
+ metadataNexus?.provides,
155
+ metadataNexus?.capabilities,
156
+ metadata?.capabilities,
155
157
  ];
156
158
  for (const candidate of candidates) {
157
159
  const parsed = normalizeStringList(candidate);
@@ -456,7 +458,7 @@ async function computeArtifactInfo(filePath) {
456
458
  return { sha256: hash, bytes: stats.size };
457
459
  }
458
460
  async function resolveHubToken(input) {
459
- if (input && input.trim())
461
+ if (input?.trim())
460
462
  return input.trim();
461
463
  const envToken = process.env.NEXUS_HUB_TOKEN ||
462
464
  process.env.NEXUS_WEBSITE_TOKEN ||
@@ -483,7 +485,9 @@ function buildManifestPayload(input) {
483
485
  (typeof frontmatter.name === "string" ? frontmatter.name : "") ||
484
486
  "";
485
487
  const description = overrides.description ||
486
- (typeof frontmatter.description === "string" ? frontmatter.description : "") ||
488
+ (typeof frontmatter.description === "string"
489
+ ? frontmatter.description
490
+ : "") ||
487
491
  "";
488
492
  const type = overrides.type ||
489
493
  (typeof frontmatter.type === "string" ? frontmatter.type : "") ||
@@ -496,7 +500,9 @@ function buildManifestPayload(input) {
496
500
  (typeof frontmatter.license === "string" ? frontmatter.license : "") ||
497
501
  "";
498
502
  const repository = overrides.repository ||
499
- (typeof frontmatter.repository === "string" ? frontmatter.repository : "") ||
503
+ (typeof frontmatter.repository === "string"
504
+ ? frontmatter.repository
505
+ : "") ||
500
506
  (typeof frontmatter.repo === "string" ? frontmatter.repo : "") ||
501
507
  "";
502
508
  const homepage = overrides.homepage ||
@@ -530,11 +536,15 @@ function buildManifestPayload(input) {
530
536
  const capabilities = resolveCapabilities(capabilityCandidates);
531
537
  if (capabilities.length > 0) {
532
538
  manifestEntry.capabilities = capabilities;
533
- const nexus = manifestEntry.metadata;
534
- nexus.nexus = {
535
- ...(nexus.nexus ?? {}),
539
+ const metadata = (manifestEntry.metadata ?? {});
540
+ const existingNexus = metadata.nexus && typeof metadata.nexus === "object"
541
+ ? metadata.nexus
542
+ : {};
543
+ metadata.nexus = {
544
+ ...existingNexus,
536
545
  provides: capabilities,
537
546
  };
547
+ manifestEntry.metadata = metadata;
538
548
  }
539
549
  const readmeExcerpt = extractExcerpt(body || description);
540
550
  return {
@@ -595,7 +605,9 @@ async function installHubSkill(params) {
595
605
  return { installed: true, destDir };
596
606
  }
597
607
  export function registerSkillsHubCommand(program) {
598
- const skills = program.command("skills").description("Search and install skills from the Nexus Hub");
608
+ const skills = program
609
+ .command("skills")
610
+ .description("Search and install skills from the Nexus Hub");
599
611
  skills
600
612
  .command("search <query>")
601
613
  .description("Search local skills and the Nexus Hub")
@@ -625,7 +637,10 @@ export function registerSkillsHubCommand(program) {
625
637
  config: cfg,
626
638
  managedSkillsDir,
627
639
  });
628
- const metadataByName = new Map(metadataEntries.map((entry) => [entry.skill.name, getSkillMetadata(entry)]));
640
+ const metadataByName = new Map(metadataEntries.map((entry) => [
641
+ entry.skill.name,
642
+ getSkillMetadata(entry),
643
+ ]));
629
644
  const installedNames = new Set(metadataEntries.map((entry) => normalizeSkillKey(entry.skill.name)));
630
645
  const installedSlugs = await loadManagedSkillSlugs(managedSkillsDir);
631
646
  const installedIndex = {
@@ -666,7 +681,7 @@ export function registerSkillsHubCommand(program) {
666
681
  })
667
682
  .filter((skill) => skill.score > 0)
668
683
  .sort((a, b) => b.score - a.score)
669
- .map(({ score, ...rest }) => rest);
684
+ .map(({ score: _score, ...rest }) => rest);
670
685
  let hubMatches = [];
671
686
  if (!opts.localOnly) {
672
687
  try {
@@ -801,7 +816,10 @@ export function registerSkillsHubCommand(program) {
801
816
  console.log(` Requires → ${deps.join(" | ")}`);
802
817
  }
803
818
  if (skill.install.length > 0) {
804
- console.log(` Install → ${skill.install.map((i) => i.label).filter(Boolean).join(", ")}`);
819
+ console.log(` Install → ${skill.install
820
+ .map((i) => i.label)
821
+ .filter(Boolean)
822
+ .join(", ")}`);
805
823
  }
806
824
  }
807
825
  console.log("");
@@ -873,20 +891,32 @@ export function registerSkillsHubCommand(program) {
873
891
  const capOverrides = [
874
892
  ...(Array.isArray(opts.capability) ? opts.capability : []),
875
893
  ...normalizeStringList(opts.capabilities),
876
- ].map((cap) => cap.trim()).filter(Boolean);
894
+ ]
895
+ .map((cap) => cap.trim())
896
+ .filter(Boolean);
877
897
  const overrides = {
878
898
  path: skillDir,
879
899
  slug: typeof opts.slug === "string" ? opts.slug.trim() : undefined,
880
900
  name: typeof opts.name === "string" ? opts.name.trim() : undefined,
881
- description: typeof opts.description === "string" ? opts.description.trim() : undefined,
901
+ description: typeof opts.description === "string"
902
+ ? opts.description.trim()
903
+ : undefined,
882
904
  type: typeof opts.type === "string" ? opts.type.trim() : undefined,
883
905
  version: typeof opts.version === "string" ? opts.version.trim() : undefined,
884
906
  license: typeof opts.license === "string" ? opts.license.trim() : undefined,
885
- repository: typeof opts.repository === "string" ? opts.repository.trim() : undefined,
886
- homepage: typeof opts.homepage === "string" ? opts.homepage.trim() : undefined,
887
- visibility: typeof opts.visibility === "string" ? opts.visibility.trim() : "public",
907
+ repository: typeof opts.repository === "string"
908
+ ? opts.repository.trim()
909
+ : undefined,
910
+ homepage: typeof opts.homepage === "string"
911
+ ? opts.homepage.trim()
912
+ : undefined,
913
+ visibility: typeof opts.visibility === "string"
914
+ ? opts.visibility.trim()
915
+ : "public",
888
916
  capabilities: capOverrides.length > 0 ? capOverrides : undefined,
889
- sourcePath: typeof opts.source === "string" ? resolveUserPath(opts.source) : undefined,
917
+ sourcePath: typeof opts.source === "string"
918
+ ? resolveUserPath(opts.source)
919
+ : undefined,
890
920
  sourceCommit: typeof opts.sourceCommit === "string"
891
921
  ? opts.sourceCommit.trim()
892
922
  : undefined,