@scanton/phase2s 0.13.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/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/bin/phase2s.d.ts +3 -0
- package/dist/bin/phase2s.d.ts.map +1 -0
- package/dist/bin/phase2s.js +7 -0
- package/dist/bin/phase2s.js.map +1 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +499 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/core/agent.d.ts +63 -0
- package/dist/src/core/agent.d.ts.map +1 -0
- package/dist/src/core/agent.js +178 -0
- package/dist/src/core/agent.js.map +1 -0
- package/dist/src/core/config.d.ts +52 -0
- package/dist/src/core/config.d.ts.map +1 -0
- package/dist/src/core/config.js +103 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/conversation.d.ts +44 -0
- package/dist/src/core/conversation.d.ts.map +1 -0
- package/dist/src/core/conversation.js +128 -0
- package/dist/src/core/conversation.js.map +1 -0
- package/dist/src/core/memory.d.ts +24 -0
- package/dist/src/core/memory.d.ts.map +1 -0
- package/dist/src/core/memory.js +65 -0
- package/dist/src/core/memory.js.map +1 -0
- package/dist/src/mcp/server.d.ts +73 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +215 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/providers/codex.d.ts +28 -0
- package/dist/src/providers/codex.d.ts.map +1 -0
- package/dist/src/providers/codex.js +162 -0
- package/dist/src/providers/codex.js.map +1 -0
- package/dist/src/providers/index.d.ts +6 -0
- package/dist/src/providers/index.d.ts.map +1 -0
- package/dist/src/providers/index.js +13 -0
- package/dist/src/providers/index.js.map +1 -0
- package/dist/src/providers/openai.d.ts +36 -0
- package/dist/src/providers/openai.d.ts.map +1 -0
- package/dist/src/providers/openai.js +117 -0
- package/dist/src/providers/openai.js.map +1 -0
- package/dist/src/providers/types.d.ts +34 -0
- package/dist/src/providers/types.d.ts.map +1 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/providers/types.js.map +1 -0
- package/dist/src/skills/index.d.ts +3 -0
- package/dist/src/skills/index.d.ts.map +1 -0
- package/dist/src/skills/index.js +2 -0
- package/dist/src/skills/index.js.map +1 -0
- package/dist/src/skills/loader.d.ts +23 -0
- package/dist/src/skills/loader.d.ts.map +1 -0
- package/dist/src/skills/loader.js +136 -0
- package/dist/src/skills/loader.js.map +1 -0
- package/dist/src/skills/types.d.ts +14 -0
- package/dist/src/skills/types.d.ts.map +1 -0
- package/dist/src/skills/types.js +2 -0
- package/dist/src/skills/types.js.map +1 -0
- package/dist/src/tools/file-read.d.ts +3 -0
- package/dist/src/tools/file-read.d.ts.map +1 -0
- package/dist/src/tools/file-read.js +50 -0
- package/dist/src/tools/file-read.js.map +1 -0
- package/dist/src/tools/file-write.d.ts +3 -0
- package/dist/src/tools/file-write.d.ts.map +1 -0
- package/dist/src/tools/file-write.js +90 -0
- package/dist/src/tools/file-write.js.map +1 -0
- package/dist/src/tools/glob-tool.d.ts +3 -0
- package/dist/src/tools/glob-tool.d.ts.map +1 -0
- package/dist/src/tools/glob-tool.js +38 -0
- package/dist/src/tools/glob-tool.js.map +1 -0
- package/dist/src/tools/grep-tool.d.ts +3 -0
- package/dist/src/tools/grep-tool.d.ts.map +1 -0
- package/dist/src/tools/grep-tool.js +59 -0
- package/dist/src/tools/grep-tool.js.map +1 -0
- package/dist/src/tools/index.d.ts +5 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +17 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/registry.d.ts +13 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +50 -0
- package/dist/src/tools/registry.js.map +1 -0
- package/dist/src/tools/sandbox.d.ts +29 -0
- package/dist/src/tools/sandbox.d.ts.map +1 -0
- package/dist/src/tools/sandbox.js +84 -0
- package/dist/src/tools/sandbox.js.map +1 -0
- package/dist/src/tools/shell.d.ts +10 -0
- package/dist/src/tools/shell.d.ts.map +1 -0
- package/dist/src/tools/shell.js +108 -0
- package/dist/src/tools/shell.js.map +1 -0
- package/dist/src/tools/types.d.ts +25 -0
- package/dist/src/tools/types.d.ts.map +1 -0
- package/dist/src/tools/types.js +54 -0
- package/dist/src/tools/types.js.map +1 -0
- package/dist/src/utils/logger.d.ts +10 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +25 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/prompt.d.ts +3 -0
- package/dist/src/utils/prompt.d.ts.map +1 -0
- package/dist/src/utils/prompt.js +26 -0
- package/dist/src/utils/prompt.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ChatCompletionChunk } from "openai/resources/chat/completions.js";
|
|
2
|
+
import type { Config } from "../core/config.js";
|
|
3
|
+
import type { Provider, Message, ProviderEvent } from "./types.js";
|
|
4
|
+
import type { OpenAIFunctionDef } from "../tools/types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Structural interface for the OpenAI client's streaming chat.completions.create.
|
|
7
|
+
* Exported so tests can inject a typed stub without importing the full OpenAI SDK class.
|
|
8
|
+
* We only use the streaming overload (stream: true) — non-streaming is no longer needed.
|
|
9
|
+
*/
|
|
10
|
+
export interface OpenAIClientLike {
|
|
11
|
+
chat: {
|
|
12
|
+
completions: {
|
|
13
|
+
create(params: {
|
|
14
|
+
model: string;
|
|
15
|
+
messages: unknown[];
|
|
16
|
+
tools?: unknown[];
|
|
17
|
+
stream: true;
|
|
18
|
+
}): Promise<AsyncIterable<ChatCompletionChunk>>;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Direct OpenAI API provider.
|
|
24
|
+
*
|
|
25
|
+
* Streams responses via chat completions with stream: true.
|
|
26
|
+
* Tool call fragments are accumulated per-index across chunks and emitted
|
|
27
|
+
* as a single tool_calls event after the stream ends.
|
|
28
|
+
*/
|
|
29
|
+
export declare class OpenAIProvider implements Provider {
|
|
30
|
+
name: string;
|
|
31
|
+
private client;
|
|
32
|
+
private model;
|
|
33
|
+
constructor(config: Config, client?: OpenAIClientLike);
|
|
34
|
+
chatStream(messages: Message[], tools: OpenAIFunctionDef[], options?: import("./types.js").ChatStreamOptions): AsyncIterable<ProviderEvent>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../src/providers/openai.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAY,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE;QACJ,WAAW,EAAE;YACX,MAAM,CAAC,MAAM,EAAE;gBACb,KAAK,EAAE,MAAM,CAAC;gBACd,QAAQ,EAAE,OAAO,EAAE,CAAC;gBACpB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;gBAClB,MAAM,EAAE,IAAI,CAAC;aACd,GAAG,OAAO,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,CAAC;SACjD,CAAC;KACH,CAAC;CACH;AAED;;;;;;GAMG;AACH,qBAAa,cAAe,YAAW,QAAQ;IAC7C,IAAI,SAAgB;IACpB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,gBAAgB;IAY9C,UAAU,CACf,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,iBAAiB,EAAE,EAC1B,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,iBAAiB,GAC/C,aAAa,CAAC,aAAa,CAAC;CAiGhC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { log } from "../utils/logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* Direct OpenAI API provider.
|
|
5
|
+
*
|
|
6
|
+
* Streams responses via chat completions with stream: true.
|
|
7
|
+
* Tool call fragments are accumulated per-index across chunks and emitted
|
|
8
|
+
* as a single tool_calls event after the stream ends.
|
|
9
|
+
*/
|
|
10
|
+
export class OpenAIProvider {
|
|
11
|
+
name = "openai-api";
|
|
12
|
+
client;
|
|
13
|
+
model;
|
|
14
|
+
constructor(config, client) {
|
|
15
|
+
if (!client && !config.apiKey) {
|
|
16
|
+
throw new Error("OpenAI API key is required for the openai-api provider. " +
|
|
17
|
+
"Set OPENAI_API_KEY environment variable or apiKey in .phase2s.yaml");
|
|
18
|
+
}
|
|
19
|
+
// The real OpenAI class satisfies OpenAIClientLike structurally; one cast at construction.
|
|
20
|
+
this.client = client ?? new OpenAI({ apiKey: config.apiKey });
|
|
21
|
+
this.model = config.model;
|
|
22
|
+
}
|
|
23
|
+
async *chatStream(messages, tools, options) {
|
|
24
|
+
const openaiMessages = messages.map((m) => {
|
|
25
|
+
if (m.role === "tool") {
|
|
26
|
+
return {
|
|
27
|
+
role: "tool",
|
|
28
|
+
content: m.content,
|
|
29
|
+
tool_call_id: m.toolCallId,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (m.role === "assistant" && m.toolCalls?.length) {
|
|
33
|
+
return {
|
|
34
|
+
role: "assistant",
|
|
35
|
+
content: m.content || null,
|
|
36
|
+
tool_calls: m.toolCalls.map((tc) => ({
|
|
37
|
+
id: tc.id,
|
|
38
|
+
type: "function",
|
|
39
|
+
function: {
|
|
40
|
+
name: tc.name,
|
|
41
|
+
arguments: tc.arguments,
|
|
42
|
+
},
|
|
43
|
+
})),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
role: m.role,
|
|
48
|
+
content: m.content,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
const stream = await this.client.chat.completions.create({
|
|
52
|
+
model: options?.model ?? this.model,
|
|
53
|
+
messages: openaiMessages,
|
|
54
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
55
|
+
stream: true,
|
|
56
|
+
});
|
|
57
|
+
let lastFinishReason = null;
|
|
58
|
+
// Per-index accumulator for streaming tool call argument fragments.
|
|
59
|
+
// OpenAI sends tool call data split across multiple chunks, indexed by position.
|
|
60
|
+
const toolCallAccum = [];
|
|
61
|
+
for await (const chunk of stream) {
|
|
62
|
+
const choice = chunk.choices[0];
|
|
63
|
+
if (!choice)
|
|
64
|
+
continue;
|
|
65
|
+
const delta = choice.delta;
|
|
66
|
+
const finishReason = choice.finish_reason ?? null;
|
|
67
|
+
if (finishReason)
|
|
68
|
+
lastFinishReason = finishReason;
|
|
69
|
+
// Early-exit for terminal finish reasons that block normal output.
|
|
70
|
+
// These come on the final chunk which has no content delta.
|
|
71
|
+
if (finishReason === "length") {
|
|
72
|
+
log.warn("Response truncated (finish_reason: length). Consider a shorter prompt.");
|
|
73
|
+
yield { type: "text", content: "\n\n[Note: response was truncated]" };
|
|
74
|
+
yield { type: "done", stopReason: "length" };
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (finishReason === "content_filter") {
|
|
78
|
+
log.warn("Response blocked by OpenAI content filter (finish_reason: content_filter).");
|
|
79
|
+
yield { type: "text", content: "[Response blocked by content filter]" };
|
|
80
|
+
yield { type: "done", stopReason: "content_filter" };
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Yield text delta if present
|
|
84
|
+
if (delta?.content) {
|
|
85
|
+
yield { type: "text", content: delta.content };
|
|
86
|
+
}
|
|
87
|
+
// Accumulate tool call fragments by chunk index.
|
|
88
|
+
// - The 'id' and 'function.name' arrive only in the first chunk for each index.
|
|
89
|
+
// - 'function.arguments' is split across multiple chunks and must be concatenated.
|
|
90
|
+
for (const tcDelta of delta?.tool_calls ?? []) {
|
|
91
|
+
const i = tcDelta.index;
|
|
92
|
+
if (!toolCallAccum[i]) {
|
|
93
|
+
toolCallAccum[i] = { id: "", name: "", arguments: "" };
|
|
94
|
+
}
|
|
95
|
+
if (tcDelta.id)
|
|
96
|
+
toolCallAccum[i].id = tcDelta.id;
|
|
97
|
+
if (tcDelta.function?.name)
|
|
98
|
+
toolCallAccum[i].name = tcDelta.function.name;
|
|
99
|
+
if (tcDelta.function?.arguments)
|
|
100
|
+
toolCallAccum[i].arguments += tcDelta.function.arguments;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Filter out any sparse-array holes (undefined slots from non-contiguous indices).
|
|
104
|
+
// Normally OpenAI sends tool call indices as 0, 1, 2... but guard against gaps.
|
|
105
|
+
const validCalls = toolCallAccum.filter((tc) => tc !== undefined && tc.id !== "");
|
|
106
|
+
if (validCalls.length > 0) {
|
|
107
|
+
const calls = validCalls.map((tc) => ({
|
|
108
|
+
id: tc.id,
|
|
109
|
+
name: tc.name,
|
|
110
|
+
arguments: tc.arguments, // raw JSON string — parsed by tool executor
|
|
111
|
+
}));
|
|
112
|
+
yield { type: "tool_calls", calls };
|
|
113
|
+
}
|
|
114
|
+
yield { type: "done", stopReason: lastFinishReason ?? "stop" };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/providers/openai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { OpenAIFunctionDef } from "../tools/types.js";
|
|
2
|
+
export interface Message {
|
|
3
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
4
|
+
content: string;
|
|
5
|
+
toolCallId?: string;
|
|
6
|
+
toolCalls?: ToolCall[];
|
|
7
|
+
}
|
|
8
|
+
export interface ToolCall {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
arguments: string;
|
|
12
|
+
}
|
|
13
|
+
export type ProviderEvent = {
|
|
14
|
+
type: "text";
|
|
15
|
+
content: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "tool_calls";
|
|
18
|
+
calls: ToolCall[];
|
|
19
|
+
} | {
|
|
20
|
+
type: "done";
|
|
21
|
+
stopReason: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: "error";
|
|
24
|
+
error: string;
|
|
25
|
+
};
|
|
26
|
+
export interface ChatStreamOptions {
|
|
27
|
+
model?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface Provider {
|
|
30
|
+
name: string;
|
|
31
|
+
/** Stream response events: text deltas, tool_calls, done, or error. */
|
|
32
|
+
chatStream(messages: Message[], tools: OpenAIFunctionDef[], options?: ChatStreamOptions): AsyncIterable<ProviderEvent>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IAEb,uEAAuE;IACvE,UAAU,CACR,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,iBAAiB,EAAE,EAC1B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,aAAa,CAAC,aAAa,CAAC,CAAC;CACjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/providers/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/skills/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/skills/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Skill } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Load skills from SKILL.md files in a directory.
|
|
4
|
+
*
|
|
5
|
+
* Follows a convention similar to gstack: each skill is a directory
|
|
6
|
+
* containing a SKILL.md file with YAML-like frontmatter.
|
|
7
|
+
*
|
|
8
|
+
* Compatible with the SKILL.md standard used by:
|
|
9
|
+
* - phase2s (.phase2s/skills/)
|
|
10
|
+
* - Codex CLI (~/.codex/skills/)
|
|
11
|
+
* - gstack/Claude Code (~/.claude/skills/)
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadSkillsFromDir(dir: string): Promise<Skill[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Load skills from all standard locations (in priority order):
|
|
16
|
+
* 1. .phase2s/skills/ — current project skills (highest priority)
|
|
17
|
+
* 2. ~/.phase2s/skills/ — global user skills
|
|
18
|
+
* 3. ~/.codex/skills/ — Codex CLI's native skill directory (cross-tool compatibility)
|
|
19
|
+
*
|
|
20
|
+
* Deduplication: first skill found with a given name wins (project > global > codex).
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadAllSkills(): Promise<Skill[]>;
|
|
23
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/skills/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAkCrE;AA4DD;;;;;;;GAOG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAwBtD"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
/**
|
|
5
|
+
* Load skills from SKILL.md files in a directory.
|
|
6
|
+
*
|
|
7
|
+
* Follows a convention similar to gstack: each skill is a directory
|
|
8
|
+
* containing a SKILL.md file with YAML-like frontmatter.
|
|
9
|
+
*
|
|
10
|
+
* Compatible with the SKILL.md standard used by:
|
|
11
|
+
* - phase2s (.phase2s/skills/)
|
|
12
|
+
* - Codex CLI (~/.codex/skills/)
|
|
13
|
+
* - gstack/Claude Code (~/.claude/skills/)
|
|
14
|
+
*/
|
|
15
|
+
export async function loadSkillsFromDir(dir) {
|
|
16
|
+
const skills = [];
|
|
17
|
+
const absDir = resolve(dir);
|
|
18
|
+
let entries;
|
|
19
|
+
try {
|
|
20
|
+
entries = await readdir(absDir);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return skills;
|
|
24
|
+
}
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const entryPath = join(absDir, entry);
|
|
27
|
+
const entryStat = await stat(entryPath).catch(() => null);
|
|
28
|
+
if (entryStat?.isDirectory()) {
|
|
29
|
+
// Look for SKILL.md inside the directory
|
|
30
|
+
const skillPath = join(entryPath, "SKILL.md");
|
|
31
|
+
const skill = await parseSkillFile(skillPath);
|
|
32
|
+
if (skill) {
|
|
33
|
+
skill.name = skill.name || entry;
|
|
34
|
+
skills.push(skill);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (entry.endsWith(".md") && entry !== "README.md") {
|
|
38
|
+
// Also support flat .md files as skills
|
|
39
|
+
const skill = await parseSkillFile(entryPath);
|
|
40
|
+
if (skill) {
|
|
41
|
+
skill.name = skill.name || entry.replace(".md", "");
|
|
42
|
+
skills.push(skill);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return skills;
|
|
47
|
+
}
|
|
48
|
+
async function parseSkillFile(path) {
|
|
49
|
+
let content;
|
|
50
|
+
try {
|
|
51
|
+
content = await readFile(path, "utf-8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
// Parse YAML frontmatter (--- delimited, handles \r\n line endings)
|
|
57
|
+
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
58
|
+
let name = "";
|
|
59
|
+
let description = "";
|
|
60
|
+
let triggerPhrases = [];
|
|
61
|
+
let promptTemplate;
|
|
62
|
+
let skill_model;
|
|
63
|
+
let skill_retries;
|
|
64
|
+
if (frontmatterMatch) {
|
|
65
|
+
const rawMeta = frontmatterMatch[1];
|
|
66
|
+
promptTemplate = frontmatterMatch[2].trim();
|
|
67
|
+
// Parse frontmatter with the yaml library (handles multi-line, arrays, quoted strings)
|
|
68
|
+
let meta = {};
|
|
69
|
+
try {
|
|
70
|
+
meta = parseYaml(rawMeta) ?? {};
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Malformed frontmatter — treat as no frontmatter
|
|
74
|
+
}
|
|
75
|
+
if (typeof meta.name === "string")
|
|
76
|
+
name = meta.name;
|
|
77
|
+
if (typeof meta.description === "string")
|
|
78
|
+
description = meta.description;
|
|
79
|
+
// triggers can be a YAML array or a comma-separated string
|
|
80
|
+
if (Array.isArray(meta.triggers)) {
|
|
81
|
+
triggerPhrases = meta.triggers.filter((t) => typeof t === "string");
|
|
82
|
+
}
|
|
83
|
+
else if (typeof meta.triggers === "string") {
|
|
84
|
+
triggerPhrases = meta.triggers.split(",").map((s) => s.trim()).filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
if (typeof meta.model === "string" && meta.model.length > 0)
|
|
87
|
+
skill_model = meta.model;
|
|
88
|
+
if (typeof meta.retries === "number" && meta.retries > 0)
|
|
89
|
+
skill_retries = meta.retries;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
promptTemplate = content.trim();
|
|
93
|
+
}
|
|
94
|
+
const skill = {
|
|
95
|
+
name,
|
|
96
|
+
description,
|
|
97
|
+
triggerPhrases,
|
|
98
|
+
promptTemplate,
|
|
99
|
+
sourcePath: path,
|
|
100
|
+
};
|
|
101
|
+
if (skill_model !== undefined)
|
|
102
|
+
skill.model = skill_model;
|
|
103
|
+
if (skill_retries !== undefined)
|
|
104
|
+
skill.retries = skill_retries;
|
|
105
|
+
return skill;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Load skills from all standard locations (in priority order):
|
|
109
|
+
* 1. .phase2s/skills/ — current project skills (highest priority)
|
|
110
|
+
* 2. ~/.phase2s/skills/ — global user skills
|
|
111
|
+
* 3. ~/.codex/skills/ — Codex CLI's native skill directory (cross-tool compatibility)
|
|
112
|
+
*
|
|
113
|
+
* Deduplication: first skill found with a given name wins (project > global > codex).
|
|
114
|
+
*/
|
|
115
|
+
export async function loadAllSkills() {
|
|
116
|
+
const skills = [];
|
|
117
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
118
|
+
const dirs = [
|
|
119
|
+
join(process.cwd(), ".phase2s", "skills"),
|
|
120
|
+
join(home, ".phase2s", "skills"),
|
|
121
|
+
join(home, ".codex", "skills"), // Codex CLI native skills — same SKILL.md format
|
|
122
|
+
];
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
for (const dir of dirs) {
|
|
125
|
+
const loaded = await loadSkillsFromDir(dir);
|
|
126
|
+
for (const skill of loaded) {
|
|
127
|
+
// Deduplicate by name: project skills override global, global override codex defaults
|
|
128
|
+
if (!seen.has(skill.name)) {
|
|
129
|
+
seen.add(skill.name);
|
|
130
|
+
skills.push(skill);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return skills;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/skills/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Skill {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
triggerPhrases: string[];
|
|
5
|
+
/** The prompt template that gets injected when the skill is invoked */
|
|
6
|
+
promptTemplate: string;
|
|
7
|
+
/** Original file path the skill was loaded from */
|
|
8
|
+
sourcePath?: string;
|
|
9
|
+
/** Model alias ("fast" | "smart") or literal model string to use for this skill */
|
|
10
|
+
model?: string;
|
|
11
|
+
/** Number of satori retries (enables satori mode when > 0) */
|
|
12
|
+
retries?: number;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/skills/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uEAAuE;IACvE,cAAc,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/skills/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-read.d.ts","sourceRoot":"","sources":["../../../src/tools/file-read.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AAgB7D,eAAO,MAAM,YAAY,EAAE,cAoC1B,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { assertInSandbox } from "./sandbox.js";
|
|
4
|
+
const params = z.object({
|
|
5
|
+
path: z.string().describe("Path to the file to read (relative to project directory)"),
|
|
6
|
+
startLine: z.number().min(1).optional().describe("Start reading from this line (1-based)"),
|
|
7
|
+
endLine: z.number().min(1).optional().describe("Stop reading at this line (inclusive)"),
|
|
8
|
+
});
|
|
9
|
+
/** Sanitize an error message before returning it to the LLM — strip absolute paths. */
|
|
10
|
+
function sanitizeError(err) {
|
|
11
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12
|
+
// Remove absolute paths from error strings (e.g. ENOENT: no such file or directory, open '/home/user/...')
|
|
13
|
+
return msg.replace(/,\s*open\s+'[^']*'/g, "").replace(/\/[^\s']*/g, "<path>");
|
|
14
|
+
}
|
|
15
|
+
export const fileReadTool = {
|
|
16
|
+
name: "file_read",
|
|
17
|
+
description: "Read the contents of a file. Optionally specify a line range. Paths are relative to the project directory.",
|
|
18
|
+
parameters: params,
|
|
19
|
+
async execute(raw) {
|
|
20
|
+
const args = params.parse(raw);
|
|
21
|
+
// Sandbox: reject paths outside the project directory (realpath-based, blocks symlink escapes)
|
|
22
|
+
let fullPath;
|
|
23
|
+
try {
|
|
24
|
+
fullPath = await assertInSandbox(args.path);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
output: "",
|
|
30
|
+
error: err instanceof Error ? err.message : `Path outside project directory: ${args.path}`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const content = await readFile(fullPath, "utf-8");
|
|
35
|
+
if (args.startLine !== undefined || args.endLine !== undefined) {
|
|
36
|
+
const lines = content.split("\n");
|
|
37
|
+
const start = (args.startLine ?? 1) - 1; // already validated min(1), so min index is 0
|
|
38
|
+
const end = args.endLine ?? lines.length;
|
|
39
|
+
const slice = lines.slice(start, end);
|
|
40
|
+
const numbered = slice.map((line, i) => `${start + i + 1}\t${line}`);
|
|
41
|
+
return { success: true, output: numbered.join("\n") };
|
|
42
|
+
}
|
|
43
|
+
return { success: true, output: content };
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return { success: false, output: "", error: sanitizeError(err) };
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=file-read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-read.js","sourceRoot":"","sources":["../../../src/tools/file-read.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-write.d.ts","sourceRoot":"","sources":["../../../src/tools/file-write.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AAe7D,eAAO,MAAM,aAAa,EAAE,cA8E3B,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { writeFile, mkdir, access } from "node:fs/promises";
|
|
3
|
+
import { resolve, dirname, sep } from "node:path";
|
|
4
|
+
import { assertInSandbox } from "./sandbox.js";
|
|
5
|
+
/** Sanitize an error message before returning it to the LLM — strip absolute paths. */
|
|
6
|
+
function sanitizeError(err) {
|
|
7
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8
|
+
return msg.replace(/,\s*open\s+'[^']*'/g, "").replace(/\/[^\s']*/g, "<path>");
|
|
9
|
+
}
|
|
10
|
+
const params = z.object({
|
|
11
|
+
path: z.string().describe("Path to the file to write (relative to project directory)"),
|
|
12
|
+
content: z.string().describe("Content to write to the file"),
|
|
13
|
+
createDirs: z.boolean().optional().describe("Create parent directories if they don't exist"),
|
|
14
|
+
});
|
|
15
|
+
export const fileWriteTool = {
|
|
16
|
+
name: "file_write",
|
|
17
|
+
description: "Write content to a file. Creates the file if it doesn't exist. Paths are relative to the project directory.",
|
|
18
|
+
parameters: params,
|
|
19
|
+
async execute(raw) {
|
|
20
|
+
const args = params.parse(raw);
|
|
21
|
+
// For files that need createDirs, we need to mkdir first so that
|
|
22
|
+
// realpath() can resolve the parent directory. We use a lexical resolve
|
|
23
|
+
// pre-check before mkdir, then assertInSandbox after dirs exist.
|
|
24
|
+
const lexicalFullPath = resolve(args.path);
|
|
25
|
+
// Capture cwd once — used for both the pre-check and passed to assertInSandbox.
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
if (args.createDirs) {
|
|
28
|
+
// Pre-check using lexical resolve before creating dirs (prevents creating
|
|
29
|
+
// dirs outside the project before we can realpath-check them).
|
|
30
|
+
if (!lexicalFullPath.startsWith(cwd + sep) && lexicalFullPath !== cwd) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
output: "",
|
|
34
|
+
error: `Path outside project directory: ${args.path}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
await mkdir(dirname(lexicalFullPath), { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
// Sandbox: reject paths outside the project directory (realpath-based, blocks symlink escapes)
|
|
40
|
+
// After dirs are created, realpath() can resolve the parent reliably.
|
|
41
|
+
let fullPath;
|
|
42
|
+
try {
|
|
43
|
+
fullPath = await assertInSandbox(args.path, cwd);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
output: "",
|
|
49
|
+
error: err instanceof Error ? err.message : `Path outside project directory: ${args.path}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Guard: reject near-empty writes to existing files (silent truncation is data loss).
|
|
53
|
+
// Trim check catches whitespace-only content that would also effectively destroy a file.
|
|
54
|
+
if (args.content.trim() === "") {
|
|
55
|
+
try {
|
|
56
|
+
await access(fullPath);
|
|
57
|
+
// File exists — refuse the empty write
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
output: "",
|
|
61
|
+
error: `Refusing to truncate existing file to empty: ${args.path}. Pass non-empty content.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// File doesn't exist — writing empty is fine (creates the file)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
// Check if file exists to produce an informative log
|
|
70
|
+
let existed = false;
|
|
71
|
+
try {
|
|
72
|
+
await access(fullPath);
|
|
73
|
+
existed = true;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// doesn't exist
|
|
77
|
+
}
|
|
78
|
+
await writeFile(fullPath, args.content, "utf-8");
|
|
79
|
+
const verb = existed ? "Overwrote" : "Wrote";
|
|
80
|
+
if (existed) {
|
|
81
|
+
process.stdout.write(` [file_write] Overwriting existing file: ${args.path}\n`);
|
|
82
|
+
}
|
|
83
|
+
return { success: true, output: `${verb} ${args.content.length} bytes to ${args.path}` };
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
return { success: false, output: "", error: sanitizeError(err) };
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=file-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-write.js","sourceRoot":"","sources":["../../../src/tools/file-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob-tool.d.ts","sourceRoot":"","sources":["../../../src/tools/glob-tool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AAQ7D,eAAO,MAAM,QAAQ,EAAE,cA+BtB,CAAC"}
|