@shiva-fw/cli 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.
- package/.editorconfig +38 -0
- package/.gitattributes +18 -0
- package/.nvmrc +1 -0
- package/README.md +179 -0
- package/bin/shiva.js +4 -0
- package/package.json +44 -0
- package/recipes/full-rp.json +77 -0
- package/recipes/minimal.json +30 -0
- package/recipes/standard.json +46 -0
- package/src/commands/ai/context.js +89 -0
- package/src/commands/ai/link.js +38 -0
- package/src/commands/ai/mcp.js +39 -0
- package/src/commands/config/validate.js +65 -0
- package/src/commands/docs/api.js +81 -0
- package/src/commands/docs/build.js +14 -0
- package/src/commands/docs/deploy.js +14 -0
- package/src/commands/docs/serve.js +14 -0
- package/src/commands/init.js +167 -0
- package/src/commands/install.js +108 -0
- package/src/commands/locale/missing.js +83 -0
- package/src/commands/make/contract.js +45 -0
- package/src/commands/make/migration.js +69 -0
- package/src/commands/make/model.js +63 -0
- package/src/commands/make/module.js +115 -0
- package/src/commands/make/seed.js +51 -0
- package/src/commands/make/service.js +60 -0
- package/src/commands/make/test.js +53 -0
- package/src/commands/mcp.js +26 -0
- package/src/commands/migrate/rollback.js +155 -0
- package/src/commands/migrate/run.js +159 -0
- package/src/commands/migrate/status.js +137 -0
- package/src/commands/module/list.js +46 -0
- package/src/commands/module/status.js +64 -0
- package/src/commands/outdated.js +59 -0
- package/src/commands/remove.js +61 -0
- package/src/commands/seed.js +108 -0
- package/src/commands/test.js +88 -0
- package/src/commands/update.js +90 -0
- package/src/generators/index.js +78 -0
- package/src/generators/templates/contract.lua.tpl +12 -0
- package/src/generators/templates/migration.lua.tpl +15 -0
- package/src/generators/templates/model.lua.tpl +14 -0
- package/src/generators/templates/module/client/init.lua.tpl +5 -0
- package/src/generators/templates/module/config/config.lua.tpl +4 -0
- package/src/generators/templates/module/fxmanifest.lua.tpl +41 -0
- package/src/generators/templates/module/locales/en.lua.tpl +4 -0
- package/src/generators/templates/module/module.lua.tpl +10 -0
- package/src/generators/templates/module/server/init.lua.tpl +2 -0
- package/src/generators/templates/module/shared/init.lua.tpl +5 -0
- package/src/generators/templates/seed.lua.tpl +10 -0
- package/src/generators/templates/service.lua.tpl +7 -0
- package/src/generators/templates/test.lua.tpl +39 -0
- package/src/index.js +113 -0
- package/src/mcp/resources/contracts.js +68 -0
- package/src/mcp/resources/docs.js +56 -0
- package/src/mcp/resources/examples.js +235 -0
- package/src/mcp/server.js +121 -0
- package/src/mcp/tools/config.js +53 -0
- package/src/mcp/tools/contracts.js +37 -0
- package/src/mcp/tools/database.js +93 -0
- package/src/mcp/tools/docs.js +38 -0
- package/src/mcp/tools/events.js +26 -0
- package/src/mcp/tools/items.js +280 -0
- package/src/mcp/tools/modules.js +25 -0
- package/src/packages/lockfile.js +53 -0
- package/src/packages/registry.js +99 -0
- package/src/packages/resolver.js +83 -0
- package/src/utils/config-reader.js +74 -0
- package/src/utils/lua-annotations.js +319 -0
- package/src/utils/lua-parser.js +119 -0
- package/src/utils/server-root.js +66 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const TEMPLATES_DIR = path.join(__dirname, 'templates');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Render a template file, replacing {{KEY}} placeholders.
|
|
10
|
+
* @param {string} templateRelPath Path relative to src/generators/templates/
|
|
11
|
+
* @param {object} vars Key/value pairs for substitution
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
function renderTemplate(templateRelPath, vars = {}) {
|
|
15
|
+
const fullPath = path.join(TEMPLATES_DIR, templateRelPath);
|
|
16
|
+
if (!fs.existsSync(fullPath)) {
|
|
17
|
+
throw new Error(`Template not found: ${fullPath}`);
|
|
18
|
+
}
|
|
19
|
+
let content = fs.readFileSync(fullPath, 'utf-8');
|
|
20
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
21
|
+
content = content.split(`{{${key}}}`).join(String(value));
|
|
22
|
+
}
|
|
23
|
+
return content;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Write a rendered template to a destination file.
|
|
28
|
+
* Creates parent directories automatically.
|
|
29
|
+
* @param {string} templateRelPath
|
|
30
|
+
* @param {string} destPath
|
|
31
|
+
* @param {object} vars
|
|
32
|
+
*/
|
|
33
|
+
function writeTemplate(templateRelPath, destPath, vars = {}) {
|
|
34
|
+
const content = renderTemplate(templateRelPath, vars);
|
|
35
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
36
|
+
fs.writeFileSync(destPath, content, 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert a name to PascalCase.
|
|
41
|
+
* e.g. "fishing-rod" → "FishingRod", "fishing_rod" → "FishingRod"
|
|
42
|
+
* @param {string} name
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
function toPascalCase(name) {
|
|
46
|
+
return name
|
|
47
|
+
.replace(/[-_](.)/g, (_, c) => c.toUpperCase())
|
|
48
|
+
.replace(/^(.)/, c => c.toUpperCase());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Convert a name to snake_case.
|
|
53
|
+
* e.g. "FishingRod" → "fishing_rod"
|
|
54
|
+
* @param {string} name
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
function toSnakeCase(name) {
|
|
58
|
+
return name
|
|
59
|
+
.replace(/([A-Z])/g, '_$1')
|
|
60
|
+
.toLowerCase()
|
|
61
|
+
.replace(/^[-_]/, '')
|
|
62
|
+
.replace(/[-]/g, '_');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Normalize a module name, ensuring "shiva-" prefix.
|
|
67
|
+
* @param {string} name
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
function normalizeModuleName(name) {
|
|
71
|
+
name = name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
72
|
+
if (!name.startsWith('shiva-')) {
|
|
73
|
+
name = 'shiva-' + name;
|
|
74
|
+
}
|
|
75
|
+
return name;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { renderTemplate, writeTemplate, toPascalCase, toSnakeCase, normalizeModuleName };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
return {
|
|
2
|
+
up = function(DB)
|
|
3
|
+
DB.execute([[
|
|
4
|
+
-- TODO: Write your migration SQL
|
|
5
|
+
--
|
|
6
|
+
-- Example:
|
|
7
|
+
-- CREATE TABLE IF NOT EXISTS `{{snake_name}}` (
|
|
8
|
+
-- `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
9
|
+
-- `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
10
|
+
-- `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
11
|
+
-- PRIMARY KEY (`id`)
|
|
12
|
+
-- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
13
|
+
]])
|
|
14
|
+
end,
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---@class {{ModelName}} : ModelClass
|
|
2
|
+
{{ModelName}} = Model.define('{{table_name}}')
|
|
3
|
+
|
|
4
|
+
---@param id integer
|
|
5
|
+
---@return table|nil
|
|
6
|
+
function {{ModelName}}.findById(id)
|
|
7
|
+
return DB.table('{{table_name}}'):where('id', id):first()
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
-- TODO: Add custom query methods
|
|
11
|
+
-- Example:
|
|
12
|
+
-- function {{ModelName}}.findByField(value)
|
|
13
|
+
-- return DB.table('{{table_name}}'):where('field', value):get()
|
|
14
|
+
-- end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
fx_version 'cerulean'
|
|
2
|
+
game 'gta5'
|
|
3
|
+
lua54 'yes'
|
|
4
|
+
|
|
5
|
+
name '{{module_name}}'
|
|
6
|
+
description '{{description}}'
|
|
7
|
+
version '1.0.0'
|
|
8
|
+
author '{{author}}'
|
|
9
|
+
|
|
10
|
+
dependencies {
|
|
11
|
+
'shiva-core',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
shared_scripts {
|
|
15
|
+
'module.lua',
|
|
16
|
+
'config/config.lua',
|
|
17
|
+
'locales/en.lua',
|
|
18
|
+
'shared/sh_{{module_short}}.lua',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
server_scripts {
|
|
22
|
+
-- Migrations — add entries here as you create them:
|
|
23
|
+
-- 'migrations/YYYY_MM_DD_HHMMSS_create_example.lua',
|
|
24
|
+
|
|
25
|
+
-- Models
|
|
26
|
+
-- 'server/models/Example.lua',
|
|
27
|
+
|
|
28
|
+
-- Services
|
|
29
|
+
-- 'server/services/ExampleService.lua',
|
|
30
|
+
|
|
31
|
+
-- Events
|
|
32
|
+
-- 'server/events/handlers.lua',
|
|
33
|
+
|
|
34
|
+
'server/boot.lua',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
client_scripts {
|
|
38
|
+
-- 'client/services/ExampleClient.lua',
|
|
39
|
+
-- 'client/events/handlers.lua',
|
|
40
|
+
'client/boot.lua',
|
|
41
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---@diagnostic disable: undefined-field, duplicate-set-field, need-check-nil
|
|
2
|
+
-- tests/{{test_name}}_spec.lua
|
|
3
|
+
-- Unit tests for {{describe_name}}.
|
|
4
|
+
-- Run with: shiva test --module {{module_name}}
|
|
5
|
+
|
|
6
|
+
-- Resolve shiva-test relative to this file (both modules live in the same [category] folder)
|
|
7
|
+
package.path = '../../shiva-test/?.lua;../../shiva-test/?/init.lua;' .. package.path
|
|
8
|
+
|
|
9
|
+
local Test = require('src.init')
|
|
10
|
+
|
|
11
|
+
Test.setup({ mode = 'server' })
|
|
12
|
+
|
|
13
|
+
local describe = Test.describe
|
|
14
|
+
local it = Test.it
|
|
15
|
+
local expect = Test.expect
|
|
16
|
+
local beforeEach = Test.beforeEach
|
|
17
|
+
|
|
18
|
+
describe('{{describe_name}}', function()
|
|
19
|
+
beforeEach(function()
|
|
20
|
+
-- Stub Shiva globals used by {{describe_name}}
|
|
21
|
+
Container = { resolve = function() end, bind = function() end }
|
|
22
|
+
Config = { get = function() end, register = function() end }
|
|
23
|
+
Locale = { t = function(_, key) return key end, register = function() end }
|
|
24
|
+
EventBus = { emit = function() end, on = function() end }
|
|
25
|
+
DB = { table = function() return DB end, execute = function() end }
|
|
26
|
+
Log = { info = function() end, debug = function() end, warn = function() end }
|
|
27
|
+
end)
|
|
28
|
+
|
|
29
|
+
it('passes a basic sanity check', function()
|
|
30
|
+
expect(true):toBeTrue()
|
|
31
|
+
end)
|
|
32
|
+
|
|
33
|
+
-- it('should ...', function()
|
|
34
|
+
-- local actual = ...
|
|
35
|
+
-- expect(actual):toBe(expected)
|
|
36
|
+
-- end)
|
|
37
|
+
end)
|
|
38
|
+
|
|
39
|
+
Test.run()
|
package/src/index.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const pkg = require('../package.json');
|
|
6
|
+
|
|
7
|
+
// Scaffolding
|
|
8
|
+
const initCommand = require('./commands/init');
|
|
9
|
+
const makeModuleCommand = require('./commands/make/module');
|
|
10
|
+
const makeServiceCommand = require('./commands/make/service');
|
|
11
|
+
const makeModelCommand = require('./commands/make/model');
|
|
12
|
+
const makeMigrationCommand = require('./commands/make/migration');
|
|
13
|
+
const makeTestCommand = require('./commands/make/test');
|
|
14
|
+
const makeContractCommand = require('./commands/make/contract');
|
|
15
|
+
const makeSeedCommand = require('./commands/make/seed');
|
|
16
|
+
|
|
17
|
+
// Migrations
|
|
18
|
+
const migrateRunCommand = require('./commands/migrate/run');
|
|
19
|
+
const migrateRollbackCommand = require('./commands/migrate/rollback');
|
|
20
|
+
const migrateStatusCommand = require('./commands/migrate/status');
|
|
21
|
+
|
|
22
|
+
// Seeding
|
|
23
|
+
const seedCommand = require('./commands/seed');
|
|
24
|
+
|
|
25
|
+
// Module inspection
|
|
26
|
+
const moduleListCommand = require('./commands/module/list');
|
|
27
|
+
const moduleStatusCommand = require('./commands/module/status');
|
|
28
|
+
|
|
29
|
+
// Config & locale
|
|
30
|
+
const configValidateCommand = require('./commands/config/validate');
|
|
31
|
+
const localeMissingCommand = require('./commands/locale/missing');
|
|
32
|
+
|
|
33
|
+
// Package management
|
|
34
|
+
const installCommand = require('./commands/install');
|
|
35
|
+
const updateCommand = require('./commands/update');
|
|
36
|
+
const outdatedCommand = require('./commands/outdated');
|
|
37
|
+
const removeCommand = require('./commands/remove');
|
|
38
|
+
|
|
39
|
+
// Testing
|
|
40
|
+
const testCommand = require('./commands/test');
|
|
41
|
+
|
|
42
|
+
// AI integration
|
|
43
|
+
const aiContextCommand = require('./commands/ai/context');
|
|
44
|
+
const aiLinkCommand = require('./commands/ai/link');
|
|
45
|
+
const aiMcpCommand = require('./commands/ai/mcp');
|
|
46
|
+
|
|
47
|
+
// MCP server
|
|
48
|
+
const mcpCommand = require('./commands/mcp');
|
|
49
|
+
|
|
50
|
+
// Docs
|
|
51
|
+
const docsBuildCommand = require('./commands/docs/build');
|
|
52
|
+
const docsServeCommand = require('./commands/docs/serve');
|
|
53
|
+
const docsApiCommand = require('./commands/docs/api');
|
|
54
|
+
const docsDeployCommand = require('./commands/docs/deploy');
|
|
55
|
+
|
|
56
|
+
program
|
|
57
|
+
.name('shiva')
|
|
58
|
+
.description(chalk.cyan('Developer CLI for the Shiva FiveM framework'))
|
|
59
|
+
.version(pkg.version, '-v, --version', 'Output the current version');
|
|
60
|
+
|
|
61
|
+
initCommand(program);
|
|
62
|
+
|
|
63
|
+
makeModuleCommand(program);
|
|
64
|
+
makeServiceCommand(program);
|
|
65
|
+
makeModelCommand(program);
|
|
66
|
+
makeMigrationCommand(program);
|
|
67
|
+
makeTestCommand(program);
|
|
68
|
+
makeContractCommand(program);
|
|
69
|
+
makeSeedCommand(program);
|
|
70
|
+
|
|
71
|
+
migrateRunCommand(program);
|
|
72
|
+
migrateRollbackCommand(program);
|
|
73
|
+
migrateStatusCommand(program);
|
|
74
|
+
|
|
75
|
+
seedCommand(program);
|
|
76
|
+
|
|
77
|
+
moduleListCommand(program);
|
|
78
|
+
moduleStatusCommand(program);
|
|
79
|
+
|
|
80
|
+
configValidateCommand(program);
|
|
81
|
+
localeMissingCommand(program);
|
|
82
|
+
|
|
83
|
+
installCommand(program);
|
|
84
|
+
updateCommand(program);
|
|
85
|
+
outdatedCommand(program);
|
|
86
|
+
removeCommand(program);
|
|
87
|
+
|
|
88
|
+
testCommand(program);
|
|
89
|
+
|
|
90
|
+
aiContextCommand(program);
|
|
91
|
+
aiLinkCommand(program);
|
|
92
|
+
aiMcpCommand(program);
|
|
93
|
+
|
|
94
|
+
mcpCommand(program);
|
|
95
|
+
|
|
96
|
+
docsBuildCommand(program);
|
|
97
|
+
docsServeCommand(program);
|
|
98
|
+
docsApiCommand(program);
|
|
99
|
+
docsDeployCommand(program);
|
|
100
|
+
|
|
101
|
+
program.addHelpText('after', `
|
|
102
|
+
${chalk.bold('Examples:')}
|
|
103
|
+
$ shiva init
|
|
104
|
+
$ shiva make:module fishing
|
|
105
|
+
$ shiva make:service FishingService --module shiva-fishing
|
|
106
|
+
$ shiva make:migration create_fish_catches --module shiva-fishing
|
|
107
|
+
$ shiva migrate
|
|
108
|
+
$ shiva module:list
|
|
109
|
+
$ shiva install shiva-fishing
|
|
110
|
+
$ shiva mcp start
|
|
111
|
+
`);
|
|
112
|
+
|
|
113
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const { findServerRoot } = require('../../utils/server-root');
|
|
7
|
+
|
|
8
|
+
const TEMPLATE_URI = 'shiva:contracts/{name}';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the contracts directory — checks project-level then shiva-core submodule.
|
|
12
|
+
* @param {string} serverRoot
|
|
13
|
+
* @returns {string|null}
|
|
14
|
+
*/
|
|
15
|
+
function findContractsDir(serverRoot) {
|
|
16
|
+
const candidates = [
|
|
17
|
+
path.join(serverRoot, 'shared', 'contracts'),
|
|
18
|
+
path.join(serverRoot, 'shared', 'sh_contracts'),
|
|
19
|
+
path.join(serverRoot, 'resources', '[shiva]', 'shiva-core', 'shared', 'sh_contracts'),
|
|
20
|
+
];
|
|
21
|
+
return candidates.find(d => fs.existsSync(d)) || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* List all available contracts.
|
|
26
|
+
* @param {string} serverRoot
|
|
27
|
+
* @returns {Array<{uri, name, description, mimeType}>}
|
|
28
|
+
*/
|
|
29
|
+
function listContracts(serverRoot) {
|
|
30
|
+
const dir = findContractsDir(serverRoot);
|
|
31
|
+
if (!dir) return [];
|
|
32
|
+
|
|
33
|
+
return fs.readdirSync(dir)
|
|
34
|
+
.filter(f => f.endsWith('.lua'))
|
|
35
|
+
.map(f => {
|
|
36
|
+
const slug = f.replace(/^sh_/, '').replace(/\.lua$/, '');
|
|
37
|
+
const name = slug.charAt(0).toUpperCase() + slug.slice(1);
|
|
38
|
+
return {
|
|
39
|
+
uri: `shiva:contracts/${slug}`,
|
|
40
|
+
name: `${name} contract`,
|
|
41
|
+
description: `Contract interface: ${name}`,
|
|
42
|
+
mimeType: 'text/x-lua',
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Read a contract resource by URI.
|
|
49
|
+
* @param {string} uri e.g. "shiva:contracts/economy"
|
|
50
|
+
* @param {string} serverRoot
|
|
51
|
+
* @returns {{uri, mimeType, text}|null}
|
|
52
|
+
*/
|
|
53
|
+
function readContract(uri, serverRoot) {
|
|
54
|
+
const slug = uri.replace(/^shiva:contracts\//, '');
|
|
55
|
+
const dir = findContractsDir(serverRoot);
|
|
56
|
+
if (!dir) return null;
|
|
57
|
+
|
|
58
|
+
const file = path.join(dir, `sh_${slug}.lua`);
|
|
59
|
+
if (!fs.existsSync(file)) return null;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
uri,
|
|
63
|
+
mimeType: 'text/x-lua',
|
|
64
|
+
text: fs.readFileSync(file, 'utf-8'),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { TEMPLATE_URI, listContracts, readContract };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const { scanModules } = require('../../utils/lua-parser');
|
|
7
|
+
const { getResourcesDir } = require('../../utils/server-root');
|
|
8
|
+
|
|
9
|
+
const TEMPLATE_URI = 'shiva:docs/{topic}';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* List all available doc topics (module AGENTS.md files).
|
|
13
|
+
* @param {string} serverRoot
|
|
14
|
+
* @returns {Array<{uri, name, description, mimeType}>}
|
|
15
|
+
*/
|
|
16
|
+
function listDocs(serverRoot) {
|
|
17
|
+
const modules = scanModules(getResourcesDir(serverRoot));
|
|
18
|
+
const resources = [];
|
|
19
|
+
|
|
20
|
+
for (const mod of modules) {
|
|
21
|
+
const agentsFile = path.join(mod.path, 'AGENTS.md');
|
|
22
|
+
if (!fs.existsSync(agentsFile)) continue;
|
|
23
|
+
resources.push({
|
|
24
|
+
uri: `shiva:docs/${mod.name}`,
|
|
25
|
+
name: `${mod.name} documentation`,
|
|
26
|
+
description: mod.manifest.description || `AGENTS.md for ${mod.name}`,
|
|
27
|
+
mimeType: 'text/markdown',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return resources;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Read a doc resource by URI.
|
|
36
|
+
* @param {string} uri e.g. "shiva:docs/shiva-economy"
|
|
37
|
+
* @param {string} serverRoot
|
|
38
|
+
* @returns {{uri, mimeType, text}|null}
|
|
39
|
+
*/
|
|
40
|
+
function readDoc(uri, serverRoot) {
|
|
41
|
+
const topic = uri.replace(/^shiva:docs\//, '');
|
|
42
|
+
const modules = scanModules(getResourcesDir(serverRoot));
|
|
43
|
+
const mod = modules.find(m => m.name === topic);
|
|
44
|
+
if (!mod) return null;
|
|
45
|
+
|
|
46
|
+
const agentsFile = path.join(mod.path, 'AGENTS.md');
|
|
47
|
+
if (!fs.existsSync(agentsFile)) return null;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
uri,
|
|
51
|
+
mimeType: 'text/markdown',
|
|
52
|
+
text: fs.readFileSync(agentsFile, 'utf-8'),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { TEMPLATE_URI, listDocs, readDoc };
|