@nexrall/code-core 1.4.46 → 1.4.48

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.
Files changed (38) hide show
  1. package/dist/agent/agentTypes.d.ts.map +1 -1
  2. package/dist/agent/agentTypes.js +12 -15
  3. package/dist/agent/loop.d.ts.map +1 -1
  4. package/dist/agent/loop.js +14 -1
  5. package/dist/agent/memory.d.ts +38 -10
  6. package/dist/agent/memory.d.ts.map +1 -1
  7. package/dist/agent/memory.js +223 -67
  8. package/dist/agent/skills.d.ts +34 -4
  9. package/dist/agent/skills.d.ts.map +1 -1
  10. package/dist/agent/skills.js +111 -38
  11. package/dist/agent/trust.d.ts +7 -0
  12. package/dist/agent/trust.d.ts.map +1 -0
  13. package/dist/agent/trust.js +163 -0
  14. package/dist/api/client.d.ts +30 -0
  15. package/dist/api/client.d.ts.map +1 -1
  16. package/dist/api/client.js +52 -0
  17. package/dist/checkpoint/manager.d.ts.map +1 -1
  18. package/dist/checkpoint/manager.js +6 -0
  19. package/dist/commands/loader.d.ts.map +1 -1
  20. package/dist/commands/loader.js +2 -13
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/permissions/modePolicy.d.ts.map +1 -1
  25. package/dist/permissions/modePolicy.js +1 -0
  26. package/dist/permissions/rules.js +1 -1
  27. package/dist/plugins/index.d.ts +31 -4
  28. package/dist/plugins/index.d.ts.map +1 -1
  29. package/dist/plugins/index.js +72 -5
  30. package/dist/plugins/installer.d.ts +47 -0
  31. package/dist/plugins/installer.d.ts.map +1 -1
  32. package/dist/plugins/installer.js +173 -0
  33. package/dist/tools/executor.d.ts.map +1 -1
  34. package/dist/tools/executor.js +196 -5
  35. package/dist/util/frontmatter.d.ts +79 -0
  36. package/dist/util/frontmatter.d.ts.map +1 -0
  37. package/dist/util/frontmatter.js +172 -0
  38. package/package.json +5 -2
@@ -42,6 +42,7 @@ const path = __importStar(require("path"));
42
42
  const os = __importStar(require("os"));
43
43
  const child_process_1 = require("child_process");
44
44
  const index_1 = require("../plugins/index");
45
+ const frontmatter_1 = require("../util/frontmatter");
45
46
  // ── Built-in commands ─────────────────────────────────────────────────────────
46
47
  // Shipped with the product; lowest precedence (project > global > builtin), so
47
48
  // a user can override any of them by creating a file with the same name.
@@ -79,18 +80,6 @@ const BUILTIN_COMMANDS = [
79
80
  ].join('\n'),
80
81
  },
81
82
  ];
