@agentprojectcontext/apx 1.34.0 → 1.36.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 (75) hide show
  1. package/package.json +1 -1
  2. package/skills/apx/SKILL.md +1 -1
  3. package/src/core/agent/build-agent-system.js +134 -58
  4. package/src/core/agent/channels/voice-context.js +4 -4
  5. package/src/core/agent/prompt-builder.js +176 -123
  6. package/src/core/agent/prompts/channels/code.md +12 -10
  7. package/src/core/agent/prompts/channels/desktop.md +5 -32
  8. package/src/core/agent/prompts/channels/telegram.md +4 -15
  9. package/src/core/agent/prompts/channels/web_code.md +11 -11
  10. package/src/core/agent/prompts/core/agent-base.md +24 -0
  11. package/src/core/agent/prompts/core/project-agent.md +11 -0
  12. package/src/core/agent/prompts/core/super-agent.md +21 -0
  13. package/src/core/agent/prompts/discipline/action.md +10 -0
  14. package/src/core/agent/prompts/discipline/single-segment.md +6 -0
  15. package/src/core/agent/prompts/discipline/two-segment.md +11 -0
  16. package/src/core/agent/self-memory.js +43 -1
  17. package/src/core/agent/skills/index-store.js +307 -0
  18. package/src/core/agent/skills/index.js +15 -1
  19. package/src/core/agent/skills/inspector.js +317 -0
  20. package/src/core/agent/super-agent.js +7 -1
  21. package/src/core/agent/tools/handlers/_git.js +50 -0
  22. package/src/core/agent/tools/handlers/git-diff.js +44 -0
  23. package/src/core/agent/tools/handlers/git-log.js +38 -0
  24. package/src/core/agent/tools/handlers/git-show.js +34 -0
  25. package/src/core/agent/tools/handlers/git-status.js +61 -0
  26. package/src/core/agent/tools/names.js +31 -0
  27. package/src/core/agent/tools/registry.js +36 -5
  28. package/src/core/config/index.js +21 -0
  29. package/src/core/runtime-skills/apx/SKILL.md +27 -39
  30. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +40 -56
  31. package/src/core/runtime-skills/apx-agent/SKILL.md +27 -30
  32. package/src/core/runtime-skills/apx-mcp/SKILL.md +31 -36
  33. package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +37 -51
  34. package/src/core/runtime-skills/apx-project/SKILL.md +20 -29
  35. package/src/core/runtime-skills/apx-routine/SKILL.md +34 -47
  36. package/src/core/runtime-skills/apx-runtime/SKILL.md +32 -50
  37. package/src/core/runtime-skills/apx-sessions/SKILL.md +96 -145
  38. package/src/core/runtime-skills/apx-skill-builder/SKILL.md +53 -77
  39. package/src/core/runtime-skills/apx-task/SKILL.md +18 -21
  40. package/src/core/runtime-skills/apx-telegram/SKILL.md +43 -54
  41. package/src/core/runtime-skills/apx-voice/SKILL.md +36 -56
  42. package/src/core/stores/conversations.js +27 -2
  43. package/src/host/daemon/api/exec.js +2 -0
  44. package/src/host/daemon/api/skills.js +140 -6
  45. package/src/host/daemon/api/super-agent.js +56 -1
  46. package/src/host/daemon/index.js +17 -0
  47. package/src/interfaces/cli/branding.js +53 -0
  48. package/src/interfaces/cli/commands/skills.js +254 -0
  49. package/src/interfaces/cli/index.js +84 -2
  50. package/src/interfaces/web/dist/assets/index-Cm0KyPoZ.css +1 -0
  51. package/src/interfaces/web/dist/assets/index-DJKA763h.js +628 -0
  52. package/src/interfaces/web/dist/assets/index-DJKA763h.js.map +1 -0
  53. package/src/interfaces/web/dist/index.html +2 -2
  54. package/src/interfaces/web/src/App.tsx +0 -1
  55. package/src/interfaces/web/src/components/chat/ChatList.tsx +412 -0
  56. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +21 -1
  57. package/src/interfaces/web/src/components/settings/AppearancePanel.tsx +1 -1
  58. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +69 -1
  59. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
  60. package/src/interfaces/web/src/hooks/useChat.ts +54 -2
  61. package/src/interfaces/web/src/i18n/en.ts +12 -1
  62. package/src/interfaces/web/src/i18n/es.ts +12 -1
  63. package/src/interfaces/web/src/lib/api/agents.ts +1 -1
  64. package/src/interfaces/web/src/lib/api/skills.ts +70 -0
  65. package/src/interfaces/web/src/screens/ProjectScreen.tsx +3 -5
  66. package/src/interfaces/web/src/screens/SettingsScreen.tsx +12 -6
  67. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
  68. package/src/interfaces/web/src/screens/project/ChatTab.tsx +120 -87
  69. package/src/interfaces/web/src/types/daemon.ts +10 -0
  70. package/src/core/agent/prompts/action-discipline.md +0 -24
  71. package/src/core/agent/prompts/super-agent-base.md +0 -42
  72. package/src/interfaces/web/dist/assets/index-DdmSRtsz.css +0 -1
  73. package/src/interfaces/web/dist/assets/index-M4FspaCH.js +0 -613
  74. package/src/interfaces/web/dist/assets/index-M4FspaCH.js.map +0 -1
  75. package/src/interfaces/web/src/screens/project/ThreadsTab.tsx +0 -100
@@ -15,6 +15,20 @@ import {
15
15
  listEngineSkills,
16
16
  listLegacyPruneSlugs,
17
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";
18
32
 
19
33
  // ---------------------------------------------------------------------------
20
34
  // Prompt helper
@@ -30,6 +44,31 @@ function ask(question) {
30
44
  });
31
45
  }
32
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
+
33
72
  // ---------------------------------------------------------------------------
34
73
  // apx skills add [<target>...] [--global] [--project]
35
74
  // ---------------------------------------------------------------------------
@@ -65,6 +104,7 @@ export async function cmdSkillsAdd(args) {
65
104
  }
66
105
  console.log("\n Loaded by: Claude Code, Cursor, Codex (OpenAI), Antigravity, and skills.sh-compatible tools.");
67
106
  console.log(" Activates automatically when working in a project with AGENTS.md or .apc/");
107
+ await reindexInspectorIfEnabled();
68
108
  return;
69
109
  }
70
110
 
@@ -93,6 +133,7 @@ export async function cmdSkillsAdd(args) {
93
133
  console.log("");
94
134
  for (const t of notes) console.log(` note: ${t.note}`);
95
135
  }
136
+ await reindexInspectorIfEnabled();
96
137
  }
97
138
 
98
139
  // ---------------------------------------------------------------------------
@@ -154,6 +195,7 @@ export async function cmdSkillsSync(args) {
154
195
  }
155
196
  }
156
197
  }
198
+ await reindexInspectorIfEnabled();
157
199
  }
158
200
 
159
201
  // ---------------------------------------------------------------------------
@@ -261,3 +303,215 @@ export async function cmdSkillsStatus() {
261
303
  console.log("\n Tip: run `apx skills add` for an interactive install.");
262
304
  console.log(" Claude Desktop has no project-file support (use apx-mcp instead).");
263
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) {