@lnar/cli 0.0.1-dev.5da411d → 0.0.1-dev.68b5bd5
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 +16 -54
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +84 -1
- package/dist/commands/daemon.js.map +1 -1
- package/dist/service/install.js +72 -13
- package/dist/service/install.js.map +1 -1
- package/dist/sse-client.d.ts +72 -0
- package/dist/sse-client.js +174 -0
- package/dist/sse-client.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,29 +4,27 @@ Lnar CLI that scans your AI agent configuration files (Claude / Claude Code, Cod
|
|
|
4
4
|
|
|
5
5
|
Read-only: the CLI only reads agent config files. Environment variable **keys** are recorded, but their **values are never sent** to the server.
|
|
6
6
|
|
|
7
|
-
## Install
|
|
7
|
+
## Install
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
# 1. Install the CLI globally
|
|
11
10
|
npm install -g @lnar/cli
|
|
12
|
-
|
|
13
|
-
# 2. Sign in + start the always-on background service (one command)
|
|
14
11
|
lnar up
|
|
15
12
|
```
|
|
16
13
|
|
|
17
|
-
`lnar up`
|
|
14
|
+
`lnar up` does two things in one command:
|
|
18
15
|
|
|
19
|
-
- If you are not
|
|
20
|
-
-
|
|
16
|
+
- If you are not authenticated, runs the OAuth device-code flow (opens a URL + shows a short code).
|
|
17
|
+
- Registers a background service that auto-starts on login / boot and re-launches if it crashes:
|
|
21
18
|
- macOS → launchd LaunchAgent (`~/Library/LaunchAgents/ai.lnar.daemon.plist`)
|
|
22
19
|
- Linux → systemd `--user` unit (`~/.config/systemd/user/lnar-daemon.service`)
|
|
23
20
|
- Windows → Task Scheduler (`LnarDaemon`, logon trigger + auto-restart)
|
|
24
21
|
|
|
25
22
|
The service syncs the latest MCP-server list with lnar every 30s and applies any changes queued from the dashboard (e.g. "Add MCP server", "Disconnect").
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
## Commands
|
|
28
25
|
|
|
29
26
|
```sh
|
|
27
|
+
lnar up # sign in + start the background service
|
|
30
28
|
lnar status # show authentication + service status
|
|
31
29
|
lnar down # stop and remove the background service
|
|
32
30
|
lnar scan # one-shot: print detected MCP servers, no upload
|
|
@@ -34,14 +32,15 @@ lnar sync # one-shot: scan and upload, then exit
|
|
|
34
32
|
lnar login # re-authenticate (normally `lnar up` is enough)
|
|
35
33
|
```
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
To force a specific endpoint, pass `--api-base-url` or set `LNAR_API_BASE_URL`.
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|---|---|---|
|
|
41
|
-
| Production | `npm install -g @lnar/cli && lnar up` | `https://api.lnar.ai` |
|
|
42
|
-
| Develop (pre-release verification) | `npm install -g @lnar/cli@dev && lnar up` | `https://api-dev.lnar.ai` |
|
|
37
|
+
## Uninstall
|
|
43
38
|
|
|
44
|
-
|
|
39
|
+
```sh
|
|
40
|
+
lnar down # stop and unregister the background service
|
|
41
|
+
npm uninstall -g @lnar/cli # remove the CLI binary
|
|
42
|
+
rm -rf ~/.config/lnar # (optional) remove cached credentials
|
|
43
|
+
```
|
|
45
44
|
|
|
46
45
|
## Supported agents
|
|
47
46
|
|
|
@@ -53,44 +52,7 @@ The `@dev` tag is published from the `develop` branch and is intended for verify
|
|
|
53
52
|
| Gemini CLI | implemented | `~/.gemini/settings.json`, `.gemini/settings.json` (project) |
|
|
54
53
|
| ChatGPT | placeholder | `~/Library/Application Support/com.openai.chat` (existence only — connector data is encrypted) |
|
|
55
54
|
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
```sh
|
|
59
|
-
pnpm install
|
|
60
|
-
pnpm dev scan # run from source against your default API
|
|
61
|
-
pnpm test # run vitest
|
|
62
|
-
pnpm build # compile to dist/
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Release workflow
|
|
66
|
-
|
|
67
|
-
公開戦略は **canary + manual prod** (Next.js / Claude Code 系):
|
|
68
|
-
|
|
69
|
-
| トリガー | dist-tag | デフォルト API |
|
|
70
|
-
|---|---|---|
|
|
71
|
-
| `develop` への push | `@dev` (canary) | `api-dev.lnar.ai` |
|
|
72
|
-
| `cli-v*` の git tag push | `@latest` (本番) | `api.lnar.ai` |
|
|
73
|
-
| GitHub Actions の `workflow_dispatch` (手動) | 任意 (dev / latest / beta / rc) | tag に応じる |
|
|
74
|
-
|
|
75
|
-
### dev (canary) リリース
|
|
76
|
-
|
|
77
|
-
develop ブランチに変更を merge するだけ。CI が自動で `0.0.1-dev.abc1234` のような
|
|
78
|
-
SHA suffix 付きで `@dev` タグに publish する。
|
|
79
|
-
|
|
80
|
-
### 本番リリース
|
|
81
|
-
|
|
82
|
-
```sh
|
|
83
|
-
git checkout main
|
|
84
|
-
git merge develop # 本番化したい develop の内容を取り込む
|
|
85
|
-
cd cli
|
|
86
|
-
npm version minor # 0.0.1 → 0.1.0、自動で commit + tag (v0.1.0) が作られる
|
|
87
|
-
# ※ ただし tag 名は CI 側で "cli-v*" を期待するので
|
|
88
|
-
# git tag -a cli-v0.1.0 -m "Release 0.1.0" の方が安全
|
|
89
|
-
git push --follow-tags
|
|
90
|
-
# → cli-v0.1.0 tag が CI を発火し、@latest で publish
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### 緊急時の手動公開
|
|
55
|
+
## Contributing
|
|
94
56
|
|
|
95
|
-
|
|
96
|
-
|
|
57
|
+
For development setup (pre-release `@dev` verification, local source
|
|
58
|
+
workflow, release process), see the [lnar repository](https://github.com/NexaScience/lnar).
|
package/dist/commands/daemon.js
CHANGED
|
@@ -4,6 +4,7 @@ import { DEFAULT_CLIENT_ID, loadConfig, saveConfig } from '../config.js';
|
|
|
4
4
|
import { OAuthError, refreshAccessToken } from '../oauth-client.js';
|
|
5
5
|
import { listPendingForHost, reportApplied, reportFailed, } from '../pending-client.js';
|
|
6
6
|
import { runAllScanners } from '../scanners/index.js';
|
|
7
|
+
import { runSseClient, SseFatalError } from '../sse-client.js';
|
|
7
8
|
import { getWriter } from '../writers/registry.js';
|
|
8
9
|
const DEFAULT_INTERVAL_SECONDS = 30;
|
|
9
10
|
const ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 60;
|
|
@@ -112,6 +113,61 @@ const runOnce = async (config) => {
|
|
|
112
113
|
// 3. 反映後に再スキャン送信して dashboard の表示も最新化
|
|
113
114
|
await runScanAndSync(config);
|
|
114
115
|
};
|
|
116
|
+
/**
|
|
117
|
+
* runOnce が重複実行されないよう mutex を取る。SSE 通知と polling が同時に発火
|
|
118
|
+
* したとき、両方が runOnce を呼んで pending を二重処理するのを防ぐ。pending が
|
|
119
|
+
* あれば即時実行、すでに走っていれば 1 回だけ追い焚き予約 (coalesce)。
|
|
120
|
+
*/
|
|
121
|
+
const createRunOnceMutex = () => {
|
|
122
|
+
let inFlight = null;
|
|
123
|
+
let queued = false;
|
|
124
|
+
const trigger = (config) => {
|
|
125
|
+
if (inFlight) {
|
|
126
|
+
// すでに走っているなら次回追い焚き予約 (複数回呼ばれても 1 回に集約)。
|
|
127
|
+
queued = true;
|
|
128
|
+
return inFlight;
|
|
129
|
+
}
|
|
130
|
+
const run = async () => {
|
|
131
|
+
try {
|
|
132
|
+
await runOnce(config);
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
if (queued) {
|
|
136
|
+
queued = false;
|
|
137
|
+
// 1 回だけ追い焚き。caller の最新 trigger に応答する。
|
|
138
|
+
inFlight = run();
|
|
139
|
+
await inFlight;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
inFlight = run().finally(() => {
|
|
144
|
+
inFlight = null;
|
|
145
|
+
});
|
|
146
|
+
return inFlight;
|
|
147
|
+
};
|
|
148
|
+
return { trigger };
|
|
149
|
+
};
|
|
150
|
+
const startSseListener = (config, host, trigger, signal) => {
|
|
151
|
+
const url = new URL('/v1/monitoring/daemon/events', config.apiBaseUrl);
|
|
152
|
+
url.searchParams.set('hostname', host);
|
|
153
|
+
process.stdout.write(`lnar daemon: SSE connecting to ${url.toString()}\n`);
|
|
154
|
+
return runSseClient({
|
|
155
|
+
url: url.toString(),
|
|
156
|
+
token: bearerFor(config),
|
|
157
|
+
signal,
|
|
158
|
+
onOpen: async () => {
|
|
159
|
+
// 接続 / 再接続のたびに 1 回 fetch (取りこぼし防止)
|
|
160
|
+
process.stdout.write('lnar daemon: SSE connected\n');
|
|
161
|
+
await trigger(config);
|
|
162
|
+
},
|
|
163
|
+
onEvent: async (event) => {
|
|
164
|
+
if (event.type === 'pending_created') {
|
|
165
|
+
process.stdout.write('lnar daemon: SSE event → fetching pending\n');
|
|
166
|
+
await trigger(config);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
};
|
|
115
171
|
export const runDaemon = async (options = {}) => {
|
|
116
172
|
const initial = await loadConfig();
|
|
117
173
|
if (initial == null) {
|
|
@@ -119,13 +175,36 @@ export const runDaemon = async (options = {}) => {
|
|
|
119
175
|
process.exit(1);
|
|
120
176
|
}
|
|
121
177
|
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1000;
|
|
178
|
+
const enableSse = options.enableSse ?? !options.once;
|
|
179
|
+
const host = hostname();
|
|
180
|
+
const { trigger } = createRunOnceMutex();
|
|
122
181
|
let running = true;
|
|
182
|
+
const sseAbort = new AbortController();
|
|
123
183
|
const stop = () => {
|
|
124
184
|
running = false;
|
|
185
|
+
sseAbort.abort();
|
|
125
186
|
process.stdout.write('\nlnar daemon: shutting down…\n');
|
|
126
187
|
};
|
|
127
188
|
process.on('SIGINT', stop);
|
|
128
189
|
process.on('SIGTERM', stop);
|
|
190
|
+
// SSE listener は background で常時走らせる。fatal error (401 等) は
|
|
191
|
+
// catch して polling loop 側の token refresh に任せる。
|
|
192
|
+
// Box にしないと TS が closure 内の代入をフローに反映できず never 扱いになる。
|
|
193
|
+
const sseTaskRef = { current: null };
|
|
194
|
+
const startSse = (config) => {
|
|
195
|
+
if (!enableSse)
|
|
196
|
+
return;
|
|
197
|
+
sseTaskRef.current = startSseListener(config, host, trigger, sseAbort.signal).catch((err) => {
|
|
198
|
+
if (err instanceof SseFatalError) {
|
|
199
|
+
process.stderr.write(`lnar daemon: SSE fatal (${err.status}); falling back to polling only\n`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
203
|
+
process.stderr.write(`lnar daemon: SSE listener stopped: ${message}\n`);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
startSse(initial);
|
|
129
208
|
while (running) {
|
|
130
209
|
let config;
|
|
131
210
|
try {
|
|
@@ -140,7 +219,7 @@ export const runDaemon = async (options = {}) => {
|
|
|
140
219
|
continue;
|
|
141
220
|
}
|
|
142
221
|
try {
|
|
143
|
-
await
|
|
222
|
+
await trigger(config);
|
|
144
223
|
}
|
|
145
224
|
catch (err) {
|
|
146
225
|
if (err instanceof ApiError && err.status === 401) {
|
|
@@ -154,5 +233,9 @@ export const runDaemon = async (options = {}) => {
|
|
|
154
233
|
break;
|
|
155
234
|
await sleep(interval);
|
|
156
235
|
}
|
|
236
|
+
if (sseTaskRef.current) {
|
|
237
|
+
sseAbort.abort();
|
|
238
|
+
await sseTaskRef.current.catch(() => undefined);
|
|
239
|
+
}
|
|
157
240
|
};
|
|
158
241
|
//# sourceMappingURL=daemon.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/commands/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/commands/daemon.ts"],"names":[],"mappings":"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"}
|
package/dist/service/install.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* いずれも管理者権限不要 (per-user スコープ) でインストールできる構成。
|
|
11
11
|
*/
|
|
12
12
|
import { exec } from 'node:child_process';
|
|
13
|
+
import { realpathSync } from 'node:fs';
|
|
13
14
|
import { mkdir, mkdtemp, rm, unlink, writeFile } from 'node:fs/promises';
|
|
14
15
|
import { homedir, platform, tmpdir, userInfo } from 'node:os';
|
|
15
16
|
import { dirname, join } from 'node:path';
|
|
@@ -30,7 +31,27 @@ async function runIgnoring(cmd) {
|
|
|
30
31
|
}
|
|
31
32
|
function detectTarget() {
|
|
32
33
|
const nodePath = process.execPath;
|
|
33
|
-
const
|
|
34
|
+
const rawScriptPath = process.argv[1] ?? '';
|
|
35
|
+
// ----------------------------------------------------------------------
|
|
36
|
+
// scriptPath は symlink を解決した実体パスにする。
|
|
37
|
+
// `npm install -g @lnar/cli` 後の `lnar` は `/opt/homebrew/bin/lnar` 等の
|
|
38
|
+
// symlink で、これをそのまま plist の ProgramArguments に入れると macOS
|
|
39
|
+
// launchd が bootstrap 時に EIO ("Input/output error") を返すことがある
|
|
40
|
+
// (Homebrew bin symlink + launchd の既知の癖)。さらに symlink を経由する
|
|
41
|
+
// と、後で symlink が張り替わったり消えたときに plist が壊れるリスクも
|
|
42
|
+
// 残る。realpath で解決した絶対パス
|
|
43
|
+
// (`.../lib/node_modules/@lnar/cli/dist/cli.js`) を plist に書くことで、
|
|
44
|
+
// bootstrap の安定性とパスの永続性の両方を確保する。
|
|
45
|
+
// ----------------------------------------------------------------------
|
|
46
|
+
let scriptPath = rawScriptPath;
|
|
47
|
+
try {
|
|
48
|
+
if (rawScriptPath) {
|
|
49
|
+
scriptPath = realpathSync(rawScriptPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// 解決失敗 (パスが存在しない等) は raw を使う best-effort
|
|
54
|
+
}
|
|
34
55
|
let warning = null;
|
|
35
56
|
if (scriptPath.includes('/_npx/') || scriptPath.includes('\\_npx\\')) {
|
|
36
57
|
warning =
|
|
@@ -59,29 +80,67 @@ async function isMacOsServiceLoaded() {
|
|
|
59
80
|
return false;
|
|
60
81
|
}
|
|
61
82
|
}
|
|
83
|
+
const LAUNCHCTL_EIO_PATTERN = /Input\/output error|errno 5\b|:\s*5:/i;
|
|
84
|
+
async function bootoutBothDomains(uid) {
|
|
85
|
+
// gui ドメインと user ドメインの両方を best-effort で bootout する。
|
|
86
|
+
// `launchctl print gui/uid/<label>` で見えなくても launchd 内部に過去の
|
|
87
|
+
// 別パス bootstrap 由来の "torn-down" レコードが残っていることがあり、
|
|
88
|
+
// この状態で新規 bootstrap すると EIO になる。両ドメインを叩いて
|
|
89
|
+
// launchd 側のキャッシュを確実にクリアする。
|
|
90
|
+
await runIgnoring(`launchctl bootout gui/${uid}/${SERVICE_LABEL}`);
|
|
91
|
+
await runIgnoring(`launchctl bootout user/${uid}/${SERVICE_LABEL}`);
|
|
92
|
+
}
|
|
93
|
+
async function bootstrapMacOsWithRetry(uid, plistPath) {
|
|
94
|
+
const bootstrapCmd = `launchctl bootstrap gui/${uid} ${plistPath}`;
|
|
95
|
+
const maxAttempts = 3;
|
|
96
|
+
let lastError;
|
|
97
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
98
|
+
try {
|
|
99
|
+
await run(bootstrapCmd);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
lastError = err;
|
|
104
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
105
|
+
const isEio = LAUNCHCTL_EIO_PATTERN.test(message);
|
|
106
|
+
if (!isEio || attempt === maxAttempts) {
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
process.stderr.write(`launchctl bootstrap returned EIO; retrying (${attempt}/${maxAttempts - 1})…\n`);
|
|
110
|
+
await bootoutBothDomains(uid);
|
|
111
|
+
await new Promise((r) => setTimeout(r, 500 * attempt));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// 到達不能だが TS の型チェックのため
|
|
115
|
+
throw lastError instanceof Error ? lastError : new Error('launchctl bootstrap failed');
|
|
116
|
+
}
|
|
62
117
|
async function installMacOs(target) {
|
|
63
118
|
const plistPath = macOsPlistPath();
|
|
64
119
|
await mkdir(dirname(plistPath), { recursive: true });
|
|
65
120
|
await writeFile(plistPath, macOsPlist(target));
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
121
|
+
const uid = userInfo().uid;
|
|
122
|
+
// 既存サービスは print 結果に関わらず両ドメインを bootout する。
|
|
123
|
+
// (詳細は bootoutBothDomains のコメント参照)
|
|
124
|
+
await bootoutBothDomains(uid);
|
|
125
|
+
// bootout は非同期で完了する。実際に unload されるまで最大 ~3 秒ポーリングする。
|
|
126
|
+
// `lnar up` の冪等性に必要。
|
|
127
|
+
for (let i = 0; i < 30; i++) {
|
|
128
|
+
if (!(await isMacOsServiceLoaded()))
|
|
129
|
+
break;
|
|
130
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
76
131
|
}
|
|
77
|
-
|
|
132
|
+
// bootstrap は launchd の内部状態キャッシュにより最初の試行が EIO になる
|
|
133
|
+
// ことがある。EIO を検出して再 bootout + 短い backoff で数回リトライする。
|
|
134
|
+
await bootstrapMacOsWithRetry(uid, plistPath);
|
|
78
135
|
await run(`launchctl enable ${macOsServiceTarget()}`);
|
|
79
136
|
process.stdout.write(`Installed: ${plistPath}\n`);
|
|
80
137
|
process.stdout.write(`Logs: /tmp/lnar-daemon.log /tmp/lnar-daemon.err.log\n`);
|
|
81
138
|
}
|
|
82
139
|
async function uninstallMacOs() {
|
|
83
140
|
const plistPath = macOsPlistPath();
|
|
84
|
-
|
|
141
|
+
// install 側と同様、両ドメインの torn-down レコードまで掃除する。
|
|
142
|
+
// これを怠ると `lnar down` → `lnar up` 再実行時に EIO を踏む。
|
|
143
|
+
await bootoutBothDomains(userInfo().uid);
|
|
85
144
|
try {
|
|
86
145
|
await unlink(plistPath);
|
|
87
146
|
process.stdout.write(`Removed: ${plistPath}\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/service/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAoB,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,aAAa,EAEb,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAElC,KAAK,UAAU,GAAG,CAAC,GAAW,EAAE,UAAuB,EAAE;IACvD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACjD,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACxE,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;AACH,CAAC;AAED,SAAS,YAAY;IACnB,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,MAAM,
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/service/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal SSE client for daemon → API event channel.
|
|
3
|
+
*
|
|
4
|
+
* Node 20 にはネイティブの `EventSource` グローバルが無いため、`fetch` の
|
|
5
|
+
* `ReadableStream<Uint8Array>` を自前で SSE フレームにパースする。
|
|
6
|
+
* 依存を増やさず、必要な機能 (event name + data) だけを抜き出す最小実装。
|
|
7
|
+
*
|
|
8
|
+
* 重要な設計判断:
|
|
9
|
+
* - **canonical fetch パターン**: SSE はベストエフォート通知レイヤー。
|
|
10
|
+
* payload に頼らず、event を受けたら呼び出し元が必ず再 fetch する
|
|
11
|
+
* 責務を持つ。これにより exactly-once / 順序問題から解放される。
|
|
12
|
+
* - **exponential backoff**: 1s → 2s → 4s → ... cap 30s。401 はリトライ不可。
|
|
13
|
+
* - **再接続時の取りこぼし防止**: caller は connect-callback で必ず 1 回
|
|
14
|
+
* fetch を回す前提。SSE が落ちている間に発生したイベントは fallback
|
|
15
|
+
* poll が拾うので、SSE 側で catch-up を実装する必要は無い。
|
|
16
|
+
*/
|
|
17
|
+
import type { ApiError } from './api-client.js';
|
|
18
|
+
interface SseEvent {
|
|
19
|
+
/** SSE の `event:` フィールド。デフォルトは "message"。 */
|
|
20
|
+
type: string;
|
|
21
|
+
/** SSE の `data:` フィールドの結合結果 (連続行は `\n` で join)。 */
|
|
22
|
+
data: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SseClientOptions {
|
|
25
|
+
/** SSE エンドポイントの完全 URL (例: https://api.lnar.ai/v1/monitoring/daemon/events?hostname=...) */
|
|
26
|
+
url: string;
|
|
27
|
+
/** OAuth bearer token。 */
|
|
28
|
+
token: string;
|
|
29
|
+
/** イベント到着時に呼ばれる。例外は無視されない (caller で握る)。 */
|
|
30
|
+
onEvent: (event: SseEvent) => Promise<void> | void;
|
|
31
|
+
/** SSE がオープンしたタイミング (初回 + 各再接続時)。catch-up fetch を仕込むのに使う。 */
|
|
32
|
+
onOpen?: () => Promise<void> | void;
|
|
33
|
+
/** 致命的に止めるシグナル。AbortSignal を渡せば caller が close 可能。 */
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
/** 初期 backoff (ms)。テスト時に短縮できるよう注入可能。default 1000。 */
|
|
36
|
+
initialBackoffMs?: number;
|
|
37
|
+
/** 最大 backoff (ms)。default 30000。 */
|
|
38
|
+
maxBackoffMs?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 1 行ずつ SSE フィールドを読み取って `SseEvent` を組み立てるパーサ。
|
|
42
|
+
* 仕様: https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
43
|
+
*
|
|
44
|
+
* 必要な部分だけ実装: event / data / 空行で flush。id / retry はサーバ側で
|
|
45
|
+
* 使っていないので無視。
|
|
46
|
+
*/
|
|
47
|
+
declare class SseFrameParser {
|
|
48
|
+
private buffer;
|
|
49
|
+
private currentEvent;
|
|
50
|
+
private currentData;
|
|
51
|
+
/** 受信したテキスト断片をフィードし、確定した event だけを返す。 */
|
|
52
|
+
feed(chunk: string): SseEvent[];
|
|
53
|
+
private flush;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 終端不能エラー (401 など) を表す例外。caller がリトライをやめる判断に使う。
|
|
57
|
+
*/
|
|
58
|
+
export declare class SseFatalError extends Error {
|
|
59
|
+
status: number;
|
|
60
|
+
constructor(message: string, status: number);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* SSE エンドポイントに常時接続し、event を caller に流し続ける。
|
|
64
|
+
*
|
|
65
|
+
* 戻り値の Promise は signal が abort されたとき、もしくは SseFatalError が
|
|
66
|
+
* 発生したときに解決する。通常運用では永久ループのため await は daemon の
|
|
67
|
+
* シャットダウンと組み合わせて使う。
|
|
68
|
+
*/
|
|
69
|
+
export declare const runSseClient: (options: SseClientOptions) => Promise<void>;
|
|
70
|
+
export declare const _SseFrameParserForTest: typeof SseFrameParser;
|
|
71
|
+
export type { SseEvent };
|
|
72
|
+
export type { ApiError };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal SSE client for daemon → API event channel.
|
|
3
|
+
*
|
|
4
|
+
* Node 20 にはネイティブの `EventSource` グローバルが無いため、`fetch` の
|
|
5
|
+
* `ReadableStream<Uint8Array>` を自前で SSE フレームにパースする。
|
|
6
|
+
* 依存を増やさず、必要な機能 (event name + data) だけを抜き出す最小実装。
|
|
7
|
+
*
|
|
8
|
+
* 重要な設計判断:
|
|
9
|
+
* - **canonical fetch パターン**: SSE はベストエフォート通知レイヤー。
|
|
10
|
+
* payload に頼らず、event を受けたら呼び出し元が必ず再 fetch する
|
|
11
|
+
* 責務を持つ。これにより exactly-once / 順序問題から解放される。
|
|
12
|
+
* - **exponential backoff**: 1s → 2s → 4s → ... cap 30s。401 はリトライ不可。
|
|
13
|
+
* - **再接続時の取りこぼし防止**: caller は connect-callback で必ず 1 回
|
|
14
|
+
* fetch を回す前提。SSE が落ちている間に発生したイベントは fallback
|
|
15
|
+
* poll が拾うので、SSE 側で catch-up を実装する必要は無い。
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_INITIAL_BACKOFF_MS = 1000;
|
|
18
|
+
const DEFAULT_MAX_BACKOFF_MS = 30_000;
|
|
19
|
+
/**
|
|
20
|
+
* 1 行ずつ SSE フィールドを読み取って `SseEvent` を組み立てるパーサ。
|
|
21
|
+
* 仕様: https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
22
|
+
*
|
|
23
|
+
* 必要な部分だけ実装: event / data / 空行で flush。id / retry はサーバ側で
|
|
24
|
+
* 使っていないので無視。
|
|
25
|
+
*/
|
|
26
|
+
class SseFrameParser {
|
|
27
|
+
buffer = '';
|
|
28
|
+
currentEvent = null;
|
|
29
|
+
currentData = [];
|
|
30
|
+
/** 受信したテキスト断片をフィードし、確定した event だけを返す。 */
|
|
31
|
+
feed(chunk) {
|
|
32
|
+
this.buffer += chunk;
|
|
33
|
+
const events = [];
|
|
34
|
+
let lineEnd;
|
|
35
|
+
// CR / LF / CRLF いずれにも対応するため正規表現で分割。
|
|
36
|
+
while ((lineEnd = this.buffer.search(/\r\n|\r|\n/)) !== -1) {
|
|
37
|
+
const line = this.buffer.slice(0, lineEnd);
|
|
38
|
+
const sepLen = this.buffer.slice(lineEnd, lineEnd + 2) === '\r\n' ? 2 : 1;
|
|
39
|
+
this.buffer = this.buffer.slice(lineEnd + sepLen);
|
|
40
|
+
if (line === '') {
|
|
41
|
+
// 空行 = event 区切り
|
|
42
|
+
const event = this.flush();
|
|
43
|
+
if (event)
|
|
44
|
+
events.push(event);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// コメント行 (':' で始まる) は heartbeat 用なので無視。
|
|
48
|
+
if (line.startsWith(':'))
|
|
49
|
+
continue;
|
|
50
|
+
const colonIdx = line.indexOf(':');
|
|
51
|
+
const field = colonIdx === -1 ? line : line.slice(0, colonIdx);
|
|
52
|
+
// SSE 仕様: `:` の直後の単一スペースは省く。
|
|
53
|
+
let value = colonIdx === -1 ? '' : line.slice(colonIdx + 1);
|
|
54
|
+
if (value.startsWith(' '))
|
|
55
|
+
value = value.slice(1);
|
|
56
|
+
if (field === 'event') {
|
|
57
|
+
this.currentEvent = value;
|
|
58
|
+
}
|
|
59
|
+
else if (field === 'data') {
|
|
60
|
+
this.currentData.push(value);
|
|
61
|
+
}
|
|
62
|
+
// id / retry はサポート外。
|
|
63
|
+
}
|
|
64
|
+
return events;
|
|
65
|
+
}
|
|
66
|
+
flush() {
|
|
67
|
+
if (this.currentData.length === 0 && this.currentEvent == null)
|
|
68
|
+
return null;
|
|
69
|
+
const event = {
|
|
70
|
+
type: this.currentEvent ?? 'message',
|
|
71
|
+
data: this.currentData.join('\n'),
|
|
72
|
+
};
|
|
73
|
+
this.currentEvent = null;
|
|
74
|
+
this.currentData = [];
|
|
75
|
+
return event;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 終端不能エラー (401 など) を表す例外。caller がリトライをやめる判断に使う。
|
|
80
|
+
*/
|
|
81
|
+
export class SseFatalError extends Error {
|
|
82
|
+
status;
|
|
83
|
+
constructor(message, status) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.status = status;
|
|
86
|
+
this.name = 'SseFatalError';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* SSE エンドポイントに常時接続し、event を caller に流し続ける。
|
|
91
|
+
*
|
|
92
|
+
* 戻り値の Promise は signal が abort されたとき、もしくは SseFatalError が
|
|
93
|
+
* 発生したときに解決する。通常運用では永久ループのため await は daemon の
|
|
94
|
+
* シャットダウンと組み合わせて使う。
|
|
95
|
+
*/
|
|
96
|
+
export const runSseClient = async (options) => {
|
|
97
|
+
const initialBackoff = options.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS;
|
|
98
|
+
const maxBackoff = options.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
|
|
99
|
+
let backoff = initialBackoff;
|
|
100
|
+
while (!options.signal?.aborted) {
|
|
101
|
+
try {
|
|
102
|
+
await connectAndStream(options);
|
|
103
|
+
// 正常クローズ (サーバ側切断 / EOF) は即再接続する。
|
|
104
|
+
backoff = initialBackoff;
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
if (options.signal?.aborted)
|
|
108
|
+
return;
|
|
109
|
+
if (err instanceof SseFatalError && err.status === 401) {
|
|
110
|
+
// トークン期限切れなどは上位 daemon ループで refresh されるため
|
|
111
|
+
// ここでは終了して上位に任せる。
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
115
|
+
process.stderr.write(`lnar daemon: SSE disconnected (${message}); retry in ${Math.round(backoff / 1000)}s\n`);
|
|
116
|
+
await sleep(backoff, options.signal);
|
|
117
|
+
backoff = Math.min(backoff * 2, maxBackoff);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const sleep = (ms, signal) => new Promise((resolve) => {
|
|
122
|
+
if (signal?.aborted)
|
|
123
|
+
return resolve();
|
|
124
|
+
const timer = setTimeout(() => {
|
|
125
|
+
signal?.removeEventListener('abort', onAbort);
|
|
126
|
+
resolve();
|
|
127
|
+
}, ms);
|
|
128
|
+
const onAbort = () => {
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
resolve();
|
|
131
|
+
};
|
|
132
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
133
|
+
});
|
|
134
|
+
const connectAndStream = async (options) => {
|
|
135
|
+
const response = await fetch(options.url, {
|
|
136
|
+
method: 'GET',
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `Bearer ${options.token}`,
|
|
139
|
+
Accept: 'text/event-stream',
|
|
140
|
+
},
|
|
141
|
+
signal: options.signal,
|
|
142
|
+
});
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
const body = await response.text().catch(() => '');
|
|
145
|
+
if (response.status === 401 || response.status === 403) {
|
|
146
|
+
throw new SseFatalError(`SSE auth failed: ${response.status} ${body}`, response.status);
|
|
147
|
+
}
|
|
148
|
+
throw new Error(`SSE HTTP ${response.status}: ${body}`);
|
|
149
|
+
}
|
|
150
|
+
if (response.body == null) {
|
|
151
|
+
throw new Error('SSE response has no body');
|
|
152
|
+
}
|
|
153
|
+
if (options.onOpen) {
|
|
154
|
+
// open callback は parser 起動 *前* に走らせる。これにより SSE 接続中に
|
|
155
|
+
// 来たイベントよりも前に必ず 1 回の canonical fetch が動く。
|
|
156
|
+
await options.onOpen();
|
|
157
|
+
}
|
|
158
|
+
const decoder = new TextDecoder('utf-8');
|
|
159
|
+
const parser = new SseFrameParser();
|
|
160
|
+
const reader = response.body.getReader();
|
|
161
|
+
while (true) {
|
|
162
|
+
const { value, done } = await reader.read();
|
|
163
|
+
if (done)
|
|
164
|
+
return;
|
|
165
|
+
const text = decoder.decode(value, { stream: true });
|
|
166
|
+
const events = parser.feed(text);
|
|
167
|
+
for (const event of events) {
|
|
168
|
+
await options.onEvent(event);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
// テスト用: パーサ単体をエクスポートする (private クラスに匹敵)。
|
|
173
|
+
export const _SseFrameParserForTest = SseFrameParser;
|
|
174
|
+
//# sourceMappingURL=sse-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-client.js","sourceRoot":"","sources":["../src/sse-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA4BH,MAAM,0BAA0B,GAAG,IAAI,CAAA;AACvC,MAAM,sBAAsB,GAAG,MAAM,CAAA;AAErC;;;;;;GAMG;AACH,MAAM,cAAc;IACV,MAAM,GAAG,EAAE,CAAA;IACX,YAAY,GAAkB,IAAI,CAAA;IAClC,WAAW,GAAa,EAAE,CAAA;IAElC,yCAAyC;IACzC,IAAI,CAAC,KAAa;QAChB,IAAI,CAAC,MAAM,IAAI,KAAK,CAAA;QACpB,MAAM,MAAM,GAAe,EAAE,CAAA;QAC7B,IAAI,OAAe,CAAA;QACnB,qCAAqC;QACrC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;YAC1C,MAAM,MAAM,GACV,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAC5D,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,CAAA;YAEjD,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;gBAChB,iBAAiB;gBACjB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAA;gBAC1B,IAAI,KAAK;oBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAC7B,SAAQ;YACV,CAAC;YACD,uCAAuC;YACvC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAQ;YAElC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAClC,MAAM,KAAK,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;YAC9D,6BAA6B;YAC7B,IAAI,KAAK,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAA;YAC3D,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YAEjD,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;gBACtB,IAAI,CAAC,YAAY,GAAG,KAAK,CAAA;YAC3B,CAAC;iBAAM,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;gBAC5B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAC9B,CAAC;YACD,qBAAqB;QACvB,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IAEO,KAAK;QACX,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;QAC3E,MAAM,KAAK,GAAa;YACtB,IAAI,EAAE,IAAI,CAAC,YAAY,IAAI,SAAS;YACpC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;SAClC,CAAA;QACD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;QACxB,IAAI,CAAC,WAAW,GAAG,EAAE,CAAA;QACrB,OAAO,KAAK,CAAA;IACd,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAG7B;IAFT,YACE,OAAe,EACR,MAAc;QAErB,KAAK,CAAC,OAAO,CAAC,CAAA;QAFP,WAAM,GAAN,MAAM,CAAQ;QAGrB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;IAC7B,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,OAAyB,EAAiB,EAAE;IAC7E,MAAM,cAAc,GAAG,OAAO,CAAC,gBAAgB,IAAI,0BAA0B,CAAA;IAC7E,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,IAAI,sBAAsB,CAAA;IACjE,IAAI,OAAO,GAAG,cAAc,CAAA;IAE5B,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,gBAAgB,CAAC,OAAO,CAAC,CAAA;YAC/B,iCAAiC;YACjC,OAAO,GAAG,cAAc,CAAA;QAC1B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO;gBAAE,OAAM;YACnC,IAAI,GAAG,YAAY,aAAa,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvD,0CAA0C;gBAC1C,kBAAkB;gBAClB,MAAM,GAAG,CAAA;YACX,CAAC;YACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kCAAkC,OAAO,eAAe,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CACxF,CAAA;YACD,MAAM,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;YACpC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,UAAU,CAAC,CAAA;QAC7C,CAAC;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,MAAoB,EAAiB,EAAE,CAChE,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,OAAO,EAAE,CAAA;IACrC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC7C,OAAO,EAAE,CAAA;IACX,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,YAAY,CAAC,KAAK,CAAC,CAAA;QACnB,OAAO,EAAE,CAAA;IACX,CAAC,CAAA;IACD,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;AAC5D,CAAC,CAAC,CAAA;AAEJ,MAAM,gBAAgB,GAAG,KAAK,EAAE,OAAyB,EAAiB,EAAE;IAC1E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACxC,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,OAAO,CAAC,KAAK,EAAE;YACxC,MAAM,EAAE,mBAAmB;SAC5B;QACD,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAA;IAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;QAClD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvD,MAAM,IAAI,aAAa,CAAC,oBAAoB,QAAQ,CAAC,MAAM,IAAI,IAAI,EAAE,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;QACzF,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,YAAY,QAAQ,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAA;IACzD,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;IAC7C,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,qDAAqD;QACrD,0CAA0C;QAC1C,MAAM,OAAO,CAAC,MAAM,EAAE,CAAA;IACxB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,IAAI,cAAc,EAAE,CAAA;IACnC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAA;IAExC,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;QAC3C,IAAI,IAAI;YAAE,OAAM;QAChB,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;QACpD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC9B,CAAC;IACH,CAAC;AACH,CAAC,CAAA;AAED,yCAAyC;AACzC,MAAM,CAAC,MAAM,sBAAsB,GAAG,cAAc,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lnar/cli",
|
|
3
|
-
"version": "0.0.1-dev.
|
|
3
|
+
"version": "0.0.1-dev.68b5bd5",
|
|
4
4
|
"description": "Lnar CLI — scans local AI agents (Claude / Claude Code, Codex CLI, Cursor, Gemini CLI, ChatGPT) for connected MCP servers and reports them to your lnar account.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|