@untitled-devs/wasla 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -121,15 +121,18 @@ The same pattern applies across every asset type:
121
121
  WaslaGenie is cross-platform via `npx` — no global install required:
122
122
 
123
123
  ```bash
124
+ npx @untitled-devs/wasla config --scope workspace
124
125
  npx @untitled-devs/wasla sync
125
126
  ```
126
127
 
127
- This runs the CLI directly. It does not register helper skills inside Claude, Gemini, or other tools.
128
+ Choose `workspace` or `user` once before running operational commands. This runs the CLI directly.
129
+ It does not register helper skills inside Claude, Gemini, or other tools.
128
130
 
129
131
  **Or install globally:**
130
132
 
131
133
  ```bash
132
134
  npm install -g @untitled-devs/wasla
135
+ waslagenie config --scope workspace
133
136
  waslagenie sync
134
137
  ```
135
138
 
@@ -178,10 +181,10 @@ npx @untitled-devs/wasla visualizer
178
181
  ### You (developing this repo)
179
182
 
180
183
  ```bash
181
- # Build + run sync (workspace scope) using your local source
184
+ # Build + run sync using your configured scope
182
185
  npm run sync
183
186
 
184
- # Build + run watch (workspace scope)
187
+ # Build + run watch using your configured scope
185
188
  npm run watch
186
189
  ```
187
190
 
@@ -257,14 +260,18 @@ No restart. No manual trigger. The moment something changes — it's everywhere.
257
260
 
258
261
  ### Scope — workspace or user level
259
262
 
263
+ Choose the active scope before running sync, watch, status, or the visualizer:
264
+
260
265
  ```bash
261
- # Sync only within current project workspace
262
- waslagenie sync --scope workspace
266
+ # Use the current project workspace registry
267
+ waslagenie config --scope workspace
263
268
 
264
- # Sync across your entire user space (default)
265
- waslagenie sync --scope user
269
+ # Use the user-level registry across projects
270
+ waslagenie config --scope user
266
271
  ```
267
272
 
273
+ All other commands use the saved scope automatically. They do not accept `--scope`.
274
+
268
275
  ---
269
276
 
270
277
  ### Status — see everything and where it lives
@@ -312,22 +319,19 @@ review-pr command openclaw claude ✔ gemini ✔ codex ✔ her
312
319
 
313
320
  ## 🗃️ Registry Storage
314
321
 
315
- WaslaGenie keeps its own state separately from all orchestrators. You choose the scope at install time:
322
+ WaslaGenie keeps its own state separately from all orchestrators. You choose the active scope explicitly before the first sync:
316
323
 
317
- **User-level** (default — available across all your projects):
324
+ **User-level** (available across all your projects):
318
325
  ```
319
326
  ~/.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
327
+ ├── registry.json ← user-scope assets and stub locations
328
+ └── config.json active scope preference
323
329
  ```
324
330
 
325
331
  **Workspace-level** (scoped to current project only):
326
332
  ```
327
333
  .waslagenie/
328
- ├── registry.json
329
- ├── stubs/
330
- └── config.json
334
+ └── registry.json ← workspace-scope assets and stub locations
331
335
  ```
332
336
 
333
337
  Switch anytime:
@@ -419,6 +423,7 @@ npm run dev
419
423
 
420
424
  - [Contributing Guide](CONTRIBUTING.md)
421
425
  - [Architecture Docs](docs/docs/architecture/index.md)
426
+ - [Release Guide](RELEASING.md)
422
427
 
423
428
  ---
424
429
 
@@ -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 {};
@@ -1,26 +1,16 @@
1
- import { RegistryManager } from '../../core/registry.js';
2
1
  import { section, success, error, spacer, info } from '../../utils/cli-output.js';
