nexakit 0.1.0 → 0.2.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 (113) hide show
  1. package/.claude-plugin/marketplace.json +18 -1
  2. package/README.md +40 -30
  3. package/assets/brand/README.md +0 -2
  4. package/assets/runtime/package-lock.json +2 -2
  5. package/bin/agent-task.js +12 -10
  6. package/bin/agent-toolkit-gemini.js +11 -9
  7. package/bin/agent-toolkit-inbox.js +12 -10
  8. package/bin/agent-toolkit-mcp.js +14 -8
  9. package/bin/agent-toolkit.js +12 -9
  10. package/bin/nexakit.js +12 -9
  11. package/bin/project-memory-mcp.js +12 -9
  12. package/bin/project-memory.js +5 -3
  13. package/{skills/depurar → catalogs/skills/vercel-composition-patterns}/LICENSE.txt +6 -6
  14. package/catalogs/skills/vercel-react-best-practices/LICENSE.txt +27 -0
  15. package/catalogs/skills.json +70 -35
  16. package/docs/skills/evidencias/2026-10-28-recomendador-preparacion.md +1 -1
  17. package/package.json +5 -3
  18. package/plugins/nexakit-modelo/.claude-plugin/plugin.json +1 -0
  19. package/plugins/nexakit-modelo/hooks/hooks.json +1 -0
  20. package/plugins/nexakit-modelo/hooks/register.js +48 -0
  21. package/plugins/nexakit-modelo/hooks/status.js +8 -0
  22. package/plugins/nexakit-modelo/tests/register.test.ts +21 -0
  23. package/plugins/nexakit-panel/.claude-plugin/plugin.json +1 -0
  24. package/plugins/nexakit-panel/README.md +20 -0
  25. package/plugins/nexakit-panel/hooks/hooks.json +1 -0
  26. package/plugins/nexakit-panel/hooks/lienzo.js +382 -0
  27. package/plugins/nexakit-panel/hooks/panel.js +279 -0
  28. package/plugins/nexakit-panel/hooks/register.tsx +248 -0
  29. package/plugins/nexakit-panel/tests/lienzo.test.ts +142 -0
  30. package/plugins/nexakit-panel/tests/panel.test.ts +240 -0
  31. package/plugins/nexakit-panel/tests/register.test.ts +167 -0
  32. package/plugins/nexakit-panel/types/index.d.ts +41 -0
  33. package/plugins/nexakit-sesion/.claude-plugin/plugin.json +1 -0
  34. package/plugins/nexakit-sesion/README.md +12 -0
  35. package/plugins/nexakit-sesion/hooks/aviso.js +38 -0
  36. package/plugins/nexakit-sesion/hooks/hooks.json +1 -0
  37. package/plugins/nexakit-sesion/hooks/register.js +28 -0
  38. package/plugins/nexakit-sesion/tests/register.test.ts +96 -0
  39. package/schemas/skill-catalog.schema.json +9 -2
  40. package/skills/cerrar-memoria/SKILL.md +6 -2
  41. package/skills/cerrar-memoria/references/cierre.md +5 -1
  42. package/skills/elegir-modelo/SKILL.md +68 -0
  43. package/skills/elegir-modelo/modelos.mjs +108 -0
  44. package/skills/gemini/SKILL.md +1 -1
  45. package/skills/project-handoff/SKILL.md +39 -12
  46. package/skills/project-handoff/references/handoff-template.md +24 -17
  47. package/skills/quality-gate/SKILL.md +6 -12
  48. package/skills/revisar/SKILL.md +6 -16
  49. package/skills/{desarrollo-con-pruebas/LICENSE.txt → revisar-skill/LICENSE-find-skills.txt} +5 -5
  50. package/skills/revisar-skill/SKILL.md +94 -0
  51. package/skills/revision-cruzada/SKILL.md +53 -475
  52. package/skills/web-design-guidelines/AGENTS.md +156 -0
  53. package/skills/{spec-driven-development → web-design-guidelines}/LICENSE.txt +1 -6
  54. package/skills/web-design-guidelines/SKILL.md +20 -30
  55. package/src/agent-models.js +53 -13
  56. package/src/brand.js +117 -0
  57. package/src/cli-executable.js +5 -3
  58. package/src/cli.js +16 -9
  59. package/src/connect.js +56 -4
  60. package/src/init.js +9 -1
  61. package/src/install-lock.js +127 -0
  62. package/src/install-registry.js +61 -0
  63. package/src/legacy-repair.js +71 -0
  64. package/src/link-states.js +172 -0
  65. package/src/memory-locator.js +118 -0
  66. package/src/missing-dependencies.js +21 -0
  67. package/src/model-command.js +147 -0
  68. package/src/panel-cli.js +182 -0
  69. package/src/project-root.js +41 -0
  70. package/src/project-signals.js +339 -0
  71. package/src/recommend-report.js +77 -0
  72. package/src/review-instructions.js +211 -0
  73. package/src/review-policy.js +35 -0
  74. package/src/revision-adapters.js +157 -0
  75. package/src/revision-context.js +86 -0
  76. package/src/revision-isolation.js +222 -0
  77. package/src/revision-process.js +103 -0
  78. package/src/revision-provenance.js +89 -0
  79. package/src/revision-snapshot.js +120 -0
  80. package/src/revision.js +242 -0
  81. package/src/setup-apply.js +231 -0
  82. package/src/setup-plan.js +231 -0
  83. package/src/setup-state.js +277 -0
  84. package/src/setup-tui.js +555 -0
  85. package/src/skill-catalog-add.js +91 -0
  86. package/src/skill-catalog-audit.js +26 -27
  87. package/src/skill-files.js +39 -0
  88. package/src/skill-recommendations.js +321 -208
  89. package/src/skill-scan-report.js +14 -0
  90. package/src/skill-scan.js +178 -0
  91. package/src/stable-command.js +53 -0
  92. package/src/stable-install.js +222 -0
  93. package/src/superpowers.js +133 -0
  94. package/src/sync-apply.js +8 -5
  95. package/src/sync-files.js +2 -1
  96. package/src/terminal-text.js +68 -0
  97. package/src/toolkit-cli.js +397 -70
  98. package/src/toolkit-commands-guide.js +53 -39
  99. package/src/toolkit-setup.js +507 -110
  100. package/assets/brand/agent-toolkit-mark-v1.png +0 -0
  101. package/skills/depurar/SKILL.md +0 -69
  102. package/skills/depurar/agents/openai.yaml +0 -7
  103. package/skills/depurar/references/tecnicas-de-diagnostico.md +0 -37
  104. package/skills/desarrollo-con-pruebas/SKILL.md +0 -55
  105. package/skills/desarrollo-con-pruebas/agents/openai.yaml +0 -7
  106. package/skills/desarrollo-con-pruebas/references/buenas-pruebas.md +0 -33
  107. package/skills/estado/SKILL.md +0 -41
  108. package/skills/programar/SKILL.md +0 -19
  109. package/skills/spec-driven-development/SKILL.md +0 -48
  110. package/skills/spec-driven-development/agents/openai.yaml +0 -7
  111. package/skills/spec-driven-development/references/spec-template.md +0 -55
  112. package/skills/spec-driven-development/references/workflow.md +0 -70
  113. package/src/toolkit-tui.js +0 -961