82
- function parseFrontmatter(raw) {
83
- const m = /^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(raw);
84
- if (!m)
85
- return { meta: {}, body: raw.trim() };
86
- const meta = {};
87
- for (const line of m[1].split(/\r?\n/)) {
88
- const kv = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line.trim());
89
- if (kv)
90
- meta[kv[1].toLowerCase()] = kv[2].trim().replace(/^["']|["']$/g, '');
91
- }
92
- return { meta, body: (m[2] ?? '').trim() };
93
- }
94
83
  function loadDir(dir, source, into) {
95
84
  let files;
96
85
  try {
@@ -102,7 +91,7 @@ function loadDir(dir, source, into) {
102
91
  for (const file of files) {
103
92
  try {
104
93
  const raw = fs.readFileSync(path.join(dir, file), 'utf-8');
105
- const { meta, body } = parseFrontmatter(raw);
94
+ const { meta, body } = (0, frontmatter_1.parseFrontmatter)(raw);
106
95
  const name = (meta.name || path.basename(file, '.md')).trim().toLowerCase();
107
96
  if (!name)
108
97
  continue;
package/dist/index.d.ts CHANGED
@@ -26,4 +26,5 @@ export * from './permissions/destructive';
26
26
  export * from './plugins/index';
27
27
  export * from './plugins/installer';
28
28
  export * from './plugins/sources';
29
+ export * from './agent/trust';
29
30
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -42,4 +42,5 @@ __exportStar(require("./permissions/destructive"), exports);
42
42
  __exportStar(require("./plugins/index"), exports);
43
43
  __exportStar(require("./plugins/installer"), exports);
44
44
  __exportStar(require("./plugins/sources"), exports);
45
+ __exportStar(require("./agent/trust"), exports);
45
46
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"modePolicy.d.ts","sourceRoot":"","sources":["../../src/permissions/modePolicy.ts"],"names":[],"mappings":"AAsBA,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,kDAAkD;AAClD,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,KAAK,GACL,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,oFAAoF;IACpF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,aAU1B,CAAC;AAEH,0CAA0C;AAC1C,eAAO,MAAM,gBAAgB,aAE3B,CAAC;AAEH,oEAAoE;AACpE,eAAO,MAAM,iBAAiB,aAE5B,CAAC;AAEH,sCAAsC;AACtC,eAAO,MAAM,UAAU,aAAsD,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,CAwDvD;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAOzD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAGtD"}
1
+ {"version":3,"file":"modePolicy.d.ts","sourceRoot":"","sources":["../../src/permissions/modePolicy.ts"],"names":[],"mappings":"AAsBA,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,kDAAkD;AAClD,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,KAAK,GACL,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,oFAAoF;IACpF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,aAU1B,CAAC;AAEH,0CAA0C;AAC1C,eAAO,MAAM,gBAAgB,aAG3B,CAAC;AAEH,oEAAoE;AACpE,eAAO,MAAM,iBAAiB,aAE5B,CAAC;AAEH,sCAAsC;AACtC,eAAO,MAAM,UAAU,aAAsD,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,CAwDvD;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAOzD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAGtD"}
@@ -28,6 +28,7 @@ exports.READ_ONLY_TOOLS = new Set([
28
28
  /** Tools that write to the filesystem. */
29
29
  exports.FILE_WRITE_TOOLS = new Set([
30
30
  'write_file', 'create_file', 'edit_file', 'multi_edit', 'notebook_edit',
31
+ 'write_docx', 'write_xlsx', 'write_pptx',
31
32
  ]);
32
33
  /** Tools that move/delete files — riskier than an in-place edit. */
33
34
  exports.FILE_MUTATE_TOOLS = new Set([
@@ -200,7 +200,7 @@ function ruleMatches(rule, tool, input, workDir, opts) {
200
200
  const aliases = {
201
201
  Read: ['read_file', 'list_directory', 'search_files', 'glob', 'notebook_read'],
202
202
  Edit: ['edit_file', 'multi_edit', 'write_file', 'notebook_edit'],
203
- Write: ['write_file'],
203
+ Write: ['write_file', 'write_docx', 'write_xlsx', 'write_pptx'],
204
204
  Bash: ['bash'],
205
205
  WebFetch: ['fetch_url'],
206
206
  Image: ['generate_image', 'stock_photo'],
@@ -6,7 +6,13 @@ export interface PluginInfo {
6
6
  /** Absolute path to the plugin directory. */
7
7
  dir: string;
8
8
  scope: 'project' | 'global';
9
+ /** True when the user has toggled this plugin off (see isPluginDisabled). */
10
+ disabled: boolean;
9
11
  }
12
+ /** True if this installed plugin directory has been toggled off. */
13
+ export declare function isPluginDisabled(dir: string): boolean;
14
+ /** Toggle a plugin on/off. Idempotent — enabling an already-enabled plugin is a no-op. */
15
+ export declare function setPluginDisabled(dir: string, disabled: boolean): void;
10
16
  /**
11
17
  * Where each single-file component may live, in priority order.
12
18
  *
@@ -27,12 +33,33 @@ export declare const FILE_CANDIDATES: {
27
33
  };
28
34
  /** First existing candidate path for a component, or null. */
29
35
  export declare function resolvePluginFile(dir: string, kind: keyof typeof FILE_CANDIDATES): string | null;
30
- /** Discover installed plugins (project scope shadows global on name clash). */
36
+ /**
37
+ * Discover installed plugins (project scope shadows global on name clash).
38
+ *
39
+ * Includes disabled plugins — callers that manage the install (the desktop
40
+ * UI's list, `nex plugin list`) need to show and re-enable them. Everything
41
+ * that actually LOADS a plugin's contents at runtime (pluginAssetDirs,
42
+ * pluginHooks, pluginMcpServers below) filters `disabled` out itself, which is
43
+ * what makes toggling a plugin off actually take effect instead of only
44
+ * hiding it from a list.
45
+ */
31
46
  export declare function loadPlugins(workDir: string): PluginInfo[];
32
- /** Subdirectories of every installed plugin that hold `kind` assets (existing only). */
47
+ /**
48
+ * Find one installed plugin by name AND scope. Looking this up through
49
+ * loadPlugins() (rather than re-deriving `<scopeRoot>/<name>` the way
50
+ * removePlugin/updatePlugin in installer.ts do) is deliberate: a plugin's
51
+ * effective name can come from plugin.json's "name" field rather than its
52
+ * directory name (see scanRoot/readMeta above), so a caller that needs the
53
+ * plugin's actual `dir` for a specific scope — not just "some plugin with
54
+ * this name, whichever scope loadPlugins()'s single merged map preferred" —
55
+ * has to re-scan that one scope directly to avoid resolving to the wrong
56
+ * directory or silently matching the other scope's plugin of the same name.
57
+ */
58
+ export declare function findPlugin(workDir: string, name: string, scope: 'project' | 'global'): PluginInfo | undefined;
59
+ /** Subdirectories of every ENABLED installed plugin that hold `kind` assets (existing only). */
33
60
  export declare function pluginAssetDirs(workDir: string, kind: 'commands' | 'agents' | 'skills'): string[];
34
- /** Merge hooks.json from every plugin into a single hooks config (arrays concatenated). */
61
+ /** Merge hooks.json from every ENABLED plugin into a single hooks config (arrays concatenated). */
35
62
  export declare function pluginHooks(workDir: string): Record<string, unknown[]>;
36
- /** MCP server maps declared by plugins: { serverName → config } (first plugin wins on clash). */
63
+ /** MCP server maps declared by ENABLED plugins: { serverName → config } (first plugin wins on clash). */
37
64
  export declare function pluginMcpServers(workDir: string): Record<string, unknown>;
38
65
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugins/index.ts"],"names":[],"mappings":"AAsCA,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,eAAe,GAAG,MAAM,GAAG,IAAI,CAQhG;AAwCD,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAKzD;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,EAAE,CAMjG;AAED,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAiBtE;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAezE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugins/index.ts"],"names":[],"mappings":"AAsCA,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAYD,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAWtE;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,eAAe,GAAG,MAAM,GAAG,IAAI,CAQhG;AAwCD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAKzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAK7G;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,EAAE,CAOjG;AAED,mGAAmG;AACnG,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAkBtE;AAED,yGAAyG;AACzG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBzE"}
@@ -34,14 +34,50 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.FILE_CANDIDATES = void 0;
37
+ exports.isPluginDisabled = isPluginDisabled;
38
+ exports.setPluginDisabled = setPluginDisabled;
37
39
  exports.resolvePluginFile = resolvePluginFile;
38
40
  exports.loadPlugins = loadPlugins;
41
+ exports.findPlugin = findPlugin;
39
42
  exports.pluginAssetDirs = pluginAssetDirs;
40
43
  exports.pluginHooks = pluginHooks;
41
44
  exports.pluginMcpServers = pluginMcpServers;
42
45
  const fs = __importStar(require("fs"));
43
46
  const path = __importStar(require("path"));
44
47
  const os = __importStar(require("os"));
48
+ // ─── Enable / disable ──────────────────────────────────────────────────────────
49
+ //
50
+ // A marker file rather than a registry list, so the on/off state travels WITH
51
+ // the plugin directory itself: it survives a rename of the parent folder, and
52
+ // (deliberately, see installPlugin's preservation logic in installer.ts) it
53
+ // must survive `nex plugin update` re-copying the directory's contents — a
54
+ // user who turned a plugin off should not have it silently turn back on the
55
+ // next time it updates.
56
+ const DISABLED_MARKER = '.disabled';
57
+ /** True if this installed plugin directory has been toggled off. */
58
+ function isPluginDisabled(dir) {
59
+ try {
60
+ return fs.statSync(path.join(dir, DISABLED_MARKER)).isFile();
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ /** Toggle a plugin on/off. Idempotent — enabling an already-enabled plugin is a no-op. */
67
+ function setPluginDisabled(dir, disabled) {
68
+ const marker = path.join(dir, DISABLED_MARKER);
69
+ if (disabled) {
70
+ fs.writeFileSync(marker, '');
71
+ }
72
+ else {
73
+ try {
74
+ fs.unlinkSync(marker);
75
+ }
76
+ catch {
77
+ /* already absent */
78
+ }
79
+ }
80
+ }
45
81
  /**
46
82
  * Where each single-file component may live, in priority order.
47
83
  *
@@ -112,19 +148,46 @@ function scanRoot(root, scope, into) {
112
148
  const name = meta.name || entry.name;
113
149
  if (into.has(name))
114
150
  continue; // project scope scanned first — it wins
115
- into.set(name, { name, version: meta.version, description: meta.description, dir, scope });
151
+ into.set(name, { name, version: meta.version, description: meta.description, dir, scope, disabled: isPluginDisabled(dir) });
116
152
  }
117
153
  }
118
- /** Discover installed plugins (project scope shadows global on name clash). */
154
+ /**
155
+ * Discover installed plugins (project scope shadows global on name clash).
156
+ *
157
+ * Includes disabled plugins — callers that manage the install (the desktop
158
+ * UI's list, `nex plugin list`) need to show and re-enable them. Everything
159
+ * that actually LOADS a plugin's contents at runtime (pluginAssetDirs,
160
+ * pluginHooks, pluginMcpServers below) filters `disabled` out itself, which is
161
+ * what makes toggling a plugin off actually take effect instead of only
162
+ * hiding it from a list.
163
+ */
119
164
  function loadPlugins(workDir) {
120
165
  const out = new Map();
121
166
  scanRoot(path.join(workDir, '.nexrall', 'plugins'), 'project', out);
122
167
  scanRoot(path.join(os.homedir(), '.nexrall', 'plugins'), 'global', out);
123
168
  return [...out.values()];
124
169
  }
125
- /** Subdirectories of every installed plugin that hold `kind` assets (existing only). */
170
+ /**
171
+ * Find one installed plugin by name AND scope. Looking this up through
172
+ * loadPlugins() (rather than re-deriving `<scopeRoot>/<name>` the way
173
+ * removePlugin/updatePlugin in installer.ts do) is deliberate: a plugin's
174
+ * effective name can come from plugin.json's "name" field rather than its
175
+ * directory name (see scanRoot/readMeta above), so a caller that needs the
176
+ * plugin's actual `dir` for a specific scope — not just "some plugin with
177
+ * this name, whichever scope loadPlugins()'s single merged map preferred" —
178
+ * has to re-scan that one scope directly to avoid resolving to the wrong
179
+ * directory or silently matching the other scope's plugin of the same name.
180
+ */
181
+ function findPlugin(workDir, name, scope) {
182
+ const out = new Map();
183
+ const root = scope === 'project' ? path.join(workDir, '.nexrall', 'plugins') : path.join(os.homedir(), '.nexrall', 'plugins');
184
+ scanRoot(root, scope, out);
185
+ return out.get(name);
186
+ }
187
+ /** Subdirectories of every ENABLED installed plugin that hold `kind` assets (existing only). */
126
188
  function pluginAssetDirs(workDir, kind) {
127
189
  return loadPlugins(workDir)
190
+ .filter((p) => !p.disabled)
128
191
  .map((p) => path.join(p.dir, kind))
129
192
  .filter((d) => {
130
193
  try {
@@ -135,10 +198,12 @@ function pluginAssetDirs(workDir, kind) {
135
198
  }
136
199
  });
137
200
  }
138
- /** Merge hooks.json from every plugin into a single hooks config (arrays concatenated). */
201
+ /** Merge hooks.json from every ENABLED plugin into a single hooks config (arrays concatenated). */
139
202
  function pluginHooks(workDir) {
140
203
  const merged = {};
141
204
  for (const p of loadPlugins(workDir)) {
205
+ if (p.disabled)
206
+ continue;
142
207
  const file = resolvePluginFile(p.dir, 'hooks');
143
208
  if (!file)
144
209
  continue;
@@ -157,10 +222,12 @@ function pluginHooks(workDir) {
157
222
  }
158
223
  return merged;
159
224
  }
160
- /** MCP server maps declared by plugins: { serverName → config } (first plugin wins on clash). */
225
+ /** MCP server maps declared by ENABLED plugins: { serverName → config } (first plugin wins on clash). */
161
226
  function pluginMcpServers(workDir) {
162
227
  const merged = {};
163
228
  for (const p of loadPlugins(workDir)) {
229
+ if (p.disabled)
230
+ continue;
164
231
  const file = resolvePluginFile(p.dir, 'mcp');
165
232
  if (!file)
166
233
  continue;
@@ -86,6 +86,53 @@ export declare function parsePluginSource(spec: string): PluginSource;
86
86
  * containing no MCP servers.
87
87
  */
88
88
  export declare function inspectPluginDir(dir: string): PluginInspection;
89
+ export interface PluginComponentSummary {
90
+ name: string;
91
+ description: string;
92
+ }
93
+ export interface PluginMcpServerSummary {
94
+ name: string;
95
+ /** Transport, best-effort from the raw config shape (mirrors McpServerConfig in mcp/client.ts). */
96
+ type: 'stdio' | 'http' | 'sse';
97
+ /** Short human-readable summary of how this server connects (command, or URL host). */
98
+ summary: string;
99
+ }
100
+ export interface PluginComponentDetail {
101
+ commands: PluginComponentSummary[];
102
+ skills: PluginComponentSummary[];
103
+ agents: PluginComponentSummary[];
104
+ mcpServers: PluginMcpServerSummary[];
105
+ hasHooks: boolean;
106
+ }
107
+ /**
108
+ * Full breakdown of what a plugin bundles — one skill/command/connector per
109
+ * row, each with the human-readable text a user needs to decide whether to
110
+ * use/connect it. Distinct from inspectPluginDir (which only reports COUNTS
111
+ * and booleans for the install-time safety warning): this is the read model
112
+ * for the "click a plugin to see what's inside" detail screen, most directly
113
+ * needed because a Connectors tab has to show each MCP server by NAME so a
114
+ * user can connect them one at a time, the same way Claude Cowork's plugin
115
+ * detail view does.
116
+ */
117
+ export declare function pluginComponentDetail(dir: string): PluginComponentDetail;
118
+ export interface PluginItemDetail extends PluginComponentSummary {
119
+ /** Full raw file contents, including frontmatter — what a "Raw" toggle / Copy button should use. */
120
+ raw: string;
121
+ /** Frontmatter-stripped body — what a "Preview" toggle should render as markdown. */
122
+ body: string;
123
+ /** Absolute path to the source file, for "Edit" (open in the user's default editor/file manager). */
124
+ filePath: string;
125
+ }
126
+ /**
127
+ * Full content of ONE skill/command/agent inside a plugin — the read model
128
+ * for "click a skill row to see its content", mirroring Claude Cowork's own
129
+ * per-skill screen (rendered preview, raw markdown toggle, copy button).
130
+ * Deliberately separate from pluginComponentDetail's summaries (name +
131
+ * description only): loading every item's full body up front for a plugin
132
+ * with many skills would be wasteful, so this is fetched lazily, one row at
133
+ * a time, only when the user actually opens it.
134
+ */
135
+ export declare function pluginItemDetail(dir: string, kind: 'commands' | 'agents' | 'skills', name: string): PluginItemDetail | null;
89
136
  export interface InstallOptions {
90
137
  /** 'global' (default) → ~/.nexrall/plugins; 'project' → <workDir>/.nexrall/plugins */
91
138
  scope?: 'project' | 'global';
@@ -1 +1 @@
1
- {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/plugins/installer.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IACzB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAUD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,oEAAoE;AACpE,wBAAsB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAQ1F;AAED,uEAAuE;AACvE,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CASpF;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAGhD;AAID,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CA+C5D;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAoB9D;AAgFD,MAAM,WAAW,cAAc;IAC7B,sFAAsF;IACtF,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kGAAkG;IAClG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC3G,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAQD,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAwGnG;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAOpE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAYhH;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAA;CAAO,GACjG,OAAO,CAAC,aAAa,CAAC,CA0BxB"}
1
+ {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/plugins/installer.ts"],"names":[],"mappings":"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"}
@@ -38,6 +38,8 @@ exports.getRegistryPlugin = getRegistryPlugin;
38
38
  exports.reportInstall = reportInstall;
39
39
  exports.parsePluginSource = parsePluginSource;
40
40
  exports.inspectPluginDir = inspectPluginDir;
41
+ exports.pluginComponentDetail = pluginComponentDetail;
42
+ exports.pluginItemDetail = pluginItemDetail;
41
43
  exports.installPlugin = installPlugin;
42
44
  exports.readReceipt = readReceipt;
43
45
  exports.removePlugin = removePlugin;
@@ -49,6 +51,7 @@ const child_process_1 = require("child_process");
49
51
  const client_1 = require("../api/client");
50
52
  const index_1 = require("./index");
51
53
  const sources_1 = require("./sources");
54
+ const frontmatter_1 = require("../util/frontmatter");
52
55
  const RECEIPT_FILE = '_install.json';
53
56
  /** Search the registry (empty query = list all, official first). */
54
57
  async function searchRegistry(query, limit = 50) {
@@ -169,6 +172,176 @@ function inspectPluginDir(dir) {
169
172
  meta,
170
173
  };
171
174
  }
175
+ /** Read a flat `commands/*.md` or `agents/*.md` directory, returning name + frontmatter description. */
176
+ function listMdWithDescription(dir) {
177
+ let files;
178
+ try {
179
+ files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
180
+ }
181
+ catch {
182
+ return [];
183
+ }
184
+ const out = [];
185
+ for (const file of files) {
186
+ const name = file.replace(/\.md$/, '');
187
+ try {
188
+ const raw = fs.readFileSync(path.join(dir, file), 'utf-8');
189
+ const { meta } = (0, frontmatter_1.parseFrontmatter)(raw);
190
+ out.push({ name, description: meta.description || '' });
191
+ }
192
+ catch {
193
+ out.push({ name, description: '' }); // malformed file — still list it, just without a description
194
+ }
195
+ }
196
+ return out;
197
+ }
198
+ /** Read `skills/<name>/SKILL.md` subdirectories, returning name + frontmatter description. */
199
+ function listSkillsWithDescription(dir) {
200
+ let entries;
201
+ try {
202
+ entries = fs.readdirSync(dir, { withFileTypes: true });
203
+ }
204
+ catch {
205
+ return [];
206
+ }
207
+ const out = [];
208
+ for (const entry of entries) {
209
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
210
+ continue;
211
+ const skillFile = path.join(dir, entry.name, 'SKILL.md');
212
+ try {
213
+ if (!fs.statSync(skillFile).isFile())
214
+ continue;
215
+ const raw = fs.readFileSync(skillFile, 'utf-8');
216
+ const { meta } = (0, frontmatter_1.parseFrontmatter)(raw);
217
+ out.push({ name: meta.name || entry.name, description: meta.description || '' });
218
+ }
219
+ catch {
220
+ /* not a valid skill directory — skip */
221
+ }
222
+ }
223
+ return out;
224
+ }
225
+ /** Best-effort transport + one-line summary for an mcp.json server entry, for display only (never executed). */
226
+ function summariseMcpServer(name, cfg) {
227
+ const c = (cfg && typeof cfg === 'object' ? cfg : {});
228
+ const url = typeof c.url === 'string' ? c.url : undefined;
229
+ const command = typeof c.command === 'string' ? c.command : undefined;
230
+ const declaredType = c.type === 'http' || c.type === 'sse' || c.type === 'stdio' ? c.type : undefined;
231
+ const type = declaredType ?? (url ? 'http' : 'stdio');
232
+ let summary = 'Unknown connector configuration';
233
+ if (url) {
234
+ try {
235
+ summary = new URL(url).host;
236
+ }
237
+ catch {
238
+ summary = url;
239
+ }
240
+ }
241
+ else if (command) {
242
+ const args = Array.isArray(c.args) ? c.args.filter((a) => typeof a === 'string') : [];
243
+ summary = [command, ...args].join(' ');
244
+ }
245
+ return { name, type, summary };
246
+ }
247
+ /**
248
+ * Full breakdown of what a plugin bundles — one skill/command/connector per
249
+ * row, each with the human-readable text a user needs to decide whether to
250
+ * use/connect it. Distinct from inspectPluginDir (which only reports COUNTS
251
+ * and booleans for the install-time safety warning): this is the read model
252
+ * for the "click a plugin to see what's inside" detail screen, most directly
253
+ * needed because a Connectors tab has to show each MCP server by NAME so a
254
+ * user can connect them one at a time, the same way Claude Cowork's plugin
255
+ * detail view does.
256
+ */
257
+ function pluginComponentDetail(dir) {
258
+ const mcpFile = (0, index_1.resolvePluginFile)(dir, 'mcp');
259
+ let mcpServers = [];
260
+ if (mcpFile) {
261
+ try {
262
+ const j = JSON.parse(fs.readFileSync(mcpFile, 'utf-8'));
263
+ const servers = (j.mcpServers ?? j);
264
+ mcpServers = Object.entries(servers).map(([name, cfg]) => summariseMcpServer(name, cfg));
265
+ }
266
+ catch {
267
+ /* malformed mcp.json — report no servers rather than throwing */
268
+ }
269
+ }
270
+ return {
271
+ commands: listMdWithDescription(path.join(dir, 'commands')),
272
+ skills: listSkillsWithDescription(path.join(dir, 'skills')),
273
+ agents: listMdWithDescription(path.join(dir, 'agents')),
274
+ mcpServers,
275
+ hasHooks: (0, index_1.resolvePluginFile)(dir, 'hooks') !== null,
276
+ };
277
+ }
278
+ /** Match a flat `commands/*.md` or `agents/*.md` file by its display name (never builds a path from `name` — only compares against real directory entries, so a caller cannot path-traverse via the name argument). */
279
+ function readPluginItemFromFlatDir(dir, name) {
280
+ let files;
281
+ try {
282
+ files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
283
+ }
284
+ catch {
285
+ return null;
286
+ }
287
+ for (const file of files) {
288
+ if (file.replace(/\.md$/, '') !== name)
289
+ continue;
290
+ const filePath = path.join(dir, file);
291
+ try {
292
+ const raw = fs.readFileSync(filePath, 'utf-8');
293
+ const { meta, body } = (0, frontmatter_1.parseFrontmatter)(raw);
294
+ return { name, description: meta.description || '', raw, body, filePath };
295
+ }
296
+ catch {
297
+ return null;
298
+ }
299
+ }
300
+ return null;
301
+ }
302
+ /** Match a `skills/<dir>/SKILL.md` by its effective name (meta.name override, or the directory name). */
303
+ function readPluginSkill(skillsDir, name) {
304
+ let entries;
305
+ try {
306
+ entries = fs.readdirSync(skillsDir, { withFileTypes: true });
307
+ }
308
+ catch {
309
+ return null;
310
+ }
311
+ for (const entry of entries) {
312
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
313
+ continue;
314
+ const skillFile = path.join(skillsDir, entry.name, 'SKILL.md');
315
+ try {
316
+ if (!fs.statSync(skillFile).isFile())
317
+ continue;
318
+ const raw = fs.readFileSync(skillFile, 'utf-8');
319
+ const { meta, body } = (0, frontmatter_1.parseFrontmatter)(raw);
320
+ const skillName = meta.name || entry.name;
321
+ if (skillName !== name)
322
+ continue;
323
+ return { name: skillName, description: meta.description || '', raw, body, filePath: skillFile };
324
+ }
325
+ catch {
326
+ /* unreadable/malformed — keep looking, don't fail the whole scan */
327
+ }
328
+ }
329
+ return null;
330
+ }
331
+ /**
332
+ * Full content of ONE skill/command/agent inside a plugin — the read model
333
+ * for "click a skill row to see its content", mirroring Claude Cowork's own
334
+ * per-skill screen (rendered preview, raw markdown toggle, copy button).
335
+ * Deliberately separate from pluginComponentDetail's summaries (name +
336
+ * description only): loading every item's full body up front for a plugin
337
+ * with many skills would be wasteful, so this is fetched lazily, one row at
338
+ * a time, only when the user actually opens it.
339
+ */
340
+ function pluginItemDetail(dir, kind, name) {
341
+ if (kind === 'skills')
342
+ return readPluginSkill(path.join(dir, 'skills'), name);
343
+ return readPluginItemFromFlatDir(path.join(dir, kind), name);
344
+ }
172
345
  /** Is this directory a plausible plugin root? */
173
346
  function looksLikePlugin(dir) {
174
347
  const i = inspectPluginDir(dir);
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/tools/executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAg3EtE,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,CAAC,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,EAClC,OAAO,CAAC,EAAE,aAAa,EACvB,OAAO,CAAC,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GACjC,OAAO,CAAC,UAAU,CAAC,CA6BrB"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/tools/executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AA8kFtE,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,CAAC,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,EAClC,OAAO,CAAC,EAAE,aAAa,EACvB,OAAO,CAAC,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GACjC,OAAO,CAAC,UAAU,CAAC,CA6BrB"}