@exchanet/enet 1.0.6 → 1.0.7
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 +91 -42
- package/src/utils/agent-detector.js +84 -27
package/package.json
CHANGED
package/src/commands/install.js
CHANGED
|
@@ -2,11 +2,11 @@ import chalk from 'chalk'
|
|
|
2
2
|
import ora from 'ora'
|
|
3
3
|
import fs from 'fs-extra'
|
|
4
4
|
import path from 'path'
|
|
5
|
-
import {
|
|
6
|
-
import { getMethod } from '../utils/registry.js'
|
|
7
|
-
import { fetchFromGitHub } from '../utils/registry.js'
|
|
5
|
+
import { detectSystemAgents, getInstallPath, AGENTS } from '../utils/agent-detector.js'
|
|
6
|
+
import { getMethod, fetchFromGitHub } from '../utils/registry.js'
|
|
8
7
|
|
|
9
8
|
export async function installCommand(methodId, options) {
|
|
9
|
+
// Load method from registry
|
|
10
10
|
const spinner = ora('Fetching registry...').start()
|
|
11
11
|
const method = await getMethod(methodId).catch(() => null)
|
|
12
12
|
spinner.stop()
|
|
@@ -17,40 +17,92 @@ export async function installCommand(methodId, options) {
|
|
|
17
17
|
process.exit(1)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
//
|
|
21
|
-
let
|
|
20
|
+
// Determine target agents
|
|
21
|
+
let targetAgents = []
|
|
22
|
+
|
|
22
23
|
if (options.agent) {
|
|
24
|
+
// --agent flag: install for a specific agent only
|
|
23
25
|
if (!AGENTS[options.agent]) {
|
|
24
26
|
console.log(chalk.red(` ✗ Unknown agent: "${options.agent}"`))
|
|
25
|
-
console.log(chalk.dim(` Valid:
|
|
27
|
+
console.log(chalk.dim(` Valid: ${Object.keys(AGENTS).filter(k => k !== 'generic').join(', ')}\n`))
|
|
26
28
|
process.exit(1)
|
|
27
29
|
}
|
|
28
|
-
|
|
30
|
+
targetAgents = [{ key: options.agent, ...AGENTS[options.agent] }]
|
|
31
|
+
|
|
29
32
|
} else {
|
|
30
|
-
|
|
33
|
+
// Detect all agents installed on the system
|
|
34
|
+
const detected = await detectSystemAgents()
|
|
35
|
+
|
|
36
|
+
if (detected.length === 0) {
|
|
37
|
+
console.log(chalk.yellow(' ⚠ No AI agents detected on this system.'))
|
|
38
|
+
console.log(chalk.dim(` Use ${chalk.white('--agent <name>')} to force an agent.`))
|
|
39
|
+
console.log(chalk.dim(` Valid: cursor, windsurf, antigravity, claudecode, copilot, generic\n`))
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (detected.length === 1 || options.global) {
|
|
44
|
+
// Only one detected, or --global flag: install for all without asking
|
|
45
|
+
targetAgents = detected
|
|
46
|
+
} else {
|
|
47
|
+
// Multiple agents detected: ask the user
|
|
48
|
+
console.log(chalk.white(`\n Detected ${detected.length} agents on this system:\n`))
|
|
49
|
+
detected.forEach((a, i) => console.log(chalk.dim(` [${i + 1}] ${a.name}`)))
|
|
50
|
+
console.log(chalk.dim(` [${detected.length + 1}] All of the above\n`))
|
|
51
|
+
|
|
52
|
+
const { createInterface } = await import('readline')
|
|
53
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
54
|
+
const answer = await new Promise(resolve => {
|
|
55
|
+
rl.question(chalk.white(' Install for which agent(s)? '), resolve)
|
|
56
|
+
})
|
|
57
|
+
rl.close()
|
|
58
|
+
|
|
59
|
+
const choice = parseInt(answer.trim())
|
|
60
|
+
if (choice === detected.length + 1) {
|
|
61
|
+
targetAgents = detected
|
|
62
|
+
} else if (choice >= 1 && choice <= detected.length) {
|
|
63
|
+
targetAgents = [detected[choice - 1]]
|
|
64
|
+
} else {
|
|
65
|
+
console.log(chalk.red('\n Invalid choice. Cancelled.\n'))
|
|
66
|
+
process.exit(1)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
31
69
|
}
|
|
32
70
|
|
|
33
|
-
console.log(
|
|
34
|
-
console.log(chalk.dim(` Agent : ${chalk.white(agent.name)}${options.agent ? '' : chalk.dim(' (auto-detected)')}`))
|
|
35
|
-
console.log(chalk.dim(` Source : ${chalk.white(`github.com/${method.repo}`)}\n`))
|
|
71
|
+
console.log()
|
|
36
72
|
|
|
37
|
-
|
|
73
|
+
// Install for each target agent
|
|
74
|
+
let schemaInstalled = false
|
|
75
|
+
for (const agent of targetAgents) {
|
|
76
|
+
await installForAgent(method, agent, options, schemaInstalled)
|
|
77
|
+
schemaInstalled = true // only install schema once
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
printHints(methodId)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function installForAgent(method, agent, options, skipExtras = false) {
|
|
84
|
+
const isGlobal = options.global || false
|
|
85
|
+
const adapterKey = method.adapters[agent.key] ? agent.key : 'generic'
|
|
86
|
+
const adapterPath = method.adapters[adapterKey]
|
|
87
|
+
|
|
88
|
+
if (isGlobal && !agent.globalInstallDir) {
|
|
89
|
+
console.log(chalk.yellow(` ⚠ ${agent.name} does not support global install — skipping`))
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const spinner = ora(`Installing for ${agent.name}${isGlobal ? ' (global)' : ''}...`).start()
|
|
38
94
|
|
|
39
95
|
try {
|
|
40
|
-
const adapterPath = method.adapters[agent.key] ?? method.adapters.generic
|
|
41
96
|
const content = await fetchFromGitHub(method.repo, adapterPath)
|
|
42
|
-
|
|
43
|
-
const installPath = options.global
|
|
44
|
-
? path.join(process.env.HOME || process.env.USERPROFILE, '.enet', `${methodId}.md`)
|
|
45
|
-
: getInstallPath(agent, methodId)
|
|
97
|
+
const installPath = getInstallPath(agent, method.id, { global: isGlobal })
|
|
46
98
|
|
|
47
99
|
await fs.ensureDir(path.dirname(installPath))
|
|
48
100
|
|
|
49
|
-
// Windsurf appends
|
|
101
|
+
// Windsurf global appends to global_rules.md
|
|
50
102
|
if (agent.key === 'windsurf' && await fs.pathExists(installPath)) {
|
|
51
103
|
const existing = await fs.readFile(installPath, 'utf8')
|
|
52
104
|
if (existing.includes(method.name)) {
|
|
53
|
-
|
|
105
|
+
spinner.warn(chalk.yellow(`${agent.name} — already installed`))
|
|
54
106
|
return
|
|
55
107
|
}
|
|
56
108
|
await fs.appendFile(installPath, `\n\n---\n\n${content}`)
|
|
@@ -58,32 +110,29 @@ export async function installCommand(methodId, options) {
|
|
|
58
110
|
await fs.writeFile(installPath, content)
|
|
59
111
|
}
|
|
60
112
|
|
|
61
|
-
|
|
62
|
-
console.log(chalk.dim(` → ${
|
|
113
|
+
spinner.succeed(chalk.green(`${agent.name} — installed`))
|
|
114
|
+
console.log(chalk.dim(` → ${installPath}`))
|
|
63
115
|
console.log(chalk.dim(` ${agent.configNote}\n`))
|
|
64
116
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
117
|
+
} catch (err) {
|
|
118
|
+
spinner.fail(chalk.red(`${agent.name} — ${err.message}`))
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Download extras (schema, etc.) once
|
|
123
|
+
if (!skipExtras && method.extras) {
|
|
124
|
+
for (const [key, filePath] of Object.entries(method.extras)) {
|
|
125
|
+
const extraSpinner = ora(`Fetching ${key}...`).start()
|
|
126
|
+
try {
|
|
127
|
+
const extraContent = await fetchFromGitHub(method.repo, filePath)
|
|
128
|
+
const extraOut = path.join(process.cwd(), path.basename(filePath))
|
|
129
|
+
await fs.writeFile(extraOut, extraContent)
|
|
130
|
+
extraSpinner.succeed(chalk.dim(`${key} → ${path.basename(filePath)}`))
|
|
131
|
+
} catch {
|
|
132
|
+
extraSpinner.warn(chalk.dim(`${key} not available (non-critical)`))
|
|
77
133
|
}
|
|
78
|
-
console.log()
|
|
79
134
|
}
|
|
80
|
-
|
|
81
|
-
printHints(methodId)
|
|
82
|
-
|
|
83
|
-
} catch (err) {
|
|
84
|
-
fetchSpinner.fail(chalk.red(`Failed: ${err.message}`))
|
|
85
|
-
console.log(chalk.dim(' Check your internet connection and try again.\n'))
|
|
86
|
-
process.exit(1)
|
|
135
|
+
console.log()
|
|
87
136
|
}
|
|
88
137
|
}
|
|
89
138
|
|
|
@@ -95,7 +144,7 @@ function printHints(methodId) {
|
|
|
95
144
|
console.log(chalk.dim(` 3. Agent builds Core → modules → Admin Panel`))
|
|
96
145
|
console.log()
|
|
97
146
|
console.log(chalk.dim(` ${chalk.white('enet new module <name>')} scaffold your first module`))
|
|
98
|
-
console.log(chalk.dim(` ${chalk.white('enet validate')}
|
|
147
|
+
console.log(chalk.dim(` ${chalk.white('enet validate')} check manifests at any time\n`))
|
|
99
148
|
}
|
|
100
149
|
if (methodId === 'pdca-t') {
|
|
101
150
|
console.log(chalk.dim(` PDCA-T adds quality validation to your workflow.`))
|
|
@@ -1,70 +1,127 @@
|
|
|
1
1
|
import fs from 'fs-extra'
|
|
2
2
|
import path from 'path'
|
|
3
|
+
import os from 'os'
|
|
4
|
+
|
|
5
|
+
const HOME = os.homedir()
|
|
3
6
|
|
|
4
7
|
export const AGENTS = {
|
|
5
8
|
cursor: {
|
|
6
9
|
name: 'Cursor',
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
systemSignals: [
|
|
11
|
+
path.join(HOME, '.cursor')
|
|
12
|
+
],
|
|
13
|
+
projectSignals: ['.cursor/rules', '.cursor'],
|
|
14
|
+
projectInstallDir: '.cursor/rules',
|
|
15
|
+
globalInstallDir: path.join(HOME, '.cursor', 'rules'),
|
|
9
16
|
filename: 'enet-{id}.md',
|
|
10
17
|
configNote: 'Rule auto-applies to all files (alwaysApply: true)'
|
|
11
18
|
},
|
|
19
|
+
windsurf: {
|
|
20
|
+
name: 'Windsurf',
|
|
21
|
+
systemSignals: [
|
|
22
|
+
path.join(HOME, '.codeium', 'windsurf')
|
|
23
|
+
],
|
|
24
|
+
projectSignals: ['.windsurfrules', '.windsurf'],
|
|
25
|
+
projectInstallDir: '.',
|
|
26
|
+
globalInstallDir: path.join(HOME, '.codeium', 'windsurf', 'memories'),
|
|
27
|
+
globalFilename: 'global_rules.md',
|
|
28
|
+
filename: '.windsurfrules',
|
|
29
|
+
configNote: 'Appended to global_rules.md'
|
|
30
|
+
},
|
|
12
31
|
antigravity: {
|
|
13
32
|
name: 'Antigravity (Google)',
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
systemSignals: [
|
|
34
|
+
path.join(HOME, '.gemini', 'antigravity')
|
|
35
|
+
],
|
|
36
|
+
projectSignals: ['.agent/rules', '.agent'],
|
|
37
|
+
projectInstallDir: '.agent/rules',
|
|
38
|
+
globalInstallDir: path.join(HOME, '.gemini', 'antigravity', 'skills', 'method-modular-design'),
|
|
39
|
+
globalFilename: 'SKILL.md',
|
|
16
40
|
filename: 'enet-{id}.md',
|
|
17
|
-
configNote: '
|
|
41
|
+
configNote: 'Skill saved — set activation to Always On in Antigravity'
|
|
18
42
|
},
|
|
19
43
|
claudecode: {
|
|
20
44
|
name: 'Claude Code',
|
|
21
|
-
|
|
22
|
-
|
|
45
|
+
systemSignals: [
|
|
46
|
+
path.join(HOME, '.claude')
|
|
47
|
+
],
|
|
48
|
+
projectSignals: ['CLAUDE.md', '.claude'],
|
|
49
|
+
projectInstallDir: '.',
|
|
50
|
+
globalInstallDir: path.join(HOME, '.claude'),
|
|
51
|
+
globalFilename: 'CLAUDE.md',
|
|
23
52
|
filename: 'CLAUDE.md',
|
|
24
|
-
configNote: 'Written to CLAUDE.md — Claude Code reads this
|
|
25
|
-
},
|
|
26
|
-
windsurf: {
|
|
27
|
-
name: 'Windsurf',
|
|
28
|
-
signals: ['.windsurfrules', '.windsurf'],
|
|
29
|
-
installDir: '.',
|
|
30
|
-
filename: '.windsurfrules',
|
|
31
|
-
configNote: 'Appended to .windsurfrules'
|
|
53
|
+
configNote: 'Written to ~/.claude/CLAUDE.md — Claude Code reads this automatically'
|
|
32
54
|
},
|
|
33
55
|
copilot: {
|
|
34
56
|
name: 'GitHub Copilot',
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
systemSignals: [],
|
|
58
|
+
projectSignals: ['.github/copilot-instructions.md'],
|
|
59
|
+
projectInstallDir: '.github',
|
|
60
|
+
globalInstallDir: null,
|
|
37
61
|
filename: 'copilot-instructions.md',
|
|
38
62
|
configNote: 'Written to .github/copilot-instructions.md'
|
|
39
63
|
},
|
|
40
64
|
generic: {
|
|
41
65
|
name: 'Generic Agent',
|
|
42
|
-
|
|
43
|
-
|
|
66
|
+
systemSignals: [],
|
|
67
|
+
projectSignals: [],
|
|
68
|
+
projectInstallDir: '.enet',
|
|
69
|
+
globalInstallDir: null,
|
|
44
70
|
filename: '{id}.md',
|
|
45
71
|
configNote: 'Saved to .enet/ — paste contents into your agent\'s context'
|
|
46
72
|
}
|
|
47
73
|
}
|
|
48
74
|
|
|
49
75
|
/**
|
|
50
|
-
* Detects
|
|
76
|
+
* Detects ALL agents installed on the system by checking known global paths.
|
|
51
77
|
*/
|
|
52
|
-
export async function
|
|
78
|
+
export async function detectSystemAgents() {
|
|
79
|
+
const found = []
|
|
53
80
|
for (const [key, agent] of Object.entries(AGENTS)) {
|
|
54
81
|
if (key === 'generic') continue
|
|
55
|
-
for (const signal of agent.
|
|
82
|
+
for (const signal of agent.systemSignals) {
|
|
83
|
+
if (await fs.pathExists(signal)) {
|
|
84
|
+
found.push({ key, ...agent })
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return found
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Detects ALL agents present in the current project.
|
|
94
|
+
*/
|
|
95
|
+
export async function detectProjectAgents(cwd = process.cwd()) {
|
|
96
|
+
const found = []
|
|
97
|
+
for (const [key, agent] of Object.entries(AGENTS)) {
|
|
98
|
+
if (key === 'generic') continue
|
|
99
|
+
for (const signal of agent.projectSignals) {
|
|
56
100
|
if (await fs.pathExists(path.join(cwd, signal))) {
|
|
57
|
-
|
|
101
|
+
found.push({ key, ...agent })
|
|
102
|
+
break
|
|
58
103
|
}
|
|
59
104
|
}
|
|
60
105
|
}
|
|
61
|
-
return
|
|
106
|
+
return found
|
|
62
107
|
}
|
|
63
108
|
|
|
64
109
|
/**
|
|
65
|
-
* Returns the
|
|
110
|
+
* Returns the first detected agent (legacy, used by status/doctor).
|
|
66
111
|
*/
|
|
67
|
-
export function
|
|
112
|
+
export async function detectAgent(cwd = process.cwd()) {
|
|
113
|
+
const agents = await detectProjectAgents(cwd)
|
|
114
|
+
return agents[0] ?? { key: 'generic', ...AGENTS.generic }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Returns the install path for a method adapter.
|
|
119
|
+
*/
|
|
120
|
+
export function getInstallPath(agent, methodId, { global = false, cwd = process.cwd() } = {}) {
|
|
121
|
+
if (global) {
|
|
122
|
+
const filename = agent.globalFilename ?? agent.filename.replace('{id}', methodId)
|
|
123
|
+
return path.join(agent.globalInstallDir, filename)
|
|
124
|
+
}
|
|
68
125
|
const filename = agent.filename.replace('{id}', methodId)
|
|
69
|
-
return path.join(cwd, agent.
|
|
126
|
+
return path.join(cwd, agent.projectInstallDir, filename)
|
|
70
127
|
}
|