@exchanet/enet 1.0.9 → 1.0.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exchanet/enet",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "enet — exchanet methods manager. Install, scaffold and manage AI coding methods.",
5
5
  "bin": {
6
6
  "enet": "src/index.js"
@@ -20,6 +20,11 @@ export async function installCommand(methodId, options) {
20
20
 
21
21
  console.log(chalk.bold(`\n ◆ ${method.name}`))
22
22
  console.log(chalk.dim(` ${method.description}\n`))
23
+ const record = await readInstallRecord(methodId)
24
+ const alreadyInstalled = new Set(record?.agents ?? [])
25
+ if (alreadyInstalled.size > 0) {
26
+ console.log(chalk.dim(` Already installed for: ${[...alreadyInstalled].join(', ')}\n`))
27
+ }
23
28
 
24
29
  // 2. Determine target agents
25
30
  let targetAgents = []
@@ -1,12 +1,15 @@
1
1
  import chalk from 'chalk'
2
2
  import ora from 'ora'
3
3
  import fs from 'fs-extra'
4
- import { getAllMethods, getMethod, fetchFromGitHub } from '../utils/registry.js'
5
- import { detectAgent, getInstallPath } from '../utils/agent-detector.js'
4
+ import path from 'path'
5
+ import readline from 'readline'
6
+ import { getAllMethods, getMethod, readInstallRecord, writeInstallRecord } from '../utils/registry.js'
7
+ import { detectSystemAgents, getInstallPath, AGENTS } from '../utils/agent-detector.js'
8
+ import { installForAgent } from './install.js'
6
9
 
7
10
  export async function updateCommand(methodId, options) {
8
11
  const spinner = ora('Fetching registry...').start()
9
- const [allMethods, agent] = await Promise.all([getAllMethods(), detectAgent()])
12
+ const [allMethods, detectedAgents] = await Promise.all([getAllMethods(), detectSystemAgents()])
10
13
  spinner.stop()
11
14
 
12
15
  const targets = methodId
@@ -18,30 +21,203 @@ export async function updateCommand(methodId, options) {
18
21
  process.exit(1)
19
22
  }
20
23
 
21
- console.log(chalk.white(`\n Updating methods...\n`))
24
+ console.log(chalk.bold('\n enet update\n'))
22
25
 
23
- let updated = 0, skipped = 0
26
+ let totalUpdated = 0, totalAdded = 0, totalSkipped = 0
24
27
 
25
28
  for (const method of targets) {
26
- const installPath = getInstallPath(agent, method.id)
27
- if (!await fs.pathExists(installPath)) { skipped++; continue }
29
+ const record = await readInstallRecord(method.id)
28
30
 
29
- const s = ora(`Updating ${method.name}...`).start()
30
- try {
31
- const adapterPath = method.adapters[agent.key] ?? method.adapters.generic
32
- const content = await fetchFromGitHub(method.repo, adapterPath)
33
- await fs.writeFile(installPath, content)
34
- s.succeed(chalk.green(`${method.name} updated`))
35
- updated++
36
- } catch (err) {
37
- s.fail(chalk.yellow(`${method.name} — ${err.message}`))
31
+ if (!record || record.agents.length === 0) {
32
+ totalSkipped++
33
+ console.log(chalk.dim(` ${method.name} not installed, skipping`))
34
+ continue
35
+ }
36
+
37
+ console.log(chalk.white(` ${method.name}`))
38
+ console.log(chalk.dim(` Installed for: ${record.agents.join(', ')}\n`))
39
+
40
+ // Agents already installed for this method
41
+ const installedAgents = record.agents
42
+ .map(key => AGENTS[key] ? { key, ...AGENTS[key] } : null)
43
+ .filter(Boolean)
44
+
45
+ // Agents newly detected but not yet installed for this method
46
+ const newAgents = detectedAgents.filter(a =>
47
+ !record.agents.includes(a.key) &&
48
+ (method.adapters[a.key] || method.adapters['generic'])
49
+ )
50
+
51
+ // 1. Update already-installed adapters
52
+ if (!options.addOnly) {
53
+ for (const agent of installedAgents) {
54
+ const ok = await updateOneAdapter(method, agent, options)
55
+ if (ok) totalUpdated++
56
+ }
57
+ }
58
+
59
+ // 2. Offer to add newly detected agents
60
+ if (newAgents.length > 0 && !options.updateOnly) {
61
+ console.log(chalk.yellow(`\n New agents detected since last install:\n`))
62
+
63
+ let agentsToAdd = []
64
+ if (options.all) {
65
+ agentsToAdd = newAgents
66
+ console.log(chalk.dim(` Adding all: ${newAgents.map(a => a.name).join(', ')}\n`))
67
+ } else {
68
+ agentsToAdd = await checkboxSelectNew(newAgents, method)
69
+ }
70
+
71
+ for (const agent of agentsToAdd) {
72
+ const ok = await installForAgent(method, agent, options, true)
73
+ if (ok) { totalAdded++; record.agents.push(agent.key) }
74
+ }
75
+
76
+ await writeInstallRecord(method.id, { agents: record.agents, version: method.version })
77
+ } else if (newAgents.length === 0 && !options.addOnly) {
78
+ console.log(chalk.dim(` No new agents detected.\n`))
38
79
  }
39
80
  }
40
81
 
82
+ console.log(chalk.dim(' ─────────────────────────────'))
83
+ if (totalUpdated > 0) console.log(chalk.green(` ✓ ${totalUpdated} adapter${totalUpdated !== 1 ? 's' : ''} updated`))
84
+ if (totalAdded > 0) console.log(chalk.green(` ✓ ${totalAdded} new adapter${totalAdded !== 1 ? 's' : ''} installed`))
85
+ if (totalSkipped > 0) console.log(chalk.dim(` ${totalSkipped} method${totalSkipped !== 1 ? 's' : ''} not installed, skipped`))
86
+ if (totalUpdated === 0 && totalAdded === 0 && totalSkipped === 0) {
87
+ console.log(chalk.dim(' Everything is up to date.'))
88
+ }
41
89
  console.log()
42
- if (updated === 0 && skipped > 0) {
43
- console.log(chalk.dim(` No methods installed to update.\n`))
44
- } else {
45
- console.log(chalk.dim(` ${updated} updated, ${skipped} not installed\n`))
90
+ }
91
+
92
+ async function updateOneAdapter(method, agent, options = {}) {
93
+ const isGlobal = options.global || false
94
+ const adapterKey = method.adapters[agent.key] ? agent.key : 'generic'
95
+ const adapterPath = method.adapters[adapterKey]
96
+
97
+ if (!adapterPath) {
98
+ console.log(chalk.dim(` ${agent.name} — no adapter in registry, skipping`))
99
+ return false
100
+ }
101
+
102
+ const installPath = getInstallPath(agent, method.id, { global: isGlobal })
103
+ const exists = await fs.pathExists(installPath)
104
+ const action = exists ? 'Updating' : 'Restoring'
105
+ const s = ora(`${action} ${agent.name}...`).start()
106
+
107
+ try {
108
+ const { fetchFromGitHub } = await import('../utils/registry.js')
109
+ const content = await fetchFromGitHub(method.repo, adapterPath)
110
+ await fs.ensureDir(path.dirname(installPath))
111
+
112
+ if (agent.key === 'windsurf' && exists) {
113
+ const existing = await fs.readFile(installPath, 'utf8')
114
+ if (existing.includes(method.name)) {
115
+ const marker = '\n\n---\n\n'
116
+ const idx = existing.indexOf(method.name)
117
+ const before = existing.substring(0, existing.lastIndexOf(marker, idx) + marker.length)
118
+ await fs.writeFile(installPath, before + content)
119
+ } else {
120
+ await fs.appendFile(installPath, `\n\n---\n\n${content}`)
121
+ }
122
+ } else {
123
+ await fs.writeFile(installPath, content)
124
+ }
125
+
126
+ s.succeed(chalk.green(`${agent.name} — ${action.toLowerCase()}d`))
127
+ console.log(chalk.dim(` → ${installPath}\n`))
128
+ return true
129
+ } catch (err) {
130
+ s.fail(chalk.red(`${agent.name} — ${err.message}`))
131
+ return false
46
132
  }
47
133
  }
134
+
135
+ async function checkboxSelectNew(newAgents, method) {
136
+ const items = newAgents.map(a => ({
137
+ agent: a, checked: true, usesGeneric: !method.adapters[a.key]
138
+ }))
139
+
140
+ return new Promise((resolve) => {
141
+ let cursor = 0
142
+ const lineCount = () => items.length + 4
143
+
144
+ const render = () => {
145
+ if (render.drawn) process.stdout.write(`\x1B[${lineCount()}A`)
146
+ render.drawn = true
147
+ items.forEach((item, i) => {
148
+ const isCursor = i === cursor
149
+ const box = item.checked ? chalk.green('[✓]') : chalk.dim('[ ]')
150
+ const arrow = isCursor ? chalk.cyan(' ❯ ') : ' '
151
+ const name = isCursor ? chalk.white(item.agent.name) : chalk.dim(item.agent.name)
152
+ const tag = item.usesGeneric ? chalk.dim(' (generic adapter)') : ''
153
+ process.stdout.write(`${arrow}${box} ${name}${tag}\n`)
154
+ })
155
+ process.stdout.write('\n')
156
+ process.stdout.write(chalk.dim(' ↑↓ navigate · Space toggle · A all · Enter confirm\n\n'))
157
+ }
158
+
159
+ render()
160
+
161
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
162
+ if (process.stdin.isTTY) process.stdin.setRawMode(true)
163
+ process.stdin.resume()
164
+
165
+ process.stdin.on('data', (key) => {
166
+ const k = key.toString()
167
+ if (k === '\u001b[A') { cursor = (cursor - 1 + items.length) % items.length; render() }
168
+ else if (k === '\u001b[B') { cursor = (cursor + 1) % items.length; render() }
169
+ else if (k === ' ') { items[cursor].checked = !items[cursor].checked; render() }
170
+ else if (k === 'a' || k === 'A') {
171
+ const all = items.every(i => i.checked)
172
+ items.forEach(i => { i.checked = !all }); render()
173
+ }
174
+ else if (k === '\r' || k === '\n') {
175
+ if (process.stdin.isTTY) process.stdin.setRawMode(false)
176
+ process.stdin.pause(); rl.close()
177
+ resolve(items.filter(i => i.checked).map(i => i.agent))
178
+ }
179
+ else if (k === '\u0003') {
180
+ if (process.stdin.isTTY) process.stdin.setRawMode(false)
181
+ process.stdin.pause(); rl.close()
182
+ console.log('\n'); process.exit(0)
183
+ }
184
+ })
185
+ })
186
+ }
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Dónde van estos archivos en el repo
192
+ ```
193
+ exchanet/enet/
194
+ ├── src/
195
+ │ ├── commands/
196
+ │ │ ├── install.js ← cambios A, B, C + checkboxSelect con alreadyInstalled
197
+ │ │ └── update.js ← reemplazar completo
198
+ │ └── utils/
199
+ │ └── registry.js ← añadir al final las 2 funciones nuevas
200
+ ```
201
+
202
+ Una vez subidos, el flujo queda así:
203
+ ```
204
+ $ enet install pdca-t # primera vez
205
+ Already installed for: copilot ← lee .enet/installed.json
206
+
207
+ Select adapters to install:
208
+ ❯ [✓] Cursor — new
209
+ [✓] Antigravity — new
210
+ [✓] Claude Code — new
211
+ [✓] GitHub Copilot — installed ← sabe que ya está
212
+
213
+ $ enet update pdca-t # después de instalar Windsurf
214
+ Method PDCA-T
215
+ Installed for: cursor, claudecode
216
+
217
+ Updating Cursor... ✓ ← re-descarga adapter
218
+ Updating Claude Code... ✓
219
+
220
+ New agents detected since last install:
221
+ ❯ [✓] Windsurf — new
222
+
223
+ → instala Windsurf y actualiza installed.json
@@ -83,3 +83,31 @@ export async function fetchFromGitHub(repo, filePath) {
83
83
 
84
84
  return res.text()
85
85
  }
86
+
87
+ // ── Install state ─────────────────────────────────────────────────────────────
88
+ // Stored in <project>/.enet/installed.json
89
+ // { "pdca-t": { "agents": ["cursor", "claudecode"], "version": "3.0.0", "updatedAt": "..." } }
90
+
91
+ function getInstallRecordFile() {
92
+ return path.join(process.cwd(), '.enet', 'installed.json')
93
+ }
94
+
95
+ export async function readInstallRecord(methodId) {
96
+ try {
97
+ const file = getInstallRecordFile()
98
+ if (!await fs.pathExists(file)) return null
99
+ const data = await fs.readJson(file)
100
+ return data[methodId] ?? null
101
+ } catch { return null }
102
+ }
103
+
104
+ export async function writeInstallRecord(methodId, record) {
105
+ try {
106
+ const file = getInstallRecordFile()
107
+ await fs.ensureDir(path.dirname(file))
108
+ let data = {}
109
+ if (await fs.pathExists(file)) data = await fs.readJson(file).catch(() => ({}))
110
+ data[methodId] = { agents: record.agents, version: record.version ?? null, updatedAt: new Date().toISOString() }
111
+ await fs.writeJson(file, data, { spaces: 2 })
112
+ } catch { /* non-fatal */ }
113
+ }