@untitled-devs/wasla 0.1.2 → 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 (67) hide show
  1. package/README.md +34 -30
  2. package/dist/{utils → apps/cli/src}/cli-output.d.ts +4 -0
  3. package/dist/apps/cli/src/cli-output.js +98 -0
  4. package/dist/{cli → apps/cli/src}/commands/config.d.ts +1 -1
  5. package/dist/apps/cli/src/commands/config.js +62 -0
  6. package/dist/{cli → apps/cli/src}/commands/install.js +11 -6
  7. package/dist/{cli → apps/cli/src}/commands/register.js +8 -6
  8. package/dist/apps/cli/src/commands/status.d.ts +1 -0
  9. package/dist/apps/cli/src/commands/status.js +39 -0
  10. package/dist/{cli → apps/cli/src}/commands/sync-to.d.ts +0 -1
  11. package/dist/{cli → apps/cli/src}/commands/sync-to.js +7 -6
  12. package/dist/apps/cli/src/commands/sync.d.ts +5 -0
  13. package/dist/apps/cli/src/commands/sync.js +49 -0
  14. package/dist/apps/cli/src/commands/watch.d.ts +1 -0
  15. package/dist/{cli → apps/cli/src}/commands/watch.js +8 -7
  16. package/dist/{cli → apps/cli/src}/index.js +26 -16
  17. package/dist/{cli/commands/visualizer.d.ts → apps/cli/src/server/visualizer-server.d.ts} +0 -2
  18. package/dist/{cli/commands/visualizer.js → apps/cli/src/server/visualizer-server.js} +86 -31
  19. package/dist/{adapters → packages/adapters/src}/base.d.ts +1 -1
  20. package/dist/{adapters → packages/adapters/src}/claude.d.ts +1 -1
  21. package/dist/{adapters → packages/adapters/src}/claude.js +2 -2
  22. package/dist/{adapters → packages/adapters/src}/cursor.d.ts +1 -1
  23. package/dist/{adapters → packages/adapters/src}/cursor.js +2 -2
  24. package/dist/{adapters → packages/adapters/src}/factory.d.ts +1 -1
  25. package/dist/{adapters → packages/adapters/src}/gemini.d.ts +1 -1
  26. package/dist/{adapters → packages/adapters/src}/gemini.js +2 -2
  27. package/dist/{adapters → packages/adapters/src}/github-copilot-cli.d.ts +1 -1
  28. package/dist/{adapters → packages/adapters/src}/github-copilot-cli.js +2 -2
  29. package/dist/{adapters → packages/adapters/src}/github-copilot.d.ts +1 -1
  30. package/dist/{adapters → packages/adapters/src}/github-copilot.js +2 -2
  31. package/dist/{adapters → packages/adapters/src}/openclaw.d.ts +1 -1
  32. package/dist/{adapters → packages/adapters/src}/openclaw.js +2 -2
  33. package/dist/{adapters → packages/adapters/src}/opencode.d.ts +1 -1
  34. package/dist/{adapters → packages/adapters/src}/opencode.js +2 -2
  35. package/dist/{core → packages/core/src}/registry.d.ts +1 -1
  36. package/dist/{core → packages/core/src}/registry.js +2 -2
  37. package/dist/{core → packages/core/src}/types.d.ts +0 -1
  38. package/dist/{core → packages/core/src}/visualizer-types.d.ts +8 -0
  39. package/dist/packages/shared/src/config.d.ts +6 -0
  40. package/dist/packages/shared/src/config.js +34 -0
  41. package/dist/{utils → packages/shared/src}/fs.js +13 -7
  42. package/dist/{syncer → packages/sync/src}/index.d.ts +3 -3
  43. package/dist/{syncer → packages/sync/src}/index.js +4 -4
  44. package/dist/{core → packages/sync/src}/scanner.d.ts +1 -1
  45. package/dist/{core → packages/sync/src}/scanner.js +11 -6
  46. package/dist/visualizer/assets/index-cU_xphSj.js +144 -0
  47. package/{src/visualizer/dist → dist/visualizer}/index.html +1 -1
  48. package/package.json +76 -62
  49. package/dist/cli/commands/config.js +0 -65
  50. package/dist/cli/commands/status.d.ts +0 -5
  51. package/dist/cli/commands/status.js +0 -36
  52. package/dist/cli/commands/sync.d.ts +0 -5
  53. package/dist/cli/commands/sync.js +0 -26
  54. package/dist/cli/commands/watch.d.ts +0 -5
  55. package/dist/utils/cli-output.js +0 -44
  56. package/src/visualizer/dist/assets/index-C6aJB2Yl.js +0 -144
  57. /package/dist/{cli → apps/cli/src}/commands/install.d.ts +0 -0
  58. /package/dist/{cli → apps/cli/src}/commands/register.d.ts +0 -0
  59. /package/dist/{cli → apps/cli/src}/index.d.ts +0 -0
  60. /package/dist/{adapters → packages/adapters/src}/base.js +0 -0
  61. /package/dist/{adapters → packages/adapters/src}/factory.js +0 -0
  62. /package/dist/{core → packages/core/src}/types.js +0 -0
  63. /package/dist/{core → packages/core/src}/visualizer-types.js +0 -0
  64. /package/dist/{utils → packages/shared/src}/fs.d.ts +0 -0
  65. /package/dist/{utils → packages/shared/src}/paths.d.ts +0 -0
  66. /package/dist/{utils → packages/shared/src}/paths.js +0 -0
  67. /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)]()
