jutell 1.0.0 → 1.0.1
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 +2 -2
- package/assets/mcp-server/index.js +1 -1
- package/assets/skill/SKILL.md +1 -1
- package/assets/version.json +1 -1
- package/dist/config/managed.js +46 -5
- package/dist/output/format.js +1 -1
- package/dist/process/mcpProbe.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# jutell
|
|
2
2
|
|
|
3
|
-
JuTell by Ju0의 로컬 설치·상태·진단·관리자 실행 CLI입니다. `jutell@0.
|
|
3
|
+
JuTell by Ju0의 로컬 설치·상태·진단·관리자 실행 CLI입니다. `jutell@1.0.0`은 npm에 공개되었습니다.
|
|
4
4
|
|
|
5
5
|
```powershell
|
|
6
6
|
npm install -g jutell
|
|
@@ -11,7 +11,7 @@ jutell status
|
|
|
11
11
|
개발자·기여자용 로컬 설치 (일반 사용자는 위 registry 설치 사용):
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install -g ./jutell-1.0.
|
|
14
|
+
npm install -g ./jutell-1.0.1.tgz
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
기존 `beginner-bridge` 명령은 같은 기능의 호환 별칭이며 실행 시 새 명령 사용을 안내합니다. CLI는 프로젝트 코드, Prompt, AI 답변, Git diff와 비밀정보를 수집하거나 외부로 전송하지 않습니다.
|
|
@@ -5,7 +5,7 @@ import { activeFeatures, beginnerReportRules, bridgeStatus, reportPreferences, s
|
|
|
5
5
|
import { recordToolCall } from './tools/usage-counters.js';
|
|
6
6
|
const server = new McpServer({
|
|
7
7
|
name: 'JuTell',
|
|
8
|
-
version: '1.0.
|
|
8
|
+
version: '1.0.1',
|
|
9
9
|
}, {
|
|
10
10
|
instructions: 'JuTell by Ju0 is a local read-only report helper. Read only project configuration and approved report rules. Never access project code, Git diff, prompts, AI answers, secrets, or external networks. Skill mode remains available if this MCP server is disabled or unavailable. When both jutell and beginner_bridge servers are visible, prefer the canonical jutell server; use beginner_bridge only for compatibility. For owner-facing reports, apply the JuTell reporting guidance before composing the final answer.',
|
|
11
11
|
});
|
package/assets/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: beginner-bridge
|
|
3
|
-
jutellSkillVersion: "1.0.
|
|
3
|
+
jutellSkillVersion: "1.0.1"
|
|
4
4
|
schemaVersion: 1
|
|
5
5
|
description: JuTell by Ju0 creates concise, evidence-based work reports for non-developers, separating observed facts, code-based expectations, verification results, risks, and user actions. The legacy Skill ID is retained for compatibility.
|
|
6
6
|
---
|
package/assets/version.json
CHANGED
package/dist/config/managed.js
CHANGED
|
@@ -149,17 +149,43 @@ function withoutCanonicalBlock(content) {
|
|
|
149
149
|
function hasMcpKey(content, key) {
|
|
150
150
|
return new RegExp(`^\\s*\\[mcp_servers\\.${key}\\]\\s*$`, 'm').test(content);
|
|
151
151
|
}
|
|
152
|
-
function
|
|
152
|
+
function keySlice(content, key) {
|
|
153
153
|
const keyPattern = new RegExp(`^\\s*\\[mcp_servers\\.${key}\\]\\s*$`, 'm');
|
|
154
154
|
const match = content.match(keyPattern);
|
|
155
155
|
if (!match || match.index === undefined)
|
|
156
|
+
return undefined;
|
|
157
|
+
return content.slice(match.index, match.index + 1200);
|
|
158
|
+
}
|
|
159
|
+
function hasJuTellMcpEvidence(content, key) {
|
|
160
|
+
const slice = keySlice(content, key);
|
|
161
|
+
if (!slice)
|
|
156
162
|
return false;
|
|
157
|
-
const slice = content.slice(match.index, match.index + 1200);
|
|
158
163
|
// JuTell's server always contains `assets/mcp-server` or `apps/mcp-server` in args (see buildMcpBlock)
|
|
159
164
|
// Unrelated custom entries (e.g. jira, other, even a custom `jutell` pointing to `not-mcp-server.js`) do not –
|
|
160
165
|
// this prevents broadly adopting arbitrary unmarked MCP entries.
|
|
161
166
|
return /(?:assets|apps)[\\/]mcp-server/i.test(slice);
|
|
162
167
|
}
|
|
168
|
+
function extractField(block, field) {
|
|
169
|
+
if (!block)
|
|
170
|
+
return undefined;
|
|
171
|
+
const match = block.match(new RegExp(`^\\s*${field}\\s*=\\s*(.+?)\\s*$`, 'm'));
|
|
172
|
+
return match?.[1];
|
|
173
|
+
}
|
|
174
|
+
// Removes a bare (unmarked) `[mcp_servers.<key>]` table — header line through its own
|
|
175
|
+
// key/value lines, up to the next table header or end of file. Used when repairing a stale
|
|
176
|
+
// heuristic-recognized entry that predates JUTELL_CLI_MCP markers, so a rewrite replaces it
|
|
177
|
+
// instead of leaving a duplicate `[mcp_servers.<key>]` table behind.
|
|
178
|
+
function withoutKeySection(content, key) {
|
|
179
|
+
const headerPattern = new RegExp(`^\\s*\\[mcp_servers\\.${key}\\]\\s*$`, 'm');
|
|
180
|
+
const match = content.match(headerPattern);
|
|
181
|
+
if (!match || match.index === undefined)
|
|
182
|
+
return content;
|
|
183
|
+
const start = match.index;
|
|
184
|
+
const afterHeader = start + match[0].length;
|
|
185
|
+
const nextHeaderMatch = content.slice(afterHeader).match(/^\s*\[/m);
|
|
186
|
+
const end = nextHeaderMatch?.index !== undefined ? afterHeader + nextHeaderMatch.index : content.length;
|
|
187
|
+
return content.slice(0, start) + content.slice(end);
|
|
188
|
+
}
|
|
163
189
|
function tomlString(value) {
|
|
164
190
|
return JSON.stringify(value.replaceAll('\\', '/'));
|
|
165
191
|
}
|
|
@@ -207,6 +233,15 @@ export async function readCodexRegistration(paths, packageRoot, enabled) {
|
|
|
207
233
|
}
|
|
208
234
|
return false;
|
|
209
235
|
})();
|
|
236
|
+
const preview = buildMcpBlock(paths, packageRoot, enabled);
|
|
237
|
+
// The command/args a currently-recognized JuTell entry (managed-block or heuristic-evidence)
|
|
238
|
+
// actually has, vs. what the current installation would write today. A mismatch means the
|
|
239
|
+
// entry is stale — e.g. left over from a different machine/OS or an older JuTell install —
|
|
240
|
+
// and should be repaired even though the key is already registered and `enabled` already matches.
|
|
241
|
+
const canonicalEntryText = canonicalManaged ?? (canonicalHeuristic ? keySlice(content, CANONICAL_MCP_KEY) : undefined);
|
|
242
|
+
const canonicalUpToDate = Boolean(canonicalEntryText
|
|
243
|
+
&& extractField(canonicalEntryText, 'command') === extractField(preview, 'command')
|
|
244
|
+
&& extractField(canonicalEntryText, 'args') === extractField(preview, 'args'));
|
|
210
245
|
return {
|
|
211
246
|
content,
|
|
212
247
|
exists: content.length > 0,
|
|
@@ -216,17 +251,23 @@ export async function readCodexRegistration(paths, packageRoot, enabled) {
|
|
|
216
251
|
canonicalRegistered,
|
|
217
252
|
legacyRegistered,
|
|
218
253
|
bothRegistered: canonicalRegistered && legacyRegistered,
|
|
219
|
-
|
|
254
|
+
canonicalUpToDate,
|
|
255
|
+
preview,
|
|
220
256
|
};
|
|
221
257
|
}
|
|
222
258
|
export async function registerMcp(paths, packageRoot, enabled) {
|
|
223
259
|
const current = await readCodexRegistration(paths, packageRoot, enabled);
|
|
224
260
|
if (current.conflict)
|
|
225
261
|
throw new Error('Codex 설정에 같은 이름의 관리되지 않는 MCP 항목이 있어 자동 변경하지 않았습니다.');
|
|
226
|
-
if (current.canonicalRegistered && current.enabled === enabled)
|
|
262
|
+
if (current.canonicalRegistered && current.enabled === enabled && current.canonicalUpToDate)
|
|
227
263
|
return current;
|
|
228
264
|
await backupFile(paths.codexConfigFile);
|
|
229
|
-
|
|
265
|
+
// withoutCanonicalBlock only removes a marker-wrapped block. A stale entry recognized by
|
|
266
|
+
// heuristic evidence alone (no markers, e.g. from an older JuTell install) would otherwise
|
|
267
|
+
// survive as a leftover bare `[mcp_servers.jutell]` table alongside the freshly-appended one.
|
|
268
|
+
// Safe to strip unconditionally here: reaching this point already ruled out (via `conflict`
|
|
269
|
+
// above) any bare `[mcp_servers.jutell]` that isn't JuTell's own.
|
|
270
|
+
const withoutManaged = withoutKeySection(withoutCanonicalBlock(current.content), CANONICAL_MCP_KEY).replace(/\n{3,}/g, '\n\n').trimEnd();
|
|
230
271
|
const next = `${withoutManaged}${withoutManaged ? '\n\n' : ''}${current.preview}\n`;
|
|
231
272
|
await writeTextSafely(paths.codexConfigFile, next);
|
|
232
273
|
return readCodexRegistration(paths, packageRoot, enabled);
|
package/dist/output/format.js
CHANGED
|
@@ -140,7 +140,7 @@ export function parseOptions(args) {
|
|
|
140
140
|
}
|
|
141
141
|
export function scopeLabel(scope) { return scope === 'global' ? '사용자 전역' : '현재 프로젝트'; }
|
|
142
142
|
export function printHelp(io) {
|
|
143
|
-
io.write(`JuTell CLI 1.0.
|
|
143
|
+
io.write(`JuTell CLI 1.0.1
|
|
144
144
|
|
|
145
145
|
시작할 때는 jutell만 입력하면 됩니다.
|
|
146
146
|
처음 연결하면 안내에 따라 AI Agent와 보고 방식을 고르고
|
package/dist/process/mcpProbe.js
CHANGED
|
@@ -6,7 +6,7 @@ const INITIALIZE = JSON.stringify({
|
|
|
6
6
|
jsonrpc: '2.0',
|
|
7
7
|
id: 1,
|
|
8
8
|
method: 'initialize',
|
|
9
|
-
params: { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: 'jutell-doctor', version: '1.0.
|
|
9
|
+
params: { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: 'jutell-doctor', version: '1.0.1' } },
|
|
10
10
|
});
|
|
11
11
|
const INITIALIZED = JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
12
12
|
const TOOLS_LIST = JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
|