@cogineai/dearharness 0.1.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 +141 -0
- package/dist/cli.js +259 -0
- package/dist/config.js +21 -0
- package/dist/daemon/server.js +225 -0
- package/dist/extensions/builtin/policy-instructions.js +19 -0
- package/dist/extensions/loader.js +58 -0
- package/dist/extensions/types.js +1 -0
- package/dist/harness/install-applicator.js +126 -0
- package/dist/harness/install-apply.js +156 -0
- package/dist/harness/install-plan.js +154 -0
- package/dist/harness/install-runner.js +178 -0
- package/dist/harness/install-verification.js +117 -0
- package/dist/harness/lockfile.js +83 -0
- package/dist/harness/manifest.js +491 -0
- package/dist/harness/source.js +224 -0
- package/dist/harness/transaction.js +77 -0
- package/dist/harness/workspace.js +61 -0
- package/dist/index.js +9 -0
- package/dist/instructions/builder.js +33 -0
- package/dist/instructions/types.js +1 -0
- package/dist/model/config.js +100 -0
- package/dist/model/http.js +128 -0
- package/dist/model/index.js +22 -0
- package/dist/model/openrouter.js +9 -0
- package/dist/model/providers/anthropic.js +104 -0
- package/dist/model/providers/ollama-discovery.js +32 -0
- package/dist/model/providers/ollama.js +70 -0
- package/dist/model/providers/openai-compatible.js +118 -0
- package/dist/model/providers/openai.js +4 -0
- package/dist/model/providers/openrouter.js +79 -0
- package/dist/model/registry.js +108 -0
- package/dist/model/types.js +1 -0
- package/dist/policy/engine.js +30 -0
- package/dist/policy/types.js +1 -0
- package/dist/prompt/system.js +30 -0
- package/dist/protocol/actions.js +88 -0
- package/dist/runtime/assembly.js +54 -0
- package/dist/runtime/events.js +1 -0
- package/dist/runtime/hooks.js +13 -0
- package/dist/runtime/runner.js +193 -0
- package/dist/session/store.js +198 -0
- package/dist/session/types.js +1 -0
- package/dist/skills/loader.js +51 -0
- package/dist/skills/types.js +1 -0
- package/dist/tools/bash.js +71 -0
- package/dist/tools/edit.js +61 -0
- package/dist/tools/find.js +67 -0
- package/dist/tools/grep.js +88 -0
- package/dist/tools/ls.js +37 -0
- package/dist/tools/path.js +35 -0
- package/dist/tools/read.js +40 -0
- package/dist/tools/registry.js +18 -0
- package/dist/tools/types.js +1 -0
- package/dist/workspace/config.js +72 -0
- package/package.json +52 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { FIND_MAX_DEPTH, FIND_MAX_RESULTS } from '../config.js';
|
|
4
|
+
import { resolveWorkspaceEntry, resolveWorkspacePath } from './path.js';
|
|
5
|
+
async function collectMatches(root, query, workspaceRealPath, out, depth, seenRealPaths, maxDepth) {
|
|
6
|
+
if (out.length >= FIND_MAX_RESULTS || depth > maxDepth) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const rootRealPath = await fs.realpath(root);
|
|
10
|
+
if (seenRealPaths.has(rootRealPath)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
seenRealPaths.add(rootRealPath);
|
|
14
|
+
const entries = await fs.readdir(rootRealPath, { withFileTypes: true });
|
|
15
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
16
|
+
if (out.length >= FIND_MAX_RESULTS) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
const target = path.join(rootRealPath, entry.name);
|
|
20
|
+
const resolved = await resolveWorkspaceEntry(workspaceRealPath, target).catch(() => null);
|
|
21
|
+
if (!resolved) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (entry.name.includes(query)) {
|
|
25
|
+
if (out.length >= FIND_MAX_RESULTS) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
out.push(resolved.relativePath);
|
|
29
|
+
}
|
|
30
|
+
if (out.length >= FIND_MAX_RESULTS || depth >= maxDepth || !resolved.stat.isDirectory()) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (seenRealPaths.has(resolved.targetRealPath)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
await collectMatches(resolved.targetRealPath, query, workspaceRealPath, out, depth + 1, seenRealPaths, maxDepth);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export const findTool = {
|
|
40
|
+
name: 'find',
|
|
41
|
+
access: 'read',
|
|
42
|
+
supports(action) {
|
|
43
|
+
return typeof action.find === 'object' && !!action.find;
|
|
44
|
+
},
|
|
45
|
+
async execute(action, context) {
|
|
46
|
+
try {
|
|
47
|
+
const { relativePath, targetRealPath, workspaceRealPath } = await resolveWorkspacePath(context.cwd, action.find.path ?? '.');
|
|
48
|
+
const matches = [];
|
|
49
|
+
await collectMatches(targetRealPath, action.find.name, workspaceRealPath, matches, 0, new Set(), FIND_MAX_DEPTH);
|
|
50
|
+
return {
|
|
51
|
+
tool: 'find',
|
|
52
|
+
status: 'ok',
|
|
53
|
+
meta: { path: relativePath, matches: matches.length },
|
|
54
|
+
content: `TOOL_RESULT find OK\npath=${relativePath}\n${matches.join('\n')}`.trim()
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
return {
|
|
60
|
+
tool: 'find',
|
|
61
|
+
status: 'error',
|
|
62
|
+
meta: { path: action.find.path ?? '.', error: message },
|
|
63
|
+
content: `TOOL_RESULT find ERROR\npath=${action.find.path ?? '.'}\n${message}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { GREP_MAX_FILE_BYTES, GREP_MAX_MATCHES } from '../config.js';
|
|
4
|
+
import { resolveWorkspaceEntry, resolveWorkspacePath } from './path.js';
|
|
5
|
+
async function collectFileMatches(entry, pattern, out) {
|
|
6
|
+
if (out.length >= GREP_MAX_MATCHES) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!entry.stat.isFile()) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (entry.stat.size > GREP_MAX_FILE_BYTES) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const raw = await fs.readFile(entry.targetRealPath, 'utf8').catch(() => null);
|
|
16
|
+
if (raw === null) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const [index, line] of raw.split('\n').entries()) {
|
|
20
|
+
if (out.length >= GREP_MAX_MATCHES) {
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
if (line.includes(pattern)) {
|
|
24
|
+
out.push(`${entry.relativePath}:${index + 1}: ${line}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function collectGrepMatches(root, pattern, workspaceRealPath, out, seenRealPaths) {
|
|
29
|
+
if (out.length >= GREP_MAX_MATCHES) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const rootRealPath = await fs.realpath(root);
|
|
33
|
+
if (seenRealPaths.has(rootRealPath)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
seenRealPaths.add(rootRealPath);
|
|
37
|
+
const entries = await fs.readdir(rootRealPath, { withFileTypes: true });
|
|
38
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
39
|
+
if (out.length >= GREP_MAX_MATCHES) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
const target = path.join(rootRealPath, entry.name);
|
|
43
|
+
const resolved = await resolveWorkspaceEntry(workspaceRealPath, target).catch(() => null);
|
|
44
|
+
if (!resolved) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (resolved.stat.isDirectory()) {
|
|
48
|
+
await collectGrepMatches(resolved.targetRealPath, pattern, workspaceRealPath, out, seenRealPaths);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
await collectFileMatches(resolved, pattern, out);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export const grepTool = {
|
|
55
|
+
name: 'grep',
|
|
56
|
+
access: 'read',
|
|
57
|
+
supports(action) {
|
|
58
|
+
return typeof action.grep === 'object' && !!action.grep;
|
|
59
|
+
},
|
|
60
|
+
async execute(action, context) {
|
|
61
|
+
try {
|
|
62
|
+
const { relativePath, targetRealPath, workspaceRealPath } = await resolveWorkspacePath(context.cwd, action.grep.path ?? '.');
|
|
63
|
+
const matches = [];
|
|
64
|
+
const targetStats = await fs.stat(targetRealPath);
|
|
65
|
+
if (targetStats.isDirectory()) {
|
|
66
|
+
await collectGrepMatches(targetRealPath, action.grep.pattern, workspaceRealPath, matches, new Set());
|
|
67
|
+
}
|
|
68
|
+
else if (targetStats.isFile()) {
|
|
69
|
+
await collectFileMatches({ stat: targetStats, targetRealPath, relativePath }, action.grep.pattern, matches);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
tool: 'grep',
|
|
73
|
+
status: 'ok',
|
|
74
|
+
meta: { path: relativePath, matches: matches.length },
|
|
75
|
+
content: `TOOL_RESULT grep OK\npath=${relativePath}\n${matches.join('\n')}`.trim()
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
return {
|
|
81
|
+
tool: 'grep',
|
|
82
|
+
status: 'error',
|
|
83
|
+
meta: { path: action.grep.path ?? '.', error: message },
|
|
84
|
+
content: `TOOL_RESULT grep ERROR\npath=${action.grep.path ?? '.'}\n${message}`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
package/dist/tools/ls.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { LIST_MAX_ENTRIES } from '../config.js';
|
|
3
|
+
import { resolveWorkspacePath } from './path.js';
|
|
4
|
+
export const lsTool = {
|
|
5
|
+
name: 'ls',
|
|
6
|
+
access: 'read',
|
|
7
|
+
supports(action) {
|
|
8
|
+
return typeof action.ls === 'object' && !!action.ls;
|
|
9
|
+
},
|
|
10
|
+
async execute(action, context) {
|
|
11
|
+
try {
|
|
12
|
+
const { relativePath, targetRealPath } = await resolveWorkspacePath(context.cwd, action.ls.path ?? '.');
|
|
13
|
+
const allEntries = (await fs.readdir(targetRealPath, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
14
|
+
const truncated = allEntries.length > LIST_MAX_ENTRIES;
|
|
15
|
+
const entries = allEntries
|
|
16
|
+
.slice(0, LIST_MAX_ENTRIES)
|
|
17
|
+
.map((entry) => `${entry.isDirectory() ? 'dir' : 'file'} ${entry.name}${entry.isDirectory() ? '/' : ''}`)
|
|
18
|
+
.join('\n');
|
|
19
|
+
const truncationNotice = truncated ? `\n... (truncated, showing ${Math.min(allEntries.length, LIST_MAX_ENTRIES)} of ${allEntries.length} entries)` : '';
|
|
20
|
+
return {
|
|
21
|
+
tool: 'ls',
|
|
22
|
+
status: 'ok',
|
|
23
|
+
meta: { path: relativePath },
|
|
24
|
+
content: `TOOL_RESULT ls OK\npath=${relativePath}\n${entries}${truncationNotice}`.trim()
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
return {
|
|
30
|
+
tool: 'ls',
|
|
31
|
+
status: 'error',
|
|
32
|
+
meta: { path: action.ls.path ?? '.', error: message },
|
|
33
|
+
content: `TOOL_RESULT ls ERROR\npath=${action.ls.path ?? '.'}\n${message}`
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const WORKSPACE_PATH_ERROR = 'path must stay inside the workspace and be workspace-relative';
|
|
4
|
+
export function isPathInsideWorkspace(workspaceRealPath, targetRealPath) {
|
|
5
|
+
const relativePath = path.relative(workspaceRealPath, targetRealPath);
|
|
6
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
7
|
+
}
|
|
8
|
+
export async function resolveWorkspacePath(cwd, inputPath) {
|
|
9
|
+
const target = path.resolve(cwd, inputPath);
|
|
10
|
+
const relativePath = path.relative(cwd, target) || '.';
|
|
11
|
+
if (path.isAbsolute(inputPath) || relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
12
|
+
throw new Error(WORKSPACE_PATH_ERROR);
|
|
13
|
+
}
|
|
14
|
+
const workspaceRealPath = await fs.realpath(cwd);
|
|
15
|
+
const targetRealPath = await fs.realpath(target);
|
|
16
|
+
if (!isPathInsideWorkspace(workspaceRealPath, targetRealPath)) {
|
|
17
|
+
throw new Error(WORKSPACE_PATH_ERROR);
|
|
18
|
+
}
|
|
19
|
+
return { target, relativePath, workspaceRealPath, targetRealPath };
|
|
20
|
+
}
|
|
21
|
+
export async function resolveWorkspaceEntry(workspaceRealPath, target) {
|
|
22
|
+
const stat = await fs.lstat(target);
|
|
23
|
+
if (stat.isSymbolicLink()) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const targetRealPath = await fs.realpath(target);
|
|
27
|
+
if (!isPathInsideWorkspace(workspaceRealPath, targetRealPath)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
stat,
|
|
32
|
+
targetRealPath,
|
|
33
|
+
relativePath: path.relative(workspaceRealPath, targetRealPath) || '.'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { READ_MAX_BYTES } from '../config.js';
|
|
3
|
+
import { resolveWorkspacePath } from './path.js';
|
|
4
|
+
export const readTool = {
|
|
5
|
+
name: 'read',
|
|
6
|
+
access: 'read',
|
|
7
|
+
supports(action) {
|
|
8
|
+
return typeof action.read === 'object' && !!action.read;
|
|
9
|
+
},
|
|
10
|
+
async execute(action, context) {
|
|
11
|
+
try {
|
|
12
|
+
const { relativePath, targetRealPath } = await resolveWorkspacePath(context.cwd, action.read.path);
|
|
13
|
+
const raw = await fs.readFile(targetRealPath, 'utf8');
|
|
14
|
+
const lines = raw.split('\n');
|
|
15
|
+
const start = Math.min(Math.max(1, action.read.start_line ?? 1), lines.length);
|
|
16
|
+
const requestedEnd = action.read.end_line ?? Math.min(lines.length, start + 199);
|
|
17
|
+
const end = Math.max(start, Math.min(lines.length, requestedEnd));
|
|
18
|
+
const snippet = lines
|
|
19
|
+
.slice(start - 1, end)
|
|
20
|
+
.map((line, index) => `${start + index}| ${line}`)
|
|
21
|
+
.join('\n')
|
|
22
|
+
.slice(0, READ_MAX_BYTES);
|
|
23
|
+
return {
|
|
24
|
+
tool: 'read',
|
|
25
|
+
status: 'ok',
|
|
26
|
+
meta: { path: relativePath, start_line: start, end_line: end },
|
|
27
|
+
content: `TOOL_RESULT read OK\npath=${relativePath}\n${snippet}`.trim()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32
|
+
return {
|
|
33
|
+
tool: 'read',
|
|
34
|
+
status: 'error',
|
|
35
|
+
meta: { path: action.read.path, error: message },
|
|
36
|
+
content: `TOOL_RESULT read ERROR\npath=${action.read.path}\n${message}`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { bashTool } from './bash.js';
|
|
2
|
+
import { editTool } from './edit.js';
|
|
3
|
+
import { findTool } from './find.js';
|
|
4
|
+
import { grepTool } from './grep.js';
|
|
5
|
+
import { lsTool } from './ls.js';
|
|
6
|
+
import { readTool } from './read.js';
|
|
7
|
+
export function createToolRegistry(definitions = [bashTool, editTool, readTool, lsTool, findTool, grepTool]) {
|
|
8
|
+
return {
|
|
9
|
+
definitions,
|
|
10
|
+
resolve(action) {
|
|
11
|
+
const definition = definitions.find((candidate) => candidate.supports(action));
|
|
12
|
+
if (!definition) {
|
|
13
|
+
throw new Error(`No tool registered for action: ${JSON.stringify(action)}`);
|
|
14
|
+
}
|
|
15
|
+
return { definition };
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { APP_DIR } from '../config.js';
|
|
4
|
+
const EMPTY_WORKSPACE_CONFIG = {
|
|
5
|
+
instructionFiles: [],
|
|
6
|
+
extensions: [],
|
|
7
|
+
defaultSkills: []
|
|
8
|
+
};
|
|
9
|
+
function cloneEmptyWorkspaceConfig() {
|
|
10
|
+
return {
|
|
11
|
+
instructionFiles: [...EMPTY_WORKSPACE_CONFIG.instructionFiles],
|
|
12
|
+
extensions: [...EMPTY_WORKSPACE_CONFIG.extensions],
|
|
13
|
+
defaultSkills: [...EMPTY_WORKSPACE_CONFIG.defaultSkills]
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function readStringArray(record, key) {
|
|
17
|
+
const value = record[key];
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
|
22
|
+
throw new Error(`${key} must be an array of strings`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function readModelConfig(record) {
|
|
27
|
+
const value = record.model;
|
|
28
|
+
if (value === undefined) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
32
|
+
throw new Error('model must be an object');
|
|
33
|
+
}
|
|
34
|
+
const model = value;
|
|
35
|
+
for (const key of ['provider', 'model', 'baseUrl', 'streaming']) {
|
|
36
|
+
if (model[key] !== undefined && typeof model[key] !== 'string') {
|
|
37
|
+
throw new Error(`model.${key} must be a string`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (typeof model.streaming === 'string' && !['auto', 'on', 'off'].includes(model.streaming)) {
|
|
41
|
+
throw new Error('model.streaming must be one of: auto, on, off');
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
...(typeof model.provider === 'string' ? { provider: model.provider } : {}),
|
|
45
|
+
...(typeof model.model === 'string' ? { model: model.model } : {}),
|
|
46
|
+
...(typeof model.baseUrl === 'string' ? { baseUrl: model.baseUrl } : {}),
|
|
47
|
+
...(typeof model.streaming === 'string' ? { streaming: model.streaming } : {})
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function loadWorkspaceConfig(cwd) {
|
|
51
|
+
const target = path.join(cwd, APP_DIR, 'config.json');
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(await fs.readFile(target, 'utf8'));
|
|
54
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
55
|
+
throw new Error('workspace config must be a JSON object');
|
|
56
|
+
}
|
|
57
|
+
const record = parsed;
|
|
58
|
+
const model = readModelConfig(record);
|
|
59
|
+
return {
|
|
60
|
+
instructionFiles: readStringArray(record, 'instructionFiles'),
|
|
61
|
+
extensions: readStringArray(record, 'extensions'),
|
|
62
|
+
defaultSkills: readStringArray(record, 'defaultSkills'),
|
|
63
|
+
...(model ? { model } : {})
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code === 'ENOENT') {
|
|
68
|
+
return cloneEmptyWorkspaceConfig();
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cogineai/dearharness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw Harness operator CLI and instance-side daemon prototype.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/cogine-ai/dearharness-cli#readme",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/cogine-ai/dearharness-cli.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/cogine-ai/dearharness-cli/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"dearharness",
|
|
20
|
+
"dearclaw",
|
|
21
|
+
"openclaw",
|
|
22
|
+
"harness",
|
|
23
|
+
"cli",
|
|
24
|
+
"daemon"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"dearharness": "dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist/**/*.js",
|
|
31
|
+
"!dist/**/*.test.js",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"start": "node dist/index.js",
|
|
41
|
+
"dev": "tsx src/index.ts",
|
|
42
|
+
"test": "node --test --import tsx \"src/**/*.test.ts\""
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"fflate": "^0.8.2"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^24.5.2",
|
|
49
|
+
"tsx": "^4.20.5",
|
|
50
|
+
"typescript": "^5.9.2"
|
|
51
|
+
}
|
|
52
|
+
}
|