terminal-pilot 0.0.47 → 0.0.49
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/cli.d.ts +5 -0
- package/dist/cli.js +706 -164
- package/dist/cli.js.map +4 -4
- package/dist/commands/close-session.js.map +1 -1
- package/dist/commands/create-session.js.map +1 -1
- package/dist/commands/daemon-runtime.js.map +1 -1
- package/dist/commands/fill.js.map +1 -1
- package/dist/commands/get-session.js.map +1 -1
- package/dist/commands/index.js +77 -10
- package/dist/commands/index.js.map +4 -4
- package/dist/commands/install.js +81 -14
- package/dist/commands/install.js.map +4 -4
- package/dist/commands/installer.js +37 -6
- package/dist/commands/installer.js.map +3 -3
- package/dist/commands/list-sessions.js.map +1 -1
- package/dist/commands/press-key.js.map +1 -1
- package/dist/commands/read-history.js.map +1 -1
- package/dist/commands/read-screen.js.map +1 -1
- package/dist/commands/resize.js.map +1 -1
- package/dist/commands/runtime.js.map +1 -1
- package/dist/commands/screenshot.js.map +1 -1
- package/dist/commands/send-signal.js.map +1 -1
- package/dist/commands/type.js.map +1 -1
- package/dist/commands/uninstall.js +37 -6
- package/dist/commands/uninstall.js.map +3 -3
- package/dist/commands/wait-for-exit.js.map +1 -1
- package/dist/commands/wait-for.js.map +1 -1
- package/dist/composition.json +6 -1
- package/dist/testing/cli-repl.js +704 -163
- package/dist/testing/cli-repl.js.map +4 -4
- package/dist/testing/qa-cli.js +704 -163
- package/dist/testing/qa-cli.js.map +4 -4
- package/node_modules/@poe-code/agent-defs/README.md +15 -2
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/codex.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/cursor.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/gemini-cli.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/goose.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/index.d.ts +1 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/index.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/pi.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/pi.js +15 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/capabilities.d.ts +14 -0
- package/node_modules/@poe-code/agent-defs/dist/capabilities.js +63 -0
- package/node_modules/@poe-code/agent-defs/dist/index.d.ts +3 -2
- package/node_modules/@poe-code/agent-defs/dist/index.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/registry.js +5 -1
- package/node_modules/@poe-code/agent-defs/dist/types.d.ts +8 -1
- package/node_modules/@poe-code/agent-skill-config/dist/apply.d.ts +2 -0
- package/node_modules/@poe-code/agent-skill-config/dist/apply.js +18 -14
- package/node_modules/@poe-code/agent-skill-config/dist/bridge-active-skills.js +3 -1
- package/node_modules/@poe-code/agent-skill-config/dist/configs.d.ts +2 -0
- package/node_modules/@poe-code/agent-skill-config/dist/configs.js +2 -1
- package/node_modules/@poe-code/frontmatter/README.md +3 -0
- package/node_modules/@poe-code/frontmatter/dist/index.d.ts +1 -1
- package/node_modules/@poe-code/frontmatter/dist/index.js +1 -1
- package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +9 -0
- package/node_modules/@poe-code/frontmatter/dist/parse.js +16 -0
- package/node_modules/toolcraft-design/dist/acp/components.d.ts +8 -1
- package/node_modules/toolcraft-design/dist/acp/components.js +11 -6
- package/node_modules/toolcraft-design/dist/acp/index.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/components/command-errors.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/components/command-errors.js +8 -2
- package/node_modules/toolcraft-design/dist/components/help-formatter-plain.js +61 -25
- package/node_modules/toolcraft-design/dist/components/help-formatter.d.ts +17 -0
- package/node_modules/toolcraft-design/dist/components/help-formatter.js +128 -29
- package/node_modules/toolcraft-design/dist/components/index.d.ts +3 -3
- package/node_modules/toolcraft-design/dist/components/index.js +2 -2
- package/node_modules/toolcraft-design/dist/components/table.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/components/table.js +52 -5
- package/node_modules/toolcraft-design/dist/components/template.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/components/template.js +27 -2
- package/node_modules/toolcraft-design/dist/explorer/actions.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/explorer/actions.js +1 -0
- package/node_modules/toolcraft-design/dist/explorer/reducer.js +1 -0
- package/node_modules/toolcraft-design/dist/explorer/runtime.js +21 -2
- package/node_modules/toolcraft-design/dist/explorer/state.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/explorer/state.js +9 -6
- package/node_modules/toolcraft-design/dist/help-formatter.d.ts +2 -2
- package/node_modules/toolcraft-design/dist/help-formatter.js +1 -1
- package/node_modules/toolcraft-design/dist/index.d.ts +4 -4
- package/node_modules/toolcraft-design/dist/index.js +3 -3
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +5 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +15 -1
- package/node_modules/toolcraft-design/dist/prompts/primitives/spinner.js +1 -1
- package/package.json +4 -3
|
@@ -4,7 +4,8 @@ Shared catalog of supported coding-agent definitions.
|
|
|
4
4
|
|
|
5
5
|
This package owns the declarative agent metadata used by CLI, SDK, spawn,
|
|
6
6
|
configuration, and UI surfaces: stable ids, labels, aliases, config paths,
|
|
7
|
-
binary names, API-shape support, OTEL capture wiring,
|
|
7
|
+
binary names, API-shape support, OTEL capture wiring, brand colors, and the
|
|
8
|
+
capability matrix that says which commands accept each agent.
|
|
8
9
|
|
|
9
10
|
## Usage
|
|
10
11
|
|
|
@@ -23,7 +24,19 @@ const codex = allAgents.find((agent) => agent.id === "codex");
|
|
|
23
24
|
- `parseAgentSpecifier(input)`: parses `agent` or `agent:model` input.
|
|
24
25
|
- `formatAgentSpecifier(specifier)`: formats an agent specifier.
|
|
25
26
|
- `normalizeAgentId(input)`: normalizes the agent part through the registry.
|
|
26
|
-
- Agent definition exports such as `codexAgent`, `claudeCodeAgent`, and `
|
|
27
|
+
- Agent definition exports such as `codexAgent`, `claudeCodeAgent`, `geminiCliAgent`, and `piAgent`.
|
|
28
|
+
- `listAgentsWithCapability(capability, options?)`: ids supporting a capability, optionally including aliases.
|
|
29
|
+
- `agentSupportsCapability(input, capability)`: resolves aliases, then checks the matrix.
|
|
30
|
+
- `formatAgentCapabilityError({ agent, capability })`: the shared message for a rejected agent argument.
|
|
31
|
+
|
|
32
|
+
## Capability Matrix
|
|
33
|
+
|
|
34
|
+
Each `AgentDefinition` declares its `capabilities` (`spawn`, `configure`,
|
|
35
|
+
`install`, `test`, `skill`, `mcp`). This is the single published source for every
|
|
36
|
+
command's allow-list, so `spawn`/`configure`/`install`/`test`/`skill` cannot
|
|
37
|
+
drift apart. `src/cli/commands/agent-capability-matrix.test.ts` pins each
|
|
38
|
+
capability to the registry that implements it, and fails if a registry gains or
|
|
39
|
+
loses an agent without the matrix being updated.
|
|
27
40
|
|
|
28
41
|
## Config Options
|
|
29
42
|
|
|
@@ -2,7 +2,7 @@ export const claudeCodeAgent = {
|
|
|
2
2
|
id: "claude-code",
|
|
3
3
|
name: "claude-code",
|
|
4
4
|
label: "Claude Code",
|
|
5
|
-
summary: "
|
|
5
|
+
summary: "Anthropic's agentic coding tool for the terminal.",
|
|
6
6
|
aliases: ["claude"],
|
|
7
7
|
binaryName: "claude",
|
|
8
8
|
apiShapes: ["anthropic-messages"],
|
|
@@ -12,6 +12,7 @@ export const claudeCodeAgent = {
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
configPath: "~/.claude.json",
|
|
15
|
+
capabilities: ["spawn", "configure", "install", "test", "skill", "mcp"],
|
|
15
16
|
branding: {
|
|
16
17
|
colors: {
|
|
17
18
|
dark: "#C15F3C",
|
|
@@ -2,7 +2,7 @@ export const codexAgent = {
|
|
|
2
2
|
id: "codex",
|
|
3
3
|
name: "codex",
|
|
4
4
|
label: "Codex",
|
|
5
|
-
summary: "
|
|
5
|
+
summary: "OpenAI's coding agent for the terminal.",
|
|
6
6
|
binaryName: "codex",
|
|
7
7
|
apiShapes: ["openai-responses"],
|
|
8
8
|
otelCapture: {
|
|
@@ -16,6 +16,7 @@ export const codexAgent = {
|
|
|
16
16
|
]
|
|
17
17
|
},
|
|
18
18
|
configPath: "~/.codex/config.toml",
|
|
19
|
+
capabilities: ["spawn", "configure", "install", "test", "skill", "mcp"],
|
|
19
20
|
branding: {
|
|
20
21
|
colors: {
|
|
21
22
|
dark: "#D5D9DF",
|
|
@@ -3,10 +3,11 @@ export const geminiCliAgent = {
|
|
|
3
3
|
name: "gemini-cli",
|
|
4
4
|
aliases: ["gemini"],
|
|
5
5
|
label: "Gemini CLI",
|
|
6
|
-
summary: "
|
|
6
|
+
summary: "Google's open-source AI agent for the terminal.",
|
|
7
7
|
binaryName: "gemini",
|
|
8
8
|
configPath: "~/.gemini/settings.json",
|
|
9
9
|
apiShapes: ["google-generations"],
|
|
10
|
+
capabilities: ["spawn", "configure", "install", "test", "skill"],
|
|
10
11
|
branding: {
|
|
11
12
|
colors: {
|
|
12
13
|
dark: "#8AB4F8",
|
|
@@ -2,11 +2,12 @@ export const kimiAgent = {
|
|
|
2
2
|
id: "kimi",
|
|
3
3
|
name: "kimi",
|
|
4
4
|
label: "Kimi",
|
|
5
|
-
summary: "
|
|
5
|
+
summary: "Moonshot AI's coding agent for the terminal.",
|
|
6
6
|
aliases: ["kimi-cli"],
|
|
7
7
|
binaryName: "kimi",
|
|
8
8
|
apiShapes: ["openai-chat-completions"],
|
|
9
9
|
configPath: "~/.kimi/mcp.json",
|
|
10
|
+
capabilities: ["spawn", "configure", "install", "test", "mcp"],
|
|
10
11
|
branding: {
|
|
11
12
|
colors: {
|
|
12
13
|
dark: "#7B68EE",
|
|
@@ -2,7 +2,7 @@ export const openCodeAgent = {
|
|
|
2
2
|
id: "opencode",
|
|
3
3
|
name: "opencode",
|
|
4
4
|
label: "OpenCode CLI",
|
|
5
|
-
summary: "
|
|
5
|
+
summary: "Open-source AI coding agent for the terminal.",
|
|
6
6
|
binaryName: "opencode",
|
|
7
7
|
apiShapes: ["openai-chat-completions"],
|
|
8
8
|
otelCapture: {
|
|
@@ -11,6 +11,7 @@ export const openCodeAgent = {
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
configPath: "~/.config/opencode/opencode.json",
|
|
14
|
+
capabilities: ["spawn", "configure", "install", "test", "skill", "mcp"],
|
|
14
15
|
branding: {
|
|
15
16
|
colors: {
|
|
16
17
|
dark: "#4A4F55",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const piAgent = {
|
|
2
|
+
id: "pi",
|
|
3
|
+
name: "pi",
|
|
4
|
+
aliases: ["pi-agent"],
|
|
5
|
+
label: "Pi",
|
|
6
|
+
summary: "Minimal AI coding agent for the terminal.",
|
|
7
|
+
binaryName: "pi",
|
|
8
|
+
capabilities: ["spawn"],
|
|
9
|
+
branding: {
|
|
10
|
+
colors: {
|
|
11
|
+
dark: "#F2F2F2",
|
|
12
|
+
light: "#242424"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -5,6 +5,7 @@ export const poeAgentAgent = {
|
|
|
5
5
|
summary: "Run one-shot prompts with the built-in Poe agent runtime.",
|
|
6
6
|
apiShapes: ["openai-responses", "openai-chat-completions"],
|
|
7
7
|
configPath: "~/.poe-code/config.json",
|
|
8
|
+
capabilities: ["configure"],
|
|
8
9
|
branding: {
|
|
9
10
|
colors: {
|
|
10
11
|
dark: "#A465F7",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AgentCapability } from "./types.js";
|
|
2
|
+
export declare function listAgentsWithCapability(capability: AgentCapability, options?: {
|
|
3
|
+
includeAliases?: boolean;
|
|
4
|
+
}): readonly string[];
|
|
5
|
+
export declare function agentSupportsCapability(input: string, capability: AgentCapability): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* The single message for every agent argument that misses. It distinguishes a
|
|
8
|
+
* typo (unknown id, plus a did-you-mean) from a real capability gap ("pi
|
|
9
|
+
* supports: spawn"), and always names the agents the command does accept.
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatAgentCapabilityError(input: {
|
|
12
|
+
agent: string;
|
|
13
|
+
capability: AgentCapability;
|
|
14
|
+
}): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { allAgents, resolveAgentId } from "./registry.js";
|
|
2
|
+
export function listAgentsWithCapability(capability, options) {
|
|
3
|
+
const names = [];
|
|
4
|
+
for (const agent of allAgents) {
|
|
5
|
+
if (!agent.capabilities?.includes(capability)) {
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
names.push(agent.id);
|
|
9
|
+
if (options?.includeAliases) {
|
|
10
|
+
names.push(...(agent.aliases ?? []));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return names;
|
|
14
|
+
}
|
|
15
|
+
export function agentSupportsCapability(input, capability) {
|
|
16
|
+
const id = resolveAgentId(input);
|
|
17
|
+
return id !== undefined && listAgentsWithCapability(capability).includes(id);
|
|
18
|
+
}
|
|
19
|
+
const MAX_SUGGESTION_DISTANCE = 3;
|
|
20
|
+
function editDistance(a, b) {
|
|
21
|
+
let previous = Array.from({ length: b.length + 1 }, (_unused, index) => index);
|
|
22
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
23
|
+
const current = [i];
|
|
24
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
25
|
+
const substitution = previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
26
|
+
current[j] = Math.min(substitution, previous[j] + 1, current[j - 1] + 1);
|
|
27
|
+
}
|
|
28
|
+
previous = current;
|
|
29
|
+
}
|
|
30
|
+
return previous[b.length];
|
|
31
|
+
}
|
|
32
|
+
/** Every candidate tied for the smallest edit distance, when one is close enough. */
|
|
33
|
+
function suggest(input, candidates) {
|
|
34
|
+
const needle = input.trim().toLowerCase();
|
|
35
|
+
const scored = candidates
|
|
36
|
+
.map((candidate) => ({ candidate, score: editDistance(needle, candidate.toLowerCase()) }))
|
|
37
|
+
.filter((entry) => entry.score <= MAX_SUGGESTION_DISTANCE);
|
|
38
|
+
if (scored.length === 0) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const best = Math.min(...scored.map((entry) => entry.score));
|
|
42
|
+
return scored.filter((entry) => entry.score === best).map((entry) => entry.candidate);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The single message for every agent argument that misses. It distinguishes a
|
|
46
|
+
* typo (unknown id, plus a did-you-mean) from a real capability gap ("pi
|
|
47
|
+
* supports: spawn"), and always names the agents the command does accept.
|
|
48
|
+
*/
|
|
49
|
+
export function formatAgentCapabilityError(input) {
|
|
50
|
+
const allowed = listAgentsWithCapability(input.capability, { includeAliases: true });
|
|
51
|
+
const allowList = `Agents supporting ${input.capability}: ${allowed.length > 0 ? allowed.join(", ") : "none"}.`;
|
|
52
|
+
const id = resolveAgentId(input.agent);
|
|
53
|
+
if (!id) {
|
|
54
|
+
const near = suggest(input.agent, allowed);
|
|
55
|
+
const hint = near.length > 0 ? ` Did you mean: ${near.join(", ")}?` : "";
|
|
56
|
+
return `Unknown agent "${input.agent}".${hint} ${allowList}`;
|
|
57
|
+
}
|
|
58
|
+
const supported = ["spawn", "configure", "install", "test", "skill", "mcp"].filter((capability) => listAgentsWithCapability(capability).includes(id));
|
|
59
|
+
const supports = supported.length > 0
|
|
60
|
+
? `${id} supports: ${supported.join(", ")}.`
|
|
61
|
+
: `${id} is not supported by poe-code agent commands.`;
|
|
62
|
+
return `Agent "${id}" does not support ${input.capability}. ${supports} ${allowList}`;
|
|
63
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export type { AgentDefinition, ApiShapeId, OtelCaptureDefinition } from "./types.js";
|
|
1
|
+
export type { AgentCapability, AgentDefinition, ApiShapeId, OtelCaptureDefinition } from "./types.js";
|
|
2
2
|
export type { AgentSpecifier } from "./specifier.js";
|
|
3
|
-
export { claudeCodeAgent, claudeDesktopAgent, codexAgent, cursorAgent, geminiCliAgent, openCodeAgent, kimiAgent, gooseAgent, poeAgentAgent } from "./agents/index.js";
|
|
3
|
+
export { claudeCodeAgent, claudeDesktopAgent, codexAgent, cursorAgent, geminiCliAgent, openCodeAgent, kimiAgent, gooseAgent, piAgent, poeAgentAgent } from "./agents/index.js";
|
|
4
4
|
export { allAgents, resolveAgentId } from "./registry.js";
|
|
5
|
+
export { agentSupportsCapability, formatAgentCapabilityError, listAgentsWithCapability } from "./capabilities.js";
|
|
5
6
|
export { parseAgentSpecifier, formatAgentSpecifier, normalizeAgentId } from "./specifier.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { claudeCodeAgent, claudeDesktopAgent, codexAgent, cursorAgent, geminiCliAgent, openCodeAgent, kimiAgent, gooseAgent, poeAgentAgent } from "./agents/index.js";
|
|
1
|
+
export { claudeCodeAgent, claudeDesktopAgent, codexAgent, cursorAgent, geminiCliAgent, openCodeAgent, kimiAgent, gooseAgent, piAgent, poeAgentAgent } from "./agents/index.js";
|
|
2
2
|
export { allAgents, resolveAgentId } from "./registry.js";
|
|
3
|
+
export { agentSupportsCapability, formatAgentCapabilityError, listAgentsWithCapability } from "./capabilities.js";
|
|
3
4
|
export { parseAgentSpecifier, formatAgentSpecifier, normalizeAgentId } from "./specifier.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { claudeCodeAgent, claudeDesktopAgent, codexAgent, cursorAgent, geminiCliAgent, openCodeAgent, kimiAgent, gooseAgent, poeAgentAgent } from "./agents/index.js";
|
|
1
|
+
import { claudeCodeAgent, claudeDesktopAgent, codexAgent, cursorAgent, geminiCliAgent, openCodeAgent, kimiAgent, gooseAgent, piAgent, poeAgentAgent } from "./agents/index.js";
|
|
2
2
|
function freezeAgent(agent) {
|
|
3
3
|
if (agent.aliases !== undefined) {
|
|
4
4
|
Object.freeze(agent.aliases);
|
|
@@ -6,6 +6,9 @@ function freezeAgent(agent) {
|
|
|
6
6
|
if (agent.apiShapes !== undefined) {
|
|
7
7
|
Object.freeze(agent.apiShapes);
|
|
8
8
|
}
|
|
9
|
+
if (agent.capabilities !== undefined) {
|
|
10
|
+
Object.freeze(agent.capabilities);
|
|
11
|
+
}
|
|
9
12
|
if (agent.otelCapture?.env !== undefined) {
|
|
10
13
|
Object.freeze(agent.otelCapture.env);
|
|
11
14
|
}
|
|
@@ -25,6 +28,7 @@ export const allAgents = Object.freeze([
|
|
|
25
28
|
freezeAgent(openCodeAgent),
|
|
26
29
|
freezeAgent(kimiAgent),
|
|
27
30
|
freezeAgent(gooseAgent),
|
|
31
|
+
freezeAgent(piAgent),
|
|
28
32
|
freezeAgent(poeAgentAgent)
|
|
29
33
|
]);
|
|
30
34
|
const lookup = new Map();
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export type ApiShapeId = "openai-chat-completions" | "openai-responses" | "anthropic-messages" | "google-generations";
|
|
2
|
+
/**
|
|
3
|
+
* The poe-code surfaces an agent can be used with. This is the single
|
|
4
|
+
* published source for each command's allow-list; `agent-capability-matrix.test.ts`
|
|
5
|
+
* pins every value to the registry that implements it, so the sets cannot drift.
|
|
6
|
+
*/
|
|
7
|
+
export type AgentCapability = "spawn" | "configure" | "install" | "test" | "skill" | "mcp";
|
|
2
8
|
export interface OtelCaptureDefinition {
|
|
3
9
|
env?: Record<string, string>;
|
|
4
10
|
args?: (endpoint: string, content: boolean) => string[];
|
|
@@ -11,9 +17,10 @@ export interface AgentDefinition {
|
|
|
11
17
|
aliases?: string[];
|
|
12
18
|
/** Binary name for CLI agents. Optional for GUI-only apps like Claude Desktop. */
|
|
13
19
|
binaryName?: string;
|
|
20
|
+
readonly capabilities?: readonly AgentCapability[];
|
|
14
21
|
readonly apiShapes?: readonly ApiShapeId[];
|
|
15
22
|
readonly otelCapture?: OtelCaptureDefinition;
|
|
16
|
-
configPath
|
|
23
|
+
configPath?: string;
|
|
17
24
|
readonly configPaths?: {
|
|
18
25
|
readonly darwin: string;
|
|
19
26
|
readonly linux: string;
|
|
@@ -13,6 +13,8 @@ export type InstallSkillOptions = {
|
|
|
13
13
|
scope: ApplyOptions["scope"];
|
|
14
14
|
dryRun?: boolean;
|
|
15
15
|
observers?: ApplyOptions["observers"];
|
|
16
|
+
/** Overwrite an existing SKILL.md instead of refusing to install over it. */
|
|
17
|
+
force?: boolean;
|
|
16
18
|
};
|
|
17
19
|
export type InstallSkillResult = {
|
|
18
20
|
skillPath: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fileMutation, runMutations, templateMutation } from "@poe-code/config-mutations";
|
|
2
|
+
import { UserError } from "@poe-code/user-error";
|
|
2
3
|
import { resolveAgentSupport } from "./configs.js";
|
|
3
4
|
import { hasOwnErrorCode } from "./error-codes.js";
|
|
4
5
|
import { createTemplateLoader } from "./templates.js";
|
|
@@ -45,7 +46,7 @@ export async function configure(agentId, options) {
|
|
|
45
46
|
if (await pathExists(options.fs, targetPath)) {
|
|
46
47
|
const existing = await options.fs.readFile(targetPath, "utf8");
|
|
47
48
|
if (existing !== (await templateLoader(templateId))) {
|
|
48
|
-
throw new
|
|
49
|
+
throw new UserError(`Skill already exists and differs from the bundled ${templateId}: ${targetPath}\nMove or delete it, then re-run this command to install the poe-code version.`);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -75,27 +76,30 @@ export async function unconfigure(agentId, options) {
|
|
|
75
76
|
const scope = options.scope ?? "global";
|
|
76
77
|
const config = support.config;
|
|
77
78
|
const skillDir = scope === "global" ? config.globalSkillDir : toHomeRelative(config.localSkillDir);
|
|
79
|
+
const displayDir = scope === "global" ? config.globalSkillDir : config.localSkillDir;
|
|
78
80
|
const homeDir = scope === "global" ? options.homeDir : options.cwd;
|
|
79
81
|
const templateLoader = createTemplateLoader();
|
|
80
82
|
const removeBundledSkills = [];
|
|
81
83
|
for (const templateId of bundledSkillTemplateIds) {
|
|
82
|
-
const targetPath = `${
|
|
83
|
-
if (await pathExists(options.fs, targetPath)) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
84
|
+
const targetPath = `${homeDir}/${skillDir.slice(2)}/${templateId}`;
|
|
85
|
+
if (!(await pathExists(options.fs, targetPath))) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const existing = await options.fs.readFile(targetPath, "utf8");
|
|
89
|
+
if (options.force || existing === (await templateLoader(templateId))) {
|
|
90
|
+
removeBundledSkills.push(fileMutation.remove({
|
|
91
|
+
target: `${skillDir}/${templateId}`,
|
|
92
|
+
label: `Remove bundled skill ${templateId} from ${displayDir}`
|
|
93
|
+
}));
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
await runMutations([
|
|
94
97
|
...removeBundledSkills,
|
|
98
|
+
// Never force-remove the shared skills root: it holds skills poe-code does not
|
|
99
|
+
// manage. It is only removed once poe-code's own skills left it empty.
|
|
95
100
|
fileMutation.removeDirectory({
|
|
96
101
|
path: skillDir,
|
|
97
|
-
|
|
98
|
-
label: `Remove skills directory ${skillDir}`
|
|
102
|
+
label: `Remove empty skills directory ${displayDir}`
|
|
99
103
|
})
|
|
100
104
|
], {
|
|
101
105
|
fs: options.fs,
|
|
@@ -132,8 +136,8 @@ export async function installSkill(agentId, skill, options) {
|
|
|
132
136
|
const skillFilePath = `${skillFolderPath}/SKILL.md`;
|
|
133
137
|
const displayPath = `${scope === "global" ? config.globalSkillDir : config.localSkillDir}/${skill.name}/SKILL.md`;
|
|
134
138
|
const absoluteSkillPath = `${scope === "global" ? options.homeDir : options.cwd}/${skillFilePath.slice(2)}`;
|
|
135
|
-
if (await pathExists(options.fs, absoluteSkillPath)) {
|
|
136
|
-
throw new
|
|
139
|
+
if (!options.force && (await pathExists(options.fs, absoluteSkillPath))) {
|
|
140
|
+
throw new UserError(`Skill already exists: ${displayPath}\nRe-run with --force to overwrite it, or remove it first.`);
|
|
137
141
|
}
|
|
138
142
|
await runMutations([
|
|
139
143
|
fileMutation.ensureDirectory({
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { getAgentConfig, resolveAgentSupport, resolveSkillDir, supportedAgents } from "./configs.js";
|
|
5
|
+
import { UserError } from "@poe-code/user-error";
|
|
5
6
|
import { hasOwnErrorCode } from "./error-codes.js";
|
|
6
7
|
import { appendExcludeBlock, removeExcludeBlock } from "./git-exclude.js";
|
|
7
8
|
import { resolveSkillReference } from "./resolve-skill-reference.js";
|
|
@@ -88,8 +89,9 @@ function formatResolutionFailureError(failures) {
|
|
|
88
89
|
lines.push(` - ${searchedPath}`);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
92
|
+
lines.push("Install one with poe-code skill install <agent> --name <name> --file <path>.");
|
|
91
93
|
}
|
|
92
|
-
return new
|
|
94
|
+
return new UserError(lines.join("\n"));
|
|
93
95
|
}
|
|
94
96
|
function copyDirectory(sourcePath, targetPath) {
|
|
95
97
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface AgentSkillConfig {
|
|
2
2
|
globalSkillDir: string;
|
|
3
3
|
localSkillDir: string;
|
|
4
|
+
/** Set only when the directories deviate from the agent's own dot-directory. */
|
|
5
|
+
dirNote?: string;
|
|
4
6
|
}
|
|
5
7
|
export type SkillScope = "global" | "local";
|
|
6
8
|
export declare const supportedAgents: readonly string[];
|
|
@@ -24,7 +24,8 @@ const agentSkillConfigs = {
|
|
|
24
24
|
},
|
|
25
25
|
goose: {
|
|
26
26
|
globalSkillDir: "~/.agents/skills",
|
|
27
|
-
localSkillDir: ".agents/skills"
|
|
27
|
+
localSkillDir: ".agents/skills",
|
|
28
|
+
dirNote: "Goose discovers skills from the shared .agents/skills convention, not a goose-specific directory."
|
|
28
29
|
}
|
|
29
30
|
};
|
|
30
31
|
export const supportedAgents = Object.freeze(Object.keys(agentSkillConfigs));
|
|
@@ -6,7 +6,9 @@ Shared YAML frontmatter parsing for poe-code packages.
|
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
8
|
import {
|
|
9
|
+
FrontmatterKindError,
|
|
9
10
|
FrontmatterParseError,
|
|
11
|
+
isFrontmatterKindError,
|
|
10
12
|
parseFrontmatter,
|
|
11
13
|
parseFrontmatterDocument,
|
|
12
14
|
stringifyFrontmatter
|
|
@@ -17,6 +19,7 @@ import {
|
|
|
17
19
|
- `parseFrontmatterDocument(source)` returns `{ frontmatter, body, errors, lineCounter }` for callers that need YAML diagnostics.
|
|
18
20
|
- `stringifyFrontmatter(frontmatter, body)` writes `---` fences, YAML, and the body.
|
|
19
21
|
- `FrontmatterParseError` is thrown for malformed frontmatter, invalid YAML, non-object frontmatter, and stringify failures.
|
|
22
|
+
- `FrontmatterKindError` extends `FrontmatterParseError` and carries `expectedKind` / `foundKind` so callers can report a document kind mismatch instead of a missing file. `isFrontmatterKindError(error)` narrows to it.
|
|
20
23
|
|
|
21
24
|
When no leading frontmatter block exists, parsing returns `{ frontmatter: {}, body: source }`.
|
|
22
25
|
The returned `body` is sliced from the original input and is otherwise byte-for-byte unchanged.
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { splitFrontmatterBlock, type FrontmatterBlock, type SplitFrontmatterResult } from "./fences.js";
|
|
2
|
-
export { FrontmatterParseError, parseFrontmatter, parseFrontmatterDocument, type ParsedFrontmatter, type ParsedFrontmatterDocument, type ParseFrontmatterOptions } from "./parse.js";
|
|
2
|
+
export { FrontmatterKindError, FrontmatterParseError, isFrontmatterKindError, parseFrontmatter, parseFrontmatterDocument, type ParsedFrontmatter, type ParsedFrontmatterDocument, type ParseFrontmatterOptions } from "./parse.js";
|
|
3
3
|
export { stringifyFrontmatter } from "./stringify.js";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { splitFrontmatterBlock } from "./fences.js";
|
|
2
|
-
export { FrontmatterParseError, parseFrontmatter, parseFrontmatterDocument } from "./parse.js";
|
|
2
|
+
export { FrontmatterKindError, FrontmatterParseError, isFrontmatterKindError, parseFrontmatter, parseFrontmatterDocument } from "./parse.js";
|
|
3
3
|
export { stringifyFrontmatter } from "./stringify.js";
|
|
@@ -16,5 +16,14 @@ export interface ParseFrontmatterOptions {
|
|
|
16
16
|
export declare class FrontmatterParseError extends Error {
|
|
17
17
|
constructor(message: string);
|
|
18
18
|
}
|
|
19
|
+
export declare class FrontmatterKindError extends FrontmatterParseError {
|
|
20
|
+
readonly expectedKind: string;
|
|
21
|
+
readonly foundKind: string;
|
|
22
|
+
constructor(message: string, kinds: {
|
|
23
|
+
expected: string;
|
|
24
|
+
found: string;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export declare function isFrontmatterKindError(error: unknown): error is FrontmatterKindError;
|
|
19
28
|
export declare function parseFrontmatter(source: string, options?: ParseFrontmatterOptions): ParsedFrontmatter;
|
|
20
29
|
export declare function parseFrontmatterDocument(source: string, options?: ParseFrontmatterOptions): ParsedFrontmatterDocument;
|
|
@@ -6,6 +6,22 @@ export class FrontmatterParseError extends Error {
|
|
|
6
6
|
this.name = "FrontmatterParseError";
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
export class FrontmatterKindError extends FrontmatterParseError {
|
|
10
|
+
expectedKind;
|
|
11
|
+
foundKind;
|
|
12
|
+
constructor(message, kinds) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "FrontmatterKindError";
|
|
15
|
+
this.expectedKind = kinds.expected;
|
|
16
|
+
this.foundKind = kinds.found;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function isFrontmatterKindError(error) {
|
|
20
|
+
return (error instanceof Error &&
|
|
21
|
+
error.name === "FrontmatterKindError" &&
|
|
22
|
+
typeof error.expectedKind === "string" &&
|
|
23
|
+
typeof error.foundKind === "string");
|
|
24
|
+
}
|
|
9
25
|
export function parseFrontmatter(source, options = {}) {
|
|
10
26
|
const split = splitFrontmatter(source);
|
|
11
27
|
if (split.raw === undefined) {
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Status of the agent output being rendered.
|
|
3
|
+
*
|
|
4
|
+
* `streaming` covers partial/in-progress content, which has not reached any outcome yet and so must not
|
|
5
|
+
* claim success. `success`/`error` are terminal outcomes known to the caller.
|
|
6
|
+
*/
|
|
7
|
+
export type AcpOutputState = "streaming" | "success" | "error";
|
|
8
|
+
export declare function renderAgentMessage(text: string, state?: AcpOutputState): void;
|
|
2
9
|
export declare function renderToolStart(kind: string, title: string): void;
|
|
3
10
|
export declare function renderToolComplete(kind: string): void;
|
|
4
11
|
export declare function renderReasoning(text: string): void;
|
|
@@ -25,8 +25,13 @@ function colorForKind(kind) {
|
|
|
25
25
|
function writeLine(line) {
|
|
26
26
|
getAcpWriter()(line);
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const STATE_GLYPHS = {
|
|
29
|
+
streaming: () => color.dim("·"),
|
|
30
|
+
success: () => color.green.bold("✓"),
|
|
31
|
+
error: () => color.red.bold("✗")
|
|
32
|
+
};
|
|
33
|
+
function agentPrefix(state) {
|
|
34
|
+
return `${STATE_GLYPHS[state]()} agent: `;
|
|
30
35
|
}
|
|
31
36
|
function formatCost(costUsd) {
|
|
32
37
|
return new Intl.NumberFormat("en-US", {
|
|
@@ -36,7 +41,7 @@ function formatCost(costUsd) {
|
|
|
36
41
|
maximumFractionDigits: 6
|
|
37
42
|
}).format(costUsd);
|
|
38
43
|
}
|
|
39
|
-
export function renderAgentMessage(text) {
|
|
44
|
+
export function renderAgentMessage(text, state = "streaming") {
|
|
40
45
|
const format = resolveOutputFormat();
|
|
41
46
|
if (format === "markdown") {
|
|
42
47
|
writeLine(`- **agent:** ${text}`);
|
|
@@ -47,7 +52,7 @@ export function renderAgentMessage(text) {
|
|
|
47
52
|
return;
|
|
48
53
|
}
|
|
49
54
|
const rendered = renderMarkdown(text).trimEnd();
|
|
50
|
-
writeLine(`${agentPrefix()}${rendered}`);
|
|
55
|
+
writeLine(`${agentPrefix(state)}${rendered}`);
|
|
51
56
|
}
|
|
52
57
|
export function renderToolStart(kind, title) {
|
|
53
58
|
const format = resolveOutputFormat();
|
|
@@ -85,7 +90,7 @@ export function renderReasoning(text) {
|
|
|
85
90
|
writeLine(JSON.stringify({ event: "reasoning", text }));
|
|
86
91
|
return;
|
|
87
92
|
}
|
|
88
|
-
writeLine(color.dim(`
|
|
93
|
+
writeLine(color.dim(` · ${truncate(text, 80)}`));
|
|
89
94
|
}
|
|
90
95
|
export function renderUsage(tokens) {
|
|
91
96
|
const format = resolveOutputFormat();
|
|
@@ -109,7 +114,7 @@ export function renderUsage(tokens) {
|
|
|
109
114
|
return;
|
|
110
115
|
}
|
|
111
116
|
writeLine("");
|
|
112
|
-
writeLine(color.
|
|
117
|
+
writeLine(color.dim(`· tokens: ${tokens.input} in${cached} → ${tokens.output} out${cost}`));
|
|
113
118
|
}
|
|
114
119
|
export function renderPermissionRejected(title) {
|
|
115
120
|
const format = resolveOutputFormat();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { renderAgentMessage, renderToolStart, renderToolComplete, renderReasoning, renderUsage, renderError, renderPermissionRejected } from "./components.js";
|
|
2
|
+
export type { AcpOutputState } from "./components.js";
|
|
2
3
|
export { getAcpWriter, withAcpWriter } from "./writer.js";
|
|
3
4
|
export type { AcpLineWriter } from "./writer.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare function formatCommandNotFound(input: {
|
|
2
2
|
unknownCommand: string;
|
|
3
3
|
helpCommand: string;
|
|
4
|
+
suggestions?: readonly string[];
|
|
4
5
|
}): {
|
|
5
6
|
label: string;
|
|
6
7
|
hint: string;
|
|
@@ -8,6 +9,7 @@ export declare function formatCommandNotFound(input: {
|
|
|
8
9
|
export declare function formatCommandNotFoundPanel(input: {
|
|
9
10
|
unknownCommand: string;
|
|
10
11
|
helpCommand: string;
|
|
12
|
+
suggestions?: readonly string[];
|
|
11
13
|
title?: string;
|
|
12
14
|
}): {
|
|
13
15
|
title: string;
|
|
@@ -5,15 +5,21 @@ export function formatCommandNotFound(input) {
|
|
|
5
5
|
const unknown = unknownInput.length > 0
|
|
6
6
|
? unknownInput
|
|
7
7
|
: "<command>";
|
|
8
|
+
const suggestions = input.suggestions ?? [];
|
|
9
|
+
const didYouMean = suggestions.length > 0
|
|
10
|
+
? `
|
|
11
|
+
${text.muted("Did you mean:")} ${suggestions.map((suggestion) => text.command(suggestion)).join(text.muted(", "))}${text.muted("?")}`
|
|
12
|
+
: "";
|
|
8
13
|
return {
|
|
9
|
-
label: `${typography.bold("Unknown command:")} ${text.command(unknown)}`,
|
|
14
|
+
label: `${typography.bold("Unknown command:")} ${text.command(unknown)}${didYouMean}`,
|
|
10
15
|
hint: `${text.muted("Run")} ${text.usageCommand(input.helpCommand)} ${text.muted("for available commands.")}`
|
|
11
16
|
};
|
|
12
17
|
}
|
|
13
18
|
export function formatCommandNotFoundPanel(input) {
|
|
14
19
|
const message = formatCommandNotFound({
|
|
15
20
|
unknownCommand: input.unknownCommand,
|
|
16
|
-
helpCommand: input.helpCommand
|
|
21
|
+
helpCommand: input.helpCommand,
|
|
22
|
+
suggestions: input.suggestions
|
|
17
23
|
});
|
|
18
24
|
return {
|
|
19
25
|
title: input.title ?? "command not found",
|