@openturtle/cli 0.3.2 → 0.3.3
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 +27 -148
- package/dist/commands/resources.js +2 -5
- package/dist/core/auto-update.js +18 -1
- package/dist/core/installations.js +41 -0
- package/dist/core/{mcp-config.js → legacy-agent-cleanup.js} +10 -42
- package/dist/core/store.js +1 -20
- package/dist/index.js +33 -84
- package/dist/installers/agents.js +84 -195
- package/dist/installers/legacy-git-cleanup.js +29 -0
- package/package.json +2 -2
- package/dist/commands/hook.js +0 -162
- package/dist/installers/git.js +0 -63
- package/dist/mcp/server.js +0 -185
- package/dist/watchers/importers.js +0 -69
package/dist/commands/hook.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { compactJson } from '../core/json.js';
|
|
4
|
-
import { findWorkspaceRoot } from '../core/paths.js';
|
|
5
|
-
import { initProjectStore, recordCommit, recordHookRun, upsertEvent } from '../core/store.js';
|
|
6
|
-
const EVENT_MAP = {
|
|
7
|
-
UserPromptSubmit: 'agent.user_prompt',
|
|
8
|
-
userPromptSubmit: 'agent.user_prompt',
|
|
9
|
-
user_prompt_submit: 'agent.user_prompt',
|
|
10
|
-
pre_tool_use: 'agent.pre_tool_use',
|
|
11
|
-
PreToolUse: 'agent.pre_tool_use',
|
|
12
|
-
preToolUse: 'agent.pre_tool_use',
|
|
13
|
-
post_tool_use: 'agent.post_tool_use',
|
|
14
|
-
PostToolUse: 'agent.post_tool_use',
|
|
15
|
-
postToolUse: 'agent.post_tool_use',
|
|
16
|
-
PostToolUseFailure: 'agent.post_tool_use_failure',
|
|
17
|
-
postToolUseFailure: 'agent.post_tool_use_failure',
|
|
18
|
-
Stop: 'agent.stop',
|
|
19
|
-
stop: 'agent.stop',
|
|
20
|
-
agentSpawn: 'agent.spawn',
|
|
21
|
-
AgentSpawn: 'agent.spawn',
|
|
22
|
-
};
|
|
23
|
-
export function ingestHookPayload(store, input) {
|
|
24
|
-
const type = EVENT_MAP[input.event] || EVENT_MAP[camelToPascal(input.event)] || 'agent.session_imported';
|
|
25
|
-
const sourceId = stringValue(input.payload.session_id ||
|
|
26
|
-
input.payload.sessionId ||
|
|
27
|
-
input.payload.conversation_id ||
|
|
28
|
-
input.payload.conversationId ||
|
|
29
|
-
input.payload.request_id ||
|
|
30
|
-
input.payload.requestId);
|
|
31
|
-
const sequence = stringValue(input.payload.sequence ||
|
|
32
|
-
input.payload.sequence_number ||
|
|
33
|
-
input.payload.event_id ||
|
|
34
|
-
input.payload.eventId ||
|
|
35
|
-
input.payload.tool_use_id ||
|
|
36
|
-
input.payload.toolUseId ||
|
|
37
|
-
input.payload.request_id ||
|
|
38
|
-
input.payload.requestId);
|
|
39
|
-
const workspacePath = stringValue(input.payload.cwd || input.payload.workspace_path || input.payload.workspacePath) || store.workspace;
|
|
40
|
-
const summary = summarizeHookPayload(type, input.payload);
|
|
41
|
-
const stableId = sourceId || hashText(compactJson(input.payload)).slice(0, 16);
|
|
42
|
-
const stableSequence = sequence || hashText(`${input.event}:${summary}:${compactJson(input.payload)}`).slice(0, 16);
|
|
43
|
-
const dedupeKey = `${input.target}:${type}:${stableId}:${stableSequence}`;
|
|
44
|
-
const result = upsertEvent(store, {
|
|
45
|
-
type,
|
|
46
|
-
source: input.target,
|
|
47
|
-
sourceId,
|
|
48
|
-
workspacePath,
|
|
49
|
-
summary,
|
|
50
|
-
rawJson: input.payload,
|
|
51
|
-
occurredAt: stringValue(input.payload.timestamp || input.payload.occurred_at) || new Date().toISOString(),
|
|
52
|
-
dedupeKey,
|
|
53
|
-
});
|
|
54
|
-
return { ok: true, inserted: result.inserted, dedupeKey };
|
|
55
|
-
}
|
|
56
|
-
export function ingestRawHook(options) {
|
|
57
|
-
const started = Date.now();
|
|
58
|
-
const workspace = options.workspace || findWorkspaceRoot();
|
|
59
|
-
const store = initProjectStore(workspace);
|
|
60
|
-
try {
|
|
61
|
-
const payload = options.stdin.trim() ? JSON.parse(options.stdin) : {};
|
|
62
|
-
const normalized = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : { value: payload };
|
|
63
|
-
const result = ingestHookPayload(store, {
|
|
64
|
-
target: options.target,
|
|
65
|
-
event: options.event,
|
|
66
|
-
payload: normalized,
|
|
67
|
-
});
|
|
68
|
-
recordHookRun(store, {
|
|
69
|
-
target: options.target,
|
|
70
|
-
eventType: options.event,
|
|
71
|
-
exitCode: 0,
|
|
72
|
-
durationMs: Date.now() - started,
|
|
73
|
-
rawInputJson: options.stdin,
|
|
74
|
-
});
|
|
75
|
-
return result;
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
79
|
-
recordHookRun(store, {
|
|
80
|
-
target: options.target,
|
|
81
|
-
eventType: options.event,
|
|
82
|
-
exitCode: 0,
|
|
83
|
-
durationMs: Date.now() - started,
|
|
84
|
-
rawInputJson: options.stdin,
|
|
85
|
-
error: message,
|
|
86
|
-
});
|
|
87
|
-
return { ok: false, error: message };
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
export function recordGitPostCommit(workspace = findWorkspaceRoot()) {
|
|
91
|
-
const store = initProjectStore(workspace);
|
|
92
|
-
const hash = git(workspace, ['rev-parse', 'HEAD']).trim();
|
|
93
|
-
const message = git(workspace, ['log', '-1', '--pretty=%B']).trim();
|
|
94
|
-
const author = git(workspace, ['log', '-1', '--pretty=%an <%ae>']).trim();
|
|
95
|
-
const diffStat = git(workspace, ['diff-tree', '--stat', '--no-commit-id', '--root', '-r', hash]).trim();
|
|
96
|
-
const changedFiles = git(workspace, ['diff-tree', '--no-commit-id', '--name-only', '--root', '-r', hash])
|
|
97
|
-
.split(/\r?\n/)
|
|
98
|
-
.map((item) => item.trim())
|
|
99
|
-
.filter(Boolean);
|
|
100
|
-
const occurredAt = git(workspace, ['log', '-1', '--pretty=%cI']).trim() || new Date().toISOString();
|
|
101
|
-
recordCommit(store, { hash, message, author, diffStat, changedFiles, occurredAt });
|
|
102
|
-
upsertEvent(store, {
|
|
103
|
-
type: 'git.post_commit',
|
|
104
|
-
source: 'git',
|
|
105
|
-
sourceId: hash,
|
|
106
|
-
workspacePath: workspace,
|
|
107
|
-
summary: message.split(/\r?\n/)[0] || hash,
|
|
108
|
-
rawJson: { hash, message, author, diffStat, changedFiles },
|
|
109
|
-
occurredAt,
|
|
110
|
-
dedupeKey: `git:git.post_commit:${hash}:${hash}`,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
function summarizeHookPayload(type, payload) {
|
|
114
|
-
if (type === 'agent.user_prompt') {
|
|
115
|
-
return truncate(stringValue(payload.prompt || payload.message || payload.text || payload.input) || 'User prompt');
|
|
116
|
-
}
|
|
117
|
-
const toolName = stringValue(payload.tool_name || payload.toolName || payload.name || payload.tool);
|
|
118
|
-
if (toolName)
|
|
119
|
-
return `${eventLabel(type)} ${toolName}`;
|
|
120
|
-
return eventLabel(type);
|
|
121
|
-
}
|
|
122
|
-
function eventLabel(type) {
|
|
123
|
-
return type.replace(/^agent\./, '').replaceAll('_', ' ');
|
|
124
|
-
}
|
|
125
|
-
function stringValue(value) {
|
|
126
|
-
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
127
|
-
}
|
|
128
|
-
function truncate(value, limit = 240) {
|
|
129
|
-
return value.length <= limit ? value : `${value.slice(0, limit - 1)}...`;
|
|
130
|
-
}
|
|
131
|
-
function camelToPascal(value) {
|
|
132
|
-
return value ? `${value[0]?.toUpperCase()}${value.slice(1)}` : value;
|
|
133
|
-
}
|
|
134
|
-
function hashText(value) {
|
|
135
|
-
let hash = 0x811c9dc5;
|
|
136
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
137
|
-
hash ^= value.charCodeAt(index);
|
|
138
|
-
hash = Math.imul(hash, 0x01000193);
|
|
139
|
-
}
|
|
140
|
-
return (hash >>> 0).toString(16);
|
|
141
|
-
}
|
|
142
|
-
function git(cwd, args) {
|
|
143
|
-
return execFileSync('git', args, {
|
|
144
|
-
cwd,
|
|
145
|
-
encoding: 'utf-8',
|
|
146
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
export function hookCommandArgs(target, event) {
|
|
150
|
-
return ['hook', 'ingest', '--target', target, '--event', event, '--stdin-json'];
|
|
151
|
-
}
|
|
152
|
-
export function hookCommand(target, event) {
|
|
153
|
-
return ['ot', ...hookCommandArgs(target, event).map(shellQuote)].join(' ');
|
|
154
|
-
}
|
|
155
|
-
export function shellQuote(value) {
|
|
156
|
-
if (/^[A-Za-z0-9_./:=@-]+$/.test(value))
|
|
157
|
-
return value;
|
|
158
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
159
|
-
}
|
|
160
|
-
export function workspaceFromPayload(payload, fallback = process.cwd()) {
|
|
161
|
-
return stringValue(payload.cwd || payload.workspacePath || payload.workspace_path) || path.resolve(fallback);
|
|
162
|
-
}
|
package/dist/installers/git.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { ensureDir, projectWorklogDir } from '../core/paths.js';
|
|
4
|
-
const MARKER = 'OPENTURTLE-CLI';
|
|
5
|
-
export function installGitHook(workspace) {
|
|
6
|
-
const gitHooksDir = path.join(workspace, '.git', 'hooks');
|
|
7
|
-
ensureDir(gitHooksDir);
|
|
8
|
-
ensureDir(path.join(projectWorklogDir(workspace), 'hooks'));
|
|
9
|
-
const hookPath = path.join(gitHooksDir, 'post-commit');
|
|
10
|
-
const originalPath = path.join(projectWorklogDir(workspace), 'hooks', 'git-post-commit.original');
|
|
11
|
-
if (fs.existsSync(hookPath)) {
|
|
12
|
-
const current = fs.readFileSync(hookPath, 'utf-8');
|
|
13
|
-
if (current.includes(MARKER))
|
|
14
|
-
return { installed: false, hookPath };
|
|
15
|
-
fs.renameSync(hookPath, originalPath);
|
|
16
|
-
}
|
|
17
|
-
const wrapper = `#!/bin/sh
|
|
18
|
-
# >>> ${MARKER}:git-post-commit
|
|
19
|
-
original_status=0
|
|
20
|
-
if [ -x "${originalPath}" ]; then
|
|
21
|
-
"${originalPath}" "$@"
|
|
22
|
-
original_status="$?"
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
if command -v ot >/dev/null 2>&1; then
|
|
26
|
-
(cd "${workspace}" && ot hook git-post-commit >/dev/null 2>&1) || true
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
exit "$original_status"
|
|
30
|
-
# <<< ${MARKER}:git-post-commit
|
|
31
|
-
`;
|
|
32
|
-
fs.writeFileSync(hookPath, wrapper, 'utf-8');
|
|
33
|
-
fs.chmodSync(hookPath, 0o755);
|
|
34
|
-
return { installed: true, hookPath };
|
|
35
|
-
}
|
|
36
|
-
export function uninstallGitHook(workspace) {
|
|
37
|
-
const hookPath = path.join(workspace, '.git', 'hooks', 'post-commit');
|
|
38
|
-
const originalPath = path.join(projectWorklogDir(workspace), 'hooks', 'git-post-commit.original');
|
|
39
|
-
if (!fs.existsSync(hookPath))
|
|
40
|
-
return;
|
|
41
|
-
const current = fs.readFileSync(hookPath, 'utf-8');
|
|
42
|
-
if (!current.includes(MARKER))
|
|
43
|
-
return;
|
|
44
|
-
if (fs.existsSync(originalPath)) {
|
|
45
|
-
fs.copyFileSync(originalPath, hookPath);
|
|
46
|
-
fs.chmodSync(hookPath, 0o755);
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
fs.unlinkSync(hookPath);
|
|
50
|
-
}
|
|
51
|
-
fs.rmSync(originalPath, { force: true });
|
|
52
|
-
removeEmptyDir(path.dirname(originalPath));
|
|
53
|
-
}
|
|
54
|
-
function removeEmptyDir(dirPath) {
|
|
55
|
-
try {
|
|
56
|
-
if (fs.existsSync(dirPath) && fs.readdirSync(dirPath).length === 0) {
|
|
57
|
-
fs.rmdirSync(dirPath);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
// Uninstall should never fail because cleanup of an auxiliary directory failed.
|
|
62
|
-
}
|
|
63
|
-
}
|
package/dist/mcp/server.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import readline from 'node:readline';
|
|
2
|
-
import { createResourceCommands, draftDailyReport } from '../commands/resources.js';
|
|
3
|
-
import { initProjectStore, listEvents, upsertEvent } from '../core/store.js';
|
|
4
|
-
const TOOLS = [
|
|
5
|
-
tool('list_my_todos', 'List my OpenTurtle todos', {
|
|
6
|
-
type: 'object',
|
|
7
|
-
properties: {
|
|
8
|
-
project_id: { type: 'string' },
|
|
9
|
-
status: { type: 'string' },
|
|
10
|
-
mine: { type: 'boolean' },
|
|
11
|
-
},
|
|
12
|
-
}),
|
|
13
|
-
tool('list_goals', 'List OpenTurtle goals', {
|
|
14
|
-
type: 'object',
|
|
15
|
-
properties: {
|
|
16
|
-
project_id: { type: 'string' },
|
|
17
|
-
status: { type: 'string' },
|
|
18
|
-
mine: { type: 'boolean' },
|
|
19
|
-
},
|
|
20
|
-
}),
|
|
21
|
-
tool('list_workspace_knowledge', 'List reusable workspace knowledge objects', {
|
|
22
|
-
type: 'object',
|
|
23
|
-
properties: { project_id: { type: 'string' }, q: { type: 'string' } },
|
|
24
|
-
}),
|
|
25
|
-
tool('search_workspace_knowledge', 'Search reusable workspace knowledge objects', {
|
|
26
|
-
type: 'object',
|
|
27
|
-
properties: { keyword: { type: 'string' } },
|
|
28
|
-
required: ['keyword'],
|
|
29
|
-
}),
|
|
30
|
-
tool('list_knowledge_source_files', 'List source files behind workspace knowledge', {
|
|
31
|
-
type: 'object',
|
|
32
|
-
properties: { path: { type: 'string' }, q: { type: 'string' } },
|
|
33
|
-
}),
|
|
34
|
-
tool('read_knowledge_source_file', 'Read a source file behind workspace knowledge', {
|
|
35
|
-
type: 'object',
|
|
36
|
-
properties: { path: { type: 'string' } },
|
|
37
|
-
required: ['path'],
|
|
38
|
-
}),
|
|
39
|
-
tool('record_worklog', 'Record a work log entry', {
|
|
40
|
-
type: 'object',
|
|
41
|
-
properties: {
|
|
42
|
-
summary: { type: 'string' },
|
|
43
|
-
type: { type: 'string' },
|
|
44
|
-
},
|
|
45
|
-
required: ['summary'],
|
|
46
|
-
}),
|
|
47
|
-
tool('list_worklogs', 'List work logs', {
|
|
48
|
-
type: 'object',
|
|
49
|
-
properties: { today: { type: 'boolean' }, window: { type: 'string' } },
|
|
50
|
-
}),
|
|
51
|
-
tool('draft_daily_report', "Draft today's local daily report", {
|
|
52
|
-
type: 'object',
|
|
53
|
-
properties: { output: { type: 'string' } },
|
|
54
|
-
}),
|
|
55
|
-
tool('update_todo_progress', 'Update todo progress description with current progress, blockers, next step, and evidence such as logs, tests, screenshots, PRs, commits, or worklog references.', {
|
|
56
|
-
type: 'object',
|
|
57
|
-
properties: {
|
|
58
|
-
todo_id: { type: 'string' },
|
|
59
|
-
message: { type: 'string' },
|
|
60
|
-
},
|
|
61
|
-
required: ['todo_id', 'message'],
|
|
62
|
-
}),
|
|
63
|
-
tool('update_todo_status', 'Update todo status', {
|
|
64
|
-
type: 'object',
|
|
65
|
-
properties: {
|
|
66
|
-
todo_id: { type: 'string' },
|
|
67
|
-
status: { type: 'string' },
|
|
68
|
-
},
|
|
69
|
-
required: ['todo_id', 'status'],
|
|
70
|
-
}),
|
|
71
|
-
];
|
|
72
|
-
export async function handleMcpRequest(msg) {
|
|
73
|
-
const { id, method, params } = msg;
|
|
74
|
-
switch (method) {
|
|
75
|
-
case 'initialize':
|
|
76
|
-
return result(id, {
|
|
77
|
-
protocolVersion: '2024-11-05',
|
|
78
|
-
capabilities: { tools: {} },
|
|
79
|
-
serverInfo: { name: 'ot-cli', version: '0.2.0' },
|
|
80
|
-
});
|
|
81
|
-
case 'notifications/initialized':
|
|
82
|
-
return { jsonrpc: '2.0' };
|
|
83
|
-
case 'tools/list':
|
|
84
|
-
return result(id, { tools: TOOLS });
|
|
85
|
-
case 'tools/call': {
|
|
86
|
-
const name = String(params?.name || '');
|
|
87
|
-
const args = (params?.arguments || {});
|
|
88
|
-
try {
|
|
89
|
-
const output = await handleTool(name, args);
|
|
90
|
-
return result(id, { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] });
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
-
return result(id, { content: [{ type: 'text', text: `Error: ${message}` }], isError: true });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
case 'ping':
|
|
98
|
-
return result(id, {});
|
|
99
|
-
default:
|
|
100
|
-
return error(id, -32601, `Method not found: ${method}`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
export async function serveMcp() {
|
|
104
|
-
const rl = readline.createInterface({ input: process.stdin });
|
|
105
|
-
for await (const line of rl) {
|
|
106
|
-
if (!line.trim())
|
|
107
|
-
continue;
|
|
108
|
-
try {
|
|
109
|
-
const response = await handleMcpRequest(JSON.parse(line));
|
|
110
|
-
if (response.id !== undefined || response.result || response.error) {
|
|
111
|
-
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
catch (err) {
|
|
115
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
116
|
-
process.stderr.write(`[ot-cli-mcp] parse error: ${message}\n`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
async function handleTool(name, args) {
|
|
121
|
-
const commands = createResourceCommands();
|
|
122
|
-
try {
|
|
123
|
-
if (name === 'list_my_todos')
|
|
124
|
-
return await commands.listTodos({ ...args, mine: args.mine ?? true });
|
|
125
|
-
if (name === 'list_goals')
|
|
126
|
-
return await commands.listGoals(args);
|
|
127
|
-
if (name === 'list_workspace_knowledge')
|
|
128
|
-
return await commands.listKnowledge(args);
|
|
129
|
-
if (name === 'search_workspace_knowledge')
|
|
130
|
-
return await commands.searchKnowledge(String(args.keyword || ''));
|
|
131
|
-
if (name === 'list_knowledge_source_files')
|
|
132
|
-
return await commands.listKnowledgeSources(args);
|
|
133
|
-
if (name === 'read_knowledge_source_file')
|
|
134
|
-
return await commands.readKnowledgeSource(String(args.path || ''));
|
|
135
|
-
if (name === 'record_worklog')
|
|
136
|
-
return await commands.recordWorklog(String(args.summary || ''), String(args.type || 'note'));
|
|
137
|
-
if (name === 'list_worklogs')
|
|
138
|
-
return await commands.listWorklogs(args);
|
|
139
|
-
if (name === 'update_todo_progress') {
|
|
140
|
-
return await commands.updateTodoProgress(String(args.todo_id || ''), String(args.message || ''));
|
|
141
|
-
}
|
|
142
|
-
if (name === 'update_todo_status') {
|
|
143
|
-
return await commands.updateTodoStatus(String(args.todo_id || ''), String(args.status || ''));
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
catch (err) {
|
|
147
|
-
return localFallback(name, args, err);
|
|
148
|
-
}
|
|
149
|
-
if (name === 'draft_daily_report') {
|
|
150
|
-
return { source: 'local_cache', path: draftDailyReport(process.cwd(), { output: stringArg(args.output) }) };
|
|
151
|
-
}
|
|
152
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
153
|
-
}
|
|
154
|
-
function localFallback(name, args, err) {
|
|
155
|
-
if (name === 'record_worklog') {
|
|
156
|
-
const store = initProjectStore(process.cwd());
|
|
157
|
-
upsertEvent(store, {
|
|
158
|
-
type: 'agent.session_imported',
|
|
159
|
-
source: 'codex',
|
|
160
|
-
workspacePath: process.cwd(),
|
|
161
|
-
summary: String(args.summary || ''),
|
|
162
|
-
rawJson: { tool: name, args },
|
|
163
|
-
dedupeKey: `local:record_worklog:${Date.now()}:${Math.random().toString(16).slice(2)}`,
|
|
164
|
-
});
|
|
165
|
-
return { source: 'local_cache', ok: true };
|
|
166
|
-
}
|
|
167
|
-
const store = initProjectStore(process.cwd());
|
|
168
|
-
return {
|
|
169
|
-
source: 'local_cache',
|
|
170
|
-
warning: err instanceof Error ? err.message : String(err),
|
|
171
|
-
events: listEvents(store, 50),
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
function tool(name, description, inputSchema) {
|
|
175
|
-
return { name, description, inputSchema };
|
|
176
|
-
}
|
|
177
|
-
function result(id, value) {
|
|
178
|
-
return { jsonrpc: '2.0', id: id ?? 0, result: value };
|
|
179
|
-
}
|
|
180
|
-
function error(id, code, message) {
|
|
181
|
-
return { jsonrpc: '2.0', id: id ?? 0, error: { code, message } };
|
|
182
|
-
}
|
|
183
|
-
function stringArg(value) {
|
|
184
|
-
return typeof value === 'string' && value ? value : undefined;
|
|
185
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { initProjectStore, upsertEvent } from '../core/store.js';
|
|
5
|
-
export function candidateHistoryPaths(target, homeDir = os.homedir()) {
|
|
6
|
-
if (target === 'qoder') {
|
|
7
|
-
return [
|
|
8
|
-
path.join(homeDir, '.qoder', 'logs', 'runs'),
|
|
9
|
-
path.join(homeDir, '.qoder', 'projects'),
|
|
10
|
-
path.join(homeDir, '.qoderwork', 'projects'),
|
|
11
|
-
];
|
|
12
|
-
}
|
|
13
|
-
if (target === 'kiro') {
|
|
14
|
-
return [path.join(homeDir, '.kiro', 'sessions', 'cli')];
|
|
15
|
-
}
|
|
16
|
-
if (target === 'codex')
|
|
17
|
-
return [path.join(homeDir, '.codex', 'sessions')];
|
|
18
|
-
if (target === 'claude')
|
|
19
|
-
return [path.join(homeDir, '.claude', 'projects')];
|
|
20
|
-
return [];
|
|
21
|
-
}
|
|
22
|
-
export function importHistory(target, workspace = process.cwd()) {
|
|
23
|
-
const store = initProjectStore(workspace);
|
|
24
|
-
const paths = candidateHistoryPaths(target);
|
|
25
|
-
let imported = 0;
|
|
26
|
-
for (const filePath of paths.flatMap((item) => walkFiles(item))) {
|
|
27
|
-
const stat = safeStat(filePath);
|
|
28
|
-
if (!stat || stat.size <= 0)
|
|
29
|
-
continue;
|
|
30
|
-
const dedupeKey = `${target}:agent.session_imported:${filePath}:${stat.mtimeMs}`;
|
|
31
|
-
const result = upsertEvent(store, {
|
|
32
|
-
type: 'agent.session_imported',
|
|
33
|
-
source: target,
|
|
34
|
-
workspacePath: workspace,
|
|
35
|
-
summary: `Imported ${path.basename(filePath)}`,
|
|
36
|
-
rawJson: { filePath, size: stat.size, mtimeMs: stat.mtimeMs },
|
|
37
|
-
occurredAt: new Date(stat.mtimeMs).toISOString(),
|
|
38
|
-
dedupeKey,
|
|
39
|
-
});
|
|
40
|
-
if (result.inserted)
|
|
41
|
-
imported += 1;
|
|
42
|
-
}
|
|
43
|
-
return { imported, paths };
|
|
44
|
-
}
|
|
45
|
-
function walkFiles(root) {
|
|
46
|
-
let entries;
|
|
47
|
-
try {
|
|
48
|
-
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
return entries.flatMap((entry) => {
|
|
54
|
-
const entryPath = path.join(root, entry.name);
|
|
55
|
-
if (entry.isDirectory())
|
|
56
|
-
return walkFiles(entryPath);
|
|
57
|
-
if (entry.isFile() && /\.(jsonl|json|log)$/i.test(entry.name))
|
|
58
|
-
return [entryPath];
|
|
59
|
-
return [];
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
function safeStat(filePath) {
|
|
63
|
-
try {
|
|
64
|
-
return fs.statSync(filePath);
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|