@nathapp/nax 0.38.1 → 0.38.2

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/dist/nax.js CHANGED
@@ -18346,7 +18346,8 @@ var init_schemas3 = __esm(() => {
18346
18346
  });
18347
18347
  PluginConfigEntrySchema = exports_external.object({
18348
18348
  module: exports_external.string().min(1, "plugin.module must be non-empty"),
18349
- config: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
18349
+ config: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
18350
+ enabled: exports_external.boolean().default(true)
18350
18351
  });
18351
18352
  HooksConfigSchema = exports_external.object({
18352
18353
  skipGlobal: exports_external.boolean().optional(),
@@ -18406,6 +18407,7 @@ var init_schemas3 = __esm(() => {
18406
18407
  context: ContextConfigSchema,
18407
18408
  optimizer: OptimizerConfigSchema.optional(),
18408
18409
  plugins: exports_external.array(PluginConfigEntrySchema).optional(),
18410
+ disabledPlugins: exports_external.array(exports_external.string()).optional(),
18409
18411
  hooks: HooksConfigSchema.optional(),
18410
18412
  interaction: InteractionConfigSchema.optional(),
18411
18413
  precheck: PrecheckConfigSchema.optional(),
@@ -20791,7 +20793,7 @@ var package_default;
20791
20793
  var init_package = __esm(() => {
20792
20794
  package_default = {
20793
20795
  name: "@nathapp/nax",
20794
- version: "0.38.1",
20796
+ version: "0.38.2",
20795
20797
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
20796
20798
  type: "module",
20797
20799
  bin: {
@@ -20855,8 +20857,8 @@ var init_version = __esm(() => {
20855
20857
  NAX_VERSION = package_default.version;
20856
20858
  NAX_COMMIT = (() => {
20857
20859
  try {
20858
- if (/^[0-9a-f]{6,10}$/.test("8a59f16"))
20859
- return "8a59f16";
20860
+ if (/^[0-9a-f]{6,10}$/.test("a4df623"))
20861
+ return "a4df623";
20860
20862
  } catch {}
20861
20863
  try {
20862
20864
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -28144,16 +28146,29 @@ import * as path11 from "path";
28144
28146
  function getSafeLogger6() {
28145
28147
  return getSafeLogger();
28146
28148
  }
28147
- async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot) {
28149
+ function extractPluginName(pluginPath) {
28150
+ const basename2 = path11.basename(pluginPath);
28151
+ if (basename2 === "index.ts" || basename2 === "index.js" || basename2 === "index.mjs") {
28152
+ return path11.basename(path11.dirname(pluginPath));
28153
+ }
28154
+ return basename2.replace(/\.(ts|js|mjs)$/, "");
28155
+ }
28156
+ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
28148
28157
  const loadedPlugins = [];
28149
28158
  const effectiveProjectRoot = projectRoot || projectDir;
28150
28159
  const pluginNames = new Set;
28160
+ const disabledSet = new Set(disabledPlugins ?? []);
28161
+ const logger = getSafeLogger6();
28151
28162
  const globalPlugins = await discoverPlugins(globalDir);
28152
28163
  for (const plugin of globalPlugins) {
28164
+ const pluginName = extractPluginName(plugin.path);
28165
+ if (disabledSet.has(pluginName)) {
28166
+ logger?.info("plugins", `Skipping disabled plugin: '${pluginName}' (global directory)`);
28167
+ continue;
28168
+ }
28153
28169
  const validated = await loadAndValidatePlugin(plugin.path, {}, [globalDir]);
28154
28170
  if (validated) {
28155
28171
  if (pluginNames.has(validated.name)) {
28156
- const logger = getSafeLogger6();
28157
28172
  logger?.warn("plugins", `Plugin name collision: '${validated.name}' (global directory)`);
28158
28173
  }
28159
28174
  loadedPlugins.push({
@@ -28165,10 +28180,14 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot) {
28165
28180
  }
28166
28181
  const projectPlugins = await discoverPlugins(projectDir);
28167
28182
  for (const plugin of projectPlugins) {
28183
+ const pluginName = extractPluginName(plugin.path);
28184
+ if (disabledSet.has(pluginName)) {
28185
+ logger?.info("plugins", `Skipping disabled plugin: '${pluginName}' (project directory)`);
28186
+ continue;
28187
+ }
28168
28188
  const validated = await loadAndValidatePlugin(plugin.path, {}, [projectDir]);
28169
28189
  if (validated) {
28170
28190
  if (pluginNames.has(validated.name)) {
28171
- const logger = getSafeLogger6();
28172
28191
  logger?.warn("plugins", `Plugin name collision: '${validated.name}' (project directory overrides global)`);
28173
28192
  }
28174
28193
  loadedPlugins.push({
@@ -28179,11 +28198,14 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot) {
28179
28198
  }
28180
28199
  }
28181
28200
  for (const entry of configPlugins) {
28201
+ if (entry.enabled === false) {
28202
+ logger?.info("plugins", `Skipping disabled plugin: '${entry.module}'`);
28203
+ continue;
28204
+ }
28182
28205
  const resolvedModule = resolveModulePath(entry.module, effectiveProjectRoot);
28183
28206
  const validated = await loadAndValidatePlugin(resolvedModule, entry.config ?? {}, [globalDir, projectDir, effectiveProjectRoot].filter(Boolean), entry.module);
28184
28207
  if (validated) {
28185
28208
  if (pluginNames.has(validated.name)) {
28186
- const logger = getSafeLogger6();
28187
28209
  logger?.warn("plugins", `Plugin name collision: '${validated.name}' (config entry overrides previous)`);
28188
28210
  }
28189
28211
  loadedPlugins.push({
@@ -31115,10 +31137,10 @@ var init_parallel_executor = __esm(() => {
31115
31137
  // src/pipeline/subscribers/events-writer.ts
31116
31138
  import { appendFile as appendFile2, mkdir } from "fs/promises";
31117
31139
  import { homedir as homedir5 } from "os";
31118
- import { basename as basename2, join as join38 } from "path";
31140
+ import { basename as basename3, join as join38 } from "path";
31119
31141
  function wireEventsWriter(bus, feature, runId, workdir) {
31120
31142
  const logger = getSafeLogger();
31121
- const project = basename2(workdir);
31143
+ const project = basename3(workdir);
31122
31144
  const eventsDir = join38(homedir5(), ".nax", "events", project);
31123
31145
  const eventsFile = join38(eventsDir, "events.jsonl");
31124
31146
  let dirReady = false;
@@ -31280,10 +31302,10 @@ var init_interaction2 = __esm(() => {
31280
31302
  // src/pipeline/subscribers/registry.ts
31281
31303
  import { mkdir as mkdir2, writeFile } from "fs/promises";
31282
31304
  import { homedir as homedir6 } from "os";
31283
- import { basename as basename3, join as join39 } from "path";
31305
+ import { basename as basename4, join as join39 } from "path";
31284
31306
  function wireRegistry(bus, feature, runId, workdir) {
31285
31307
  const logger = getSafeLogger();
31286
- const project = basename3(workdir);
31308
+ const project = basename4(workdir);
31287
31309
  const runDir = join39(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
31288
31310
  const metaFile = join39(runDir, "meta.json");
31289
31311
  const unsub = bus.on("run:started", (_ev) => {
@@ -32650,7 +32672,7 @@ async function setupRun(options) {
32650
32672
  const globalPluginsDir = path17.join(os5.homedir(), ".nax", "plugins");
32651
32673
  const projectPluginsDir = path17.join(workdir, "nax", "plugins");
32652
32674
  const configPlugins = config2.plugins || [];
32653
- const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir);
32675
+ const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
32654
32676
  logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
32655
32677
  plugins: pluginRegistry.plugins.map((p) => ({ name: p.name, version: p.version, provides: p.provides }))
32656
32678
  });
@@ -65166,7 +65188,7 @@ async function pluginsListCommand(config2, workdir, overrideGlobalPluginsDir) {
65166
65188
  const globalPluginsDir = overrideGlobalPluginsDir ?? path12.join(os2.homedir(), ".nax", "plugins");
65167
65189
  const projectPluginsDir = path12.join(workdir, "nax", "plugins");
65168
65190
  const configPlugins = config2.plugins || [];
65169
- const registry2 = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir);
65191
+ const registry2 = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
65170
65192
  const plugins = registry2.plugins;
65171
65193
  if (plugins.length === 0) {
65172
65194
  console.log("No plugins installed.");
@@ -65179,29 +65201,33 @@ To install plugins:`);
65179
65201
  See https://github.com/nax/nax#plugins for more details.`);
65180
65202
  return;
65181
65203
  }
65204
+ const disabledSet = new Set(config2.disabledPlugins ?? []);
65182
65205
  const rows = plugins.map((plugin) => {
65183
65206
  const source = registry2.getSource(plugin.name);
65184
65207
  const sourceStr = source ? formatSource(source.type, source.path) : "unknown";
65208
+ const isDisabled = disabledSet.has(plugin.name);
65185
65209
  return {
65186
65210
  name: plugin.name,
65187
65211
  version: plugin.version,
65188
65212
  provides: plugin.provides.join(", "),
65189
- source: sourceStr
65213
+ source: sourceStr,
65214
+ enabled: isDisabled ? "disabled" : "enabled"
65190
65215
  };
65191
65216
  });
65192
65217
  const widths = {
65193
65218
  name: Math.max(4, ...rows.map((r) => r.name.length)),
65194
65219
  version: Math.max(7, ...rows.map((r) => r.version.length)),
65195
65220
  provides: Math.max(8, ...rows.map((r) => r.provides.length)),
65196
- source: Math.max(6, ...rows.map((r) => r.source.length))
65221
+ source: Math.max(6, ...rows.map((r) => r.source.length)),
65222
+ enabled: Math.max(7, ...rows.map((r) => r.enabled.length))
65197
65223
  };
65198
65224
  console.log(`
65199
65225
  Installed Plugins:
65200
65226
  `);
65201
- console.log(`${pad("Name", widths.name)} ${pad("Version", widths.version)} ${pad("Provides", widths.provides)} ${pad("Source", widths.source)}`);
65202
- console.log(`${"-".repeat(widths.name)} ${"-".repeat(widths.version)} ${"-".repeat(widths.provides)} ${"-".repeat(widths.source)}`);
65227
+ console.log(`${pad("Name", widths.name)} ${pad("Version", widths.version)} ${pad("Provides", widths.provides)} ${pad("Source", widths.source)} ${pad("Status", widths.enabled)}`);
65228
+ console.log(`${"-".repeat(widths.name)} ${"-".repeat(widths.version)} ${"-".repeat(widths.provides)} ${"-".repeat(widths.source)} ${"-".repeat(widths.enabled)}`);
65203
65229
  for (const row of rows) {
65204
- console.log(`${pad(row.name, widths.name)} ${pad(row.version, widths.version)} ${pad(row.provides, widths.provides)} ${pad(row.source, widths.source)}`);
65230
+ console.log(`${pad(row.name, widths.name)} ${pad(row.version, widths.version)} ${pad(row.provides, widths.provides)} ${pad(row.source, widths.source)} ${pad(row.enabled, widths.enabled)}`);
65205
65231
  }
65206
65232
  console.log();
65207
65233
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.38.1",
4
- "description": "AI Coding Agent Orchestrator \u2014 loops until done",
3
+ "version": "0.38.2",
4
+ "description": "AI Coding Agent Orchestrator loops until done",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "nax": "./dist/nax.js"
@@ -25,7 +25,13 @@ export async function pluginsListCommand(
25
25
  const globalPluginsDir = overrideGlobalPluginsDir ?? path.join(os.homedir(), ".nax", "plugins");
26
26
  const projectPluginsDir = path.join(workdir, "nax", "plugins");
27
27
  const configPlugins = config.plugins || [];
28
- const registry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir);
28
+ const registry = await loadPlugins(
29
+ globalPluginsDir,
30
+ projectPluginsDir,
31
+ configPlugins,
32
+ workdir,
33
+ config.disabledPlugins,
34
+ );
29
35
  const plugins = registry.plugins;
30
36
 
31
37
  if (plugins.length === 0) {
@@ -39,20 +45,24 @@ export async function pluginsListCommand(
39
45
  }
40
46
 
41
47
  // Build table data
48
+ const disabledSet = new Set(config.disabledPlugins ?? []);
42
49
  const rows: Array<{
43
50
  name: string;
44
51
  version: string;
45
52
  provides: string;
46
53
  source: string;
54
+ enabled: string;
47
55
  }> = plugins.map((plugin) => {
48
56
  const source = registry.getSource(plugin.name);
49
57
  const sourceStr = source ? formatSource(source.type, source.path) : "unknown";
58
+ const isDisabled = disabledSet.has(plugin.name);
50
59
 
51
60
  return {
52
61
  name: plugin.name,
53
62
  version: plugin.version,
54
63
  provides: plugin.provides.join(", "),
55
64
  source: sourceStr,
65
+ enabled: isDisabled ? "disabled" : "enabled",
56
66
  };
57
67
  });
58
68
 
@@ -62,20 +72,21 @@ export async function pluginsListCommand(
62
72
  version: Math.max(7, ...rows.map((r) => r.version.length)),
63
73
  provides: Math.max(8, ...rows.map((r) => r.provides.length)),
64
74
  source: Math.max(6, ...rows.map((r) => r.source.length)),
75
+ enabled: Math.max(7, ...rows.map((r) => r.enabled.length)),
65
76
  };
66
77
 
67
78
  // Display table
68
79
  console.log("\nInstalled Plugins:\n");
69
80
  console.log(
70
- `${pad("Name", widths.name)} ${pad("Version", widths.version)} ${pad("Provides", widths.provides)} ${pad("Source", widths.source)}`,
81
+ `${pad("Name", widths.name)} ${pad("Version", widths.version)} ${pad("Provides", widths.provides)} ${pad("Source", widths.source)} ${pad("Status", widths.enabled)}`,
71
82
  );
72
83
  console.log(
73
- `${"-".repeat(widths.name)} ${"-".repeat(widths.version)} ${"-".repeat(widths.provides)} ${"-".repeat(widths.source)}`,
84
+ `${"-".repeat(widths.name)} ${"-".repeat(widths.version)} ${"-".repeat(widths.provides)} ${"-".repeat(widths.source)} ${"-".repeat(widths.enabled)}`,
74
85
  );
75
86
 
76
87
  for (const row of rows) {
77
88
  console.log(
78
- `${pad(row.name, widths.name)} ${pad(row.version, widths.version)} ${pad(row.provides, widths.provides)} ${pad(row.source, widths.source)}`,
89
+ `${pad(row.name, widths.name)} ${pad(row.version, widths.version)} ${pad(row.provides, widths.provides)} ${pad(row.source, widths.source)} ${pad(row.enabled, widths.enabled)}`,
79
90
  );
80
91
  }
81
92
 
@@ -267,6 +267,7 @@ export interface OptimizerConfig {
267
267
  export interface PluginConfigEntry {
268
268
  module: string;
269
269
  config?: Record<string, unknown>;
270
+ enabled?: boolean;
270
271
  }
271
272
 
272
273
  export interface HooksConfig {
@@ -435,6 +436,8 @@ export interface NaxConfig {
435
436
  optimizer?: OptimizerConfig;
436
437
  /** Plugin configurations (v0.10) */
437
438
  plugins?: PluginConfigEntry[];
439
+ /** Disabled plugin names (v0.38.2) */
440
+ disabledPlugins?: string[];
438
441
  /** Hooks configuration (v0.10) */
439
442
  hooks?: HooksConfig;
440
443
  /** Interaction settings (v0.15.0) */
@@ -251,6 +251,7 @@ const OptimizerConfigSchema = z.object({
251
251
  const PluginConfigEntrySchema = z.object({
252
252
  module: z.string().min(1, "plugin.module must be non-empty"),
253
253
  config: z.record(z.string(), z.unknown()).optional(),
254
+ enabled: z.boolean().default(true),
254
255
  });
255
256
 
256
257
  const HooksConfigSchema = z.object({
@@ -330,6 +331,7 @@ export const NaxConfigSchema = z
330
331
  context: ContextConfigSchema,
331
332
  optimizer: OptimizerConfigSchema.optional(),
332
333
  plugins: z.array(PluginConfigEntrySchema).optional(),
334
+ disabledPlugins: z.array(z.string()).optional(),
333
335
  hooks: HooksConfigSchema.optional(),
334
336
  interaction: InteractionConfigSchema.optional(),
335
337
  precheck: PrecheckConfigSchema.optional(),
@@ -171,7 +171,13 @@ export async function setupRun(options: RunSetupOptions): Promise<RunSetupResult
171
171
  const globalPluginsDir = path.join(os.homedir(), ".nax", "plugins");
172
172
  const projectPluginsDir = path.join(workdir, "nax", "plugins");
173
173
  const configPlugins = config.plugins || [];
174
- const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir);
174
+ const pluginRegistry = await loadPlugins(
175
+ globalPluginsDir,
176
+ projectPluginsDir,
177
+ configPlugins,
178
+ workdir,
179
+ config.disabledPlugins,
180
+ );
175
181
 
176
182
  // Log plugins loaded
177
183
  logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
@@ -50,6 +50,24 @@ export interface PluginSource {
50
50
  path: string;
51
51
  }
52
52
 
53
+ /**
54
+ * Extract plugin name from file path.
55
+ * For index files (e.g., /path/to/plugin/index.ts), returns the parent directory name.
56
+ * For single files (e.g., /path/to/plugin.ts), returns the filename without extension.
57
+ *
58
+ * @param pluginPath - Path to plugin file
59
+ * @returns Plugin name
60
+ */
61
+ function extractPluginName(pluginPath: string): string {
62
+ const basename = path.basename(pluginPath);
63
+ if (basename === "index.ts" || basename === "index.js" || basename === "index.mjs") {
64
+ // For index files, use the parent directory name
65
+ return path.basename(path.dirname(pluginPath));
66
+ }
67
+ // For single files, use filename without extension
68
+ return basename.replace(/\.(ts|js|mjs)$/, "");
69
+ }
70
+
53
71
  /**
54
72
  * Plugin with source information.
55
73
  */
@@ -67,11 +85,13 @@ export interface LoadedPlugin {
67
85
  * 3. Load explicit modules from config.plugins[]
68
86
  *
69
87
  * Each plugin is validated, then setup() is called with its config.
88
+ * Plugins can be disabled via config.plugins[].enabled or config.disabledPlugins[].
70
89
  *
71
90
  * @param globalDir - Global plugins directory (e.g., ~/.nax/plugins)
72
91
  * @param projectDir - Project plugins directory (e.g., <project>/nax/plugins)
73
92
  * @param configPlugins - Explicit plugin entries from config
74
93
  * @param projectRoot - Project root directory for resolving relative paths in config
94
+ * @param disabledPlugins - List of plugin names to disable (auto-discovered plugins only)
75
95
  * @returns PluginRegistry with all loaded plugins and their sources
76
96
  */
77
97
  export async function loadPlugins(
@@ -79,18 +99,25 @@ export async function loadPlugins(
79
99
  projectDir: string,
80
100
  configPlugins: PluginConfigEntry[],
81
101
  projectRoot?: string,
102
+ disabledPlugins?: string[],
82
103
  ): Promise<PluginRegistry> {
83
104
  const loadedPlugins: LoadedPlugin[] = [];
84
105
  const effectiveProjectRoot = projectRoot || projectDir;
85
106
  const pluginNames = new Set<string>();
107
+ const disabledSet = new Set(disabledPlugins ?? []);
108
+ const logger = getSafeLogger();
86
109
 
87
110
  // 1. Load plugins from global directory
88
111
  const globalPlugins = await discoverPlugins(globalDir);
89
112
  for (const plugin of globalPlugins) {
113
+ const pluginName = extractPluginName(plugin.path);
114
+ if (disabledSet.has(pluginName)) {
115
+ logger?.info("plugins", `Skipping disabled plugin: '${pluginName}' (global directory)`);
116
+ continue;
117
+ }
90
118
  const validated = await loadAndValidatePlugin(plugin.path, {}, [globalDir]);
91
119
  if (validated) {
92
120
  if (pluginNames.has(validated.name)) {
93
- const logger = getSafeLogger();
94
121
  logger?.warn("plugins", `Plugin name collision: '${validated.name}' (global directory)`);
95
122
  }
96
123
  loadedPlugins.push({
@@ -104,10 +131,14 @@ export async function loadPlugins(
104
131
  // 2. Load plugins from project directory
105
132
  const projectPlugins = await discoverPlugins(projectDir);
106
133
  for (const plugin of projectPlugins) {
134
+ const pluginName = extractPluginName(plugin.path);
135
+ if (disabledSet.has(pluginName)) {
136
+ logger?.info("plugins", `Skipping disabled plugin: '${pluginName}' (project directory)`);
137
+ continue;
138
+ }
107
139
  const validated = await loadAndValidatePlugin(plugin.path, {}, [projectDir]);
108
140
  if (validated) {
109
141
  if (pluginNames.has(validated.name)) {
110
- const logger = getSafeLogger();
111
142
  logger?.warn("plugins", `Plugin name collision: '${validated.name}' (project directory overrides global)`);
112
143
  }
113
144
  loadedPlugins.push({
@@ -120,6 +151,11 @@ export async function loadPlugins(
120
151
 
121
152
  // 3. Load plugins from config entries
122
153
  for (const entry of configPlugins) {
154
+ // Check if plugin is explicitly disabled in config
155
+ if (entry.enabled === false) {
156
+ logger?.info("plugins", `Skipping disabled plugin: '${entry.module}'`);
157
+ continue;
158
+ }
123
159
  // Resolve module path relative to effective project root for relative paths
124
160
  const resolvedModule = resolveModulePath(entry.module, effectiveProjectRoot);
125
161
  const validated = await loadAndValidatePlugin(
@@ -130,7 +166,6 @@ export async function loadPlugins(
130
166
  );
131
167
  if (validated) {
132
168
  if (pluginNames.has(validated.name)) {
133
- const logger = getSafeLogger();
134
169
  logger?.warn("plugins", `Plugin name collision: '${validated.name}' (config entry overrides previous)`);
135
170
  }
136
171
  loadedPlugins.push({
@@ -201,4 +201,6 @@ export interface PluginConfigEntry {
201
201
  module: string;
202
202
  /** Plugin-specific configuration */
203
203
  config?: Record<string, unknown>;
204
+ /** Whether this plugin is enabled (default: true) */
205
+ enabled?: boolean;
204
206
  }