@agentprojectcontext/apx 1.33.1 → 1.35.0

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 (208) hide show
  1. package/package.json +1 -1
  2. package/skills/apx/SKILL.md +49 -61
  3. package/src/core/agent/a2a/reply.js +48 -0
  4. package/src/core/agent/build-agent-system.js +136 -59
  5. package/src/core/agent/channels/voice-context.js +98 -0
  6. package/src/core/agent/memory.js +2 -1
  7. package/src/core/agent/prompt-builder.js +178 -124
  8. package/src/core/agent/prompts/channels/code.md +12 -10
  9. package/src/core/agent/prompts/channels/desktop.md +5 -32
  10. package/src/core/agent/prompts/channels/telegram.md +4 -15
  11. package/src/core/agent/prompts/channels/web_code.md +11 -11
  12. package/src/core/agent/prompts/core/agent-base.md +24 -0
  13. package/src/core/agent/prompts/core/project-agent.md +11 -0
  14. package/src/core/agent/prompts/core/super-agent.md +21 -0
  15. package/src/core/agent/prompts/discipline/action.md +10 -0
  16. package/src/core/agent/prompts/discipline/single-segment.md +6 -0
  17. package/src/core/agent/prompts/discipline/two-segment.md +11 -0
  18. package/src/core/agent/prompts/modes/code-build.md +1 -0
  19. package/src/core/agent/prompts/modes/code-plan.md +1 -0
  20. package/src/core/agent/prompts/modes/index.js +28 -0
  21. package/src/core/agent/self-memory.js +43 -1
  22. package/src/core/agent/skills/index-store.js +307 -0
  23. package/src/core/agent/skills/index.js +15 -1
  24. package/src/core/agent/skills/inspector.js +317 -0
  25. package/src/core/agent/skills/loader.js +22 -18
  26. package/src/core/agent/stream/turn-accumulator.js +73 -0
  27. package/src/core/agent/suggestions.js +37 -0
  28. package/src/core/agent/super-agent.js +7 -1
  29. package/src/core/agent/tools/handlers/_git.js +50 -0
  30. package/src/core/agent/tools/handlers/add-project.js +5 -2
  31. package/src/core/agent/tools/handlers/call-runtime.js +3 -2
  32. package/src/core/agent/tools/handlers/git-diff.js +44 -0
  33. package/src/core/agent/tools/handlers/git-log.js +38 -0
  34. package/src/core/agent/tools/handlers/git-show.js +34 -0
  35. package/src/core/agent/tools/handlers/git-status.js +61 -0
  36. package/src/core/agent/tools/handlers/transcribe-audio.js +1 -1
  37. package/src/core/agent/tools/helpers.js +2 -2
  38. package/src/core/agent/tools/names.js +169 -0
  39. package/src/core/agent/tools/registry-bridge.js +6 -14
  40. package/src/core/agent/tools/registry.js +103 -69
  41. package/src/core/apc/context-copy.js +27 -0
  42. package/src/core/apc/notes.js +19 -0
  43. package/src/core/apc/parser.js +12 -5
  44. package/src/core/apc/paths.js +87 -0
  45. package/src/core/apc/scaffold.js +82 -76
  46. package/src/core/apc/skill-sync.js +10 -0
  47. package/src/{host/daemon/plugins → core/channels}/telegram/dispatch.js +38 -16
  48. package/src/core/config/index.js +24 -2
  49. package/src/core/config/redact.js +95 -0
  50. package/src/core/constants/channels.js +2 -0
  51. package/src/core/constants/code-modes.js +10 -0
  52. package/src/core/constants/index.js +1 -0
  53. package/src/core/deck/manifest.js +186 -0
  54. package/src/core/engines/catalog.js +83 -0
  55. package/src/core/{tools → http-tools}/browser.js +0 -1
  56. package/src/core/{tools → http-tools}/fetch.js +0 -1
  57. package/src/core/{tools → http-tools}/glob.js +0 -1
  58. package/src/core/{tools → http-tools}/grep.js +0 -1
  59. package/src/core/{tools → http-tools}/registry.js +0 -1
  60. package/src/core/{tools → http-tools}/search.js +0 -1
  61. package/src/core/i18n/en.js +9 -0
  62. package/src/core/i18n/es.js +12 -0
  63. package/src/core/i18n/index.js +54 -0
  64. package/src/core/i18n/pt.js +9 -0
  65. package/src/core/identity/telegram.js +2 -1
  66. package/src/core/mcp/runner.js +272 -14
  67. package/src/core/mcp/sources.js +3 -2
  68. package/src/core/routines/index.js +16 -0
  69. package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
  70. package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
  71. package/src/core/runtime-skills/apx/SKILL.md +83 -0
  72. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +125 -0
  73. package/src/core/runtime-skills/apx-agent/SKILL.md +97 -0
  74. package/src/core/runtime-skills/apx-mcp/SKILL.md +111 -0
  75. package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +169 -0
  76. package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +20 -29
  77. package/src/core/runtime-skills/apx-routine/SKILL.md +127 -0
  78. package/src/core/runtime-skills/apx-runtime/SKILL.md +99 -0
  79. package/src/core/runtime-skills/apx-sessions/SKILL.md +232 -0
  80. package/src/core/runtime-skills/apx-skill-builder/SKILL.md +129 -0
  81. package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +18 -21
  82. package/src/core/runtime-skills/apx-telegram/SKILL.md +120 -0
  83. package/src/core/runtime-skills/apx-voice/SKILL.md +117 -0
  84. package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
  85. package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
  86. package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
  87. package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
  88. package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
  89. package/src/core/stores/code-sessions.js +50 -2
  90. package/src/core/stores/routine-memory.js +1 -1
  91. package/src/core/stores/sessions-search.js +121 -0
  92. package/src/core/stores/sessions.js +38 -0
  93. package/src/core/vars/index.js +14 -0
  94. package/src/core/vars/interpolate.js +86 -0
  95. package/src/core/vars/sources.js +151 -0
  96. package/src/core/voice/audio-decode.js +38 -0
  97. package/src/core/voice/transcription.js +225 -0
  98. package/src/host/daemon/api/admin-config.js +5 -82
  99. package/src/host/daemon/api/agents.js +5 -5
  100. package/src/host/daemon/api/code.js +17 -169
  101. package/src/host/daemon/api/config.js +3 -4
  102. package/src/host/daemon/api/conversations.js +8 -29
  103. package/src/host/daemon/api/deck.js +37 -404
  104. package/src/host/daemon/api/engines.js +1 -80
  105. package/src/host/daemon/api/exec.js +1 -1
  106. package/src/host/daemon/api/mcps.js +32 -0
  107. package/src/host/daemon/api/routines.js +1 -1
  108. package/src/host/daemon/api/runtimes.js +4 -3
  109. package/src/host/daemon/api/sessions-search.js +24 -140
  110. package/src/host/daemon/api/sessions.js +12 -30
  111. package/src/host/daemon/api/shared.js +2 -1
  112. package/src/host/daemon/api/skills.js +140 -6
  113. package/src/host/daemon/api/super-agent.js +56 -1
  114. package/src/host/daemon/api/telegram.js +1 -11
  115. package/src/host/daemon/api/tools.js +6 -6
  116. package/src/host/daemon/api/transcribe.js +2 -2
  117. package/src/host/daemon/api/vars.js +137 -0
  118. package/src/host/daemon/api/voice.js +13 -290
  119. package/src/host/daemon/api.js +2 -0
  120. package/src/host/daemon/db.js +6 -6
  121. package/src/host/daemon/deck-exec.js +148 -0
  122. package/src/host/daemon/index.js +20 -3
  123. package/src/host/daemon/plugins/telegram/index.js +9 -9
  124. package/src/host/daemon/routines-scheduler.js +64 -0
  125. package/src/host/daemon/smoke.js +3 -2
  126. package/src/host/daemon/whisper-server.js +225 -0
  127. package/src/interfaces/cli/branding.js +53 -0
  128. package/src/interfaces/cli/commands/agent.js +3 -2
  129. package/src/interfaces/cli/commands/command.js +2 -3
  130. package/src/interfaces/cli/commands/messages.js +6 -2
  131. package/src/interfaces/cli/commands/pair.js +5 -4
  132. package/src/interfaces/cli/commands/search.js +1 -1
  133. package/src/interfaces/cli/commands/sessions.js +3 -2
  134. package/src/interfaces/cli/commands/skills.js +290 -55
  135. package/src/interfaces/cli/index.js +84 -2
  136. package/src/interfaces/web/dist/assets/index-C0fm31dY.js +618 -0
  137. package/src/interfaces/web/dist/assets/index-C0fm31dY.js.map +1 -0
  138. package/src/interfaces/web/dist/assets/index-UcAqlBO6.css +1 -0
  139. package/src/interfaces/web/dist/index.html +2 -2
  140. package/src/interfaces/web/package-lock.json +182 -182
  141. package/src/interfaces/web/src/components/ModelCombobox.tsx +2 -1
  142. package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
  143. package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
  144. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +37 -4
  145. package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
  146. package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
  147. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
  148. package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
  149. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
  150. package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
  151. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
  152. package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
  153. package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
  154. package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
  155. package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
  156. package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
  157. package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
  158. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +73 -4
  159. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
  160. package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
  161. package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
  162. package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
  163. package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
  164. package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
  165. package/src/interfaces/web/src/constants/index.ts +1 -1
  166. package/src/interfaces/web/src/hooks/useChat.ts +19 -0
  167. package/src/interfaces/web/src/i18n/en.ts +175 -7
  168. package/src/interfaces/web/src/i18n/es.ts +180 -15
  169. package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
  170. package/src/interfaces/web/src/lib/api/skills.ts +70 -0
  171. package/src/interfaces/web/src/lib/api/vars.ts +38 -0
  172. package/src/interfaces/web/src/lib/api.ts +1 -0
  173. package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
  174. package/src/interfaces/web/src/screens/SettingsScreen.tsx +6 -2
  175. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
  176. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
  177. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
  178. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
  179. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
  180. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
  181. package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
  182. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
  183. package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
  184. package/src/interfaces/web/src/types/daemon.ts +15 -0
  185. package/skills/apx-agency-agents/SKILL.md +0 -141
  186. package/skills/apx-agent/SKILL.md +0 -100
  187. package/skills/apx-mcp-builder/SKILL.md +0 -183
  188. package/skills/apx-routine/SKILL.md +0 -140
  189. package/skills/apx-runtime/SKILL.md +0 -117
  190. package/skills/apx-sessions/SKILL.md +0 -281
  191. package/skills/apx-skill-builder/SKILL.md +0 -153
  192. package/skills/apx-telegram/SKILL.md +0 -131
  193. package/skills/apx-voice/SKILL.md +0 -137
  194. package/src/core/agent/prompts/action-discipline.md +0 -24
  195. package/src/core/agent/prompts/super-agent-base.md +0 -42
  196. package/src/host/daemon/transcription.js +0 -538
  197. package/src/host/daemon/whisper-transcribe.py +0 -73
  198. package/src/interfaces/web/dist/assets/index-Aaiw8BZN.css +0 -1
  199. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +0 -602
  200. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js.map +0 -1
  201. /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
  202. /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
  203. /package/src/{host/daemon/plugins → core/channels}/telegram/helpers.js +0 -0
  204. /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
  205. /package/src/core/{tools → http-tools}/index.js +0 -0
  206. /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
  207. /package/src/{host/daemon → core/stores}/conversations.js +0 -0
  208. /package/src/{host/daemon → core/util}/thinking.js +0 -0
