@zhijiewang/openharness 2.4.0 → 2.8.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/README.md +2 -2
- package/dist/Tool.d.ts +2 -0
- package/dist/commands/ai.d.ts +6 -0
- package/dist/commands/ai.js +244 -0
- package/dist/commands/git.d.ts +6 -0
- package/dist/commands/git.js +167 -0
- package/dist/commands/index.d.ts +10 -31
- package/dist/commands/index.js +22 -1052
- package/dist/commands/info.d.ts +8 -0
- package/dist/commands/info.js +671 -0
- package/dist/commands/session.d.ts +6 -0
- package/dist/commands/session.js +214 -0
- package/dist/commands/settings.d.ts +6 -0
- package/dist/commands/settings.js +187 -0
- package/dist/commands/skills.d.ts +6 -0
- package/dist/commands/skills.js +117 -0
- package/dist/commands/types.d.ts +36 -0
- package/dist/commands/types.js +5 -0
- package/dist/components/InitWizard.js +61 -61
- package/dist/harness/config.d.ts +2 -0
- package/dist/harness/hooks.js +9 -6
- package/dist/harness/memory.js +28 -1
- package/dist/harness/plugins.d.ts +2 -0
- package/dist/harness/plugins.js +44 -11
- package/dist/harness/session-db.js +3 -1
- package/dist/harness/skill-registry.d.ts +21 -0
- package/dist/harness/skill-registry.js +35 -0
- package/dist/lsp/client.js +2 -1
- package/dist/main.js +10 -2
- package/dist/mcp/client.js +2 -1
- package/dist/mcp/server-mode.d.ts +10 -0
- package/dist/mcp/server-mode.js +17 -0
- package/dist/providers/anthropic.js +7 -8
- package/dist/providers/fallback.js +2 -3
- package/dist/providers/openai.js +3 -2
- package/dist/query/index.js +30 -6
- package/dist/query/tools.js +11 -0
- package/dist/query/types.d.ts +4 -0
- package/dist/renderer/layout-sections.d.ts +56 -0
- package/dist/renderer/layout-sections.js +462 -0
- package/dist/renderer/layout.d.ts +4 -2
- package/dist/renderer/layout.js +25 -500
- package/dist/repl.js +3 -1
- package/dist/services/SkillExtractor.js +2 -0
- package/dist/tools/SkillTool/index.js +26 -2
- package/dist/tools/TodoWriteTool/index.d.ts +37 -0
- package/dist/tools/TodoWriteTool/index.js +78 -0
- package/dist/tools.js +2 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { discoverSkills, findSkill } from "../../harness/plugins.js";
|
|
@@ -24,7 +24,7 @@ export const SkillTool = {
|
|
|
24
24
|
return { output: "Error: Invalid skill name.", isError: true };
|
|
25
25
|
}
|
|
26
26
|
// Early path traversal check for Level 2
|
|
27
|
-
if (input.path
|
|
27
|
+
if (input.path?.includes("..")) {
|
|
28
28
|
return { output: "Error: Path traversal not allowed.", isError: true };
|
|
29
29
|
}
|
|
30
30
|
// List skills if "list" or "ls"
|
|
@@ -62,6 +62,30 @@ export const SkillTool = {
|
|
|
62
62
|
return { output: `File not found: ${input.path} (looked in ${skillDir}/)`, isError: true };
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
// Track usage (fire-and-forget, don't block skill invocation)
|
|
66
|
+
try {
|
|
67
|
+
let raw = readFileSync(skill.filePath, "utf-8");
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const usedMatch = raw.match(/^timesUsed:\s*(\d+)$/m);
|
|
70
|
+
const count = usedMatch ? parseInt(usedMatch[1], 10) + 1 : 1;
|
|
71
|
+
if (usedMatch) {
|
|
72
|
+
raw = raw.replace(/^timesUsed:\s*\d+$/m, `timesUsed: ${count}`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const first = raw.indexOf("---");
|
|
76
|
+
const closing = raw.indexOf("---", first + 3);
|
|
77
|
+
if (closing > 0) {
|
|
78
|
+
raw = `${raw.slice(0, closing)}timesUsed: ${count}\nlastUsed: ${now}\n${raw.slice(closing)}`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (raw.match(/^lastUsed:/m)) {
|
|
82
|
+
raw = raw.replace(/^lastUsed:\s*\d+$/m, `lastUsed: ${now}`);
|
|
83
|
+
}
|
|
84
|
+
writeFileSync(skill.filePath, raw);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
/* don't block on tracking failure */
|
|
88
|
+
}
|
|
65
89
|
return { output: skill.content, isError: false };
|
|
66
90
|
},
|
|
67
91
|
prompt() {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Tool } from "../../Tool.js";
|
|
3
|
+
declare const inputSchema: z.ZodObject<{
|
|
4
|
+
todos: z.ZodArray<z.ZodObject<{
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
content: z.ZodString;
|
|
7
|
+
status: z.ZodDefault<z.ZodEnum<["pending", "in_progress", "completed"]>>;
|
|
8
|
+
priority: z.ZodOptional<z.ZodEnum<["high", "medium", "low"]>>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
content: string;
|
|
11
|
+
status: "completed" | "pending" | "in_progress";
|
|
12
|
+
id: string;
|
|
13
|
+
priority?: "low" | "medium" | "high" | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
content: string;
|
|
16
|
+
id: string;
|
|
17
|
+
status?: "completed" | "pending" | "in_progress" | undefined;
|
|
18
|
+
priority?: "low" | "medium" | "high" | undefined;
|
|
19
|
+
}>, "many">;
|
|
20
|
+
}, "strip", z.ZodTypeAny, {
|
|
21
|
+
todos: {
|
|
22
|
+
content: string;
|
|
23
|
+
status: "completed" | "pending" | "in_progress";
|
|
24
|
+
id: string;
|
|
25
|
+
priority?: "low" | "medium" | "high" | undefined;
|
|
26
|
+
}[];
|
|
27
|
+
}, {
|
|
28
|
+
todos: {
|
|
29
|
+
content: string;
|
|
30
|
+
id: string;
|
|
31
|
+
status?: "completed" | "pending" | "in_progress" | undefined;
|
|
32
|
+
priority?: "low" | "medium" | "high" | undefined;
|
|
33
|
+
}[];
|
|
34
|
+
}>;
|
|
35
|
+
export declare const TodoWriteTool: Tool<typeof inputSchema>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const todoSchema = z.object({
|
|
5
|
+
id: z.string(),
|
|
6
|
+
content: z.string(),
|
|
7
|
+
status: z.enum(["pending", "in_progress", "completed"]).default("pending"),
|
|
8
|
+
priority: z.enum(["high", "medium", "low"]).optional(),
|
|
9
|
+
});
|
|
10
|
+
const inputSchema = z.object({
|
|
11
|
+
todos: z.array(todoSchema).describe("List of todo items to write. Existing items with matching IDs are updated."),
|
|
12
|
+
});
|
|
13
|
+
export const TodoWriteTool = {
|
|
14
|
+
name: "TodoWrite",
|
|
15
|
+
description: "Write or update todo items. Creates new items or updates existing ones by ID.",
|
|
16
|
+
inputSchema,
|
|
17
|
+
riskLevel: "low",
|
|
18
|
+
isReadOnly() {
|
|
19
|
+
return false;
|
|
20
|
+
},
|
|
21
|
+
isConcurrencySafe() {
|
|
22
|
+
return false;
|
|
23
|
+
},
|
|
24
|
+
async call(input, context) {
|
|
25
|
+
const dir = path.join(context.workingDir, ".oh");
|
|
26
|
+
const filePath = path.join(dir, "todos.json");
|
|
27
|
+
try {
|
|
28
|
+
await fs.mkdir(dir, { recursive: true });
|
|
29
|
+
let existing = [];
|
|
30
|
+
try {
|
|
31
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
32
|
+
existing = JSON.parse(content);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// File doesn't exist yet
|
|
36
|
+
}
|
|
37
|
+
const existingMap = new Map(existing.map((t) => [t.id, t]));
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
let created = 0;
|
|
40
|
+
let updated = 0;
|
|
41
|
+
for (const item of input.todos) {
|
|
42
|
+
const prev = existingMap.get(item.id);
|
|
43
|
+
if (prev) {
|
|
44
|
+
existingMap.set(item.id, { ...prev, ...item, updatedAt: now });
|
|
45
|
+
updated++;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
existingMap.set(item.id, { ...item, createdAt: now, updatedAt: now });
|
|
49
|
+
created++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const todos = [...existingMap.values()];
|
|
53
|
+
await fs.writeFile(filePath, JSON.stringify(todos, null, 2), "utf-8");
|
|
54
|
+
const parts = [];
|
|
55
|
+
if (created > 0)
|
|
56
|
+
parts.push(`${created} created`);
|
|
57
|
+
if (updated > 0)
|
|
58
|
+
parts.push(`${updated} updated`);
|
|
59
|
+
const total = todos.filter((t) => t.status !== "completed").length;
|
|
60
|
+
return {
|
|
61
|
+
output: `Todos: ${parts.join(", ")}. ${total} remaining (${todos.length} total).`,
|
|
62
|
+
isError: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return { output: `Error writing todos: ${err.message}`, isError: true };
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
prompt() {
|
|
70
|
+
return `Write or update todo items in .oh/todos.json. Each item has:
|
|
71
|
+
- id (string, required): Unique identifier for the todo.
|
|
72
|
+
- content (string, required): Description of what needs to be done.
|
|
73
|
+
- status ("pending" | "in_progress" | "completed"): Current status. Default: "pending".
|
|
74
|
+
- priority ("high" | "medium" | "low", optional): Priority level.
|
|
75
|
+
Items with matching IDs are updated; new IDs create new items.`;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=index.js.map
|
package/dist/tools.js
CHANGED
|
@@ -43,6 +43,7 @@ import { TaskListTool } from "./tools/TaskListTool/index.js";
|
|
|
43
43
|
import { TaskOutputTool } from "./tools/TaskOutputTool/index.js";
|
|
44
44
|
import { TaskStopTool } from "./tools/TaskStopTool/index.js";
|
|
45
45
|
import { TaskUpdateTool } from "./tools/TaskUpdateTool/index.js";
|
|
46
|
+
import { TodoWriteTool } from "./tools/TodoWriteTool/index.js";
|
|
46
47
|
import { ToolSearchTool } from "./tools/ToolSearchTool/index.js";
|
|
47
48
|
import { WebFetchTool } from "./tools/WebFetchTool/index.js";
|
|
48
49
|
import { WebSearchTool } from "./tools/WebSearchTool/index.js";
|
|
@@ -104,6 +105,7 @@ export function getAllTools() {
|
|
|
104
105
|
MonitorTool,
|
|
105
106
|
ScheduleWakeupTool,
|
|
106
107
|
SessionSearchTool,
|
|
108
|
+
TodoWriteTool,
|
|
107
109
|
];
|
|
108
110
|
return [...core, ...extended.map((t) => new DeferredTool(t))];
|
|
109
111
|
}
|