@almyty/skills 1.0.5 → 1.0.7
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/dist/__tests__/client.test.d.ts +1 -0
- package/dist/__tests__/client.test.js +145 -0
- package/dist/auth.d.ts +4 -18
- package/dist/auth.js +3 -55
- package/dist/client.js +10 -10
- package/dist/index.js +7 -7
- package/package.json +5 -2
|
@@ -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,20 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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.
|
|
7
|
-
*
|
|
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`)
|
|
2
|
+
* Thin re-export from the shared @almyty/client credential resolver.
|
|
3
|
+
* Keeps `./auth.js` imports working across the skills-cli codebase.
|
|
12
4
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* if no credentials are available.
|
|
16
|
-
*/
|
|
17
|
-
export declare function resolveAuth(): {
|
|
18
|
-
url: string;
|
|
19
|
-
token: string;
|
|
20
|
-
};
|
|
5
|
+
export { resolveCredentials, resolveCredentialsOrExit, resolveCredentialsOrExit as resolveAuth, loadCredentials, CREDENTIALS_FILE, } from '@almyty/client';
|
|
6
|
+
export type { StoredCredentials } from '@almyty/client';
|
package/dist/auth.js
CHANGED
|
@@ -1,57 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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.
|
|
7
|
-
*
|
|
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`)
|
|
2
|
+
* Thin re-export from the shared @almyty/client credential resolver.
|
|
3
|
+
* Keeps `./auth.js` imports working across the skills-cli codebase.
|
|
12
4
|
*/
|
|
13
|
-
|
|
14
|
-
import { homedir } from 'os';
|
|
15
|
-
import { join } from 'path';
|
|
16
|
-
const CREDENTIALS_FILE = join(homedir(), '.almyty', 'credentials.json');
|
|
17
|
-
function loadCredentials() {
|
|
18
|
-
try {
|
|
19
|
-
if (!existsSync(CREDENTIALS_FILE))
|
|
20
|
-
return null;
|
|
21
|
-
return JSON.parse(readFileSync(CREDENTIALS_FILE, 'utf-8'));
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Resolve token: env var > stored credentials. Exits with a clear hint
|
|
29
|
-
* if no credentials are available.
|
|
30
|
-
*/
|
|
31
|
-
export function resolveAuth() {
|
|
32
|
-
const envToken = process.env.ALMYTY_TOKEN;
|
|
33
|
-
// Read URL from: env > config file > default
|
|
34
|
-
let configUrl;
|
|
35
|
-
try {
|
|
36
|
-
const { readFileSync, existsSync } = require('fs');
|
|
37
|
-
const { join } = require('path');
|
|
38
|
-
const { homedir } = require('os');
|
|
39
|
-
const configPath = join(homedir(), '.almyty', 'config.json');
|
|
40
|
-
if (existsSync(configPath)) {
|
|
41
|
-
configUrl = JSON.parse(readFileSync(configPath, 'utf-8')).apiUrl;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch { }
|
|
45
|
-
const envUrl = process.env.ALMYTY_URL || configUrl || 'https://api.almyty.com';
|
|
46
|
-
if (envToken) {
|
|
47
|
-
return { url: envUrl, token: envToken };
|
|
48
|
-
}
|
|
49
|
-
const creds = loadCredentials();
|
|
50
|
-
if (creds?.token) {
|
|
51
|
-
return { url: creds.url, token: creds.token };
|
|
52
|
-
}
|
|
53
|
-
console.error('Not authenticated. Run one of:');
|
|
54
|
-
console.error(' npx @almyty/auth login # browser-based login');
|
|
55
|
-
console.error(' export ALMYTY_TOKEN=<token> # for CI');
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
5
|
+
export { resolveCredentials, resolveCredentialsOrExit, resolveCredentialsOrExit as resolveAuth, loadCredentials, CREDENTIALS_FILE, } from '@almyty/client';
|
package/dist/client.js
CHANGED
|
@@ -4,14 +4,14 @@ export function parseRef(ref) {
|
|
|
4
4
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5
5
|
if (uuidRegex.test(ref))
|
|
6
6
|
return { type: 'uuid', uuid: ref, raw: ref };
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
7
|
+
// Accept both @org/gateway and org/gateway (@ is optional)
|
|
8
|
+
const normalized = ref.startsWith('@') ? ref.slice(1) : ref;
|
|
9
|
+
const parts = normalized.split('/');
|
|
10
|
+
if (parts.length === 3) {
|
|
11
|
+
return { type: 'skill', orgSlug: parts[0], gatewaySlug: parts[1], skillName: parts[2], raw: ref };
|
|
12
|
+
}
|
|
13
|
+
if (parts.length === 2) {
|
|
14
|
+
return { type: 'gateway', orgSlug: parts[0], gatewaySlug: parts[1], raw: ref };
|
|
15
15
|
}
|
|
16
16
|
return { type: 'search', raw: ref };
|
|
17
17
|
}
|
|
@@ -70,7 +70,7 @@ export class AlmytyClient {
|
|
|
70
70
|
return data?.data?.skills || [];
|
|
71
71
|
}
|
|
72
72
|
async fetchGateway(gatewayIdOrRef) {
|
|
73
|
-
if (gatewayIdOrRef.
|
|
73
|
+
if (gatewayIdOrRef.includes('/')) {
|
|
74
74
|
const parsed = parseRef(gatewayIdOrRef);
|
|
75
75
|
if (parsed.orgSlug && parsed.gatewaySlug) {
|
|
76
76
|
return this.resolveGateway(parsed.orgSlug, parsed.gatewaySlug);
|
|
@@ -133,7 +133,7 @@ export class AlmytyClient {
|
|
|
133
133
|
return this.post(`/gateways/${gatewayId}/skills/${resolvedId}/execute`, { parameters });
|
|
134
134
|
}
|
|
135
135
|
async resolveRef(ref) {
|
|
136
|
-
if (ref.
|
|
136
|
+
if (ref.includes('/')) {
|
|
137
137
|
const parsed = parseRef(ref);
|
|
138
138
|
if (parsed.orgSlug && parsed.gatewaySlug) {
|
|
139
139
|
const gw = await this.resolveGateway(parsed.orgSlug, parsed.gatewaySlug);
|
package/dist/index.js
CHANGED
|
@@ -28,8 +28,8 @@ Authentication:
|
|
|
28
28
|
are shared across every almyty CLI.
|
|
29
29
|
|
|
30
30
|
References:
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
org/gateway All skills from a gateway
|
|
32
|
+
org/gateway/skill A specific skill
|
|
33
33
|
skill-name Search by name
|
|
34
34
|
<uuid> Direct ID reference
|
|
35
35
|
|
|
@@ -48,9 +48,9 @@ Options:
|
|
|
48
48
|
|
|
49
49
|
Examples:
|
|
50
50
|
npx @almyty/skills daemon
|
|
51
|
-
npx @almyty/skills install
|
|
51
|
+
npx @almyty/skills install myorg/petstore/get-pet
|
|
52
52
|
npx @almyty/skills search "weather"
|
|
53
|
-
npx @almyty/skills run
|
|
53
|
+
npx @almyty/skills run myorg/petstore/get-pet --petId 123
|
|
54
54
|
`);
|
|
55
55
|
}
|
|
56
56
|
function parseArgs(argv) {
|
|
@@ -76,7 +76,7 @@ function parseArgs(argv) {
|
|
|
76
76
|
else if (arg === '--version' || arg === '-v') {
|
|
77
77
|
result.flags.version = true;
|
|
78
78
|
}
|
|
79
|
-
else if (arg.
|
|
79
|
+
else if (arg.includes('/')) {
|
|
80
80
|
result.ref = arg;
|
|
81
81
|
}
|
|
82
82
|
else if (arg.startsWith('--')) {
|
|
@@ -302,7 +302,7 @@ async function main() {
|
|
|
302
302
|
const desc = r.toolDescription ? ` — ${r.toolDescription}` : '';
|
|
303
303
|
console.log(` ${label}${desc}`);
|
|
304
304
|
}
|
|
305
|
-
console.log(`\nBe more specific: npx @almyty/skills install
|
|
305
|
+
console.log(`\nBe more specific: npx @almyty/skills install org/gateway/skill`);
|
|
306
306
|
return;
|
|
307
307
|
}
|
|
308
308
|
}
|
|
@@ -351,7 +351,7 @@ async function main() {
|
|
|
351
351
|
toolId = results[0].toolId;
|
|
352
352
|
}
|
|
353
353
|
else {
|
|
354
|
-
console.error('Error: run requires a skill reference (
|
|
354
|
+
console.error('Error: run requires a skill reference (org/gateway/skill or skill-name)');
|
|
355
355
|
process.exit(1);
|
|
356
356
|
}
|
|
357
357
|
const params = parseRunParams(args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@almyty/skills",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Install API skills into AI coding agents — Claude Code, Cursor, Windsurf, Copilot, Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"build": "tsc",
|
|
14
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
15
15
|
"dev": "tsx src/index.ts",
|
|
16
16
|
"test": "vitest run",
|
|
17
17
|
"test:watch": "vitest",
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
],
|
|
32
32
|
"author": "almyty",
|
|
33
33
|
"license": "BSL-1.1",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@almyty/client": "^0.1.0"
|
|
36
|
+
},
|
|
34
37
|
"devDependencies": {
|
|
35
38
|
"@types/node": "^25.4.0",
|
|
36
39
|
"tsx": "^4.7.0",
|