@plimeor/harness 0.1.0 → 0.1.2
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/package.json +1 -1
- package/src/adapters/claude.ts +35 -39
- package/src/adapters/codex-extensions.ts +266 -0
- package/src/adapters/codex.ts +16 -19
- package/src/adapters/extensions.ts +168 -450
- package/src/adapters/kiro-extensions.ts +182 -0
- package/src/adapters/kiro.ts +20 -19
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import type { HarnessContext, HookResource } from '../types'
|
|
5
|
+
import type { HookExtensionDriver, InstalledHook, JsonObject, McpExtensionDriver } from './extensions'
|
|
6
|
+
import {
|
|
7
|
+
createJsonMcpDriver,
|
|
8
|
+
jsonFingerprint,
|
|
9
|
+
mcpServerRecord,
|
|
10
|
+
pathExists,
|
|
11
|
+
readJsonFileFingerprint,
|
|
12
|
+
safeName,
|
|
13
|
+
writeJsonFile
|
|
14
|
+
} from './extensions'
|
|
15
|
+
|
|
16
|
+
export function createKiroMcpDriver(input: {
|
|
17
|
+
configDirectory: string
|
|
18
|
+
configFile: string
|
|
19
|
+
context?: HarnessContext
|
|
20
|
+
}): McpExtensionDriver {
|
|
21
|
+
const jsonDriver = createJsonMcpDriver(input.configFile)
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
configFile: input.configFile,
|
|
25
|
+
currentFingerprint: jsonDriver.currentFingerprint,
|
|
26
|
+
async install({ name, server }) {
|
|
27
|
+
const args = ['mcp', 'add', '--scope', 'global', '--name', name, '--command', server.command]
|
|
28
|
+
|
|
29
|
+
if (server.args && server.args.length > 0) {
|
|
30
|
+
args.push('--args', JSON.stringify(server.args))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const [key, value] of Object.entries(server.env ?? {})) {
|
|
34
|
+
args.push('--env', `${key}=${value}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await runKiroCommand(input, args)
|
|
38
|
+
|
|
39
|
+
const fingerprint = await jsonDriver.currentFingerprint(name)
|
|
40
|
+
if (!fingerprint) {
|
|
41
|
+
await removeKiroMcpServer(input, name)
|
|
42
|
+
throw new Error(`Kiro MCP server ${name} was not written to ${input.configFile}.`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { fingerprint, name, server: mcpServerRecord(server) }
|
|
46
|
+
},
|
|
47
|
+
async remove(name: string) {
|
|
48
|
+
await removeKiroMcpServer(input, name)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createKiroHookDriver(hooksDirectory: string, events: readonly string[]): HookExtensionDriver {
|
|
54
|
+
return {
|
|
55
|
+
events,
|
|
56
|
+
async conflicts({ extensionId, hooks }) {
|
|
57
|
+
const issues: Awaited<ReturnType<HookExtensionDriver['conflicts']>> = []
|
|
58
|
+
|
|
59
|
+
for (const hook of hooks) {
|
|
60
|
+
const targetPath = kiroHookTargetPath(hooksDirectory, extensionId, hook)
|
|
61
|
+
if (await pathExists(targetPath)) {
|
|
62
|
+
issues.push({
|
|
63
|
+
kind: 'conflict',
|
|
64
|
+
reason: `Hook install target already exists: ${targetPath}.`,
|
|
65
|
+
resourceKind: 'hooks',
|
|
66
|
+
resourceName: hook.name
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return issues
|
|
72
|
+
},
|
|
73
|
+
async currentFingerprint(hook: InstalledHook) {
|
|
74
|
+
if (!hook.targetPath) {
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return readJsonFileFingerprint(hook.targetPath)
|
|
79
|
+
},
|
|
80
|
+
async install({ extensionId, hooks }) {
|
|
81
|
+
const installed: InstalledHook[] = []
|
|
82
|
+
|
|
83
|
+
for (const hook of hooks) {
|
|
84
|
+
const targetPath = kiroHookTargetPath(hooksDirectory, extensionId, hook)
|
|
85
|
+
const hookConfig = kiroHookConfig(hook)
|
|
86
|
+
await writeJsonFile(targetPath, hookConfig)
|
|
87
|
+
installed.push({
|
|
88
|
+
command: hook.command,
|
|
89
|
+
event: hook.event,
|
|
90
|
+
fingerprint: jsonFingerprint(hookConfig),
|
|
91
|
+
name: hook.name,
|
|
92
|
+
targetPath
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return installed
|
|
97
|
+
},
|
|
98
|
+
async restore(hooks: InstalledHook[]) {
|
|
99
|
+
for (const hook of hooks) {
|
|
100
|
+
if (!hook.targetPath || !hook.name || (await pathExists(hook.targetPath))) {
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await writeJsonFile(
|
|
105
|
+
hook.targetPath,
|
|
106
|
+
kiroHookConfig({ command: hook.command, event: hook.event, name: hook.name })
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async uninstall(hooks: InstalledHook[]) {
|
|
111
|
+
for (const hook of hooks) {
|
|
112
|
+
if (!hook.targetPath) {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const current = await readJsonFileFingerprint(hook.targetPath)
|
|
117
|
+
if (current !== hook.fingerprint) {
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await rm(hook.targetPath, { force: true })
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function removeKiroMcpServer(
|
|
128
|
+
config: { configDirectory: string; context?: HarnessContext },
|
|
129
|
+
name: string
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
await runKiroCommand(config, ['mcp', 'remove', '--scope', 'global', '--name', name])
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function runKiroCommand(
|
|
135
|
+
config: { configDirectory: string; context?: HarnessContext },
|
|
136
|
+
args: string[]
|
|
137
|
+
): Promise<{ exitCode: number; stderr: string; stdout: string }> {
|
|
138
|
+
const subprocess = Bun.spawn({
|
|
139
|
+
cmd: ['kiro-cli', ...args],
|
|
140
|
+
cwd: config.context?.cwd ?? process.cwd(),
|
|
141
|
+
env: Object.fromEntries(
|
|
142
|
+
Object.entries({
|
|
143
|
+
...process.env,
|
|
144
|
+
...(config.context?.home ? { KIRO_HOME: config.configDirectory } : {}),
|
|
145
|
+
...(config.context?.env ?? {})
|
|
146
|
+
}).filter((entry): entry is [string, string] => {
|
|
147
|
+
return typeof entry[1] === 'string'
|
|
148
|
+
})
|
|
149
|
+
),
|
|
150
|
+
stderr: 'pipe',
|
|
151
|
+
stdout: 'pipe'
|
|
152
|
+
})
|
|
153
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
154
|
+
subprocess.exited,
|
|
155
|
+
new Response(subprocess.stdout).text(),
|
|
156
|
+
new Response(subprocess.stderr).text()
|
|
157
|
+
])
|
|
158
|
+
|
|
159
|
+
if (exitCode !== 0) {
|
|
160
|
+
throw new Error(`kiro-cli ${args.join(' ')} failed: ${stderr || stdout}`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { exitCode, stderr, stdout }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function kiroHookTargetPath(hooksDirectory: string, extensionId: string, hook: HookResource): string {
|
|
167
|
+
return join(hooksDirectory, `${safeName(extensionId)}__${safeName(hook.name)}.json`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function kiroHookConfig(hook: HookResource): JsonObject {
|
|
171
|
+
return {
|
|
172
|
+
version: 'v1',
|
|
173
|
+
hooks: [
|
|
174
|
+
{
|
|
175
|
+
action: { command: hook.command, type: 'command' },
|
|
176
|
+
enabled: true,
|
|
177
|
+
name: hook.name,
|
|
178
|
+
trigger: hook.event
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
}
|
package/src/adapters/kiro.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { harness } from '../registry'
|
|
2
2
|
import type { HarnessContext, RunOutputRequest, RunRequest, TextOutputRequest } from '../types'
|
|
3
3
|
import { configDirectory, createExtensionFacet } from './extensions'
|
|
4
|
+
import { createKiroHookDriver, createKiroMcpDriver } from './kiro-extensions'
|
|
4
5
|
import { createBuiltInAdapter, planTextCommand, unsupportedOutputMode } from './shared'
|
|
5
6
|
|
|
6
7
|
const HARNESS_ID = 'kiro'
|
|
@@ -16,25 +17,25 @@ export const kiroAdapter = createBuiltInAdapter({
|
|
|
16
17
|
configDirectory: directory,
|
|
17
18
|
context,
|
|
18
19
|
harnessId: HARNESS_ID,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
20
|
+
hooks: createKiroHookDriver(`${directory}/hooks`, [
|
|
21
|
+
'Manual',
|
|
22
|
+
'PostFileCreate',
|
|
23
|
+
'PostFileDelete',
|
|
24
|
+
'PostFileSave',
|
|
25
|
+
'PostTaskExec',
|
|
26
|
+
'PostToolUse',
|
|
27
|
+
'PreTaskExec',
|
|
28
|
+
'PreToolUse',
|
|
29
|
+
'SessionStart',
|
|
30
|
+
'Stop',
|
|
31
|
+
'UserPromptSubmit'
|
|
32
|
+
]),
|
|
33
|
+
mcp: createKiroMcpDriver({
|
|
34
|
+
configDirectory: directory,
|
|
35
|
+
configFile: `${directory}/settings/mcp.json`,
|
|
36
|
+
context
|
|
37
|
+
}),
|
|
38
|
+
skillsDirectory: `${directory}/skills`
|
|
38
39
|
})
|
|
39
40
|
},
|
|
40
41
|
plan(request: RunRequest<RunOutputRequest>, command: string, cwd: string) {
|