@nogataka/smart-edit 0.0.14

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.
Files changed (186) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +244 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +7 -0
  5. package/dist/devtools/generate_prompt_factory.d.ts +5 -0
  6. package/dist/devtools/generate_prompt_factory.js +114 -0
  7. package/dist/index.d.ts +34 -0
  8. package/dist/index.js +34 -0
  9. package/dist/interprompt/index.d.ts +2 -0
  10. package/dist/interprompt/index.js +1 -0
  11. package/dist/interprompt/jinja_template.d.ts +10 -0
  12. package/dist/interprompt/jinja_template.js +174 -0
  13. package/dist/interprompt/multilang_prompt.d.ts +54 -0
  14. package/dist/interprompt/multilang_prompt.js +302 -0
  15. package/dist/interprompt/prompt_factory.d.ts +16 -0
  16. package/dist/interprompt/prompt_factory.js +189 -0
  17. package/dist/interprompt/util/class_decorators.d.ts +1 -0
  18. package/dist/interprompt/util/class_decorators.js +1 -0
  19. package/dist/interprompt/util/index.d.ts +1 -0
  20. package/dist/interprompt/util/index.js +1 -0
  21. package/dist/serena/agent.d.ts +118 -0
  22. package/dist/serena/agent.js +675 -0
  23. package/dist/serena/agno.d.ts +111 -0
  24. package/dist/serena/agno.js +278 -0
  25. package/dist/serena/analytics.d.ts +24 -0
  26. package/dist/serena/analytics.js +119 -0
  27. package/dist/serena/cli.d.ts +9 -0
  28. package/dist/serena/cli.js +731 -0
  29. package/dist/serena/code_editor.d.ts +42 -0
  30. package/dist/serena/code_editor.js +239 -0
  31. package/dist/serena/config/context_mode.d.ts +41 -0
  32. package/dist/serena/config/context_mode.js +239 -0
  33. package/dist/serena/config/serena_config.d.ts +134 -0
  34. package/dist/serena/config/serena_config.js +718 -0
  35. package/dist/serena/constants.d.ts +18 -0
  36. package/dist/serena/constants.js +27 -0
  37. package/dist/serena/dashboard.d.ts +55 -0
  38. package/dist/serena/dashboard.js +472 -0
  39. package/dist/serena/generated/generated_prompt_factory.d.ts +27 -0
  40. package/dist/serena/generated/generated_prompt_factory.js +42 -0
  41. package/dist/serena/gui_log_viewer.d.ts +41 -0
  42. package/dist/serena/gui_log_viewer.js +436 -0
  43. package/dist/serena/mcp.d.ts +118 -0
  44. package/dist/serena/mcp.js +904 -0
  45. package/dist/serena/project.d.ts +62 -0
  46. package/dist/serena/project.js +321 -0
  47. package/dist/serena/prompt_factory.d.ts +20 -0
  48. package/dist/serena/prompt_factory.js +42 -0
  49. package/dist/serena/resources/config/contexts/agent.yml +8 -0
  50. package/dist/serena/resources/config/contexts/chatgpt.yml +28 -0
  51. package/dist/serena/resources/config/contexts/codex.yml +27 -0
  52. package/dist/serena/resources/config/contexts/context.template.yml +11 -0
  53. package/dist/serena/resources/config/contexts/desktop-app.yml +17 -0
  54. package/dist/serena/resources/config/contexts/ide-assistant.yml +26 -0
  55. package/dist/serena/resources/config/contexts/oaicompat-agent.yml +8 -0
  56. package/dist/serena/resources/config/internal_modes/jetbrains.yml +15 -0
  57. package/dist/serena/resources/config/modes/editing.yml +112 -0
  58. package/dist/serena/resources/config/modes/interactive.yml +11 -0
  59. package/dist/serena/resources/config/modes/mode.template.yml +7 -0
  60. package/dist/serena/resources/config/modes/no-onboarding.yml +8 -0
  61. package/dist/serena/resources/config/modes/onboarding.yml +16 -0
  62. package/dist/serena/resources/config/modes/one-shot.yml +15 -0
  63. package/dist/serena/resources/config/modes/planning.yml +15 -0
  64. package/dist/serena/resources/config/prompt_templates/simple_tool_outputs.yml +75 -0
  65. package/dist/serena/resources/config/prompt_templates/system_prompt.yml +66 -0
  66. package/dist/serena/resources/dashboard/dashboard.js +815 -0
  67. package/dist/serena/resources/dashboard/index.html +314 -0
  68. package/dist/serena/resources/dashboard/jquery.min.js +3 -0
  69. package/dist/serena/resources/dashboard/serena-icon-16.png +0 -0
  70. package/dist/serena/resources/dashboard/serena-icon-32.png +0 -0
  71. package/dist/serena/resources/dashboard/serena-icon-48.png +0 -0
  72. package/dist/serena/resources/dashboard/serena-logs-dark-mode.png +0 -0
  73. package/dist/serena/resources/dashboard/serena-logs.png +0 -0
  74. package/dist/serena/resources/project.template.yml +67 -0
  75. package/dist/serena/resources/serena_config.template.yml +85 -0
  76. package/dist/serena/symbol.d.ts +199 -0
  77. package/dist/serena/symbol.js +616 -0
  78. package/dist/serena/text_utils.d.ts +51 -0
  79. package/dist/serena/text_utils.js +267 -0
  80. package/dist/serena/tools/cmd_tools.d.ts +31 -0
  81. package/dist/serena/tools/cmd_tools.js +48 -0
  82. package/dist/serena/tools/config_tools.d.ts +53 -0
  83. package/dist/serena/tools/config_tools.js +176 -0
  84. package/dist/serena/tools/file_tools.d.ts +231 -0
  85. package/dist/serena/tools/file_tools.js +511 -0
  86. package/dist/serena/tools/index.d.ts +7 -0
  87. package/dist/serena/tools/index.js +7 -0
  88. package/dist/serena/tools/memory_tools.d.ts +60 -0
  89. package/dist/serena/tools/memory_tools.js +135 -0
  90. package/dist/serena/tools/symbol_tools.d.ts +165 -0
  91. package/dist/serena/tools/symbol_tools.js +362 -0
  92. package/dist/serena/tools/tools_base.d.ts +162 -0
  93. package/dist/serena/tools/tools_base.js +378 -0
  94. package/dist/serena/tools/workflow_tools.d.ts +35 -0
  95. package/dist/serena/tools/workflow_tools.js +161 -0
  96. package/dist/serena/util/class_decorators.d.ts +7 -0
  97. package/dist/serena/util/class_decorators.js +37 -0
  98. package/dist/serena/util/exception.d.ts +8 -0
  99. package/dist/serena/util/exception.js +53 -0
  100. package/dist/serena/util/file_system.d.ts +30 -0
  101. package/dist/serena/util/file_system.js +352 -0
  102. package/dist/serena/util/general.d.ts +11 -0
  103. package/dist/serena/util/general.js +42 -0
  104. package/dist/serena/util/git.d.ts +11 -0
  105. package/dist/serena/util/git.js +37 -0
  106. package/dist/serena/util/inspection.d.ts +45 -0
  107. package/dist/serena/util/inspection.js +221 -0
  108. package/dist/serena/util/logging.d.ts +46 -0
  109. package/dist/serena/util/logging.js +205 -0
  110. package/dist/serena/util/shell.d.ts +21 -0
  111. package/dist/serena/util/shell.js +95 -0
  112. package/dist/serena/util/thread.d.ts +23 -0
  113. package/dist/serena/util/thread.js +88 -0
  114. package/dist/serena/version.d.ts +1 -0
  115. package/dist/serena/version.js +23 -0
  116. package/dist/solidlsp/language_servers/autoload.d.ts +23 -0
  117. package/dist/solidlsp/language_servers/autoload.js +25 -0
  118. package/dist/solidlsp/language_servers/bash_language_server.d.ts +10 -0
  119. package/dist/solidlsp/language_servers/bash_language_server.js +64 -0
  120. package/dist/solidlsp/language_servers/clangd_language_server.d.ts +13 -0
  121. package/dist/solidlsp/language_servers/clangd_language_server.js +110 -0
  122. package/dist/solidlsp/language_servers/clojure_lsp.d.ts +13 -0
  123. package/dist/solidlsp/language_servers/clojure_lsp.js +137 -0
  124. package/dist/solidlsp/language_servers/common.d.ts +41 -0
  125. package/dist/solidlsp/language_servers/common.js +365 -0
  126. package/dist/solidlsp/language_servers/csharp_language_server.d.ts +21 -0
  127. package/dist/solidlsp/language_servers/csharp_language_server.js +694 -0
  128. package/dist/solidlsp/language_servers/dart_language_server.d.ts +10 -0
  129. package/dist/solidlsp/language_servers/dart_language_server.js +122 -0
  130. package/dist/solidlsp/language_servers/eclipse_jdtls.d.ts +24 -0
  131. package/dist/solidlsp/language_servers/eclipse_jdtls.js +671 -0
  132. package/dist/solidlsp/language_servers/erlang_language_server.d.ts +22 -0
  133. package/dist/solidlsp/language_servers/erlang_language_server.js +327 -0
  134. package/dist/solidlsp/language_servers/gopls.d.ts +12 -0
  135. package/dist/solidlsp/language_servers/gopls.js +59 -0
  136. package/dist/solidlsp/language_servers/intelephense.d.ts +13 -0
  137. package/dist/solidlsp/language_servers/intelephense.js +121 -0
  138. package/dist/solidlsp/language_servers/jedi_server.d.ts +18 -0
  139. package/dist/solidlsp/language_servers/jedi_server.js +234 -0
  140. package/dist/solidlsp/language_servers/kotlin_language_server.d.ts +19 -0
  141. package/dist/solidlsp/language_servers/kotlin_language_server.js +474 -0
  142. package/dist/solidlsp/language_servers/lua_ls.d.ts +18 -0
  143. package/dist/solidlsp/language_servers/lua_ls.js +319 -0
  144. package/dist/solidlsp/language_servers/nixd_language_server.d.ts +17 -0
  145. package/dist/solidlsp/language_servers/nixd_language_server.js +341 -0
  146. package/dist/solidlsp/language_servers/pyright_server.d.ts +19 -0
  147. package/dist/solidlsp/language_servers/pyright_server.js +180 -0
  148. package/dist/solidlsp/language_servers/r_language_server.d.ts +19 -0
  149. package/dist/solidlsp/language_servers/r_language_server.js +184 -0
  150. package/dist/solidlsp/language_servers/ruby_common.d.ts +10 -0
  151. package/dist/solidlsp/language_servers/ruby_common.js +136 -0
  152. package/dist/solidlsp/language_servers/ruby_lsp.d.ts +18 -0
  153. package/dist/solidlsp/language_servers/ruby_lsp.js +230 -0
  154. package/dist/solidlsp/language_servers/rust_analyzer.d.ts +13 -0
  155. package/dist/solidlsp/language_servers/rust_analyzer.js +96 -0
  156. package/dist/solidlsp/language_servers/solargraph.d.ts +18 -0
  157. package/dist/solidlsp/language_servers/solargraph.js +208 -0
  158. package/dist/solidlsp/language_servers/sourcekit_lsp.d.ts +24 -0
  159. package/dist/solidlsp/language_servers/sourcekit_lsp.js +449 -0
  160. package/dist/solidlsp/language_servers/terraform_ls.d.ts +13 -0
  161. package/dist/solidlsp/language_servers/terraform_ls.js +139 -0
  162. package/dist/solidlsp/language_servers/typescript_language_server.d.ts +20 -0
  163. package/dist/solidlsp/language_servers/typescript_language_server.js +237 -0
  164. package/dist/solidlsp/language_servers/vts_language_server.d.ts +13 -0
  165. package/dist/solidlsp/language_servers/vts_language_server.js +121 -0
  166. package/dist/solidlsp/language_servers/zls.d.ts +20 -0
  167. package/dist/solidlsp/language_servers/zls.js +254 -0
  168. package/dist/solidlsp/ls.d.ts +197 -0
  169. package/dist/solidlsp/ls.js +507 -0
  170. package/dist/solidlsp/ls_config.d.ts +43 -0
  171. package/dist/solidlsp/ls_config.js +157 -0
  172. package/dist/solidlsp/ls_exceptions.d.ts +5 -0
  173. package/dist/solidlsp/ls_exceptions.js +14 -0
  174. package/dist/solidlsp/ls_handler.d.ts +54 -0
  175. package/dist/solidlsp/ls_handler.js +406 -0
  176. package/dist/solidlsp/ls_request.d.ts +31 -0
  177. package/dist/solidlsp/ls_request.js +42 -0
  178. package/dist/solidlsp/ls_types.d.ts +7 -0
  179. package/dist/solidlsp/ls_types.js +8 -0
  180. package/dist/solidlsp/lsp_protocol_handler/server.d.ts +61 -0
  181. package/dist/solidlsp/lsp_protocol_handler/server.js +68 -0
  182. package/dist/solidlsp/util/subprocess_util.d.ts +6 -0
  183. package/dist/solidlsp/util/subprocess_util.js +11 -0
  184. package/dist/solidlsp/util/zip.d.ts +25 -0
  185. package/dist/solidlsp/util/zip.js +188 -0
  186. package/package.json +65 -0
