@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
@@ -5,19 +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';
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';
13
14
  export function resolveVisualizerDist(moduleUrl) {
14
- return resolve(dirname(fileURLToPath(moduleUrl)), '../../../src/visualizer/dist');
15
+ return resolve(dirname(fileURLToPath(moduleUrl)), '../../../../visualizer');
15
16
  }
16
- async function getEntityContent(scope, type, name) {
17
+ async function getEntityContent(scope, type, name, providerId) {
17
18
  const scanner = new Scanner(scope);
18
19
  await scanner.initialize();
19
- const discovered = await scanner.scanAllTools();
20
- 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];
21
27
  if (!target)
22
28
  return null;
23
29
  if (target.content)
@@ -29,11 +35,23 @@ async function getEntityContent(scope, type, name) {
29
35
  return null;
30
36
  }
31
37
  }
38
+ function isVisualizerEntityType(type) {
39
+ return type === 'instruction' || type === 'agent' || type === 'skill' || type === 'mcp';
40
+ }
32
41
  function mapType(type) {
33
42
  if (type === 'context')
34
43
  return 'instruction';
35
44
  return type;
36
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
+ }
37
55
  function mapEntityType(type) {
38
56
  if (type === 'instruction')
39
57
  return 'context';
@@ -84,12 +102,20 @@ async function buildConfig(scope) {
84
102
  await scanner.initialize();
85
103
  const discovered = await scanner.scanAllTools();
86
104
  const installed = await getInstalledAdapters(scope);
105
+ const installedNames = new Set(installed.map((adapter) => adapter.name));
87
106
  const providers = [
88
- { id: 'waslagenie', title: 'WaslaGenie', iconUrl: PROVIDER_ICONS.waslagenie, isHub: true },
89
- ...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) => ({
90
115
  id: adapter.name,
91
116
  title: adapter.displayName,
92
117
  iconUrl: PROVIDER_ICONS[adapter.name],
118
+ isInstalled: installedNames.has(adapter.name),
93
119
  })),
94
120
  ];
95
121
  const entityMap = new Map();
@@ -128,7 +154,6 @@ async function buildConfig(scope) {
128
154
  providers,
129
155
  entities: grouped,
130
156
  connections: Array.from(connectionMap.values()),
131
- websocketUrl: '',
132
157
  };
133
158
  }
