@lnar/cli 0.0.1-dev.1da013d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +96 -0
  2. package/dist/api-client.d.ts +17 -0
  3. package/dist/api-client.js +35 -0
  4. package/dist/api-client.js.map +1 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +91 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/daemon.d.ts +5 -0
  9. package/dist/commands/daemon.js +158 -0
  10. package/dist/commands/daemon.js.map +1 -0
  11. package/dist/commands/down.d.ts +1 -0
  12. package/dist/commands/down.js +12 -0
  13. package/dist/commands/down.js.map +1 -0
  14. package/dist/commands/login.d.ts +6 -0
  15. package/dist/commands/login.js +86 -0
  16. package/dist/commands/login.js.map +1 -0
  17. package/dist/commands/scan.d.ts +4 -0
  18. package/dist/commands/scan.js +36 -0
  19. package/dist/commands/scan.js.map +1 -0
  20. package/dist/commands/status.d.ts +1 -0
  21. package/dist/commands/status.js +32 -0
  22. package/dist/commands/status.js.map +1 -0
  23. package/dist/commands/sync.d.ts +4 -0
  24. package/dist/commands/sync.js +96 -0
  25. package/dist/commands/sync.js.map +1 -0
  26. package/dist/commands/up.d.ts +3 -0
  27. package/dist/commands/up.js +27 -0
  28. package/dist/commands/up.js.map +1 -0
  29. package/dist/config.d.ts +25 -0
  30. package/dist/config.js +82 -0
  31. package/dist/config.js.map +1 -0
  32. package/dist/hash.d.ts +7 -0
  33. package/dist/hash.js +12 -0
  34. package/dist/hash.js.map +1 -0
  35. package/dist/oauth-client.d.ts +32 -0
  36. package/dist/oauth-client.js +62 -0
  37. package/dist/oauth-client.js.map +1 -0
  38. package/dist/pending-client.d.ts +40 -0
  39. package/dist/pending-client.js +48 -0
  40. package/dist/pending-client.js.map +1 -0
  41. package/dist/scanners/_normalize.d.ts +13 -0
  42. package/dist/scanners/_normalize.js +76 -0
  43. package/dist/scanners/_normalize.js.map +1 -0
  44. package/dist/scanners/chatgpt.d.ts +20 -0
  45. package/dist/scanners/chatgpt.js +49 -0
  46. package/dist/scanners/chatgpt.js.map +1 -0
  47. package/dist/scanners/claude-code-plugins.d.ts +2 -0
  48. package/dist/scanners/claude-code-plugins.js +104 -0
  49. package/dist/scanners/claude-code-plugins.js.map +1 -0
  50. package/dist/scanners/claude-code.d.ts +13 -0
  51. package/dist/scanners/claude-code.js +221 -0
  52. package/dist/scanners/claude-code.js.map +1 -0
  53. package/dist/scanners/claude-desktop.d.ts +1 -0
  54. package/dist/scanners/claude-desktop.js +2 -0
  55. package/dist/scanners/claude-desktop.js.map +1 -0
  56. package/dist/scanners/codex.d.ts +5 -0
  57. package/dist/scanners/codex.js +69 -0
  58. package/dist/scanners/codex.js.map +1 -0
  59. package/dist/scanners/cursor.d.ts +9 -0
  60. package/dist/scanners/cursor.js +25 -0
  61. package/dist/scanners/cursor.js.map +1 -0
  62. package/dist/scanners/gemini-cli.d.ts +21 -0
  63. package/dist/scanners/gemini-cli.js +37 -0
  64. package/dist/scanners/gemini-cli.js.map +1 -0
  65. package/dist/scanners/index.d.ts +6 -0
  66. package/dist/scanners/index.js +16 -0
  67. package/dist/scanners/index.js.map +1 -0
  68. package/dist/service/files.d.ts +26 -0
  69. package/dist/service/files.js +119 -0
  70. package/dist/service/files.js.map +1 -0
  71. package/dist/service/install.d.ts +14 -0
  72. package/dist/service/install.js +231 -0
  73. package/dist/service/install.js.map +1 -0
  74. package/dist/types.d.ts +161 -0
  75. package/dist/types.js +53 -0
  76. package/dist/types.js.map +1 -0
  77. package/dist/writers/_fs.d.ts +22 -0
  78. package/dist/writers/_fs.js +55 -0
  79. package/dist/writers/_fs.js.map +1 -0
  80. package/dist/writers/chatgpt.d.ts +8 -0
  81. package/dist/writers/chatgpt.js +12 -0
  82. package/dist/writers/chatgpt.js.map +1 -0
  83. package/dist/writers/claude-code-plugins.d.ts +3 -0
  84. package/dist/writers/claude-code-plugins.js +74 -0
  85. package/dist/writers/claude-code-plugins.js.map +1 -0
  86. package/dist/writers/claude-code.d.ts +4 -0
  87. package/dist/writers/claude-code.js +128 -0
  88. package/dist/writers/claude-code.js.map +1 -0
  89. package/dist/writers/codex.d.ts +4 -0
  90. package/dist/writers/codex.js +88 -0
  91. package/dist/writers/codex.js.map +1 -0
  92. package/dist/writers/cursor.d.ts +4 -0
  93. package/dist/writers/cursor.js +74 -0
  94. package/dist/writers/cursor.js.map +1 -0
  95. package/dist/writers/gemini-cli.d.ts +4 -0
  96. package/dist/writers/gemini-cli.js +87 -0
  97. package/dist/writers/gemini-cli.js.map +1 -0
  98. package/dist/writers/index.d.ts +18 -0
  99. package/dist/writers/index.js +11 -0
  100. package/dist/writers/index.js.map +1 -0
  101. package/dist/writers/registry.d.ts +7 -0
  102. package/dist/writers/registry.js +31 -0
  103. package/dist/writers/registry.js.map +1 -0
  104. package/package.json +38 -0
