@untitled-devs/wasla 0.1.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +14 -15
  2. package/dist/{utils → apps/cli/src}/cli-output.d.ts +1 -1
  3. package/dist/{cli → apps/cli/src}/commands/config.js +2 -2
  4. package/dist/{cli → apps/cli/src}/commands/install.js +2 -2
  5. package/dist/{cli → apps/cli/src}/commands/register.js +5 -5
  6. package/dist/{cli → apps/cli/src}/commands/status.js +6 -6
  7. package/dist/{cli → apps/cli/src}/commands/sync-to.js +5 -5
  8. package/dist/{cli → apps/cli/src}/commands/sync.js +5 -5
  9. package/dist/{cli → apps/cli/src}/commands/watch.js +6 -6
  10. package/dist/{cli → apps/cli/src}/index.js +19 -7
  11. package/dist/{cli/commands/visualizer.d.ts → apps/cli/src/server/visualizer-server.d.ts} +0 -1
  12. package/dist/{cli/commands/visualizer.js → apps/cli/src/server/visualizer-server.js} +85 -31
  13. package/dist/{adapters → packages/adapters/src}/base.d.ts +1 -1
  14. package/dist/{adapters → packages/adapters/src}/claude.d.ts +1 -1
  15. package/dist/{adapters → packages/adapters/src}/claude.js +2 -2
  16. package/dist/{adapters → packages/adapters/src}/cursor.d.ts +1 -1
  17. package/dist/{adapters → packages/adapters/src}/cursor.js +2 -2
  18. package/dist/{adapters → packages/adapters/src}/factory.d.ts +1 -1
  19. package/dist/{adapters → packages/adapters/src}/gemini.d.ts +1 -1
  20. package/dist/{adapters → packages/adapters/src}/gemini.js +2 -2
  21. package/dist/{adapters → packages/adapters/src}/github-copilot-cli.d.ts +1 -1
  22. package/dist/{adapters → packages/adapters/src}/github-copilot-cli.js +2 -2
  23. package/dist/{adapters → packages/adapters/src}/github-copilot.d.ts +1 -1
  24. package/dist/{adapters → packages/adapters/src}/github-copilot.js +2 -2
  25. package/dist/{adapters → packages/adapters/src}/openclaw.d.ts +1 -1
  26. package/dist/{adapters → packages/adapters/src}/openclaw.js +2 -2
  27. package/dist/{adapters → packages/adapters/src}/opencode.d.ts +1 -1
  28. package/dist/{adapters → packages/adapters/src}/opencode.js +2 -2
  29. package/dist/{core → packages/core/src}/registry.d.ts +1 -1
  30. package/dist/{core → packages/core/src}/registry.js +2 -2
  31. package/dist/{core → packages/core/src}/visualizer-types.d.ts +8 -0
  32. package/dist/{syncer → packages/sync/src}/index.d.ts +3 -3
  33. package/dist/{syncer → packages/sync/src}/index.js +4 -4
  34. package/dist/{core → packages/sync/src}/scanner.d.ts +1 -1
  35. package/dist/{core → packages/sync/src}/scanner.js +3 -3
  36. package/dist/visualizer/assets/index-cU_xphSj.js +144 -0
  37. package/{src/visualizer/dist → dist/visualizer}/index.html +1 -1
  38. package/package.json +73 -62
  39. package/src/visualizer/dist/assets/index-C6aJB2Yl.js +0 -144
  40. /package/dist/{utils → apps/cli/src}/cli-output.js +0 -0
  41. /package/dist/{cli → apps/cli/src}/commands/config.d.ts +0 -0
  42. /package/dist/{cli → apps/cli/src}/commands/install.d.ts +0 -0
  43. /package/dist/{cli → apps/cli/src}/commands/register.d.ts +0 -0
  44. /package/dist/{cli → apps/cli/src}/commands/status.d.ts +0 -0
  45. /package/dist/{cli → apps/cli/src}/commands/sync-to.d.ts +0 -0
  46. /package/dist/{cli → apps/cli/src}/commands/sync.d.ts +0 -0
  47. /package/dist/{cli → apps/cli/src}/commands/watch.d.ts +0 -0
  48. /package/dist/{cli → apps/cli/src}/index.d.ts +0 -0
  49. /package/dist/{adapters → packages/adapters/src}/base.js +0 -0
  50. /package/dist/{adapters → packages/adapters/src}/factory.js +0 -0
  51. /package/dist/{core → packages/core/src}/types.d.ts +0 -0
  52. /package/dist/{core → packages/core/src}/types.js +0 -0
  53. /package/dist/{core → packages/core/src}/visualizer-types.js +0 -0
  54. /package/dist/{utils → packages/shared/src}/config.d.ts +0 -0
  55. /package/dist/{utils → packages/shared/src}/config.js +0 -0
  56. /package/dist/{utils → packages/shared/src}/fs.d.ts +0 -0
  57. /package/dist/{utils → packages/shared/src}/fs.js +0 -0
  58. /package/dist/{utils → packages/shared/src}/paths.d.ts +0 -0
  59. /package/dist/{utils → packages/shared/src}/paths.js +0 -0
  60. /package/{src/visualizer/dist → dist/visualizer}/logo.png +0 -0
