@almyty/skills 1.0.2 → 1.0.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 +35 -38
- package/dist/__tests__/client.test.d.ts +1 -0
- package/dist/__tests__/client.test.js +145 -0
- package/dist/auth.d.ts +10 -31
- package/dist/auth.js +16 -88
- package/dist/client.js +23 -6
- package/dist/index.js +14 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,55 +1,38 @@
|
|
|
1
1
|
# @almyty/skills
|
|
2
2
|
|
|
3
|
-
Install and manage
|
|
3
|
+
Install and manage almyty skills in 30+ AI coding agents (Claude Code, Cursor, Windsurf, Copilot, Codex, and more).
|
|
4
4
|
|
|
5
|
-
## Quick
|
|
5
|
+
## Quick start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
npx @almyty/skills
|
|
10
|
-
|
|
11
|
-
# Keep skills synced (daemon mode)
|
|
12
|
-
npx @almyty/skills daemon
|
|
13
|
-
|
|
14
|
-
# Run a skill directly
|
|
15
|
-
npx @almyty/skills run @your-org/your-gateway/tool-name --param value
|
|
8
|
+
$ npx @almyty/auth login
|
|
9
|
+
$ npx @almyty/skills gateways
|
|
10
|
+
$ npx @almyty/skills install @org/gateway
|
|
16
11
|
```
|
|
17
12
|
|
|
18
|
-
## What It Does
|
|
19
|
-
|
|
20
|
-
almyty turns any API into AI-ready tools. This CLI installs those tools as **SKILL.md files** into your project, where they're automatically picked up by supported agents.
|
|
21
|
-
|
|
22
|
-
## Supported Agents
|
|
23
|
-
|
|
24
|
-
| Category | Agents |
|
|
25
|
-
|----------|--------|
|
|
26
|
-
| **IDE** | Claude Code, Cursor, Windsurf, VS Code Copilot, JetBrains |
|
|
27
|
-
| **AI Assistants** | Claude Desktop, ChatGPT, Gemini |
|
|
28
|
-
| **Dev Tools** | Aider, Continue, Cody, GitHub Copilot CLI |
|
|
29
|
-
| **Frameworks** | LangChain, LlamaIndex, CrewAI, AutoGPT |
|
|
30
|
-
|
|
31
13
|
## Commands
|
|
32
14
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
15
|
+
| Command | Description |
|
|
16
|
+
|---------|-------------|
|
|
17
|
+
| `gateways` | List your gateways |
|
|
18
|
+
| `list` | List all available skills |
|
|
19
|
+
| `list @org/gateway` | List skills from one gateway |
|
|
20
|
+
| `search <query>` | Search skills by keyword |
|
|
21
|
+
| `install @org/gateway` | Install all skills from a gateway |
|
|
22
|
+
| `install @org/gateway/skill` | Install a single skill |
|
|
23
|
+
| `installed` | Show locally installed skills |
|
|
24
|
+
| `remove` | Remove all installed skills |
|
|
25
|
+
| `run @org/gateway/skill [--key value]` | Execute a skill |
|
|
26
|
+
| `daemon [--interval 60]` | Sync all skills on a schedule |
|
|
27
|
+
| `watch @org/gateway [--interval 60]` | Watch a specific gateway for changes |
|
|
45
28
|
|
|
46
29
|
## References
|
|
47
30
|
|
|
48
31
|
Skills are referenced as `@org/gateway` or `@org/gateway/skill`:
|
|
49
32
|
|
|
50
33
|
```bash
|
|
51
|
-
npx @almyty/skills install @acme/petstore
|
|
52
|
-
npx @almyty/skills run @acme/petstore/get-pet
|
|
34
|
+
$ npx @almyty/skills install @acme/petstore
|
|
35
|
+
$ npx @almyty/skills run @acme/petstore/get-pet --id 123
|
|
53
36
|
```
|
|
54
37
|
|
|
55
38
|
## Configuration
|
|
@@ -63,7 +46,21 @@ Create `.almytyrc` in your project or home directory:
|
|
|
63
46
|
}
|
|
64
47
|
```
|
|
65
48
|
|
|
66
|
-
|
|
49
|
+
## Environment variables
|
|
50
|
+
|
|
51
|
+
| Variable | Description |
|
|
52
|
+
|----------|-------------|
|
|
53
|
+
| `ALMYTY_TOKEN` | Auth token override |
|
|
54
|
+
| `ALMYTY_URL` | API URL override |
|
|
55
|
+
| `ALMYTY_SKILLS_DIR` | Custom directory for installed skill files |
|
|
56
|
+
|
|
57
|
+
## Authentication
|
|
58
|
+
|
|
59
|
+
Requires `npx @almyty/auth login` first. Reads credentials from `~/.almyty/credentials.json`.
|
|
60
|
+
|
|
61
|
+
## Docs
|
|
62
|
+
|
|
63
|
+
https://almyty.com/docs
|
|
67
64
|
|
|
68
65
|
## License
|
|
69
66
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { parseRef } from '../client.js';
|
|
3
|
+
import { generateMetaSkill } from '../meta-skill.js';
|
|
4
|
+
import { loadConfig } from '../config.js';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// parseRef()
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
describe('parseRef', () => {
|
|
9
|
+
it('parses @org/gateway as a gateway ref', () => {
|
|
10
|
+
const result = parseRef('@org/gateway');
|
|
11
|
+
expect(result).toEqual({
|
|
12
|
+
type: 'gateway',
|
|
13
|
+
orgSlug: 'org',
|
|
14
|
+
gatewaySlug: 'gateway',
|
|
15
|
+
raw: '@org/gateway',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
it('parses @org/gateway/skill as a skill ref', () => {
|
|
19
|
+
const result = parseRef('@org/gateway/skill');
|
|
20
|
+
expect(result).toEqual({
|
|
21
|
+
type: 'skill',
|
|
22
|
+
orgSlug: 'org',
|
|
23
|
+
gatewaySlug: 'gateway',
|
|
24
|
+
skillName: 'skill',
|
|
25
|
+
raw: '@org/gateway/skill',
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it('parses a UUID string as a uuid ref', () => {
|
|
29
|
+
const uuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
|
30
|
+
const result = parseRef(uuid);
|
|
31
|
+
expect(result).toEqual({
|
|
32
|
+
type: 'uuid',
|
|
33
|
+
uuid,
|
|
34
|
+
raw: uuid,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
it('parses uppercase UUID correctly', () => {
|
|
38
|
+
const uuid = 'A1B2C3D4-E5F6-7890-ABCD-EF1234567890';
|
|
39
|
+
const result = parseRef(uuid);
|
|
40
|
+
expect(result.type).toBe('uuid');
|
|
41
|
+
expect(result.uuid).toBe(uuid);
|
|
42
|
+
});
|
|
43
|
+
it('returns search for a random string', () => {
|
|
44
|
+
const result = parseRef('some-random-query');
|
|
45
|
+
expect(result).toEqual({
|
|
46
|
+
type: 'search',
|
|
47
|
+
raw: 'some-random-query',
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
it('returns search for an empty string', () => {
|
|
51
|
+
const result = parseRef('');
|
|
52
|
+
expect(result).toEqual({
|
|
53
|
+
type: 'search',
|
|
54
|
+
raw: '',
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it('returns search for @ with only one segment', () => {
|
|
58
|
+
const result = parseRef('@org');
|
|
59
|
+
expect(result).toEqual({
|
|
60
|
+
type: 'search',
|
|
61
|
+
raw: '@org',
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it('returns search for @ with more than three segments', () => {
|
|
65
|
+
const result = parseRef('@org/gw/skill/extra');
|
|
66
|
+
expect(result).toEqual({
|
|
67
|
+
type: 'search',
|
|
68
|
+
raw: '@org/gw/skill/extra',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// generateMetaSkill()
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
describe('generateMetaSkill', () => {
|
|
76
|
+
it('returns a SkillFile with name "skills" and fileName "almyty-skills"', () => {
|
|
77
|
+
const skill = generateMetaSkill();
|
|
78
|
+
expect(skill.name).toBe('skills');
|
|
79
|
+
expect(skill.fileName).toBe('almyty-skills');
|
|
80
|
+
});
|
|
81
|
+
it('content includes npx @almyty/skills commands', () => {
|
|
82
|
+
const { content } = generateMetaSkill();
|
|
83
|
+
expect(content).toContain('npx @almyty/skills');
|
|
84
|
+
});
|
|
85
|
+
it('content references daemon command', () => {
|
|
86
|
+
const { content } = generateMetaSkill();
|
|
87
|
+
expect(content).toContain('daemon');
|
|
88
|
+
});
|
|
89
|
+
it('content references search command', () => {
|
|
90
|
+
const { content } = generateMetaSkill();
|
|
91
|
+
expect(content).toContain('search');
|
|
92
|
+
});
|
|
93
|
+
it('content references install command', () => {
|
|
94
|
+
const { content } = generateMetaSkill();
|
|
95
|
+
expect(content).toContain('install');
|
|
96
|
+
});
|
|
97
|
+
it('content references run command', () => {
|
|
98
|
+
const { content } = generateMetaSkill();
|
|
99
|
+
expect(content).toContain('run');
|
|
100
|
+
});
|
|
101
|
+
it('content includes SKILL.md front-matter', () => {
|
|
102
|
+
const { content } = generateMetaSkill();
|
|
103
|
+
expect(content).toContain('---');
|
|
104
|
+
expect(content).toContain('name: almyty-skills');
|
|
105
|
+
expect(content).toContain('description:');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// loadConfig()
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
describe('loadConfig', () => {
|
|
112
|
+
const originalEnv = process.env;
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
// Reset env between tests
|
|
115
|
+
process.env = { ...originalEnv };
|
|
116
|
+
delete process.env.APIFAI_SKILLS_DIR;
|
|
117
|
+
});
|
|
118
|
+
afterEach(() => {
|
|
119
|
+
process.env = originalEnv;
|
|
120
|
+
vi.restoreAllMocks();
|
|
121
|
+
});
|
|
122
|
+
it('returns empty config when no .almytyrc exists and no env var', () => {
|
|
123
|
+
// Use a temp directory that definitely has no .almytyrc
|
|
124
|
+
const config = loadConfig('/tmp/nonexistent-dir-' + Date.now());
|
|
125
|
+
expect(config).toEqual({});
|
|
126
|
+
});
|
|
127
|
+
it('uses APIFAI_SKILLS_DIR env var when set', () => {
|
|
128
|
+
process.env.APIFAI_SKILLS_DIR = '/custom/skills/dir';
|
|
129
|
+
const config = loadConfig();
|
|
130
|
+
expect(config).toEqual({ skillsDir: '/custom/skills/dir' });
|
|
131
|
+
});
|
|
132
|
+
it('env var takes precedence over .almytyrc files', () => {
|
|
133
|
+
process.env.APIFAI_SKILLS_DIR = '/env/override';
|
|
134
|
+
// Even if we pass a projectDir, env should take priority
|
|
135
|
+
const config = loadConfig('/some/project');
|
|
136
|
+
expect(config.skillsDir).toBe('/env/override');
|
|
137
|
+
});
|
|
138
|
+
it('returns empty config for a directory with no rc file and no home rc', () => {
|
|
139
|
+
// Use a deeply nested temp path that certainly has no .almytyrc anywhere
|
|
140
|
+
// and also won't match ~/.almytyrc (already handled by the missing file)
|
|
141
|
+
const fakePath = `/tmp/almyty-test-${Date.now()}/deeply/nested/project`;
|
|
142
|
+
const config = loadConfig(fakePath);
|
|
143
|
+
expect(config).toEqual({});
|
|
144
|
+
});
|
|
145
|
+
});
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,41 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Credential resolver for @almyty/skills.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Authentication itself lives in the dedicated @almyty/auth package
|
|
5
|
+
* (`npx @almyty/auth login`). This module just READS the shared
|
|
6
|
+
* credentials store and surfaces them to the rest of the skills CLI.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Lookup order:
|
|
9
|
+
* 1. ALMYTY_TOKEN environment variable (CI / scripts)
|
|
10
|
+
* 2. ~/.almyty/credentials.json (interactive use, written
|
|
11
|
+
* by `npx @almyty/auth login`)
|
|
10
12
|
*/
|
|
11
|
-
interface StoredCredentials {
|
|
12
|
-
url: string;
|
|
13
|
-
token: string;
|
|
14
|
-
email?: string;
|
|
15
|
-
expiresAt?: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Load stored credentials from ~/.almyty/credentials.json
|
|
19
|
-
*/
|
|
20
|
-
export declare function loadCredentials(): StoredCredentials | null;
|
|
21
|
-
/**
|
|
22
|
-
* Save credentials to ~/.almyty/credentials.json
|
|
23
|
-
*/
|
|
24
|
-
export declare function saveCredentials(creds: StoredCredentials): void;
|
|
25
|
-
/**
|
|
26
|
-
* Remove stored credentials
|
|
27
|
-
*/
|
|
28
|
-
export declare function logout(): void;
|
|
29
13
|
/**
|
|
30
|
-
* Resolve token: env var > stored credentials
|
|
14
|
+
* Resolve token: env var > stored credentials. Exits with a clear hint
|
|
15
|
+
* if no credentials are available.
|
|
31
16
|
*/
|
|
32
17
|
export declare function resolveAuth(): {
|
|
33
18
|
url: string;
|
|
34
19
|
token: string;
|
|
35
20
|
};
|
|
36
|
-
/**
|
|
37
|
-
* Interactive login: prompts for email/password, authenticates against
|
|
38
|
-
* the almyty backend, and stores the JWT token.
|
|
39
|
-
*/
|
|
40
|
-
export declare function login(baseUrl: string): Promise<void>;
|
|
41
|
-
export {};
|
package/dist/auth.js
CHANGED
|
@@ -1,59 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Credential resolver for @almyty/skills.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Authentication itself lives in the dedicated @almyty/auth package
|
|
5
|
+
* (`npx @almyty/auth login`). This module just READS the shared
|
|
6
|
+
* credentials store and surfaces them to the rest of the skills CLI.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Lookup order:
|
|
9
|
+
* 1. ALMYTY_TOKEN environment variable (CI / scripts)
|
|
10
|
+
* 2. ~/.almyty/credentials.json (interactive use, written
|
|
11
|
+
* by `npx @almyty/auth login`)
|
|
10
12
|
*/
|
|
11
|
-
import { readFileSync,
|
|
13
|
+
import { readFileSync, existsSync } from 'fs';
|
|
12
14
|
import { homedir } from 'os';
|
|
13
15
|
import { join } from 'path';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const CREDENTIALS_FILE = join(CREDENTIALS_DIR, 'credentials.json');
|
|
17
|
-
/**
|
|
18
|
-
* Load stored credentials from ~/.almyty/credentials.json
|
|
19
|
-
*/
|
|
20
|
-
export function loadCredentials() {
|
|
16
|
+
const CREDENTIALS_FILE = join(homedir(), '.almyty', 'credentials.json');
|
|
17
|
+
function loadCredentials() {
|
|
21
18
|
try {
|
|
22
19
|
if (!existsSync(CREDENTIALS_FILE))
|
|
23
20
|
return null;
|
|
24
|
-
|
|
25
|
-
return JSON.parse(data);
|
|
21
|
+
return JSON.parse(readFileSync(CREDENTIALS_FILE, 'utf-8'));
|
|
26
22
|
}
|
|
27
23
|
catch {
|
|
28
24
|
return null;
|
|
29
25
|
}
|
|
30
26
|
}
|
|
31
27
|
/**
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
export function saveCredentials(creds) {
|
|
35
|
-
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
36
|
-
writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Remove stored credentials
|
|
40
|
-
*/
|
|
41
|
-
export function logout() {
|
|
42
|
-
try {
|
|
43
|
-
if (existsSync(CREDENTIALS_FILE)) {
|
|
44
|
-
unlinkSync(CREDENTIALS_FILE);
|
|
45
|
-
console.log('Logged out. Credentials removed.');
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
console.log('No stored credentials found.');
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
console.error('Failed to remove credentials.');
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Resolve token: env var > stored credentials
|
|
28
|
+
* Resolve token: env var > stored credentials. Exits with a clear hint
|
|
29
|
+
* if no credentials are available.
|
|
57
30
|
*/
|
|
58
31
|
export function resolveAuth() {
|
|
59
32
|
const envToken = process.env.ALMYTY_TOKEN;
|
|
@@ -66,52 +39,7 @@ export function resolveAuth() {
|
|
|
66
39
|
return { url: creds.url, token: creds.token };
|
|
67
40
|
}
|
|
68
41
|
console.error('Not authenticated. Run one of:');
|
|
69
|
-
console.error(' npx @almyty/
|
|
70
|
-
console.error(' export ALMYTY_TOKEN=<
|
|
42
|
+
console.error(' npx @almyty/auth login # browser-based login');
|
|
43
|
+
console.error(' export ALMYTY_TOKEN=<token> # for CI');
|
|
71
44
|
process.exit(1);
|
|
72
45
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Interactive login: prompts for email/password, authenticates against
|
|
75
|
-
* the almyty backend, and stores the JWT token.
|
|
76
|
-
*/
|
|
77
|
-
export async function login(baseUrl) {
|
|
78
|
-
const rl = createInterface({
|
|
79
|
-
input: process.stdin,
|
|
80
|
-
output: process.stderr,
|
|
81
|
-
});
|
|
82
|
-
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
83
|
-
try {
|
|
84
|
-
console.error(`Logging in to ${baseUrl}...\n`);
|
|
85
|
-
const email = await ask('Email: ');
|
|
86
|
-
const password = await ask('Password: ');
|
|
87
|
-
console.error('\nAuthenticating...');
|
|
88
|
-
const response = await fetch(`${baseUrl}/api/auth/login`, {
|
|
89
|
-
method: 'POST',
|
|
90
|
-
headers: { 'Content-Type': 'application/json' },
|
|
91
|
-
body: JSON.stringify({ email, password }),
|
|
92
|
-
});
|
|
93
|
-
if (!response.ok) {
|
|
94
|
-
const errBody = await response.json().catch(() => ({ message: 'Authentication failed' }));
|
|
95
|
-
console.error(`Error: ${errBody.message || 'Authentication failed'}`);
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
const data = await response.json();
|
|
99
|
-
const token = data.accessToken || data.token;
|
|
100
|
-
if (!token) {
|
|
101
|
-
console.error('Error: No token received from server');
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
saveCredentials({
|
|
105
|
-
url: baseUrl,
|
|
106
|
-
token,
|
|
107
|
-
email,
|
|
108
|
-
});
|
|
109
|
-
console.error(`\n✓ Logged in as ${email}`);
|
|
110
|
-
console.error(` Credentials saved to ${CREDENTIALS_FILE}`);
|
|
111
|
-
console.error('\nYou can now install skills:');
|
|
112
|
-
console.error(' npx @almyty/skills install --gateway <id>');
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
rl.close();
|
|
116
|
-
}
|
|
117
|
-
}
|
package/dist/client.js
CHANGED
|
@@ -23,7 +23,7 @@ export class AlmytyClient {
|
|
|
23
23
|
this.token = token;
|
|
24
24
|
}
|
|
25
25
|
async request(path) {
|
|
26
|
-
const url = `${this.baseUrl}
|
|
26
|
+
const url = `${this.baseUrl}${path}`;
|
|
27
27
|
const response = await fetch(url, {
|
|
28
28
|
headers: {
|
|
29
29
|
'Authorization': `Bearer ${this.token}`,
|
|
@@ -32,7 +32,7 @@ export class AlmytyClient {
|
|
|
32
32
|
});
|
|
33
33
|
if (!response.ok) {
|
|
34
34
|
if (response.status === 401) {
|
|
35
|
-
throw new Error('Authentication failed. Run: npx @almyty/
|
|
35
|
+
throw new Error('Authentication failed. Run: npx @almyty/auth login');
|
|
36
36
|
}
|
|
37
37
|
const text = await response.text();
|
|
38
38
|
throw new Error(`API error (${response.status}): ${text}`);
|
|
@@ -40,7 +40,7 @@ export class AlmytyClient {
|
|
|
40
40
|
return response.json();
|
|
41
41
|
}
|
|
42
42
|
async post(path, body) {
|
|
43
|
-
const url = `${this.baseUrl}
|
|
43
|
+
const url = `${this.baseUrl}${path}`;
|
|
44
44
|
const resp = await fetch(url, {
|
|
45
45
|
method: 'POST',
|
|
46
46
|
headers: {
|
|
@@ -86,8 +86,8 @@ export class AlmytyClient {
|
|
|
86
86
|
}
|
|
87
87
|
async listGateways() {
|
|
88
88
|
const data = await this.request('/gateways');
|
|
89
|
-
const gateways = data?.data?.
|
|
90
|
-
return gateways.map((gw) => ({
|
|
89
|
+
const gateways = data?.data?.gateways || data?.data?.data || data?.data || [];
|
|
90
|
+
return (Array.isArray(gateways) ? gateways : []).map((gw) => ({
|
|
91
91
|
id: gw.id,
|
|
92
92
|
name: gw.name,
|
|
93
93
|
type: gw.type,
|
|
@@ -113,7 +113,24 @@ export class AlmytyClient {
|
|
|
113
113
|
return skills;
|
|
114
114
|
}
|
|
115
115
|
async executeSkill(gatewayId, toolId, parameters) {
|
|
116
|
-
|
|
116
|
+
// The execute endpoint expects a UUID. If toolId is a name slug,
|
|
117
|
+
// resolve it by listing the gateway's tools and matching.
|
|
118
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
119
|
+
let resolvedId = toolId;
|
|
120
|
+
if (!uuidRegex.test(toolId)) {
|
|
121
|
+
const tools = await this.request(`/gateways/${gatewayId}/tools`);
|
|
122
|
+
const list = tools?.data?.gatewayTools || tools?.data?.data || tools?.data || [];
|
|
123
|
+
const match = (Array.isArray(list) ? list : []).find((gt) => {
|
|
124
|
+
const t = gt.tool || gt;
|
|
125
|
+
const slug = t.name?.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
126
|
+
return slug === toolId || t.name === toolId;
|
|
127
|
+
});
|
|
128
|
+
if (!match) {
|
|
129
|
+
throw new Error(`Tool "${toolId}" not found in gateway ${gatewayId}`);
|
|
130
|
+
}
|
|
131
|
+
resolvedId = (match.tool || match).id;
|
|
132
|
+
}
|
|
133
|
+
return this.post(`/gateways/${gatewayId}/skills/${resolvedId}/execute`, { parameters });
|
|
117
134
|
}
|
|
118
135
|
async resolveRef(ref) {
|
|
119
136
|
if (ref.startsWith('@')) {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { resolveAuth } from './auth.js';
|
|
3
3
|
import { AlmytyClient, parseRef } from './client.js';
|
|
4
4
|
import { getAllTargets } from './agents.js';
|
|
5
5
|
import { installSkills, removeSkills, listInstalledSkills } from './installer.js';
|
|
@@ -14,8 +14,6 @@ Usage:
|
|
|
14
14
|
npx @almyty/skills <command> [options]
|
|
15
15
|
|
|
16
16
|
Commands:
|
|
17
|
-
login Authenticate with almyty
|
|
18
|
-
logout Remove stored credentials
|
|
19
17
|
daemon Start skill daemon (syncs all skills)
|
|
20
18
|
install <ref> Install skills
|
|
21
19
|
list [ref] List available skills
|
|
@@ -25,6 +23,10 @@ Commands:
|
|
|
25
23
|
remove Remove all installed skills
|
|
26
24
|
gateways List your gateways
|
|
27
25
|
|
|
26
|
+
Authentication:
|
|
27
|
+
Run \`npx @almyty/auth login\` once. Credentials at ~/.almyty/credentials.json
|
|
28
|
+
are shared across every almyty CLI.
|
|
29
|
+
|
|
28
30
|
References:
|
|
29
31
|
@org/gateway All skills from a gateway
|
|
30
32
|
@org/gateway/skill A specific skill
|
|
@@ -142,14 +144,15 @@ async function main() {
|
|
|
142
144
|
const config = loadConfig(projectDir);
|
|
143
145
|
const urlOverride = args.flags.url || config.url;
|
|
144
146
|
switch (command) {
|
|
145
|
-
case 'login':
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
case 'login':
|
|
148
|
+
case 'logout':
|
|
149
|
+
case 'whoami': {
|
|
150
|
+
// Auth has moved to its own dedicated package. Redirect rather
|
|
151
|
+
// than silently doing nothing — users typing the old commands
|
|
152
|
+
// should see the new entry point.
|
|
153
|
+
console.error(`Authentication moved to @almyty/auth.`);
|
|
154
|
+
console.error(` npx @almyty/auth ${command}`);
|
|
155
|
+
process.exit(1);
|
|
153
156
|
}
|
|
154
157
|
case 'gateways': {
|
|
155
158
|
const { url, token } = resolveAuth();
|