@@ -121,15 +124,18 @@ The same pattern applies across every asset type:
121
124
  WaslaGenie is cross-platform via `npx` — no global install required:
122
125
 
123
126
  ```bash
127
+ npx @untitled-devs/wasla config --scope workspace
124
128
  npx @untitled-devs/wasla sync
125
129
  ```
126
130
 
127
- This runs the CLI directly. It does not register helper skills inside Claude, Gemini, or other tools.
131
+ Choose `workspace` or `user` once before running operational commands. This runs the CLI directly.
132
+ It does not register helper skills inside Claude, Gemini, or other tools.
128
133
 
129
134
  **Or install globally:**
130
135
 
131
136
  ```bash
132
137
  npm install -g @untitled-devs/wasla
138
+ waslagenie config --scope workspace
133
139
  waslagenie sync
134
140
  ```
135
141
 
@@ -178,10 +184,10 @@ npx @untitled-devs/wasla visualizer
178
184
  ### You (developing this repo)
179
185
 
180
186
  ```bash
181
- # Build + run sync (workspace scope) using your local source
187
+ # Build + run sync using your configured scope
182
188
  npm run sync
183
189
 
184
- # Build + run watch (workspace scope)
190
+ # Build + run watch using your configured scope
185
191
  npm run watch
186
192
  ```
187
193
 
@@ -257,14 +263,18 @@ No restart. No manual trigger. The moment something changes — it's everywhere.
257
263
 
258
264
  ### Scope — workspace or user level
259
265
 
266
+ Choose the active scope before running sync, watch, status, or the visualizer:
267
+
260
268
  ```bash
261
- # Sync only within current project workspace
262
- waslagenie sync --scope workspace
269
+ # Use the current project workspace registry
270
+ waslagenie config --scope workspace
263
271
 
264
- # Sync across your entire user space (default)
265
- waslagenie sync --scope user
272
+ # Use the user-level registry across projects
273
+ waslagenie config --scope user
266
274
  ```
267
275
 
276
+ All other commands use the saved scope automatically. They do not accept `--scope`.
277
+
268
278
  ---
269
279
 
270
280
  ### Status — see everything and where it lives
@@ -312,22 +322,19 @@ review-pr command openclaw claude ✔ gemini ✔ codex ✔ her
312
322
 
313
323
  ## 🗃️ Registry Storage
314
324
 
315
- WaslaGenie keeps its own state separately from all orchestrators. You choose the scope at install time:
325
+ WaslaGenie keeps its own state separately from all orchestrators. You choose the active scope explicitly before the first sync:
316
326
 
317
- **User-level** (default — available across all your projects):
327
+ **User-level** (available across all your projects):
318
328
  ```
319
329
  ~/.waslagenie/
320
- ├── registry.json ← every discovered asset + origin tool + stub locations
321
- ├── stubs/ log of every stub written and when
322
- └── config.json ← your scope and preferences
330
+ ├── registry.json ← user-scope assets and stub locations
331
+ └── config.json active scope preference
323
332
  ```