3
- import { getRegistryPath } from '../../utils/paths.js';
4
- import { fileExists } from '../../utils/fs.js';
2
+ import prompts from 'prompts';
3
+ import { getConfigPath, getConfiguredRegistryPath, readConfiguredScope, writeConfiguredScope, } from '../../utils/config.js';
5
4
  export async function configCommand(options) {
6
5
  try {
7
- // Determine current scope
8
- const userPath = getRegistryPath('user');
9
- const workspacePath = getRegistryPath('workspace');
10
- const userExists = await fileExists(userPath);
11
- const workspaceExists = await fileExists(workspacePath);
12
- let currentScope = 'user';
13
- if (workspaceExists && !userExists) {
14
- currentScope = 'workspace';
15
- }
6
+ const currentScope = await readConfiguredScope();
16
7
  // Show current config
17
8
  if (options.show) {
18
9
  section('Current Configuration');
19
10
  spacer();
20
- info(`Scope: ${currentScope}`);
21
- info(`Registry: ${getRegistryPath(currentScope)}`);
11
+ showConfig(currentScope);
22
12
  spacer();
23
- return;
13
+ return true;
24
14
  }
25
15
  // Change scope if requested
26
16
  if (options.scope) {
@@ -29,37 +19,44 @@ export async function configCommand(options) {
29
19
  error('Invalid scope. Use: user or workspace');
30
20
  process.exit(1);
31
21
  }
32
- const registry = new RegistryManager(currentScope);
33
- // Load existing registry if it exists, otherwise it will be created on first sync
34
- if ((currentScope === 'user' && userExists) ||
35
- (currentScope === 'workspace' && workspaceExists)) {
36
- await registry.load();
37
- }
38
- else {
39
- // Create empty registry
40
- await registry.load();
41
- }
42
- // Change scope and save
43
- registry.setScope(newScope);
44
- await registry.save();
45
- success(`Scope changed to: ${newScope}`);
46
- info(`Registry: ${getRegistryPath(newScope)}`);
47
- spacer();
48
- return;
22
+ await saveScope(newScope);
23
+ return true;
49
24
  }
50
- // If no option provided, show current config
51
- section('Current Configuration');
25
+ section('Configure Scope');
52
26
  spacer();
53
- info(`Scope: ${currentScope}`);
54
- info(`Registry: ${getRegistryPath(currentScope)}`);
55
- spacer();
56
- console.log('Usage:');
57
- console.log(' waslagenie config --scope user # Store registry in ~/.waslagenie/');
58
- console.log(' waslagenie config --scope workspace # Store registry in .waslagenie/');
59
- console.log(' waslagenie config --show # Show current config');
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;
60
44
  }
61
45
  catch (err) {
62
46
  error(`Config failed: ${err}`);
63
47
  process.exit(1);
64
48
  }
65
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
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';
2
+ import { readConfiguredScope } from '../../utils/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('');
@@ -2,11 +2,13 @@ import { getInstalledAdapters } from '../../adapters/factory.js';
2
2
  import { section, success, error, warning, highlight, spacer } from '../../utils/cli-output.js';
3
3
  import { getRegistryDir } from '../../utils/paths.js';
4
4
  import { ensureDir } from '../../utils/fs.js';
5
+ import { requireConfiguredScope } from '../../utils/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();
@@ -1,5 +1 @@
1
- interface StatusOptions {
2
- scope?: string;
3
- }
4
- export declare function statusCommand(options: StatusOptions): Promise<void>;
5
- export {};
1
+ export declare function statusCommand(): Promise<void>;
@@ -1,10 +1,12 @@
1
1
  import { RegistryManager } from '../../core/registry.js';
2
- import { section, error, spacer, table } from '../../utils/cli-output.js';
2
+ import { Scanner } from '../../core/scanner.js';
3
+ import { assetList, section, error, info, metric, spacer } from '../../utils/cli-output.js';
3
4
  import { fileExists } from '../../utils/fs.js';
4
5
  import { getRegistryPath } from '../../utils/paths.js';
5
- export async function statusCommand(options) {
6
+ import { requireConfiguredScope } from '../../utils/config.js';
7
+ export async function statusCommand() {
6
8
  try {
7
- const scope = (options.scope || 'workspace');
9
+ const scope = await requireConfiguredScope();
8
10
  const registryPath = getRegistryPath(scope);
9
11
  if (!(await fileExists(registryPath))) {
10
12
  error('Registry not found. Run: waslagenie sync');
@@ -13,21 +15,22 @@ export async function statusCommand(options) {
13
15
  const registry = new RegistryManager(scope);
14
16
  await registry.load();
15
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);
16
25
  if (registryData.assets.length === 0) {
26
+ spacer();
17
27
  section('No assets synced yet');
18
28
  spacer();
19
29
  return;
20
30
  }
21
- section('Synced Assets');
22
- spacer();
23
- const rows = registryData.assets.map((asset) => [
24
- asset.name,
25
- asset.type,
26
- asset.stubs.map((s) => s.tool).join(', ') || 'none',
27
- new Date(asset.last_modified_at).toLocaleString(),
28
- ]);
29
- table([['ASSET', 'TYPE', 'STUBS', 'LAST MODIFIED'], ...rows.map((row) => row.map(String))], [20, 8, 20, 24]);
31
+ section('Synced assets');
30
32
  spacer();
33
+ assetList(registryData.assets, true, installedTools);
31
34
  }
32
35
  catch (err) {
33
36
  error(`Status check failed: ${err}`);
@@ -1,5 +1,4 @@
1
1
  interface SyncToOptions {
2
- scope?: string;
3
2
  from?: string;
4
3
  to?: string;
5
4
  }
@@ -2,14 +2,15 @@ import { RegistryManager } from '../../core/registry.js';
2
2
  import { Scanner } from '../../core/scanner.js';
3
3
  import { Syncer } from '../../syncer/index.js';
4
4
  import { section, error, highlight, spacer } from '../../utils/cli-output.js';
5
+ import { requireConfiguredScope } from '../../utils/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
  }
@@ -1,5 +1,5 @@
1
1
  interface SyncOptions {
2
- scope?: string;
2
+ promptForScope?: boolean;
3
3
  }
4
- export declare function syncCommand(options: SyncOptions): Promise<void>;
4
+ export declare function syncCommand(options?: SyncOptions): Promise<void>;
5
5
  export {};
@@ -1,23 +1,46 @@
1
1
  import { RegistryManager } from '../../core/registry.js';
2
2
  import { Scanner } from '../../core/scanner.js';
3
3
  import { Syncer } from '../../syncer/index.js';
4
- import { section, error, highlight, spacer } from '../../utils/cli-output.js';
5
- export async function syncCommand(options) {
4
+ import { assetList, bulletPoint, section, error, highlight, info, metric, spacer, } from '../../utils/cli-output.js';
5
+ import { getConfiguredRegistryPath, requireConfiguredScope } from '../../utils/config.js';
6
+ import { configCommand } from './config.js';
7
+ export async function syncCommand(options = {}) {
6
8
  try {
7
- const scope = (options.scope || 'workspace');
8
- section('Scanning...');
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)}`);
9
16
  spacer();
17
+ section('Scanning providers');
10
18
  const registry = new RegistryManager(scope);
11
19
  await registry.load();
12
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();
13
27
  const syncer = new Syncer(registry, scanner, scope);
14
- const result = await syncer.sync(true);
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);
15
35
  spacer();
16
- highlight('Sync complete!');
17
- console.log(' Note: sync only mirrors assets. It does not install helper skills.');
18
- console.log(` ${result.assetsDiscovered} assets discovered`);
19
- console.log(` ${result.stubsWritten} stubs written`);
20
- console.log(` ${result.stubsDeleted} stubs deleted`);
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
+ }
21
44
  }
22
45
  catch (err) {
23
46
  error(`Sync failed: ${err}`);
@@ -1,6 +1,5 @@
1
1
  export declare function resolveVisualizerDist(moduleUrl: string): string;
2
2
  interface VisualizerOptions {
3
- scope?: string;
4
3
  port?: string;
5
4
  host?: string;
6
5
  noOpen?: boolean;
@@ -10,6 +10,7 @@ import { RegistryManager } from '../../core/registry.js';
10
10
  import { getInstalledAdapters } from '../../adapters/factory.js';
11
11
  import { error, highlight, info, section, spacer } from '../../utils/cli-output.js';
12
12
  import { Syncer } from '../../syncer/index.js';
13
+ import { requireConfiguredScope } from '../../utils/config.js';
13
14
  export function resolveVisualizerDist(moduleUrl) {
14
15
  return resolve(dirname(fileURLToPath(moduleUrl)), '../../../src/visualizer/dist');
15
16
  }
@@ -138,7 +139,7 @@ function sendJson(res, statusCode, body) {
138
139
  }
139
140
  export async function visualizerCommand(options) {
140
141
  try {
141
- const scope = (options.scope || 'workspace');
142
+ const scope = await requireConfiguredScope();
142
143
  const host = options.host || '127.0.0.1';
143
144
  const port = Number(options.port || 4072);
144
145
  const shouldOpen = options.noOpen !== true;
@@ -1,5 +1 @@
1
- interface WatchOptions {
2
- scope?: string;
3
- }
4
- export declare function watchCommand(options: WatchOptions): Promise<void>;
5
- export {};
1
+ export declare function watchCommand(): Promise<void>;
@@ -4,9 +4,10 @@ import { Scanner } from '../../core/scanner.js';
4
4
  import { Syncer } from '../../syncer/index.js';
5
5
  import { getAllAdapters } from '../../adapters/factory.js';
6
6
  import { section, success, error, info, spacer } from '../../utils/cli-output.js';
7
- export async function watchCommand(options) {
7
+ import { requireConfiguredScope } from '../../utils/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);
package/dist/cli/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import { readFileSync } from 'fs';
3
4
  import { installCommand } from './commands/install.js';
4
5
  import { registerCommand } from './commands/register.js';
5
6
  import { syncCommand } from './commands/sync.js';
@@ -8,51 +9,48 @@ import { statusCommand } from './commands/status.js';
8
9
  import { configCommand } from './commands/config.js';
9
10
  import { watchCommand } from './commands/watch.js';
10
11
  import { visualizerCommand } from './commands/visualizer.js';
12
+ import { banner } from '../utils/cli-output.js';
11
13
  const program = new Command();
14
+ const packageVersion = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf-8'));
12
15
  program
13
16
  .name('waslagenie')
14
17
  .description('Universal synchronization layer for AI agent orchestrators')
15
- .version('0.1.0');
18
+ .version(packageVersion.version);
16
19
  program.addCommand(new Command('install').description('Prepare WaslaGenie CLI state').action(installCommand));
17
20
  program.addCommand(new Command('register')
18
21
  .option('--to <targets>', 'Target provider(s), comma-separated. Example: claude,gemini')
19
22
  .description('Register WaslaGenie helper skills inside installed AI tools')
20
23
  .action((options) => registerCommand(options)));
21
24
  program.addCommand(new Command('sync')
22
- .option('--scope <scope>', 'user or workspace', 'workspace')
23
25
  .description('Scan and sync agents/MCPs across tools')
24
- .action((options) => syncCommand(options)));
26
+ .action(() => syncCommand()));
25
27
  program.addCommand(new Command('sync-to')
26
28
  .option('--from <source>', 'Source tool (gemini, claude, etc.)')
27
29
  .option('--to <targets>', 'Target tool(s), comma-separated')
28
- .option('--scope <scope>', 'user or workspace', 'workspace')
29
30
  .description('Sync agents/MCPs from one tool to specific target(s)')
30
31
  .action((options) => syncToCommand(options)));
31
32
  program.addCommand(new Command('status')
32
- .option('--scope <scope>', 'user or workspace', 'workspace')
33
33
  .description('Show all discovered assets and their sync state')
34
- .action((options) => statusCommand(options)));
34
+ .action(() => statusCommand()));
35
35
  program.addCommand(new Command('config')
36
36
  .option('--scope <scope>', 'Set scope to user or workspace')
37
37
  .option('--show', 'Show current config')
38
38
  .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)));
39
+ .action(async (options) => {
40
+ await configCommand(options);
41
+ }));
42
+ program.addCommand(new Command('watch').description('Watch for changes and auto-sync').action(() => watchCommand()));
44
43
  program.addCommand(new Command('visualizer')
45
- .option('--scope <scope>', 'user or workspace', 'workspace')
46
44
  .option('--host <host>', 'Host to bind', '127.0.0.1')
47
45
  .option('--port <port>', 'Port to bind', '4072')
48
46
  .option('--no-open', 'Do not open browser automatically')
49
47
  .description('Open interactive sync visualizer with built-in backend')
50
48
  .action((options) => visualizerCommand(options)));
51
49
  program.addCommand(new Command('ui')
52
- .option('--scope <scope>', 'user or workspace', 'workspace')
53
50
  .option('--host <host>', 'Host to bind', '127.0.0.1')
54
51
  .option('--port <port>', 'Port to bind', '4072')
55
52
  .option('--no-open', 'Do not open browser automatically')
56
53
  .description('Alias for `visualizer`')
57
54
  .action((options) => visualizerCommand(options)));
55
+ banner();
58
56
  program.parse(process.argv);
@@ -1,5 +1,5 @@
1
1
  import { fileExists, isDirectory, readJSON } from '../utils/fs.js';
2
- import { join, relative, sep } from 'path';
2
+ import { join, relative } from 'path';
3
3
  import { getToolMarkers, getRegistryPath } from '../utils/paths.js';
4
4
  import { stat, readdir } from 'fs/promises';
5
5
  import { getAdapter } from '../adapters/factory.js';
@@ -203,13 +203,18 @@ export class Scanner {
203
203
  extractAssetName(relativePathOrFileName) {
204
204
  // For nested paths: waslagenie/SKILL.md -> waslagenie
205
205
  // For flat files: researcher.md -> researcher
206
- const parts = relativePathOrFileName.split(sep);
206
+ const parts = relativePathOrFileName.split(/[/\\]/);
207
207
  if (parts.length > 1) {
208
208
  // Nested: return first directory
209
209
  return parts[0];
210
210
  }
211
211
  // Flat: remove extension
212
- return parts[0].split('.')[0];
212
+ const fileName = parts[0];
213
+ const dotIndex = fileName.lastIndexOf('.');
214
+ if (dotIndex <= 0) {
215
+ return fileName;
216
+ }
217
+ return fileName.substring(0, dotIndex);
213
218
  }
214
219
  readNestedRecord(config, keyPath) {
215
220
  let value = config;
@@ -68,7 +68,6 @@ export interface DiscoveredFile {
68
68
  content?: string;
69
69
  }
70
70
  export interface SyncOptions {
71
- scope?: 'user' | 'workspace';
72
71
  interactive?: boolean;
73
72
  }
74
73
  export interface ConfigOptions {
@@ -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;
@@ -1,23 +1,77 @@
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
+ }
1
23
  export function success(message) {
2
- console.log(`✔ ${message}`);
24
+ console.log(color(`✔ ${message}`, ansi.green));
3
25
  }
4
26
  export function error(message) {
5
- console.error(`✗ ${message}`);
27
+ console.error(color(`✗ ${message}`, ansi.red));
6
28
  }
7
29
  export function info(message) {
8
- console.log(`ℹ ${message}`);
30
+ console.log(color(`ℹ ${message}`, ansi.blue));
9
31
  }
10
32
  export function warning(message) {
11
- console.log(`⚠ ${message}`);
33
+ console.log(color(`⚠ ${message}`, ansi.yellow));
12
34
  }
13
35
  export function highlight(message) {
14
- console.log(`✨ ${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
+ }
15
69
  }
16
70
  export function step(title) {
17
71
  console.log(`\n${title}`);
18
72
  }
19
73
  export function section(title) {
20
- console.log(`\n🔍 ${title}`);
74
+ console.log(color(`\n🔍 ${title}`, ansi.blue, ansi.bold));
21
75
  }
22
76
  export function table(rows, columnWidths) {
23
77
  if (rows.length === 0)
@@ -0,0 +1,6 @@
1
+ export type WaslaScope = 'user' | 'workspace';
2
+ export declare function getConfigPath(): string;
3
+ export declare function readConfiguredScope(): Promise<WaslaScope | null>;
4
+ export declare function requireConfiguredScope(): Promise<WaslaScope>;
5
+ export declare function writeConfiguredScope(scope: WaslaScope): Promise<void>;
6
+ export declare function getConfiguredRegistryPath(scope: WaslaScope): string;
@@ -0,0 +1,34 @@
1
+ import { dirname, join } from 'path';
2
+ import { ensureDir, fileExists, readJSON, writeJSON } from './fs.js';
3
+ import { getRegistryDir, getRegistryPath } from './paths.js';
4
+ export function getConfigPath() {
5
+ return join(getRegistryDir('user'), 'config.json');
6
+ }
7
+ export async function readConfiguredScope() {
8
+ const configPath = getConfigPath();
9
+ if (!(await fileExists(configPath)))
10
+ return null;
11
+ const config = await readJSON(configPath);
12
+ if (typeof config !== 'object' || config === null || typeof config.scope !== 'string') {
13
+ throw new Error(`Invalid scope in ${configPath}. Run: waslagenie config --scope <scope>`);
14
+ }
15
+ if (config.scope !== 'user' && config.scope !== 'workspace') {
16
+ throw new Error(`Invalid scope in ${configPath}. Run: waslagenie config --scope <scope>`);
17
+ }
18
+ return config.scope;
19
+ }
20
+ export async function requireConfiguredScope() {
21
+ const scope = await readConfiguredScope();
22
+ if (!scope) {
23
+ throw new Error('Scope is not configured. Run: waslagenie config --scope <user|workspace>');
24
+ }
25
+ return scope;
26
+ }
27
+ export async function writeConfiguredScope(scope) {
28
+ const configPath = getConfigPath();
29
+ await ensureDir(dirname(configPath));
30
+ await writeJSON(configPath, { scope });
31
+ }
32
+ export function getConfiguredRegistryPath(scope) {
33
+ return getRegistryPath(scope);
34
+ }
package/dist/utils/fs.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { readdir, readFile, writeFile, stat, mkdir, rm } from 'fs/promises';
2
- import { basename, extname } from 'path';
3
2
  export async function fileExists(path) {
4
3
  try {
5
4
  await stat(path);
@@ -60,14 +59,21 @@ export async function listDirs(dir) {
60
59
  }
61
60
  }
62
61
  export function getFileName(path) {
63
- return basename(path);
62
+ return path.split(/[/\\]/).filter(Boolean).pop() || '';
64
63
  }
65
64
  export function getFileNameWithoutExt(path) {
66
- const fileName = basename(path);
67
- const ext = extname(fileName);
68
- return fileName.slice(0, fileName.length - ext.length);
65
+ const fileName = getFileName(path);
66
+ const dotIndex = fileName.lastIndexOf('.');
67
+ if (dotIndex <= 0) {
68
+ return fileName;
69
+ }
70
+ return fileName.substring(0, dotIndex);
69
71
  }
70
72
  export function getFileExtension(path) {
71
- const ext = extname(path);
72
- return ext.startsWith('.') ? ext.slice(1) : ext;
73
+ const fileName = getFileName(path);
74
+ const dotIndex = fileName.lastIndexOf('.');
75
+ if (dotIndex <= 0) {
76
+ return '';
77
+ }
78
+ return fileName.substring(dotIndex + 1);
73
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@untitled-devs/wasla",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Universal synchronization layer for AI agent orchestrators",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -17,50 +17,50 @@
17
17
  "postbuild": "node -e \"require('fs').chmodSync('dist/cli/index.js', 0o755)\"",
18
18
  "dev": "tsc --watch",
19
19
  "start": "node dist/cli/index.js",
20
- "sync": "npm run build && node dist/cli/index.js sync --scope workspace",
21
- "watch": "npm run build && node dist/cli/index.js watch --scope workspace",
22
- "sync:claude:gemini": "npm run build && node dist/cli/index.js sync-to --from claude --to gemini --scope workspace",
23
- "sync:claude:openclaw": "npm run build && node dist/cli/index.js sync-to --from claude --to openclaw --scope workspace",
24
- "sync:claude:opencode": "npm run build && node dist/cli/index.js sync-to --from claude --to opencode --scope workspace",
25
- "sync:claude:cursor": "npm run build && node dist/cli/index.js sync-to --from claude --to cursor --scope workspace",
26
- "sync:claude:github-copilot": "npm run build && node dist/cli/index.js sync-to --from claude --to github-copilot --scope workspace",
27
- "sync:claude:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from claude --to github-copilot-cli --scope workspace",
28
- "sync:gemini:claude": "npm run build && node dist/cli/index.js sync-to --from gemini --to claude --scope workspace",
29
- "sync:gemini:openclaw": "npm run build && node dist/cli/index.js sync-to --from gemini --to openclaw --scope workspace",
30
- "sync:gemini:opencode": "npm run build && node dist/cli/index.js sync-to --from gemini --to opencode --scope workspace",
31
- "sync:gemini:cursor": "npm run build && node dist/cli/index.js sync-to --from gemini --to cursor --scope workspace",
32
- "sync:gemini:github-copilot": "npm run build && node dist/cli/index.js sync-to --from gemini --to github-copilot --scope workspace",
33
- "sync:gemini:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from gemini --to github-copilot-cli --scope workspace",
34
- "sync:openclaw:claude": "npm run build && node dist/cli/index.js sync-to --from openclaw --to claude --scope workspace",
35
- "sync:openclaw:gemini": "npm run build && node dist/cli/index.js sync-to --from openclaw --to gemini --scope workspace",
36
- "sync:openclaw:opencode": "npm run build && node dist/cli/index.js sync-to --from openclaw --to opencode --scope workspace",
37
- "sync:openclaw:cursor": "npm run build && node dist/cli/index.js sync-to --from openclaw --to cursor --scope workspace",
38
- "sync:openclaw:github-copilot": "npm run build && node dist/cli/index.js sync-to --from openclaw --to github-copilot --scope workspace",
39
- "sync:openclaw:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from openclaw --to github-copilot-cli --scope workspace",
40
- "sync:opencode:claude": "npm run build && node dist/cli/index.js sync-to --from opencode --to claude --scope workspace",
41
- "sync:opencode:gemini": "npm run build && node dist/cli/index.js sync-to --from opencode --to gemini --scope workspace",
42
- "sync:opencode:openclaw": "npm run build && node dist/cli/index.js sync-to --from opencode --to openclaw --scope workspace",
43
- "sync:opencode:cursor": "npm run build && node dist/cli/index.js sync-to --from opencode --to cursor --scope workspace",
44
- "sync:opencode:github-copilot": "npm run build && node dist/cli/index.js sync-to --from opencode --to github-copilot --scope workspace",
45
- "sync:opencode:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from opencode --to github-copilot-cli --scope workspace",
46
- "sync:cursor:claude": "npm run build && node dist/cli/index.js sync-to --from cursor --to claude --scope workspace",
47
- "sync:cursor:gemini": "npm run build && node dist/cli/index.js sync-to --from cursor --to gemini --scope workspace",
48
- "sync:cursor:openclaw": "npm run build && node dist/cli/index.js sync-to --from cursor --to openclaw --scope workspace",
49
- "sync:cursor:opencode": "npm run build && node dist/cli/index.js sync-to --from cursor --to opencode --scope workspace",
50
- "sync:cursor:github-copilot": "npm run build && node dist/cli/index.js sync-to --from cursor --to github-copilot --scope workspace",
51
- "sync:cursor:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from cursor --to github-copilot-cli --scope workspace",
52
- "sync:github-copilot:claude": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to claude --scope workspace",
53
- "sync:github-copilot:gemini": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to gemini --scope workspace",
54
- "sync:github-copilot:openclaw": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to openclaw --scope workspace",
55
- "sync:github-copilot:opencode": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to opencode --scope workspace",
56
- "sync:github-copilot:cursor": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to cursor --scope workspace",
57
- "sync:github-copilot:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to github-copilot-cli --scope workspace",
58
- "sync:github-copilot-cli:claude": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to claude --scope workspace",
59
- "sync:github-copilot-cli:gemini": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to gemini --scope workspace",
60
- "sync:github-copilot-cli:openclaw": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to openclaw --scope workspace",
61
- "sync:github-copilot-cli:opencode": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to opencode --scope workspace",
62
- "sync:github-copilot-cli:cursor": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to cursor --scope workspace",
63
- "sync:github-copilot-cli:github-copilot": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to github-copilot --scope workspace",
20
+ "sync": "npm run build && node dist/cli/index.js sync",
21
+ "watch": "npm run build && node dist/cli/index.js watch",
22
+ "sync:claude:gemini": "npm run build && node dist/cli/index.js sync-to --from claude --to gemini",
23
+ "sync:claude:openclaw": "npm run build && node dist/cli/index.js sync-to --from claude --to openclaw",
24
+ "sync:claude:opencode": "npm run build && node dist/cli/index.js sync-to --from claude --to opencode",
25
+ "sync:claude:cursor": "npm run build && node dist/cli/index.js sync-to --from claude --to cursor",
26
+ "sync:claude:github-copilot": "npm run build && node dist/cli/index.js sync-to --from claude --to github-copilot",
27
+ "sync:claude:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from claude --to github-copilot-cli",
28
+ "sync:gemini:claude": "npm run build && node dist/cli/index.js sync-to --from gemini --to claude",
29
+ "sync:gemini:openclaw": "npm run build && node dist/cli/index.js sync-to --from gemini --to openclaw",
30
+ "sync:gemini:opencode": "npm run build && node dist/cli/index.js sync-to --from gemini --to opencode",
31
+ "sync:gemini:cursor": "npm run build && node dist/cli/index.js sync-to --from gemini --to cursor",
32
+ "sync:gemini:github-copilot": "npm run build && node dist/cli/index.js sync-to --from gemini --to github-copilot",
33
+ "sync:gemini:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from gemini --to github-copilot-cli",
34
+ "sync:openclaw:claude": "npm run build && node dist/cli/index.js sync-to --from openclaw --to claude",
35
+ "sync:openclaw:gemini": "npm run build && node dist/cli/index.js sync-to --from openclaw --to gemini",
36
+ "sync:openclaw:opencode": "npm run build && node dist/cli/index.js sync-to --from openclaw --to opencode",
37
+ "sync:openclaw:cursor": "npm run build && node dist/cli/index.js sync-to --from openclaw --to cursor",
38
+ "sync:openclaw:github-copilot": "npm run build && node dist/cli/index.js sync-to --from openclaw --to github-copilot",
39
+ "sync:openclaw:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from openclaw --to github-copilot-cli",
40
+ "sync:opencode:claude": "npm run build && node dist/cli/index.js sync-to --from opencode --to claude",
41
+ "sync:opencode:gemini": "npm run build && node dist/cli/index.js sync-to --from opencode --to gemini",
42
+ "sync:opencode:openclaw": "npm run build && node dist/cli/index.js sync-to --from opencode --to openclaw",
43
+ "sync:opencode:cursor": "npm run build && node dist/cli/index.js sync-to --from opencode --to cursor",
44
+ "sync:opencode:github-copilot": "npm run build && node dist/cli/index.js sync-to --from opencode --to github-copilot",
45
+ "sync:opencode:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from opencode --to github-copilot-cli",
46
+ "sync:cursor:claude": "npm run build && node dist/cli/index.js sync-to --from cursor --to claude",
47
+ "sync:cursor:gemini": "npm run build && node dist/cli/index.js sync-to --from cursor --to gemini",
48
+ "sync:cursor:openclaw": "npm run build && node dist/cli/index.js sync-to --from cursor --to openclaw",
49
+ "sync:cursor:opencode": "npm run build && node dist/cli/index.js sync-to --from cursor --to opencode",
50
+ "sync:cursor:github-copilot": "npm run build && node dist/cli/index.js sync-to --from cursor --to github-copilot",
51
+ "sync:cursor:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from cursor --to github-copilot-cli",
52
+ "sync:github-copilot:claude": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to claude",
53
+ "sync:github-copilot:gemini": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to gemini",
54
+ "sync:github-copilot:openclaw": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to openclaw",
55
+ "sync:github-copilot:opencode": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to opencode",
56
+ "sync:github-copilot:cursor": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to cursor",
57
+ "sync:github-copilot:github-copilot-cli": "npm run build && node dist/cli/index.js sync-to --from github-copilot --to github-copilot-cli",
58
+ "sync:github-copilot-cli:claude": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to claude",
59
+ "sync:github-copilot-cli:gemini": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to gemini",
60
+ "sync:github-copilot-cli:openclaw": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to openclaw",
61
+ "sync:github-copilot-cli:opencode": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to opencode",
62
+ "sync:github-copilot-cli:cursor": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to cursor",
63
+ "sync:github-copilot-cli:github-copilot": "npm run build && node dist/cli/index.js sync-to --from github-copilot-cli --to github-copilot",
64
64
  "test": "vitest",
65
65
  "test:run": "vitest run",
66
66
  "test:watch": "vitest --watch",
@@ -72,7 +72,7 @@
72
72
  "docs": "cd docs && npm run start",
73
73
  "docs:install": "npm --prefix docs install",
74
74
  "docs:build": "cd docs && npm run build",
75
- "visualizer": "npm run visualizer:install && npm run visualizer:build && npm run build && node dist/cli/index.js visualizer --scope workspace",
75
+ "visualizer": "npm run visualizer:install && npm run visualizer:build && npm run build && node dist/cli/index.js visualizer",
76
76
  "ui": "npm run visualizer",
77
77
  "visualizer:install": "npm --prefix src/visualizer install",
78
78
  "visualizer:dev": "npm --prefix src/visualizer run dev",
@@ -87,6 +87,8 @@
87
87
  "type:fix": "tsc --noEmit && prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
88
88
  "check": "npm run type:check && npm run format:check",
89
89
  "fix": "npm run format && npm run lint:fix",
90
+ "changelog": "node scripts/release.mjs changelog",
91
+ "release": "node scripts/release.mjs release",
90
92
  "clean": "node -e \"['dist', '.nyc_output', 'output', 'coverage'].forEach(p => require('fs').rmSync(p, { recursive: true, force: true }))\"",
91
93
  "all": "npm run clean && npm run build && npm run check && npm test"
92
94
  },
@@ -110,6 +112,7 @@
110
112
  "devDependencies": {
111
113
  "@ai-hero/sandcastle": "^0.6.6",
112
114
  "@types/node": "^20.10.6",
115
+ "@types/prompts": "^2.4.9",
113
116
  "@types/ws": "^8.18.1",
114
117
  "@vitest/coverage-v8": "^1.6.1",
115
118
  "@vitest/ui": "^1.0.0",