@@ -8,6 +8,7 @@
8
8
  import os from "node:os";
9
9
  import qrcode from "qrcode-terminal";
10
10
  import { http } from "../http.js";
11
+ import { CHANNELS } from "#core/constants/channels.js";
11
12
 
12
13
  const c = {
13
14
  reset: "\x1b[0m",
@@ -88,10 +89,10 @@ async function runPairing(args, channel) {
88
89
  // 2. QR payload differs per channel — no mixing.
89
90
  // deck: JSON {v,url,pid,fp} that the Deck app parses.
90
91
  // web : the scan-to-login URL the browser understands.
91
- const qrData = channel === "web"
92
+ const qrData = channel === CHANNELS.WEB
92
93
  ? webLink
93
94
  : JSON.stringify({ v: 1, url: baseUrl, pid: init.pairing_id, fp: init.fingerprint });
94
- const title = channel === "web" ? "APX pairing (web)" : "APX pairing (deck)";
95
+ const title = channel === CHANNELS.WEB ? "APX pairing (web)" : "APX pairing (deck)";
95
96
 
96
97
  console.log("");
97
98
  console.log(` ${fmt.bold(title)} ${fmt.gray("·")} ${fmt.dim(`expires in ${ttlSec}s`)}`);
@@ -100,7 +101,7 @@ async function runPairing(args, channel) {
100
101
  console.log(qr);
101
102
  });
102
103
 
103
- if (channel === "web") {
104
+ if (channel === CHANNELS.WEB) {
104
105
  console.log(` ${fmt.gray("scan with the phone camera — opens the web already linked")}`);
105
106
  console.log(` ${fmt.gray("link:")} ${fmt.cyan(webLink)}`);
106
107
  console.log(` ${fmt.gray("code:")} ${fmt.bold(init.pairing_id)} ${fmt.dim("(or paste it in the web pairing screen)")}`);
@@ -111,7 +112,7 @@ async function runPairing(args, channel) {
111
112
  console.log("");
112
113
 
113
114
  // 3. Poll /pair/status until confirmed or expired.
114
- const reRun = channel === "web" ? "apx pair web" : "apx pair";
115
+ const reRun = channel === CHANNELS.WEB ? "apx pair web" : "apx pair";
115
116
  const deadline = Date.now() + (init.ttl_ms || 90_000) + 5_000;
116
117
  while (Date.now() < deadline) {
117
118
  await sleep(1500);
@@ -3,7 +3,7 @@
3
3
  // Uses the daemon's tools/search.js module directly (no HTTP roundtrip,
4
4
  // no need to have `apx daemon start` running).
5
5
 
6
- import { webSearch } from "#core/tools/search.js";
6
+ import { webSearch } from "#core/http-tools/search.js";
7
7
 
8
8
  const DIM = "\x1b[2m";
9
9
  const BOLD = "\x1b[1m";
@@ -5,6 +5,7 @@
5
5
  import fs from "node:fs";
6
6
  import os from "node:os";
7
7
  import path from "node:path";
8
+ import { apcProjectFile } from "#core/apc/paths.js";
8
9
 
9
10
  // ── shared helpers ───────────────────────────────────────────────────────────
10
11
 
@@ -124,7 +125,7 @@ function readApxProjects(opts) {
124
125
  const proj = { path: path.resolve(e.path), name: null, apxId: null };
125
126
  try {
126
127
  const pj = JSON.parse(
127
- fs.readFileSync(path.join(proj.path, ".apc", "project.json"), "utf8")
128
+ fs.readFileSync(apcProjectFile(proj.path), "utf8")
128
129
  );
129
130
  if (pj.name) proj.name = pj.name;
130
131
  if (pj.apx_id) proj.apxId = pj.apx_id;
@@ -608,7 +609,7 @@ const apxEngine = {
608
609
  let apxId = null;
609
610
  try {
610
611
  const pj = JSON.parse(
611
- fs.readFileSync(path.join(dir, ".apc", "project.json"), "utf8")
612
+ fs.readFileSync(apcProjectFile(dir), "utf8")
612
613
  );
613
614
  apxId = pj.apx_id || null;
614
615
  } catch {}
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import os from "node:os";
5
5
  import readline from "node:readline";
6
6
  import { findApfRoot } from "#core/apc/parser.js";
7
+ import { apcSkillsDir } from "#core/apc/paths.js";
7
8
  import { http } from "../http.js";
8
9
  import {
9
10
  IDE_TARGETS,
@@ -11,7 +12,23 @@ import {
11
12
  installGlobalSkills,
12
13
  listBundledSkillSlugs,
13
14
  listBundledSkills,
15
+ listEngineSkills,
16
+ listLegacyPruneSlugs,
14
17
  } from "#core/apc/scaffold.js";
18
+ import {
19
+ ensureIndex,
20
+ planIndex,
21
+ readIndex,
22
+ clearIndex,
23
+ indexPath,
24
+ } from "#core/agent/skills/index-store.js";
25
+ import { isInspectorEnabled } from "#core/agent/skills/inspector.js";
26
+ import {
27
+ inspectPromptForSkills,
28
+ summarizeTrace,
29
+ INSPECTOR_DEFAULTS,
30
+ } from "#core/agent/skills/inspector.js";
31
+ import { readConfig, writeConfig } from "#core/config/index.js";
15
32
 
16
33
  // ---------------------------------------------------------------------------
17
34
  // Prompt helper
@@ -27,6 +44,31 @@ function ask(question) {
27
44
  });
28
45
  }
29
46
 
47
+ // When the Skill Inspector is on, the catalog it scores against must stay in
48
+ // sync with what's installed. Called after add/sync so a freshly available
49
+ // skill is searchable immediately, without a separate `apx skills index`.
50
+ // No-op (silent) when the inspector is disabled — nothing reads the index then.
51
+ async function reindexInspectorIfEnabled() {
52
+ let config;
53
+ try {
54
+ config = readConfig();
55
+ } catch {
56
+ return;
57
+ }
58
+ if (!isInspectorEnabled(config)) return;
59
+ try {
60
+ const plan = planIndex({});
61
+ const work = plan.missing.length + plan.stale.length + plan.gone.length;
62
+ if (work === 0) return;
63
+ process.stdout.write(`\n Skill Inspector on — reindexing ${work} changed skill(s)… `);
64
+ const out = await ensureIndex({ embedOpts: { globalConfig: config } });
65
+ const c = out.changed;
66
+ console.log(`done (${out.embedder}: +${c.added.length} ~${c.refreshed.length} -${c.removed.length}).`);
67
+ } catch (e) {
68
+ console.log(`\n (skill index refresh failed: ${e.message})`);
69
+ }
70
+ }
71
+
30
72
  // ---------------------------------------------------------------------------
31
73
  // apx skills add [<target>...] [--global] [--project]
32
74
  // ---------------------------------------------------------------------------
@@ -62,6 +104,7 @@ export async function cmdSkillsAdd(args) {
62
104
  }
63
105
  console.log("\n Loaded by: Claude Code, Cursor, Codex (OpenAI), Antigravity, and skills.sh-compatible tools.");
64
106
  console.log(" Activates automatically when working in a project with AGENTS.md or .apc/");
107
+ await reindexInspectorIfEnabled();
65
108
  return;
66
109
  }
67
110
 
@@ -90,6 +133,7 @@ export async function cmdSkillsAdd(args) {
90
133
  console.log("");
91
134
  for (const t of notes) console.log(` note: ${t.note}`);
92
135
  }
136
+ await reindexInspectorIfEnabled();
93
137
  }
94
138
 
95
139
  // ---------------------------------------------------------------------------
@@ -100,37 +144,27 @@ export async function cmdSkillsAdd(args) {
100
144
  // ---------------------------------------------------------------------------
101
145
 
102
146
  export async function cmdSkillsSync(args) {
103
- const includeOptional = !!args?.flags?.["include-optional"] || !!args?.flags?.optional;
104
- const includeInternal = !!args?.flags?.["include-internal"] || !!args?.flags?.internal;
105
147
  const prune = args?.flags?.["no-prune"] ? false : true;
106
148
 
107
- const results = installGlobalSkills({ includeOptional, includeInternal, prune });
149
+ const results = installGlobalSkills({ prune });
108
150
  const home = os.homedir();
109
151
 
110
- // Group by skill so the output is dense and scannable.
111
152
  const bySkill = {};
112
- const scopeOf = {};
113
153
  for (const r of results) {
114
154
  if (!bySkill[r.skill]) bySkill[r.skill] = [];
115
- bySkill[r.skill].push({ dir: r.dir.replace(home, "~"), status: r.status });
116
- scopeOf[r.skill] = r.scope;
155
+ bySkill[r.skill].push({ dir: r.dir.replace(home, "~"), status: r.status, scope: r.scope });
117
156
  }
118
157
 
119
- const slugs = Object.keys(bySkill).sort();
120
- if (slugs.length === 0) {
121
- console.log("(no bundled skills found in skills/)");
158
+ const engineSet = listEngineSkills().map((s) => s.slug);
159
+ if (engineSet.length === 0) {
160
+ console.log("(no engine skills found in skills/engines/)");
122
161
  return;
123
162
  }
124
163
 
125
- const filters = [];
126
- if (includeOptional) filters.push("+optional");
127
- if (includeInternal) filters.push("+internal");
128
- console.log(
129
- `Syncing ${slugs.length} bundled skill(s) to global skill dirs` +
130
- (filters.length ? ` [${filters.join(" ")}]` : "") + ":\n"
131
- );
164
+ console.log(`Syncing engine skill set (${engineSet.join(", ")}) to global dirs:\n`);
132
165
 
133
- const sw = Math.max(...slugs.map((s) => s.length));
166
+ const slugs = Object.keys(bySkill).sort();
167
+ const sw = Math.max(...slugs.map((s) => s.length), 8);
134
168
  const totals = { unchanged: 0, updated: 0, created: 0, pruned: 0 };
135
169
  for (const slug of slugs) {
136
170
  const entries = bySkill[slug];
@@ -141,9 +175,8 @@ export async function cmdSkillsSync(args) {
141
175
  for (const k of ["created", "updated", "unchanged", "pruned"]) {
142
176
  if (counts[k]) parts.push(`${counts[k]} ${k}`);
143
177
  }
144
- const scope = scopeOf[slug];
145
- const tag = scope === "public" ? "" : ` [${scope}]`;
146
- console.log(` ${slug.padEnd(sw)}${tag.padEnd(11)} ${parts.join(", ")}`);
178
+ const tag = entries[0]?.scope === "legacy" ? " [legacy]" : "";
179
+ console.log(` ${slug.padEnd(sw)}${tag.padEnd(10)} ${parts.join(", ")}`);
147
180
  }
148
181
  console.log("");
149
182
  console.log(`Targets: .claude/skills, .cursor/skills, .codex/skills, .agents/skills`);
@@ -153,21 +186,6 @@ export async function cmdSkillsSync(args) {
153
186
  }
154
187
  console.log(`Totals: ${totalParts.join(", ") || "(no changes)"}`);
155
188
 
156
- // Hint about non-default tiers.
157
- const skipped = listBundledSkills().filter(
158
- (s) =>
159
- (s.scope === "internal" && !includeInternal) ||
160
- (s.scope === "optional" && !includeOptional)
161
- );
162
- if (skipped.length > 0) {
163
- console.log("");
164
- console.log("Skipped (not pushed by default):");
165
- for (const s of skipped) console.log(` ${s.slug.padEnd(sw)} scope=${s.scope}`);
166
- console.log("");
167
- console.log("Re-run with --include-optional / --include-internal to push them too,");
168
- console.log("or `apx skills add <slug> --global` for one-off install.");
169
- }
170
-
171
189
  if (args?.flags?.verbose) {
172
190
  console.log("");
173
191
  for (const slug of slugs) {
@@ -177,6 +195,7 @@ export async function cmdSkillsSync(args) {
177
195
  }
178
196
  }
179
197
  }
198
+ await reindexInspectorIfEnabled();
180
199
  }
181
200
 
182
201
  // ---------------------------------------------------------------------------
@@ -184,8 +203,8 @@ export async function cmdSkillsSync(args) {
184
203
  // ---------------------------------------------------------------------------
185
204
 
186
205
  export async function cmdSkillsList(args = {}) {
187
- // --all queries the daemon, which returns project + global + bundled +
188
- // runtime-skills (same catalog the super-agent sees and the web picker uses).
206
+ // --all queries the daemon, which returns project + global + bundled
207
+ // (the same catalog the super-agent sees and the web picker uses).
189
208
  // Without --all we only list `.apc/skills/` (what the user installed in
190
209
  // THIS project), matching the historical behaviour.
191
210
  if (args?.flags?.all) {
@@ -205,7 +224,7 @@ export async function cmdSkillsList(args = {}) {
205
224
  }
206
225
 
207
226
  const root = findApfRoot();
208
- const skillsDir = root ? path.join(root, ".apc", "skills") : null;
227
+ const skillsDir = root ? apcSkillsDir(root) : null;
209
228
  const files = skillsDir && fs.existsSync(skillsDir)
210
229
  ? fs.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))
211
230
  : [];
@@ -224,17 +243,15 @@ export async function cmdSkillsList(args = {}) {
224
243
  export async function cmdSkillsStatus() {
225
244
  const root = findApfRoot();
226
245
 
227
- // Global discovered list of every bundled skill with its scope.
228
- const bundled = listBundledSkills();
229
- const byScope = {
230
- public: bundled.filter((s) => s.scope === "public"),
231
- optional: bundled.filter((s) => s.scope === "optional"),
232
- internal: bundled.filter((s) => s.scope === "internal"),
233
- };
246
+ const engineSet = listEngineSkills(); // what we publish to engines
247
+ const bundled = listBundledSkills(); // what stays in-repo for the super-agent
248
+ const legacy = listLegacyPruneSlugs(); // slugs APX shipped historically — pruned on sync
249
+
234
250
  console.log(
235
- `Bundled skills: ${bundled.length} total (${byScope.public.length} public, ` +
236
- `${byScope.optional.length} optional, ${byScope.internal.length} internal)`
251
+ `Engine skill set (replicated to global dirs): ${engineSet.length} ` +
252
+ `(${engineSet.map((s) => s.slug).join(", ") || "—"})`
237
253
  );
254
+ console.log(`In-repo bundled skills (super-agent only): ${bundled.length}`);
238
255
  console.log("");
239
256
  console.log(`Global skill dirs:`);
240
257
  const GLOBAL_DIRS = [
@@ -243,17 +260,23 @@ export async function cmdSkillsStatus() {
243
260
  { label: "Codex", dir: path.join(os.homedir(), ".codex", "skills") },
244
261
  { label: "Antigravity / others", dir: path.join(os.homedir(), ".agents", "skills") },
245
262
  ];
246
- const sw = Math.max(...bundled.map((s) => s.slug.length));
263
+ const allSlugs = [...engineSet.map((s) => s.slug), ...legacy];
264
+ const sw = Math.max(...allSlugs.map((s) => s.length), 8);
247
265
  for (const { label, dir } of GLOBAL_DIRS) {
248
266
  console.log(`\n ${label} — ${dir.replace(os.homedir(), "~")}`);
249
- for (const { slug, scope } of bundled) {
267
+ for (const { slug } of engineSet) {
250
268
  const dest = path.join(dir, slug, "SKILL.md");
251
269
  const present = fs.existsSync(dest);
252
- const tag = scope === "public" ? "" : ` [${scope}]`;
253
- const state = present
254
- ? "✓ installed"
255
- : (scope === "public" ? "✗ MISSING (run `apx skills sync`)" : "—");
256
- console.log(` ${slug.padEnd(sw)}${tag.padEnd(11)} ${state}`);
270
+ const state = present ? "✓ installed" : " MISSING (run `apx skills sync`)";
271
+ console.log(` ${slug.padEnd(sw)} ${state}`);
272
+ }
273
+ const stale = legacy.filter((slug) =>
274
+ fs.existsSync(path.join(dir, slug, "SKILL.md"))
275
+ );
276
+ if (stale.length) {
277
+ for (const slug of stale) {
278
+ console.log(` ${slug.padEnd(sw)} [legacy] ⚠ stale (run \`apx skills sync\` to prune)`);
279
+ }
257
280
  }
258
281
  }
259
282
 
@@ -280,3 +303,215 @@ export async function cmdSkillsStatus() {
280
303
  console.log("\n Tip: run `apx skills add` for an interactive install.");
281
304
  console.log(" Claude Desktop has no project-file support (use apx-mcp instead).");
282
305
  }
306
+
307
+ // ---------------------------------------------------------------------------
308
+ // apx skills index [--reset] [--force]
309
+ //
310
+ // Build the persistent vector index that powers the skill Inspector. Runs the
311
+ // configured embedding provider (defaults to local: ollama → tf fallback) over
312
+ // every skill's condensed description and writes ~/.apx/skills/.index.json.
313
+ // ---------------------------------------------------------------------------
314
+
315
+ function renderBar(done, total, width = 24) {
316
+ if (!Number.isFinite(total) || total <= 0) return "";
317
+ const ratio = Math.max(0, Math.min(1, done / total));
318
+ const filled = Math.round(ratio * width);
319
+ return "[" + "█".repeat(filled) + " ".repeat(width - filled) + "]";
320
+ }
321
+
322
+ export async function cmdSkillsIndex(args = {}) {
323
+ const reset = !!args?.flags?.reset;
324
+ const force = !!args?.flags?.force;
325
+ if (reset) clearIndex();
326
+
327
+ const config = readConfig();
328
+ const root = findApfRoot();
329
+ const projectPath = root || undefined;
330
+
331
+ const plan = planIndex({ projectPath });
332
+ if (plan.total === 0) {
333
+ console.log("(no skills available — install some first with `apx skills sync` or drop a SKILL.md in ~/.apx/skills/<slug>/)");
334
+ return;
335
+ }
336
+
337
+ const headline = force
338
+ ? `Rebuilding index for ${plan.total} skills (force).`
339
+ : `Indexing ${plan.total} skills (${plan.existing.length} cached · ${plan.missing.length} new · ${plan.stale.length} stale · ${plan.gone.length} gone).`;
340
+ console.log(headline);
341
+
342
+ const t0 = Date.now();
343
+ let lastLine = "";
344
+ const out = await ensureIndex({
345
+ projectPath,
346
+ embedOpts: { globalConfig: config },
347
+ force,
348
+ onProgress: ({ done, total, slug, action }) => {
349
+ const bar = renderBar(done, total);
350
+ const tag = action.padEnd(9);
351
+ const line = `\r${bar} ${done}/${total} ${tag} ${slug}`.padEnd(lastLine.length, " ");
352
+ process.stdout.write(line);
353
+ lastLine = line;
354
+ },
355
+ });
356
+
357
+ const elapsedMs = Date.now() - t0;
358
+ process.stdout.write("\r" + " ".repeat(lastLine.length) + "\r");
359
+
360
+ const c = out.changed;
361
+ console.log(
362
+ `Done in ${(elapsedMs / 1000).toFixed(1)}s using ${out.embedder} (dim ${out.dim}).\n` +
363
+ ` added: ${c.added.length}\n` +
364
+ ` refreshed: ${c.refreshed.length}\n` +
365
+ ` removed: ${c.removed.length}\n` +
366
+ ` kept: ${c.kept.length}\n` +
367
+ ` index: ${indexPath()}`
368
+ );
369
+ if (c.added.length || c.refreshed.length) {
370
+ const sample = [...c.added, ...c.refreshed].slice(0, 6).join(", ");
371
+ if (sample) console.log(` changes: ${sample}${(c.added.length + c.refreshed.length) > 6 ? ", …" : ""}`);
372
+ }
373
+ if (out.embedder === "tf") {
374
+ console.log(" note: using offline TF fallback (no embedding provider reachable). Configure one in config.memory.embeddings for better recall.");
375
+ }
376
+ }
377
+
378
+ // ---------------------------------------------------------------------------
379
+ // apx skills inspect <prompt>
380
+ //
381
+ // Show what the Inspector would inject for a given user prompt. Doesn't touch
382
+ // the model — pure middleware debug. Useful to tune thresholds and to see why
383
+ // a skill did or didn't fire.
384
+ // ---------------------------------------------------------------------------
385
+
386
+ export async function cmdSkillsInspect(args) {
387
+ const promptParts = args?._ || [];
388
+ const prompt = promptParts.join(" ").trim();
389
+ if (!prompt) {
390
+ console.error("usage: apx skills inspect \"<prompt text>\"");
391
+ process.exitCode = 2;
392
+ return;
393
+ }
394
+
395
+ const config = readConfig();
396
+ const root = findApfRoot();
397
+ const projectPath = root || undefined;
398
+
399
+ // Force the inspector on for this command even if config has it off — the
400
+ // operator is explicitly asking "what WOULD the inspector do?".
401
+ const probedConfig = structuredClone(config);
402
+ probedConfig.skills = probedConfig.skills || {};
403
+ probedConfig.skills.inspector = {
404
+ ...INSPECTOR_DEFAULTS,
405
+ ...(probedConfig.skills.inspector || {}),
406
+ enabled: true,
407
+ };
408
+
409
+ const out = await inspectPromptForSkills({
410
+ prompt,
411
+ projectPath,
412
+ globalConfig: probedConfig,
413
+ });
414
+
415
+ console.log(`prompt: ${prompt}`);
416
+ console.log(`embedder: ${out.trace.embedder || "(none)"}`);
417
+ console.log(`decision: ${summarizeTrace(out.trace)}`);
418
+ if (out.trace.scored?.length) {
419
+ console.log("scores:");
420
+ for (const s of out.trace.scored) console.log(` ${s.sim.toFixed(3)} ${s.slug}`);
421
+ }
422
+ if (out.contextNote) {
423
+ console.log("");
424
+ console.log("--- contextNote that would be injected ---");
425
+ console.log(out.contextNote);
426
+ console.log("--- end ---");
427
+ }
428
+ }
429
+
430
+ // ---------------------------------------------------------------------------
431
+ // apx skills inspector [enable|disable|status|set <key> <value>]
432
+ //
433
+ // Manage config.skills.inspector. All keys live under that namespace; this
434
+ // command is a thin shortcut so you don't have to remember the path.
435
+ // ---------------------------------------------------------------------------
436
+
437
+ const KNOWN_INSPECTOR_KEYS = Object.keys(INSPECTOR_DEFAULTS);
438
+
439
+ function ensureInspectorBlock(cfg) {
440
+ cfg.skills = cfg.skills || {};
441
+ cfg.skills.inspector = { ...INSPECTOR_DEFAULTS, ...(cfg.skills.inspector || {}) };
442
+ return cfg;
443
+ }
444
+
445
+ function printInspectorStatus(cfg) {
446
+ const insp = cfg.skills?.inspector || {};
447
+ const merged = { ...INSPECTOR_DEFAULTS, ...insp };
448
+ console.log(`Skill Inspector: ${merged.enabled ? "ENABLED" : "disabled"}`);
449
+ for (const k of KNOWN_INSPECTOR_KEYS) {
450
+ if (k === "enabled") continue;
451
+ console.log(` ${k.padEnd(16)} ${merged[k]}`);
452
+ }
453
+ const idx = readIndex();
454
+ const count = Object.keys(idx.items || {}).length;
455
+ console.log("");
456
+ console.log(`Index: ${count} skills (${idx.embedder || "—"}, dim ${idx.dim || "—"})`);
457
+ console.log(`File: ${indexPath()}`);
458
+ }
459
+
460
+ export async function cmdSkillsInspector(args) {
461
+ const sub = (args?._ || [])[0];
462
+ const cfg = readConfig();
463
+
464
+ if (!sub || sub === "status") {
465
+ printInspectorStatus(cfg);
466
+ return;
467
+ }
468
+ if (sub === "enable" || sub === "on") {
469
+ ensureInspectorBlock(cfg).skills.inspector.enabled = true;
470
+ writeConfig(cfg);
471
+ console.log("Skill Inspector ENABLED. The catalog-wide hint block will be suppressed; per-turn RAG decides what skills go into context.");
472
+ console.log("Tip: run `apx skills index` once so the inspector has cached vectors to score against.");
473
+ return;
474
+ }
475
+ if (sub === "disable" || sub === "off") {
476
+ ensureInspectorBlock(cfg).skills.inspector.enabled = false;
477
+ writeConfig(cfg);
478
+ console.log("Skill Inspector disabled. Falling back to the legacy slug hint + passive RAG nudge.");
479
+ return;
480
+ }
481
+ if (sub === "set") {
482
+ const key = args._[1];
483
+ const value = args._[2];
484
+ if (!key || value === undefined) {
485
+ console.error("usage: apx skills inspector set <key> <value>");
486
+ console.error(`keys: ${KNOWN_INSPECTOR_KEYS.join(", ")}`);
487
+ process.exitCode = 2;
488
+ return;
489
+ }
490
+ if (!KNOWN_INSPECTOR_KEYS.includes(key)) {
491
+ console.error(`unknown key "${key}". Known: ${KNOWN_INSPECTOR_KEYS.join(", ")}`);
492
+ process.exitCode = 2;
493
+ return;
494
+ }
495
+ ensureInspectorBlock(cfg);
496
+ const def = INSPECTOR_DEFAULTS[key];
497
+ let coerced = value;
498
+ if (typeof def === "boolean") coerced = value === "true" || value === "1" || value === "on";
499
+ else if (typeof def === "number") {
500
+ const n = Number(value);
501
+ if (!Number.isFinite(n)) {
502
+ console.error(`value for "${key}" must be a number; got "${value}"`);
503
+ process.exitCode = 2;
504
+ return;
505
+ }
506
+ coerced = n;
507
+ }
508
+ cfg.skills.inspector[key] = coerced;
509
+ writeConfig(cfg);
510
+ console.log(`skills.inspector.${key} = ${coerced}`);
511
+ return;
512
+ }
513
+
514
+ console.error(`unknown inspector subcommand: ${sub}`);
515
+ console.error("usage: apx skills inspector [status|enable|disable|set <key> <value>]");
516
+ process.exitCode = 2;
517
+ }
@@ -100,7 +100,7 @@ import {
100
100
  import { cmdPluginsList, cmdPluginStatus } from "./commands/plugins.js";
101
101
  import { cmdDesktopStart, cmdDesktopStop, cmdDesktopStatus, cmdDesktopInstall, cmdDesktopUninstall } from "./commands/desktop.js";
102
102
  import { cmdVoiceSay, cmdVoiceListen, cmdVoiceProviders } from "./commands/voice.js";
103
- import { cmdSkillsAdd, cmdSkillsList, cmdSkillsStatus, cmdSkillsSync } from "./commands/skills.js";
103
+ import { cmdSkillsAdd, cmdSkillsList, cmdSkillsStatus, cmdSkillsSync, cmdSkillsIndex, cmdSkillsInspect, cmdSkillsInspector } from "./commands/skills.js";
104
104
  import { cmdIdentity } from "./commands/identity.js";
105
105
  import { cmdCommandList, cmdCommandShow } from "./commands/command.js";
106
106
  import { cmdUpdate } from "./commands/update.js";
@@ -110,6 +110,7 @@ import { cmdModel } from "./commands/model.js";
110
110
  import { cmdPair, cmdPairWeb, cmdPairList, cmdPairRevoke } from "./commands/pair.js";
111
111
  import { checkForUpdate } from "#core/update-check.js";
112
112
  import { mascot } from "#core/mascot.js";
113
+ import { apxHeader, apxBanner } from "./branding.js";
113
114
  import {
114
115
  cmdRoutineList,
115
116
  cmdRoutineGet,
@@ -1350,12 +1351,23 @@ const HELP_TOPICS = new Map(Object.entries({
1350
1351
  skills: topic({
1351
1352
  title: "apx skills",
1352
1353
  summary: "Install and inspect APX skill files for IDEs and agent tools.",
1353
- usage: ["apx skills [add] [targets] [--global]", "apx skills sync", "apx skills list", "apx skills status"],
1354
+ usage: [
1355
+ "apx skills [add] [targets] [--global]",
1356
+ "apx skills sync",
1357
+ "apx skills list",
1358
+ "apx skills status",
1359
+ "apx skills index [--reset] [--force]",
1360
+ "apx skills inspect \"<prompt>\"",
1361
+ "apx skills inspector [status|enable|disable|set <key> <value>]",
1362
+ ],
1354
1363
  commands: [
1355
1364
  ["add [targets]", "Install APX skills into selected targets."],
1356
1365
  ["sync | refresh", "Re-install every bundled skill to every global skill dir (idempotent)."],
1357
1366
  ["list | ls", "List skills installed in this project's .apc/skills/."],
1358
1367
  ["status", "Show which bundled skills are present in each global dir."],
1368
+ ["index", "Build/refresh the local RAG vector index used by the Skill Inspector."],
1369
+ ["inspect", "Show which skills the Inspector would surface for a given prompt (debug)."],
1370
+ ["inspector", "Toggle and tune the Skill Inspector (per-turn skill RAG middleware)."],
1359
1371
  ],
1360
1372
  examples: [
1361
1373
  "apx skills add claude-code cursor",
@@ -1403,6 +1415,40 @@ const HELP_TOPICS = new Map(Object.entries({
1403
1415
  usage: ["apx skills status"],
1404
1416
  examples: ["apx skills status"],
1405
1417
  }),
1418
+ "skills index": topic({
1419
+ title: "apx skills index",
1420
+ summary:
1421
+ "Build or refresh the local vector index that powers the Skill Inspector. Embeds each skill's condensed description with the configured embeddings provider (defaults to local: ollama → tf fallback). Idempotent: skills with unchanged file+description are kept. Use --force to re-embed everything; --reset to also delete the on-disk index first.",
1422
+ usage: ["apx skills index [--reset] [--force]"],
1423
+ options: [
1424
+ ["--force", "Re-embed every skill, ignoring cached vectors."],
1425
+ ["--reset", "Delete the index file before rebuilding."],
1426
+ ],
1427
+ examples: ["apx skills index", "apx skills index --force", "apx skills index --reset"],
1428
+ }),
1429
+ "skills inspect": topic({
1430
+ title: "apx skills inspect",
1431
+ summary:
1432
+ "Dry-run the Skill Inspector against a prompt. Shows top similarity scores, which skill (if any) would be loaded inline, which would be hinted, and the actual contextNote that would be injected into the next turn's system prompt. The model is NOT called.",
1433
+ usage: ["apx skills inspect \"<prompt text>\""],
1434
+ examples: [
1435
+ "apx skills inspect \"crear un video promocional\"",
1436
+ "apx skills inspect \"profile the slow endpoint\"",
1437
+ ],
1438
+ }),
1439
+ "skills inspector": topic({
1440
+ title: "apx skills inspector",
1441
+ summary:
1442
+ "Toggle and tune the Skill Inspector (per-turn skill RAG). When ON: the static slug-dump in the system prompt is suppressed and a local RAG picks 0–N skills per turn — loading the body of high-confidence matches, hinting mid-confidence ones, injecting nothing below threshold. When OFF: legacy behaviour (full slug list + passive suggestion). Opt-in test feature.",
1443
+ usage: ["apx skills inspector [status|enable|disable|set <key> <value>]"],
1444
+ examples: [
1445
+ "apx skills inspector enable",
1446
+ "apx skills inspector disable",
1447
+ "apx skills inspector status",
1448
+ "apx skills inspector set load_threshold 0.5",
1449
+ "apx skills inspector set max_loaded 2",
1450
+ ],
1451
+ }),
1406
1452
  plugins: topic({
1407
1453
  title: "apx plugins",
1408
1454
  summary: "Inspect loaded APX daemon plugins.",
@@ -2530,6 +2576,9 @@ async function dispatch(cmd, rest) {
2530
2576
  else if (sub === "list" || sub === "ls") await cmdSkillsList(a);
2531
2577
  else if (sub === "status") await cmdSkillsStatus();
2532
2578
  else if (sub === "sync" || sub === "refresh") await cmdSkillsSync(a);
2579
+ else if (sub === "index") await cmdSkillsIndex(a);
2580
+ else if (sub === "inspect") await cmdSkillsInspect(a);
2581
+ else if (sub === "inspector") await cmdSkillsInspector(a);
2533
2582
  else die(`unknown skills subcommand: ${sub}`);
2534
2583
  break;
2535
2584
  }
@@ -2583,8 +2632,41 @@ async function dispatch(cmd, rest) {
2583
2632
  }
2584
2633
 
2585
2634
  const [topCmd, ...topRest] = argv;
2635
+
2636
+ // ── CLI branding ────────────────────────────────────────────────────────────
2637
+ // Every command prints an "APX CLI · vX · <command>" mark to stderr (so stdout
2638
+ // pipes stay clean). Two exceptions:
2639
+ // - SELF_BRANDED: commands that already render their own logo/mascot/status
2640
+ // block — re-stamping them would double up.
2641
+ // - BANNERED: branding-heavy moments that get the big ASCII wordmark instead
2642
+ // of the compact line.
2643
+ // Suppress everything with APX_QUIET=1 / APX_NO_BANNER=1 (see branding.js).
2644
+ const SELF_BRANDED = new Set([
2645
+ "status", "setup", "install", "daemon", "update", "upgrade", "help",
2646
+ ]);
2647
+ const BANNERED = new Set(["init"]);
2648
+
2649
+ function brandFor(cmd, rest) {
2650
+ if (SELF_BRANDED.has(cmd)) return;
2651
+ // Subtitle = the command path only (cmd + leading subcommand tokens), never
2652
+ // free-form args. Stop at the first token that looks like an argument: a flag,
2653
+ // something with spaces (a quoted prompt), or anything long. So
2654
+ // `skills inspector status` shows fully, but `exec "long prompt…"` shows just
2655
+ // `exec`.
2656
+ const path = [cmd];
2657
+ for (const tok of rest) {
2658
+ if (!tok || tok.startsWith("-") || /\s/.test(tok) || tok.length > 24) break;
2659
+ path.push(tok);
2660
+ if (path.length >= 3) break;
2661
+ }
2662
+ const subtitle = path.join(" ");
2663
+ if (BANNERED.has(cmd)) apxBanner(VERSION, subtitle);
2664
+ else apxHeader(VERSION, subtitle);
2665
+ }
2666
+
2586
2667
  (async () => {
2587
2668
  try {
2669
+ brandFor(topCmd, topRest);
2588
2670
  await dispatch(topCmd, topRest);
2589
2671
  checkForUpdate(VERSION);
2590
2672
  } catch (err) {