@@ -0,0 +1,4 @@
1
+ export type SyncCommandOptions = {
2
+ dryRun?: boolean;
3
+ };
4
+ export declare const runSync: (options?: SyncCommandOptions) => Promise<void>;
@@ -0,0 +1,96 @@
1
+ import { hostname } from 'node:os';
2
+ import { ApiError, postSnapshot } from '../api-client.js';
3
+ import { DEFAULT_CLIENT_ID, loadConfig, saveConfig } from '../config.js';
4
+ import { OAuthError, refreshAccessToken } from '../oauth-client.js';
5
+ import { runAllScanners } from '../scanners/index.js';
6
+ const ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 60;
7
+ const isAccessTokenExpired = (config) => {
8
+ if (!config.accessTokenExpiresAt)
9
+ return false;
10
+ const expires = Date.parse(config.accessTokenExpiresAt);
11
+ if (Number.isNaN(expires))
12
+ return true;
13
+ return expires - Date.now() < ACCESS_TOKEN_REFRESH_SKEW_SECONDS * 1000;
14
+ };
15
+ const refreshIfNeeded = async (config) => {
16
+ if (!config.accessToken)
17
+ return config;
18
+ if (!isAccessTokenExpired(config))
19
+ return config;
20
+ if (!config.refreshToken) {
21
+ process.stderr.write('lnar: access token expired and no refresh token available. Run `lnar login`.\n');
22
+ process.exit(1);
23
+ }
24
+ const clientId = config.clientId ?? DEFAULT_CLIENT_ID;
25
+ let token;
26
+ try {
27
+ token = await refreshAccessToken(config.apiBaseUrl, clientId, config.refreshToken);
28
+ }
29
+ catch (err) {
30
+ if (err instanceof OAuthError && err.errorCode === 'invalid_grant') {
31
+ process.stderr.write('lnar: refresh token expired or revoked. Run `lnar login`.\n');
32
+ process.exit(1);
33
+ }
34
+ throw err;
35
+ }
36
+ const accessTokenExpiresAt = new Date(Date.now() + token.expires_in * 1000).toISOString();
37
+ const next = {
38
+ apiBaseUrl: config.apiBaseUrl,
39
+ accessToken: token.access_token,
40
+ refreshToken: token.refresh_token ?? config.refreshToken,
41
+ accessTokenExpiresAt,
42
+ scopes: token.scope ? token.scope.split(' ') : config.scopes,
43
+ clientId,
44
+ };
45
+ await saveConfig(next);
46
+ return { ...next, savedAt: new Date().toISOString() };
47
+ };
48
+ const bearerFor = (config) => {
49
+ if (config.accessToken)
50
+ return config.accessToken;
51
+ if (config.apiKey)
52
+ return config.apiKey;
53
+ throw new Error('No credentials available');
54
+ };
55
+ export const runSync = async (options = {}) => {
56
+ const initial = await loadConfig();
57
+ if (initial == null) {
58
+ process.stderr.write('lnar: not logged in. Run `lnar login` first.\n');
59
+ process.exit(1);
60
+ }
61
+ const config = await refreshIfNeeded(initial);
62
+ const agents = await runAllScanners();
63
+ const scan = {
64
+ hostname: hostname(),
65
+ scannedAt: new Date().toISOString(),
66
+ agents,
67
+ };
68
+ if (agents.length === 0) {
69
+ process.stdout.write('No AI agents with MCP servers detected. Nothing to sync.\n');
70
+ return;
71
+ }
72
+ if (options.dryRun) {
73
+ process.stdout.write(`${JSON.stringify(scan, null, 2)}\n`);
74
+ process.stdout.write('(dry-run: not uploaded)\n');
75
+ return;
76
+ }
77
+ try {
78
+ const result = await postSnapshot(config.apiBaseUrl, bearerFor(config), scan);
79
+ process.stdout.write(`Synced ${result.agents.length} agent${result.agents.length === 1 ? '' : 's'} to ${config.apiBaseUrl}\n`);
80
+ for (const a of result.agents) {
81
+ process.stdout.write(` - ${a.agent_kind.padEnd(16)} ${a.mcp_server_count} MCP server${a.mcp_server_count === 1 ? '' : 's'}\n`);
82
+ }
83
+ }
84
+ catch (err) {
85
+ if (err instanceof ApiError) {
86
+ if (err.status === 401) {
87
+ process.stderr.write('lnar: not authorized (token may be expired). Run `lnar login`.\n');
88
+ process.exit(1);
89
+ }
90
+ process.stderr.write(`lnar: ${err.message}\n`);
91
+ process.exit(1);
92
+ }
93
+ throw err;
94
+ }
95
+ };
96
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,UAAU,EAAqB,MAAM,cAAc,CAAA;AAC3F,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAOrD,MAAM,iCAAiC,GAAG,EAAE,CAAA;AAE5C,MAAM,oBAAoB,GAAG,CAAC,MAAoB,EAAW,EAAE;IAC7D,IAAI,CAAC,MAAM,CAAC,oBAAoB;QAAE,OAAO,KAAK,CAAA;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;IACvD,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAA;IACtC,OAAO,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,iCAAiC,GAAG,IAAI,CAAA;AACxE,CAAC,CAAA;AAED,MAAM,eAAe,GAAG,KAAK,EAAE,MAAoB,EAAyB,EAAE;IAC5E,IAAI,CAAC,MAAM,CAAC,WAAW;QAAE,OAAO,MAAM,CAAA;IACtC,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAA;IAChD,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gFAAgF,CACjF,CAAA;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,iBAAiB,CAAA;IACrD,IAAI,KAAK,CAAA;IACT,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,kBAAkB,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;IACpF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,UAAU,IAAI,GAAG,CAAC,SAAS,KAAK,eAAe,EAAE,CAAC;YACnE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,6DAA6D,CAC9D,CAAA;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;IACD,MAAM,oBAAoB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;IACzF,MAAM,IAAI,GAAkC;QAC1C,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,WAAW,EAAE,KAAK,CAAC,YAAY;QAC/B,YAAY,EAAE,KAAK,CAAC,aAAa,IAAI,MAAM,CAAC,YAAY;QACxD,oBAAoB;QACpB,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM;QAC5D,QAAQ;KACT,CAAA;IACD,MAAM,UAAU,CAAC,IAAI,CAAC,CAAA;IACtB,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAA;AACvD,CAAC,CAAA;AAED,MAAM,SAAS,GAAG,CAAC,MAAoB,EAAU,EAAE;IACjD,IAAI,MAAM,CAAC,WAAW;QAAE,OAAO,MAAM,CAAC,WAAW,CAAA;IACjD,IAAI,MAAM,CAAC,MAAM;QAAE,OAAO,MAAM,CAAC,MAAM,CAAA;IACvC,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;AAC7C,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAAE,UAA8B,EAAE,EAAiB,EAAE;IAC/E,MAAM,OAAO,GAAG,MAAM,UAAU,EAAE,CAAA;IAClC,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAA;QACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAA;IAE7C,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAA;IACrC,MAAM,IAAI,GAAe;QACvB,QAAQ,EAAE,QAAQ,EAAE;QACpB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,MAAM;KACP,CAAA;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAA;QAClF,OAAM;IACR,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;QAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAA;QACjD,OAAM;IACR,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAA;QAC7E,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,UAAU,MAAM,CAAC,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,MAAM,CAAC,UAAU,IAAI,CACzG,CAAA;QACD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,OAAO,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,gBAAgB,cAAc,CAAC,CAAC,gBAAgB,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAC1G,CAAA;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;YAC5B,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kEAAkE,CACnE,CAAA;gBACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,OAAO,IAAI,CAAC,CAAA;YAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;AACH,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ import { type LoginOptions } from './login.js';
2
+ export type UpOptions = LoginOptions;
3
+ export declare const runUp: (options?: UpOptions) => Promise<void>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `lnar up` — Tailscale 風の "全部入り" 導線。
3
+ *
4
+ * 1. `~/.config/lnar/auth.json` を読んで、認証済みでなければ device flow を起動する
5
+ * 2. OS の常駐サービスとして登録 (macOS=launchd / Linux=systemd / Windows=Task Scheduler)
6
+ *
7
+ * 認証済みかどうかは「資格情報ファイルが存在し、accessToken or apiKey が入っているか」で
8
+ * 判断する。トークンの有効期限は daemon 側の API クライアントが refresh するので、
9
+ * ここでは「あれば良し」とする (Tailscale も同様で、`tailscale up` は token が
10
+ * 失効していてもまずは upload を試み、失敗時に re-auth プロンプトを出す)。
11
+ */
12
+ import { loadConfig } from '../config.js';
13
+ import { runDaemonInstall } from '../service/install.js';
14
+ import { runLogin } from './login.js';
15
+ export const runUp = async (options = {}) => {
16
+ const config = await loadConfig();
17
+ if (config == null) {
18
+ process.stdout.write('No credentials found — starting device authorization first.\n\n');
19
+ await runLogin(options);
20
+ process.stdout.write('\n');
21
+ }
22
+ else {
23
+ process.stdout.write(`Using existing credentials (${config.apiBaseUrl}).\n\n`);
24
+ }
25
+ await runDaemonInstall();
26
+ };
27
+ //# sourceMappingURL=up.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"up.js","sourceRoot":"","sources":["../../src/commands/up.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAqB,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIzD,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,EAAE,UAAqB,EAAE,EAAiB,EAAE;IACpE,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAC;QACxF,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,MAAM,CAAC,UAAU,QAAQ,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,gBAAgB,EAAE,CAAC;AAC3B,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * 永続化される CLI 認証情報。
4
+ *
5
+ * `apiKey` (legacy, lnar_pat_xxx) と `accessToken` (OAuth, lnar_oat_xxx) の
6
+ * 両方を許容する。`accessToken` がある場合は `refreshToken` で更新可能。
7
+ * 期限切れ・refresh 失敗時は再ログインを案内する。
8
+ */
9
+ declare const StoredConfig: z.ZodObject<{
10
+ apiBaseUrl: z.ZodString;
11
+ savedAt: z.ZodString;
12
+ apiKey: z.ZodOptional<z.ZodString>;
13
+ accessToken: z.ZodOptional<z.ZodString>;
14
+ refreshToken: z.ZodOptional<z.ZodString>;
15
+ accessTokenExpiresAt: z.ZodOptional<z.ZodString>;
16
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
17
+ clientId: z.ZodOptional<z.ZodString>;
18
+ }, z.core.$strip>;
19
+ export type StoredConfig = z.infer<typeof StoredConfig>;
20
+ export declare const DEFAULT_API_BASE_URL: string;
21
+ export declare const DEFAULT_CLIENT_ID = "lnar-cli";
22
+ export declare const configPath: (home?: string) => string;
23
+ export declare const loadConfig: (home?: string) => Promise<StoredConfig | null>;
24
+ export declare const saveConfig: (config: Omit<StoredConfig, "savedAt">, home?: string) => Promise<string>;
25
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,82 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { z } from 'zod';
7
+ /**
8
+ * 永続化される CLI 認証情報。
9
+ *
10
+ * `apiKey` (legacy, lnar_pat_xxx) と `accessToken` (OAuth, lnar_oat_xxx) の
11
+ * 両方を許容する。`accessToken` がある場合は `refreshToken` で更新可能。
12
+ * 期限切れ・refresh 失敗時は再ログインを案内する。
13
+ */
14
+ const StoredConfig = z
15
+ .object({
16
+ apiBaseUrl: z.string().url(),
17
+ savedAt: z.string(),
18
+ apiKey: z.string().optional(),
19
+ accessToken: z.string().optional(),
20
+ refreshToken: z.string().optional(),
21
+ accessTokenExpiresAt: z.string().optional(),
22
+ scopes: z.array(z.string()).optional(),
23
+ clientId: z.string().optional(),
24
+ })
25
+ .refine((c) => c.apiKey != null || c.accessToken != null, {
26
+ message: 'config must have either apiKey or accessToken',
27
+ });
28
+ /**
29
+ * デフォルト API base URL の優先順位:
30
+ * 1. 環境変数 `LNAR_API_BASE_URL` (一時的に切り替えたい開発者向け)
31
+ * 2. package.json の `lnar:defaultApiBaseUrl` (npm dist-tag ごとに CI が書き換える)
32
+ * - `@lnar/cli@latest` → "https://api.lnar.ai"
33
+ * - `@lnar/cli@dev` → "https://api-dev.lnar.ai"
34
+ * 3. ハードコード fallback "https://api.lnar.ai" (公開直前の安全装置)
35
+ *
36
+ * これにより `npx @lnar/cli login` と `npx @lnar/cli@dev login` がそれぞれ
37
+ * 自然と本番/dev を向く。
38
+ */
39
+ const readPackagedDefaultUrl = () => {
40
+ try {
41
+ const here = dirname(fileURLToPath(import.meta.url));
42
+ // dist/config.js → ../package.json
43
+ const pkgPath = join(here, '..', 'package.json');
44
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
45
+ return pkg['lnar:defaultApiBaseUrl'];
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ };
51
+ export const DEFAULT_API_BASE_URL = process.env.LNAR_API_BASE_URL ?? readPackagedDefaultUrl() ?? 'https://api.lnar.ai';
52
+ export const DEFAULT_CLIENT_ID = 'lnar-cli';
53
+ export const configPath = (home = homedir()) => join(home, '.config', 'lnar', 'auth.json');
54
+ // 旧ファイル名: CLI ディレクトリ rename (monitor → cli) に合わせて auth.json に移行した。
55
+ // 既存開発者の手元にある旧ファイルを読み込んで自然に移行できるよう fallback を残す。
56
+ const legacyConfigPath = (home) => join(home, '.config', 'lnar', 'monitor.json');
57
+ export const loadConfig = async (home = homedir()) => {
58
+ const tryRead = async (path) => {
59
+ try {
60
+ const text = await readFile(path, 'utf-8');
61
+ return StoredConfig.parse(JSON.parse(text));
62
+ }
63
+ catch (err) {
64
+ const code = err.code;
65
+ if (code === 'ENOENT')
66
+ return null;
67
+ throw err;
68
+ }
69
+ };
70
+ const fromNew = await tryRead(configPath(home));
71
+ if (fromNew != null)
72
+ return fromNew;
73
+ return tryRead(legacyConfigPath(home));
74
+ };
75
+ export const saveConfig = async (config, home = homedir()) => {
76
+ const path = configPath(home);
77
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
78
+ const next = { ...config, savedAt: new Date().toISOString() };
79
+ await writeFile(path, JSON.stringify(next, null, 2), { mode: 0o600 });
80
+ return path;
81
+ };
82
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/hash.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export declare const hashConfig: (parts: {
2
+ transport: string;
3
+ command?: string;
4
+ args?: ReadonlyArray<string>;
5
+ url?: string;
6
+ envKeys?: ReadonlyArray<string>;
7
+ }) => string;
package/dist/hash.js ADDED
@@ -0,0 +1,12 @@
1
+ import { createHash } from 'node:crypto';
2
+ export const hashConfig = (parts) => {
3
+ const canonical = JSON.stringify({
4
+ transport: parts.transport,
5
+ command: parts.command ?? null,
6
+ args: parts.args ?? [],
7
+ url: parts.url ?? null,
8
+ envKeys: [...(parts.envKeys ?? [])].sort(),
9
+ });
10
+ return createHash('sha256').update(canonical).digest('hex').slice(0, 16);
11
+ };
12
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAM1B,EAAU,EAAE;IACX,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAC/B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI;QAC9B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;QACtB,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,IAAI;QACtB,OAAO,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE;KAC3C,CAAC,CAAC;IACH,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC3E,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * OAuth 2.0 Device Authorization Grant (RFC 8628) クライアント実装。
3
+ *
4
+ * @lnar/cli は public client (`client_id=lnar-cli`) としてサーバーに
5
+ * 登録されている。secret は持たず、ブラウザでユーザーが user_code を承認すると
6
+ * access_token + refresh_token を取得する。
7
+ */
8
+ export declare const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
9
+ export interface DeviceAuthorizationResponse {
10
+ device_code: string;
11
+ user_code: string;
12
+ verification_uri: string;
13
+ verification_uri_complete: string;
14
+ expires_in: number;
15
+ interval: number;
16
+ }
17
+ export interface TokenResponse {
18
+ access_token: string;
19
+ token_type: string;
20
+ expires_in: number;
21
+ refresh_token?: string;
22
+ scope: string;
23
+ }
24
+ export declare class OAuthError extends Error {
25
+ status: number;
26
+ errorCode: string;
27
+ constructor(status: number, errorCode: string, description: string);
28
+ }
29
+ export declare const requestDeviceAuthorization: (apiBaseUrl: string, clientId: string, scopes: ReadonlyArray<string>) => Promise<DeviceAuthorizationResponse>;
30
+ export declare const pollForToken: (apiBaseUrl: string, clientId: string, deviceCode: string) => Promise<TokenResponse>;
31
+ export declare const refreshAccessToken: (apiBaseUrl: string, clientId: string, refreshToken: string) => Promise<TokenResponse>;
32
+ export declare const sleep: (ms: number) => Promise<void>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * OAuth 2.0 Device Authorization Grant (RFC 8628) クライアント実装。
3
+ *
4
+ * @lnar/cli は public client (`client_id=lnar-cli`) としてサーバーに
5
+ * 登録されている。secret は持たず、ブラウザでユーザーが user_code を承認すると
6
+ * access_token + refresh_token を取得する。
7
+ */
8
+ export const DEVICE_CODE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
9
+ export class OAuthError extends Error {
10
+ status;
11
+ errorCode;
12
+ constructor(status, errorCode, description) {
13
+ super(`${errorCode}: ${description}`);
14
+ this.status = status;
15
+ this.errorCode = errorCode;
16
+ }
17
+ }
18
+ async function postForm(baseUrl, path, body) {
19
+ const url = new URL(path, baseUrl).toString();
20
+ const response = await fetch(url, {
21
+ method: 'POST',
22
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
23
+ body: new URLSearchParams(body).toString(),
24
+ });
25
+ const text = await response.text();
26
+ let parsed;
27
+ try {
28
+ parsed = text ? JSON.parse(text) : {};
29
+ }
30
+ catch {
31
+ parsed = { error: 'invalid_response', error_description: text };
32
+ }
33
+ if (!response.ok) {
34
+ const errBody = parsed;
35
+ throw new OAuthError(response.status, errBody.error ?? 'unknown_error', errBody.error_description ?? response.statusText);
36
+ }
37
+ return parsed;
38
+ }
39
+ export const requestDeviceAuthorization = async (apiBaseUrl, clientId, scopes) => {
40
+ return postForm(apiBaseUrl, '/oauth/device/authorize', {
41
+ client_id: clientId,
42
+ scope: scopes.join(' '),
43
+ });
44
+ };
45
+ export const pollForToken = async (apiBaseUrl, clientId, deviceCode) => {
46
+ return postForm(apiBaseUrl, '/oauth/token', {
47
+ grant_type: DEVICE_CODE_GRANT_TYPE,
48
+ client_id: clientId,
49
+ device_code: deviceCode,
50
+ });
51
+ };
52
+ export const refreshAccessToken = async (apiBaseUrl, clientId, refreshToken) => {
53
+ return postForm(apiBaseUrl, '/oauth/token', {
54
+ grant_type: 'refresh_token',
55
+ client_id: clientId,
56
+ refresh_token: refreshToken,
57
+ });
58
+ };
59
+ export const sleep = (ms) => new Promise((resolve) => {
60
+ setTimeout(resolve, ms);
61
+ });
62
+ //# sourceMappingURL=oauth-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-client.js","sourceRoot":"","sources":["../src/oauth-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,8CAA8C,CAAC;AAmBrF,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,MAAM,CAAS;IACf,SAAS,CAAS;IAClB,YAAY,MAAc,EAAE,SAAiB,EAAE,WAAmB;QAChE,KAAK,CAAC,GAAG,SAAS,KAAK,WAAW,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED,KAAK,UAAU,QAAQ,CACrB,OAAe,EACf,IAAY,EACZ,IAA4B;IAE5B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC9C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI,EAAE,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;KAC3C,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,GAAG,EAAE,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,MAAwD,CAAC;QACzE,MAAM,IAAI,UAAU,CAClB,QAAQ,CAAC,MAAM,EACf,OAAO,CAAC,KAAK,IAAI,eAAe,EAChC,OAAO,CAAC,iBAAiB,IAAI,QAAQ,CAAC,UAAU,CACjD,CAAC;IACJ,CAAC;IACD,OAAO,MAAW,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,0BAA0B,GAAG,KAAK,EAC7C,UAAkB,EAClB,QAAgB,EAChB,MAA6B,EACS,EAAE;IACxC,OAAO,QAAQ,CAA8B,UAAU,EAAE,yBAAyB,EAAE;QAClF,SAAS,EAAE,QAAQ;QACnB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;KACxB,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,UAAkB,EAClB,QAAgB,EAChB,UAAkB,EACM,EAAE;IAC1B,OAAO,QAAQ,CAAgB,UAAU,EAAE,cAAc,EAAE;QACzD,UAAU,EAAE,sBAAsB;QAClC,SAAS,EAAE,QAAQ;QACnB,WAAW,EAAE,UAAU;KACxB,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,UAAkB,EAClB,QAAgB,EAChB,YAAoB,EACI,EAAE;IAC1B,OAAO,QAAQ,CAAgB,UAAU,EAAE,cAAc,EAAE;QACzD,UAAU,EAAE,eAAe;QAC3B,SAAS,EAAE,QAAQ;QACnB,aAAa,EAAE,YAAY;KAC5B,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CACjD,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Bidirectional control: lnar API の pending_changes との RPC クライアント。
3
+ *
4
+ * - `GET /v1/monitoring/pending?hostname=...&status=pending` で取りに行く
5
+ * - `POST /v1/monitoring/pending/{id}/applied` で適用完了報告
6
+ * - `POST /v1/monitoring/pending/{id}/failed` で失敗報告
7
+ *
8
+ * 認証は OAuth access token (`Bearer lnar_oat_...`)。env 値は一切扱わない。
9
+ */
10
+ export type PendingAction = 'add_server' | 'remove_server' | 'install_plugin' | 'uninstall_plugin' | 'enable_plugin' | 'disable_plugin';
11
+ export type PendingStatus = 'pending' | 'applied' | 'failed' | 'cancelled';
12
+ export type AgentKind = 'claude_code' | 'codex' | 'cursor' | 'gemini_cli' | 'chatgpt';
13
+ export type SourceScope = 'global' | 'project' | 'local' | 'remote';
14
+ export type PluginScope = 'user' | 'local' | 'project';
15
+ export interface PendingSpec {
16
+ transport: 'stdio' | 'http' | 'sse';
17
+ command?: string | null;
18
+ args?: string[] | null;
19
+ url?: string | null;
20
+ envKeys?: string[] | null;
21
+ }
22
+ export interface PendingChange {
23
+ id: string;
24
+ agent_id: string;
25
+ action: PendingAction;
26
+ target_name: string;
27
+ spec: PendingSpec | null;
28
+ requested_by: string;
29
+ requested_at: string;
30
+ status: PendingStatus;
31
+ applied_at: string | null;
32
+ error: string | null;
33
+ agent_kind: AgentKind;
34
+ hostname: string;
35
+ source_scope: SourceScope | PluginScope | null;
36
+ project_path: string | null;
37
+ }
38
+ export declare const listPendingForHost: (baseUrl: string, token: string, hostname: string) => Promise<PendingChange[]>;
39
+ export declare const reportApplied: (baseUrl: string, token: string, changeId: string) => Promise<void>;
40
+ export declare const reportFailed: (baseUrl: string, token: string, changeId: string, error: string) => Promise<void>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Bidirectional control: lnar API の pending_changes との RPC クライアント。
3
+ *
4
+ * - `GET /v1/monitoring/pending?hostname=...&status=pending` で取りに行く
5
+ * - `POST /v1/monitoring/pending/{id}/applied` で適用完了報告
6
+ * - `POST /v1/monitoring/pending/{id}/failed` で失敗報告
7
+ *
8
+ * 認証は OAuth access token (`Bearer lnar_oat_...`)。env 値は一切扱わない。
9
+ */
10
+ import { ApiError } from './api-client.js';
11
+ async function jsonOrError(response) {
12
+ if (!response.ok) {
13
+ const text = await response.text().catch(() => '');
14
+ throw new ApiError(response.status, text);
15
+ }
16
+ if (response.status === 204)
17
+ return undefined;
18
+ return (await response.json());
19
+ }
20
+ const auth = (token) => ({
21
+ Authorization: `Bearer ${token}`,
22
+ 'Content-Type': 'application/json',
23
+ });
24
+ export const listPendingForHost = async (baseUrl, token, hostname) => {
25
+ const url = new URL('/v1/monitoring/pending', baseUrl);
26
+ url.searchParams.set('hostname', hostname);
27
+ url.searchParams.set('status', 'pending');
28
+ const response = await fetch(url.toString(), { headers: auth(token) });
29
+ return jsonOrError(response);
30
+ };
31
+ export const reportApplied = async (baseUrl, token, changeId) => {
32
+ const url = new URL(`/v1/monitoring/pending/${encodeURIComponent(changeId)}/applied`, baseUrl);
33
+ const response = await fetch(url.toString(), {
34
+ method: 'POST',
35
+ headers: auth(token),
36
+ });
37
+ await jsonOrError(response);
38
+ };
39
+ export const reportFailed = async (baseUrl, token, changeId, error) => {
40
+ const url = new URL(`/v1/monitoring/pending/${encodeURIComponent(changeId)}/failed`, baseUrl);
41
+ const response = await fetch(url.toString(), {
42
+ method: 'POST',
43
+ headers: auth(token),
44
+ body: JSON.stringify({ error }),
45
+ });
46
+ await jsonOrError(response);
47
+ };
48
+ //# sourceMappingURL=pending-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-client.js","sourceRoot":"","sources":["../src/pending-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AA2C3C,KAAK,UAAU,WAAW,CAAI,QAAkB;IAC9C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,SAAyB,CAAC;IAC9D,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;AACtC,CAAC;AAED,MAAM,IAAI,GAAG,CAAC,KAAa,EAA0B,EAAE,CAAC,CAAC;IACvD,aAAa,EAAE,UAAU,KAAK,EAAE;IAChC,cAAc,EAAE,kBAAkB;CACnC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,OAAe,EACf,KAAa,EACb,QAAgB,EACU,EAAE;IAC5B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,wBAAwB,EAAE,OAAO,CAAC,CAAC;IACvD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC3C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACvE,OAAO,WAAW,CAAkB,QAAQ,CAAC,CAAC;AAChD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAChC,OAAe,EACf,KAAa,EACb,QAAgB,EACD,EAAE;IACjB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,0BAA0B,kBAAkB,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC/F,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;QAC3C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC;KACrB,CAAC,CAAC;IACH,MAAM,WAAW,CAAU,QAAQ,CAAC,CAAC;AACvC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,OAAe,EACf,KAAa,EACb,QAAgB,EAChB,KAAa,EACE,EAAE;IACjB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,0BAA0B,kBAAkB,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9F,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;QAC3C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC;QACpB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;KAChC,CAAC,CAAC;IACH,MAAM,WAAW,CAAU,QAAQ,CAAC,CAAC;AACvC,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { McpServerEntry, SourceScope } from '../types.js';
2
+ export type RawMcpEntry = {
3
+ command?: unknown;
4
+ args?: unknown;
5
+ url?: unknown;
6
+ type?: unknown;
7
+ transport?: unknown;
8
+ env?: unknown;
9
+ };
10
+ export declare const normalizeEntry: (name: string, raw: RawMcpEntry, sourceScope: SourceScope, sourcePath: string) => McpServerEntry;
11
+ export declare const extractFromMcpServersField: (data: unknown, field: string, sourceScope: SourceScope, sourcePath: string) => McpServerEntry[];
12
+ export declare const readJsonIfExists: (path: string) => Promise<unknown | null>;
13
+ export declare const readTextIfExists: (path: string) => Promise<string | null>;
@@ -0,0 +1,76 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { hashConfig } from '../hash.js';
3
+ const isStringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === 'string');
4
+ const inferTransport = (raw) => {
5
+ const t = raw.type ?? raw.transport;
6
+ const s = typeof t === 'string' ? t.toLowerCase() : undefined;
7
+ if (s === 'sse')
8
+ return 'sse';
9
+ if (s === 'http' || s === 'streamable-http' || s === 'streamable_http')
10
+ return 'http';
11
+ if (s === 'stdio')
12
+ return 'stdio';
13
+ if (typeof raw.url === 'string')
14
+ return 'http';
15
+ if (typeof raw.command === 'string')
16
+ return 'stdio';
17
+ return 'stdio';
18
+ };
19
+ const envKeysOf = (env) => {
20
+ if (env && typeof env === 'object' && !Array.isArray(env)) {
21
+ return Object.keys(env).sort();
22
+ }
23
+ return undefined;
24
+ };
25
+ export const normalizeEntry = (name, raw, sourceScope, sourcePath) => {
26
+ const transport = inferTransport(raw);
27
+ const command = typeof raw.command === 'string' ? raw.command : undefined;
28
+ const args = isStringArray(raw.args) ? raw.args : undefined;
29
+ const url = typeof raw.url === 'string' ? raw.url : undefined;
30
+ const envKeys = envKeysOf(raw.env);
31
+ return {
32
+ name,
33
+ transport,
34
+ command,
35
+ args,
36
+ url,
37
+ envKeys,
38
+ configHash: hashConfig({ transport, command, args, url, envKeys }),
39
+ sourceScope,
40
+ sourcePath,
41
+ };
42
+ };
43
+ export const extractFromMcpServersField = (data, field, sourceScope, sourcePath) => {
44
+ if (!data || typeof data !== 'object')
45
+ return [];
46
+ const mcp = data[field];
47
+ if (!mcp || typeof mcp !== 'object' || Array.isArray(mcp))
48
+ return [];
49
+ return Object.entries(mcp)
50
+ .filter(([, v]) => v && typeof v === 'object')
51
+ .map(([name, raw]) => normalizeEntry(name, raw, sourceScope, sourcePath));
52
+ };
53
+ export const readJsonIfExists = async (path) => {
54
+ try {
55
+ const text = await readFile(path, 'utf-8');
56
+ return JSON.parse(text);
57
+ }
58
+ catch (err) {
59
+ const code = err.code;
60
+ if (code === 'ENOENT')
61
+ return null;
62
+ throw err;
63
+ }
64
+ };
65
+ export const readTextIfExists = async (path) => {
66
+ try {
67
+ return await readFile(path, 'utf-8');
68
+ }
69
+ catch (err) {
70
+ const code = err.code;
71
+ if (code === 'ENOENT')
72
+ return null;
73
+ throw err;
74
+ }
75
+ };
76
+ //# sourceMappingURL=_normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_normalize.js","sourceRoot":"","sources":["../../src/scanners/_normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { AgentSnapshot } from '../types.js';
2
+ /**
3
+ * ChatGPT Desktop / ChatGPT connectors のスキャナー (placeholder)。
4
+ *
5
+ * 現状の制限:
6
+ * ChatGPT Desktop (macOS) の connector 一覧は
7
+ * `~/Library/Application Support/com.openai.chat/connector-apps-<uuid>/apps.data` に
8
+ * **暗号化されたバイナリ** で保存されており、平文の MCP 設定として読めない。
9
+ * 暗号鍵は OS Keychain (`com.openai.chat`) に格納されている可能性が高く、
10
+ * ユーザー同意なくアクセスするのは不適切。
11
+ *
12
+ * したがって本スキャナーは「ChatGPT Desktop が **インストールされている**」ことだけを
13
+ * 検出し、agent_kind='chatgpt' の空 snapshot を返す。MCP リストの取得は将来 OpenAI 側で
14
+ * 公開 API が用意されるか、ユーザー操作で明示的に export してもらう必要がある。
15
+ */
16
+ export declare const chatgptDataDir: (home?: string, os?: NodeJS.Platform) => string | null;
17
+ export declare const scanChatgpt: (options?: {
18
+ home?: string;
19
+ platform?: NodeJS.Platform;
20
+ }) => Promise<AgentSnapshot | null>;