@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 +1 -1
- package/src/commands/install.js +5 -0
- package/src/commands/update.js +196 -20
- package/src/utils/registry.js +28 -0
package/package.json
CHANGED
package/src/commands/install.js
CHANGED
|
@@ -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 = []
|
package/src/commands/update.js
CHANGED
|
@@ -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
|
|
5
|
-
import
|
|
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,
|
|
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.
|
|
24
|
+
console.log(chalk.bold('\n ◆ enet update\n'))
|
|
22
25
|
|
|
23
|
-
let
|
|
26
|
+
let totalUpdated = 0, totalAdded = 0, totalSkipped = 0
|
|
24
27
|
|
|
25
28
|
for (const method of targets) {
|
|
26
|
-
const
|
|
27
|
-
if (!await fs.pathExists(installPath)) { skipped++; continue }
|
|
29
|
+
const record = await readInstallRecord(method.id)
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
package/src/utils/registry.js
CHANGED
|
@@ -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
|
+
}
|