@@ -0,0 +1,731 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import { spawn } from 'node:child_process';
5
+ import { Command, Option } from 'commander';
6
+ import { DEFAULT_CONTEXT, DEFAULT_MODES, PROMPT_TEMPLATES_DIR_IN_USER_HOME, PROMPT_TEMPLATES_DIR_INTERNAL, SERENA_MANAGED_DIR_IN_HOME, SERENAS_OWN_CONTEXT_YAMLS_DIR, SERENAS_OWN_MODE_YAMLS_DIR, SERENA_LOG_FORMAT, USER_CONTEXT_YAMLS_DIR, USER_MODE_YAMLS_DIR } from './constants.js';
7
+ import { SerenaAgentContext, SerenaAgentMode } from './config/context_mode.js';
8
+ import { ProjectConfig, SerenaConfig, SerenaPaths } from './config/serena_config.js';
9
+ import { createSerenaLogger, setConsoleLoggingEnabled } from './util/logging.js';
10
+ import { SerenaAgent } from './agent.js';
11
+ import { SerenaMCPFactorySingleProcess, createSerenaHttpServer, createSerenaStdioServer } from './mcp.js';
12
+ import { ToolRegistry } from './tools/tools_base.js';
13
+ import { coerceLanguage } from '../solidlsp/ls_config.js';
14
+ import { ensureDefaultSubprocessOptions } from '../solidlsp/util/subprocess_util.js';
15
+ const LOG_LEVEL_NAMES = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'];
16
+ function isNonEmptyString(value) {
17
+ return typeof value === 'string' && value.length > 0;
18
+ }
19
+ function isTransportChoice(value) {
20
+ return value === 'stdio' || value === 'sse' || value === 'streamable-http';
21
+ }
22
+ function isLogLevelName(value) {
23
+ return LOG_LEVEL_NAMES.some((name) => name === value);
24
+ }
25
+ function parseOptionalBoolean(value) {
26
+ if (value === undefined) {
27
+ return true;
28
+ }
29
+ const normalized = value.toLowerCase();
30
+ if (['true', '1', 'yes', 'on'].includes(normalized)) {
31
+ return true;
32
+ }
33
+ if (['false', '0', 'no', 'off'].includes(normalized)) {
34
+ return false;
35
+ }
36
+ throw new Error(`ブーリアン値として解釈できません: ${value}`);
37
+ }
38
+ function parseInteger(value, optionName) {
39
+ const parsed = Number.parseInt(value, 10);
40
+ if (Number.isNaN(parsed)) {
41
+ throw new Error(`${optionName} は整数で指定してください: ${value}`);
42
+ }
43
+ return parsed;
44
+ }
45
+ function parseFloatSeconds(value) {
46
+ const parsed = Number.parseFloat(value);
47
+ if (Number.isNaN(parsed)) {
48
+ throw new Error(`秒数は数値で指定してください: ${value}`);
49
+ }
50
+ return parsed;
51
+ }
52
+ function normalizeStartMcpServerOptions(raw) {
53
+ const context = isNonEmptyString(raw.context) ? raw.context : DEFAULT_CONTEXT;
54
+ const rawModes = raw.modes ?? raw.mode;
55
+ const normalizedModes = (() => {
56
+ if (Array.isArray(rawModes)) {
57
+ const filtered = rawModes.filter(isNonEmptyString);
58
+ return filtered.length > 0 ? filtered : Array.from(DEFAULT_MODES);
59
+ }
60
+ if (isNonEmptyString(rawModes)) {
61
+ return [rawModes];
62
+ }
63
+ return Array.from(DEFAULT_MODES);
64
+ })();
65
+ const transportCandidate = typeof raw.transport === 'string' ? raw.transport : 'stdio';
66
+ if (!isTransportChoice(transportCandidate)) {
67
+ throw new Error(`未知のトランスポートが指定されました: ${String(raw.transport)}`);
68
+ }
69
+ const transport = transportCandidate;
70
+ const host = isNonEmptyString(raw.host) ? raw.host : '0.0.0.0';
71
+ const port = typeof raw.port === 'number' ? raw.port : 8000;
72
+ const coerceBoolean = (value) => {
73
+ if (value === undefined) {
74
+ return undefined;
75
+ }
76
+ if (value === null) {
77
+ return null;
78
+ }
79
+ if (typeof value === 'boolean') {
80
+ return value;
81
+ }
82
+ return undefined;
83
+ };
84
+ const coerceLogLevel = (value) => {
85
+ if (value === undefined) {
86
+ return undefined;
87
+ }
88
+ if (value === null) {
89
+ return null;
90
+ }
91
+ if (typeof value === 'string') {
92
+ const normalized = value.toUpperCase();
93
+ if (isLogLevelName(normalized)) {
94
+ return normalized;
95
+ }
96
+ }
97
+ return undefined;
98
+ };
99
+ return {
100
+ project: isNonEmptyString(raw.project) ? raw.project : null,
101
+ projectFile: isNonEmptyString(raw.projectFile) ? raw.projectFile : null,
102
+ context,
103
+ modes: normalizedModes.slice(),
104
+ transport,
105
+ host,
106
+ port,
107
+ enableWebDashboard: coerceBoolean(raw.enableWebDashboard) ?? null,
108
+ enableGuiLogWindow: coerceBoolean(raw.enableGuiLogWindow) ?? null,
109
+ logLevel: coerceLogLevel(raw.logLevel) ?? null,
110
+ traceLspCommunication: coerceBoolean(raw.traceLspCommunication) ?? null,
111
+ toolTimeout: typeof raw.toolTimeout === 'number' ? raw.toolTimeout : null,
112
+ instructionsOverride: typeof raw.instructionsOverride === 'string' ? raw.instructionsOverride : null
113
+ };
114
+ }
115
+ function isPathInside(child, parent) {
116
+ const relative = path.relative(parent, child);
117
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
118
+ }
119
+ function normalizeProjectArgument(value) {
120
+ if (!value) {
121
+ return null;
122
+ }
123
+ const resolved = path.resolve(value);
124
+ try {
125
+ const stat = fs.statSync(resolved);
126
+ if (stat.isDirectory()) {
127
+ return resolved;
128
+ }
129
+ }
130
+ catch {
131
+ // ignore resolution errors and fall back to the raw value
132
+ }
133
+ return value;
134
+ }
135
+ async function openInEditor(targetPath) {
136
+ if (process.env.SERENA_SKIP_EDITOR === '1') {
137
+ return;
138
+ }
139
+ const run = (command, args, options = {}) => new Promise((resolve, reject) => {
140
+ const child = spawn(command, args, ensureDefaultSubprocessOptions({
141
+ stdio: 'ignore',
142
+ detached: false,
143
+ shell: options.shell ?? false
144
+ }));
145
+ child.on('error', reject);
146
+ child.on('close', () => resolve());
147
+ });
148
+ try {
149
+ const editor = process.env.EDITOR;
150
+ if (editor && editor.trim().length > 0) {
151
+ await run(editor, [targetPath], { shell: true });
152
+ return;
153
+ }
154
+ if (process.platform === 'win32') {
155
+ await run('cmd', ['/c', 'start', '', `"${targetPath}"`], { shell: true });
156
+ return;
157
+ }
158
+ if (process.platform === 'darwin') {
159
+ await run('open', [targetPath]);
160
+ return;
161
+ }
162
+ await run('xdg-open', [targetPath]);
163
+ }
164
+ catch (error) {
165
+ const message = error instanceof Error ? error.message : String(error);
166
+ console.error(`Failed to open ${targetPath}: ${message}`);
167
+ }
168
+ }
169
+ function setupCliLogging(prefix, options) {
170
+ const { logger, memoryHandler } = createSerenaLogger({
171
+ level: 'info',
172
+ emitToConsole: options.emitToConsole,
173
+ name: `serena.cli.${prefix}`
174
+ });
175
+ const paths = new SerenaPaths();
176
+ const logFilePath = paths.getNextLogFilePath(prefix);
177
+ const stream = fs.createWriteStream(logFilePath, { encoding: 'utf-8', flags: 'w' });
178
+ const callback = (message) => {
179
+ stream.write(`${message}\n`);
180
+ if (options.duplicateToStderr) {
181
+ process.stderr.write(`${message}\n`);
182
+ }
183
+ };
184
+ memoryHandler.addEmitCallback(callback);
185
+ const dispose = () => {
186
+ memoryHandler.removeEmitCallback(callback);
187
+ stream.end();
188
+ };
189
+ return { logger, memoryHandler, logFilePath, dispose };
190
+ }
191
+ async function handleStartMcpServer(options, projectArg) {
192
+ const suppressConsoleLogs = options.transport === 'stdio';
193
+ let restoreConsoleLogging = () => {
194
+ // noop
195
+ };
196
+ if (suppressConsoleLogs) {
197
+ setConsoleLoggingEnabled(false);
198
+ restoreConsoleLogging = () => setConsoleLoggingEnabled(true);
199
+ }
200
+ const cliLog = setupCliLogging('mcp', {
201
+ emitToConsole: !suppressConsoleLogs,
202
+ duplicateToStderr: suppressConsoleLogs
203
+ });
204
+ const { logger } = cliLog;
205
+ logger.info('Initializing Serena MCP server');
206
+ logger.info(`Storing logs in ${cliLog.logFilePath}`);
207
+ logger.info(`Serena CLI log format: ${SERENA_LOG_FORMAT}`);
208
+ const project = normalizeProjectArgument(projectArg ?? options.projectFile ?? options.project ?? null);
209
+ if (projectArg && projectArg !== project) {
210
+ logger.warn('--project 引数の代わりに位置引数を使用する方法は非推奨です (--project を使用してください)。');
211
+ }
212
+ const modes = options.modes.length > 0 ? options.modes : Array.from(DEFAULT_MODES);
213
+ const factory = new SerenaMCPFactorySingleProcess({
214
+ context: options.context ?? DEFAULT_CONTEXT,
215
+ project,
216
+ memoryLogHandler: cliLog.memoryHandler,
217
+ agentFactory: (agentOptions) => new SerenaAgent({
218
+ project: agentOptions.project,
219
+ serenaConfig: agentOptions.serenaConfig,
220
+ context: agentOptions.context,
221
+ modes: agentOptions.modes,
222
+ memoryLogHandler: agentOptions.memoryLogHandler ?? undefined
223
+ })
224
+ });
225
+ try {
226
+ const serverOptions = {
227
+ host: options.host,
228
+ port: options.port,
229
+ modes,
230
+ enableWebDashboard: options.enableWebDashboard ?? undefined,
231
+ enableGuiLogWindow: options.enableGuiLogWindow ?? undefined,
232
+ logLevel: options.logLevel ?? undefined,
233
+ traceLspCommunication: options.traceLspCommunication ?? undefined,
234
+ toolTimeout: options.toolTimeout ?? undefined,
235
+ instructionsOverride: options.instructionsOverride ?? undefined
236
+ };
237
+ switch (options.transport) {
238
+ case 'streamable-http': {
239
+ const server = await createSerenaHttpServer(factory, serverOptions);
240
+ logger.info(`Streamable HTTP MCP server started: ${server.url.href}`);
241
+ logger.info('Press Ctrl+C to exit.');
242
+ await new Promise((resolve) => {
243
+ const shutdown = async () => {
244
+ logger.info('Stopping HTTP MCP server...');
245
+ await server.close();
246
+ resolve();
247
+ };
248
+ process.once('SIGINT', () => {
249
+ void shutdown();
250
+ });
251
+ process.once('SIGTERM', () => {
252
+ void shutdown();
253
+ });
254
+ });
255
+ break;
256
+ }
257
+ case 'stdio': {
258
+ const server = await createSerenaStdioServer(factory, serverOptions);
259
+ logger.info('STDIO MCP server started. Press Ctrl+C to exit.');
260
+ await new Promise((resolve) => {
261
+ let settled = false;
262
+ const finalize = async (reason) => {
263
+ if (settled) {
264
+ return;
265
+ }
266
+ settled = true;
267
+ logger.info('Stopping STDIO MCP server...');
268
+ if (reason === 'signal') {
269
+ await server.close();
270
+ }
271
+ resolve();
272
+ };
273
+ server.transport.onclose = () => {
274
+ void finalize('transport-close');
275
+ };
276
+ server.transport.onerror = (error) => {
277
+ logger.error('STDIO MCP トランスポートでエラーが発生しました。', error);
278
+ void finalize('transport-close');
279
+ };
280
+ const onSignal = (signal) => {
281
+ logger.info(`Received signal ${signal}`);
282
+ void finalize('signal');
283
+ };
284
+ process.once('SIGINT', onSignal);
285
+ process.once('SIGTERM', onSignal);
286
+ });
287
+ break;
288
+ }
289
+ case 'sse':
290
+ throw new Error('SSE トランスポートは TypeScript 版では未実装です。HTTP モード (--transport streamable-http) を利用してください。');
291
+ default:
292
+ throw new Error(`未知のトランスポートが指定されました: ${options.transport}`);
293
+ }
294
+ }
295
+ catch (error) {
296
+ const message = error instanceof Error ? error.message : String(error);
297
+ logger.error(`Serena MCP サーバーの起動に失敗しました: ${message}`);
298
+ throw error;
299
+ }
300
+ finally {
301
+ cliLog.dispose();
302
+ restoreConsoleLogging();
303
+ }
304
+ }
305
+ function formatModeLine(name, yamlPath) {
306
+ const isInternal = isPathInside(yamlPath, SERENAS_OWN_MODE_YAMLS_DIR);
307
+ const descriptor = isInternal ? '(internal)' : `(at ${yamlPath})`;
308
+ return `${name} ${descriptor}`;
309
+ }
310
+ function formatContextLine(name, yamlPath) {
311
+ const isInternal = isPathInside(yamlPath, SERENAS_OWN_CONTEXT_YAMLS_DIR);
312
+ const descriptor = isInternal ? '(internal)' : `(at ${yamlPath})`;
313
+ return `${name} ${descriptor}`;
314
+ }
315
+ function ensureDirExists(dir) {
316
+ fs.mkdirSync(dir, { recursive: true });
317
+ }
318
+ function handleModeCreate(options) {
319
+ const { name, fromInternal } = options;
320
+ if (!name && !fromInternal) {
321
+ throw new Error('--name か --from-internal のいずれかを指定してください。');
322
+ }
323
+ const modeName = name ?? fromInternal ?? '';
324
+ const destination = path.join(USER_MODE_YAMLS_DIR, `${modeName}.yml`);
325
+ const source = fromInternal
326
+ ? path.join(SERENAS_OWN_MODE_YAMLS_DIR, `${fromInternal}.yml`)
327
+ : path.join(SERENAS_OWN_MODE_YAMLS_DIR, 'mode.template.yml');
328
+ if (!fs.existsSync(source)) {
329
+ const available = SerenaAgentMode.listRegisteredModeNames().join(', ');
330
+ throw new Error(`内部モード '${fromInternal ?? ''}' が見つかりません。利用可能なモード: ${available}`);
331
+ }
332
+ ensureDirExists(path.dirname(destination));
333
+ fs.copyFileSync(source, destination);
334
+ return destination;
335
+ }
336
+ function handleContextCreate(options) {
337
+ const { name, fromInternal } = options;
338
+ if (!name && !fromInternal) {
339
+ throw new Error('--name か --from-internal のいずれかを指定してください。');
340
+ }
341
+ const contextName = name ?? fromInternal ?? '';
342
+ const destination = path.join(USER_CONTEXT_YAMLS_DIR, `${contextName}.yml`);
343
+ const source = fromInternal
344
+ ? path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, `${fromInternal}.yml`)
345
+ : path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, 'context.template.yml');
346
+ if (!fs.existsSync(source)) {
347
+ const available = SerenaAgentContext.listRegisteredContextNames().join(', ');
348
+ throw new Error(`内部コンテキスト '${fromInternal ?? ''}' が見つかりません。利用可能なコンテキスト: ${available}`);
349
+ }
350
+ ensureDirExists(path.dirname(destination));
351
+ fs.copyFileSync(source, destination);
352
+ return destination;
353
+ }
354
+ function formatPromptListLine(promptYamlName, userPromptYamlPath) {
355
+ if (fs.existsSync(userPromptYamlPath)) {
356
+ return `${userPromptYamlPath} merged with default prompts in ${promptYamlName}`;
357
+ }
358
+ return promptYamlName;
359
+ }
360
+ function getUserPromptPath(promptYamlName) {
361
+ ensureDirExists(PROMPT_TEMPLATES_DIR_IN_USER_HOME);
362
+ return path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, promptYamlName);
363
+ }
364
+ function ensurePromptYamlName(promptYamlName) {
365
+ return promptYamlName.endsWith('.yml') ? promptYamlName : `${promptYamlName}.yml`;
366
+ }
367
+ export function createSerenaCli(options = {}) {
368
+ const program = new Command('serena');
369
+ program
370
+ .description('Serena CLI commands. 各コマンドの詳細は `<command> --help` を参照してください。')
371
+ .showHelpAfterError('(ヘルプ: serena --help)')
372
+ .configureOutput({
373
+ writeOut: options.writeOut ?? ((str) => process.stdout.write(str)),
374
+ writeErr: options.writeErr ?? ((str) => process.stderr.write(str)),
375
+ outputError: (str, write) => write(str)
376
+ });
377
+ if (options.enableExitOverride) {
378
+ program.exitOverride();
379
+ }
380
+ const startMcpServerCommand = new Command('start-mcp-server')
381
+ .description('Serena MCP サーバーを起動します。')
382
+ .option('--project [project]', '起動時にアクティブ化するプロジェクト名またはパス。')
383
+ .option('--project-file [project]', '[非推奨] --project の旧名称。')
384
+ .argument('[project]', '[非推奨] プロジェクトの位置引数。')
385
+ .option('--context <context>', 'ビルトインコンテキスト名またはカスタム YAML へのパス。')
386
+ .addOption(new Option('--mode <mode...>', 'ビルトインモード名またはカスタムモード YAML を複数指定できます。')
387
+ .default(Array.from(DEFAULT_MODES)))
388
+ .addOption(new Option('--transport <transport>', '使用するトランスポート。')
389
+ .choices(['stdio', 'sse', 'streamable-http'])
390
+ .default('stdio'))
391
+ .option('--host <host>', 'サーバーのバインドホスト。', '0.0.0.0')
392
+ .option('--port <port>', 'サーバーのポート。', (value) => parseInteger(value, '--port'), 8000)
393
+ .option('--enable-web-dashboard [value]', 'Config の web_dashboard 設定を上書きします。', parseOptionalBoolean, undefined)
394
+ .option('--enable-gui-log-window [value]', 'Config の gui_log_window 設定を上書きします。', parseOptionalBoolean, undefined)
395
+ .addOption(new Option('--log-level <level>', 'Config の log_level を上書きします。')
396
+ .choices([...LOG_LEVEL_NAMES]))
397
+ .option('--trace-lsp-communication [value]', 'LSP 通信のトレースを有効/無効にします。', parseOptionalBoolean, undefined)
398
+ .option('--tool-timeout <seconds>', 'ツール実行のタイムアウト(秒)。', (value) => parseFloatSeconds(value))
399
+ .option('--instructions-override <prompt>', 'ツールに公開する初期インストラクションを明示的に上書きします。', undefined)
400
+ .action(async function (projectArg) {
401
+ const opts = normalizeStartMcpServerOptions(this.optsWithGlobals());
402
+ const normalizedProjectArg = projectArg ?? null;
403
+ try {
404
+ await handleStartMcpServer(opts, normalizedProjectArg);
405
+ }
406
+ catch (error) {
407
+ const message = error instanceof Error ? error.message : String(error);
408
+ this.error(`${message}\n`, { exitCode: 1 });
409
+ }
410
+ });
411
+ const printSystemPromptCommand = new Command('print-system-prompt')
412
+ .description('プロジェクトに対するシステムプロンプトを表示します。')
413
+ .argument('[project]', 'プロジェクトパス (省略時はカレントディレクトリ)。', process.cwd())
414
+ .option('--log-level <level>', 'プロンプト生成時のログレベル。', 'WARNING')
415
+ .option('--only-instructions', '初期指示部分のみ出力します。')
416
+ .option('--context <context>', 'ビルトインコンテキスト名または YAML パス。', DEFAULT_CONTEXT)
417
+ .addOption(new Option('--mode <mode...>', 'ビルトインモードまたは YAML を複数指定。').default(Array.from(DEFAULT_MODES)))
418
+ .action((projectPath, command) => {
419
+ const err = 'print-system-prompt はエージェント実装が移植され次第サポート予定です。現在は未実装のため利用できません。';
420
+ command.error(`${err}\n`, { exitCode: 1 });
421
+ });
422
+ program.addCommand(startMcpServerCommand);
423
+ program.addCommand(printSystemPromptCommand);
424
+ const modeCommand = new Command('mode')
425
+ .description('Serena モードを管理します。');
426
+ modeCommand
427
+ .command('list')
428
+ .description('利用可能なモードを一覧表示します。')
429
+ .action(() => {
430
+ const modes = SerenaAgentMode.listRegisteredModeNames();
431
+ const output = modes
432
+ .map((name) => {
433
+ const yamlPath = SerenaAgentMode.getPath(name);
434
+ return formatModeLine(name, yamlPath);
435
+ })
436
+ .join('\n');
437
+ if (output.length > 0) {
438
+ console.log(output);
439
+ }
440
+ });
441
+ modeCommand
442
+ .command('create')
443
+ .description('新しいモードを作成するか、内部モードをコピーします。')
444
+ .option('--name <name>', '新しいモード名。')
445
+ .option('--from-internal <name>', '内部モードからコピーします。')
446
+ .action(async (options) => {
447
+ try {
448
+ const destination = handleModeCreate({
449
+ name: options.name ?? null,
450
+ fromInternal: options.fromInternal ?? null
451
+ });
452
+ console.log(`Created mode '${path.parse(destination).name}' at ${destination}`);
453
+ await openInEditor(destination);
454
+ }
455
+ catch (error) {
456
+ const message = error instanceof Error ? error.message : String(error);
457
+ throw new Error(`${message}`);
458
+ }
459
+ });
460
+ modeCommand
461
+ .command('edit')
462
+ .description('カスタムモード YAML を編集します。')
463
+ .argument('<modeName>', 'モード名')
464
+ .action(async (modeName) => {
465
+ const destination = path.join(USER_MODE_YAMLS_DIR, `${modeName}.yml`);
466
+ if (!fs.existsSync(destination)) {
467
+ if (SerenaAgentMode.listRegisteredModeNames(false).includes(modeName)) {
468
+ throw new Error(`Mode '${modeName}' は内部モードのため直接編集できません。'mode create --from-internal ${modeName}' を使用してカスタムモードを作成してください。`);
469
+ }
470
+ throw new Error(`カスタムモード '${modeName}' が見つかりません。'mode create --name ${modeName}' を使用してください。`);
471
+ }
472
+ await openInEditor(destination);
473
+ });
474
+ modeCommand
475
+ .command('delete')
476
+ .description('カスタムモードファイルを削除します。')
477
+ .argument('<modeName>', 'モード名')
478
+ .action((modeName) => {
479
+ const destination = path.join(USER_MODE_YAMLS_DIR, `${modeName}.yml`);
480
+ if (!fs.existsSync(destination)) {
481
+ throw new Error(`カスタムモード '${modeName}' が存在しません。`);
482
+ }
483
+ fs.rmSync(destination);
484
+ console.log(`Deleted custom mode '${modeName}'.`);
485
+ });
486
+ program.addCommand(modeCommand);
487
+ const contextCommand = new Command('context')
488
+ .description('Serena コンテキストを管理します。');
489
+ contextCommand
490
+ .command('list')
491
+ .description('利用可能なコンテキストを一覧表示します。')
492
+ .action(() => {
493
+ const contexts = SerenaAgentContext.listRegisteredContextNames();
494
+ const output = contexts
495
+ .map((name) => {
496
+ const yamlPath = SerenaAgentContext.getPath(name);
497
+ return formatContextLine(name, yamlPath);
498
+ })
499
+ .join('\n');
500
+ if (output.length > 0) {
501
+ console.log(output);
502
+ }
503
+ });
504
+ contextCommand
505
+ .command('create')
506
+ .description('新しいコンテキストを作成するか、内部コンテキストをコピーします。')
507
+ .option('--name <name>', '新しいコンテキスト名。')
508
+ .option('--from-internal <name>', '内部コンテキストからコピーします。')
509
+ .action(async (options) => {
510
+ try {
511
+ const destination = handleContextCreate({
512
+ name: options.name ?? null,
513
+ fromInternal: options.fromInternal ?? null
514
+ });
515
+ console.log(`Created context '${path.parse(destination).name}' at ${destination}`);
516
+ await openInEditor(destination);
517
+ }
518
+ catch (error) {
519
+ const message = error instanceof Error ? error.message : String(error);
520
+ throw new Error(`${message}`);
521
+ }
522
+ });
523
+ contextCommand
524
+ .command('edit')
525
+ .description('カスタムコンテキスト YAML を編集します。')
526
+ .argument('<contextName>', 'コンテキスト名')
527
+ .action(async (contextName) => {
528
+ const destination = path.join(USER_CONTEXT_YAMLS_DIR, `${contextName}.yml`);
529
+ if (!fs.existsSync(destination)) {
530
+ if (SerenaAgentContext.listRegisteredContextNames(false).includes(contextName)) {
531
+ throw new Error(`Context '${contextName}' は内部コンテキストのため直接編集できません。'context create --from-internal ${contextName}' を使用してカスタムコンテキストを作成してください。`);
532
+ }
533
+ throw new Error(`カスタムコンテキスト '${contextName}' が見つかりません。'context create --name ${contextName}' を使用してください。`);
534
+ }
535
+ await openInEditor(destination);
536
+ });
537
+ contextCommand
538
+ .command('delete')
539
+ .description('カスタムコンテキストファイルを削除します。')
540
+ .argument('<contextName>', 'コンテキスト名')
541
+ .action((contextName) => {
542
+ const destination = path.join(USER_CONTEXT_YAMLS_DIR, `${contextName}.yml`);
543
+ if (!fs.existsSync(destination)) {
544
+ throw new Error(`カスタムコンテキスト '${contextName}' は存在しません。`);
545
+ }
546
+ fs.rmSync(destination);
547
+ console.log(`Deleted custom context '${contextName}'.`);
548
+ });
549
+ program.addCommand(contextCommand);
550
+ const configCommand = new Command('config')
551
+ .description('Serena の設定ファイルを扱います。');
552
+ configCommand
553
+ .command('edit')
554
+ .description('serena_config.yml を既定のエディタで開きます。')
555
+ .action(async () => {
556
+ const configPath = path.join(SERENA_MANAGED_DIR_IN_HOME, 'serena_config.yml');
557
+ if (!fs.existsSync(configPath)) {
558
+ ensureDirExists(path.dirname(configPath));
559
+ SerenaConfig.generateConfigFile(configPath);
560
+ }
561
+ await openInEditor(configPath);
562
+ });
563
+ program.addCommand(configCommand);
564
+ const projectCommand = new Command('project')
565
+ .description('Serena プロジェクト関連の操作。');
566
+ projectCommand
567
+ .command('generate-yml')
568
+ .description('プロジェクトの project.yml を生成します。')
569
+ .argument('[projectPath]', 'プロジェクトディレクトリ (既定: カレントディレクトリ)。', process.cwd())
570
+ .option('--language <language>', 'プロジェクトの言語。指定しない場合は自動推測。')
571
+ .action((projectPath, options) => {
572
+ const resolved = path.resolve(projectPath);
573
+ try {
574
+ const language = options.language ? coerceLanguage(options.language) : undefined;
575
+ const config = ProjectConfig.autogenerate(resolved, {
576
+ projectLanguage: language ?? null
577
+ });
578
+ console.log(`Generated project.yml with language ${config.language} at ${path.join(resolved, ProjectConfig.relPathToProjectYml())}.`);
579
+ }
580
+ catch (error) {
581
+ const message = error instanceof Error ? error.message : String(error);
582
+ throw new Error(message);
583
+ }
584
+ });
585
+ projectCommand
586
+ .command('index')
587
+ .description('プロジェクトのシンボルをインデックスします。')
588
+ .action(() => {
589
+ throw new Error('project index はエージェントおよび SolidLSP モジュール移植後に実装予定です。');
590
+ });
591
+ projectCommand
592
+ .command('index-deprecated')
593
+ .description('project index の旧名称。')
594
+ .action(() => {
595
+ throw new Error('project index-deprecated は TypeScript 版では利用できません。`serena project index` をお待ちください。');
596
+ });
597
+ projectCommand
598
+ .command('is_ignored_path')
599
+ .description('プロジェクト設定で無視されるパスか確認します。')
600
+ .action(() => {
601
+ throw new Error('is_ignored_path は TypeScript 版で未実装です。');
602
+ });
603
+ projectCommand
604
+ .command('index-file')
605
+ .description('単一ファイルのインデックス化を行います。')
606
+ .action(() => {
607
+ throw new Error('index-file は TypeScript 版で未実装です。');
608
+ });
609
+ projectCommand
610
+ .command('health-check')
611
+ .description('プロジェクトのヘルスチェックを実行します。')
612
+ .action(() => {
613
+ throw new Error('health-check は SolidLSP およびエージェント移植後に実装予定です。');
614
+ });
615
+ program.addCommand(projectCommand);
616
+ const toolsCommand = new Command('tools')
617
+ .description('Serena のツール情報を表示します。');
618
+ toolsCommand
619
+ .command('list')
620
+ .description('利用可能なツールの概要を表示します。')
621
+ .option('--quiet', '-q', 'ツール名のみを表示します。')
622
+ .option('--all', '-a', 'オプションツールを含め全て表示します。')
623
+ .option('--only-optional', 'オプションツールのみ表示します。')
624
+ .action((options) => {
625
+ const registry = new ToolRegistry();
626
+ if (options.quiet) {
627
+ let toolNames;
628
+ if (options.onlyOptional) {
629
+ toolNames = registry.getToolNamesOptional();
630
+ }
631
+ else if (options.all) {
632
+ toolNames = registry.getToolNames();
633
+ }
634
+ else {
635
+ toolNames = registry.getToolNamesDefaultEnabled();
636
+ }
637
+ console.log(toolNames.join('\n'));
638
+ return;
639
+ }
640
+ registry.printToolOverview(undefined, {
641
+ includeOptional: options.all,
642
+ onlyOptional: options.onlyOptional
643
+ });
644
+ });
645
+ toolsCommand
646
+ .command('description')
647
+ .description('指定したツールの説明を表示します。')
648
+ .argument('<toolName>', 'ツール名')
649
+ .option('--context <context>', 'コンテキスト名または YAML パス。')
650
+ .action(() => {
651
+ throw new Error('tools description はエージェント移植後に実装予定です。');
652
+ });
653
+ program.addCommand(toolsCommand);
654
+ const promptsCommand = new Command('prompts')
655
+ .description('プロンプト関連のコマンド。');
656
+ promptsCommand
657
+ .command('list')
658
+ .description('プロンプト定義に使用される YAML を一覧表示します。')
659
+ .action(() => {
660
+ const names = fs
661
+ .readdirSync(PROMPT_TEMPLATES_DIR_INTERNAL, { withFileTypes: true })
662
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.yml'))
663
+ .map((entry) => entry.name)
664
+ .sort();
665
+ const lines = names.map((name) => formatPromptListLine(name, getUserPromptPath(name)));
666
+ console.log(lines.join('\n'));
667
+ });
668
+ promptsCommand
669
+ .command('create-override')
670
+ .description('内部プロンプト YAML のオーバーライドを作成します。')
671
+ .argument('<promptYamlName>', 'プロンプト YAML 名')
672
+ .action(async (promptYamlName) => {
673
+ const normalized = ensurePromptYamlName(promptYamlName);
674
+ const destination = getUserPromptPath(normalized);
675
+ if (fs.existsSync(destination)) {
676
+ throw new Error(`${destination} は既に存在します。`);
677
+ }
678
+ const source = path.join(PROMPT_TEMPLATES_DIR_INTERNAL, normalized);
679
+ if (!fs.existsSync(source)) {
680
+ throw new Error(`内部プロンプト '${normalized}' が見つかりません。'prompts list' で確認してください。`);
681
+ }
682
+ fs.copyFileSync(source, destination);
683
+ await openInEditor(destination);
684
+ });
685
+ promptsCommand
686
+ .command('edit-override')
687
+ .description('既存のプロンプトオーバーライドを編集します。')
688
+ .argument('<promptYamlName>', 'プロンプト YAML 名')
689
+ .action(async (promptYamlName) => {
690
+ const normalized = ensurePromptYamlName(promptYamlName);
691
+ const destination = getUserPromptPath(normalized);
692
+ if (!fs.existsSync(destination)) {
693
+ throw new Error(`Override file '${normalized}' は存在しません。'prompts create-override ${normalized}' を使用してください。`);
694
+ }
695
+ await openInEditor(destination);
696
+ });
697
+ promptsCommand
698
+ .command('list-overrides')
699
+ .description('既存のプロンプトオーバーライドを一覧表示します。')
700
+ .action(() => {
701
+ ensureDirExists(PROMPT_TEMPLATES_DIR_IN_USER_HOME);
702
+ const overrides = fs
703
+ .readdirSync(PROMPT_TEMPLATES_DIR_IN_USER_HOME, { withFileTypes: true })
704
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.yml'))
705
+ .map((entry) => path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, entry.name))
706
+ .sort();
707
+ for (const filePath of overrides) {
708
+ console.log(filePath);
709
+ }
710
+ });
711
+ promptsCommand
712
+ .command('delete-override')
713
+ .description('プロンプトオーバーライドファイルを削除します。')
714
+ .argument('<promptYamlName>', 'プロンプト YAML 名')
715
+ .action((promptYamlName) => {
716
+ const normalized = ensurePromptYamlName(promptYamlName);
717
+ const destination = getUserPromptPath(normalized);
718
+ if (!fs.existsSync(destination)) {
719
+ throw new Error(`Override file '${normalized}' は存在しません。`);
720
+ }
721
+ fs.rmSync(destination);
722
+ console.log(`Deleted override file '${normalized}'.`);
723
+ });
724
+ program.addCommand(promptsCommand);
725
+ return program;
726
+ }
727
+ export async function runSerenaCli(argv = process.argv.slice(2)) {
728
+ const cli = createSerenaCli();
729
+ await cli.parseAsync(argv, { from: 'user' });
730
+ }
731
+ export default runSerenaCli;