@openturtle/cli 0.3.2 → 0.3.4
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 +52 -152
- package/RELEASE_RUNBOOK.md +193 -0
- package/dist/commands/collaboration.js +88 -4
- package/dist/commands/local-knowledge.js +129 -0
- package/dist/commands/resources.js +2 -5
- package/dist/core/auth.js +4 -2
- package/dist/core/auto-update.js +30 -2
- 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 +39 -85
- package/dist/installers/agents.js +79 -222
- package/dist/installers/legacy-git-cleanup.js +29 -0
- package/dist/local-knowledge/index.js +398 -0
- package/dist/local-knowledge/library.js +328 -0
- package/dist/local-knowledge/root.js +5 -0
- package/package.json +5 -3
- package/skill/openturtle-cli/SKILL.md +81 -0
- 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/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
|
-
}
|