@exchanet/enet 1.0.11 → 1.0.12

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.11",
3
+ "version": "1.0.12",
4
4
  "description": "enet — exchanet methods manager. Install, scaffold and manage AI coding methods.",
5
5
  "bin": {
6
6
  "enet": "src/index.js"
package/src/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { program } from 'commander'
4
4
  import chalk from 'chalk'
5
+ import { createRequire } from 'module'
5
6
  import { installCommand } from './commands/install.js'
6
7
  import { listCommand } from './commands/list.js'
7
8
  import { initCommand } from './commands/init.js'
@@ -11,10 +12,48 @@ import { updateCommand } from './commands/update.js'
11
12
  import { statusCommand } from './commands/status.js'
12
13
  import { doctorCommand } from './commands/doctor.js'
13
14
 
14
- const VERSION = '1.0.0'
15
+ // Read version from package.json — single source of truth
16
+ const require = createRequire(import.meta.url)
17
+ const pkg = require('../package.json')
18
+ const VERSION = pkg.version
19
+
20
+ // ── Version check ─────────────────────────────────────────────────────────────
21
+ // Runs in background — never blocks the command, never crashes if offline
22
+
23
+ async function checkForUpdate() {
24
+ try {
25
+ const res = await fetch(
26
+ 'https://registry.npmjs.org/@exchanet/enet/latest',
27
+ { signal: AbortSignal.timeout(3000) }
28
+ )
29
+ if (!res.ok) return
30
+ const data = await res.json()
31
+ const latest = data.version
32
+ if (latest && latest !== VERSION) {
33
+ console.log(
34
+ chalk.yellow(' ⚠ Update available: ') +
35
+ chalk.dim(`v${VERSION}`) +
36
+ chalk.white(' → ') +
37
+ chalk.green(`v${latest}`) + '\n' +
38
+ chalk.dim(' Run ') +
39
+ chalk.white('npm install -g @exchanet/enet') +
40
+ chalk.dim(' to update.\n')
41
+ )
42
+ }
43
+ } catch {
44
+ // Offline or npm unreachable — silently skip
45
+ }
46
+ }
47
+
48
+ // Fire in background without await — command runs immediately
49
+ checkForUpdate()
50
+
51
+ // ── Header ────────────────────────────────────────────────────────────────────
15
52
 
16
53
  console.log(chalk.cyan(`\n◆ enet v${VERSION} — exchanet methods manager\n`))
17
54
 
55
+ // ── Commands ──────────────────────────────────────────────────────────────────
56
+
18
57
  program
19
58
  .name('enet')
20
59
  .description('Install, scaffold and manage exchanet AI coding methods')
@@ -23,8 +62,9 @@ program
23
62
  program
24
63
  .command('install <method>')
25
64
  .description('Install a method into the current project')
26
- .option('-a, --agent <agent>', 'Force agent: cursor | windsurf | copilot | generic')
65
+ .option('-a, --agent <agent>', 'Force agent: cursor | windsurf | antigravity | claudecode | copilot | generic')
27
66
  .option('-g, --global', 'Install globally to home directory')
67
+ .option('--all', 'Install for all detected agents without prompting')
28
68
  .action(installCommand)
29
69
 
30
70
  program
@@ -58,8 +98,11 @@ program
58
98
 
59
99
  program
60
100
  .command('update [method]')
61
- .description('Update installed methods to latest version')
62
- .option('--all', 'Update all installed methods')
101
+ .description('Update installed methods and add adapters for new agents')
102
+ .option('--all', 'Add all new agents without prompting')
103
+ .option('--add-only', 'Only add new agents, skip re-downloading existing')
104
+ .option('--update-only', 'Only re-download existing, skip new agent prompt')
105
+ .option('-g, --global', 'Update global install')
63
106
  .action(updateCommand)
64
107
 
65
108
  program
@@ -1,43 +1,60 @@
1
- import fetch from 'node-fetch'
2
1
  import fs from 'fs-extra'
3
2
  import path from 'path'
4
3
  import { fileURLToPath } from 'url'
5
4
 
6
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
- const RAW_BASE = 'https://raw.githubusercontent.com'
6
+ const RAW_BASE = 'https://raw.githubusercontent.com'
8
7
  const REGISTRY_URL = `${RAW_BASE}/exchanet/enet/main/registry.json`
9
8
  const CACHE_FILE = path.join(__dirname, '../../.registry-cache.json')
10
9
  const CACHE_TTL_MS = 1000 * 60 * 60 // 1 hour
11
10
 
12
11
  // ── Registry ──────────────────────────────────────────────────────────────────
13
12
 
13
+ /**
14
+ * Loads the registry from:
15
+ * 1. Remote GitHub (exchanet/enet/registry.json) — always fresh
16
+ * 2. Local cache if remote fails — fallback
17
+ * 3. Bundled registry.json in the package — last resort
18
+ */
14
19
  export async function loadRegistry() {
15
20
  try {
16
- const res = await fetch(REGISTRY_URL, { timeout: 5000 })
21
+ const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(5000) })
17
22
  if (res.ok) {
18
23
  const data = await res.json()
19
24
  await fs.writeJson(CACHE_FILE, { ...data, _cachedAt: Date.now() }).catch(() => {})
20
25
  return data
21
26
  }
22
- } catch { /* network unavailable */ }
27
+ } catch {
28
+ // Network unavailable — fall through to cache
29
+ }
23
30
 