324
333
 
325
334
  **Workspace-level** (scoped to current project only):
326
335
  ```
327
336
  .waslagenie/
328
- ├── registry.json
329
- ├── stubs/
330
- └── config.json
337
+ └── registry.json ← workspace-scope assets and stub locations
331
338
  ```
332
339
 
333
340
  Switch anytime:
@@ -374,22 +381,17 @@ Nothing is forced. Centralization is a convenience, not a requirement.
374
381
 
375
382
  ```
376
383
  wasla-genie/
377
- ├── src/
378
- │ ├── cli/ # CLI entry point and commands
379
- ├── scanner/ # Scans known tool config directories
380
- ├── registry/ # Builds and maintains the asset registry
381
- │ ├── syncer/ # Writes and tracks stub files
382
- │ ├── watcher/ # Daemon / filesystem watcher
383
- └── adapters/ # Per-tool directory knowledge + stub format
384
- ├── claude.js
385
- ├── gemini.js
386
- ├── codex.js
387
- │ ├── openclaw.js
388
- │ └── 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/
389
394
  ├── docs/
390
- │ ├── how-stubs-work.md
391
- │ ├── adapters.md
392
- │ └── roadmap.md
393
395
  ├── package.json
394
396
  └── README.md
395
397
  ```
@@ -414,11 +416,13 @@ Nothing is ever duplicated.
414
416
  git clone https://github.com/The-Untitled-Org/wasla-genie
415
417
  cd wasla-genie
416
418
  npm install
419
+ npm run visualizer:install
417
420
  npm run dev
