gitclaw 0.3.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.
Files changed (58) hide show
  1. package/README.md +440 -0
  2. package/dist/agents.d.ts +8 -0
  3. package/dist/agents.js +82 -0
  4. package/dist/audit.d.ts +27 -0
  5. package/dist/audit.js +55 -0
  6. package/dist/compliance.d.ts +30 -0
  7. package/dist/compliance.js +108 -0
  8. package/dist/config.d.ts +11 -0
  9. package/dist/config.js +43 -0
  10. package/dist/examples.d.ts +6 -0
  11. package/dist/examples.js +40 -0
  12. package/dist/exports.d.ts +13 -0
  13. package/dist/exports.js +6 -0
  14. package/dist/hooks.d.ts +24 -0
  15. package/dist/hooks.js +108 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.js +542 -0
  18. package/dist/knowledge.d.ts +17 -0
  19. package/dist/knowledge.js +55 -0
  20. package/dist/loader.d.ts +64 -0
  21. package/dist/loader.js +222 -0
  22. package/dist/sandbox.d.ts +28 -0
  23. package/dist/sandbox.js +54 -0
  24. package/dist/sdk-hooks.d.ts +8 -0
  25. package/dist/sdk-hooks.js +31 -0
  26. package/dist/sdk-types.d.ts +127 -0
  27. package/dist/sdk-types.js +1 -0
  28. package/dist/sdk.d.ts +6 -0
  29. package/dist/sdk.js +444 -0
  30. package/dist/session.d.ts +15 -0
  31. package/dist/session.js +127 -0
  32. package/dist/skills.d.ts +18 -0
  33. package/dist/skills.js +104 -0
  34. package/dist/tool-loader.d.ts +3 -0
  35. package/dist/tool-loader.js +138 -0
  36. package/dist/tools/cli.d.ts +3 -0
  37. package/dist/tools/cli.js +86 -0
  38. package/dist/tools/index.d.ts +13 -0
  39. package/dist/tools/index.js +29 -0
  40. package/dist/tools/memory.d.ts +3 -0
  41. package/dist/tools/memory.js +128 -0
  42. package/dist/tools/read.d.ts +3 -0
  43. package/dist/tools/read.js +46 -0
  44. package/dist/tools/sandbox-cli.d.ts +4 -0
  45. package/dist/tools/sandbox-cli.js +48 -0
  46. package/dist/tools/sandbox-memory.d.ts +4 -0
  47. package/dist/tools/sandbox-memory.js +117 -0
  48. package/dist/tools/sandbox-read.d.ts +4 -0
  49. package/dist/tools/sandbox-read.js +25 -0
  50. package/dist/tools/sandbox-write.d.ts +4 -0
  51. package/dist/tools/sandbox-write.js +26 -0
  52. package/dist/tools/shared.d.ts +38 -0
  53. package/dist/tools/shared.js +69 -0
  54. package/dist/tools/write.d.ts +3 -0
  55. package/dist/tools/write.js +28 -0
  56. package/dist/workflows.d.ts +8 -0
  57. package/dist/workflows.js +81 -0
  58. package/package.json +57 -0
