@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
@@ -1,5 +1,5 @@
1
1
  import { cancel, confirm, isCancel } from "@clack/prompts";
2
- import { addCredential, flagCredential, getCredential, getCredentialPaths, listCredentials, removeCredential, scanCredentialEnv, } from "../commands/credential.js";
2
+ import { addCredential, flagCredential, getCredential, getCredentialPaths, getCredentialValue, importCliCredential, listCredentials, removeCredential, scanCredentialEnv, verifyCredentials, } from "../commands/credential.js";
3
3
  function parseFields(raw, fieldsArgs) {
4
4
  const out = {};
5
5
  if (raw) {
@@ -25,7 +25,9 @@ function parseFields(raw, fieldsArgs) {
25
25
  return out;
26
26
  }
27
27
  export function registerCredentialCli(program) {
28
- const credential = program.command("credential").description("Manage credentials");
28
+ const credential = program
29
+ .command("credential")
30
+ .description("Manage credentials");
29
31
  credential
30
32
  .command("list")
31
33
  .description("List credentials from the index")
@@ -54,29 +56,72 @@ export function registerCredentialCli(program) {
54
56
  const paths = getCredentialPaths();
55
57
  console.log(`\nIndex: ${paths.indexPath}`);
56
58
  });
59
+ credential
60
+ .command("verify <service>")
61
+ .description("Verify credential status for a service")
62
+ .option("--json", "Output as JSON")
63
+ .action(async (service, opts) => {
64
+ const result = await verifyCredentials({ service });
65
+ if (!result) {
66
+ console.error("Credential service not found.");
67
+ process.exit(1);
68
+ }
69
+ if (opts.json) {
70
+ console.log(JSON.stringify(result, null, 2));
71
+ return;
72
+ }
73
+ const iconFor = (status) => status === "ok" ? "āœ…" : status === "skipped" ? "āš ļø" : "āŒ";
74
+ console.log(`${result.ok ? "āœ…" : "āŒ"} ${result.service}: ${result.accounts} account(s) checked`);
75
+ for (const entry of result.checked) {
76
+ const err = entry.error ? ` - ${entry.error}` : "";
77
+ console.log(` ${iconFor(entry.status)} ${entry.account} (${entry.authId})${err}`);
78
+ }
79
+ if (!result.ok) {
80
+ process.exit(1);
81
+ }
82
+ });
57
83
  credential
58
84
  .command("get")
59
- .description("Get a credential record")
85
+ .description("Get a credential value")
60
86
  .requiredOption("--service <id>", "Service id")
61
87
  .requiredOption("--account <id>", "Account id")
62
88
  .requiredOption("--auth <id>", "Auth id")
63
89
  .option("--json", "Output as JSON")
90
+ .option("--record", "Show credential record instead of value")
64
91
  .action(async (opts) => {
65
- const result = await getCredential({
92
+ const params = {
66
93
  service: opts.service,
67
94
  account: opts.account,
68
95
  authId: opts.auth,
69
- });
70
- if (!result) {
71
- console.error("Credential not found.");
96
+ };
97
+ if (opts.record) {
98
+ const result = await getCredential(params);
99
+ if (!result) {
100
+ console.error("Credential not found.");
101
+ process.exit(1);
102
+ }
103
+ if (opts.json) {
104
+ console.log(JSON.stringify(result, null, 2));
105
+ return;
106
+ }
107
+ console.log(`\n${result.filePath}`);
108
+ console.log(JSON.stringify(result.record, null, 2));
109
+ return;
110
+ }
111
+ const valueResult = await getCredentialValue(params);
112
+ if (!valueResult) {
113
+ console.error("Credential value not found.");
72
114
  process.exit(1);
73
115
  }
74
116
  if (opts.json) {
75
- console.log(JSON.stringify(result, null, 2));
117
+ console.log(JSON.stringify({
118
+ filePath: valueResult.filePath,
119
+ field: valueResult.field,
120
+ value: valueResult.value,
121
+ }, null, 2));
76
122
  return;
77
123
  }
78
- console.log(`\n${result.filePath}`);
79
- console.log(JSON.stringify(result.record, null, 2));
124
+ console.log(valueResult.value);
80
125
  });
81
126
  credential
82
127
  .command("add")
@@ -86,23 +131,55 @@ export function registerCredentialCli(program) {
86
131
  .requiredOption("--type <type>", "api_key | token | oauth | config")
87
132
  .option("--auth <id>", "Auth id (defaults to type)")
88
133
  .option("--owner <owner>", "shared | user | agent:<id>", "user")
89
- .option("--storage <provider>", "plaintext | keychain | 1password | external", "plaintext")
90
- .option("--value <value>", "Secret value for plaintext/keychain")
134
+ .option("--storage <provider>", "keychain | 1password | env | external")
135
+ .option("--value <value>", "Secret value for keychain")
91
136
  .option("--refresh-token <token>", "OAuth refresh token")
92
137
  .option("--expires-at <ts>", "Expiration (unix ms or ISO string)")
93
138
  .option("--vault <name>", "1Password vault")
94
139
  .option("--item <name>", "1Password item")
95
140
  .option("--fields <json>", "1Password field map as JSON")
96
141
  .option("--field <pair...>", "1Password field map: key=value")
97
- .option("--sync-command <cmd>", "External sync command")
142
+ .option("--env-var <name>", "Env var name for env storage")
143
+ .option("--command <cmd>", "External command to fetch secret")
144
+ .option("--sync-command <cmd>", "External sync command (deprecated)")
145
+ .option("--format <format>", "External command output format: raw|json")
146
+ .option("--json-path <path>", "JSON path for external output")
98
147
  .action(async (opts) => {
99
148
  const type = String(opts.type).trim();
100
149
  if (!["api_key", "token", "oauth", "config"].includes(type)) {
101
150
  console.error("Invalid --type");
102
151
  process.exit(1);
103
152
  }
104
- const storage = String(opts.storage).trim();
153
+ const storageRaw = opts.storage ? String(opts.storage).trim() : "";
154
+ const storage = storageRaw || (process.platform === "darwin" ? "keychain" : "");
155
+ if (!storage) {
156
+ console.error("Missing --storage. Use keychain (macOS), 1password, env, or external.");
157
+ process.exit(1);
158
+ }
159
+ if (storage === "keychain" && !opts.value) {
160
+ console.error("Missing --value for keychain storage.");
161
+ process.exit(1);
162
+ }
163
+ if (storage === "env" && !opts.envVar) {
164
+ console.error("Missing --env-var for env storage.");
165
+ process.exit(1);
166
+ }
167
+ if (storage === "external" && !opts.command && !opts.syncCommand) {
168
+ console.error("Missing --command for external storage.");
169
+ process.exit(1);
170
+ }
171
+ if (opts.format && !["raw", "json"].includes(String(opts.format))) {
172
+ console.error("Invalid --format. Use raw or json.");
173
+ process.exit(1);
174
+ }
175
+ if (!["keychain", "1password", "env", "external"].includes(storage)) {
176
+ console.error("Invalid --storage. Use keychain, 1password, env, external.");
177
+ process.exit(1);
178
+ }
105
179
  const fields = parseFields(opts.fields, opts.field);
180
+ const format = opts.format === "raw" || opts.format === "json"
181
+ ? opts.format
182
+ : undefined;
106
183
  const expiresAt = opts.expiresAt && /^\d+$/.test(String(opts.expiresAt).trim())
107
184
  ? Number.parseInt(String(opts.expiresAt).trim(), 10)
108
185
  : opts.expiresAt;
@@ -121,14 +198,59 @@ export function registerCredentialCli(program) {
121
198
  item: String(opts.item ?? ""),
122
199
  fields,
123
200
  }
124
- : storage === "external"
125
- ? { provider: "external", syncCommand: String(opts.syncCommand ?? "") }
126
- : { provider: "plaintext", value: opts.value },
201
+ : storage === "env"
202
+ ? { provider: "env", var: String(opts.envVar ?? "") }
203
+ : storage === "external"
204
+ ? {
205
+ provider: "external",
206
+ command: opts.command ? String(opts.command) : undefined,
207
+ syncCommand: opts.syncCommand
208
+ ? String(opts.syncCommand)
209
+ : undefined,
210
+ ...(format ? { format } : {}),
211
+ jsonPath: opts.jsonPath
212
+ ? String(opts.jsonPath)
213
+ : undefined,
214
+ }
215
+ : { provider: "env", var: String(opts.envVar ?? "") },
127
216
  refreshToken: opts.refreshToken,
128
217
  expiresAt,
129
218
  });
130
219
  console.log(`Added credential at ${record.filePath}`);
131
220
  });
221
+ credential
222
+ .command("import <source>")
223
+ .description("Import external CLI credentials")
224
+ .option("--account <id>", "Account id override")
225
+ .option("--owner <owner>", "shared | user | agent:<id>", "user")
226
+ .option("--force", "Overwrite existing record")
227
+ .option("--json", "Output as JSON")
228
+ .option("--no-keychain-prompt", "Skip keychain prompts")
229
+ .action(async (source, opts) => {
230
+ const normalized = String(source ?? "").trim();
231
+ if (!["claude-cli", "codex-cli"].includes(normalized)) {
232
+ console.error("Invalid source. Use claude-cli or codex-cli.");
233
+ process.exit(1);
234
+ }
235
+ try {
236
+ const result = await importCliCredential({
237
+ source: normalized,
238
+ account: opts.account,
239
+ owner: opts.owner,
240
+ force: Boolean(opts.force),
241
+ allowKeychainPrompt: opts.keychainPrompt !== false,
242
+ });
243
+ if (opts.json) {
244
+ console.log(JSON.stringify(result, null, 2));
245
+ return;
246
+ }
247
+ console.log(`Imported ${result.source} credentials into ${result.profileId} (${result.filePath})`);
248
+ }
249
+ catch (err) {
250
+ console.error(err instanceof Error ? err.message : String(err));
251
+ process.exit(1);
252
+ }
253
+ });
132
254
  credential
133
255
  .command("remove")
134
256
  .description("Remove a credential record")
@@ -1,5 +1,5 @@
1
1
  import fs from "node:fs";
2
- import { CONFIG_PATH_NEXUS, loadConfig, resolveGatewayPort } from "../config/config.js";
2
+ import { CONFIG_PATH_NEXUS, loadConfig, resolveGatewayPort, } from "../config/config.js";
3
3
  import { GATEWAY_LAUNCH_AGENT_LABEL, GATEWAY_SYSTEMD_SERVICE_NAME, GATEWAY_WINDOWS_TASK_NAME, } from "../daemon/constants.js";
4
4
  import { resolveGatewayService } from "../daemon/service.js";
5
5
  import { callGateway, randomIdempotencyKey } from "../gateway/call.js";
@@ -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
  }