418
421
  ```
419
422
 
420
423
  - [Contributing Guide](CONTRIBUTING.md)
421
424
  - [Architecture Docs](docs/docs/architecture/index.md)
425
+ - [Release Guide](RELEASING.md)
422
426
 
423
427
  ---
424
428
 
@@ -1,8 +1,12 @@
1
+ import type { Asset } from '#core/types.js';
2
+ export declare function banner(): void;
1
3
  export declare function success(message: string): void;
2
4
  export declare function error(message: string): void;
3
5
  export declare function info(message: string): void;
4
6
  export declare function warning(message: string): void;
5
7
  export declare function highlight(message: string): void;
8
+ export declare function metric(label: string, value: string | number): void;
9
+ export declare function assetList(assets: Asset[], includeModifiedAt?: boolean, activeProviders?: string[]): void;
6
10
  export declare function step(title: string): void;
7
11
  export declare function section(title: string): void;
8
12
  export declare function table(rows: string[][], columnWidths?: number[]): void;
@@ -0,0 +1,98 @@
1
+ const ansi = {
2
+ reset: '\u001b[0m',
3
+ bold: '\u001b[1m',
4
+ cyan: '\u001b[36m',
5
+ blue: '\u001b[34m',
6
+ green: '\u001b[32m',
7
+ red: '\u001b[31m',
8
+ yellow: '\u001b[33m',
9
+ magenta: '\u001b[35m',
10
+ };
11
+ function color(text, ...codes) {
12
+ return `${codes.join('')}${text}${ansi.reset}`;
13
+ }
14
+ export function banner() {
15
+ console.log(color(`
16
+ __ __ _ ____ _
17
+ \\ \\ / /_ _ ___| | __ _ / ___| ___ _ __ (_) ___
18
+ \\ \\ /\\ / / _\` / __| |/ _\` | | _ / _ \\ '_ \\| |/ _ \\
19
+ \\ V V / (_| \\__ \\ | (_| | |_| | __/ | | | | __/
20
+ \\_/\\_/ \\__,_|___/_|\\__,_|\\____|\\___|_| |_|_|\\___|
21
+ `, ansi.bold, ansi.cyan));
22
+ }
23
+ export function success(message) {
24
+ console.log(color(`✔ ${message}`, ansi.green));
25
+ }
26
+ export function error(message) {
27
+ console.error(color(`✗ ${message}`, ansi.red));
28
+ }
29
+ export function info(message) {
30
+ console.log(color(`ℹ ${message}`, ansi.blue));
31
+ }
32
+ export function warning(message) {
33
+ console.log(color(`⚠ ${message}`, ansi.yellow));
34
+ }
35
+ export function highlight(message) {
36
+ console.log(color(`✨ ${message}`, ansi.magenta, ansi.bold));
37
+ }
38
+ export function metric(label, value) {
39
+ console.log(` ${color(label.padEnd(20), ansi.blue)} ${color(String(value), ansi.bold)}`);
40
+ }
41
+ export function assetList(assets, includeModifiedAt = false, activeProviders) {
42
+ const assetTypes = ['agent', 'skill', 'mcp', 'context'];
43
+ const headings = {
44
+ agent: 'AGENTS',
45
+ skill: 'SKILLS',
46
+ mcp: 'MCP SERVERS',
47
+ context: 'CONTEXT FILES',
48
+ };
49
+ const activeProviderSet = activeProviders ? new Set(activeProviders) : null;
50
+ for (const type of assetTypes) {
51
+ const typedAssets = assets
52
+ .filter((asset) => asset.type === type)
53
+ .sort((a, b) => a.name.localeCompare(b.name));
54
+ if (typedAssets.length === 0)
55
+ continue;
56
+ console.log(color(` ${headings[type]} (${typedAssets.length})`, ansi.cyan, ansi.bold));
57
+ for (const asset of typedAssets) {
58
+ const providers = [...new Set(asset.stubs.map((stub) => stub.tool))]
59
+ .sort()
60
+ .filter((provider) => !activeProviderSet || activeProviderSet.has(provider));
61
+ console.log(` ${color('•', ansi.green)} ${color(asset.name, ansi.bold)}`);
62
+ console.log(` ${color('Mirrors:', ansi.blue)} ${providers.join(', ') || 'none'}`);
63
+ if (includeModifiedAt) {
64
+ console.log(` ${color('Updated:', ansi.blue)} ${new Date(asset.last_modified_at).toLocaleString()}`);
65
+ }
66
+ }
67
+ spacer();
68
+ }
69
+ }
70
+ export function step(title) {
71
+ console.log(`\n${title}`);
72
+ }
73
+ export function section(title) {
74
+ console.log(color(`\n🔍 ${title}`, ansi.blue, ansi.bold));
75
+ }
76
+ export function table(rows, columnWidths) {
77
+ if (rows.length === 0)
78
+ return;
79
+ // Calculate column widths if not provided
80
+ const widths = columnWidths ||
81
+ rows[0].map((_, i) => {
82
+ return Math.max(...rows.map((row) => (row[i] || '').length));
83
+ });
84
+ rows.forEach((row) => {
85
+ const line = row.map((cell, i) => (cell || '').padEnd(widths[i] || 10)).join(' ');
86
+ console.log(line);
87
+ });
88
+ }
89
+ export function spacer() {
90
+ console.log('');
91
+ }
92
+ export function bulletPoint(text, indent = 0) {
93
+ const spaces = ' '.repeat(indent);
94
+ console.log(`${spaces}• ${text}`);
95
+ }
96
+ export function code(text) {
97
+ return `\`${text}\``;
98
+ }
@@ -2,5 +2,5 @@ interface ConfigOptions {
2
2
  scope?: string;
3
3
  show?: boolean;
4
4
  }
5
- export declare function configCommand(options: ConfigOptions): Promise<void>;
5
+ export declare function configCommand(options: ConfigOptions): Promise<boolean>;
6
6
  export {};
