gm-skill 0.1.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.
- package/README.md +99 -0
- package/index.js +9 -0
- package/lib/manifest.js +96 -0
- package/package.json +30 -0
- package/test.js +89 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# gm-skill
|
|
2
|
+
|
|
3
|
+
Unified skill library for gm platform implementations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install gm-skill
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Daemon Bootstrap Functions
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
const {
|
|
17
|
+
ensureRsLearningDaemonRunning,
|
|
18
|
+
ensureRsCodeinsightDaemonRunning,
|
|
19
|
+
ensureRsSearchDaemonRunning,
|
|
20
|
+
ensureAcptoapiRunning,
|
|
21
|
+
checkPortReachable
|
|
22
|
+
} = require('gm-skill');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
#### Functions
|
|
26
|
+
|
|
27
|
+
- `ensureRsLearningDaemonRunning()` - Ensures rs-learn daemon is running
|
|
28
|
+
- `ensureRsCodeinsightDaemonRunning()` - Ensures rs-codeinsight daemon is running
|
|
29
|
+
- `ensureRsSearchDaemonRunning()` - Ensures rs-search daemon is running
|
|
30
|
+
- `ensureAcptoapiRunning()` - Ensures acptoapi daemon is running
|
|
31
|
+
- `checkPortReachable(host, port, timeoutMs)` - Check if a port is reachable
|
|
32
|
+
|
|
33
|
+
All functions are async and return an object with `ok` and additional metadata.
|
|
34
|
+
|
|
35
|
+
### Spool Dispatch Helpers
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
const { spool } = require('gm-skill');
|
|
39
|
+
const { writeSpool, readSpoolOutput, waitForCompletion, getAllOutputs } = spool;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
#### Functions
|
|
43
|
+
|
|
44
|
+
- `writeSpool(body, lang, options)` - Write code to spool input directory; returns `{ id, path, lang, ext }`
|
|
45
|
+
- `body` (string) - Code to execute
|
|
46
|
+
- `lang` (string, default: `'nodejs'`) - Language: nodejs, python, bash, typescript, go, rust, c, cpp, java, deno
|
|
47
|
+
- `options` (object, optional) - `{ taskId, sessionId }`
|
|
48
|
+
- `readSpoolOutput(id)` - Read completed task output; returns `{ id, stdout, stderr, metadata, exitCode, durationMs, timedOut }`
|
|
49
|
+
- `waitForCompletion(id, timeoutMs)` - Poll for task completion; returns promise with `{ ok, ...output }`
|
|
50
|
+
- `getAllOutputs()` - Enumerate all completed tasks in spool directory; returns array of output objects
|
|
51
|
+
|
|
52
|
+
All paths are platform-aware (Windows and POSIX compatible).
|
|
53
|
+
|
|
54
|
+
#### Example
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
const { spool } = require('gm-skill');
|
|
58
|
+
|
|
59
|
+
const result = spool.writeSpool('console.log("hello")', 'nodejs');
|
|
60
|
+
console.log(result.id);
|
|
61
|
+
|
|
62
|
+
const output = await spool.waitForCompletion(result.id, 30000);
|
|
63
|
+
console.log(output.stdout);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Skill Manifests
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
const { manifest } = require('gm-skill');
|
|
70
|
+
const { getManifest, getSkill, getAllSkills } = manifest;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Functions
|
|
74
|
+
|
|
75
|
+
- `getManifest()` - Get full package manifest with all 4 core skills; returns `{ name, version, description, skills: [...] }`
|
|
76
|
+
- `getSkill(name)` - Get individual skill manifest by name (gm, gm-execute, gm-emit, gm-complete); returns skill object with metadata
|
|
77
|
+
- `getAllSkills()` - Get array of all 4 core skills with full metadata; returns `[{ name, description, allowedTools, compatiblePlatforms, endToEnd, skillMdContent }, ...]`
|
|
78
|
+
|
|
79
|
+
Skill metadata includes:
|
|
80
|
+
- `name` - Skill identifier
|
|
81
|
+
- `description` - Human-readable skill purpose
|
|
82
|
+
- `allowedTools` - Array of tool names the skill permits
|
|
83
|
+
- `compatiblePlatforms` - Array of platforms this skill targets
|
|
84
|
+
- `endToEnd` - Boolean flag indicating end-to-end skill vs helper
|
|
85
|
+
- `skillMdContent` - Full SKILL.md file content
|
|
86
|
+
|
|
87
|
+
#### Example
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
const { manifest } = require('gm-skill');
|
|
91
|
+
|
|
92
|
+
const allSkills = manifest.getAllSkills();
|
|
93
|
+
allSkills.forEach(skill => {
|
|
94
|
+
console.log(`${skill.name}: ${skill.description}`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const gmSkill = manifest.getSkill('gm');
|
|
98
|
+
console.log('Allowed tools:', gmSkill.allowedTools);
|
|
99
|
+
```
|
package/index.js
ADDED
package/lib/manifest.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function parseSkillMarkdown(content) {
|
|
5
|
+
const normalized = content.replace(/\r\n/g, '\n');
|
|
6
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
7
|
+
|
|
8
|
+
if (!match) return {};
|
|
9
|
+
|
|
10
|
+
const frontmatter = match[1];
|
|
11
|
+
const metadata = {};
|
|
12
|
+
|
|
13
|
+
frontmatter.split('\n').forEach(line => {
|
|
14
|
+
const colonIdx = line.indexOf(':');
|
|
15
|
+
if (colonIdx === -1) return;
|
|
16
|
+
|
|
17
|
+
const key = line.substring(0, colonIdx).trim();
|
|
18
|
+
const value = line.substring(colonIdx + 1).trim();
|
|
19
|
+
|
|
20
|
+
if (key === 'allowed-tools') {
|
|
21
|
+
metadata.allowedTools = value
|
|
22
|
+
.split(',')
|
|
23
|
+
.map(t => t.trim().replace(/^['\"]|['\"]$/g, ''));
|
|
24
|
+
} else {
|
|
25
|
+
metadata[key] = value;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return metadata;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readSkillManifest(skillName) {
|
|
33
|
+
const skillMdPath = path.join(
|
|
34
|
+
__dirname,
|
|
35
|
+
'..',
|
|
36
|
+
'..',
|
|
37
|
+
'gm-starter',
|
|
38
|
+
'skills',
|
|
39
|
+
skillName,
|
|
40
|
+
'SKILL.md'
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
44
|
+
return {
|
|
45
|
+
name: skillName,
|
|
46
|
+
description: '',
|
|
47
|
+
allowedTools: [],
|
|
48
|
+
compatiblePlatforms: [],
|
|
49
|
+
endToEnd: false,
|
|
50
|
+
skillMdContent: ''
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const content = fs.readFileSync(skillMdPath, 'utf8');
|
|
55
|
+
const metadata = parseSkillMarkdown(content);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
name: metadata.name || skillName,
|
|
59
|
+
description: metadata.description || '',
|
|
60
|
+
version: metadata.version || '1.0.0',
|
|
61
|
+
category: metadata.category || 'skill',
|
|
62
|
+
allowedTools: metadata.allowedTools || [],
|
|
63
|
+
compatiblePlatforms: metadata['compatible-platforms']
|
|
64
|
+
? metadata['compatible-platforms'].split(',').map(p => p.trim())
|
|
65
|
+
: [],
|
|
66
|
+
endToEnd: metadata['end-to-end'] === 'true',
|
|
67
|
+
skillMdContent: content
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getManifest() {
|
|
72
|
+
const skills = ['gm', 'gm-execute', 'gm-emit', 'gm-complete'];
|
|
73
|
+
return {
|
|
74
|
+
name: 'gm-skill',
|
|
75
|
+
version: '1.0.0',
|
|
76
|
+
description: 'gm skill manifest and daemon bootstrap integration',
|
|
77
|
+
skills: skills.map(name => readSkillManifest(name))
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getSkill(name) {
|
|
82
|
+
return readSkillManifest(name);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getAllSkills() {
|
|
86
|
+
const skills = ['gm', 'gm-execute', 'gm-emit', 'gm-complete'];
|
|
87
|
+
return skills.map(name => readSkillManifest(name));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
getManifest,
|
|
92
|
+
getSkill,
|
|
93
|
+
getAllSkills,
|
|
94
|
+
parseSkillMarkdown,
|
|
95
|
+
readSkillManifest
|
|
96
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gm-skill",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unified skill library for gm platform implementations",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.js",
|
|
8
|
+
"./daemon-bootstrap": "./index.js",
|
|
9
|
+
"./manifest": "./lib/manifest.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "node test.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"gm",
|
|
16
|
+
"skills",
|
|
17
|
+
"daemon",
|
|
18
|
+
"bootstrap"
|
|
19
|
+
],
|
|
20
|
+
"author": "AnEntrypoint",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/AnEntrypoint/gm.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/AnEntrypoint/gm#readme",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=16.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/test.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const { spool } = require('./index.js');
|
|
6
|
+
|
|
7
|
+
const testDir = path.join(os.tmpdir(), 'gm-skill-test');
|
|
8
|
+
|
|
9
|
+
function cleanup() {
|
|
10
|
+
if (fs.existsSync(testDir)) {
|
|
11
|
+
fs.rmSync(testDir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function runTests() {
|
|
16
|
+
cleanup();
|
|
17
|
+
process.chdir(testDir);
|
|
18
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
19
|
+
|
|
20
|
+
console.log('Testing spool.writeSpool...');
|
|
21
|
+
const writeResult = spool.writeSpool('console.log("test output")', 'nodejs');
|
|
22
|
+
assert(writeResult.id, 'writeSpool returns id');
|
|
23
|
+
assert(writeResult.path, 'writeSpool returns path');
|
|
24
|
+
assert(writeResult.lang === 'nodejs', 'writeSpool returns correct lang');
|
|
25
|
+
assert(writeResult.ext === 'js', 'writeSpool returns correct ext');
|
|
26
|
+
assert(fs.existsSync(writeResult.path), 'spool file created on disk');
|
|
27
|
+
const fileContent = fs.readFileSync(writeResult.path, 'utf-8');
|
|
28
|
+
assert(fileContent.includes('console.log("test output")'), 'spool file contains body');
|
|
29
|
+
console.log('✓ writeSpool works');
|
|
30
|
+
|
|
31
|
+
console.log('\nTesting spool.readSpoolOutput (empty case)...');
|
|
32
|
+
const nonExistentId = 'does-not-exist';
|
|
33
|
+
const emptyRead = spool.readSpoolOutput(nonExistentId);
|
|
34
|
+
assert.strictEqual(emptyRead.id, nonExistentId, 'readSpoolOutput returns id');
|
|
35
|
+
assert.strictEqual(emptyRead.stdout, '', 'readSpoolOutput returns empty stdout when no output');
|
|
36
|
+
assert.strictEqual(emptyRead.stderr, '', 'readSpoolOutput returns empty stderr when no output');
|
|
37
|
+
assert(emptyRead.metadata, 'readSpoolOutput returns metadata object');
|
|
38
|
+
console.log('✓ readSpoolOutput handles missing files gracefully');
|
|
39
|
+
|
|
40
|
+
console.log('\nTesting spool.getAllOutputs...');
|
|
41
|
+
const baseDir = spool.getSpoolBaseDir();
|
|
42
|
+
const outDir = path.join(baseDir, 'out');
|
|
43
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(path.join(outDir, 'task1.out'), 'output1');
|
|
46
|
+
fs.writeFileSync(path.join(outDir, 'task1.json'), JSON.stringify({ exitCode: 0 }));
|
|
47
|
+
fs.writeFileSync(path.join(outDir, 'task2.err'), 'error2');
|
|
48
|
+
fs.writeFileSync(path.join(outDir, 'task2.json'), JSON.stringify({ exitCode: 1 }));
|
|
49
|
+
|
|
50
|
+
const allOutputs = spool.getAllOutputs();
|
|
51
|
+
assert(allOutputs.length === 2, 'getAllOutputs returns 2 tasks');
|
|
52
|
+
const ids = allOutputs.map(o => o.id).sort();
|
|
53
|
+
assert(ids.includes('task1') && ids.includes('task2'), 'getAllOutputs returns correct task ids');
|
|
54
|
+
const task1 = allOutputs.find(o => o.id === 'task1');
|
|
55
|
+
assert(task1.stdout === 'output1', 'getAllOutputs includes stdout');
|
|
56
|
+
const task2 = allOutputs.find(o => o.id === 'task2');
|
|
57
|
+
assert(task2.stderr === 'error2', 'getAllOutputs includes stderr');
|
|
58
|
+
console.log('✓ getAllOutputs enumerates completed tasks');
|
|
59
|
+
|
|
60
|
+
console.log('\nTesting spool.waitForCompletion (timeout)...');
|
|
61
|
+
const timeoutResult = await spool.waitForCompletion('instant-timeout', 100);
|
|
62
|
+
assert(timeoutResult.timedOut === true, 'waitForCompletion times out gracefully');
|
|
63
|
+
assert(timeoutResult.ok === false, 'waitForCompletion returns ok:false on timeout');
|
|
64
|
+
console.log('✓ waitForCompletion respects timeout');
|
|
65
|
+
|
|
66
|
+
console.log('\nTesting spool helpers with different languages...');
|
|
67
|
+
const pyResult = spool.writeSpool('print("hello")', 'python');
|
|
68
|
+
assert(pyResult.ext === 'py', 'writeSpool maps python to .py');
|
|
69
|
+
const tsResult = spool.writeSpool('console.log()', 'typescript');
|
|
70
|
+
assert(tsResult.ext === 'ts', 'writeSpool maps typescript to .ts');
|
|
71
|
+
const bashResult = spool.writeSpool('echo hello', 'bash');
|
|
72
|
+
assert(bashResult.ext === 'sh', 'writeSpool maps bash to .sh');
|
|
73
|
+
console.log('✓ writeSpool handles language variants');
|
|
74
|
+
|
|
75
|
+
console.log('\nTesting platform-aware paths...');
|
|
76
|
+
const baseDir2 = spool.getSpoolBaseDir();
|
|
77
|
+
assert(baseDir2, 'getSpoolBaseDir returns a path');
|
|
78
|
+
assert(baseDir2.includes('.gm'), 'spool path contains .gm directory');
|
|
79
|
+
console.log('✓ Platform paths are set correctly');
|
|
80
|
+
|
|
81
|
+
cleanup();
|
|
82
|
+
console.log('\n✓✓✓ All tests passed ✓✓✓');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
runTests().catch(e => {
|
|
86
|
+
console.error('Test failed:', e.message);
|
|
87
|
+
cleanup();
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|