package/dist/sdk.js ADDED
@@ -0,0 +1,444 @@
1
+ import { Agent } from "@mariozechner/pi-agent-core";
2
+ import { loadAgent } from "./loader.js";
3
+ import { createBuiltinTools } from "./tools/index.js";
4
+ import { createSandboxContext } from "./sandbox.js";
5
+ import { loadHooksConfig, runHooks, wrapToolWithHooks } from "./hooks.js";
6
+ import { loadDeclarativeTools } from "./tool-loader.js";
7
+ import { buildTypeboxSchema } from "./tool-loader.js";
8
+ import { wrapToolWithProgrammaticHooks } from "./sdk-hooks.js";
9
+ import { initLocalSession } from "./session.js";
10
+ function createChannel() {
11
+ const buffer = [];
12
+ let resolve = null;
13
+ let done = false;
14
+ return {
15
+ push(v) {
16
+ if (resolve) {
17
+ resolve({ value: v, done: false });
18
+ resolve = null;
19
+ }
20
+ else {
21
+ buffer.push(v);
22
+ }
23
+ },
24
+ finish() {
25
+ done = true;
26
+ if (resolve) {
27
+ resolve({ value: undefined, done: true });
28
+ resolve = null;
29
+ }
30
+ },
31
+ pull() {
32
+ if (buffer.length) {
33
+ return Promise.resolve({ value: buffer.shift(), done: false });
34
+ }
35
+ if (done) {
36
+ return Promise.resolve({ value: undefined, done: true });
37
+ }
38
+ return new Promise((r) => { resolve = r; });
39
+ },
40
+ };
41
+ }
42
+ // ── Convert GCToolDefinition → AgentTool ───────────────────────────────
43
+ function toAgentTool(def) {
44
+ const schema = buildTypeboxSchema(def.inputSchema);
45
+ return {
46
+ name: def.name,
47
+ label: def.name,
48
+ description: def.description,
49
+ parameters: schema,
50
+ execute: async (_toolCallId, params, signal) => {
51
+ const result = await def.handler(params, signal);
52
+ const text = typeof result === "string" ? result : result.text;
53
+ const details = typeof result === "object" && "details" in result
54
+ ? result.details
55
+ : undefined;
56
+ return { content: [{ type: "text", text }], details };
57
+ },
58
+ };
59
+ }
60
+ // ── Extract text/thinking from AssistantMessage ────────────────────────
61
+ function extractContent(msg) {
62
+ let text = "";
63
+ let thinking = "";
64
+ for (const block of msg.content) {
65
+ if (block.type === "text")
66
+ text += block.text;
67
+ if (block.type === "thinking")
68
+ thinking += block.thinking;
69
+ }
70
+ return { text, thinking };
71
+ }
72
+ // ── query() ────────────────────────────────────────────────────────────
73
+ export function query(options) {
74
+ const channel = createChannel();
75
+ const collectedMessages = [];
76
+ const ac = options.abortController ?? new AbortController();
77
+ // These are set once the agent is loaded (async init below)
78
+ let _sessionId = options.sessionId ?? "";
79
+ let _manifest = null;
80
+ // Accumulate streaming deltas for the current message
81
+ let accText = "";
82
+ let accThinking = "";
83
+ function pushMsg(msg) {
84
+ collectedMessages.push(msg);
85
+ channel.push(msg);
86
+ }
87
+ // Sandbox context (hoisted for cleanup in catch)
88
+ let sandboxCtx;
89
+ // Local session (hoisted for cleanup in catch)
90
+ let localSession;
91
+ // Async initialization + run
92
+ const runPromise = (async () => {
93
+ // Validate mutually exclusive options
94
+ if (options.repo && options.sandbox) {
95
+ throw new Error("repo and sandbox options are mutually exclusive");
96
+ }
97
+ let dir = options.dir ?? process.cwd();
98
+ // Local repo mode
99
+ if (options.repo) {
100
+ const token = options.repo.token || process.env.GITHUB_TOKEN || process.env.GIT_TOKEN;
101
+ if (!token) {
102
+ throw new Error("repo.token, GITHUB_TOKEN, or GIT_TOKEN is required with repo option");
103
+ }
104
+ localSession = initLocalSession({
105
+ url: options.repo.url,
106
+ token,
107
+ dir: options.repo.dir || dir,
108
+ session: options.repo.session,
109
+ });
110
+ dir = localSession.dir;
111
+ }
112
+ // 1. Load agent
113
+ const loaded = await loadAgent(dir, options.model, options.env);
114
+ _manifest = loaded.manifest;
115
+ _sessionId = _sessionId || loaded.sessionId;
116
+ // 2. Apply system prompt overrides
117
+ let systemPrompt = loaded.systemPrompt;
118
+ if (options.systemPrompt !== undefined) {
119
+ systemPrompt = options.systemPrompt;
120
+ }
121
+ if (options.systemPromptSuffix) {
122
+ systemPrompt += "\n\n" + options.systemPromptSuffix;
123
+ }
124
+ // 3. Build tools (with optional sandbox)
125
+ if (options.sandbox) {
126
+ const sandboxConfig = options.sandbox === true
127
+ ? { provider: "e2b" }
128
+ : options.sandbox;
129
+ sandboxCtx = await createSandboxContext(sandboxConfig, dir);
130
+ await sandboxCtx.gitMachine.start();
131
+ }
132
+ let tools = [];
133
+ if (!options.replaceBuiltinTools) {
134
+ tools = createBuiltinTools({
135
+ dir,
136
+ timeout: loaded.manifest.runtime.timeout,
137
+ sandbox: sandboxCtx,
138
+ });
139
+ }
140
+ // Declarative tools from tools/*.yaml
141
+ const declarativeTools = await loadDeclarativeTools(loaded.agentDir);
142
+ tools = [...tools, ...declarativeTools];
143
+ // SDK-provided tools
144
+ if (options.tools) {
145
+ tools = [...tools, ...options.tools.map(toAgentTool)];
146
+ }
147
+ // Filter by allowlist/denylist
148
+ if (options.allowedTools) {
149
+ const allowed = new Set(options.allowedTools);
150
+ tools = tools.filter((t) => allowed.has(t.name));
151
+ }
152
+ if (options.disallowedTools) {
153
+ const denied = new Set(options.disallowedTools);
154
+ tools = tools.filter((t) => !denied.has(t.name));
155
+ }
156
+ // 4. Wrap with script-based hooks
157
+ const hooksConfig = await loadHooksConfig(loaded.agentDir);
158
+ if (hooksConfig) {
159
+ tools = tools.map((t) => wrapToolWithHooks(t, hooksConfig, loaded.agentDir, _sessionId));
160
+ }
161
+ // 5. Wrap with programmatic hooks
162
+ if (options.hooks) {
163
+ tools = tools.map((t) => wrapToolWithProgrammaticHooks(t, options.hooks, _sessionId, loaded.manifest.name));
164
+ }
165
+ // 6. Run on_session_start hooks (script-based)
166
+ if (hooksConfig?.hooks.on_session_start) {
167
+ const result = await runHooks(hooksConfig.hooks.on_session_start, loaded.agentDir, {
168
+ event: "on_session_start",
169
+ session_id: _sessionId,
170
+ agent: loaded.manifest.name,
171
+ });
172
+ if (result.action === "block") {
173
+ pushMsg({
174
+ type: "system",
175
+ subtype: "hook_blocked",
176
+ content: `Session blocked by hook: ${result.reason || "no reason given"}`,
177
+ });
178
+ channel.finish();
179
+ return;
180
+ }
181
+ }
182
+ // 6b. Run on_session_start programmatic hook
183
+ if (options.hooks?.onSessionStart) {
184
+ const ctx = {
185
+ sessionId: _sessionId,
186
+ agentName: loaded.manifest.name,
187
+ event: "SessionStart",
188
+ };
189
+ const result = await options.hooks.onSessionStart(ctx);
190
+ if (result.action === "block") {
191
+ pushMsg({
192
+ type: "system",
193
+ subtype: "hook_blocked",
194
+ content: `Session blocked by hook: ${result.reason || "no reason given"}`,
195
+ });
196
+ channel.finish();
197
+ return;
198
+ }
199
+ }
200
+ // 7. Build model options from constraints
201
+ const modelOptions = {};
202
+ const constraints = options.constraints ?? loaded.manifest.model.constraints;
203
+ if (constraints) {
204
+ const c = constraints;
205
+ if (c.temperature !== undefined)
206
+ modelOptions.temperature = c.temperature;
207
+ if (c.maxTokens !== undefined)
208
+ modelOptions.maxTokens = c.maxTokens;
209
+ if (c.max_tokens !== undefined)
210
+ modelOptions.maxTokens = c.max_tokens;
211
+ if (c.topP !== undefined)
212
+ modelOptions.topP = c.topP;
213
+ if (c.top_p !== undefined)
214
+ modelOptions.topP = c.top_p;
215
+ if (c.topK !== undefined)
216
+ modelOptions.topK = c.topK;
217
+ if (c.top_k !== undefined)
218
+ modelOptions.topK = c.top_k;
219
+ }
220
+ if (options.maxTurns !== undefined) {
221
+ modelOptions.maxTurns = options.maxTurns;
222
+ }
223
+ // 8. Create Agent
224
+ const agent = new Agent({
225
+ initialState: {
226
+ systemPrompt,
227
+ model: loaded.model,
228
+ tools,
229
+ ...modelOptions,
230
+ },
231
+ });
232
+ // 9. Subscribe to events and map to GCMessage
233
+ agent.subscribe((event) => {
234
+ switch (event.type) {
235
+ case "agent_start":
236
+ pushMsg({
237
+ type: "system",
238
+ subtype: "session_start",
239
+ content: `Agent ${loaded.manifest.name} started`,
240
+ metadata: { sessionId: _sessionId },
241
+ });
242
+ break;
243
+ case "message_update": {
244
+ const e = event.assistantMessageEvent;
245
+ if (e.type === "text_delta") {
246
+ accText += e.delta;
247
+ pushMsg({
248
+ type: "delta",
249
+ deltaType: "text",
250
+ content: e.delta,
251
+ });
252
+ }
253
+ else if (e.type === "thinking_delta") {
254
+ accThinking += e.delta;
255
+ pushMsg({
256
+ type: "delta",
257
+ deltaType: "thinking",
258
+ content: e.delta,
259
+ });
260
+ }
261
+ break;
262
+ }
263
+ case "message_end": {
264
+ // Only process assistant messages — skip user/toolResult
265
+ const raw = event.message;
266
+ if (!raw || raw.role !== "assistant")
267
+ break;
268
+ const msg = raw;
269
+ // Emit error system message if the LLM call failed
270
+ if (msg.stopReason === "error") {
271
+ pushMsg({
272
+ type: "system",
273
+ subtype: "error",
274
+ content: msg.errorMessage || "LLM request failed (unknown error)",
275
+ metadata: {
276
+ model: msg.model,
277
+ provider: msg.provider,
278
+ api: msg.api,
279
+ },
280
+ });
281
+ // Still emit the assistant message so callers can inspect stopReason
282
+ }
283
+ const { text, thinking } = extractContent(msg);
284
+ const assistantMsg = {
285
+ type: "assistant",
286
+ content: text || accText,
287
+ thinking: (thinking || accThinking) || undefined,
288
+ model: msg.model ?? "unknown",
289
+ provider: msg.provider ?? "unknown",
290
+ stopReason: msg.stopReason ?? "stop",
291
+ errorMessage: msg.errorMessage,
292
+ usage: msg.usage ? {
293
+ inputTokens: msg.usage.input ?? 0,
294
+ outputTokens: msg.usage.output ?? 0,
295
+ cacheReadTokens: msg.usage.cacheRead ?? 0,
296
+ cacheWriteTokens: msg.usage.cacheWrite ?? 0,
297
+ totalTokens: msg.usage.totalTokens ?? 0,
298
+ costUsd: msg.usage.cost?.total ?? 0,
299
+ } : undefined,
300
+ };
301
+ pushMsg(assistantMsg);
302
+ // Reset accumulators
303
+ accText = "";
304
+ accThinking = "";
305
+ // Fire post_response hooks (non-blocking)
306
+ if (hooksConfig?.hooks.post_response) {
307
+ runHooks(hooksConfig.hooks.post_response, loaded.agentDir, {
308
+ event: "post_response",
309
+ session_id: _sessionId,
310
+ }).catch(() => { });
311
+ }
312
+ if (options.hooks?.postResponse) {
313
+ Promise.resolve(options.hooks.postResponse({
314
+ sessionId: _sessionId,
315
+ agentName: loaded.manifest.name,
316
+ event: "PostResponse",
317
+ })).catch(() => { });
318
+ }
319
+ break;
320
+ }
321
+ case "tool_execution_start":
322
+ pushMsg({
323
+ type: "tool_use",
324
+ toolCallId: event.toolCallId,
325
+ toolName: event.toolName,
326
+ args: event.args ?? {},
327
+ });
328
+ break;
329
+ case "tool_execution_end": {
330
+ const text = event.result?.content?.[0]?.text ?? "";
331
+ pushMsg({
332
+ type: "tool_result",
333
+ toolCallId: event.toolCallId,
334
+ toolName: event.toolName,
335
+ content: text,
336
+ isError: event.isError,
337
+ });
338
+ break;
339
+ }
340
+ case "agent_end":
341
+ pushMsg({
342
+ type: "system",
343
+ subtype: "session_end",
344
+ content: `Agent ${loaded.manifest.name} finished`,
345
+ metadata: { sessionId: _sessionId },
346
+ });
347
+ channel.finish();
348
+ break;
349
+ }
350
+ });
351
+ // 10. Send prompt
352
+ if (typeof options.prompt === "string") {
353
+ await agent.prompt(options.prompt);
354
+ }
355
+ else {
356
+ // Multi-turn: iterate the async iterable
357
+ for await (const userMsg of options.prompt) {
358
+ pushMsg({ type: "user", content: userMsg.content });
359
+ await agent.prompt(userMsg.content);
360
+ }
361
+ }
362
+ // Finalize local session if active
363
+ if (localSession) {
364
+ try {
365
+ localSession.finalize();
366
+ }
367
+ catch { /* best-effort */ }
368
+ }
369
+ // Stop sandbox if active
370
+ if (sandboxCtx) {
371
+ await sandboxCtx.gitMachine.stop().catch(() => { });
372
+ }
373
+ // Ensure channel finishes even if no agent_end event
374
+ channel.finish();
375
+ })().catch(async (err) => {
376
+ // Finalize local session on error
377
+ if (localSession) {
378
+ try {
379
+ localSession.finalize();
380
+ }
381
+ catch { /* best-effort */ }
382
+ }
383
+ // Stop sandbox on error
384
+ if (sandboxCtx) {
385
+ await sandboxCtx.gitMachine.stop().catch(() => { });
386
+ }
387
+ // Fire on_error hooks
388
+ if (options.hooks?.onError) {
389
+ Promise.resolve(options.hooks.onError({
390
+ sessionId: _sessionId,
391
+ agentName: _manifest?.name ?? "unknown",
392
+ event: "OnError",
393
+ error: err.message,
394
+ })).catch(() => { });
395
+ }
396
+ pushMsg({
397
+ type: "system",
398
+ subtype: "error",
399
+ content: err.message,
400
+ });
401
+ channel.finish();
402
+ });
403
+ // Build the Query object (AsyncGenerator + helpers)
404
+ const generator = {
405
+ abort() {
406
+ ac.abort();
407
+ },
408
+ steer(_message) {
409
+ // Steering requires agent reference — for now this is a placeholder.
410
+ // Full steering support would require exposing the Agent instance.
411
+ },
412
+ sessionId() {
413
+ return _sessionId;
414
+ },
415
+ manifest() {
416
+ if (!_manifest)
417
+ throw new Error("Agent not yet loaded");
418
+ return _manifest;
419
+ },
420
+ messages() {
421
+ return [...collectedMessages];
422
+ },
423
+ // AsyncGenerator protocol
424
+ next() {
425
+ return channel.pull();
426
+ },
427
+ return(value) {
428
+ channel.finish();
429
+ return Promise.resolve({ value, done: true });
430
+ },
431
+ throw(err) {
432
+ channel.finish();
433
+ return Promise.reject(err);
434
+ },
435
+ [Symbol.asyncIterator]() {
436
+ return generator;
437
+ },
438
+ };
439
+ return generator;
440
+ }
441
+ // ── tool() helper ──────────────────────────────────────────────────────
442
+ export function tool(name, description, inputSchema, handler) {
443
+ return { name, description, inputSchema, handler };
444
+ }
@@ -0,0 +1,15 @@
1
+ export interface LocalRepoOptions {
2
+ url: string;
3
+ token: string;
4
+ dir: string;
5
+ session?: string;
6
+ }
7
+ export interface LocalSession {
8
+ dir: string;
9
+ branch: string;
10
+ sessionId: string;
11
+ commitChanges(msg?: string): void;
12
+ push(): void;
13
+ finalize(): void;
14
+ }
15
+ export declare function initLocalSession(opts: LocalRepoOptions): LocalSession;
@@ -0,0 +1,127 @@
1
+ import { execSync } from "child_process";
2
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { randomBytes } from "crypto";
5
+ // ── Helpers ───────────────────────────────────────────────────────────
6
+ function authedUrl(url, token) {
7
+ // https://github.com/org/repo → https://<token>@github.com/org/repo
8
+ return url.replace(/^https:\/\//, `https://${token}@`);
9
+ }
10
+ function cleanUrl(url) {
11
+ return url.replace(/^https:\/\/[^@]+@/, "https://");
12
+ }
13
+ function git(args, cwd) {
14
+ return execSync(`git ${args}`, { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
15
+ }
16
+ function getDefaultBranch(cwd) {
17
+ try {
18
+ // e.g. "origin/main" → "main"
19
+ const ref = git("symbolic-ref refs/remotes/origin/HEAD", cwd);
20
+ return ref.replace("refs/remotes/origin/", "");
21
+ }
22
+ catch {
23
+ // Fallback: try main, then master
24
+ try {
25
+ git("rev-parse --verify origin/main", cwd);
26
+ return "main";
27
+ }
28
+ catch {
29
+ return "master";
30
+ }
31
+ }
32
+ }
33
+ // ── initLocalSession ──────────────────────────────────────────────────
34
+ export function initLocalSession(opts) {
35
+ const { url, token, session } = opts;
36
+ const dir = resolve(opts.dir);
37
+ const aUrl = authedUrl(url, token);
38
+ // Clone or update
39
+ if (!existsSync(dir)) {
40
+ execSync(`git clone --depth 1 --no-single-branch ${aUrl} ${dir}`, { stdio: "pipe" });
41
+ }
42
+ else {
43
+ git(`remote set-url origin ${aUrl}`, dir);
44
+ git("fetch origin", dir);
45
+ // Reset local default branch to latest remote
46
+ const defaultBranch = getDefaultBranch(dir);
47
+ git(`checkout ${defaultBranch}`, dir);
48
+ git(`reset --hard origin/${defaultBranch}`, dir);
49
+ }
50
+ // Determine branch
51
+ let branch;
52
+ let sessionId;
53
+ if (session) {
54
+ // Resume existing session
55
+ branch = session;
56
+ sessionId = branch.replace(/^gitclaw\/session-/, "") || branch;
57
+ // Try local checkout first, fall back to remote tracking
58
+ try {
59
+ git(`checkout ${branch}`, dir);
60
+ }
61
+ catch {
62
+ git(`checkout -b ${branch} origin/${branch}`, dir);
63
+ }
64
+ // Pull latest for existing session branch
65
+ try {
66
+ git(`pull origin ${branch}`, dir);
67
+ }
68
+ catch { /* branch may not exist on remote yet */ }
69
+ }
70
+ else {
71
+ // New session — branch off latest default branch
72
+ sessionId = randomBytes(4).toString("hex"); // 8-char hex
73
+ branch = `gitclaw/session-${sessionId}`;
74
+ git(`checkout -b ${branch}`, dir);
75
+ }
76
+ // Scaffold agent.yaml + memory if missing (on session branch only)
77
+ const agentYamlPath = `${dir}/agent.yaml`;
78
+ if (!existsSync(agentYamlPath)) {
79
+ const name = url.split("/").pop()?.replace(/\.git$/, "") || "agent";
80
+ writeFileSync(agentYamlPath, [
81
+ 'spec_version: "0.1.0"',
82
+ `name: ${name}`,
83
+ "version: 0.1.0",
84
+ `description: Gitclaw agent for ${name}`,
85
+ "model:",
86
+ ' preferred: "openai:gpt-4o-mini"',
87
+ " fallback: []",
88
+ "tools: [cli, read, write, memory]",
89
+ "runtime:",
90
+ " max_turns: 50",
91
+ "",
92
+ ].join("\n"), "utf-8");
93
+ }
94
+ const memoryFile = `${dir}/memory/MEMORY.md`;
95
+ if (!existsSync(memoryFile)) {
96
+ mkdirSync(`${dir}/memory`, { recursive: true });
97
+ writeFileSync(memoryFile, "# Memory\n", "utf-8");
98
+ }
99
+ // Build session object
100
+ const localSession = {
101
+ dir,
102
+ branch,
103
+ sessionId,
104
+ commitChanges(msg) {
105
+ git("add -A", dir);
106
+ try {
107
+ git("diff --cached --quiet", dir);
108
+ // Nothing staged — skip
109
+ }
110
+ catch {
111
+ // There are staged changes
112
+ const commitMsg = msg || `gitclaw: auto-commit (${branch})`;
113
+ git(`commit -m "${commitMsg}"`, dir);
114
+ }
115
+ },
116
+ push() {
117
+ git(`push origin ${branch}`, dir);
118
+ },
119
+ finalize() {
120
+ localSession.commitChanges();
121
+ localSession.push();
122
+ // Strip PAT from remote URL
123
+ git(`remote set-url origin ${cleanUrl(url)}`, dir);
124
+ },
125
+ };
126
+ return localSession;
127
+ }
@@ -0,0 +1,18 @@
1
+ export interface SkillMetadata {
2
+ name: string;
3
+ description: string;
4
+ directory: string;
5
+ filePath: string;
6
+ }
7
+ export interface ParsedSkill extends SkillMetadata {
8
+ instructions: string;
9
+ hasScripts: boolean;
10
+ hasReferences: boolean;
11
+ }
12
+ export declare function discoverSkills(agentDir: string): Promise<SkillMetadata[]>;
13
+ export declare function loadSkill(meta: SkillMetadata): Promise<ParsedSkill>;
14
+ export declare function formatSkillsForPrompt(skills: SkillMetadata[]): string;
15
+ export declare function expandSkillCommand(input: string, skills: SkillMetadata[]): Promise<{
16
+ expanded: string;
17
+ skillName: string;
18
+ } | null>;