mu-harness 0.17.5 → 0.19.0
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/esm/agents/index.d.ts +0 -1
- package/esm/agents/index.js +0 -1
- package/esm/commands/index.d.ts +1 -0
- package/esm/commands/index.js +1 -0
- package/esm/commands/skill.d.ts +12 -0
- package/esm/commands/skill.js +21 -0
- package/esm/common/index.d.ts +0 -1
- package/esm/harness/create.js +21 -16
- package/esm/harness/types.d.ts +1 -13
- package/esm/skills/index.d.ts +0 -1
- package/esm/skills/index.js +0 -1
- package/esm/skills/parser.js +3 -0
- package/esm/skills/types.d.ts +5 -0
- package/esm/tui/chat/ChatApp.d.ts +17 -0
- package/esm/tui/chat/ChatApp.js +29 -2
- package/package.json +3 -3
- package/script/agents/index.d.ts +0 -1
- package/script/agents/index.js +1 -3
- package/script/commands/index.d.ts +1 -0
- package/script/commands/index.js +3 -1
- package/script/commands/skill.d.ts +12 -0
- package/script/commands/skill.js +25 -0
- package/script/common/index.d.ts +0 -1
- package/script/harness/create.js +18 -13
- package/script/harness/types.d.ts +1 -13
- package/script/skills/index.d.ts +0 -1
- package/script/skills/index.js +1 -3
- package/script/skills/parser.js +3 -0
- package/script/skills/types.d.ts +5 -0
- package/script/tui/chat/ChatApp.d.ts +17 -0
- package/script/tui/chat/ChatApp.js +29 -2
- package/esm/agents/writer.d.ts +0 -14
- package/esm/agents/writer.js +0 -81
- package/esm/common/scope.d.ts +0 -8
- package/esm/common/scope.js +0 -1
- package/esm/skills/writer.d.ts +0 -8
- package/esm/skills/writer.js +0 -55
- package/script/agents/writer.d.ts +0 -14
- package/script/agents/writer.js +0 -85
- package/script/common/scope.d.ts +0 -8
- package/script/common/scope.js +0 -2
- package/script/skills/writer.d.ts +0 -8
- package/script/skills/writer.js +0 -59
package/esm/agents/index.d.ts
CHANGED
|
@@ -2,4 +2,3 @@ export type { Agent, GrantValue, ToolDecision, ToolGrants } from './types.js';
|
|
|
2
2
|
export { type AgentRegistry, createAgentRegistry, grantArg, toolDecision, toolNames } from './registry.js';
|
|
3
3
|
export { parseAgent } from './parser.js';
|
|
4
4
|
export { loadAgents } from './loader.js';
|
|
5
|
-
export { createAgentWriterTool } from './writer.js';
|
package/esm/agents/index.js
CHANGED
package/esm/commands/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { createAgentsCommand, createHelpCommand, createQuitCommand, createSessionsCommand, createSkillsCommand, } from './defaults.js';
|
|
2
2
|
export { createCommandRegistry } from './registry.js';
|
|
3
|
+
export { createSkillCommand, type SkillCommandDeps } from './skill.js';
|
|
3
4
|
export type { Command, CommandContext, CommandRegistry, CommandResult } from './types.js';
|
package/esm/commands/index.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Agent } from '../agents/index.js';
|
|
2
|
+
import { type RunSkillDeps, type Skill } from '../skills/index.js';
|
|
3
|
+
import type { Command } from './types.js';
|
|
4
|
+
export type SkillCommandDeps = RunSkillDeps & {
|
|
5
|
+
activeAgent?: () => Agent | undefined;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Builds a slash command that runs a skill — the opt-in counterpart to a skill's
|
|
9
|
+
* `command` field. Invoking it runs the skill (as the active agent, or the first
|
|
10
|
+
* registered one) with the command args as its task, and returns the output.
|
|
11
|
+
*/
|
|
12
|
+
export declare const createSkillCommand: (skill: Skill, deps: SkillCommandDeps) => Command;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { runSkill } from '../skills/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a slash command that runs a skill — the opt-in counterpart to a skill's
|
|
4
|
+
* `command` field. Invoking it runs the skill (as the active agent, or the first
|
|
5
|
+
* registered one) with the command args as its task, and returns the output.
|
|
6
|
+
*/
|
|
7
|
+
export const createSkillCommand = (skill, deps) => ({
|
|
8
|
+
name: skill.command,
|
|
9
|
+
description: skill.description || `Run the "${skill.name}" skill`,
|
|
10
|
+
run: async (args) => {
|
|
11
|
+
const agent = deps.activeAgent?.()?.name ?? deps.agents?.list()[0]?.name;
|
|
12
|
+
if (!agent)
|
|
13
|
+
return { ok: false, error: 'no agent available to run the skill' };
|
|
14
|
+
try {
|
|
15
|
+
return { ok: true, output: await runSkill(deps, { skill: skill.name, task: args, agent }) };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
});
|
package/esm/common/index.d.ts
CHANGED
package/esm/harness/create.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import process from 'node:process';
|
|
4
|
-
import { createAgentRegistry,
|
|
5
|
-
import { createAgentsCommand, createCommandRegistry, createHelpCommand, createSessionsCommand, createSkillsCommand, } from '../commands/index.js';
|
|
4
|
+
import { createAgentRegistry, grantArg, loadAgents, toolDecision, toolNames } from '../agents/index.js';
|
|
5
|
+
import { createAgentsCommand, createCommandRegistry, createHelpCommand, createSessionsCommand, createSkillCommand, createSkillsCommand, } from '../commands/index.js';
|
|
6
6
|
import { createHarnessConfig } from '../config/index.js';
|
|
7
7
|
import { mergeHooks } from '../hooks/index.js';
|
|
8
8
|
import { allowList } from '../permissions/index.js';
|
|
9
9
|
import { createPluginStore } from '../plugin/index.js';
|
|
10
10
|
import { createScheduler, createScheduleTaskTool, createTasksCommand, createTaskStore } from '../scheduler/index.js';
|
|
11
11
|
import { createAgentSession, createSessionCatalog, createSessionManager, createSessionStore, persistTo, runTitler, } from '../session/index.js';
|
|
12
|
-
import { createRunSkillTool, createSkillRegistry, createSkillTool,
|
|
12
|
+
import { createRunSkillTool, createSkillRegistry, createSkillTool, loadSkills, runSkill } from '../skills/index.js';
|
|
13
13
|
import { createSubAgentRegistry, createSubAgentTool, runSubAgent } from '../subAgents/index.js';
|
|
14
14
|
import { environmentBlock } from './environment.js';
|
|
15
15
|
import { createModelRegistry } from './models.js';
|
|
@@ -20,7 +20,7 @@ const TITLE_AGENT = {
|
|
|
20
20
|
tools: [],
|
|
21
21
|
};
|
|
22
22
|
export const createHarness = async (options) => {
|
|
23
|
-
const { hostName, xdg, providers, model, agents: hostAgents = [], defaultAgents = [], skills: hostSkills = [],
|
|
23
|
+
const { hostName, xdg, providers, model, agents: hostAgents = [], defaultAgents = [], skills: hostSkills = [], agentDirs, title, titleModel, scheduler: enableScheduler = false, approvals, ...sessionDefaults } = options;
|
|
24
24
|
const cwd = options.cwd ?? process.cwd();
|
|
25
25
|
const config = createHarnessConfig({ hostName, xdg });
|
|
26
26
|
const models = createModelRegistry({ providers, default: model });
|
|
@@ -61,16 +61,6 @@ export const createHarness = async (options) => {
|
|
|
61
61
|
...await loadSkills(skillsDir),
|
|
62
62
|
];
|
|
63
63
|
const skills = createSkillRegistry(await mergedSkills());
|
|
64
|
-
const skillWriterTool = createSkillWriterTool({
|
|
65
|
-
dirs: { local: cwdSkillsDir, config: skillsDir },
|
|
66
|
-
registry: skills,
|
|
67
|
-
forceScope: skillScope,
|
|
68
|
-
});
|
|
69
|
-
const agentWriterTool = createAgentWriterTool({
|
|
70
|
-
dirs: { local: localAgentsDir, config: agentsDir },
|
|
71
|
-
registry: agents,
|
|
72
|
-
forceScope: agentScope,
|
|
73
|
-
});
|
|
74
64
|
const scopeSkills = (agent) => {
|
|
75
65
|
if (!agent)
|
|
76
66
|
return skills;
|
|
@@ -84,8 +74,6 @@ export const createHarness = async (options) => {
|
|
|
84
74
|
...(sessionDefaults.tools ?? []),
|
|
85
75
|
...extra,
|
|
86
76
|
createSkillTool(scopeSkills(agent)),
|
|
87
|
-
skillWriterTool,
|
|
88
|
-
agentWriterTool,
|
|
89
77
|
...schedulerTools,
|
|
90
78
|
];
|
|
91
79
|
const approvalHook = (getAgent) => approvals
|
|
@@ -173,6 +161,22 @@ export const createHarness = async (options) => {
|
|
|
173
161
|
...(tasks ? [createTasksCommand(tasks)] : []),
|
|
174
162
|
]);
|
|
175
163
|
commands.register(createHelpCommand(() => commands.list()));
|
|
164
|
+
// Register slash commands for skills that opt in via their `command` field,
|
|
165
|
+
// and keep them in sync across hot-reloads. A name already taken by another
|
|
166
|
+
// command is left alone (built-ins win).
|
|
167
|
+
const skillCommandNames = new Set();
|
|
168
|
+
const syncSkillCommands = () => {
|
|
169
|
+
for (const name of skillCommandNames)
|
|
170
|
+
commands.unregister(name);
|
|
171
|
+
skillCommandNames.clear();
|
|
172
|
+
for (const skill of skills.list()) {
|
|
173
|
+
if (!skill.command || commands.get(skill.command))
|
|
174
|
+
continue;
|
|
175
|
+
commands.register(createSkillCommand(skill, { skills, agents, spawn, runs, activeAgent: () => approvals?.activeAgent() }));
|
|
176
|
+
skillCommandNames.add(skill.command);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
syncSkillCommands();
|
|
176
180
|
if (scheduler)
|
|
177
181
|
await scheduler.start();
|
|
178
182
|
return {
|
|
@@ -192,6 +196,7 @@ export const createHarness = async (options) => {
|
|
|
192
196
|
reloadDefinitions: async () => {
|
|
193
197
|
agents.replaceAll(await mergedAgents());
|
|
194
198
|
skills.replaceAll(await mergedSkills());
|
|
199
|
+
syncSkillCommands();
|
|
195
200
|
},
|
|
196
201
|
scheduler,
|
|
197
202
|
tasks,
|
package/esm/harness/types.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Provider } from 'mu-core';
|
|
2
2
|
import type { Agent, AgentRegistry, ToolDecision } from '../agents/index.js';
|
|
3
3
|
import type { CommandRegistry } from '../commands/index.js';
|
|
4
|
-
import type { Scope } from '../common/index.js';
|
|
5
4
|
import type { HarnessConfig, HarnessConfigOptions } from '../config/index.js';
|
|
6
5
|
import type { ApprovalManager } from '../permissions/index.js';
|
|
7
6
|
import type { PluginStore } from '../plugin/index.js';
|
|
@@ -21,18 +20,7 @@ export type HarnessOptions = HarnessConfigOptions & Omit<AgentSessionConfig, 'pr
|
|
|
21
20
|
defaultAgents?: Agent[];
|
|
22
21
|
skills?: Skill[];
|
|
23
22
|
/**
|
|
24
|
-
*
|
|
25
|
-
* argument is overridden (and dropped from the tool schema). Unset → the
|
|
26
|
-
* model chooses, defaulting to 'local'.
|
|
27
|
-
*/
|
|
28
|
-
skillScope?: Scope;
|
|
29
|
-
/**
|
|
30
|
-
* Forces the save location of `create_agent`. Same semantics as
|
|
31
|
-
* {@link skillScope}: set to pin the scope, leave unset to let the model choose.
|
|
32
|
-
*/
|
|
33
|
-
agentScope?: Scope;
|
|
34
|
-
/**
|
|
35
|
-
* Overrides where `create_agent` writes (and which dirs are loaded at boot).
|
|
23
|
+
* Overrides which agent directories are loaded at boot (and watched/reloaded).
|
|
36
24
|
* Defaults to `{ local: <cwd>/agents, config: <configDir>/agents }`.
|
|
37
25
|
*/
|
|
38
26
|
agentDirs?: {
|
package/esm/skills/index.d.ts
CHANGED
|
@@ -4,5 +4,4 @@ export { parseSkill } from './parser.js';
|
|
|
4
4
|
export { loadSkills } from './loader.js';
|
|
5
5
|
export { skillMatchesPlatform } from './platform.js';
|
|
6
6
|
export { createSkillTool } from './tool.js';
|
|
7
|
-
export { createSkillWriterTool } from './writer.js';
|
|
8
7
|
export { createRunSkillTool, runSkill, type RunSkillDeps } from './run.js';
|
package/esm/skills/index.js
CHANGED
|
@@ -3,5 +3,4 @@ export { parseSkill } from './parser.js';
|
|
|
3
3
|
export { loadSkills } from './loader.js';
|
|
4
4
|
export { skillMatchesPlatform } from './platform.js';
|
|
5
5
|
export { createSkillTool } from './tool.js';
|
|
6
|
-
export { createSkillWriterTool } from './writer.js';
|
|
7
6
|
export { createRunSkillTool, runSkill } from './run.js';
|
package/esm/skills/parser.js
CHANGED
package/esm/skills/types.d.ts
CHANGED
|
@@ -5,4 +5,9 @@ export interface Skill {
|
|
|
5
5
|
dir?: string;
|
|
6
6
|
/** OS allow-list (Hermes/agentskills.io `platforms`): macos | linux | windows. Empty/absent = all. */
|
|
7
7
|
platforms?: string[];
|
|
8
|
+
/**
|
|
9
|
+
* Opt-in slash command name. When set, the harness registers a command that
|
|
10
|
+
* runs this skill (args become its task). Absent = no command for this skill.
|
|
11
|
+
*/
|
|
12
|
+
command?: string;
|
|
8
13
|
}
|
|
@@ -62,6 +62,21 @@ export interface ChatHost {
|
|
|
62
62
|
* in the status line) leave this unset (the default).
|
|
63
63
|
*/
|
|
64
64
|
minimal?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Harness slash commands to surface in the TUI alongside the built-in ones
|
|
67
|
+
* (e.g. `/agents`, skill commands). Called fresh so hot-reloaded commands show
|
|
68
|
+
* up. A name that collides with a built-in TUI command is ignored.
|
|
69
|
+
*/
|
|
70
|
+
commands?(): {
|
|
71
|
+
name: string;
|
|
72
|
+
description: string;
|
|
73
|
+
}[];
|
|
74
|
+
/** Runs a harness command (e.g. "/agents foo"); its output is shown in the transcript. */
|
|
75
|
+
runCommand?(input: string): Promise<{
|
|
76
|
+
ok: boolean;
|
|
77
|
+
output?: unknown;
|
|
78
|
+
error?: string;
|
|
79
|
+
}>;
|
|
65
80
|
onExit(code: number): void;
|
|
66
81
|
}
|
|
67
82
|
export declare class ChatApp {
|
|
@@ -145,6 +160,8 @@ export declare class ChatApp {
|
|
|
145
160
|
private cycleAgent;
|
|
146
161
|
private onEscape;
|
|
147
162
|
private cancelGeneration;
|
|
163
|
+
private harnessCommands;
|
|
164
|
+
private allCommands;
|
|
148
165
|
private paletteItems;
|
|
149
166
|
private paletteMove;
|
|
150
167
|
private paletteComplete;
|
package/esm/tui/chat/ChatApp.js
CHANGED
|
@@ -691,8 +691,35 @@ export class ChatApp {
|
|
|
691
691
|
this.onTurnComplete();
|
|
692
692
|
this.tui.requestRender();
|
|
693
693
|
}
|
|
694
|
+
// Built-in TUI commands plus any harness commands (computed fresh so hot-reloaded
|
|
695
|
+
// skill commands appear). Harness names that collide with a TUI command are dropped.
|
|
696
|
+
harnessCommands() {
|
|
697
|
+
if (!this.host.commands || !this.host.runCommand)
|
|
698
|
+
return [];
|
|
699
|
+
const taken = new Set(this.commands.map((c) => c.name));
|
|
700
|
+
return this.host.commands()
|
|
701
|
+
.filter((c) => !taken.has(c.name))
|
|
702
|
+
.map((c) => ({
|
|
703
|
+
name: c.name,
|
|
704
|
+
description: c.description,
|
|
705
|
+
run: async (args) => {
|
|
706
|
+
const res = await this.host.runCommand(`/${c.name}${args ? ` ${args}` : ''}`);
|
|
707
|
+
if (res.ok) {
|
|
708
|
+
if (res.output != null)
|
|
709
|
+
this.transcript.note(String(res.output));
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
this.transcript.note(res.error ?? 'command failed', true);
|
|
713
|
+
}
|
|
714
|
+
this.tui.requestRender();
|
|
715
|
+
},
|
|
716
|
+
}));
|
|
717
|
+
}
|
|
718
|
+
allCommands() {
|
|
719
|
+
return [...this.commands, ...this.harnessCommands()];
|
|
720
|
+
}
|
|
694
721
|
paletteItems() {
|
|
695
|
-
return filterCommands(this.
|
|
722
|
+
return filterCommands(this.allCommands(), this.editor.getValue(), this.paletteDismissedFor).slice(0, MAX_LIST_ROWS);
|
|
696
723
|
}
|
|
697
724
|
paletteMove(delta) {
|
|
698
725
|
const items = this.paletteItems();
|
|
@@ -726,7 +753,7 @@ export class ChatApp {
|
|
|
726
753
|
const space = body.indexOf(' ');
|
|
727
754
|
const name = (space === -1 ? body : body.slice(0, space)).toLowerCase();
|
|
728
755
|
const args = space === -1 ? '' : body.slice(space + 1).trim();
|
|
729
|
-
const command = this.
|
|
756
|
+
const command = this.allCommands().find((c) => c.name === name);
|
|
730
757
|
if (!command) {
|
|
731
758
|
this.transcript.note(`Unknown command: /${name}`, true);
|
|
732
759
|
this.tui.requestRender();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mu-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Agent harness: createHarness wires mu-core into a host — XDG paths, model registry, plugins, disk-loaded agents & skills, sub-agents, sessions (JSONL + SQLite catalog), slash commands, permission/approval hooks, an optional scheduler, and a composable TUI chat app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./script/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"@swc/wasm-typescript": "^1.15.0",
|
|
24
24
|
"cli-highlight": "^2.1.11",
|
|
25
25
|
"croner": "^9.0.0",
|
|
26
|
-
"mu-core": "^0.
|
|
27
|
-
"mu-tui": "^0.
|
|
26
|
+
"mu-core": "^0.19.0",
|
|
27
|
+
"mu-tui": "^0.19.0"
|
|
28
28
|
},
|
|
29
29
|
"_generatedBy": "dnt@dev",
|
|
30
30
|
"types": "./esm/index.d.ts"
|
package/script/agents/index.d.ts
CHANGED
|
@@ -2,4 +2,3 @@ export type { Agent, GrantValue, ToolDecision, ToolGrants } from './types.js';
|
|
|
2
2
|
export { type AgentRegistry, createAgentRegistry, grantArg, toolDecision, toolNames } from './registry.js';
|
|
3
3
|
export { parseAgent } from './parser.js';
|
|
4
4
|
export { loadAgents } from './loader.js';
|
|
5
|
-
export { createAgentWriterTool } from './writer.js';
|
package/script/agents/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.loadAgents = exports.parseAgent = exports.toolNames = exports.toolDecision = exports.grantArg = exports.createAgentRegistry = void 0;
|
|
4
4
|
var registry_js_1 = require("./registry.js");
|
|
5
5
|
Object.defineProperty(exports, "createAgentRegistry", { enumerable: true, get: function () { return registry_js_1.createAgentRegistry; } });
|
|
6
6
|
Object.defineProperty(exports, "grantArg", { enumerable: true, get: function () { return registry_js_1.grantArg; } });
|
|
@@ -10,5 +10,3 @@ var parser_js_1 = require("./parser.js");
|
|
|
10
10
|
Object.defineProperty(exports, "parseAgent", { enumerable: true, get: function () { return parser_js_1.parseAgent; } });
|
|
11
11
|
var loader_js_1 = require("./loader.js");
|
|
12
12
|
Object.defineProperty(exports, "loadAgents", { enumerable: true, get: function () { return loader_js_1.loadAgents; } });
|
|
13
|
-
var writer_js_1 = require("./writer.js");
|
|
14
|
-
Object.defineProperty(exports, "createAgentWriterTool", { enumerable: true, get: function () { return writer_js_1.createAgentWriterTool; } });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { createAgentsCommand, createHelpCommand, createQuitCommand, createSessionsCommand, createSkillsCommand, } from './defaults.js';
|
|
2
2
|
export { createCommandRegistry } from './registry.js';
|
|
3
|
+
export { createSkillCommand, type SkillCommandDeps } from './skill.js';
|
|
3
4
|
export type { Command, CommandContext, CommandRegistry, CommandResult } from './types.js';
|
package/script/commands/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createCommandRegistry = exports.createSkillsCommand = exports.createSessionsCommand = exports.createQuitCommand = exports.createHelpCommand = exports.createAgentsCommand = void 0;
|
|
3
|
+
exports.createSkillCommand = exports.createCommandRegistry = exports.createSkillsCommand = exports.createSessionsCommand = exports.createQuitCommand = exports.createHelpCommand = exports.createAgentsCommand = void 0;
|
|
4
4
|
var defaults_js_1 = require("./defaults.js");
|
|
5
5
|
Object.defineProperty(exports, "createAgentsCommand", { enumerable: true, get: function () { return defaults_js_1.createAgentsCommand; } });
|
|
6
6
|
Object.defineProperty(exports, "createHelpCommand", { enumerable: true, get: function () { return defaults_js_1.createHelpCommand; } });
|
|
@@ -9,3 +9,5 @@ Object.defineProperty(exports, "createSessionsCommand", { enumerable: true, get:
|
|
|
9
9
|
Object.defineProperty(exports, "createSkillsCommand", { enumerable: true, get: function () { return defaults_js_1.createSkillsCommand; } });
|
|
10
10
|
var registry_js_1 = require("./registry.js");
|
|
11
11
|
Object.defineProperty(exports, "createCommandRegistry", { enumerable: true, get: function () { return registry_js_1.createCommandRegistry; } });
|
|
12
|
+
var skill_js_1 = require("./skill.js");
|
|
13
|
+
Object.defineProperty(exports, "createSkillCommand", { enumerable: true, get: function () { return skill_js_1.createSkillCommand; } });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Agent } from '../agents/index.js';
|
|
2
|
+
import { type RunSkillDeps, type Skill } from '../skills/index.js';
|
|
3
|
+
import type { Command } from './types.js';
|
|
4
|
+
export type SkillCommandDeps = RunSkillDeps & {
|
|
5
|
+
activeAgent?: () => Agent | undefined;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Builds a slash command that runs a skill — the opt-in counterpart to a skill's
|
|
9
|
+
* `command` field. Invoking it runs the skill (as the active agent, or the first
|
|
10
|
+
* registered one) with the command args as its task, and returns the output.
|
|
11
|
+
*/
|
|
12
|
+
export declare const createSkillCommand: (skill: Skill, deps: SkillCommandDeps) => Command;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSkillCommand = void 0;
|
|
4
|
+
const index_js_1 = require("../skills/index.js");
|
|
5
|
+
/**
|
|
6
|
+
* Builds a slash command that runs a skill — the opt-in counterpart to a skill's
|
|
7
|
+
* `command` field. Invoking it runs the skill (as the active agent, or the first
|
|
8
|
+
* registered one) with the command args as its task, and returns the output.
|
|
9
|
+
*/
|
|
10
|
+
const createSkillCommand = (skill, deps) => ({
|
|
11
|
+
name: skill.command,
|
|
12
|
+
description: skill.description || `Run the "${skill.name}" skill`,
|
|
13
|
+
run: async (args) => {
|
|
14
|
+
const agent = deps.activeAgent?.()?.name ?? deps.agents?.list()[0]?.name;
|
|
15
|
+
if (!agent)
|
|
16
|
+
return { ok: false, error: 'no agent available to run the skill' };
|
|
17
|
+
try {
|
|
18
|
+
return { ok: true, output: await (0, index_js_1.runSkill)(deps, { skill: skill.name, task: args, agent }) };
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
exports.createSkillCommand = createSkillCommand;
|
package/script/common/index.d.ts
CHANGED
package/script/harness/create.js
CHANGED
|
@@ -26,7 +26,7 @@ const TITLE_AGENT = {
|
|
|
26
26
|
tools: [],
|
|
27
27
|
};
|
|
28
28
|
const createHarness = async (options) => {
|
|
29
|
-
const { hostName, xdg, providers, model, agents: hostAgents = [], defaultAgents = [], skills: hostSkills = [],
|
|
29
|
+
const { hostName, xdg, providers, model, agents: hostAgents = [], defaultAgents = [], skills: hostSkills = [], agentDirs, title, titleModel, scheduler: enableScheduler = false, approvals, ...sessionDefaults } = options;
|
|
30
30
|
const cwd = options.cwd ?? node_process_1.default.cwd();
|
|
31
31
|
const config = (0, index_js_3.createHarnessConfig)({ hostName, xdg });
|
|
32
32
|
const models = (0, models_js_1.createModelRegistry)({ providers, default: model });
|
|
@@ -67,16 +67,6 @@ const createHarness = async (options) => {
|
|
|
67
67
|
...await (0, index_js_9.loadSkills)(skillsDir),
|
|
68
68
|
];
|
|
69
69
|
const skills = (0, index_js_9.createSkillRegistry)(await mergedSkills());
|
|
70
|
-
const skillWriterTool = (0, index_js_9.createSkillWriterTool)({
|
|
71
|
-
dirs: { local: cwdSkillsDir, config: skillsDir },
|
|
72
|
-
registry: skills,
|
|
73
|
-
forceScope: skillScope,
|
|
74
|
-
});
|
|
75
|
-
const agentWriterTool = (0, index_js_1.createAgentWriterTool)({
|
|
76
|
-
dirs: { local: localAgentsDir, config: agentsDir },
|
|
77
|
-
registry: agents,
|
|
78
|
-
forceScope: agentScope,
|
|
79
|
-
});
|
|
80
70
|
const scopeSkills = (agent) => {
|
|
81
71
|
if (!agent)
|
|
82
72
|
return skills;
|
|
@@ -90,8 +80,6 @@ const createHarness = async (options) => {
|
|
|
90
80
|
...(sessionDefaults.tools ?? []),
|
|
91
81
|
...extra,
|
|
92
82
|
(0, index_js_9.createSkillTool)(scopeSkills(agent)),
|
|
93
|
-
skillWriterTool,
|
|
94
|
-
agentWriterTool,
|
|
95
83
|
...schedulerTools,
|
|
96
84
|
];
|
|
97
85
|
const approvalHook = (getAgent) => approvals
|
|
@@ -179,6 +167,22 @@ const createHarness = async (options) => {
|
|
|
179
167
|
...(tasks ? [(0, index_js_7.createTasksCommand)(tasks)] : []),
|
|
180
168
|
]);
|
|
181
169
|
commands.register((0, index_js_2.createHelpCommand)(() => commands.list()));
|
|
170
|
+
// Register slash commands for skills that opt in via their `command` field,
|
|
171
|
+
// and keep them in sync across hot-reloads. A name already taken by another
|
|
172
|
+
// command is left alone (built-ins win).
|
|
173
|
+
const skillCommandNames = new Set();
|
|
174
|
+
const syncSkillCommands = () => {
|
|
175
|
+
for (const name of skillCommandNames)
|
|
176
|
+
commands.unregister(name);
|
|
177
|
+
skillCommandNames.clear();
|
|
178
|
+
for (const skill of skills.list()) {
|
|
179
|
+
if (!skill.command || commands.get(skill.command))
|
|
180
|
+
continue;
|
|
181
|
+
commands.register((0, index_js_2.createSkillCommand)(skill, { skills, agents, spawn, runs, activeAgent: () => approvals?.activeAgent() }));
|
|
182
|
+
skillCommandNames.add(skill.command);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
syncSkillCommands();
|
|
182
186
|
if (scheduler)
|
|
183
187
|
await scheduler.start();
|
|
184
188
|
return {
|
|
@@ -198,6 +202,7 @@ const createHarness = async (options) => {
|
|
|
198
202
|
reloadDefinitions: async () => {
|
|
199
203
|
agents.replaceAll(await mergedAgents());
|
|
200
204
|
skills.replaceAll(await mergedSkills());
|
|
205
|
+
syncSkillCommands();
|
|
201
206
|
},
|
|
202
207
|
scheduler,
|
|
203
208
|
tasks,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Provider } from 'mu-core';
|
|
2
2
|
import type { Agent, AgentRegistry, ToolDecision } from '../agents/index.js';
|
|
3
3
|
import type { CommandRegistry } from '../commands/index.js';
|
|
4
|
-
import type { Scope } from '../common/index.js';
|
|
5
4
|
import type { HarnessConfig, HarnessConfigOptions } from '../config/index.js';
|
|
6
5
|
import type { ApprovalManager } from '../permissions/index.js';
|
|
7
6
|
import type { PluginStore } from '../plugin/index.js';
|
|
@@ -21,18 +20,7 @@ export type HarnessOptions = HarnessConfigOptions & Omit<AgentSessionConfig, 'pr
|
|
|
21
20
|
defaultAgents?: Agent[];
|
|
22
21
|
skills?: Skill[];
|
|
23
22
|
/**
|
|
24
|
-
*
|
|
25
|
-
* argument is overridden (and dropped from the tool schema). Unset → the
|
|
26
|
-
* model chooses, defaulting to 'local'.
|
|
27
|
-
*/
|
|
28
|
-
skillScope?: Scope;
|
|
29
|
-
/**
|
|
30
|
-
* Forces the save location of `create_agent`. Same semantics as
|
|
31
|
-
* {@link skillScope}: set to pin the scope, leave unset to let the model choose.
|
|
32
|
-
*/
|
|
33
|
-
agentScope?: Scope;
|
|
34
|
-
/**
|
|
35
|
-
* Overrides where `create_agent` writes (and which dirs are loaded at boot).
|
|
23
|
+
* Overrides which agent directories are loaded at boot (and watched/reloaded).
|
|
36
24
|
* Defaults to `{ local: <cwd>/agents, config: <configDir>/agents }`.
|
|
37
25
|
*/
|
|
38
26
|
agentDirs?: {
|
package/script/skills/index.d.ts
CHANGED
|
@@ -4,5 +4,4 @@ export { parseSkill } from './parser.js';
|
|
|
4
4
|
export { loadSkills } from './loader.js';
|
|
5
5
|
export { skillMatchesPlatform } from './platform.js';
|
|
6
6
|
export { createSkillTool } from './tool.js';
|
|
7
|
-
export { createSkillWriterTool } from './writer.js';
|
|
8
7
|
export { createRunSkillTool, runSkill, type RunSkillDeps } from './run.js';
|
package/script/skills/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runSkill = exports.createRunSkillTool = exports.
|
|
3
|
+
exports.runSkill = exports.createRunSkillTool = exports.createSkillTool = exports.skillMatchesPlatform = exports.loadSkills = exports.parseSkill = exports.createSkillRegistry = void 0;
|
|
4
4
|
var registry_js_1 = require("./registry.js");
|
|
5
5
|
Object.defineProperty(exports, "createSkillRegistry", { enumerable: true, get: function () { return registry_js_1.createSkillRegistry; } });
|
|
6
6
|
var parser_js_1 = require("./parser.js");
|
|
@@ -11,8 +11,6 @@ var platform_js_1 = require("./platform.js");
|
|
|
11
11
|
Object.defineProperty(exports, "skillMatchesPlatform", { enumerable: true, get: function () { return platform_js_1.skillMatchesPlatform; } });
|
|
12
12
|
var tool_js_1 = require("./tool.js");
|
|
13
13
|
Object.defineProperty(exports, "createSkillTool", { enumerable: true, get: function () { return tool_js_1.createSkillTool; } });
|
|
14
|
-
var writer_js_1 = require("./writer.js");
|
|
15
|
-
Object.defineProperty(exports, "createSkillWriterTool", { enumerable: true, get: function () { return writer_js_1.createSkillWriterTool; } });
|
|
16
14
|
var run_js_1 = require("./run.js");
|
|
17
15
|
Object.defineProperty(exports, "createRunSkillTool", { enumerable: true, get: function () { return run_js_1.createRunSkillTool; } });
|
|
18
16
|
Object.defineProperty(exports, "runSkill", { enumerable: true, get: function () { return run_js_1.runSkill; } });
|
package/script/skills/parser.js
CHANGED
|
@@ -15,6 +15,9 @@ const parseSkill = (source, fallbackName, dir) => {
|
|
|
15
15
|
skill.dir = dir;
|
|
16
16
|
if (platforms.length)
|
|
17
17
|
skill.platforms = platforms;
|
|
18
|
+
const command = (0, index_js_1.str)(fields.command);
|
|
19
|
+
if (command)
|
|
20
|
+
skill.command = command;
|
|
18
21
|
return skill;
|
|
19
22
|
};
|
|
20
23
|
exports.parseSkill = parseSkill;
|
package/script/skills/types.d.ts
CHANGED
|
@@ -5,4 +5,9 @@ export interface Skill {
|
|
|
5
5
|
dir?: string;
|
|
6
6
|
/** OS allow-list (Hermes/agentskills.io `platforms`): macos | linux | windows. Empty/absent = all. */
|
|
7
7
|
platforms?: string[];
|
|
8
|
+
/**
|
|
9
|
+
* Opt-in slash command name. When set, the harness registers a command that
|
|
10
|
+
* runs this skill (args become its task). Absent = no command for this skill.
|
|
11
|
+
*/
|
|
12
|
+
command?: string;
|
|
8
13
|
}
|
|
@@ -62,6 +62,21 @@ export interface ChatHost {
|
|
|
62
62
|
* in the status line) leave this unset (the default).
|
|
63
63
|
*/
|
|
64
64
|
minimal?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Harness slash commands to surface in the TUI alongside the built-in ones
|
|
67
|
+
* (e.g. `/agents`, skill commands). Called fresh so hot-reloaded commands show
|
|
68
|
+
* up. A name that collides with a built-in TUI command is ignored.
|
|
69
|
+
*/
|
|
70
|
+
commands?(): {
|
|
71
|
+
name: string;
|
|
72
|
+
description: string;
|
|
73
|
+
}[];
|
|
74
|
+
/** Runs a harness command (e.g. "/agents foo"); its output is shown in the transcript. */
|
|
75
|
+
runCommand?(input: string): Promise<{
|
|
76
|
+
ok: boolean;
|
|
77
|
+
output?: unknown;
|
|
78
|
+
error?: string;
|
|
79
|
+
}>;
|
|
65
80
|
onExit(code: number): void;
|
|
66
81
|
}
|
|
67
82
|
export declare class ChatApp {
|
|
@@ -145,6 +160,8 @@ export declare class ChatApp {
|
|
|
145
160
|
private cycleAgent;
|
|
146
161
|
private onEscape;
|
|
147
162
|
private cancelGeneration;
|
|
163
|
+
private harnessCommands;
|
|
164
|
+
private allCommands;
|
|
148
165
|
private paletteItems;
|
|
149
166
|
private paletteMove;
|
|
150
167
|
private paletteComplete;
|
|
@@ -694,8 +694,35 @@ class ChatApp {
|
|
|
694
694
|
this.onTurnComplete();
|
|
695
695
|
this.tui.requestRender();
|
|
696
696
|
}
|
|
697
|
+
// Built-in TUI commands plus any harness commands (computed fresh so hot-reloaded
|
|
698
|
+
// skill commands appear). Harness names that collide with a TUI command are dropped.
|
|
699
|
+
harnessCommands() {
|
|
700
|
+
if (!this.host.commands || !this.host.runCommand)
|
|
701
|
+
return [];
|
|
702
|
+
const taken = new Set(this.commands.map((c) => c.name));
|
|
703
|
+
return this.host.commands()
|
|
704
|
+
.filter((c) => !taken.has(c.name))
|
|
705
|
+
.map((c) => ({
|
|
706
|
+
name: c.name,
|
|
707
|
+
description: c.description,
|
|
708
|
+
run: async (args) => {
|
|
709
|
+
const res = await this.host.runCommand(`/${c.name}${args ? ` ${args}` : ''}`);
|
|
710
|
+
if (res.ok) {
|
|
711
|
+
if (res.output != null)
|
|
712
|
+
this.transcript.note(String(res.output));
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
this.transcript.note(res.error ?? 'command failed', true);
|
|
716
|
+
}
|
|
717
|
+
this.tui.requestRender();
|
|
718
|
+
},
|
|
719
|
+
}));
|
|
720
|
+
}
|
|
721
|
+
allCommands() {
|
|
722
|
+
return [...this.commands, ...this.harnessCommands()];
|
|
723
|
+
}
|
|
697
724
|
paletteItems() {
|
|
698
|
-
return (0, commands_js_1.filterCommands)(this.
|
|
725
|
+
return (0, commands_js_1.filterCommands)(this.allCommands(), this.editor.getValue(), this.paletteDismissedFor).slice(0, MAX_LIST_ROWS);
|
|
699
726
|
}
|
|
700
727
|
paletteMove(delta) {
|
|
701
728
|
const items = this.paletteItems();
|
|
@@ -729,7 +756,7 @@ class ChatApp {
|
|
|
729
756
|
const space = body.indexOf(' ');
|
|
730
757
|
const name = (space === -1 ? body : body.slice(0, space)).toLowerCase();
|
|
731
758
|
const args = space === -1 ? '' : body.slice(space + 1).trim();
|
|
732
|
-
const command = this.
|
|
759
|
+
const command = this.allCommands().find((c) => c.name === name);
|
|
733
760
|
if (!command) {
|
|
734
761
|
this.transcript.note(`Unknown command: /${name}`, true);
|
|
735
762
|
this.tui.requestRender();
|
package/esm/agents/writer.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Tool } from 'mu-core';
|
|
2
|
-
import type { Scope } from '../common/index.js';
|
|
3
|
-
import type { AgentRegistry } from './registry.js';
|
|
4
|
-
/**
|
|
5
|
-
* `create_agent` — authors a reusable agent definition (`.md` with frontmatter)
|
|
6
|
-
* and registers it live via {@link AgentRegistry.add}, so it can be delegated to
|
|
7
|
-
* through `subagent` without a restart. Mirrors {@link createSkillWriterTool}:
|
|
8
|
-
* the `scope` selects the save location, or a configured `forceScope` pins it.
|
|
9
|
-
*/
|
|
10
|
-
export declare const createAgentWriterTool: (deps: {
|
|
11
|
-
dirs: Record<Scope, string>;
|
|
12
|
-
registry: AgentRegistry;
|
|
13
|
-
forceScope?: Scope;
|
|
14
|
-
}) => Tool;
|
package/esm/agents/writer.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { stringify as stringifyYaml } from '../deps/jsr.io/@std/yaml/1.1.0/mod.js';
|
|
5
|
-
import { parseAgent } from './parser.js';
|
|
6
|
-
const slug = (name) => name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
7
|
-
/**
|
|
8
|
-
* `create_agent` — authors a reusable agent definition (`.md` with frontmatter)
|
|
9
|
-
* and registers it live via {@link AgentRegistry.add}, so it can be delegated to
|
|
10
|
-
* through `subagent` without a restart. Mirrors {@link createSkillWriterTool}:
|
|
11
|
-
* the `scope` selects the save location, or a configured `forceScope` pins it.
|
|
12
|
-
*/
|
|
13
|
-
export const createAgentWriterTool = (deps) => {
|
|
14
|
-
const { forceScope } = deps;
|
|
15
|
-
const scopeProp = forceScope ? {} : {
|
|
16
|
-
scope: {
|
|
17
|
-
type: 'string',
|
|
18
|
-
enum: ['local', 'config'],
|
|
19
|
-
description: 'Where to save it: "local" = this project (repo-first), "config" = global config dir. Defaults to "local".',
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
return {
|
|
23
|
-
name: 'create_agent',
|
|
24
|
-
description: forceScope
|
|
25
|
-
? `Define a reusable agent (name, description, system prompt, optional per-tool grants) that can be delegated to via \`subagent\`. Always saved to the "${forceScope}" agents directory.`
|
|
26
|
-
: 'Define a reusable agent (name, description, system prompt, optional per-tool grants) that can be delegated to via `subagent`. `scope: "local"` saves it to this project, `scope: "config"` makes it available across all projects.',
|
|
27
|
-
parameters: {
|
|
28
|
-
type: 'object',
|
|
29
|
-
properties: {
|
|
30
|
-
name: { type: 'string', description: 'Short agent name (kebab-case); also the filename.' },
|
|
31
|
-
description: { type: 'string', description: 'One line describing what this agent is for.' },
|
|
32
|
-
prompt: { type: 'string', description: 'The system prompt that defines the agent.' },
|
|
33
|
-
tools: {
|
|
34
|
-
type: 'object',
|
|
35
|
-
description: 'Optional per-tool grants: map a tool name to "allow" | "ask" | "deny" (or a nested {glob: decision} map). Omitted tools are denied — be explicit about what it may use.',
|
|
36
|
-
additionalProperties: true,
|
|
37
|
-
},
|
|
38
|
-
model: { type: 'string', description: 'Optional model ref override.' },
|
|
39
|
-
color: { type: 'string', description: 'Optional hex color for the UI.' },
|
|
40
|
-
...scopeProp,
|
|
41
|
-
},
|
|
42
|
-
required: ['name', 'description', 'prompt'],
|
|
43
|
-
additionalProperties: false,
|
|
44
|
-
},
|
|
45
|
-
run: async (input) => {
|
|
46
|
-
const { name, description, prompt, tools, model, color, scope } = (input ?? {});
|
|
47
|
-
if (!name || !description || !prompt) {
|
|
48
|
-
return [{ type: 'text', text: 'Error: create_agent requires `name`, `description`, and `prompt`.' }];
|
|
49
|
-
}
|
|
50
|
-
// A configured forceScope wins over whatever the model passed.
|
|
51
|
-
const resolved = forceScope ?? scope ?? 'local';
|
|
52
|
-
const base = deps.dirs[resolved];
|
|
53
|
-
if (!base)
|
|
54
|
-
return [{ type: 'text', text: `Error: unknown scope "${scope}".` }];
|
|
55
|
-
const id = slug(name);
|
|
56
|
-
if (!id)
|
|
57
|
-
return [{ type: 'text', text: `Error: invalid agent name "${name}".` }];
|
|
58
|
-
if (deps.registry.get(id)) {
|
|
59
|
-
return [{ type: 'text', text: `Error: an agent named "${id}" already exists.` }];
|
|
60
|
-
}
|
|
61
|
-
const file = join(base, `${id}.md`);
|
|
62
|
-
if (existsSync(file))
|
|
63
|
-
return [{ type: 'text', text: `Error: ${file} already exists.` }];
|
|
64
|
-
const frontmatter = { name: id, description };
|
|
65
|
-
if (model)
|
|
66
|
-
frontmatter.model = model;
|
|
67
|
-
if (color)
|
|
68
|
-
frontmatter.color = color;
|
|
69
|
-
if (tools && typeof tools === 'object')
|
|
70
|
-
frontmatter.tools = tools;
|
|
71
|
-
const source = `---\n${stringifyYaml(frontmatter).trimEnd()}\n---\n\n${prompt.trim()}\n`;
|
|
72
|
-
await mkdir(base, { recursive: true });
|
|
73
|
-
await writeFile(file, source, 'utf-8');
|
|
74
|
-
deps.registry.add(parseAgent(source, id));
|
|
75
|
-
return [{
|
|
76
|
-
type: 'text',
|
|
77
|
-
text: `Created agent "${id}" at ${file}. It can now be delegated to via the \`subagent\` tool.`,
|
|
78
|
-
}];
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
};
|
package/esm/common/scope.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Where a host saves agent-authored definitions (skills, sub-agents, …):
|
|
3
|
-
* - `local` → the current project (repo-first, e.g. `<cwd>/skills`).
|
|
4
|
-
* - `config` → the global config dir, shared across every project.
|
|
5
|
-
*
|
|
6
|
-
* Generic across definition kinds so every writer tool speaks the same vocabulary.
|
|
7
|
-
*/
|
|
8
|
-
export type Scope = 'local' | 'config';
|
package/esm/common/scope.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/esm/skills/writer.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Tool } from 'mu-core';
|
|
2
|
-
import type { Scope } from '../common/index.js';
|
|
3
|
-
import type { SkillRegistry } from './registry.js';
|
|
4
|
-
export declare const createSkillWriterTool: (deps: {
|
|
5
|
-
dirs: Record<Scope, string>;
|
|
6
|
-
registry: SkillRegistry;
|
|
7
|
-
forceScope?: Scope;
|
|
8
|
-
}) => Tool;
|
package/esm/skills/writer.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { parseSkill } from './parser.js';
|
|
4
|
-
const slug = (name) => name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
5
|
-
export const createSkillWriterTool = (deps) => {
|
|
6
|
-
const { forceScope } = deps;
|
|
7
|
-
const scopeProp = forceScope ? {} : {
|
|
8
|
-
scope: {
|
|
9
|
-
type: 'string',
|
|
10
|
-
enum: ['local', 'config'],
|
|
11
|
-
description: 'Where to save it: "local" = this project (<cwd>/skills), "config" = global config dir. Defaults to "local".',
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
return {
|
|
15
|
-
name: 'create_skill',
|
|
16
|
-
description: forceScope
|
|
17
|
-
? `Create a reusable skill (name, description, instructions) the agent can load on demand later — capture a reusable workflow worth keeping. Always saved to the global "${forceScope}" skills directory; load it later via \`skill\`.`
|
|
18
|
-
: 'Create a reusable skill (name, description, instructions) the agent can load on demand later — capture a reusable workflow worth keeping. `scope: "local"` saves it to this project, `scope: "config"` makes it available across all projects; load it later via `skill`.',
|
|
19
|
-
parameters: {
|
|
20
|
-
type: 'object',
|
|
21
|
-
properties: {
|
|
22
|
-
name: { type: 'string', description: 'Short skill name (kebab-case).' },
|
|
23
|
-
description: { type: 'string', description: 'One line describing when this skill should be used.' },
|
|
24
|
-
instructions: {
|
|
25
|
-
type: 'string',
|
|
26
|
-
description: 'The skill body: the instructions to follow when it is invoked.',
|
|
27
|
-
},
|
|
28
|
-
...scopeProp,
|
|
29
|
-
},
|
|
30
|
-
required: ['name', 'description', 'instructions'],
|
|
31
|
-
additionalProperties: false,
|
|
32
|
-
},
|
|
33
|
-
run: async (input) => {
|
|
34
|
-
const { name, description, instructions, scope } = (input ?? {});
|
|
35
|
-
if (!name || !description || !instructions) {
|
|
36
|
-
return [{ type: 'text', text: 'Error: create_skill requires `name`, `description`, and `instructions`.' }];
|
|
37
|
-
}
|
|
38
|
-
// A configured forceScope wins over whatever the model passed.
|
|
39
|
-
const resolved = forceScope ?? scope ?? 'local';
|
|
40
|
-
const base = deps.dirs[resolved];
|
|
41
|
-
if (!base)
|
|
42
|
-
return [{ type: 'text', text: `Error: unknown scope "${scope}".` }];
|
|
43
|
-
const id = slug(name);
|
|
44
|
-
if (!id)
|
|
45
|
-
return [{ type: 'text', text: `Error: invalid skill name "${name}".` }];
|
|
46
|
-
const skillDir = join(base, id);
|
|
47
|
-
const file = join(skillDir, 'SKILL.md');
|
|
48
|
-
const source = `---\nname: ${id}\ndescription: ${JSON.stringify(description)}\n---\n\n${instructions.trim()}\n`;
|
|
49
|
-
await mkdir(skillDir, { recursive: true });
|
|
50
|
-
await writeFile(file, source, 'utf-8');
|
|
51
|
-
deps.registry.add(parseSkill(source, id, skillDir));
|
|
52
|
-
return [{ type: 'text', text: `Created skill "${id}" at ${file}. It is now available via the \`skill\` tool.` }];
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Tool } from 'mu-core';
|
|
2
|
-
import type { Scope } from '../common/index.js';
|
|
3
|
-
import type { AgentRegistry } from './registry.js';
|
|
4
|
-
/**
|
|
5
|
-
* `create_agent` — authors a reusable agent definition (`.md` with frontmatter)
|
|
6
|
-
* and registers it live via {@link AgentRegistry.add}, so it can be delegated to
|
|
7
|
-
* through `subagent` without a restart. Mirrors {@link createSkillWriterTool}:
|
|
8
|
-
* the `scope` selects the save location, or a configured `forceScope` pins it.
|
|
9
|
-
*/
|
|
10
|
-
export declare const createAgentWriterTool: (deps: {
|
|
11
|
-
dirs: Record<Scope, string>;
|
|
12
|
-
registry: AgentRegistry;
|
|
13
|
-
forceScope?: Scope;
|
|
14
|
-
}) => Tool;
|
package/script/agents/writer.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createAgentWriterTool = void 0;
|
|
4
|
-
const node_fs_1 = require("node:fs");
|
|
5
|
-
const promises_1 = require("node:fs/promises");
|
|
6
|
-
const node_path_1 = require("node:path");
|
|
7
|
-
const mod_js_1 = require("../deps/jsr.io/@std/yaml/1.1.0/mod.js");
|
|
8
|
-
const parser_js_1 = require("./parser.js");
|
|
9
|
-
const slug = (name) => name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
10
|
-
/**
|
|
11
|
-
* `create_agent` — authors a reusable agent definition (`.md` with frontmatter)
|
|
12
|
-
* and registers it live via {@link AgentRegistry.add}, so it can be delegated to
|
|
13
|
-
* through `subagent` without a restart. Mirrors {@link createSkillWriterTool}:
|
|
14
|
-
* the `scope` selects the save location, or a configured `forceScope` pins it.
|
|
15
|
-
*/
|
|
16
|
-
const createAgentWriterTool = (deps) => {
|
|
17
|
-
const { forceScope } = deps;
|
|
18
|
-
const scopeProp = forceScope ? {} : {
|
|
19
|
-
scope: {
|
|
20
|
-
type: 'string',
|
|
21
|
-
enum: ['local', 'config'],
|
|
22
|
-
description: 'Where to save it: "local" = this project (repo-first), "config" = global config dir. Defaults to "local".',
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
return {
|
|
26
|
-
name: 'create_agent',
|
|
27
|
-
description: forceScope
|
|
28
|
-
? `Define a reusable agent (name, description, system prompt, optional per-tool grants) that can be delegated to via \`subagent\`. Always saved to the "${forceScope}" agents directory.`
|
|
29
|
-
: 'Define a reusable agent (name, description, system prompt, optional per-tool grants) that can be delegated to via `subagent`. `scope: "local"` saves it to this project, `scope: "config"` makes it available across all projects.',
|
|
30
|
-
parameters: {
|
|
31
|
-
type: 'object',
|
|
32
|
-
properties: {
|
|
33
|
-
name: { type: 'string', description: 'Short agent name (kebab-case); also the filename.' },
|
|
34
|
-
description: { type: 'string', description: 'One line describing what this agent is for.' },
|
|
35
|
-
prompt: { type: 'string', description: 'The system prompt that defines the agent.' },
|
|
36
|
-
tools: {
|
|
37
|
-
type: 'object',
|
|
38
|
-
description: 'Optional per-tool grants: map a tool name to "allow" | "ask" | "deny" (or a nested {glob: decision} map). Omitted tools are denied — be explicit about what it may use.',
|
|
39
|
-
additionalProperties: true,
|
|
40
|
-
},
|
|
41
|
-
model: { type: 'string', description: 'Optional model ref override.' },
|
|
42
|
-
color: { type: 'string', description: 'Optional hex color for the UI.' },
|
|
43
|
-
...scopeProp,
|
|
44
|
-
},
|
|
45
|
-
required: ['name', 'description', 'prompt'],
|
|
46
|
-
additionalProperties: false,
|
|
47
|
-
},
|
|
48
|
-
run: async (input) => {
|
|
49
|
-
const { name, description, prompt, tools, model, color, scope } = (input ?? {});
|
|
50
|
-
if (!name || !description || !prompt) {
|
|
51
|
-
return [{ type: 'text', text: 'Error: create_agent requires `name`, `description`, and `prompt`.' }];
|
|
52
|
-
}
|
|
53
|
-
// A configured forceScope wins over whatever the model passed.
|
|
54
|
-
const resolved = forceScope ?? scope ?? 'local';
|
|
55
|
-
const base = deps.dirs[resolved];
|
|
56
|
-
if (!base)
|
|
57
|
-
return [{ type: 'text', text: `Error: unknown scope "${scope}".` }];
|
|
58
|
-
const id = slug(name);
|
|
59
|
-
if (!id)
|
|
60
|
-
return [{ type: 'text', text: `Error: invalid agent name "${name}".` }];
|
|
61
|
-
if (deps.registry.get(id)) {
|
|
62
|
-
return [{ type: 'text', text: `Error: an agent named "${id}" already exists.` }];
|
|
63
|
-
}
|
|
64
|
-
const file = (0, node_path_1.join)(base, `${id}.md`);
|
|
65
|
-
if ((0, node_fs_1.existsSync)(file))
|
|
66
|
-
return [{ type: 'text', text: `Error: ${file} already exists.` }];
|
|
67
|
-
const frontmatter = { name: id, description };
|
|
68
|
-
if (model)
|
|
69
|
-
frontmatter.model = model;
|
|
70
|
-
if (color)
|
|
71
|
-
frontmatter.color = color;
|
|
72
|
-
if (tools && typeof tools === 'object')
|
|
73
|
-
frontmatter.tools = tools;
|
|
74
|
-
const source = `---\n${(0, mod_js_1.stringify)(frontmatter).trimEnd()}\n---\n\n${prompt.trim()}\n`;
|
|
75
|
-
await (0, promises_1.mkdir)(base, { recursive: true });
|
|
76
|
-
await (0, promises_1.writeFile)(file, source, 'utf-8');
|
|
77
|
-
deps.registry.add((0, parser_js_1.parseAgent)(source, id));
|
|
78
|
-
return [{
|
|
79
|
-
type: 'text',
|
|
80
|
-
text: `Created agent "${id}" at ${file}. It can now be delegated to via the \`subagent\` tool.`,
|
|
81
|
-
}];
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
exports.createAgentWriterTool = createAgentWriterTool;
|
package/script/common/scope.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Where a host saves agent-authored definitions (skills, sub-agents, …):
|
|
3
|
-
* - `local` → the current project (repo-first, e.g. `<cwd>/skills`).
|
|
4
|
-
* - `config` → the global config dir, shared across every project.
|
|
5
|
-
*
|
|
6
|
-
* Generic across definition kinds so every writer tool speaks the same vocabulary.
|
|
7
|
-
*/
|
|
8
|
-
export type Scope = 'local' | 'config';
|
package/script/common/scope.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Tool } from 'mu-core';
|
|
2
|
-
import type { Scope } from '../common/index.js';
|
|
3
|
-
import type { SkillRegistry } from './registry.js';
|
|
4
|
-
export declare const createSkillWriterTool: (deps: {
|
|
5
|
-
dirs: Record<Scope, string>;
|
|
6
|
-
registry: SkillRegistry;
|
|
7
|
-
forceScope?: Scope;
|
|
8
|
-
}) => Tool;
|
package/script/skills/writer.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createSkillWriterTool = void 0;
|
|
4
|
-
const promises_1 = require("node:fs/promises");
|
|
5
|
-
const node_path_1 = require("node:path");
|
|
6
|
-
const parser_js_1 = require("./parser.js");
|
|
7
|
-
const slug = (name) => name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
8
|
-
const createSkillWriterTool = (deps) => {
|
|
9
|
-
const { forceScope } = deps;
|
|
10
|
-
const scopeProp = forceScope ? {} : {
|
|
11
|
-
scope: {
|
|
12
|
-
type: 'string',
|
|
13
|
-
enum: ['local', 'config'],
|
|
14
|
-
description: 'Where to save it: "local" = this project (<cwd>/skills), "config" = global config dir. Defaults to "local".',
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
return {
|
|
18
|
-
name: 'create_skill',
|
|
19
|
-
description: forceScope
|
|
20
|
-
? `Create a reusable skill (name, description, instructions) the agent can load on demand later — capture a reusable workflow worth keeping. Always saved to the global "${forceScope}" skills directory; load it later via \`skill\`.`
|
|
21
|
-
: 'Create a reusable skill (name, description, instructions) the agent can load on demand later — capture a reusable workflow worth keeping. `scope: "local"` saves it to this project, `scope: "config"` makes it available across all projects; load it later via `skill`.',
|
|
22
|
-
parameters: {
|
|
23
|
-
type: 'object',
|
|
24
|
-
properties: {
|
|
25
|
-
name: { type: 'string', description: 'Short skill name (kebab-case).' },
|
|
26
|
-
description: { type: 'string', description: 'One line describing when this skill should be used.' },
|
|
27
|
-
instructions: {
|
|
28
|
-
type: 'string',
|
|
29
|
-
description: 'The skill body: the instructions to follow when it is invoked.',
|
|
30
|
-
},
|
|
31
|
-
...scopeProp,
|
|
32
|
-
},
|
|
33
|
-
required: ['name', 'description', 'instructions'],
|
|
34
|
-
additionalProperties: false,
|
|
35
|
-
},
|
|
36
|
-
run: async (input) => {
|
|
37
|
-
const { name, description, instructions, scope } = (input ?? {});
|
|
38
|
-
if (!name || !description || !instructions) {
|
|
39
|
-
return [{ type: 'text', text: 'Error: create_skill requires `name`, `description`, and `instructions`.' }];
|
|
40
|
-
}
|
|
41
|
-
// A configured forceScope wins over whatever the model passed.
|
|
42
|
-
const resolved = forceScope ?? scope ?? 'local';
|
|
43
|
-
const base = deps.dirs[resolved];
|
|
44
|
-
if (!base)
|
|
45
|
-
return [{ type: 'text', text: `Error: unknown scope "${scope}".` }];
|
|
46
|
-
const id = slug(name);
|
|
47
|
-
if (!id)
|
|
48
|
-
return [{ type: 'text', text: `Error: invalid skill name "${name}".` }];
|
|
49
|
-
const skillDir = (0, node_path_1.join)(base, id);
|
|
50
|
-
const file = (0, node_path_1.join)(skillDir, 'SKILL.md');
|
|
51
|
-
const source = `---\nname: ${id}\ndescription: ${JSON.stringify(description)}\n---\n\n${instructions.trim()}\n`;
|
|
52
|
-
await (0, promises_1.mkdir)(skillDir, { recursive: true });
|
|
53
|
-
await (0, promises_1.writeFile)(file, source, 'utf-8');
|
|
54
|
-
deps.registry.add((0, parser_js_1.parseSkill)(source, id, skillDir));
|
|
55
|
-
return [{ type: 'text', text: `Created skill "${id}" at ${file}. It is now available via the \`skill\` tool.` }];
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
};
|
|
59
|
-
exports.createSkillWriterTool = createSkillWriterTool;
|