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.
- package/.claude-plugin/marketplace.json +18 -1
- package/README.md +40 -30
- package/assets/brand/README.md +0 -2
- package/assets/runtime/package-lock.json +2 -2
- package/bin/agent-task.js +12 -10
- package/bin/agent-toolkit-gemini.js +11 -9
- package/bin/agent-toolkit-inbox.js +12 -10
- package/bin/agent-toolkit-mcp.js +14 -8
- package/bin/agent-toolkit.js +12 -9
- package/bin/nexakit.js +12 -9
- package/bin/project-memory-mcp.js +12 -9
- package/bin/project-memory.js +5 -3
- package/{skills/depurar → catalogs/skills/vercel-composition-patterns}/LICENSE.txt +6 -6
- package/catalogs/skills/vercel-react-best-practices/LICENSE.txt +27 -0
- package/catalogs/skills.json +70 -35
- package/docs/skills/evidencias/2026-10-28-recomendador-preparacion.md +1 -1
- package/package.json +5 -3
- package/plugins/nexakit-modelo/.claude-plugin/plugin.json +1 -0
- package/plugins/nexakit-modelo/hooks/hooks.json +1 -0
- package/plugins/nexakit-modelo/hooks/register.js +48 -0
- package/plugins/nexakit-modelo/hooks/status.js +8 -0
- package/plugins/nexakit-modelo/tests/register.test.ts +21 -0
- package/plugins/nexakit-panel/.claude-plugin/plugin.json +1 -0
- package/plugins/nexakit-panel/README.md +20 -0
- package/plugins/nexakit-panel/hooks/hooks.json +1 -0
- package/plugins/nexakit-panel/hooks/lienzo.js +382 -0
- package/plugins/nexakit-panel/hooks/panel.js +279 -0
- package/plugins/nexakit-panel/hooks/register.tsx +248 -0
- package/plugins/nexakit-panel/tests/lienzo.test.ts +142 -0
- package/plugins/nexakit-panel/tests/panel.test.ts +240 -0
- package/plugins/nexakit-panel/tests/register.test.ts +167 -0
- package/plugins/nexakit-panel/types/index.d.ts +41 -0
- package/plugins/nexakit-sesion/.claude-plugin/plugin.json +1 -0
- package/plugins/nexakit-sesion/README.md +12 -0
- package/plugins/nexakit-sesion/hooks/aviso.js +38 -0
- package/plugins/nexakit-sesion/hooks/hooks.json +1 -0
- package/plugins/nexakit-sesion/hooks/register.js +28 -0
- package/plugins/nexakit-sesion/tests/register.test.ts +96 -0
- package/schemas/skill-catalog.schema.json +9 -2
- package/skills/cerrar-memoria/SKILL.md +6 -2
- package/skills/cerrar-memoria/references/cierre.md +5 -1
- package/skills/elegir-modelo/SKILL.md +68 -0
- package/skills/elegir-modelo/modelos.mjs +108 -0
- package/skills/gemini/SKILL.md +1 -1
- package/skills/project-handoff/SKILL.md +39 -12
- package/skills/project-handoff/references/handoff-template.md +24 -17
- package/skills/quality-gate/SKILL.md +6 -12
- package/skills/revisar/SKILL.md +6 -16
- package/skills/{desarrollo-con-pruebas/LICENSE.txt → revisar-skill/LICENSE-find-skills.txt} +5 -5
- package/skills/revisar-skill/SKILL.md +94 -0
- package/skills/revision-cruzada/SKILL.md +53 -475
- package/skills/web-design-guidelines/AGENTS.md +156 -0
- package/skills/{spec-driven-development → web-design-guidelines}/LICENSE.txt +1 -6
- package/skills/web-design-guidelines/SKILL.md +20 -30
- package/src/agent-models.js +53 -13
- package/src/brand.js +117 -0
- package/src/cli-executable.js +5 -3
- package/src/cli.js +16 -9
- package/src/connect.js +56 -4
- package/src/init.js +9 -1
- package/src/install-lock.js +127 -0
- package/src/install-registry.js +61 -0
- package/src/legacy-repair.js +71 -0
- package/src/link-states.js +172 -0
- package/src/memory-locator.js +118 -0
- package/src/missing-dependencies.js +21 -0
- package/src/model-command.js +147 -0
- package/src/panel-cli.js +182 -0
- package/src/project-root.js +41 -0
- package/src/project-signals.js +339 -0
- package/src/recommend-report.js +77 -0
- package/src/review-instructions.js +211 -0
- package/src/review-policy.js +35 -0
- package/src/revision-adapters.js +157 -0
- package/src/revision-context.js +86 -0
- package/src/revision-isolation.js +222 -0
- package/src/revision-process.js +103 -0
- package/src/revision-provenance.js +89 -0
- package/src/revision-snapshot.js +120 -0
- package/src/revision.js +242 -0
- package/src/setup-apply.js +231 -0
- package/src/setup-plan.js +231 -0
- package/src/setup-state.js +277 -0
- package/src/setup-tui.js +555 -0
- package/src/skill-catalog-add.js +91 -0
- package/src/skill-catalog-audit.js +26 -27
- package/src/skill-files.js +39 -0
- package/src/skill-recommendations.js +321 -208
- package/src/skill-scan-report.js +14 -0
- package/src/skill-scan.js +178 -0
- package/src/stable-command.js +53 -0
- package/src/stable-install.js +222 -0
- package/src/superpowers.js +133 -0
- package/src/sync-apply.js +8 -5
- package/src/sync-files.js +2 -1
- package/src/terminal-text.js +68 -0
- package/src/toolkit-cli.js +397 -70
- package/src/toolkit-commands-guide.js +53 -39
- package/src/toolkit-setup.js +507 -110
- package/assets/brand/agent-toolkit-mark-v1.png +0 -0
- package/skills/depurar/SKILL.md +0 -69
- package/skills/depurar/agents/openai.yaml +0 -7
- package/skills/depurar/references/tecnicas-de-diagnostico.md +0 -37
- package/skills/desarrollo-con-pruebas/SKILL.md +0 -55
- package/skills/desarrollo-con-pruebas/agents/openai.yaml +0 -7
- package/skills/desarrollo-con-pruebas/references/buenas-pruebas.md +0 -33
- package/skills/estado/SKILL.md +0 -41
- package/skills/programar/SKILL.md +0 -19
- package/skills/spec-driven-development/SKILL.md +0 -48
- package/skills/spec-driven-development/agents/openai.yaml +0 -7
- package/skills/spec-driven-development/references/spec-template.md +0 -55
- package/skills/spec-driven-development/references/workflow.md +0 -70
- 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 MB`, `⌘ 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
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
#
|
|
7
|
+
# Pautas de interfaz web
|
|
11
8
|
|
|
12
|
-
|
|
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
|
-
##
|
|
15
|
+
## Cómo revisar
|
|
15
16
|
|
|
16
|
-
1.
|
|
17
|
-
2.
|
|
18
|
-
3.
|
|
19
|
-
4.
|
|
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
|
-
##
|
|
24
|
+
## Límites
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
package/src/agent-models.js
CHANGED
|
@@ -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: "
|
|
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: "
|
|
15
|
-
{ level: "Exigente", total: "5–6", category: "
|
|
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
|
|
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 "
|
|
24
|
-
return
|
|
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
|
|
32
|
-
.filter(item => item && typeof item.slug === "string" && (item.visibility ?? "list") === "list")
|
|
33
|
-
|
|
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
|
+
}
|
package/src/cli-executable.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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: [
|