134
159
  function sendJson(res, statusCode, body) {
@@ -138,8 +163,8 @@ function sendJson(res, statusCode, body) {
138
163
  }
139
164
  export async function visualizerCommand(options) {
140
165
  try {
141
- const scope = (options.scope || 'workspace');
142
- const host = options.host || '127.0.0.1';
166
+ const scope = await requireConfiguredScope();
167
+ const host = '127.0.0.1';
143
168
  const port = Number(options.port || 4072);
144
169
  const shouldOpen = options.noOpen !== true;
145
170
  section('Starting visualizer...');
@@ -160,36 +185,43 @@ export async function visualizerCommand(options) {
160
185
  };
161
186
  const server = createServer(async (req, res) => {
162
187
  const url = req.url || '/';
163
- if (req.method === 'GET' && url === '/api/config') {
164
- const freshConfig = await buildConfig(scope);
165
- freshConfig.websocketUrl = `ws://${host}:${port}`;
166
- 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' });
167
192
  return;
168
193
  }
169
- if (req.method === 'GET' && url.startsWith('/api/entity-content')) {
170
- 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') {
171
199
  const type = parsed.searchParams.get('type');
172
200
  const name = parsed.searchParams.get('name');
173
- if (!type || !name) {
174
- 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' });
175
207
  return;
176
208
  }
177
- const content = await getEntityContent(scope, type, name);
209
+ const content = await getEntityContent(scope, type, name, providerId);
178
210
  sendJson(res, 200, { content: content ?? '' });
179
211
  return;
180
212
  }
181
- if (req.method === 'POST' && url === '/api/shutdown') {
213
+ if (req.method === 'POST' && pathname === '/api/shutdown') {
182
214
  sendJson(res, 200, { ok: true });
183
215
  setTimeout(shutdown, 50);
184
216
  return;
185
217
  }
186
- if (url === '/' || url === '/index.html') {
218
+ if (pathname === '/' || pathname === '/index.html') {
187
219
  const html = await readFile(join(visualizerDist, 'index.html'));
188
220
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
189
221
  res.end(html);
190
222
  return;
191
223
  }
192
- const staticPath = join(visualizerDist, url.replace(/^\//, ''));
224
+ const staticPath = join(visualizerDist, pathname.replace(/^\//, ''));
193
225
  if (!staticPath.startsWith(visualizerDist) || !existsSync(staticPath)) {
194
226
  res.writeHead(404);
195
227
  res.end('Not Found');
@@ -199,15 +231,24 @@ export async function visualizerCommand(options) {
199
231
  res.writeHead(200, { 'Content-Type': mimeType(staticPath) });
200
232
  res.end(payload);
201
233
  });
202
- const wss = new WebSocketServer({ server });
234
+ const wss = new WebSocketServer({
235
+ server,
236
+ verifyClient: ({ origin }) => isAllowedOrigin(origin, port),
237
+ });
203
238
  wss.on('connection', (socket) => {
204
239
  socket.on('message', async (raw) => {
240
+ let requestId = '';
205
241
  try {
206
242
  const data = JSON.parse(raw.toString());
207
- if (data.type !== 'CONNECTION_CHANGED' || !data.payload)
243
+ if (data.type !== 'CONNECTION_CHANGED' || !data.requestId || !data.payload)
208
244
  return;
245
+ requestId = data.requestId;
209
246
  const { sourceEntity, entityType, sourceProvider, targetProvider, action } = data.payload;
210
- if (!sourceEntity || !entityType || !sourceProvider || !targetProvider || !action) {
247
+ if (!sourceEntity ||
248
+ !isVisualizerEntityType(entityType) ||
249
+ !sourceProvider ||
250
+ !targetProvider ||
251
+ (action !== 'ATTACH' && action !== 'DETACH')) {
211
252
  return;
212
253
  }
213
254
  info(`[Visualizer] ${action} ${entityType}:${sourceEntity} ${action === 'ATTACH' ? 'to' : 'from'} ${targetProvider}`);
@@ -217,9 +258,23 @@ export async function visualizerCommand(options) {
217
258
  else {
218
259
  await syncer.detachAssetFromTool(sourceEntity, mapEntityType(entityType), targetProvider);
219
260
  }
261
+ const result = {
262
+ type: 'CONNECTION_CHANGED_RESULT',
263
+ requestId,
264
+ ok: true,
265
+ };
266
+ socket.send(JSON.stringify(result));
220
267
  }
221
- catch {
222
- // 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));
223
278
  }
224
279
  });
225
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') {
@@ -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 {
@@ -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
+ }
@@ -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
+ }
@@ -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
  }
@@ -1,6 +1,6 @@
1
- import { AssetType } from '../core/types.js';
2
- import { RegistryManager } from '../core/registry.js';
3
- import { Scanner } from '../core/scanner.js';
1
+ import { AssetType } from '#core/types.js';
2
+ import { RegistryManager } from '#core/registry.js';
3
+ import { Scanner } from './scanner.js';
4
4
  export declare class Syncer {
5
5
  private registry;
6
6
  private scanner;
@@ -1,8 +1,8 @@
1
- import { RegistryManager } from '../core/registry.js';
2
- import { getAdapter } from '../adapters/factory.js';
1
+ import { RegistryManager } from '#core/registry.js';
2
+ import { getAdapter } from '#adapters/factory.js';
3
3
  import { basename, dirname, join, sep } from 'path';
4
- import { getRegistryDir } from '../utils/paths.js';
5
- import { readText, writeText, ensureDir, fileExists, readJSON, writeJSON, removePath, } from '../utils/fs.js';
4
+ import { getRegistryDir } from '#shared/paths.js';
5
+ import { readText, writeText, ensureDir, fileExists, readJSON, writeJSON, removePath, } from '#shared/fs.js';
6
6
  import { createHash } from 'crypto';
7
7
  export class Syncer {
8
8
  constructor(registry, scanner, scope = 'workspace') {
@@ -1,4 +1,4 @@
1
- import { AssetType, DiscoveredFile, Conflict } from '../core/types.js';
1
+ import { AssetType, DiscoveredFile, Conflict } from '#core/types.js';
2
2
  export declare class Scanner {
3
3
  private scope;
4
4
  private stubPaths;
@@ -1,8 +1,8 @@
1
- import { fileExists, isDirectory, readJSON } from '../utils/fs.js';
2
- import { join, relative, sep } from 'path';
3
- import { getToolMarkers, getRegistryPath } from '../utils/paths.js';
1
+ import { fileExists, isDirectory, readJSON } from '#shared/fs.js';
2
+ import { join, relative } from 'path';
3
+ import { getToolMarkers, getRegistryPath } from '#shared/paths.js';
4
4
  import { stat, readdir } from 'fs/promises';
5
- import { getAdapter } from '../adapters/factory.js';
5
+ import { getAdapter } from '#adapters/factory.js';
6
6
  export class Scanner {
7
7
  constructor(scope = 'workspace') {
8
8
  this.stubPaths = new Set();
@@ -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;