@lnar/cli 0.0.1-dev.1da013d → 0.0.1-dev.3982ccb
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/api-client.d.ts +19 -0
- package/dist/api-client.js +42 -0
- package/dist/api-client.js.map +1 -1
- package/dist/auth.d.ts +20 -0
- package/dist/auth.js +74 -0
- package/dist/auth.js.map +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +68 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.js +30 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +100 -1
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +19 -9
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/record.d.ts +11 -0
- package/dist/commands/record.js +136 -0
- package/dist/commands/record.js.map +1 -0
- package/dist/commands/scan.js +12 -0
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/sync.js +3 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/up.js +19 -0
- package/dist/commands/up.js.map +1 -1
- package/dist/machine-id.d.ts +2 -0
- package/dist/machine-id.js +48 -0
- package/dist/machine-id.js.map +1 -0
- package/dist/pending-client.d.ts +1 -1
- package/dist/recording/bundle.d.ts +23 -0
- package/dist/recording/bundle.js +41 -0
- package/dist/recording/bundle.js.map +1 -0
- package/dist/recording/capture.d.ts +33 -0
- package/dist/recording/capture.js +173 -0
- package/dist/recording/capture.js.map +1 -0
- package/dist/recording/session.d.ts +27 -0
- package/dist/recording/session.js +81 -0
- package/dist/recording/session.js.map +1 -0
- package/dist/recording/types.d.ts +59 -0
- package/dist/recording/types.js +8 -0
- package/dist/recording/types.js.map +1 -0
- package/dist/run-client.d.ts +19 -0
- package/dist/run-client.js +44 -0
- package/dist/run-client.js.map +1 -0
- package/dist/run-worker.d.ts +25 -0
- package/dist/run-worker.js +85 -0
- package/dist/run-worker.js.map +1 -0
- package/dist/runtime/actions.d.ts +13 -0
- package/dist/runtime/actions.js +107 -0
- package/dist/runtime/actions.js.map +1 -0
- package/dist/runtime/client.d.ts +3 -0
- package/dist/runtime/client.js +85 -0
- package/dist/runtime/client.js.map +1 -0
- package/dist/runtime/login.d.ts +20 -0
- package/dist/runtime/login.js +34 -0
- package/dist/runtime/login.js.map +1 -0
- package/dist/runtime/loop.d.ts +28 -0
- package/dist/runtime/loop.js +68 -0
- package/dist/runtime/loop.js.map +1 -0
- package/dist/runtime/playbook.d.ts +49 -0
- package/dist/runtime/playbook.js +73 -0
- package/dist/runtime/playbook.js.map +1 -0
- package/dist/runtime/runner.d.ts +20 -0
- package/dist/runtime/runner.js +54 -0
- package/dist/runtime/runner.js.map +1 -0
- package/dist/runtime/types.d.ts +69 -0
- package/dist/runtime/types.js +10 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/scanners/claude-code-skills.d.ts +6 -0
- package/dist/scanners/claude-code-skills.js +201 -0
- package/dist/scanners/claude-code-skills.js.map +1 -0
- package/dist/scanners/claude-code.js +10 -3
- package/dist/scanners/claude-code.js.map +1 -1
- package/dist/scanners/index.js +3 -2
- package/dist/scanners/index.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/dist/types.d.ts +43 -3
- package/dist/types.js +34 -2
- package/dist/types.js.map +1 -1
- package/dist/writers/claude-code.js +31 -10
- package/dist/writers/claude-code.js.map +1 -1
- package/dist/writers/registry.js +0 -4
- package/dist/writers/registry.js.map +1 -1
- package/package.json +10 -2
- package/dist/scanners/chatgpt.d.ts +0 -20
- package/dist/scanners/chatgpt.js +0 -49
- package/dist/scanners/chatgpt.js.map +0 -1
- package/dist/writers/chatgpt.d.ts +0 -8
- package/dist/writers/chatgpt.js +0 -12
- package/dist/writers/chatgpt.js.map +0 -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/api-client.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RecordedAction, RecordingManifest } from './recording/types.js';
|
|
1
2
|
import type { ScanResult } from './types.js';
|
|
2
3
|
export type SnapshotResponse = {
|
|
3
4
|
agents: Array<{
|
|
@@ -15,3 +16,21 @@ export declare class ApiError extends Error {
|
|
|
15
16
|
constructor(status: number, body: string);
|
|
16
17
|
}
|
|
17
18
|
export declare const postSnapshot: (baseUrl: string, apiKey: string, scan: ScanResult) => Promise<SnapshotResponse>;
|
|
19
|
+
export type RecordingResponse = {
|
|
20
|
+
id: string;
|
|
21
|
+
status: string;
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
/** 録画メタデータ + 操作トレースを登録し、recording id を得る (status=pending)。 */
|
|
25
|
+
export declare const createRecording: (baseUrl: string, apiKey: string, input: {
|
|
26
|
+
name: string;
|
|
27
|
+
startUrl: string | null;
|
|
28
|
+
purpose?: string | null;
|
|
29
|
+
manifest: RecordingManifest;
|
|
30
|
+
actions: ReadonlyArray<RecordedAction>;
|
|
31
|
+
}) => Promise<RecordingResponse>;
|
|
32
|
+
/**
|
|
33
|
+
* バンドル tar.gz を raw body で PUT する。サーバー側で即解析され、解析済み録画
|
|
34
|
+
* (status=analyzed / playbook 付き) が返る。画像は S3 に保存されない。
|
|
35
|
+
*/
|
|
36
|
+
export declare const uploadRecordingBundle: (baseUrl: string, apiKey: string, recordingId: string, tarPath: string) => Promise<RecordingResponse>;
|
package/dist/api-client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
1
2
|
export class ApiError extends Error {
|
|
2
3
|
status;
|
|
3
4
|
body;
|
|
@@ -17,12 +18,14 @@ export const postSnapshot = async (baseUrl, apiKey, scan) => {
|
|
|
17
18
|
},
|
|
18
19
|
body: JSON.stringify({
|
|
19
20
|
hostname: scan.hostname,
|
|
21
|
+
machineId: scan.machineId,
|
|
20
22
|
scannedAt: scan.scannedAt,
|
|
21
23
|
agents: scan.agents.map((a) => ({
|
|
22
24
|
agentKind: a.agentKind,
|
|
23
25
|
agentVersion: a.agentVersion,
|
|
24
26
|
servers: a.servers,
|
|
25
27
|
plugins: a.plugins ?? [],
|
|
28
|
+
skills: a.skills ?? [],
|
|
26
29
|
})),
|
|
27
30
|
}),
|
|
28
31
|
});
|
|
@@ -32,4 +35,43 @@ export const postSnapshot = async (baseUrl, apiKey, scan) => {
|
|
|
32
35
|
}
|
|
33
36
|
return (await response.json());
|
|
34
37
|
};
|
|
38
|
+
const recordingHeaders = (apiKey) => ({
|
|
39
|
+
Authorization: `Bearer ${apiKey}`,
|
|
40
|
+
});
|
|
41
|
+
/** 録画メタデータ + 操作トレースを登録し、recording id を得る (status=pending)。 */
|
|
42
|
+
export const createRecording = async (baseUrl, apiKey, input) => {
|
|
43
|
+
const url = new URL('/v1/recordings', baseUrl).toString();
|
|
44
|
+
const response = await fetch(url, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { ...recordingHeaders(apiKey), 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
name: input.name,
|
|
49
|
+
start_url: input.startUrl,
|
|
50
|
+
purpose: input.purpose ?? null,
|
|
51
|
+
manifest: input.manifest,
|
|
52
|
+
actions: input.actions,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new ApiError(response.status, await response.text().catch(() => ''));
|
|
57
|
+
}
|
|
58
|
+
return (await response.json());
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* バンドル tar.gz を raw body で PUT する。サーバー側で即解析され、解析済み録画
|
|
62
|
+
* (status=analyzed / playbook 付き) が返る。画像は S3 に保存されない。
|
|
63
|
+
*/
|
|
64
|
+
export const uploadRecordingBundle = async (baseUrl, apiKey, recordingId, tarPath) => {
|
|
65
|
+
const url = new URL(`/v1/recordings/${recordingId}/bundle`, baseUrl).toString();
|
|
66
|
+
const body = await readFile(tarPath);
|
|
67
|
+
const response = await fetch(url, {
|
|
68
|
+
method: 'PUT',
|
|
69
|
+
headers: { ...recordingHeaders(apiKey), 'Content-Type': 'application/gzip' },
|
|
70
|
+
body,
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new ApiError(response.status, await response.text().catch(() => ''));
|
|
74
|
+
}
|
|
75
|
+
return (await response.json());
|
|
76
|
+
};
|
|
35
77
|
//# sourceMappingURL=api-client.js.map
|
package/dist/api-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 認証情報の解決。
|
|
3
|
+
*
|
|
4
|
+
* 保存済み config を読み、必要なら OAuth access token を refresh し、API 呼び出し用の
|
|
5
|
+
* Bearer 値を返す。`sync` コマンドと同じ方針 (期限切れ + refresh 不可なら再ログイン案内)。
|
|
6
|
+
*/
|
|
7
|
+
import { type StoredConfig } from './config.js';
|
|
8
|
+
export declare class NotLoggedInError extends Error {
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
/** API 呼び出し用の Bearer 値を取り出す。 */
|
|
12
|
+
export declare const bearerFor: (config: StoredConfig) => string;
|
|
13
|
+
export interface ResolvedAuth {
|
|
14
|
+
readonly baseUrl: string;
|
|
15
|
+
readonly token: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 認証を解決する。未ログインなら NotLoggedInError を投げる。
|
|
19
|
+
*/
|
|
20
|
+
export declare const resolveAuth: () => Promise<ResolvedAuth>;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 認証情報の解決。
|
|
3
|
+
*
|
|
4
|
+
* 保存済み config を読み、必要なら OAuth access token を refresh し、API 呼び出し用の
|
|
5
|
+
* Bearer 値を返す。`sync` コマンドと同じ方針 (期限切れ + refresh 不可なら再ログイン案内)。
|
|
6
|
+
*/
|
|
7
|
+
import { DEFAULT_CLIENT_ID, loadConfig, saveConfig } from './config.js';
|
|
8
|
+
import { OAuthError, refreshAccessToken } from './oauth-client.js';
|
|
9
|
+
const ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 60;
|
|
10
|
+
export class NotLoggedInError extends Error {
|
|
11
|
+
constructor(message = 'not logged in. Run `lnar login` first.') {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'NotLoggedInError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const isAccessTokenExpired = (config) => {
|
|
17
|
+
if (!config.accessTokenExpiresAt)
|
|
18
|
+
return false;
|
|
19
|
+
const expires = Date.parse(config.accessTokenExpiresAt);
|
|
20
|
+
if (Number.isNaN(expires))
|
|
21
|
+
return true;
|
|
22
|
+
return expires - Date.now() < ACCESS_TOKEN_REFRESH_SKEW_SECONDS * 1000;
|
|
23
|
+
};
|
|
24
|
+
const refreshIfNeeded = async (config) => {
|
|
25
|
+
if (!config.accessToken)
|
|
26
|
+
return config;
|
|
27
|
+
if (!isAccessTokenExpired(config))
|
|
28
|
+
return config;
|
|
29
|
+
if (!config.refreshToken) {
|
|
30
|
+
throw new NotLoggedInError('access token expired and no refresh token. Run `lnar login`.');
|
|
31
|
+
}
|
|
32
|
+
const clientId = config.clientId ?? DEFAULT_CLIENT_ID;
|
|
33
|
+
let token;
|
|
34
|
+
try {
|
|
35
|
+
token = await refreshAccessToken(config.apiBaseUrl, clientId, config.refreshToken);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (err instanceof OAuthError && err.errorCode === 'invalid_grant') {
|
|
39
|
+
throw new NotLoggedInError('refresh token expired or revoked. Run `lnar login`.');
|
|
40
|
+
}
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
const accessTokenExpiresAt = new Date(Date.now() + token.expires_in * 1000).toISOString();
|
|
44
|
+
const next = {
|
|
45
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
46
|
+
accessToken: token.access_token,
|
|
47
|
+
refreshToken: token.refresh_token ?? config.refreshToken,
|
|
48
|
+
accessTokenExpiresAt,
|
|
49
|
+
scopes: token.scope ? token.scope.split(' ') : config.scopes,
|
|
50
|
+
clientId,
|
|
51
|
+
};
|
|
52
|
+
await saveConfig(next);
|
|
53
|
+
return { ...next, savedAt: new Date().toISOString() };
|
|
54
|
+
};
|
|
55
|
+
/** API 呼び出し用の Bearer 値を取り出す。 */
|
|
56
|
+
export const bearerFor = (config) => {
|
|
57
|
+
if (config.accessToken)
|
|
58
|
+
return config.accessToken;
|
|
59
|
+
if (config.apiKey)
|
|
60
|
+
return config.apiKey;
|
|
61
|
+
throw new NotLoggedInError('no credentials available. Run `lnar login`.');
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* 認証を解決する。未ログインなら NotLoggedInError を投げる。
|
|
65
|
+
*/
|
|
66
|
+
export const resolveAuth = async () => {
|
|
67
|
+
const initial = await loadConfig();
|
|
68
|
+
if (initial == null) {
|
|
69
|
+
throw new NotLoggedInError();
|
|
70
|
+
}
|
|
71
|
+
const config = await refreshIfNeeded(initial);
|
|
72
|
+
return { baseUrl: config.apiBaseUrl, token: bearerFor(config) };
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const openBrowser: (url: string) => boolean;
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS のデフォルトブラウザで URL を開く。
|
|
3
|
+
*
|
|
4
|
+
* 依存を増やさず、プラットフォームごとの組み込みコマンドを spawn する。
|
|
5
|
+
* macOS → `open -- <url>`
|
|
6
|
+
* Linux → `xdg-open -- <url>`
|
|
7
|
+
* Windows → `rundll32 url.dll,FileProtocolHandler <url>`
|
|
8
|
+
*
|
|
9
|
+
* セキュリティ:
|
|
10
|
+
* - URL は http / https スキームのみ許可(javascript:, file: 等は拒否)
|
|
11
|
+
* - macOS / Linux では `--` セパレータで先頭が `-` の URL をフラグと誤認させない
|
|
12
|
+
* - Windows は cmd.exe メタ文字解釈を避けるため `rundll32 url.dll,FileProtocolHandler`
|
|
13
|
+
* を使用(cmd /c start 経由は `&` 等で injection 余地が残る)
|
|
14
|
+
*
|
|
15
|
+
* CI / SSH / コンテナ等の "browser を出せない" 環境では子プロセスが失敗するが、
|
|
16
|
+
* 呼び出し側で URL を必ず stdout に表示するフォールバックを残すため、ここでは
|
|
17
|
+
* 例外を握り潰して boolean で結果だけ返す。
|
|
18
|
+
*
|
|
19
|
+
* 強制無効化: `LNAR_NO_BROWSER=1` を export しておくと自動オープンを抑止する。
|
|
20
|
+
*/
|
|
21
|
+
import { spawn } from 'node:child_process';
|
|
22
|
+
import { platform } from 'node:os';
|
|
23
|
+
const isSafeHttpUrl = (url) => {
|
|
24
|
+
try {
|
|
25
|
+
const parsed = new URL(url);
|
|
26
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const resolveOpenCommand = (url) => {
|
|
33
|
+
const os = platform();
|
|
34
|
+
if (os === 'darwin') {
|
|
35
|
+
// `--` で URL がフラグ扱いされないようにする
|
|
36
|
+
return { command: 'open', args: ['--', url] };
|
|
37
|
+
}
|
|
38
|
+
if (os === 'win32') {
|
|
39
|
+
// cmd.exe を経由しないことで `&`, `|` 等のメタ文字解釈を回避
|
|
40
|
+
return { command: 'rundll32', args: ['url.dll,FileProtocolHandler', url] };
|
|
41
|
+
}
|
|
42
|
+
return { command: 'xdg-open', args: ['--', url] };
|
|
43
|
+
};
|
|
44
|
+
export const openBrowser = (url) => {
|
|
45
|
+
if (process.env.LNAR_NO_BROWSER === '1') {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (!isSafeHttpUrl(url)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const { command, args } = resolveOpenCommand(url);
|
|
52
|
+
try {
|
|
53
|
+
const child = spawn(command, args, {
|
|
54
|
+
detached: true,
|
|
55
|
+
stdio: 'ignore',
|
|
56
|
+
});
|
|
57
|
+
// spawn 自体が失敗 (コマンドが PATH に無い等) しても unhandled error にしない。
|
|
58
|
+
child.on('error', () => {
|
|
59
|
+
/* swallow — フォールバックは呼び出し側の stdout に任せる */
|
|
60
|
+
});
|
|
61
|
+
child.unref();
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAOnC,MAAM,aAAa,GAAG,CAAC,GAAW,EAAW,EAAE;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAAe,EAAE;IACtD,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAC;IACtB,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;QACpB,6BAA6B;QAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;IAChD,CAAC;IACD,IAAI,EAAE,KAAK,OAAO,EAAE,CAAC;QACnB,yCAAyC;QACzC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,6BAA6B,EAAE,GAAG,CAAC,EAAE,CAAC;IAC7E,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;AACpD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAW,EAAW,EAAE;IAClD,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,GAAG,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YACjC,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,QAAQ;SAChB,CAAC,CAAC;QACH,0DAA0D;QAC1D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,0CAA0C;QAC5C,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Command } from 'commander';
|
|
|
3
3
|
import { runDaemon } from './commands/daemon.js';
|
|
4
4
|
import { runDown } from './commands/down.js';
|
|
5
5
|
import { runLogin } from './commands/login.js';
|
|
6
|
+
import { runRecord } from './commands/record.js';
|
|
6
7
|
import { runScan } from './commands/scan.js';
|
|
7
8
|
import { runStatus } from './commands/status.js';
|
|
8
9
|
import { runSync } from './commands/sync.js';
|
|
@@ -23,10 +24,13 @@ program
|
|
|
23
24
|
.description('Authenticate (if needed) and install lnar as a background service that runs forever.')
|
|
24
25
|
.option('--api-base-url <url>', 'lnar API base URL')
|
|
25
26
|
.option('--client-id <id>', 'OAuth client_id (default: lnar-cli)')
|
|
27
|
+
.option('--no-browser', 'Do not open the authorization URL in a browser automatically')
|
|
26
28
|
.action(async (opts) => {
|
|
27
29
|
await runUp({
|
|
28
30
|
apiBaseUrl: opts.apiBaseUrl,
|
|
29
31
|
clientId: opts.clientId,
|
|
32
|
+
// commander の --no-X は opts.browser=false で渡る
|
|
33
|
+
noBrowser: opts.browser === false,
|
|
30
34
|
});
|
|
31
35
|
});
|
|
32
36
|
program
|
|
@@ -49,10 +53,12 @@ program
|
|
|
49
53
|
.description('Re-authorize this device via the OAuth Device Code flow (normally not needed — `lnar up` handles login).')
|
|
50
54
|
.option('--api-base-url <url>', 'lnar API base URL')
|
|
51
55
|
.option('--client-id <id>', 'OAuth client_id (default: lnar-cli)')
|
|
56
|
+
.option('--no-browser', 'Do not open the authorization URL in a browser automatically')
|
|
52
57
|
.action(async (opts) => {
|
|
53
58
|
await runLogin({
|
|
54
59
|
apiBaseUrl: opts.apiBaseUrl,
|
|
55
60
|
clientId: opts.clientId,
|
|
61
|
+
noBrowser: opts.browser === false,
|
|
56
62
|
});
|
|
57
63
|
});
|
|
58
64
|
program
|
|
@@ -69,6 +75,30 @@ program
|
|
|
69
75
|
.action(async (opts) => {
|
|
70
76
|
await runSync({ dryRun: Boolean(opts.dryRun) });
|
|
71
77
|
});
|
|
78
|
+
program
|
|
79
|
+
.command('record')
|
|
80
|
+
.description('Record a browser demo locally and upload it to lnar (Demo-to-MCP). You operate your own browser; the captured actions/keyframes become the material for generating a browser-automation MCP.')
|
|
81
|
+
.option('--url <url>', 'Start URL to open in the recording browser')
|
|
82
|
+
.option('--name <name>', 'Recording name (default: recording-<timestamp>)')
|
|
83
|
+
.option('--purpose <text>', 'Purpose of the task (improves playbook analysis; prompted after recording if omitted)')
|
|
84
|
+
.option('--out <dir>', 'Output bundle directory (default: ./lnar-<name>)')
|
|
85
|
+
.option('--headless', 'Run the browser headless (not recommended for interactive demos)')
|
|
86
|
+
.option('--video', 'Also record a video of the session (off by default; included in the upload when set)')
|
|
87
|
+
.option('--trace', 'Also record a Playwright trace (off by default)')
|
|
88
|
+
.option('--no-upload', 'Produce the bundle locally but do not upload to lnar')
|
|
89
|
+
.action(async (opts) => {
|
|
90
|
+
await runRecord({
|
|
91
|
+
url: opts.url,
|
|
92
|
+
name: opts.name,
|
|
93
|
+
purpose: opts.purpose,
|
|
94
|
+
out: opts.out,
|
|
95
|
+
headless: Boolean(opts.headless),
|
|
96
|
+
video: Boolean(opts.video),
|
|
97
|
+
trace: Boolean(opts.trace),
|
|
98
|
+
// commander の --no-upload は opts.upload=false で渡る
|
|
99
|
+
noUpload: opts.upload === false,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
72
102
|
// ---------------------------------------------------------------------------
|
|
73
103
|
// Hidden internal command: launchd / systemd / Task Scheduler が exec する実体。
|
|
74
104
|
// ユーザー向けには `lnar up` が完全に隠蔽するため、--help に出さない。
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/daemon.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { hostname } from 'node:os';
|
|
2
2
|
import { ApiError, postSnapshot } from '../api-client.js';
|
|
3
3
|
import { DEFAULT_CLIENT_ID, loadConfig, saveConfig } from '../config.js';
|
|
4
|
+
import { loadOrCreateMachineId } from '../machine-id.js';
|
|
4
5
|
import { OAuthError, refreshAccessToken } from '../oauth-client.js';
|
|
5
6
|
import { listPendingForHost, reportApplied, reportFailed, } from '../pending-client.js';
|
|
7
|
+
import { executePendingRuns } from '../run-worker.js';
|
|
6
8
|
import { runAllScanners } from '../scanners/index.js';
|
|
9
|
+
import { runSseClient, SseFatalError } from '../sse-client.js';
|
|
7
10
|
import { getWriter } from '../writers/registry.js';
|
|
8
11
|
const DEFAULT_INTERVAL_SECONDS = 30;
|
|
9
12
|
const ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 60;
|
|
@@ -60,8 +63,10 @@ const bearerFor = (config) => {
|
|
|
60
63
|
};
|
|
61
64
|
const runScanAndSync = async (config) => {
|
|
62
65
|
const agents = await runAllScanners();
|
|
66
|
+
const machineId = await loadOrCreateMachineId();
|
|
63
67
|
const scan = {
|
|
64
68
|
hostname: hostname(),
|
|
69
|
+
machineId,
|
|
65
70
|
scannedAt: new Date().toISOString(),
|
|
66
71
|
agents,
|
|
67
72
|
};
|
|
@@ -97,6 +102,18 @@ const applyPending = async (config, change) => {
|
|
|
97
102
|
const runOnce = async (config) => {
|
|
98
103
|
// 1. 最新スナップショットを送信 → サーバ側 agent ID が最新化される
|
|
99
104
|
await runScanAndSync(config);
|
|
105
|
+
// 1.5. 録画 run-job をローカル実行する (Demo-to-MCP)。失敗しても以降の
|
|
106
|
+
// スキャン / pending 適用は継続させる (機能を分離する)。
|
|
107
|
+
try {
|
|
108
|
+
const ran = await executePendingRuns(config.apiBaseUrl, bearerFor(config), hostname());
|
|
109
|
+
if (ran > 0) {
|
|
110
|
+
process.stdout.write(`lnar daemon: executed ${ran} recording run(s)\n`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
115
|
+
process.stderr.write(`lnar daemon: recording runs error: ${message}\n`);
|
|
116
|
+
}
|
|
100
117
|
// 2. 自 host 向けの pending を取得
|
|
101
118
|
const host = hostname();
|
|
102
119
|
const pending = await listPendingForHost(config.apiBaseUrl, bearerFor(config), host);
|
|
@@ -112,6 +129,61 @@ const runOnce = async (config) => {
|
|
|
112
129
|
// 3. 反映後に再スキャン送信して dashboard の表示も最新化
|
|
113
130
|
await runScanAndSync(config);
|
|
114
131
|
};
|
|
132
|
+
/**
|
|
133
|
+
* runOnce が重複実行されないよう mutex を取る。SSE 通知と polling が同時に発火
|
|
134
|
+
* したとき、両方が runOnce を呼んで pending を二重処理するのを防ぐ。pending が
|
|
135
|
+
* あれば即時実行、すでに走っていれば 1 回だけ追い焚き予約 (coalesce)。
|
|
136
|
+
*/
|
|
137
|
+
const createRunOnceMutex = () => {
|
|
138
|
+
let inFlight = null;
|
|
139
|
+
let queued = false;
|
|
140
|
+
const trigger = (config) => {
|
|
141
|
+
if (inFlight) {
|
|
142
|
+
// すでに走っているなら次回追い焚き予約 (複数回呼ばれても 1 回に集約)。
|
|
143
|
+
queued = true;
|
|
144
|
+
return inFlight;
|
|
145
|
+
}
|
|
146
|
+
const run = async () => {
|
|
147
|
+
try {
|
|
148
|
+
await runOnce(config);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
if (queued) {
|
|
152
|
+
queued = false;
|
|
153
|
+
// 1 回だけ追い焚き。caller の最新 trigger に応答する。
|
|
154
|
+
inFlight = run();
|
|
155
|
+
await inFlight;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
inFlight = run().finally(() => {
|
|
160
|
+
inFlight = null;
|
|
161
|
+
});
|
|
162
|
+
return inFlight;
|
|
163
|
+
};
|
|
164
|
+
return { trigger };
|
|
165
|
+
};
|
|
166
|
+
const startSseListener = (config, host, trigger, signal) => {
|
|
167
|
+
const url = new URL('/v1/monitoring/daemon/events', config.apiBaseUrl);
|
|
168
|
+
url.searchParams.set('hostname', host);
|
|
169
|
+
process.stdout.write(`lnar daemon: SSE connecting to ${url.toString()}\n`);
|
|
170
|
+
return runSseClient({
|
|
171
|
+
url: url.toString(),
|
|
172
|
+
token: bearerFor(config),
|
|
173
|
+
signal,
|
|
174
|
+
onOpen: async () => {
|
|
175
|
+
// 接続 / 再接続のたびに 1 回 fetch (取りこぼし防止)
|
|
176
|
+
process.stdout.write('lnar daemon: SSE connected\n');
|
|
177
|
+
await trigger(config);
|
|
178
|
+
},
|
|
179
|
+
onEvent: async (event) => {
|
|
180
|
+
if (event.type === 'pending_created') {
|
|
181
|
+
process.stdout.write('lnar daemon: SSE event → fetching pending\n');
|
|
182
|
+
await trigger(config);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
};
|
|
115
187
|
export const runDaemon = async (options = {}) => {
|
|
116
188
|
const initial = await loadConfig();
|
|
117
189
|
if (initial == null) {
|
|
@@ -119,13 +191,36 @@ export const runDaemon = async (options = {}) => {
|
|
|
119
191
|
process.exit(1);
|
|
120
192
|
}
|
|
121
193
|
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1000;
|
|
194
|
+
const enableSse = options.enableSse ?? !options.once;
|
|
195
|
+
const host = hostname();
|
|
196
|
+
const { trigger } = createRunOnceMutex();
|
|
122
197
|
let running = true;
|
|
198
|
+
const sseAbort = new AbortController();
|
|
123
199
|
const stop = () => {
|
|
124
200
|
running = false;
|
|
201
|
+
sseAbort.abort();
|
|
125
202
|
process.stdout.write('\nlnar daemon: shutting down…\n');
|
|
126
203
|
};
|
|
127
204
|
process.on('SIGINT', stop);
|
|
128
205
|
process.on('SIGTERM', stop);
|
|
206
|
+
// SSE listener は background で常時走らせる。fatal error (401 等) は
|
|
207
|
+
// catch して polling loop 側の token refresh に任せる。
|
|
208
|
+
// Box にしないと TS が closure 内の代入をフローに反映できず never 扱いになる。
|
|
209
|
+
const sseTaskRef = { current: null };
|
|
210
|
+
const startSse = (config) => {
|
|
211
|
+
if (!enableSse)
|
|
212
|
+
return;
|
|
213
|
+
sseTaskRef.current = startSseListener(config, host, trigger, sseAbort.signal).catch((err) => {
|
|
214
|
+
if (err instanceof SseFatalError) {
|
|
215
|
+
process.stderr.write(`lnar daemon: SSE fatal (${err.status}); falling back to polling only\n`);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
219
|
+
process.stderr.write(`lnar daemon: SSE listener stopped: ${message}\n`);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
startSse(initial);
|
|
129
224
|
while (running) {
|
|
130
225
|
let config;
|
|
131
226
|
try {
|
|
@@ -140,7 +235,7 @@ export const runDaemon = async (options = {}) => {
|
|
|
140
235
|
continue;
|
|
141
236
|
}
|
|
142
237
|
try {
|
|
143
|
-
await
|
|
238
|
+
await trigger(config);
|
|
144
239
|
}
|
|
145
240
|
catch (err) {
|
|
146
241
|
if (err instanceof ApiError && err.status === 401) {
|
|
@@ -154,5 +249,9 @@ export const runDaemon = async (options = {}) => {
|
|
|
154
249
|
break;
|
|
155
250
|
await sleep(interval);
|
|
156
251
|
}
|
|
252
|
+
if (sseTaskRef.current) {
|
|
253
|
+
sseAbort.abort();
|
|
254
|
+
await sseTaskRef.current.catch(() => undefined);
|
|
255
|
+
}
|
|
157
256
|
};
|
|
158
257
|
//# sourceMappingURL=daemon.js.map
|