@@ -0,0 +1,62 @@
1
+ import { section, success, error, spacer, info } from '../cli-output.js';
2
+ import prompts from 'prompts';
3
+ import { getConfigPath, getConfiguredRegistryPath, readConfiguredScope, writeConfiguredScope, } from '#shared/config.js';
4
+ export async function configCommand(options) {
5
+ try {
6
+ const currentScope = await readConfiguredScope();
7
+ // Show current config
8
+ if (options.show) {
9
+ section('Current Configuration');
10
+ spacer();
11
+ showConfig(currentScope);
12
+ spacer();
13
+ return true;
14
+ }
15
+ // Change scope if requested
16
+ if (options.scope) {
17
+ const newScope = options.scope;
18
+ if (newScope !== 'user' && newScope !== 'workspace') {
19
+ error('Invalid scope. Use: user or workspace');
20
+ process.exit(1);
21
+ }
22
+ await saveScope(newScope);
23
+ return true;
24
+ }
25
+ section('Configure Scope');
26
+ spacer();
27
+ const response = await prompts({
28
+ type: 'select',
29
+ name: 'scope',
30
+ message: 'Where should WaslaGenie store and sync assets?',
31
+ choices: [
32
+ { title: 'Workspace - current project only', value: 'workspace' },
33
+ { title: 'User - available across all projects', value: 'user' },
34
+ ],
35
+ initial: currentScope === 'user' ? 1 : 0,
36
+ });
37
+ const scope = response.scope;
38
+ if (!scope) {
39
+ info('Configuration cancelled');
40
+ return false;
41
+ }
42
+ await saveScope(scope);
43
+ return true;
44
+ }
45
+ catch (err) {
46
+ error(`Config failed: ${err}`);
47
+ process.exit(1);
48
+ }
49
+ }
50
+ async function saveScope(scope) {
51
+ await writeConfiguredScope(scope);
52
+ success(`Scope changed to: ${scope}`);
53
+ info(`Config: ${getConfigPath()}`);
54
+ info(`Registry: ${getConfiguredRegistryPath(scope)}`);
55
+ spacer();
56
+ }
57
+ function showConfig(scope) {
58
+ info(`Scope: ${scope ?? 'not configured'}`);
59
+ info(`Config: ${getConfigPath()}`);
60
+ if (scope)
61
+ info(`Registry: ${getConfiguredRegistryPath(scope)}`);
62
+ }
@@ -1,13 +1,18 @@
1
- import { section, success, error, highlight, spacer } from '../../utils/cli-output.js';
2
- import { getRegistryDir } from '../../utils/paths.js';
3
- import { ensureDir } from '../../utils/fs.js';
1
+ import { section, success, error, highlight, spacer } from '../cli-output.js';
2
+ import { readConfiguredScope } from '#shared/config.js';
4
3
  export async function installCommand() {
5
4
  try {
6
5
  section('Preparing WaslaGenie CLI...');
7
6
  spacer();
8
- // Ensure registry directory exists
9
- await ensureDir(getRegistryDir('user'));
10
- success('Registry directory ready');
7
+ const scope = await readConfiguredScope();
8
+ if (scope) {
9
+ success(`Scope configured: ${scope}`);
10
+ }
11
+ else {
12
+ console.log('Choose a scope before running sync:');
13
+ console.log(' waslagenie config --scope user');
14
+ console.log(' waslagenie config --scope workspace');
15
+ }
11
16
  spacer();
12
17
  highlight('CLI setup complete!');
13
18
  console.log('');
@@ -1,12 +1,14 @@
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';
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';
5
6
  export async function registerCommand(options = {}) {
6
7
  try {
7
8
  section('Detecting installed orchestrators...');
8
9
  spacer();
9
- const adapters = await getInstalledAdapters();
10
+ const scope = await requireConfiguredScope();
11
+ const adapters = await getInstalledAdapters(scope);
10
12
  if (adapters.length === 0) {
11
13
  error('No supported orchestrators found');
12
14
  warning('Please install Claude Code, Gemini CLI, or OpenCode first');
@@ -35,7 +37,7 @@ export async function registerCommand(options = {}) {
35
37
  spacer();
36
38
  section('Registering WaslaGenie helper skills...');
37
39
  spacer();
38
- await ensureDir(getRegistryDir('user'));
40
+ await ensureDir(getRegistryDir(scope));
39
41
  for (const adapter of targets) {
40
42
  try {
41
43
  await adapter.installSkill();
@@ -0,0 +1 @@
1
+ export declare function statusCommand(): Promise<void>;
@@ -0,0 +1,39 @@
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
+ export async function statusCommand() {
8
+ try {
9
+ const scope = await requireConfiguredScope();
10
+ const registryPath = getRegistryPath(scope);
11
+ if (!(await fileExists(registryPath))) {
12
+ error('Registry not found. Run: waslagenie sync');
13
+ process.exit(1);
14
+ }
15
+ const registry = new RegistryManager(scope);
16
+ await registry.load();
17
+ const registryData = registry.get();
18
+ const installedTools = await new Scanner(scope).detectInstalledTools();
19
+ section('Registry status');
20
+ info(`Scope: ${scope}`);
21
+ info(`Registry: ${registryPath}`);
22
+ spacer();
23
+ metric('Assets', registryData.assets.length);
24
+ metric('Conflicts', registryData.conflicts.length);
25
+ if (registryData.assets.length === 0) {
26
+ spacer();
27
+ section('No assets synced yet');
28
+ spacer();
29
+ return;
30
+ }
31
+ section('Synced assets');
32
+ spacer();
33
+ assetList(registryData.assets, true, installedTools);
34
+ }
35
+ catch (err) {
36
+ error(`Status check failed: ${err}`);
37
+ process.exit(1);
38
+ }
39
+ }
@@ -1,5 +1,4 @@
1
1
  interface SyncToOptions {
2
- scope?: string;
3
2
  from?: string;
4
3
  to?: string;
5
4
  }
@@ -1,15 +1,16 @@
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';
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';
5
6
  export async function syncToCommand(options) {
6
7
  try {
7
- const scope = (options.scope || 'workspace');
8
+ const scope = await requireConfiguredScope();
8
9
  const from = options.from;
9
10
  const to = options.to;
10
11
  if (!from || !to) {
11
12
  error('Error: --from and --to are required');
12
- console.log('Usage: waslagenie sync-to --from <source> --to <target> [--scope workspace|user]');
13
+ console.log('Usage: waslagenie sync-to --from <source> --to <target>');
13
14
  console.log('Example: waslagenie sync-to --from gemini --to claude');
14
15
  process.exit(1);
15
16
  }
@@ -0,0 +1,5 @@
1
+ interface SyncOptions {
2
+ promptForScope?: boolean;
3
+ }
4
+ export declare function syncCommand(options?: SyncOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,49 @@
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
+ import { configCommand } from './config.js';
7
+ export async function syncCommand(options = {}) {
8
+ try {
9
+ if (options.promptForScope !== false && !(await configCommand({}))) {
10
+ return;
11
+ }
12
+ const scope = await requireConfiguredScope();
13
+ section('Syncing assets');
14
+ info(`Scope: ${scope}`);
15
+ info(`Registry: ${getConfiguredRegistryPath(scope)}`);
16
+ spacer();
17
+ section('Scanning providers');
18
+ const registry = new RegistryManager(scope);
19
+ await registry.load();
20
+ const scanner = new Scanner(scope);
21
+ const installedTools = await scanner.detectInstalledTools();
22
+ info(`${installedTools.length} providers detected`);
23
+ for (const tool of installedTools) {
24
+ bulletPoint(tool, 1);
25
+ }
26
+ spacer();
27
+ const syncer = new Syncer(registry, scanner, scope);
28
+ const result = await syncer.sync(false);
29
+ spacer();
30
+ highlight('Sync complete');
31
+ spacer();
32
+ metric('Assets discovered', result.assetsDiscovered);
33
+ metric('Stubs written', result.stubsWritten);
34
+ metric('Stubs deleted', result.stubsDeleted);
35
+ spacer();
36
+ if (registry.get().assets.length === 0) {
37
+ info('No assets discovered');
38
+ }
39
+ else {
40
+ section('Discovered assets');
41
+ spacer();
42
+ assetList(registry.get().assets, false, installedTools);
43
+ }
44
+ }
45
+ catch (err) {
46
+ error(`Sync failed: ${err}`);
47
+ process.exit(1);
48
+ }
49
+ }
@@ -0,0 +1 @@
1
+ export declare function watchCommand(): Promise<void>;
@@ -1,12 +1,13 @@
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
- export async function watchCommand(options) {
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
+ export async function watchCommand() {
8
9
  try {
9
- const scope = (options.scope || 'workspace');
10
+ const scope = await requireConfiguredScope();
10
11
  section('Watching for changes...');
11
12
  spacer();
12
13
  const registry = new RegistryManager(scope);
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { dirname, join } from 'path';
5
+ import { fileURLToPath } from 'url';
3
6
  import { installCommand } from './commands/install.js';
4
7
  import { registerCommand } from './commands/register.js';
5
8
  import { syncCommand } from './commands/sync.js';
@@ -7,52 +10,59 @@ import { syncToCommand } from './commands/sync-to.js';
7
10
  import { statusCommand } from './commands/status.js';
8
11
  import { configCommand } from './commands/config.js';
9
12
  import { watchCommand } from './commands/watch.js';
10
- import { visualizerCommand } from './commands/visualizer.js';
13
+ import { visualizerCommand } from './server/visualizer-server.js';
14
+ import { banner } from './cli-output.js';
11
15
  const program = new Command();
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
+ }
12
29
  program
13
30
  .name('waslagenie')
14
31
  .description('Universal synchronization layer for AI agent orchestrators')
15
- .version('0.1.0');
32
+ .version(readPackageVersion(import.meta.url));
16
33
  program.addCommand(new Command('install').description('Prepare WaslaGenie CLI state').action(installCommand));
17
34
  program.addCommand(new Command('register')
18
35
  .option('--to <targets>', 'Target provider(s), comma-separated. Example: claude,gemini')
19
36
  .description('Register WaslaGenie helper skills inside installed AI tools')
20
37
  .action((options) => registerCommand(options)));
21
38
  program.addCommand(new Command('sync')
22
- .option('--scope <scope>', 'user or workspace', 'workspace')
23
39
  .description('Scan and sync agents/MCPs across tools')
24
- .action((options) => syncCommand(options)));
40
+ .action(() => syncCommand()));
25
41
  program.addCommand(new Command('sync-to')
26
42
  .option('--from <source>', 'Source tool (gemini, claude, etc.)')
27
43
  .option('--to <targets>', 'Target tool(s), comma-separated')
28
- .option('--scope <scope>', 'user or workspace', 'workspace')
29
44
  .description('Sync agents/MCPs from one tool to specific target(s)')
30
45
  .action((options) => syncToCommand(options)));
31
46
  program.addCommand(new Command('status')
32
- .option('--scope <scope>', 'user or workspace', 'workspace')
33
47
  .description('Show all discovered assets and their sync state')
34
- .action((options) => statusCommand(options)));
48
+ .action(() => statusCommand()));
35
49
  program.addCommand(new Command('config')
36
50
  .option('--scope <scope>', 'Set scope to user or workspace')
37
51
  .option('--show', 'Show current config')
38
52
  .description('Configure WaslaGenie settings')
39
- .action((options) => configCommand(options)));
40
- program.addCommand(new Command('watch')
41
- .option('--scope <scope>', 'user or workspace', 'workspace')
42
- .description('Watch for changes and auto-sync')
43
- .action((options) => watchCommand(options)));
53
+ .action(async (options) => {
54
+ await configCommand(options);
55
+ }));
56
+ program.addCommand(new Command('watch').description('Watch for changes and auto-sync').action(() => watchCommand()));
44
57
  program.addCommand(new Command('visualizer')
45
- .option('--scope <scope>', 'user or workspace', 'workspace')
46
- .option('--host <host>', 'Host to bind', '127.0.0.1')
47
58
  .option('--port <port>', 'Port to bind', '4072')
48
59
  .option('--no-open', 'Do not open browser automatically')
49
60
  .description('Open interactive sync visualizer with built-in backend')
50
61
  .action((options) => visualizerCommand(options)));
51
62
  program.addCommand(new Command('ui')
52
- .option('--scope <scope>', 'user or workspace', 'workspace')
53
- .option('--host <host>', 'Host to bind', '127.0.0.1')
54
63
  .option('--port <port>', 'Port to bind', '4072')
55
64
  .option('--no-open', 'Do not open browser automatically')
56
65
  .description('Alias for `visualizer`')
57
66
  .action((options) => visualizerCommand(options)));
67
+ banner();
58
68
  program.parse(process.argv);
@@ -1,8 +1,6 @@
1
1
  export declare function resolveVisualizerDist(moduleUrl: string): string;
2
2
  interface VisualizerOptions {
3
- scope?: string;
4
3
  port?: string;
5
- host?: string;
6
4
  noOpen?: boolean;
7
5
  }
8
6
  export declare const PROVIDER_ICONS: Record<string, string>;