gitclaw 0.3.0 → 0.4.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 +54 -28
- package/dist/composio/adapter.d.ts +26 -0
- package/dist/composio/adapter.js +92 -0
- package/dist/composio/client.d.ts +39 -0
- package/dist/composio/client.js +170 -0
- package/dist/composio/index.d.ts +2 -0
- package/dist/composio/index.js +2 -0
- package/dist/context.d.ts +20 -0
- package/dist/context.js +211 -0
- package/dist/exports.d.ts +2 -0
- package/dist/exports.js +1 -0
- package/dist/index.js +99 -7
- package/dist/learning/reinforcement.d.ts +11 -0
- package/dist/learning/reinforcement.js +91 -0
- package/dist/loader.js +34 -1
- package/dist/sdk.js +5 -1
- package/dist/skills.d.ts +5 -0
- package/dist/skills.js +58 -7
- package/dist/tools/capture-photo.d.ts +3 -0
- package/dist/tools/capture-photo.js +91 -0
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +12 -2
- package/dist/tools/read.js +4 -0
- package/dist/tools/shared.d.ts +20 -0
- package/dist/tools/shared.js +24 -0
- package/dist/tools/skill-learner.d.ts +3 -0
- package/dist/tools/skill-learner.js +358 -0
- package/dist/tools/task-tracker.d.ts +20 -0
- package/dist/tools/task-tracker.js +275 -0
- package/dist/tools/write.js +4 -0
- package/dist/voice/adapter.d.ts +97 -0
- package/dist/voice/adapter.js +30 -0
- package/dist/voice/chat-history.d.ts +8 -0
- package/dist/voice/chat-history.js +121 -0
- package/dist/voice/gemini-live.d.ts +20 -0
- package/dist/voice/gemini-live.js +279 -0
- package/dist/voice/index.d.ts +4 -0
- package/dist/voice/index.js +3 -0
- package/dist/voice/openai-realtime.d.ts +27 -0
- package/dist/voice/openai-realtime.js +291 -0
- package/dist/voice/server.d.ts +2 -0
- package/dist/voice/server.js +2319 -0
- package/dist/voice/ui.html +2556 -0
- package/package.json +21 -7
package/dist/skills.js
CHANGED
|
@@ -27,9 +27,13 @@ export async function discoverSkills(agentDir) {
|
|
|
27
27
|
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
28
28
|
const skills = [];
|
|
29
29
|
for (const entry of entries) {
|
|
30
|
-
|
|
30
|
+
// Accept both real directories and symlinks pointing to directories
|
|
31
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
31
32
|
continue;
|
|
32
33
|
const skillDir = join(skillsDir, entry.name);
|
|
34
|
+
// For symlinks, verify the target is actually a directory
|
|
35
|
+
if (entry.isSymbolicLink() && !(await dirExists(skillDir)))
|
|
36
|
+
continue;
|
|
33
37
|
const skillFile = join(skillDir, "SKILL.md");
|
|
34
38
|
let content;
|
|
35
39
|
try {
|
|
@@ -53,12 +57,22 @@ export async function discoverSkills(agentDir) {
|
|
|
53
57
|
console.warn(`Skipping skill "${entry.name}": name must be kebab-case`);
|
|
54
58
|
continue;
|
|
55
59
|
}
|
|
56
|
-
|
|
60
|
+
const meta = {
|
|
57
61
|
name,
|
|
58
62
|
description,
|
|
59
63
|
directory: skillDir,
|
|
60
64
|
filePath: skillFile,
|
|
61
|
-
}
|
|
65
|
+
};
|
|
66
|
+
// Parse optional learning fields
|
|
67
|
+
if (typeof frontmatter.confidence === "number")
|
|
68
|
+
meta.confidence = frontmatter.confidence;
|
|
69
|
+
if (typeof frontmatter.usage_count === "number")
|
|
70
|
+
meta.usage_count = frontmatter.usage_count;
|
|
71
|
+
if (typeof frontmatter.success_count === "number")
|
|
72
|
+
meta.success_count = frontmatter.success_count;
|
|
73
|
+
if (typeof frontmatter.failure_count === "number")
|
|
74
|
+
meta.failure_count = frontmatter.failure_count;
|
|
75
|
+
skills.push(meta);
|
|
62
76
|
}
|
|
63
77
|
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
64
78
|
}
|
|
@@ -76,15 +90,47 @@ export function formatSkillsForPrompt(skills) {
|
|
|
76
90
|
if (skills.length === 0)
|
|
77
91
|
return "";
|
|
78
92
|
const skillEntries = skills
|
|
79
|
-
.map((s) =>
|
|
93
|
+
.map((s) => {
|
|
94
|
+
let entry = `<skill>\n<name>${s.name}</name>\n<description>${s.description}</description>`;
|
|
95
|
+
entry += `\n<location>skills/${s.name}/SKILL.md</location>`;
|
|
96
|
+
if (s.confidence !== undefined) {
|
|
97
|
+
entry += `\n<confidence>${s.confidence}</confidence>`;
|
|
98
|
+
}
|
|
99
|
+
entry += "\n</skill>";
|
|
100
|
+
return entry;
|
|
101
|
+
})
|
|
80
102
|
.join("\n");
|
|
81
|
-
return `# Skills
|
|
103
|
+
return `# Skills — FIRST PRIORITY (MANDATORY)
|
|
104
|
+
|
|
105
|
+
CRITICAL: You have installed skills that provide specialized capabilities.
|
|
106
|
+
Before attempting ANY task — simple or complex — you MUST check if an installed skill handles it.
|
|
107
|
+
|
|
108
|
+
## Rules (MUST follow in order)
|
|
109
|
+
1. ALWAYS scan the skill list below BEFORE taking ANY action on a user request
|
|
110
|
+
2. If a skill's description matches or partially matches the task, you MUST load its full
|
|
111
|
+
instructions using the \`read\` tool: \`skills/<name>/SKILL.md\` — do this BEFORE anything else
|
|
112
|
+
3. Follow the loaded skill instructions EXACTLY — do NOT improvise or use alternative approaches
|
|
113
|
+
4. NEVER use general-purpose workarounds when a skill provides the right tool
|
|
114
|
+
(e.g., use \`agent-browser open <url>\` NOT \`open -a Safari\`)
|
|
115
|
+
5. If multiple skills could apply, load the most specific one first
|
|
116
|
+
6. Even for seemingly simple tasks, CHECK SKILLS FIRST — skills often handle edge cases
|
|
117
|
+
and produce higher quality results than ad-hoc approaches
|
|
118
|
+
|
|
119
|
+
## Enforcement
|
|
120
|
+
- If you skip checking skills and use a raw approach for a task that a skill handles,
|
|
121
|
+
this is considered a FAILURE. Always check skills first.
|
|
122
|
+
- When calling \`task_tracker\` "begin", if it returns matching skills, you MUST load
|
|
123
|
+
the top match immediately before proceeding.
|
|
82
124
|
|
|
83
125
|
<available_skills>
|
|
84
126
|
${skillEntries}
|
|
85
127
|
</available_skills>
|
|
86
128
|
|
|
87
|
-
|
|
129
|
+
To load a skill's full instructions: read \`skills/<name>/SKILL.md\`
|
|
130
|
+
Scripts within a skill are relative to the skill's directory: \`skills/<name>/scripts/\``;
|
|
131
|
+
}
|
|
132
|
+
export async function refreshSkills(agentDir) {
|
|
133
|
+
return discoverSkills(agentDir);
|
|
88
134
|
}
|
|
89
135
|
export async function expandSkillCommand(input, skills) {
|
|
90
136
|
const match = input.match(/^\/skill:([a-z0-9-]+)\s*([\s\S]*)$/);
|
|
@@ -96,7 +142,12 @@ export async function expandSkillCommand(input, skills) {
|
|
|
96
142
|
if (!skill)
|
|
97
143
|
return null;
|
|
98
144
|
const parsed = await loadSkill(skill);
|
|
99
|
-
let expanded = `<skill name="${skillName}" baseDir="${skill.directory}"
|
|
145
|
+
let expanded = `<skill name="${skillName}" baseDir="${skill.directory}">
|
|
146
|
+
References are relative to ${skill.directory}.
|
|
147
|
+
|
|
148
|
+
${parsed.instructions}
|
|
149
|
+
</skill>
|
|
150
|
+
You MUST follow the skill instructions above. Do NOT use general alternatives.`;
|
|
100
151
|
if (args) {
|
|
101
152
|
expanded += `\n\n${args}`;
|
|
102
153
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, stat } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { capturePhotoSchema } from "./shared.js";
|
|
5
|
+
const PHOTOS_DIR = "memory/photos";
|
|
6
|
+
const INDEX_FILE = "memory/photos/INDEX.md";
|
|
7
|
+
const LATEST_FRAME_FILE = "memory/.latest-frame.jpg";
|
|
8
|
+
function slugify(text) {
|
|
9
|
+
return text
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
12
|
+
.replace(/^-|-$/g, "")
|
|
13
|
+
.slice(0, 40);
|
|
14
|
+
}
|
|
15
|
+
export function createCapturePhotoTool(cwd) {
|
|
16
|
+
return {
|
|
17
|
+
name: "capture_photo",
|
|
18
|
+
label: "capture_photo",
|
|
19
|
+
description: "Capture a photo from the webcam during a memorable moment. Reads the latest camera frame, saves it as a named photo in memory/photos/, updates the index, and commits to git.",
|
|
20
|
+
parameters: capturePhotoSchema,
|
|
21
|
+
execute: async (_toolCallId, { reason }, signal) => {
|
|
22
|
+
if (signal?.aborted)
|
|
23
|
+
throw new Error("Operation aborted");
|
|
24
|
+
const framePath = join(cwd, LATEST_FRAME_FILE);
|
|
25
|
+
// Check if frame file exists and isn't stale
|
|
26
|
+
let frameStat;
|
|
27
|
+
try {
|
|
28
|
+
frameStat = await stat(framePath);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: "text", text: "No camera frame available. The webcam may not be active." }],
|
|
33
|
+
details: undefined,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const ageMs = Date.now() - frameStat.mtimeMs;
|
|
37
|
+
if (ageMs > 5000) {
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: "text", text: "No recent camera frame (camera may be off). Last frame is too stale to capture." }],
|
|
40
|
+
details: undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Read the frame
|
|
44
|
+
const frameData = await readFile(framePath);
|
|
45
|
+
// Build filename
|
|
46
|
+
const now = new Date();
|
|
47
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
48
|
+
const datePart = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
49
|
+
const timePart = `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
50
|
+
const slug = slugify(reason);
|
|
51
|
+
const filename = `${datePart}_${timePart}_${slug}.jpg`;
|
|
52
|
+
const photoRelPath = `${PHOTOS_DIR}/${filename}`;
|
|
53
|
+
const photoAbsPath = join(cwd, photoRelPath);
|
|
54
|
+
// Ensure photos directory exists
|
|
55
|
+
await mkdir(join(cwd, PHOTOS_DIR), { recursive: true });
|
|
56
|
+
// Write photo
|
|
57
|
+
await writeFile(photoAbsPath, frameData);
|
|
58
|
+
// Update INDEX.md
|
|
59
|
+
const indexPath = join(cwd, INDEX_FILE);
|
|
60
|
+
let indexContent = "";
|
|
61
|
+
try {
|
|
62
|
+
indexContent = await readFile(indexPath, "utf-8");
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
indexContent = "# Memorable Moments\n\nPhotos captured during happy and memorable moments.\n\n";
|
|
66
|
+
}
|
|
67
|
+
const entry = `- **${datePart} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}** — ${reason} → [\`${filename}\`](${filename})\n`;
|
|
68
|
+
indexContent += entry;
|
|
69
|
+
await writeFile(indexPath, indexContent, "utf-8");
|
|
70
|
+
// Git add + commit
|
|
71
|
+
const commitMsg = `Capture moment: ${reason}`;
|
|
72
|
+
try {
|
|
73
|
+
execSync(`git add "${photoRelPath}" "${INDEX_FILE}" && git commit -m "${commitMsg.replace(/"/g, '\\"')}"`, {
|
|
74
|
+
cwd,
|
|
75
|
+
stdio: "pipe",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const stderr = err.stderr?.toString() || "";
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: `Photo saved to ${photoRelPath} but git commit failed: ${stderr.trim() || "unknown error"}` }],
|
|
82
|
+
details: undefined,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: `Memorable moment captured! Photo saved to ${photoRelPath} and committed: "${commitMsg}"` }],
|
|
87
|
+
details: undefined,
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -4,9 +4,10 @@ export interface BuiltinToolsConfig {
|
|
|
4
4
|
dir: string;
|
|
5
5
|
timeout?: number;
|
|
6
6
|
sandbox?: SandboxContext;
|
|
7
|
+
gitagentDir?: string;
|
|
7
8
|
}
|
|
8
9
|
/**
|
|
9
|
-
* Create the
|
|
10
|
+
* Create the built-in tools (cli, read, write, memory, task_tracker, skill_learner).
|
|
10
11
|
* If a SandboxContext is provided, returns sandbox-backed tools;
|
|
11
12
|
* otherwise returns the standard local tools.
|
|
12
13
|
*/
|
package/dist/tools/index.js
CHANGED
|
@@ -2,12 +2,15 @@ import { createCliTool } from "./cli.js";
|
|
|
2
2
|
import { createReadTool } from "./read.js";
|
|
3
3
|
import { createWriteTool } from "./write.js";
|
|
4
4
|
import { createMemoryTool } from "./memory.js";
|
|
5
|
+
import { createTaskTrackerTool } from "./task-tracker.js";
|
|
6
|
+
import { createSkillLearnerTool } from "./skill-learner.js";
|
|
7
|
+
import { createCapturePhotoTool } from "./capture-photo.js";
|
|
5
8
|
import { createSandboxCliTool } from "./sandbox-cli.js";
|
|
6
9
|
import { createSandboxReadTool } from "./sandbox-read.js";
|
|
7
10
|
import { createSandboxWriteTool } from "./sandbox-write.js";
|
|
8
11
|
import { createSandboxMemoryTool } from "./sandbox-memory.js";
|
|
9
12
|
/**
|
|
10
|
-
* Create the
|
|
13
|
+
* Create the built-in tools (cli, read, write, memory, task_tracker, skill_learner).
|
|
11
14
|
* If a SandboxContext is provided, returns sandbox-backed tools;
|
|
12
15
|
* otherwise returns the standard local tools.
|
|
13
16
|
*/
|
|
@@ -20,10 +23,17 @@ export function createBuiltinTools(config) {
|
|
|
20
23
|
createSandboxMemoryTool(config.sandbox),
|
|
21
24
|
];
|
|
22
25
|
}
|
|
23
|
-
|
|
26
|
+
const tools = [
|
|
24
27
|
createCliTool(config.dir, config.timeout),
|
|
25
28
|
createReadTool(config.dir),
|
|
26
29
|
createWriteTool(config.dir),
|
|
27
30
|
createMemoryTool(config.dir),
|
|
31
|
+
createCapturePhotoTool(config.dir),
|
|
28
32
|
];
|
|
33
|
+
// Add learning tools if gitagentDir is available
|
|
34
|
+
if (config.gitagentDir) {
|
|
35
|
+
tools.push(createTaskTrackerTool(config.dir, config.gitagentDir));
|
|
36
|
+
tools.push(createSkillLearnerTool(config.dir, config.gitagentDir));
|
|
37
|
+
}
|
|
38
|
+
return tools;
|
|
29
39
|
}
|
package/dist/tools/read.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
2
|
import { resolve } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
3
4
|
import { readSchema, MAX_LINES, paginateLines } from "./shared.js";
|
|
4
5
|
function resolvePath(path, cwd) {
|
|
6
|
+
if (path.startsWith("~/") || path === "~") {
|
|
7
|
+
path = homedir() + path.slice(1);
|
|
8
|
+
}
|
|
5
9
|
return path.startsWith("/") ? path : resolve(cwd, path);
|
|
6
10
|
}
|
|
7
11
|
function isBinary(buffer) {
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -22,6 +22,26 @@ export declare const memorySchema: import("@sinclair/typebox").TObject<{
|
|
|
22
22
|
content: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
23
23
|
message: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
24
24
|
}>;
|
|
25
|
+
export declare const taskTrackerSchema: import("@sinclair/typebox").TObject<{
|
|
26
|
+
action: import("@sinclair/typebox").TUnsafe<string>;
|
|
27
|
+
objective: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
28
|
+
task_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
29
|
+
step: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
30
|
+
outcome: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<string>>;
|
|
31
|
+
failure_reason: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
32
|
+
skill_used: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
33
|
+
}>;
|
|
34
|
+
export declare const capturePhotoSchema: import("@sinclair/typebox").TObject<{
|
|
35
|
+
reason: import("@sinclair/typebox").TString;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const skillLearnerSchema: import("@sinclair/typebox").TObject<{
|
|
38
|
+
action: import("@sinclair/typebox").TUnsafe<string>;
|
|
39
|
+
task_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
40
|
+
skill_name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
41
|
+
skill_description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
42
|
+
instructions: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
43
|
+
override_heuristic: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
44
|
+
}>;
|
|
25
45
|
/** Truncate output to MAX_OUTPUT, keeping the tail. */
|
|
26
46
|
export declare function truncateOutput(text: string): string;
|
|
27
47
|
/**
|
package/dist/tools/shared.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
3
|
+
import { homedir } from "os";
|
|
3
4
|
// ── Constants ───────────────────────────────────────────────────────────
|
|
4
5
|
export const MAX_OUTPUT = 100_000; // ~100KB max output to send to LLM
|
|
5
6
|
export const MAX_LINES = 2000;
|
|
@@ -26,6 +27,26 @@ export const memorySchema = Type.Object({
|
|
|
26
27
|
content: Type.Optional(Type.String({ description: "Memory content to save (required for save)" })),
|
|
27
28
|
message: Type.Optional(Type.String({ description: "Commit message describing why this memory changed (required for save)" })),
|
|
28
29
|
});
|
|
30
|
+
export const taskTrackerSchema = Type.Object({
|
|
31
|
+
action: StringEnum(["begin", "update", "end", "list"], { description: "Action to perform" }),
|
|
32
|
+
objective: Type.Optional(Type.String({ description: "Task objective (required for begin)" })),
|
|
33
|
+
task_id: Type.Optional(Type.String({ description: "Task ID (required for update/end)" })),
|
|
34
|
+
step: Type.Optional(Type.String({ description: "Step description (for update)" })),
|
|
35
|
+
outcome: Type.Optional(StringEnum(["success", "failure", "partial"], { description: "Task outcome (for end)" })),
|
|
36
|
+
failure_reason: Type.Optional(Type.String({ description: "Why the task failed (for end+failure)" })),
|
|
37
|
+
skill_used: Type.Optional(Type.String({ description: "Name of skill used, if any (for end)" })),
|
|
38
|
+
});
|
|
39
|
+
export const capturePhotoSchema = Type.Object({
|
|
40
|
+
reason: Type.String({ description: "Why this moment is being captured (e.g. 'user celebrating project launch')" }),
|
|
41
|
+
});
|
|
42
|
+
export const skillLearnerSchema = Type.Object({
|
|
43
|
+
action: StringEnum(["evaluate", "crystallize", "status", "review", "update", "delete"], { description: "Action to perform" }),
|
|
44
|
+
task_id: Type.Optional(Type.String({ description: "Task ID (for evaluate/crystallize)" })),
|
|
45
|
+
skill_name: Type.Optional(Type.String({ description: "Skill name (for crystallize/update/delete)" })),
|
|
46
|
+
skill_description: Type.Optional(Type.String({ description: "Skill description (for crystallize)" })),
|
|
47
|
+
instructions: Type.Optional(Type.String({ description: "New instructions content (for update)" })),
|
|
48
|
+
override_heuristic: Type.Optional(Type.Boolean({ description: "Override skill-worthiness heuristic (for evaluate)" })),
|
|
49
|
+
});
|
|
29
50
|
// ── Shared helpers ──────────────────────────────────────────────────────
|
|
30
51
|
/** Truncate output to MAX_OUTPUT, keeping the tail. */
|
|
31
52
|
export function truncateOutput(text) {
|
|
@@ -63,6 +84,9 @@ export function paginateLines(text, offset, limit) {
|
|
|
63
84
|
}
|
|
64
85
|
/** Resolve a path relative to a sandbox repo root. */
|
|
65
86
|
export function resolveSandboxPath(path, repoRoot) {
|
|
87
|
+
if (path.startsWith("~/") || path === "~") {
|
|
88
|
+
path = homedir() + path.slice(1);
|
|
89
|
+
}
|
|
66
90
|
if (path.startsWith("/"))
|
|
67
91
|
return path;
|
|
68
92
|
return repoRoot.endsWith("/") ? repoRoot + path : repoRoot + "/" + path;
|