@@ -0,0 +1,156 @@
1
+ Concise rules for building accessible, fast, delightful UIs. Use MUST/SHOULD/NEVER to guide decisions.
2
+
3
+ ## Interactions
4
+
5
+ ### Keyboard
6
+
7
+ - MUST: Full keyboard support per [WAI-ARIA APG](https://www.w3.org/WAI/ARIA/apg/patterns/)
8
+ - MUST: Visible, unobscured focus rings (`:focus-visible`; group with `:focus-within`); sticky/fixed elements never cover focus
9
+ - MUST: Manage focus (trap, move, return) per APG patterns
10
+ - NEVER: `outline: none` without visible focus replacement
11
+
12
+ ### Targets & Input
13
+
14
+ - MUST: Hit target ≥24px (mobile ≥44px); if visual <24px, expand hit area
15
+ - MUST: Mobile `<input>` font-size ≥16px to prevent iOS zoom
16
+ - NEVER: Disable browser zoom (`user-scalable=no`, `maximum-scale=1`)
17
+ - MUST: `touch-action: manipulation` to prevent double-tap zoom
18
+ - SHOULD: Set `-webkit-tap-highlight-color` to match design
19
+
20
+ ### Forms
21
+
22
+ - MUST: Hydration-safe inputs (no lost focus/value)
23
+ - NEVER: Block paste in `<input>`/`<textarea>`
24
+ - MUST: Loading buttons show spinner and keep original label
25
+ - MUST: Enter submits focused input; in `<textarea>`, ⌘/Ctrl+Enter submits
26
+ - MUST: Keep submit enabled until request starts; then disable with spinner
27
+ - MUST: Accept free text, validate after—don't block typing
28
+ - MUST: Allow incomplete form submission to surface validation
29
+ - MUST: Errors inline next to fields; on submit, focus first error
30
+ - MUST: `autocomplete` + meaningful `name`; correct `type` and `inputmode`
31
+ - SHOULD: Disable spellcheck for emails/codes/usernames
32
+ - SHOULD: Placeholders end with `…` and show example pattern
33
+ - MUST: Warn on unsaved changes before navigation
34
+ - MUST: Compatible with password managers & 2FA; allow pasting codes
35
+ - MUST: Trim values to handle text expansion trailing spaces
36
+ - MUST: No dead zones on checkboxes/radios; label+control share one hit target
37
+
38
+ ### State & Navigation
39
+
40
+ - MUST: URL reflects state (deep-link filters/tabs/pagination/expanded panels)
41
+ - MUST: Back/Forward restores scroll position
42
+ - MUST: Links use `<a>`/`<Link>` for navigation (support Cmd/Ctrl/middle-click)
43
+ - NEVER: Use `<div onClick>` for navigation
44
+
45
+ ### Feedback
46
+
47
+ - SHOULD: Optimistic UI; reconcile on response; on failure rollback or offer Undo
48
+ - MUST: Confirm destructive actions or provide Undo window
49
+ - MUST: Use polite `aria-live` for toasts/inline validation
50
+ - SHOULD: Ellipsis (`…`) for options opening follow-ups ("Rename…") and loading states ("Loading…")
51
+
52
+ ### Touch & Drag
53
+
54
+ - MUST: Generous targets, clear affordances; avoid finicky interactions
55
+ - MUST: Delay first tooltip; subsequent peers instant
56
+ - MUST: `overscroll-behavior: contain` in modals/drawers
57
+ - SHOULD: When suppressing page rubber-band bounce, set `overscroll-behavior: none` on `<html>` inside `@media (pointer: fine)`; keep coarse-pointer page defaults for pull-to-refresh. Test hybrid touch devices: `pointer` describes the primary input, and `none` also disables scroll chaining and native swipe navigation
58
+ - MUST: During drag, disable text selection and set `inert` on dragged elements
59
+ - MUST: Drag/swipe/pinch/path gestures have a tap/click and keyboard alternative unless essential
60
+ - MUST: If it looks clickable, it must be clickable
61
+
62
+ ### Autofocus
63
+
64
+ - SHOULD: Autofocus on desktop with single primary input; rarely on mobile
65
+
66
+ ## Animation
67
+
68
+ - MUST: Honor `prefers-reduced-motion` (provide reduced variant or disable)
69
+ - SHOULD: Prefer CSS > Web Animations API > JS libraries
70
+ - MUST: Animate compositor-friendly props (`transform`, `opacity`) only
71
+ - NEVER: Animate layout props (`top`, `left`, `width`, `height`)
72
+ - NEVER: `transition: all`—list properties explicitly
73
+ - SHOULD: Animate only to clarify cause/effect or add deliberate delight
74
+ - SHOULD: Choose easing to match the change (size/distance/trigger)
75
+ - MUST: Animations interruptible and input-driven; autoplay only for muted, non-essential loops
76
+ - MUST: Autoplay motion >5s alongside other content has pause, stop, or hide controls
77
+ - MUST: Correct `transform-origin` (motion starts where it "physically" should)
78
+ - MUST: SVG transforms on `<g>` wrapper with `transform-box: fill-box`
79
+
80
+ ## Layout
81
+
82
+ - SHOULD: Optical alignment; adjust ±1px when perception beats geometry
83
+ - MUST: Deliberate alignment to grid/baseline/edges—no accidental placement
84
+ - SHOULD: Balance icon/text lockups (weight/size/spacing/color)
85
+ - MUST: Verify mobile, laptop, ultra-wide (simulate ultra-wide at 50% zoom)
86
+ - MUST: Respect safe areas (`env(safe-area-inset-*)`)
87
+ - MUST: Avoid unwanted scrollbars; fix overflows
88
+ - SHOULD: Flex/grid over JS measurement for layout
89
+
90
+ ## Content & Accessibility
91
+
92
+ - SHOULD: Inline help first; tooltips last resort
93
+ - MUST: Skeletons mirror final content to avoid layout shift
94
+ - MUST: `<title>` matches current context
95
+ - MUST: No dead ends; always offer next step/recovery
96
+ - MUST: Design empty/sparse/dense/error states
97
+ - SHOULD: Curly quotes (“ ”); avoid widows/orphans (`text-wrap: balance`)
98
+ - MUST: `font-variant-numeric: tabular-nums` for number comparisons
99
+ - MUST: Redundant status cues (not color-only); icons have text labels
100
+ - MUST: Accessible names exist even when visuals omit labels
101
+ - MUST: Use `…` character (not `...`)
102
+ - MUST: `scroll-margin-top` on headings; "Skip to content" link; hierarchical `<h1>`–`<h6>`
103
+ - MUST: Resilient to user-generated content (short/avg/very long)
104
+ - MUST: Locale-aware dates/times/numbers (`Intl.DateTimeFormat`, `Intl.NumberFormat`)
105
+ - SHOULD: `translate="no"` on brand names, code tokens, & identifiers to prevent garbled auto-translation
106
+ - MUST: Accurate `aria-label`; decorative elements `aria-hidden`
107
+ - MUST: Icon-only buttons have descriptive `aria-label`
108
+ - MUST: Prefer native semantics (`button`, `a`, `label`, `table`) before ARIA
109
+ - MUST: Media has captions/transcripts/descriptions as applicable; controls are keyboard-operable; decorative media hidden from assistive tech
110
+ - MUST: Non-breaking spaces: `10&nbsp;MB`, `⌘&nbsp;K`, brand names
111
+
112
+ ## Content Handling
113
+
114
+ - MUST: Text containers handle long content (`truncate`, `line-clamp-*`, `break-words`)
115
+ - MUST: Flex children need `min-w-0` to allow truncation
116
+ - MUST: Handle empty states—no broken UI for empty strings/arrays
117
+
118
+ ## Performance
119
+
120
+ - SHOULD: Test iOS Low Power Mode and macOS Safari
121
+ - MUST: Measure reliably (disable extensions that skew runtime)
122
+ - MUST: Track and minimize re-renders (React DevTools/React Scan)
123
+ - MUST: Profile with CPU/network throttling
124
+ - MUST: Batch layout reads/writes; avoid reflows/repaints
125
+ - MUST: Mutations (`POST`/`PATCH`/`DELETE`) target <500ms
126
+ - SHOULD: Prefer uncontrolled inputs; controlled inputs cheap per keystroke
127
+ - MUST: Virtualize large lists (>50 items)
128
+ - MUST: Preload above-fold images; lazy-load the rest
129
+ - MUST: Prevent CLS (explicit image dimensions)
130
+ - SHOULD: `<link rel="preconnect">` for CDN domains
131
+ - SHOULD: Critical fonts: `<link rel="preload" as="font">` with `font-display: swap`
132
+ - SHOULD: `<video autoplay muted loop playsinline>` over animated GIF; provide still/reduced-motion alternative
133
+ - SHOULD: Short non-essential loops include a Safari H.264 MP4 `<picture>` source, `prefers-reduced-motion` media condition, and still fallback
134
+
135
+ ## Dark Mode & Theming
136
+
137
+ - MUST: `color-scheme: dark` on `<html>` for dark themes
138
+ - SHOULD: `<meta name="theme-color">` matches page background
139
+ - MUST: Native `<select>`: explicit `background-color` and `color` (Windows fix)
140
+
141
+ ## Hydration
142
+
143
+ - MUST: Inputs with `value` need `onChange` (or use `defaultValue`)
144
+ - SHOULD: Guard date/time rendering against hydration mismatch
145
+
146
+ ## Design
147
+
148
+ - SHOULD: Layered shadows (ambient + direct)
149
+ - SHOULD: Crisp edges via semi-transparent borders + shadows
150
+ - SHOULD: Nested radii: child ≤ parent; concentric
151
+ - SHOULD: Hue consistency: tint borders/shadows/text toward bg hue
152
+ - MUST: Accessible charts (color-blind-friendly palettes)
153
+ - MUST: Meet contrast—prefer [APCA](https://apcacontrast.com/) over WCAG 2
154
+ - MUST: Increase contrast on `:hover`/`:active`/`:focus`
155
+ - SHOULD: Match browser UI to bg
156
+ - SHOULD: Avoid dark color gradient banding (use background images when needed)
@@ -1,11 +1,6 @@
1
- Adaptación parcial en español para Agent Toolkit a partir de prácticas de
2
- Superpowers 7.0.1 (https://github.com/obra/superpowers, skills `brainstorming`
3
- y `writing-plans`). Se conserva íntegro el aviso de copyright y licencia MIT
4
- del origen:
5
-
6
1
  MIT License
7
2
 
8
- Copyright (c) 2025 Jesse Vincent
3
+ Copyright (c) 2025 Vercel Labs
9
4
 
10
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
11
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,39 +1,29 @@
1
1
  ---
2
2
  name: web-design-guidelines
3
- description: Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
4
- metadata:
5
- author: vercel
6
- version: "1.0.0"
7
- argument-hint: <file-or-pattern>
3
+ license: Reglas de Vercel Labs con licencia MIT en LICENSE.txt; estas instrucciones, MIT de nexakit
4
+ description: Revisa código de interfaz web contra las Web Interface Guidelines (accesibilidad, foco, formularios, animación, tipografía, rendimiento y tema oscuro). Úsala cuando pidan revisar la UI, la accesibilidad o la UX, o auditar una página contra buenas prácticas.
8
5
  ---
9
6
 
10
- # Web Interface Guidelines
7
+ # Pautas de interfaz web
11
8
 
12
- Review files for compliance with Web Interface Guidelines.
9
+ Revisa archivos de interfaz contra las reglas de [`AGENTS.md`](AGENTS.md): las Web Interface Guidelines de
10
+ Vercel Labs, copiadas tal cual del commit `434b7f9` de
11
+ [vercel-labs/web-interface-guidelines](https://github.com/vercel-labs/web-interface-guidelines) con su
12
+ licencia MIT en [`LICENSE.txt`](LICENSE.txt). Las reglas están fijadas en esta carpeta: no se descargan
13
+ en cada revisión.
13
14
 
14
- ## How It Works
15
+ ## Cómo revisar
15
16
 
16
- 1. Fetch the latest guidelines from the source URL below
17
- 2. Read the specified files (or prompt user for files/pattern)
18
- 3. Check against all rules in the fetched guidelines
19
- 4. Output findings in the terse `file:line` format
17
+ 1. Si no te dicen qué revisar, pregunta por los archivos o el patrón (por ejemplo `src/**/*.tsx`).
18
+ 2. Lee `AGENTS.md` completo y los archivos indicados.
19
+ 3. Comprueba cada archivo contra las reglas. `MUST` y `NEVER` son obligatorias; `SHOULD`, recomendaciones.
20
+ 4. Informa cada hallazgo en una línea: `archivo:línea — regla — qué cambiar`. Agrupa por archivo y ordena
21
+ primero los `MUST`/`NEVER`.
22
+ 5. Si un archivo cumple, dilo en una línea; no inventes hallazgos para llenar el informe.
20
23
 
21
- ## Guidelines Source
24
+ ## Límites
22
25
 
23
- Fetch fresh guidelines before each review:
24
-
25
- ```
26
- https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md
27
- ```
28
-
29
- Use WebFetch to retrieve the latest rules. The fetched content contains all the rules and output format instructions.
30
-
31
- ## Usage
32
-
33
- When a user provides a file or pattern argument:
34
- 1. Fetch guidelines from the source URL above
35
- 2. Read the specified files
36
- 3. Apply all rules from the fetched guidelines
37
- 4. Output findings using the format specified in the guidelines
38
-
39
- If no files specified, ask the user which files to review.
26
+ - Es una revisión estática del código: no sustituye probar con teclado, lector de pantalla o en un móvil real.
27
+ Dilo al final del informe.
28
+ - Algunas reglas son de React/Next.js; en otros frameworks aplica su equivalente o márcalas como no aplicables.
29
+ - Para diseñar o rediseñar, usa `frontend-design`; esta skill revisa lo que ya existe.
@@ -6,31 +6,71 @@ import { getStaticTOMLValue, parseTOML } from "toml-eslint-parser";
6
6
  // Claude Code accepts stable aliases; Codex publishes its catalog in models_cache.json.
7
7
  const CLAUDE = [
8
8
  { model: "haiku", category: "ligero", efforts: ["low", "medium", "high", "xhigh", "max"] },
9
- { model: "sonnet", category: "trabajo diario", efforts: ["low", "medium", "high", "xhigh", "max"] },
10
- { model: "opus", category: "más capaz", efforts: ["low", "medium", "high", "xhigh", "max"] }
9
+ { model: "sonnet", category: "uso diario", efforts: ["low", "medium", "high", "xhigh", "max"] },
10
+ { model: "opus", category: "el más capaz", efforts: ["low", "medium", "high", "xhigh", "max"] }
11
11
  ];
12
12
  const LEVELS = [
13
13
  { level: "Ligero", total: "0–2", category: "ligero", effort: "low" },
14
- { level: "Normal", total: "3–4", category: "trabajo diario", effort: "medium" },
15
- { level: "Exigente", total: "5–6", category: "trabajo diario", effort: "high" },
16
- { level: "Alto", total: "7–10", category: "más capaz", effort: "high" }
14
+ { level: "Normal", total: "3–4", category: "uso diario", effort: "medium" },
15
+ { level: "Exigente", total: "5–6", category: "uso diario", effort: "high" },
16
+ { level: "Alto", total: "7–10", category: "el más capaz", effort: "high" }
17
17
  ];
18
+ const FAMILY_ROLES = Object.freeze({
19
+ luna: "ligero",
20
+ flash: "ligero",
21
+ haiku: "ligero",
22
+ terra: "uso diario",
23
+ sol: "uso diario",
24
+ sonnet: "uso diario",
25
+ astra: "el más capaz",
26
+ argon: "el más capaz",
27
+ opus: "el más capaz"
28
+ });
18
29
 
19
- function category(description = "") {
20
- if (/\b(older|previous)\b/iu.test(description)) return "anterior";
30
+ export function roleFromDescription(description = "") {
21
31
  if (/\b(fast and (affordable|efficient)|easier tasks|lightweight)\b/iu.test(description)) return "ligero";
22
- if (/\b(frontier|most demanding)\b/iu.test(description)) return "más capaz";
23
- if (/\b(workhorse|everyday|balanced)\b/iu.test(description)) return "trabajo diario";
24
- return "otro";
32
+ if (/\b(frontier|most demanding)\b/iu.test(description)) return "el más capaz";
33
+ if (/\b(workhorse|everyday|balanced|straightforward work)\b/iu.test(description)) return "uso diario";
34
+ return null;
35
+ }
36
+
37
+ export function familyOfModel(slug = "") {
38
+ const base = String(slug).replace(/-(?:low|medium|high|xhigh|max|ultra)$/iu, "");
39
+ const known = base.match(/\b(luna|terra|sol|astra|flash|argon|haiku|sonnet|opus)\b/iu);
40
+ if (known) return known[1].toLowerCase();
41
+ const family = base.match(/^(?:gpt|gemini|claude)-\d+(?:\.\d+)*-([a-z][a-z0-9]*)$/iu);
42
+ return family ? family[1].toLowerCase() : null;
43
+ }
44
+
45
+ export function classifyModelRole(slug = "", description = "", familyRoles = new Map(Object.entries(FAMILY_ROLES))) {
46
+ if (/\b(older|previous)\b/iu.test(description)) return "anterior";
47
+ const direct = roleFromDescription(description);
48
+ if (direct) return direct;
49
+ const family = familyOfModel(slug);
50
+ if (family && familyRoles.has(family)) return familyRoles.get(family);
51
+ return "sin clasificar";
52
+ }
53
+
54
+ export function buildFamilyRoleMap(items = []) {
55
+ const map = new Map(Object.entries(FAMILY_ROLES));
56
+ for (const item of items) {
57
+ const family = familyOfModel(item.slug ?? item.model ?? "");
58
+ const role = roleFromDescription(item.description ?? "");
59
+ if (family && role) map.set(family, role);
60
+ }
61
+ return map;
25
62
  }
26
63
 
27
64
  async function codexCatalog(codexHome) {
28
65
  let cache;
29
66
  try { cache = JSON.parse(await fs.readFile(path.join(codexHome, "models_cache.json"), "utf8")); }
30
67
  catch { return { available: false, models: [] }; }
31
- const models = (Array.isArray(cache?.models) ? cache.models : [])
32
- .filter(item => item && typeof item.slug === "string" && (item.visibility ?? "list") === "list")
33
- .map(item => ({ model: item.slug, description: String(item.description ?? "").slice(0, 120), category: category(item.description),
68
+ const visible = (Array.isArray(cache?.models) ? cache.models : [])
69
+ .filter(item => item && typeof item.slug === "string" && (item.visibility ?? "list") === "list");
70
+ const familyRoles = buildFamilyRoleMap(visible);
71
+ const models = visible
72
+ .map(item => ({ model: item.slug, description: String(item.description ?? "").slice(0, 120),
73
+ category: classifyModelRole(item.slug, item.description, familyRoles),
34
74
  priority: Number.isFinite(item.priority) ? item.priority : 999,
35
75
  efforts: (item.supported_reasoning_levels ?? []).map(level => level?.effort).filter(effort => typeof effort === "string") }));
36
76
  return { available: true, fetchedAt: cache.fetched_at ?? null, models };
package/src/brand.js ADDED
@@ -0,0 +1,117 @@
1
+ // La marca de nexakit en la terminal: el logo de píxeles, la paleta y el nivel de color que admite cada terminal.
2
+ // Sin color (NO_COLOR, salida que no es una terminal, TERM=dumb) no hay logo ni paleta: se usa texto plano.
3
+
4
+ // Fuente de píxeles de 5×7. Cada letra es una columna de 5 píxeles más 1 de separación.
5
+ const GLYPHS = {
6
+ N: ['10001', '11001', '10101', '10011', '10001', '10001', '10001'],
7
+ E: ['11111', '10000', '10000', '11110', '10000', '10000', '11111'],
8
+ X: ['10001', '10001', '01010', '00100', '01010', '10001', '10001'],
9
+ A: ['01110', '10001', '10001', '11111', '10001', '10001', '10001'],
10
+ K: ['10001', '10010', '10100', '11000', '10100', '10010', '10001'],
11
+ I: ['11111', '00100', '00100', '00100', '00100', '00100', '11111'],
12
+ T: ['11111', '00100', '00100', '00100', '00100', '00100', '00100'],
13
+ };
14
+ const WORD = 'NEXAKIT';
15
+ const HEIGHT = 7;
16
+ const FACE = [...Array(HEIGHT).keys()].map((row) =>
17
+ [...[...WORD].map((letter) => GLYPHS[letter][row]).join('0')].map(Number),
18
+ );
19
+ const PIXEL_WIDTH = FACE[0].length;
20
+
21
+ // La sombra cae un píxel a la derecha y uno abajo, como en el logo original: 41 columnas de letras + 1 de sombra.
22
+ export const LOGO_WIDTH = PIXEL_WIDTH + 1;
23
+ export const LOGO_ROWS = Math.ceil((HEIGHT + 1) / 2);
24
+
25
+ const pixel = (row, column, shadow) => {
26
+ if (FACE[row]?.[column]) return 'face';
27
+ return shadow && FACE[row - 1]?.[column - 1] ? 'shadow' : null;
28
+ };
29
+
30
+ // Dos píxeles por celda con medios bloques: arriba ▀, abajo ▄, los dos █.
31
+ export function logoArt() {
32
+ const lines = [];
33
+ for (let row = 0; row < HEIGHT + 1; row += 2) {
34
+ let line = '';
35
+ for (let column = 0; column < PIXEL_WIDTH; column++) {
36
+ const top = pixel(row, column, false);
37
+ const bottom = pixel(row + 1, column, false);
38
+ line += top && bottom ? '█' : top ? '▀' : bottom ? '▄' : ' ';
39
+ }
40
+ lines.push(line.trimEnd());
41
+ }
42
+ return lines;
43
+ }
44
+
45
+ // none: sin color · 16: colores básicos · 256: paleta de 256 · truecolor: 24 bits.
46
+ export function colorLevel(output, env = process.env) {
47
+ if (!output?.isTTY || env.NO_COLOR || env.TERM === 'dumb') return 'none';
48
+ if (env.COLORTERM === 'truecolor' || env.COLORTERM === '24bit') return 'truecolor';
49
+ if (/256color/u.test(env.TERM ?? '')) return '256';
50
+ return '16';
51
+ }
52
+ // Quien llama con el antiguo true/false sigue funcionando.
53
+ export const levelOf = (color) => (color === true ? 'truecolor' : !color ? 'none' : color);
54
+
55
+ const PALETTE = {
56
+ truecolor: {
57
+ face: { fg: '38;2;236;232;244', bg: '48;2;236;232;244' },
58
+ shadow: { fg: '38;2;62;48;110', bg: '48;2;62;48;110' },
59
+ accent: '38;2;167;139;250',
60
+ soft: '38;2;196;181;253',
61
+ chip: '48;2;54;44;98;38;2;221;214;254',
62
+ },
63
+ 256: {
64
+ face: { fg: '38;5;255', bg: '48;5;255' },
65
+ shadow: { fg: '38;5;60', bg: '48;5;60' },
66
+ accent: '38;5;141',
67
+ soft: '38;5;183',
68
+ chip: '48;5;60;38;5;189',
69
+ },
70
+ 16: {
71
+ face: { fg: '97', bg: '107' },
72
+ shadow: { fg: '35', bg: '45' },
73
+ accent: '95',
74
+ soft: '35',
75
+ chip: '45;97',
76
+ },
77
+ };
78
+
79
+ export function renderLogo(level) {
80
+ const colors = PALETTE[level];
81
+ if (!colors) return null;
82
+ const lines = [];
83
+ for (let row = 0; row < HEIGHT + 1; row += 2) {
84
+ let line = '';
85
+ for (let column = 0; column < LOGO_WIDTH; column++) {
86
+ const top = pixel(row, column, true);
87
+ const bottom = pixel(row + 1, column, true);
88
+ if (!top && !bottom) line += ' ';
89
+ else if (!top) line += `\x1b[${colors[bottom].fg}m▄\x1b[0m`;
90
+ else if (!bottom) line += `\x1b[${colors[top].fg}m▀\x1b[0m`;
91
+ else if (top === bottom) line += `\x1b[${colors[top].fg}m█\x1b[0m`;
92
+ else line += `\x1b[${colors[top].fg};${colors[bottom].bg}m▀\x1b[0m`;
93
+ }
94
+ lines.push(line);
95
+ }
96
+ return lines;
97
+ }
98
+
99
+ // Los estilos de texto de las pantallas. Sin color, cada uno devuelve el texto tal cual.
100
+ export function paint(level) {
101
+ const colors = PALETTE[level];
102
+ if (!colors) {
103
+ const same = (text) => String(text);
104
+ return { accent: same, soft: same, chip: same, bold: same, dim: same, green: same, yellow: same, red: same };
105
+ }
106
+ const wrap = (open, close) => (text) => `\x1b[${open}m${text}\x1b[${close}m`;
107
+ return {
108
+ accent: (text) => `\x1b[1;${colors.accent}m${text}\x1b[22;39m`,
109
+ soft: wrap(colors.soft, 39),
110
+ chip: (text) => `\x1b[${colors.chip}m ${text} \x1b[0m`,
111
+ bold: wrap(1, 22),
112
+ dim: wrap(2, 22),
113
+ green: wrap(32, 39),
114
+ yellow: wrap(33, 39),
115
+ red: wrap(31, 39),
116
+ };
117
+ }
@@ -36,7 +36,9 @@ async function packageInvocation(directory, name, io, expectedScript) {
36
36
  }
37
37
 
38
38
  export async function resolveCliInvocation({ name, override, environment = process.env, fileIo = fs } = {}) {
39
- if (!Object.hasOwn(PACKAGES, name)) unavailable();
39
+ // agy (Antigravity) is a native executable, not an npm package: only found as such on the PATH.
40
+ const native = name === "agy";
41
+ if (!native && !Object.hasOwn(PACKAGES, name)) unavailable();
40
42
  if (override) {
41
43
  const absolute = path.resolve(override);
42
44
  let stat;
@@ -48,7 +50,7 @@ export async function resolveCliInvocation({ name, override, environment = proce
48
50
  }
49
51
  const extension = path.extname(absolute).toLowerCase();
50
52
  if ([".exe", ".com"].includes(extension)) return { executable: absolute, argsPrefix: [] };
51
- if (extension === ".cmd" || NODE_EXTENSIONS.has(extension)) {
53
+ if (!native && (extension === ".cmd" || NODE_EXTENSIONS.has(extension))) {
52
54
  const directories = [path.dirname(absolute)];
53
55
  // Explicit npm bin scripts may live inside the CLI package itself.
54
56
  if (NODE_EXTENSIONS.has(extension)) {
@@ -77,7 +79,7 @@ export async function resolveCliInvocation({ name, override, environment = proce
77
79
  if (stat.isFile() && (IS_WINDOWS || ((stat.mode & 0o111) && !(stat.mode & 0o022)))) return { executable, argsPrefix: [] };
78
80
  } catch {}
79
81
  }
80
- if (IS_WINDOWS) {
82
+ if (IS_WINDOWS && !native) {
81
83
  const result = await packageInvocation(directory, name, fileIo);
82
84
  if (result) return result;
83
85
  }
package/src/cli.js CHANGED
@@ -39,7 +39,7 @@ Opciones de doctor:
39
39
  --strict Devuelve error cuando existan advertencias.
40
40
 
41
41
  Opciones de connect:
42
- --agent <agente> Agente a conectar: codex o claude.
42
+ --agent <agente> Agente a conectar: codex, claude o antigravity.
43
43
  --manifest <ruta> Selecciona un manifiesto concreto.
44
44
  --apply Aplica el plan; sin esta opción solo previsualiza.
45
45
 
@@ -87,9 +87,16 @@ Evita editar o sincronizar la memoria durante --apply: su bloqueo no controla
87
87
  editores externos. Ni record ni apply crean commits o hacen push. apply puede
88
88
  quedar parcial entre notas: no revierte ni registra automáticamente el PR.`;
89
89
 
90
+ // Invoked as `npx nexakit memoria`, the help names that command and keeps project-memory as the compatible executable.
91
+ const DEFAULT_COMMAND = "project-memory";
92
+ function helpFor(commandName = DEFAULT_COMMAND) {
93
+ if (commandName === DEFAULT_COMMAND) return HELP;
94
+ return `${HELP.replaceAll(DEFAULT_COMMAND, commandName)}\n\nEl ejecutable ${DEFAULT_COMMAND} es compatible y acepta los mismos subcomandos.`;
95
+ }
96
+
90
97
  export async function runCli(argv, io = defaultIo(), context = {}) {
91
98
  if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
92
- io.out(HELP);
99
+ io.out(helpFor(context.commandName));
93
100
  return 0;
94
101
  }
95
102
 
@@ -103,11 +110,11 @@ export async function runCli(argv, io = defaultIo(), context = {}) {
103
110
  }
104
111
 
105
112
  if (command === "connect") {
106
- return runConnectCommand(rest, io);
113
+ return runConnectCommand(rest, io, context);
107
114
  }
108
115
 
109
116
  if (command === "status") {
110
- return runStatusCommand(rest, io);
117
+ return runStatusCommand(rest, io, context);
111
118
  }
112
119
 
113
120
  if (command === "sync") {
@@ -115,7 +122,7 @@ export async function runCli(argv, io = defaultIo(), context = {}) {
115
122
  }
116
123
 
117
124
  io.err(`Comando desconocido: ${command}`);
118
- io.err(HELP);
125
+ io.err(helpFor(context.commandName));
119
126
  return 2;
120
127
  }
121
128
 
@@ -319,7 +326,7 @@ async function runSyncApplyCommand(args, io, context) {
319
326
  }
320
327
  }
321
328
 
322
- async function runStatusCommand(rest, io) {
329
+ async function runStatusCommand(rest, io, context = {}) {
323
330
  const options = {
324
331
  targetPath: process.cwd()
325
332
  };
@@ -366,7 +373,7 @@ async function runStatusCommand(rest, io) {
366
373
 
367
374
  let report;
368
375
  try {
369
- report = await inspectMemoryStatus(options);
376
+ report = await inspectMemoryStatus(options, context);
370
377
  } catch (error) {
371
378
  const message = error instanceof Error ? error.message : String(error);
372
379
  io.err(`No se pudo inspeccionar el estado: ${message}`);
@@ -388,7 +395,7 @@ async function runStatusCommand(rest, io) {
388
395
  return 0;
389
396
  }
390
397
 
391
- async function runConnectCommand(rest, io) {
398
+ async function runConnectCommand(rest, io, context = {}) {
392
399
  const options = {
393
400
  apply: false,
394
401
  targetPath: process.cwd()
@@ -435,7 +442,7 @@ async function runConnectCommand(rest, io) {
435
442
 
436
443
  let report;
437
444
  try {
438
- report = await connectMemory(options);
445
+ report = await connectMemory(options, context);
439
446
  } catch (error) {
440
447
  if (error instanceof ConnectionError) {
441
448
  for (const detail of error.details) {
package/src/connect.js CHANGED
@@ -42,14 +42,20 @@ const ADAPTERS = Object.freeze({
42
42
  },
43
43
  claude: {
44
44
  skillDirectory: ".claude/skills"
45
+ },
46
+ antigravity: {
47
+ skillDirectory: ".gemini/antigravity-cli/skills"
45
48
  }
46
49
  });
50
+ // The command each agent is driven with.
51
+ const AGENT_COMMANDS = Object.freeze({ codex: "codex", claude: "claude", antigravity: "agy" });
52
+ const MEMORY_SCRIPT = path.join("bin", "project-memory-mcp.js");
47
53
 
48
54
  export async function connectMemory(options, context = {}) {
49
55
  const config = loadConfig(options, context);
50
- const runner = context.runCommand ?? (IS_WINDOWS ? await windowsCommandRunner(config.agentId) : runCommand);
56
+ const runner = context.runCommand ?? (IS_WINDOWS ? await windowsCommandRunner(AGENT_COMMANDS[config.agentId]) : runCommand);
51
57
  const skill = inspectSkill(config);
52
- const connection = inspectConnection(config, runner);
58
+ const connection = inspectConnection(config, runner, context);
53
59
  const components = [skill, ...connection.components];
54
60
  const report = createReport(config, components, options.apply === true);
55
61
 
@@ -94,7 +100,7 @@ function loadConfig(options, context) {
94
100
  errors.push("No se encontró project-memory.json.");
95
101
  }
96
102
  if (!ADAPTERS[agentId]) {
97
- errors.push("--agent debe ser codex o claude.");
103
+ errors.push("--agent debe ser codex, claude o antigravity.");
98
104
  }
99
105
  if (errors.length > 0) {
100
106
  throw new ConnectionError(errors);
@@ -221,10 +227,13 @@ function inspectSkill(config) {
221
227
  };
222
228
  }
223
229
 
224
- function inspectConnection(config, runner) {
230
+ function inspectConnection(config, runner, context) {
225
231
  if (config.agentId === "codex") {
226
232
  return inspectCodex(config, runner);
227
233
  }
234
+ if (config.agentId === "antigravity") {
235
+ return inspectAntigravity(config, runner, context);
236
+ }
228
237
  return inspectClaude(config, runner);
229
238
  }
230
239
 
@@ -364,6 +373,49 @@ function inspectClaude(config, runner) {
364
373
  };
365
374
  }
366
375
 
376
+ // Antigravity cannot restrict the tools of an MCP server, so it gets nexakit's own memory server, which only has
377
+ // read tools, run from the active nexakit root. Its configuration is read from mcp_config.json, which keeps the
378
+ // exact arguments (the text of `agy mcp list` does not separate paths with spaces).
379
+ function inspectAntigravity(config, runner, context) {
380
+ const probe = runner("agy", ["mcp", "list"]);
381
+ if (probe.errorCode === "ENOENT") {
382
+ const unavailable = unavailableCommand("agy");
383
+ unavailable.components = unavailable.components.filter((item) => item.id === "mcp");
384
+ return unavailable;
385
+ }
386
+ const nodePath = context.nodePath ?? process.execPath;
387
+ const script = path.join(path.resolve(context.toolkitRoot ?? path.resolve(SOURCE_DIRECTORY, "..")), MEMORY_SCRIPT);
388
+ const manifestPath = fs.realpathSync(config.manifestPath);
389
+ const configPath = path.join(config.homeDirectory, ".gemini", "config", "mcp_config.json");
390
+ let current;
391
+ if (pathEntryExists(configPath)) {
392
+ if (fs.lstatSync(configPath).isSymbolicLink()) {
393
+ return { components: [component("mcp", "conflict", "La configuración de Antigravity es un enlace simbólico y no se reemplazará.")], apply() {} };
394
+ }
395
+ current = readJson(configPath, "configuración de Antigravity").mcpServers?.[config.mcpName];
396
+ }
397
+ const same = current && Array.isArray(current.args) && current.args.length === 3 &&
398
+ samePath(current.args[0], script) && current.args[1] === "--manifest" && samePath(current.args[2], manifestPath) &&
399
+ typeof current.command === "string" && /^node(\.exe)?$/iu.test(path.basename(current.command));
400
+ const mcp = !current
401
+ ? component("mcp", "create", "Registrar la memoria de solo lectura en Antigravity.")
402
+ : same
403
+ ? component("mcp", "unchanged", "Antigravity ya usa esta memoria.")
404
+ : component("mcp", "conflict", "Antigravity tiene un MCP homónimo diferente.");
405
+ mcp.path = configPath;
406
+ return {
407
+ components: [mcp],
408
+ apply() {
409
+ if (mcp.action === "create") {
410
+ requireSuccess(
411
+ runner("agy", ["mcp", "add", config.mcpName, "--", nodePath, script, "--manifest", manifestPath]),
412
+ "No se pudo registrar el MCP en Antigravity."
413
+ );
414
+ }
415
+ }
416
+ };
417
+ }
418
+
367
419
  function unavailableCommand(command) {
368
420
  return {
369
421
  components: [