axmap-cli 1.5.0 → 1.6.0
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.ko.md +20 -0
- package/README.md +20 -0
- package/bin/axmap.mjs +6 -0
- package/mcp/README.md +6 -0
- package/mcp/pipeline.html +157 -0
- package/mcp/server.mjs +128 -18
- package/package.json +11 -3
- package/src/agentcli.mjs +17 -14
- package/src/agentcli.mts +19 -0
- package/src/bridge.mjs +296 -219
- package/src/bridge.mts +281 -0
- package/src/external-console.mjs +140 -97
- package/src/external-console.mts +128 -0
- package/src/managed-session.mjs +263 -169
- package/src/managed-session.mts +198 -0
- package/src/monitor-cli.mjs +87 -0
- package/src/monitor-cli.mts +64 -0
- package/src/monitor.mjs +240 -0
- package/src/monitor.mts +195 -0
- package/src/node-pty.d.ts +13 -0
- package/src/pipeline-mcp.mjs +109 -0
- package/src/pipeline-mcp.mts +103 -0
- package/src/pipeline-statusline.mjs +135 -0
- package/src/pipeline-statusline.mts +106 -0
- package/src/session-dialogue.mjs +168 -116
- package/src/session-dialogue.mts +136 -0
- package/src/session-status.mjs +47 -41
- package/src/session-status.mts +50 -0
- package/src/session-types.mjs +7 -0
- package/src/session-types.mts +118 -0
- package/src/session-usage.mjs +33 -28
- package/src/session-usage.mts +39 -0
- package/src/terminal-bridge.mjs +293 -209
- package/src/terminal-bridge.mts +235 -0
- package/src/windows-console.mjs +42 -0
- package/src/windows-console.mts +29 -0
- package/tools/bench-console.mjs +70 -0
- package/tools/bridge.mjs +9 -6
- package/tools/build.mjs +39 -0
- package/tools/monitor.mjs +5 -0
- package/tools/pipeline.mjs +26 -0
- package/tools/run-tests.mjs +3 -2
- package/tools/windows-console.cs +116 -0
package/README.ko.md
CHANGED
|
@@ -19,12 +19,32 @@ axmap setup
|
|
|
19
19
|
|
|
20
20
|
[사용법](docs/INSTALL.md) · [MCP 연결](mcp/README.md) · [개발 가이드](docs/ONBOARD-TEAM.md)
|
|
21
21
|
|
|
22
|
+
세션·메시지 핵심 모듈은 strict TypeScript로 작성합니다. 단계적 전환 범위와 빌드 방법은 [TypeScript 개발 안내](docs/TYPESCRIPT.md)를 참고하세요.
|
|
23
|
+
|
|
24
|
+
지원되는 MCP Apps 호스트에서는 대화 안에서 파이프라인을 확인하고, Claude Code·agy에서는 기존 상태줄에 진행을 추가할 수 있습니다. 호스트별 지원이 다르며 ChatGPT·Claude 앱의 실제 임베디드 화면은 아직 검증하지 않았습니다. [클라이언트 설정과 제한](docs/PIPELINE-CLIENTS.md). 선택적으로 별도 터미널의 `axmap monitor`도 제공합니다([사용법](docs/MONITOR.md)).
|
|
25
|
+
|
|
22
26
|
---
|
|
23
27
|
|
|
24
28
|
## 변경 기록
|
|
25
29
|
|
|
26
30
|
최신 변경부터 정리합니다.
|
|
27
31
|
|
|
32
|
+
### 1.6.0
|
|
33
|
+
|
|
34
|
+
<sub>2026.10.26</sub>
|
|
35
|
+
|
|
36
|
+
**요청 흐름을 표시하고 기존 Windows agy 연결의 관측 비용을 줄입니다.**
|
|
37
|
+
|
|
38
|
+
- Windows 콘솔 도우미를 매번 컴파일하지 않고 캐시하며 PowerShell 대체 경로를 유지합니다.
|
|
39
|
+
- 제한 시간 안에서 응답을 기다리는 `wait_ms`·`--wait-ms`와 비동기 MCP 진행 알림을 추가합니다.
|
|
40
|
+
- MCP Apps 파이프라인 리소스, 텍스트 대체 출력, 기존 명령을 보존하는 Claude Code·agy 상태줄을 제공합니다. 별도 터미널 표시가 필요하면 `axmap monitor`를 사용합니다.
|
|
41
|
+
- 세션·파이프라인 모듈을 strict TypeScript로 작성하며 Node.js 20+용 JavaScript를 함께 배포합니다. 실행에 TypeScript 설치는 필요하지 않습니다.
|
|
42
|
+
|
|
43
|
+
Windows의 실제 agy에서 전달·진행 알림·상태줄의 완료 표시를 확인했습니다. ChatGPT·Claude UI는 기능 협상과 프로토콜 테스트로 검증했으며 실제 제품의 임베디드 화면 검증은 남아 있습니다. 이 Codex 세션에는 새 파이프라인 도구가 노출되지 않았습니다. 로컬 stdio 지원만으로 ChatGPT 웹용 원격 엔드포인트가 생기지는 않습니다.
|
|
44
|
+
[클라이언트 설정과 제한 →](docs/PIPELINE-CLIENTS.md)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
28
48
|
### 1.5.0
|
|
29
49
|
|
|
30
50
|
<sub>2026.10.21</sub>
|
package/README.md
CHANGED
|
@@ -19,12 +19,32 @@ Each Git repository's ledger is prepared on first use. Collaborators must share
|
|
|
19
19
|
|
|
20
20
|
[Usage](docs/INSTALL.md) · [MCP setup](mcp/README.md) · [Development](docs/ONBOARD-TEAM.md)
|
|
21
21
|
|
|
22
|
+
Session and messaging modules are authored in strict TypeScript. See the [TypeScript development guide](docs/TYPESCRIPT.md) for the incremental migration and build workflow.
|
|
23
|
+
|
|
24
|
+
View pipeline state inside compatible MCP Apps hosts, or add it to the existing Claude Code / agy statusline. Host support varies; actual ChatGPT and Claude embedded rendering remains unverified. See [client support and setup](docs/PIPELINE-CLIENTS.md). An optional separate terminal view is available with `axmap monitor` ([guide](docs/MONITOR.md)).
|
|
25
|
+
|
|
22
26
|
---
|
|
23
27
|
|
|
24
28
|
## Changelog
|
|
25
29
|
|
|
26
30
|
Newest changes first.
|
|
27
31
|
|
|
32
|
+
### 1.6.0
|
|
33
|
+
|
|
34
|
+
<sub>October 26, 2026</sub>
|
|
35
|
+
|
|
36
|
+
**Observe request flow and reduce the overhead of existing Windows agy connections.**
|
|
37
|
+
|
|
38
|
+
- Cache the Windows console helper instead of recompiling it for every observation, with PowerShell fallback.
|
|
39
|
+
- Add bounded `wait_ms` / `--wait-ms` response waiting and asynchronous MCP progress notifications.
|
|
40
|
+
- Add MCP Apps pipeline resources, text fallback, and optional Claude Code / agy statuslines that preserve existing commands. An optional `axmap monitor` shows the same flow in a terminal.
|
|
41
|
+
- Author session and pipeline modules in strict TypeScript while shipping generated JavaScript for Node.js 20+; TypeScript is not required at runtime.
|
|
42
|
+
|
|
43
|
+
Actual agy delivery, progress and native statusline completion were verified on Windows. ChatGPT / Claude UI support is capability-based and covered by protocol tests; embedded rendering in those products remains unverified. This Codex session did not expose the new pipeline tools. Local stdio support does not provide a remote ChatGPT web endpoint.
|
|
44
|
+
[Client setup and limits →](docs/PIPELINE-CLIENTS.md)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
28
48
|
### 1.5.0
|
|
29
49
|
|
|
30
50
|
<sub>October 21, 2026</sub>
|
package/bin/axmap.mjs
CHANGED
|
@@ -2624,6 +2624,8 @@ exec node "$AX" verify
|
|
|
2624
2624
|
const RUNNERS = {
|
|
2625
2625
|
run: ['tools/run.mjs', '사용자가 여는 AI CLI를 연결한다'],
|
|
2626
2626
|
bridge: ['tools/bridge.mjs', '열린 로컬 AI 세션에 작업을 전달한다'],
|
|
2627
|
+
monitor: ['tools/monitor.mjs', '세션과 요청 파이프라인을 실시간으로 표시한다'],
|
|
2628
|
+
pipeline: ['tools/pipeline.mjs', '대화 안 파이프라인 표시와 CLI 상태줄을 연결한다'],
|
|
2627
2629
|
gate: ['governance/gate.mjs', '이 MR 이 정족수를 채웠는가'],
|
|
2628
2630
|
vote: ['governance/vote.mjs', '표를 던진다'],
|
|
2629
2631
|
bus: ['tools/bus.mjs', '에이전트 사이 쪽지함'],
|
|
@@ -2834,6 +2836,8 @@ const HELP = `axmap - AI 에이전트 작업 선점 프로토콜
|
|
|
2834
2836
|
axmap vote 표를 던진다
|
|
2835
2837
|
axmap bus 에이전트 사이 쪽지함 (post · list · read · reply)
|
|
2836
2838
|
axmap bridge 로컬 세션·자동 대화 (serve · sessions · send · chat · dialogue)
|
|
2839
|
+
axmap monitor 에이전트·요청 파이프라인·최근 이벤트 실시간 화면
|
|
2840
|
+
axmap pipeline 대화 안 표시·Claude/agy 상태줄 연결 (--help)
|
|
2837
2841
|
axmap run agy [인수...] 연결된 Antigravity CLI를 직접 연다
|
|
2838
2842
|
axmap mr-target 이 브랜치가 저 브랜치로 갈 수 있는가
|
|
2839
2843
|
axmap version 브랜치 단계로 다음 버전을 정한다
|
|
@@ -2876,6 +2880,8 @@ switch (cmd) {
|
|
|
2876
2880
|
// 이름을 경로로 바꿔 주는 것들 (RUNNERS). 인자는 손대지 않고 그대로 간다.
|
|
2877
2881
|
case 'setup':
|
|
2878
2882
|
case 'bridge':
|
|
2883
|
+
case 'monitor':
|
|
2884
|
+
case 'pipeline':
|
|
2879
2885
|
case 'run':
|
|
2880
2886
|
case 'gate':
|
|
2881
2887
|
case 'vote':
|
package/mcp/README.md
CHANGED
|
@@ -30,10 +30,16 @@ https://github.com/rl2aderjoon/axmap/blob/main/mcp/SETUP-FOR-AI.md
|
|
|
30
30
|
|
|
31
31
|
## 도구
|
|
32
32
|
|
|
33
|
+
현재 대화 안의 파이프라인 패널과 Claude Code·agy 상태줄 설치는
|
|
34
|
+
[PIPELINE-CLIENTS.md](../docs/PIPELINE-CLIENTS.md)를 참고한다.
|
|
35
|
+
|
|
33
36
|
| 도구 | 언제 |
|
|
34
37
|
|---|---|
|
|
35
38
|
| `ax_brief()` | 이 저장소에서 처음 일할 때 **가장 먼저** |
|
|
36
39
|
| `ax_status()` | 지금 누가 어디를 잡고 있나 |
|
|
40
|
+
| `ax_pipeline()` | 대화 안의 파이프라인 패널 또는 텍스트 스냅샷 |
|
|
41
|
+
| `ax_pipeline_state()` | MCP Apps 패널의 상태 갱신 전용 |
|
|
42
|
+
| `ax_pipeline_watch()` | 현재 도구 호출의 진행 알림으로 상태 관찰 |
|
|
37
43
|
| `ax_check(paths)` | 내가 건드릴 곳이 비었나 — **선언은 안 한다.** 계획 세울 때 |
|
|
38
44
|
| `ax_claim(paths, intent, task?)` | **파일을 고치기 전에 반드시.** intent 는 사람이 화면에서 읽는다 |
|
|
39
45
|
| `ax_release(paths?)` | 끝나면 즉시. 생략하면 전부 반납 |
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ko">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<style>
|
|
7
|
+
@import url('https://cdn.jsdelivr.net/npm/pretendard@1.3.9/dist/web/static/pretendard.css');
|
|
8
|
+
:root { color-scheme:light dark; --surface:var(--color-background-primary,light-dark(#fff,#17191d)); --ink:var(--color-text-primary,light-dark(#20242b,#edf0f5)); --muted:var(--color-text-secondary,light-dark(#5b6574,#afb8c6)); --line:var(--color-border-primary,light-dark(#dce1e8,#3b414b)); --accent:light-dark(#2461c6,#80b0ff); --soft:light-dark(#edf3ff,#25364e); --warning:light-dark(#8a5600,#f5c56b); }
|
|
9
|
+
:root[data-theme="dark"] { color-scheme:dark; }
|
|
10
|
+
:root[data-theme="light"] { color-scheme:light; }
|
|
11
|
+
* { box-sizing:border-box; }
|
|
12
|
+
body { margin:0; padding:18px; background:var(--surface); color:var(--ink); font:14px/1.55 Pretendard,system-ui,sans-serif; }
|
|
13
|
+
h1 { font-size:18px; font-weight:600; margin:0; }
|
|
14
|
+
h2 { font-size:12px; font-weight:500; color:var(--muted); margin:20px 0 8px; }
|
|
15
|
+
header { display:flex; flex-wrap:wrap; align-items:center; gap:12px; }
|
|
16
|
+
.status { display:flex; align-items:center; gap:7px; color:var(--muted); font-size:12px; }
|
|
17
|
+
.status::before { content:''; width:7px; height:7px; border-radius:50%; background:var(--muted); }
|
|
18
|
+
body[data-live="true"] .status::before { background:var(--accent); }
|
|
19
|
+
.scope { font-size:12px; color:var(--muted); margin:6px 0 16px; overflow-wrap:anywhere; }
|
|
20
|
+
button { margin-left:auto; font:inherit; color:var(--ink); background:var(--surface); border:1px solid var(--line); border-radius:6px; padding:5px 10px; cursor:pointer; }
|
|
21
|
+
button:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
|
|
22
|
+
.sessions { display:flex; flex-wrap:wrap; gap:8px 18px; font-size:12px; }
|
|
23
|
+
.session { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
|
|
24
|
+
.session strong { font-weight:600; }
|
|
25
|
+
.subtle { color:var(--muted); }
|
|
26
|
+
.job { padding:14px 0; border-bottom:1px solid var(--line); }
|
|
27
|
+
.job:last-child { border-bottom:0; }
|
|
28
|
+
.route { font-weight:500; overflow-wrap:anywhere; }
|
|
29
|
+
.task { margin-top:9px; color:var(--muted); overflow-wrap:anywhere; }
|
|
30
|
+
.steps { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:8px; margin-top:16px; }
|
|
31
|
+
.step { position:relative; display:flex; flex-direction:column; align-items:center; text-align:center; gap:6px; font-size:12px; }
|
|
32
|
+
.step:not(:last-child)::after { content:''; position:absolute; top:14px; left:calc(50% + 18px); width:calc(100% - 28px); height:1px; background:var(--line); }
|
|
33
|
+
.mark { display:grid; place-items:center; position:relative; width:28px; height:28px; border-radius:50%; background:var(--line); color:var(--ink); }
|
|
34
|
+
.step.done .mark,.step.current .mark { background:var(--soft); }
|
|
35
|
+
.step.current .mark::after { content:''; position:absolute; inset:-3px; border:2px solid transparent; border-top-color:var(--accent); border-radius:50%; animation:spin 1s linear infinite; }
|
|
36
|
+
body:not([data-live="true"]) .step.current .mark::after { animation-play-state:paused; }
|
|
37
|
+
.issue { color:var(--warning); margin-top:10px; overflow-wrap:anywhere; }
|
|
38
|
+
.events { list-style:none; padding:0; margin:0; font-size:12px; }
|
|
39
|
+
.events li { padding:3px 0; overflow-wrap:anywhere; }
|
|
40
|
+
time { color:var(--muted); margin-right:10px; font-variant-numeric:tabular-nums; }
|
|
41
|
+
footer { border-top:1px solid var(--line); padding-top:10px; margin-top:14px; color:var(--muted); font-size:12px; }
|
|
42
|
+
@keyframes spin { to { transform:rotate(360deg); } }
|
|
43
|
+
@media (prefers-reduced-motion:reduce) { .step.current .mark::after { animation:none; } }
|
|
44
|
+
@media(max-width:420px) { body{padding:12px;} .steps{grid-template-columns:1fr;gap:10px;} .step{flex-direction:row;text-align:left;gap:12px;} .step:not(:last-child)::after{top:31px;left:14px;height:7px;width:1px;} button{margin-left:0;} }
|
|
45
|
+
</style>
|
|
46
|
+
</head>
|
|
47
|
+
<body data-live="false">
|
|
48
|
+
<main id="axmap-pipeline">
|
|
49
|
+
<header><h1>axMap 실행 흐름</h1><span class="status" id="status">연결 확인 중</span><button type="button" id="pause">새로고침 일시정지</button></header>
|
|
50
|
+
<div class="scope" id="scope">실제 작업 상태를 기다리는 중</div>
|
|
51
|
+
<div class="sessions" id="sessions"></div>
|
|
52
|
+
<section aria-label="요청 파이프라인" id="jobs"></section>
|
|
53
|
+
<div class="issue" id="issue" role="status" aria-live="polite"></div>
|
|
54
|
+
<section id="event-section"><h2>최근 이벤트</h2><ul class="events" id="events"></ul></section>
|
|
55
|
+
<footer id="checked">대화 안에서 상태만 조회합니다. 모델 호출·작업 재전송·자동 승인 없음.</footer>
|
|
56
|
+
</main>
|
|
57
|
+
<script>
|
|
58
|
+
(() => {
|
|
59
|
+
const root=document.getElementById('axmap-pipeline');
|
|
60
|
+
const status=document.getElementById('status'),scope=document.getElementById('scope');
|
|
61
|
+
const sessions=document.getElementById('sessions'),jobs=document.getElementById('jobs');
|
|
62
|
+
const issue=document.getElementById('issue'),events=document.getElementById('events');
|
|
63
|
+
const checked=document.getElementById('checked'),pause=document.getElementById('pause');
|
|
64
|
+
const requests=new Map();let sequence=0,ready=false,mode='standard',cwd=null,requestedCwd=null,paused=false,stopped=false,inflight=false,timer,unavailable=false;
|
|
65
|
+
const pending=new Set(['sending','submitted','unknown']);
|
|
66
|
+
const states={sending:'전달 중',submitted:'응답 대기',completed:'완료 확인',unknown:'전송 확인 필요',failed:'실패',superseded:'다른 입력으로 대체됨'};
|
|
67
|
+
const sessionStates={starting:'시작 중',running:'작업 중',idle:'대기',awaiting_user:'승인 필요',settling:'대기 전환 중',closed:'종료',unknown:'상태 미확인'};
|
|
68
|
+
const connectionStates={connected:'연결됨',closed:'종료됨',unreachable:'연결 확인 불가',unverified:'확인 미지원'};
|
|
69
|
+
const text=value=>String(value??'').replace(/[\x00-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/g,' ').slice(0,600);
|
|
70
|
+
const node=(tag,content,className)=>{const el=document.createElement(tag);if(content!==undefined)el.textContent=text(content);if(className)el.className=className;return el};
|
|
71
|
+
const notify=(method,params)=>window.parent.postMessage({jsonrpc:'2.0',method,params},'*');
|
|
72
|
+
function request(method,params){
|
|
73
|
+
const id=`axmap-ui-${++sequence}`;
|
|
74
|
+
return new Promise((resolve,reject)=>{
|
|
75
|
+
const timeout=setTimeout(()=>{requests.delete(id);reject(new Error('호스트 응답 시간 초과'))},10000);
|
|
76
|
+
requests.set(id,{resolve,reject,timeout});window.parent.postMessage({jsonrpc:'2.0',id,method,params},'*');
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function context(value){
|
|
80
|
+
if(value?.theme==='light'||value?.theme==='dark')document.documentElement.dataset.theme=value.theme;
|
|
81
|
+
const allowed=new Set(['--color-background-primary','--color-text-primary','--color-text-secondary','--color-border-primary']);
|
|
82
|
+
for(const [key,color]of Object.entries(value?.styles?.variables||{}))if(allowed.has(key)&&typeof color==='string')document.documentElement.style.setProperty(key,color);
|
|
83
|
+
}
|
|
84
|
+
function setLive(value,message){document.body.dataset.live=String(value);status.textContent=message;}
|
|
85
|
+
function render(data){
|
|
86
|
+
if(!data||data.schemaVersion!==1||data.type!=='pipeline'||typeof data.cwd!=='string'||!Array.isArray(data.requests)||!Array.isArray(data.sessions))return;
|
|
87
|
+
if(cwd&&data.cwd!==cwd){issue.textContent='다른 저장소의 상태를 받았습니다. 현재 패널의 저장소를 유지합니다.';setLive(false,'저장소 확인 필요');return;}
|
|
88
|
+
cwd=data.cwd;scope.textContent=text(cwd);sessions.replaceChildren();jobs.replaceChildren();events.replaceChildren();
|
|
89
|
+
for(const session of data.sessions.slice(0,6)){
|
|
90
|
+
const line=node('div',undefined,'session');line.append(node('strong',`${session.provider} · ${text(session.id).slice(0,8)}`),node('span',`${session.pendingApproval?'사용자 승인 필요':sessionStates[session.state]||'상태 미확인'} · ${connectionStates[session.connection]||'확인 미지원'}`,'subtle'));sessions.append(line);
|
|
91
|
+
}
|
|
92
|
+
const active=data.requests.filter(r=>pending.has(r.state));
|
|
93
|
+
const selected=[...active,...data.requests.filter(r=>!pending.has(r.state))].slice(0,4);
|
|
94
|
+
if(!selected.length)jobs.append(node('p','새 요청을 기다리는 중','subtle'));
|
|
95
|
+
for(const job of selected){
|
|
96
|
+
const item=node('article',undefined,'job');item.append(node('div',`${text(job.from)} → ${text(job.to).slice(0,8)} · ${states[job.state]||'확인 필요'}`,'route'));
|
|
97
|
+
const steps=node('div',undefined,'steps');
|
|
98
|
+
const done=job.state==='completed',sent=job.state==='submitted'||done;
|
|
99
|
+
['접수','전달','응답','완료 확인'].forEach((label,index)=>{
|
|
100
|
+
const complete=done||index===0||(sent&&index===1);
|
|
101
|
+
const current=!done&&((job.state==='sending'&&index===1)||(job.state==='submitted'&&index===2));
|
|
102
|
+
const step=node('div',undefined,`step${complete?' done':current?' current':''}`);
|
|
103
|
+
const mark=node('span',complete?'✓':job.state==='unknown'&&index===1?'?':String(index+1),'mark');mark.setAttribute('aria-hidden','true');
|
|
104
|
+
step.append(mark,node('span',current&&index===2?'응답 대기':label));steps.append(step);
|
|
105
|
+
});
|
|
106
|
+
item.append(steps,node('div',job.task,'task'));if(job.error)item.append(node('div',job.error,'issue'));jobs.append(item);
|
|
107
|
+
}
|
|
108
|
+
for(const event of (data.events||[]).slice(0,5)){
|
|
109
|
+
const row=node('li');const date=new Date(event.at);row.append(node('time',Number.isFinite(date.getTime())?date.toLocaleTimeString('en-GB',{hour12:false}):'시각 미확인'),node('span',event.label));events.append(row);
|
|
110
|
+
}
|
|
111
|
+
issue.textContent=(data.issues||[]).slice(0,2).map(text).join(' · ');
|
|
112
|
+
checked.textContent=`관측 ${text(data.checkedAt)} · 상태 조회만 수행 · 작업 제어 없음`;
|
|
113
|
+
setLive(ready&&!paused&&!document.hidden,!ready?(unavailable?'현재 상태 · 자동 갱신 미지원':'연결 확인 중'):paused?'새로고침 일시정지':document.hidden?'화면 숨김':`진행 확인 ${active.length}개`);
|
|
114
|
+
}
|
|
115
|
+
function consume(result){if(result?.isError)throw new Error(result.content?.[0]?.text||'상태 조회 실패');render(result?.structuredContent);}
|
|
116
|
+
function schedule(){clearTimeout(timer);if(ready&&!paused&&!stopped&&!document.hidden)timer=setTimeout(poll,1200);}
|
|
117
|
+
async function poll(){
|
|
118
|
+
if(inflight||!ready||paused||stopped||document.hidden)return;
|
|
119
|
+
inflight=true;
|
|
120
|
+
try{const scope=cwd||requestedCwd,args=scope?{cwd:scope}:{};const result=mode==='openai'?await window.openai.callTool('ax_pipeline_state',args):await request('tools/call',{name:'ax_pipeline_state',arguments:args});consume(result);}
|
|
121
|
+
catch(error){setLive(false,'연결 확인 필요');issue.textContent=text(error?.message||error);}
|
|
122
|
+
finally{inflight=false;schedule();}
|
|
123
|
+
}
|
|
124
|
+
window.addEventListener('message',event=>{
|
|
125
|
+
if(event.source!==window.parent)return;const message=event.data;if(!message||message.jsonrpc!=='2.0')return;
|
|
126
|
+
if(requests.has(message.id)){const waiting=requests.get(message.id);requests.delete(message.id);clearTimeout(waiting.timeout);message.error?waiting.reject(new Error(message.error.message||'호스트 오류')):waiting.resolve(message.result);return;}
|
|
127
|
+
if(message.method==='ui/notifications/tool-result'){try{consume(message.params)}catch(error){issue.textContent=text(error.message);}}
|
|
128
|
+
if(message.method==='ui/notifications/tool-input'&&!cwd&&typeof message.params?.arguments?.cwd==='string')requestedCwd=message.params.arguments.cwd;
|
|
129
|
+
if(message.method==='ui/notifications/host-context-changed')context(message.params);
|
|
130
|
+
if(message.method==='ui/resource-teardown'){
|
|
131
|
+
stopped=true;clearTimeout(timer);resize.disconnect();setLive(false,'관측 종료');
|
|
132
|
+
if(message.id!==undefined)window.parent.postMessage({jsonrpc:'2.0',id:message.id,result:{}},'*');
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
document.addEventListener('visibilitychange',()=>{if(document.hidden){clearTimeout(timer);setLive(false,'화면 숨김')}else if(!paused){void poll()}});
|
|
136
|
+
pause.addEventListener('click',()=>{paused=!paused;pause.textContent=paused?'실시간 새로고침 재개':'새로고침 일시정지';if(paused){clearTimeout(timer);setLive(false,'새로고침 일시정지')}else{void poll()}});
|
|
137
|
+
let reportedHeight=0;
|
|
138
|
+
const resize=new ResizeObserver(()=>{if(!ready||mode!=='standard'||stopped)return;const height=Math.ceil(root.getBoundingClientRect().height)+36;if(height!==reportedHeight){reportedHeight=height;notify('ui/notifications/size-changed',{height})}});
|
|
139
|
+
resize.observe(root);
|
|
140
|
+
window.addEventListener('pagehide',()=>{stopped=true;clearTimeout(timer);resize.disconnect()});
|
|
141
|
+
if(window.openai?.toolOutput)render(window.openai.toolOutput);
|
|
142
|
+
(async()=>{
|
|
143
|
+
try{
|
|
144
|
+
const result=await request('ui/initialize',{protocolVersion:'2026-01-26',appInfo:{name:'axmap-pipeline',version:'1.0.0'},appCapabilities:{availableDisplayModes:['inline']}});
|
|
145
|
+
context(result?.hostContext);notify('ui/notifications/initialized',{});
|
|
146
|
+
ready=!!result?.hostCapabilities?.serverTools;
|
|
147
|
+
if(!ready){unavailable=true;setLive(false,'자동 갱신 미지원');issue.textContent='현재 호스트가 상태 조회 도구 호출을 제공하지 않습니다. ax_pipeline_watch로 진행 상태를 확인할 수 있습니다.';return;}
|
|
148
|
+
}catch(error){
|
|
149
|
+
if(typeof window.openai?.callTool==='function'){mode='openai';ready=true;}
|
|
150
|
+
else{unavailable=true;setLive(false,'MCP Apps 연결 필요');issue.textContent='이 화면에서는 실시간 UI 연결을 확인하지 못했습니다. 현재 대화에서 ax_pipeline_watch를 사용할 수 있습니다.';return;}
|
|
151
|
+
}
|
|
152
|
+
void poll();
|
|
153
|
+
})();
|
|
154
|
+
})();
|
|
155
|
+
</script>
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
package/mcp/server.mjs
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* stdio 전송 — 줄바꿈으로 구분된 JSON-RPC 2.0.
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
import { spawnSync } from 'node:child_process'
|
|
43
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
44
44
|
import fs from 'node:fs'
|
|
45
45
|
import path from 'node:path'
|
|
46
46
|
import readline from 'node:readline'
|
|
@@ -213,6 +213,57 @@ const TTL = process.env.AXMAP_TTL ?? '45m'
|
|
|
213
213
|
const SESSION = process.env.AXMAP_SESSION || process.env.CLAUDE_CODE_SESSION_ID || `mcp-${process.pid}`
|
|
214
214
|
|
|
215
215
|
const BRIDGE_TOOLS = new Set(['ax_sessions', 'ax_dispatch', 'ax_result', 'ax_chat', 'ax_dialogue', 'ax_chat_stop'])
|
|
216
|
+
const PIPELINE_TOOLS = new Set(['ax_pipeline', 'ax_pipeline_state', 'ax_pipeline_watch'])
|
|
217
|
+
const PIPELINE_URI = 'ui://axmap/pipeline/v1.html'
|
|
218
|
+
const PIPELINE_MIME = 'text/html;profile=mcp-app'
|
|
219
|
+
let clientHasApps = false
|
|
220
|
+
const runningPipeline = new Map()
|
|
221
|
+
|
|
222
|
+
/** Each observation captures its repo. It may run beside a long dispatch without
|
|
223
|
+
* changing REPO, switching claims, or blocking UI refresh behind model work. */
|
|
224
|
+
function pipeline(action, args, req, cwd) {
|
|
225
|
+
return new Promise(resolve => {
|
|
226
|
+
const offered = req.params?._meta?.progressToken
|
|
227
|
+
const token = typeof offered === 'string' || Number.isInteger(offered) ? offered : undefined
|
|
228
|
+
let buffer = '', stderr = '', result = null, ended = false, sequence = 0, reason = ''
|
|
229
|
+
const child = spawn(process.execPath, [path.join(SELF, 'tools', 'pipeline.mjs'), action], {
|
|
230
|
+
cwd, windowsHide: true, env: { ...process.env, AXMAP_AGENT: AGENT }, stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
|
231
|
+
})
|
|
232
|
+
const finish = fallback => {
|
|
233
|
+
if (ended) return
|
|
234
|
+
ended = true; clearTimeout(timer); runningPipeline.delete(req.id)
|
|
235
|
+
const answer = result || { content: [{ type: 'text', text: fallback || reason || stderr || '파이프라인 응답을 확인하지 못했습니다. 요청을 자동 재전송하지 않습니다.' }], isError: true }
|
|
236
|
+
if (answer.structuredContent) answer.structuredContent.display = { mode: clientHasApps ? 'mcp-app' : 'text-progress', progress: token !== undefined }
|
|
237
|
+
resolve(answer)
|
|
238
|
+
}
|
|
239
|
+
const timer = setTimeout(() => { reason = '파이프라인 조회 시간 초과. 이미 전달한 작업은 계속될 수 있습니다.'; child.kill() }, 45000)
|
|
240
|
+
runningPipeline.set(req.id, () => {
|
|
241
|
+
reason = '조회 대기를 중단했습니다. 이미 전달한 작업은 계속 실행됩니다.'
|
|
242
|
+
// A dispatch holds a cross-process lock during submission. Let it finish
|
|
243
|
+
// that atomic step, then stop waiting; killing it could strand the lock.
|
|
244
|
+
if (child.connected) child.send({ type: 'cancel-observation' }, () => {})
|
|
245
|
+
})
|
|
246
|
+
child.stdout.setEncoding('utf8')
|
|
247
|
+
child.stdout.on('data', chunk => {
|
|
248
|
+
buffer += chunk
|
|
249
|
+
if (Buffer.byteLength(buffer) > 2 * 1024 * 1024) { reason = '파이프라인 응답 크기 제한 초과'; child.kill(); return }
|
|
250
|
+
while (buffer.includes('\n')) {
|
|
251
|
+
const at = buffer.indexOf('\n'), line = buffer.slice(0, at); buffer = buffer.slice(at + 1)
|
|
252
|
+
try {
|
|
253
|
+
const event = JSON.parse(line)
|
|
254
|
+
if (event.type === 'progress' && token !== undefined && !result && !ended && !reason) {
|
|
255
|
+
send({ jsonrpc: '2.0', method: 'notifications/progress', params: { progressToken: token, progress: ++sequence, message: String(event.message) } })
|
|
256
|
+
} else if (event.type === 'result' && event.result?.content) result = event.result
|
|
257
|
+
} catch { reason = '파이프라인 응답 형식 오류'; child.kill() }
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
child.stderr.on('data', chunk => { stderr = (stderr + chunk.toString('utf8')).slice(-8192) })
|
|
261
|
+
child.stdin.on('error', () => {})
|
|
262
|
+
child.once('error', error => finish(error.message))
|
|
263
|
+
child.once('close', () => finish())
|
|
264
|
+
child.stdin.end(JSON.stringify({ ...args, __progress: token !== undefined }))
|
|
265
|
+
})
|
|
266
|
+
}
|
|
216
267
|
function bridge(args, input) {
|
|
217
268
|
const r = spawnSync(process.execPath, [path.join(SELF, 'tools', 'bridge.mjs'), ...args], {
|
|
218
269
|
cwd: REPO, input, encoding: 'utf8', windowsHide: true,
|
|
@@ -385,6 +436,29 @@ function cli(args) {
|
|
|
385
436
|
// ---------------------------------------------------------------------------
|
|
386
437
|
|
|
387
438
|
const TOOLS = [
|
|
439
|
+
{
|
|
440
|
+
name: 'ax_pipeline',
|
|
441
|
+
description: '현재 대화에서 axMap 실행 흐름을 연다. MCP Apps 지원 화면은 실시간 패널, 다른 클라이언트는 같은 상태를 텍스트로 표시한다. 상태 조회만 수행하며 새 창이나 모델을 실행하지 않는다.',
|
|
442
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
443
|
+
inputSchema: { type: 'object', properties: {} },
|
|
444
|
+
_meta: { ui: { resourceUri: PIPELINE_URI }, 'openai/outputTemplate': PIPELINE_URI, 'antigravity/alwaysLoad': true },
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: 'ax_pipeline_state',
|
|
448
|
+
description: '열린 파이프라인 패널의 실제 상태를 갱신한다. 다른 작업을 중단하거나 재전송하지 않는다.',
|
|
449
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
450
|
+
inputSchema: { type: 'object', properties: {} },
|
|
451
|
+
_meta: { ui: { visibility: ['app'] } },
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: 'ax_pipeline_watch',
|
|
455
|
+
description: '현재 대화의 도구 진행 표시로 axMap 상태를 관측한다. progressToken이 있는 클라이언트에 MCP 진행 알림을 보내며, 없는 경우 현재 상태를 한 번 반환한다. 모델 호출이나 새 창 없음.',
|
|
456
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
457
|
+
inputSchema: { type: 'object', properties: {
|
|
458
|
+
duration_ms: { type: 'integer', minimum: 0, maximum: 25000, description: '최대 관측 시간. 기본 5000ms.' },
|
|
459
|
+
interval_ms: { type: 'integer', minimum: 500, maximum: 5000, description: '조회 간격. 기본 1000ms.' },
|
|
460
|
+
} },
|
|
461
|
+
},
|
|
388
462
|
{
|
|
389
463
|
name: 'ax_sessions',
|
|
390
464
|
description: '현재 repo에 연결된 로컬 AI 세션, 역할, 상태와 측정 토큰 사용량을 확인한다. stdio는 사용자가 bridge serve로 시작한 관리형 세션이다. 등록만으로 연결 가능성이나 실행 성공을 보장하지 않는다.',
|
|
@@ -396,6 +470,7 @@ const TOOLS = [
|
|
|
396
470
|
inputSchema: { type: 'object', properties: {
|
|
397
471
|
to: { type: 'string', description: 'ax_sessions에서 확인한 상대 세션 UUID' },
|
|
398
472
|
prompt: { type: 'string', description: '상대가 실행할 작업 요청' },
|
|
473
|
+
wait_ms: { type: 'integer', minimum: 0, maximum: 30000, description: '전달 후 회신을 로컬에서 기다릴 최대 시간(ms). 짧은 요청은 30000으로 한 번에 회수한다. 기본 0.' },
|
|
399
474
|
}, required: ['to', 'prompt'] },
|
|
400
475
|
},
|
|
401
476
|
{
|
|
@@ -403,6 +478,7 @@ const TOOLS = [
|
|
|
403
478
|
description: '작업 요청의 제출/완료/불명 상태, 결과와 측정 사용량을 확인한다. 관리형 세션은 구조화된 완료 이벤트, PTY는 종료 훅, 독립 Windows console은 요청 UUID가 있는 대화 기록과 현재 빈 입력창의 대기 상태로 판정한다.',
|
|
404
479
|
inputSchema: { type: 'object', properties: {
|
|
405
480
|
id: { type: 'string', description: 'ax_dispatch가 반환한 요청 UUID' },
|
|
481
|
+
wait_ms: { type: 'integer', minimum: 0, maximum: 30000, description: '로컬 회신 대기(ms). 모델 호출 없이 완료 즉시 반환. 기본 0.' },
|
|
406
482
|
}, required: ['id'] },
|
|
407
483
|
},
|
|
408
484
|
{
|
|
@@ -848,8 +924,8 @@ function dispatch(name, args = {}) {
|
|
|
848
924
|
|
|
849
925
|
switch (name) {
|
|
850
926
|
case 'ax_sessions': return bridge(['sessions'])
|
|
851
|
-
case 'ax_dispatch': return bridge(['send', '--to', args.to], args.prompt)
|
|
852
|
-
case 'ax_result': return bridge(['result', args.id])
|
|
927
|
+
case 'ax_dispatch': return bridge(['send', '--to', args.to, '--wait-ms', String(args.wait_ms ?? 0)], args.prompt)
|
|
928
|
+
case 'ax_result': return bridge(['result', args.id, '--wait-ms', String(args.wait_ms ?? 0)])
|
|
853
929
|
case 'ax_chat': return bridge(['chat', '--background', '--left', args.left, '--right', args.right,
|
|
854
930
|
...['maxTurns', 'maxTokens', 'maxMessageChars'].flatMap(key => args[key] === undefined ? [] :
|
|
855
931
|
[`--${key.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`)}`, String(args[key])])], args.prompt)
|
|
@@ -1041,13 +1117,28 @@ async function handle(req) {
|
|
|
1041
1117
|
|
|
1042
1118
|
if (method === 'initialize') {
|
|
1043
1119
|
rootsSupported = !!params?.capabilities?.roots
|
|
1120
|
+
clientHasApps = !!params?.capabilities?.extensions?.['io.modelcontextprotocol/ui']?.mimeTypes?.includes(PIPELINE_MIME)
|
|
1044
1121
|
return {
|
|
1045
1122
|
protocolVersion: '2024-11-05',
|
|
1046
|
-
capabilities: { tools: {} },
|
|
1123
|
+
capabilities: { tools: {}, resources: {} },
|
|
1047
1124
|
serverInfo: { name: 'axmap', version: VERSION },
|
|
1125
|
+
instructions: 'axMap works across ChatGPT, Claude and agy. Open ax_pipeline once for an inline panel when the host supports MCP Apps. Otherwise ax_pipeline_watch and ax_dispatch/ax_result with wait_ms report standard MCP progress when the client supplies progressToken. These observations never call a model. Do not resubmit unknown requests.',
|
|
1048
1126
|
}
|
|
1049
1127
|
}
|
|
1050
|
-
if (method === 'tools/list') return { tools: TOOLS
|
|
1128
|
+
if (method === 'tools/list') return { tools: TOOLS.filter(tool => clientHasApps || tool.name !== 'ax_pipeline_state').map(tool => {
|
|
1129
|
+
if (clientHasApps || !tool._meta?.ui?.resourceUri) return tool
|
|
1130
|
+
const { ui, 'openai/outputTemplate': outputTemplate, ...metadata } = tool._meta
|
|
1131
|
+
return { ...tool, _meta: metadata }
|
|
1132
|
+
}) }
|
|
1133
|
+
if (method === 'resources/list') return { resources: [{ uri: PIPELINE_URI, name: 'axmap-pipeline', title: 'axMap 실행 흐름', mimeType: PIPELINE_MIME }] }
|
|
1134
|
+
if (method === 'resources/read') {
|
|
1135
|
+
if (params?.uri !== PIPELINE_URI) throw Object.assign(new Error('알 수 없는 UI 리소스입니다.'), { code: -32602 })
|
|
1136
|
+
return { contents: [{ uri: PIPELINE_URI, mimeType: PIPELINE_MIME,
|
|
1137
|
+
text: fs.readFileSync(path.join(SELF, 'mcp', 'pipeline.html'), 'utf8'),
|
|
1138
|
+
_meta: { ui: { prefersBorder: true, csp: { connectDomains: [], resourceDomains: ['https://cdn.jsdelivr.net'] } },
|
|
1139
|
+
'openai/widgetCSP': { connect_domains: [], resource_domains: ['https://cdn.jsdelivr.net'] } },
|
|
1140
|
+
}] }
|
|
1141
|
+
}
|
|
1051
1142
|
if (method === 'tools/call') {
|
|
1052
1143
|
if (rootsSupported && !process.env.AXMAP_REPO) {
|
|
1053
1144
|
if (!rootsTask || rootsError) refreshRoots()
|
|
@@ -1055,7 +1146,19 @@ async function handle(req) {
|
|
|
1055
1146
|
let task
|
|
1056
1147
|
do { task = rootsTask; await task } while (task !== rootsTask)
|
|
1057
1148
|
}
|
|
1058
|
-
const
|
|
1149
|
+
const args = params?.arguments ?? {}
|
|
1150
|
+
if (PIPELINE_TOOLS.has(params?.name)) {
|
|
1151
|
+
const cwd = resolveRepo(args)
|
|
1152
|
+
return pipeline(params.name === 'ax_pipeline_watch' ? 'watch' : 'snapshot', args, req, cwd)
|
|
1153
|
+
}
|
|
1154
|
+
if (['ax_dispatch', 'ax_result'].includes(params?.name)) {
|
|
1155
|
+
let switched
|
|
1156
|
+
try { switched = syncRepo(args) } catch (e) { return { content: [{ type: 'text', text: e.message }], isError: true } }
|
|
1157
|
+
const answer = await pipeline(params.name === 'ax_dispatch' ? 'dispatch' : 'result', args, req, REPO)
|
|
1158
|
+
if (switched) answer.content[0].text = switchNotice(switched) + '\n\n' + answer.content[0].text
|
|
1159
|
+
return answer
|
|
1160
|
+
}
|
|
1161
|
+
const r = callTool(params?.name, args)
|
|
1059
1162
|
const localBridge = BRIDGE_TOOLS.has(params?.name)
|
|
1060
1163
|
const warning = r.ok && !localBridge ? syncConfiguredProfile(REPO, BUS, { ...process.env, AXMAP_AGENT: AGENT }) : ''
|
|
1061
1164
|
return { content: [{ type: 'text', text: r.text + (warning ? '\n' + warning : '') + (r.ok && !localBridge ? unreadBanner(params?.name, r.text) : '') }], isError: !r.ok }
|
|
@@ -1067,6 +1170,16 @@ async function handle(req) {
|
|
|
1067
1170
|
const rl = readline.createInterface({ input: process.stdin })
|
|
1068
1171
|
// Execute tools in order, but keep reading client replies while a tool awaits roots.
|
|
1069
1172
|
let requests = Promise.resolve()
|
|
1173
|
+
let initialized = Promise.resolve(), concurrentViews = 0
|
|
1174
|
+
async function respond(req) {
|
|
1175
|
+
try {
|
|
1176
|
+
const result = await handle(req)
|
|
1177
|
+
if (result === null) send({ jsonrpc: '2.0', id: req.id, error: { code: -32601, message: `지원하지 않는 메서드: ${req.method}` } })
|
|
1178
|
+
else send({ jsonrpc: '2.0', id: req.id, result })
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
send({ jsonrpc: '2.0', id: req.id, error: { code: typeof e.code === 'number' ? e.code : -32603, message: e.message } })
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1070
1183
|
rl.on('line', (line) => {
|
|
1071
1184
|
const s = line.trim()
|
|
1072
1185
|
if (!s) return
|
|
@@ -1083,22 +1196,19 @@ rl.on('line', (line) => {
|
|
|
1083
1196
|
return
|
|
1084
1197
|
}
|
|
1085
1198
|
if (req.id === undefined) {
|
|
1199
|
+
if (req.method === 'notifications/cancelled') { runningPipeline.get(req.params?.requestId)?.(); return }
|
|
1086
1200
|
if (rootsSupported && !process.env.AXMAP_REPO &&
|
|
1087
1201
|
['notifications/initialized', 'notifications/roots/list_changed'].includes(req.method)) refreshRoots()
|
|
1088
1202
|
return
|
|
1089
1203
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
} catch (e) {
|
|
1099
|
-
send({ jsonrpc: '2.0', id: req.id, error: { code: -32603, message: e.message } })
|
|
1100
|
-
}
|
|
1101
|
-
})
|
|
1204
|
+
if ((req.method === 'tools/call' && PIPELINE_TOOLS.has(req.params?.name)) || ['resources/list', 'resources/read'].includes(req.method)) {
|
|
1205
|
+
if (concurrentViews >= 4) { send({ jsonrpc: '2.0', id: req.id, error: { code: -32000, message: '동시 파이프라인 조회 한도입니다. 잠시 후 다시 조회하세요.' } }); return }
|
|
1206
|
+
concurrentViews++
|
|
1207
|
+
void initialized.then(() => respond(req)).finally(() => { concurrentViews-- })
|
|
1208
|
+
return
|
|
1209
|
+
}
|
|
1210
|
+
requests = requests.then(() => respond(req))
|
|
1211
|
+
if (req.method === 'initialize') initialized = requests
|
|
1102
1212
|
})
|
|
1103
1213
|
|
|
1104
1214
|
process.stderr.write(`axMap MCP · 저장소 ${REPO} · 에이전트 ${AGENT} (${ACTOR})\n`)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//name": "🔴 npm 의 'axmap' 은 2022년부터 남이 쓰고 있는 다른 꾸러미다(leozin_ 의 Map 라이브러리, 1.1.2). 그래서 `npx axmap` 은 우리 것이 아니고, 누구에게도 그렇게 안내하면 안 된다. 2026-08-28 에 axmap-cli 로 정했다 — 스코프(@사용자/이름)를 쓰지 않는 이유는 스코프가 npm 계정 이름에 묶여서 나중에 팀이나 조직으로 옮길 때 이름이 통째로 바뀌기 때문이다. 사람이 치는 **명령** 이름은 그대로 axmap 이다 (아래 bin) — 꾸러미 이름과 명령 이름은 달라도 된다.",
|
|
3
3
|
"name": "axmap-cli",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"//private": "🔴 여기 있던 \"private\": true 를 2026-08-28 에 지웠다. 그 줄이 있는 동안 npm publish 는 거부됐다. 되돌리려면 다시 넣으면 되지만, 이미 올라간 버전은 그래도 안 사라진다 — npm 은 같은 번호를 덮어쓰지 못하고 삭제도 72시간 안에만 된다. 즉 이 줄을 되살리는 것은 '앞으로 안 올린다' 는 뜻이지 '올린 것을 없앤다' 는 뜻이 아니다.",
|
|
6
6
|
"//publishConfig": "axmap-cli 는 스코프가 없어서 기본이 이미 공개다. 즉 지금은 없어도 된다. 그래도 남겨 두는 이유: 나중에 @조직/axmap 처럼 스코프를 붙이는 날 이 줄이 없으면 publish 가 '유료 플랜이 필요하다'는 엉뚱한 말로 실패한다.",
|
|
7
7
|
"publishConfig": {
|
|
@@ -37,11 +37,15 @@
|
|
|
37
37
|
"CLAUDE.md"
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
|
-
"test": "node tools/run-tests.mjs",
|
|
40
|
+
"test": "npm run build:check && node tools/run-tests.mjs",
|
|
41
41
|
"setup": "node tools/setup.mjs",
|
|
42
42
|
"demo": "bash demo/run-demo.sh",
|
|
43
43
|
"demo:compare": "bash demo/conflict-vs-parser.sh",
|
|
44
|
-
"demo:chaos": "bash demo/chaos.sh"
|
|
44
|
+
"demo:chaos": "bash demo/chaos.sh",
|
|
45
|
+
"build": "node tools/build.mjs",
|
|
46
|
+
"build:check": "node tools/build.mjs --check",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"prepack": "npm run build"
|
|
45
49
|
},
|
|
46
50
|
"engines": {
|
|
47
51
|
"node": ">=20"
|
|
@@ -52,5 +56,9 @@
|
|
|
52
56
|
},
|
|
53
57
|
"dependencies": {
|
|
54
58
|
"node-pty": "npm:@lydell/node-pty@1.1.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "20.19.43",
|
|
62
|
+
"typescript": "7.0.2"
|
|
55
63
|
}
|
|
56
64
|
}
|
package/src/agentcli.mjs
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
+
// Generated from src/agentcli.mts. Edit the .mts source and run npm run build.
|
|
1
2
|
/** AI CLI executable discovery shared by setup and MCP registration. */
|
|
2
|
-
import { spawnSync } from 'node:child_process'
|
|
3
|
-
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
4
|
export const AGENTS = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
]
|
|
9
|
-
|
|
5
|
+
{ id: 'claude', name: 'Claude Code', bin: 'claude' },
|
|
6
|
+
{ id: 'agy', name: 'Antigravity', bin: 'agy' },
|
|
7
|
+
{ id: 'codex', name: 'Codex CLI', bin: 'codex' },
|
|
8
|
+
];
|
|
10
9
|
/** Resolve the executable without a shell so paths and arguments stay intact. */
|
|
11
10
|
export function resolveBin(bin) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
try {
|
|
12
|
+
const r = spawnSync(process.platform === 'win32' ? 'where.exe' : 'which', [bin], {
|
|
13
|
+
encoding: 'utf8', windowsHide: true,
|
|
14
|
+
});
|
|
15
|
+
if (r.status !== 0)
|
|
16
|
+
return null;
|
|
17
|
+
return r.stdout.split(/\r?\n/).map(s => s.trim()).find(Boolean) || null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
19
22
|
}
|
package/src/agentcli.mts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** AI CLI executable discovery shared by setup and MCP registration. */
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
|
|
4
|
+
export const AGENTS = [
|
|
5
|
+
{ id: 'claude', name: 'Claude Code', bin: 'claude' },
|
|
6
|
+
{ id: 'agy', name: 'Antigravity', bin: 'agy' },
|
|
7
|
+
{ id: 'codex', name: 'Codex CLI', bin: 'codex' },
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
/** Resolve the executable without a shell so paths and arguments stay intact. */
|
|
11
|
+
export function resolveBin(bin: string): string | null {
|
|
12
|
+
try {
|
|
13
|
+
const r = spawnSync(process.platform === 'win32' ? 'where.exe' : 'which', [bin], {
|
|
14
|
+
encoding: 'utf8', windowsHide: true,
|
|
15
|
+
})
|
|
16
|
+
if (r.status !== 0) return null
|
|
17
|
+
return r.stdout.split(/\r?\n/).map(s => s.trim()).find(Boolean) || null
|
|
18
|
+
} catch { return null }
|
|
19
|
+
}
|