package/README.md CHANGED
@@ -13,6 +13,9 @@
13
13
 
14
14
  [![MIT License](https://img.shields.io/badge/license-MIT-00C896?style=flat-square)](LICENSE)
15
15
  [![GitHub](https://img.shields.io/badge/github-The--Untitled--Org-00C896?style=flat-square&logo=github)](https://github.com/The-Untitled-Org/wasla-genie)
16
+ [![npm version](https://img.shields.io/npm/v/@untitled-devs/wasla?style=flat-square&logo=npm)](https://www.npmjs.com/package/@untitled-devs/wasla)
17
+ [![npm downloads](https://img.shields.io/npm/dm/@untitled-devs/wasla?style=flat-square&logo=npm)](https://www.npmjs.com/package/@untitled-devs/wasla)
18
+ [![GitHub Release](https://img.shields.io/github/v/release/The-Untitled-Org/wasla-genie?style=flat-square)](https://github.com/The-Untitled-Org/wasla-genie/releases)
16
19
  [![CI & Docs Deployment](https://github.com/The-Untitled-Org/wasla-genie/actions/workflows/ci-docs.yml/badge.svg)](https://github.com/The-Untitled-Org/wasla-genie/actions/workflows/ci-docs.yml)
17
20
  [![Coverage](https://codecov.io/gh/The-Untitled-Org/wasla-genie/branch/main/graph/badge.svg)](https://codecov.io/gh/The-Untitled-Org/wasla-genie)
18
21
  [![Status](https://img.shields.io/badge/status-alpha-orange?style=flat-square)]()
@@ -378,22 +381,17 @@ Nothing is forced. Centralization is a convenience, not a requirement.
378
381
 
379
382
  ```
380
383
  wasla-genie/
381
- ├── src/
382
- │ ├── cli/ # CLI entry point and commands
383
- ├── scanner/ # Scans known tool config directories
384
- ├── registry/ # Builds and maintains the asset registry
385
- │ ├── syncer/ # Writes and tracks stub files
386
- │ ├── watcher/ # Daemon / filesystem watcher
387
- └── adapters/ # Per-tool directory knowledge + stub format
388
- ├── claude.js
389
- ├── gemini.js
390
- ├── codex.js
391
- │ ├── openclaw.js
392
- │ └── hermes.js
384
+ ├── apps/
385
+ │ ├── cli/src/ # CLI commands and visualizer server
386
+ └── visualizer/src/ # React visualizer
387
+ ├── packages/
388
+ │ ├── adapters/src/ # Per-tool directory knowledge + stub format
389
+ │ ├── core/src/ # Registry, scanner, and shared types
390
+ ├── shared/src/ # Shared config, filesystem, and path helpers
391
+ └── sync/src/ # Sync orchestration and filesystem watcher
392
+ ├── tests/
393
+ ├── scripts/
393
394
  ├── docs/
394
- │ ├── how-stubs-work.md
395
- │ ├── adapters.md
396
- │ └── roadmap.md
397
395
  ├── package.json
398
396
  └── README.md
399
397
  ```
@@ -418,6 +416,7 @@ Nothing is ever duplicated.
418
416
  git clone https://github.com/The-Untitled-Org/wasla-genie
419
417
  cd wasla-genie
420
418
  npm install
419
+ npm run visualizer:install
421
420
  npm run dev
422
421
  ```
423
422
 
@@ -1,4 +1,4 @@
1
- import type { Asset } from '../core/types.js';
1
+ import type { Asset } from '#core/types.js';
2
2
  export declare function banner(): void;
3
3
  export declare function success(message: string): void;
4
4
  export declare function error(message: string): void;
@@ -1,6 +1,6 @@
1
- import { section, success, error, spacer, info } from '../../utils/cli-output.js';
1
+ import { section, success, error, spacer, info } from '../cli-output.js';
2
2
  import prompts from 'prompts';
3
- import { getConfigPath, getConfiguredRegistryPath, readConfiguredScope, writeConfiguredScope, } from '../../utils/config.js';
3
+ import { getConfigPath, getConfiguredRegistryPath, readConfiguredScope, writeConfiguredScope, } from '#shared/config.js';
4
4
  export async function configCommand(options) {
5
5
  try {
6
6
  const currentScope = await readConfiguredScope();
@@ -1,5 +1,5 @@
1
- import { section, success, error, highlight, spacer } from '../../utils/cli-output.js';
2
- import { readConfiguredScope } from '../../utils/config.js';
1
+ import { section, success, error, highlight, spacer } from '../cli-output.js';
2
+ import { readConfiguredScope } from '#shared/config.js';
3
3
  export async function installCommand() {
4
4
  try {
5
5
  section('Preparing WaslaGenie CLI...');
@@ -1,8 +1,8 @@
1
- import { getInstalledAdapters } from '../../adapters/factory.js';
2
- import { section, success, error, warning, highlight, spacer } from '../../utils/cli-output.js';
3
- import { getRegistryDir } from '../../utils/paths.js';
4
- import { ensureDir } from '../../utils/fs.js';
5
- import { requireConfiguredScope } from '../../utils/config.js';
1
+ import { getInstalledAdapters } from '#adapters/factory.js';
2
+ import { section, success, error, warning, highlight, spacer } from '../cli-output.js';
3
+ import { getRegistryDir } from '#shared/paths.js';
4
+ import { ensureDir } from '#shared/fs.js';
5
+ import { requireConfiguredScope } from '#shared/config.js';
6
6
  export async function registerCommand(options = {}) {
7
7
  try {
8
8
  section('Detecting installed orchestrators...');
@@ -1,9 +1,9 @@
1
- import { RegistryManager } from '../../core/registry.js';
2
- import { Scanner } from '../../core/scanner.js';
3
- import { assetList, section, error, info, metric, spacer } from '../../utils/cli-output.js';
4
- import { fileExists } from '../../utils/fs.js';
5
- import { getRegistryPath } from '../../utils/paths.js';
6
- import { requireConfiguredScope } from '../../utils/config.js';
1
+ import { RegistryManager } from '#core/registry.js';
2
+ import { Scanner } from '#sync/scanner.js';
3
+ import { assetList, section, error, info, metric, spacer } from '../cli-output.js';
4
+ import { fileExists } from '#shared/fs.js';
5
+ import { getRegistryPath } from '#shared/paths.js';
6
+ import { requireConfiguredScope } from '#shared/config.js';
7
7
  export async function statusCommand() {
8
8
  try {
9
9
  const scope = await requireConfiguredScope();
@@ -1,8 +1,8 @@
1
- import { RegistryManager } from '../../core/registry.js';
2
- import { Scanner } from '../../core/scanner.js';
3
- import { Syncer } from '../../syncer/index.js';
4
- import { section, error, highlight, spacer } from '../../utils/cli-output.js';
5
- import { requireConfiguredScope } from '../../utils/config.js';
1
+ import { RegistryManager } from '#core/registry.js';
2
+ import { Scanner } from '#sync/scanner.js';
3
+ import { Syncer } from '#sync/index.js';
4
+ import { section, error, highlight, spacer } from '../cli-output.js';
5
+ import { requireConfiguredScope } from '#shared/config.js';
6
6
  export async function syncToCommand(options) {
7
7
  try {
8
8
  const scope = await requireConfiguredScope();
@@ -1,8 +1,8 @@
1
- import { RegistryManager } from '../../core/registry.js';
2
- import { Scanner } from '../../core/scanner.js';
3
- import { Syncer } from '../../syncer/index.js';
4
- import { assetList, bulletPoint, section, error, highlight, info, metric, spacer, } from '../../utils/cli-output.js';
5
- import { getConfiguredRegistryPath, requireConfiguredScope } from '../../utils/config.js';
1
+ import { RegistryManager } from '#core/registry.js';
2
+ import { Scanner } from '#sync/scanner.js';
3
+ import { Syncer } from '#sync/index.js';
4
+ import { assetList, bulletPoint, section, error, highlight, info, metric, spacer, } from '../cli-output.js';
5
+ import { getConfiguredRegistryPath, requireConfiguredScope } from '#shared/config.js';
6
6
  import { configCommand } from './config.js';
7
7
  export async function syncCommand(options = {}) {
8
8
  try {
@@ -1,10 +1,10 @@
1
1
  import chokidar from 'chokidar';
2
- import { RegistryManager } from '../../core/registry.js';
3
- import { Scanner } from '../../core/scanner.js';
4
- import { Syncer } from '../../syncer/index.js';
5
- import { getAllAdapters } from '../../adapters/factory.js';
6
- import { section, success, error, info, spacer } from '../../utils/cli-output.js';
7
- import { requireConfiguredScope } from '../../utils/config.js';
2
+ import { RegistryManager } from '#core/registry.js';
3
+ import { Scanner } from '#sync/scanner.js';
4
+ import { Syncer } from '#sync/index.js';
5
+ import { getAllAdapters } from '#adapters/factory.js';
6
+ import { section, success, error, info, spacer } from '../cli-output.js';
7
+ import { requireConfiguredScope } from '#shared/config.js';
8
8
  export async function watchCommand() {
9
9
  try {
10
10
  const scope = await requireConfiguredScope();
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { readFileSync } from 'fs';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { dirname, join } from 'path';
5
+ import { fileURLToPath } from 'url';
4
6
  import { installCommand } from './commands/install.js';
5
7
  import { registerCommand } from './commands/register.js';
6
8
  import { syncCommand } from './commands/sync.js';
@@ -8,14 +10,26 @@ import { syncToCommand } from './commands/sync-to.js';
8
10
  import { statusCommand } from './commands/status.js';
9
11
  import { configCommand } from './commands/config.js';
10
12
  import { watchCommand } from './commands/watch.js';
11
- import { visualizerCommand } from './commands/visualizer.js';
12
- import { banner } from '../utils/cli-output.js';
13
+ import { visualizerCommand } from './server/visualizer-server.js';
14
+ import { banner } from './cli-output.js';
13
15
  const program = new Command();
14
- const packageVersion = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf-8'));
16
+ function readPackageVersion(moduleUrl) {
17
+ let directory = dirname(fileURLToPath(moduleUrl));
18
+ while (true) {
19
+ const packagePath = join(directory, 'package.json');
20
+ if (existsSync(packagePath)) {
21
+ return JSON.parse(readFileSync(packagePath, 'utf-8')).version;
22
+ }
23
+ const parent = dirname(directory);
24
+ if (parent === directory)
25
+ throw new Error('Cannot find package.json');
26
+ directory = parent;
27
+ }
28
+ }
15
29
  program
16
30
  .name('waslagenie')
17
31
  .description('Universal synchronization layer for AI agent orchestrators')
18
- .version(packageVersion.version);
32
+ .version(readPackageVersion(import.meta.url));
19
33
  program.addCommand(new Command('install').description('Prepare WaslaGenie CLI state').action(installCommand));
20
34
  program.addCommand(new Command('register')
21
35
  .option('--to <targets>', 'Target provider(s), comma-separated. Example: claude,gemini')
@@ -41,13 +55,11 @@ program.addCommand(new Command('config')
41
55
  }));
42
56
  program.addCommand(new Command('watch').description('Watch for changes and auto-sync').action(() => watchCommand()));
43
57
  program.addCommand(new Command('visualizer')
44
- .option('--host <host>', 'Host to bind', '127.0.0.1')
45
58
  .option('--port <port>', 'Port to bind', '4072')
46
59
  .option('--no-open', 'Do not open browser automatically')
47
60
  .description('Open interactive sync visualizer with built-in backend')
48
61
  .action((options) => visualizerCommand(options)));
49
62
  program.addCommand(new Command('ui')
50
- .option('--host <host>', 'Host to bind', '127.0.0.1')
51
63
  .option('--port <port>', 'Port to bind', '4072')
52
64
  .option('--no-open', 'Do not open browser automatically')
53
65
  .description('Alias for `visualizer`')
@@ -1,7 +1,6 @@
1
1
  export declare function resolveVisualizerDist(moduleUrl: string): string;
2
2
  interface VisualizerOptions {
3
3
  port?: string;
4
- host?: string;
5
4
  noOpen?: boolean;
6
5
  }
7
6
  export declare const PROVIDER_ICONS: Record<string, string>;
@@ -5,20 +5,25 @@ import { readFile } from 'fs/promises';
5
5
  import { existsSync } from 'fs';
6
6
  import { exec } from 'child_process';
7
7
  import { WebSocketServer } from 'ws';
8
- import { Scanner } from '../../core/scanner.js';
9
- import { RegistryManager } from '../../core/registry.js';
10
- import { getInstalledAdapters } from '../../adapters/factory.js';
11
- import { error, highlight, info, section, spacer } from '../../utils/cli-output.js';
12
- import { Syncer } from '../../syncer/index.js';
13
- import { requireConfiguredScope } from '../../utils/config.js';
8
+ import { RegistryManager } from '#core/registry.js';
9
+ import { getAllAdapters, getInstalledAdapters } from '#adapters/factory.js';
10
+ import { error, highlight, info, section, spacer } from '../cli-output.js';
11
+ import { Syncer } from '#sync/index.js';
12
+ import { Scanner } from '#sync/scanner.js';
13
+ import { requireConfiguredScope } from '#shared/config.js';
14
14
  export function resolveVisualizerDist(moduleUrl) {
15
- return resolve(dirname(fileURLToPath(moduleUrl)), '../../../src/visualizer/dist');
15
+ return resolve(dirname(fileURLToPath(moduleUrl)), '../../../../visualizer');
16
16
  }
17
- async function getEntityContent(scope, type, name) {
17
+ async function getEntityContent(scope, type, name, providerId) {
18
18
  const scanner = new Scanner(scope);
19
19
  await scanner.initialize();
20
- const discovered = await scanner.scanAllTools();
21
- const target = discovered.find((item) => mapType(item.type) === type && item.name === name);
20
+ const assetType = mapEntityType(type);
21
+ const discovered = providerId === 'waslagenie'
22
+ ? await scanner.scanAllTools([assetType])
23
+ : await scanner.scanTool(providerId, [assetType]);
24
+ const target = discovered
25
+ .filter((item) => mapType(item.type) === type && item.name === name)
26
+ .sort((a, b) => b.modifiedAt - a.modifiedAt)[0];
22
27
  if (!target)
23
28
  return null;
24
29
  if (target.content)
@@ -30,11 +35,23 @@ async function getEntityContent(scope, type, name) {
30
35
  return null;
31
36
  }
32
37
  }
38
+ function isVisualizerEntityType(type) {
39
+ return type === 'instruction' || type === 'agent' || type === 'skill' || type === 'mcp';
40
+ }
33
41
  function mapType(type) {
34
42
  if (type === 'context')
35
43
  return 'instruction';
36
44
  return type;
37
45
  }
46
+ function isAllowedOrigin(origin, port) {
47
+ if (!origin)
48
+ return true;
49
+ return origin === `http://127.0.0.1:${port}` || origin === `http://localhost:${port}`;
50
+ }
51
+ function isKnownProvider(scope, providerId) {
52
+ return (providerId === 'waslagenie' ||
53
+ getAllAdapters(scope).some((adapter) => adapter.name === providerId));
54
+ }
38
55
  function mapEntityType(type) {
39
56
  if (type === 'instruction')
40
57
  return 'context';
@@ -85,12 +102,20 @@ async function buildConfig(scope) {
85
102
  await scanner.initialize();
86
103
  const discovered = await scanner.scanAllTools();
87
104
  const installed = await getInstalledAdapters(scope);
105
+ const installedNames = new Set(installed.map((adapter) => adapter.name));
88
106
  const providers = [
89
- { id: 'waslagenie', title: 'WaslaGenie', iconUrl: PROVIDER_ICONS.waslagenie, isHub: true },
90
- ...installed.map((adapter) => ({
107
+ {
108
+ id: 'waslagenie',
109
+ title: 'WaslaGenie',
110
+ iconUrl: PROVIDER_ICONS.waslagenie,
111
+ isHub: true,
112
+ isInstalled: true,
113
+ },
114
+ ...getAllAdapters(scope).map((adapter) => ({
91
115
  id: adapter.name,
92
116
  title: adapter.displayName,
93
117
  iconUrl: PROVIDER_ICONS[adapter.name],
118
+ isInstalled: installedNames.has(adapter.name),
94
119
  })),
95
120
  ];
96
121
  const entityMap = new Map();
@@ -129,7 +154,6 @@ async function buildConfig(scope) {
129
154
  providers,
130
155
  entities: grouped,
131
156
  connections: Array.from(connectionMap.values()),
132
- websocketUrl: '',
133
157
  };
134
158
  }
135
159
  function sendJson(res, statusCode, body) {
@@ -140,7 +164,7 @@ function sendJson(res, statusCode, body) {
140
164
  export async function visualizerCommand(options) {
141
165
  try {
142
166
  const scope = await requireConfiguredScope();
143
- const host = options.host || '127.0.0.1';
167
+ const host = '127.0.0.1';
144
168
  const port = Number(options.port || 4072);
145
169
  const shouldOpen = options.noOpen !== true;
146
170
  section('Starting visualizer...');
@@ -161,36 +185,43 @@ export async function visualizerCommand(options) {
161
185
  };
162
186
  const server = createServer(async (req, res) => {
163
187
  const url = req.url || '/';
164
- if (req.method === 'GET' && url === '/api/config') {
165
- const freshConfig = await buildConfig(scope);
166
- freshConfig.websocketUrl = `ws://${host}:${port}`;
167
- sendJson(res, 200, freshConfig);
188
+ const parsed = new URL(`http://${host}:${port}${url}`);
189
+ const pathname = parsed.pathname;
190
+ if (!isAllowedOrigin(req.headers.origin, port)) {
191
+ sendJson(res, 403, { error: 'Forbidden origin' });
168
192
  return;
169
193
  }
170
- if (req.method === 'GET' && url.startsWith('/api/entity-content')) {
171
- const parsed = new URL(`http://${host}:${port}${url}`);
194
+ if (req.method === 'GET' && pathname === '/api/config') {
195
+ sendJson(res, 200, await buildConfig(scope));
196
+ return;
197
+ }
198
+ if (req.method === 'GET' && pathname === '/api/entity-content') {
172
199
  const type = parsed.searchParams.get('type');
173
200
  const name = parsed.searchParams.get('name');
174
- if (!type || !name) {
175
- sendJson(res, 400, { error: 'type and name are required' });
201
+ const providerId = parsed.searchParams.get('provider');
202
+ if (!isVisualizerEntityType(type) ||
203
+ !name ||
204
+ !providerId ||
205
+ !isKnownProvider(scope, providerId)) {
206
+ sendJson(res, 400, { error: 'valid type, name, and provider are required' });
176
207
  return;
177
208
  }
178
- const content = await getEntityContent(scope, type, name);
209
+ const content = await getEntityContent(scope, type, name, providerId);
179
210
  sendJson(res, 200, { content: content ?? '' });
180
211
  return;
181
212
  }
182
- if (req.method === 'POST' && url === '/api/shutdown') {
213
+ if (req.method === 'POST' && pathname === '/api/shutdown') {
183
214
  sendJson(res, 200, { ok: true });
184
215
  setTimeout(shutdown, 50);
185
216
  return;
186
217
  }
187
- if (url === '/' || url === '/index.html') {
218
+ if (pathname === '/' || pathname === '/index.html') {
188
219
  const html = await readFile(join(visualizerDist, 'index.html'));
189
220
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
190
221
  res.end(html);
191
222
  return;
192
223
  }
193
- const staticPath = join(visualizerDist, url.replace(/^\//, ''));
224
+ const staticPath = join(visualizerDist, pathname.replace(/^\//, ''));
194
225
  if (!staticPath.startsWith(visualizerDist) || !existsSync(staticPath)) {
195
226
  res.writeHead(404);
196
227
  res.end('Not Found');
@@ -200,15 +231,24 @@ export async function visualizerCommand(options) {
200
231
  res.writeHead(200, { 'Content-Type': mimeType(staticPath) });
201
232
  res.end(payload);
202
233
  });
203
- const wss = new WebSocketServer({ server });
234
+ const wss = new WebSocketServer({
235
+ server,
236
+ verifyClient: ({ origin }) => isAllowedOrigin(origin, port),
237
+ });
204
238
  wss.on('connection', (socket) => {
205
239
  socket.on('message', async (raw) => {
240
+ let requestId = '';
206
241
  try {
207
242
  const data = JSON.parse(raw.toString());
208
- if (data.type !== 'CONNECTION_CHANGED' || !data.payload)
243
+ if (data.type !== 'CONNECTION_CHANGED' || !data.requestId || !data.payload)
209
244
  return;
245
+ requestId = data.requestId;
210
246
  const { sourceEntity, entityType, sourceProvider, targetProvider, action } = data.payload;
211
- if (!sourceEntity || !entityType || !sourceProvider || !targetProvider || !action) {
247
+ if (!sourceEntity ||
248
+ !isVisualizerEntityType(entityType) ||
249
+ !sourceProvider ||
250
+ !targetProvider ||
251
+ (action !== 'ATTACH' && action !== 'DETACH')) {
212
252
  return;
213
253
  }
214
254
  info(`[Visualizer] ${action} ${entityType}:${sourceEntity} ${action === 'ATTACH' ? 'to' : 'from'} ${targetProvider}`);
@@ -218,9 +258,23 @@ export async function visualizerCommand(options) {
218
258
  else {
219
259
  await syncer.detachAssetFromTool(sourceEntity, mapEntityType(entityType), targetProvider);
220
260
  }
261
+ const result = {
262
+ type: 'CONNECTION_CHANGED_RESULT',
263
+ requestId,
264
+ ok: true,
265
+ };
266
+ socket.send(JSON.stringify(result));
221
267
  }
222
- catch {
223
- // Ignore invalid websocket messages from the client.
268
+ catch (err) {
269
+ if (!requestId)
270
+ return;
271
+ const result = {
272
+ type: 'CONNECTION_CHANGED_RESULT',
273
+ requestId,
274
+ ok: false,
275
+ error: err instanceof Error ? err.message : String(err),
276
+ };
277
+ socket.send(JSON.stringify(result));
224
278
  }
225
279
  });
226
280
  });
@@ -1,4 +1,4 @@
1
- import { WaslaGenieAdapter, Asset, AssetFormat } from '../core/types.js';
1
+ import { WaslaGenieAdapter, Asset, AssetFormat } from '#core/types.js';
2
2
  export declare abstract class BaseAdapter implements WaslaGenieAdapter {
3
3
  abstract name: string;
4
4
  abstract displayName: string;
@@ -1,5 +1,5 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { Asset } from '../core/types.js';
2
+ import { Asset } from '#core/types.js';
3
3
  export declare class ClaudeAdapter extends BaseAdapter {
4
4
  name: string;
5
5
  displayName: string;
@@ -1,7 +1,7 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { fileExists, writeText, ensureDir } from '../utils/fs.js';
2
+ import { fileExists, writeText, ensureDir } from '#shared/fs.js';
3
3
  import { join } from 'path';
4
- import { getToolMarkers } from '../utils/paths.js';
4
+ import { getToolMarkers } from '#shared/paths.js';
5
5
  export class ClaudeAdapter extends BaseAdapter {
6
6
  constructor(scope = 'workspace') {
7
7
  super();
@@ -1,5 +1,5 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { Asset } from '../core/types.js';
2
+ import { Asset } from '#core/types.js';
3
3
  export declare class CursorAdapter extends BaseAdapter {
4
4
  name: string;
5
5
  displayName: string;
@@ -1,7 +1,7 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { fileExists, writeText, ensureDir } from '../utils/fs.js';
2
+ import { fileExists, writeText, ensureDir } from '#shared/fs.js';
3
3
  import { dirname, join } from 'path';
4
- import { getToolMarkers } from '../utils/paths.js';
4
+ import { getToolMarkers } from '#shared/paths.js';
5
5
  export class CursorAdapter extends BaseAdapter {
6
6
  constructor(scope = 'workspace') {
7
7
  super();
@@ -1,4 +1,4 @@
1
- import { WaslaGenieAdapter } from '../core/types.js';
1
+ import { WaslaGenieAdapter } from '#core/types.js';
2
2
  export declare function getAdapter(toolName: string, scope?: 'user' | 'workspace'): WaslaGenieAdapter;
3
3
  export declare function getInstalledAdapters(scope?: 'user' | 'workspace'): Promise<WaslaGenieAdapter[]>;
4
4
  export declare function getAllAdapters(scope?: 'user' | 'workspace'): WaslaGenieAdapter[];
@@ -1,5 +1,5 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { Asset } from '../core/types.js';
2
+ import { Asset } from '#core/types.js';
3
3
  export declare class GeminiAdapter extends BaseAdapter {
4
4
  name: string;
5
5
  displayName: string;
@@ -1,7 +1,7 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { fileExists, writeText, ensureDir } from '../utils/fs.js';
2
+ import { fileExists, writeText, ensureDir } from '#shared/fs.js';
3
3
  import { dirname, join } from 'path';
4
- import { getToolMarkers } from '../utils/paths.js';
4
+ import { getToolMarkers } from '#shared/paths.js';
5
5
  export class GeminiAdapter extends BaseAdapter {
6
6
  constructor(scope = 'workspace') {
7
7
  super();
@@ -1,5 +1,5 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { Asset } from '../core/types.js';
2
+ import { Asset } from '#core/types.js';
3
3
  export declare class GithubCopilotCliAdapter extends BaseAdapter {
4
4
  name: string;
5
5
  displayName: string;
@@ -1,7 +1,7 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { fileExists, writeText, ensureDir } from '../utils/fs.js';
2
+ import { fileExists, writeText, ensureDir } from '#shared/fs.js';
3
3
  import { dirname, join } from 'path';
4
- import { getToolMarkers } from '../utils/paths.js';
4
+ import { getToolMarkers } from '#shared/paths.js';
5
5
  export class GithubCopilotCliAdapter extends BaseAdapter {
6
6
  constructor(scope = 'workspace') {
7
7
  super();
@@ -1,5 +1,5 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { Asset } from '../core/types.js';
2
+ import { Asset } from '#core/types.js';
3
3
  export declare class GithubCopilotAdapter extends BaseAdapter {
4
4
  name: string;
5
5
  displayName: string;
@@ -1,7 +1,7 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { fileExists, writeText, ensureDir } from '../utils/fs.js';
2
+ import { fileExists, writeText, ensureDir } from '#shared/fs.js';
3
3
  import { dirname, join } from 'path';
4
- import { getToolMarkers } from '../utils/paths.js';
4
+ import { getToolMarkers } from '#shared/paths.js';
5
5
  export class GithubCopilotAdapter extends BaseAdapter {
6
6
  constructor(scope = 'workspace') {
7
7
  super();
@@ -1,5 +1,5 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { Asset } from '../core/types.js';
2
+ import { Asset } from '#core/types.js';
3
3
  export declare class OpenclawAdapter extends BaseAdapter {
4
4
  name: string;
5
5
  displayName: string;
@@ -1,7 +1,7 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { fileExists, writeText, ensureDir } from '../utils/fs.js';
2
+ import { fileExists, writeText, ensureDir } from '#shared/fs.js';
3
3
  import { dirname, join } from 'path';
4
- import { getToolMarkers } from '../utils/paths.js';
4
+ import { getToolMarkers } from '#shared/paths.js';
5
5
  export class OpenclawAdapter extends BaseAdapter {
6
6
  constructor(scope = 'workspace') {
7
7
  super();
@@ -1,5 +1,5 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { Asset } from '../core/types.js';
2
+ import { Asset } from '#core/types.js';
3
3
  export declare class OpenCodeAdapter extends BaseAdapter {
4
4
  name: string;
5
5
  displayName: string;
@@ -1,7 +1,7 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { fileExists, writeText, ensureDir } from '../utils/fs.js';
2
+ import { fileExists, writeText, ensureDir } from '#shared/fs.js';
3
3
  import { dirname, join } from 'path';
4
- import { getToolMarkers } from '../utils/paths.js';
4
+ import { getToolMarkers } from '#shared/paths.js';
5
5
  export class OpenCodeAdapter extends BaseAdapter {
6
6
  constructor(scope = 'workspace') {
7
7
  super();
@@ -1,4 +1,4 @@
1
- import { Registry, Asset, Conflict } from '../core/types.js';
1
+ import { Registry, Asset, Conflict } from './types.js';
2
2
  export declare class RegistryManager {
3
3
  private scope;
4
4
  private registry;
@@ -1,5 +1,5 @@
1
- import { readJSON, writeJSON, fileExists, ensureDir } from '../utils/fs.js';
2
- import { getRegistryPath, getRegistryDir } from '../utils/paths.js';
1
+ import { readJSON, writeJSON, fileExists, ensureDir } from '#shared/fs.js';
2
+ import { getRegistryPath, getRegistryDir } from '#shared/paths.js';
3
3
  import { randomUUID } from 'crypto';
4
4
  export class RegistryManager {
5
5
  constructor(scope = 'workspace') {
@@ -10,6 +10,7 @@ export interface VisualizerProvider {
10
10
  title: string;
11
11
  iconUrl?: string;
12
12
  isHub?: boolean;
13
+ isInstalled?: boolean;
13
14
  }
14
15
  export interface VisualizerConnection {
15
16
  entityId: string;
@@ -28,6 +29,7 @@ export interface VisualizerConfiguration {
28
29
  }
29
30
  export interface ConnectionChangedMessage {
30
31
  type: 'CONNECTION_CHANGED';
32
+ requestId: string;
31
33
  payload: {
32
34
  sourceEntity: string;
33
35
  entityType: VisualizerEntityType;
@@ -36,3 +38,9 @@ export interface ConnectionChangedMessage {
36
38
  action: 'ATTACH' | 'DETACH';
37
39
  };
38
40
  }
41
+ export interface ConnectionChangedResultMessage {
42
+ type: 'CONNECTION_CHANGED_RESULT';
43
+ requestId: string;
44
+ ok: boolean;
45
+ error?: string;
46
+ }