24
31
  try {
25
32
  if (await fs.pathExists(CACHE_FILE)) {
26
33
  const cached = await fs.readJson(CACHE_FILE)
27
34
  const age = Date.now() - (cached._cachedAt || 0)
28
- if (age < CACHE_TTL_MS * 24) return cached
35
+ if (age < CACHE_TTL_MS * 24) {
36
+ return cached
37
+ }
29
38
  }
30
- } catch { /* cache corrupted */ }
39
+ } catch {
40
+ // Cache corrupted — fall through to bundled
41
+ }
31
42
 
32
43
  const bundled = path.join(__dirname, '../../registry.json')
33
44
  return fs.readJson(bundled)
34
45
  }
35
46
 
47
+ /**
48
+ * Returns a single method from the registry, or null if not found.
49
+ */
36
50
  export async function getMethod(id) {
37
51
  const registry = await loadRegistry()
38
52
  return registry.methods?.[id] ?? null
39
53
  }
40
54
 
55
+ /**
56
+ * Returns all methods from the registry as an array.
57
+ */
41
58
  export async function getAllMethods() {
42
59
  const registry = await loadRegistry()
43
60
  return Object.values(registry.methods ?? {})
@@ -45,22 +62,27 @@ export async function getAllMethods() {
45
62
 
46
63
  // ── GitHub file fetcher ───────────────────────────────────────────────────────
47
64
 
65
+ /**
66
+ * Fetches a raw file from a GitHub repo (main branch).
67
+ */
48
68
  export async function fetchFromGitHub(repo, filePath) {
49
69
  const url = `${RAW_BASE}/${repo}/main/${filePath}`
50
70
  const res = await fetch(url)
71
+
51
72
  if (!res.ok) {
52
73
  throw new Error(
53
74
  `Could not fetch ${filePath} from ${repo} (HTTP ${res.status})\n` +
54
75
  ` URL: ${url}`
55
76
  )
56
77
  }
78
+
57
79
  return res.text()
58
80
  }
59
81
 
60
82
  // ── Install state ─────────────────────────────────────────────────────────────
61
83
  //
62
84
  // Tracks which agents have each method installed.
63
- // Stored in <project>/.enet/installed.json
85
+ // Stored in .enet/installed.json in the project root.
64
86
  //
65
87
  // Format:
66
88
  // {
@@ -68,15 +90,19 @@ export async function fetchFromGitHub(repo, filePath) {
68
90
  // "agents": ["cursor", "claudecode", "antigravity"],
69
91
  // "version": "3.0.0",
70
92
  // "updatedAt": "2025-03-01T10:00:00.000Z"
71
- // }
93
+ // },
94
+ // "modular-design": { ... }
72
95
  // }
73
96
 
74
97
  function getInstallRecordFile() {
98
+ // Resolve relative to cwd so it works in any project
75
99
  return path.join(process.cwd(), '.enet', 'installed.json')
76
100
  }
77
101
 
78
102
  /**
79
- * Returns the install record for a single method, or null if never installed.
103
+ * Reads the install record for a single method.
104
+ * Returns { agents: ['cursor', ...], version: '3.0.0', updatedAt: '...' }
105
+ * or null if the method has never been installed.
80
106
  */
81
107
  export async function readInstallRecord(methodId) {
82
108
  try {
@@ -90,22 +116,25 @@ export async function readInstallRecord(methodId) {
90
116
  }
91
117
 
92
118
  /**
93
- * Writes the install record for a single method.
94
- * Merges with existing records — other methods are never touched.
119
+ * Writes (or updates) the install record for a single method.
120
+ * Merges with existing records — other methods are not touched.
95
121
  */
96
122
  export async function writeInstallRecord(methodId, record) {
97
123
  try {
98
124
  const file = getInstallRecordFile()
99
125
  await fs.ensureDir(path.dirname(file))
126
+
100
127
  let data = {}
101
128
  if (await fs.pathExists(file)) {
102
129
  data = await fs.readJson(file).catch(() => ({}))
103
130
  }
131
+
104
132
  data[methodId] = {
105
- agents: record.agents,
106
- version: record.version ?? null,
133
+ agents: record.agents,
134
+ version: record.version ?? null,
107
135
  updatedAt: new Date().toISOString()
108
136
  }
137
+
109
138
  await fs.writeJson(file, data, { spaces: 2 })
110
139
  } catch {
111
140
  // Non-fatal — install works correctly even if state cannot be saved