hirmos 1.3.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 +107 -0
- package/dist/commands/init.js +76 -0
- package/dist/index.js +36 -0
- package/dist/lib/args.js +79 -0
- package/dist/lib/errors.js +14 -0
- package/dist/lib/filesystem.js +45 -0
- package/dist/lib/integration-registry.js +66 -0
- package/dist/lib/managed-blocks.js +59 -0
- package/dist/lib/project-config.js +45 -0
- package/dist/lib/source-install.js +89 -0
- package/dist/lib/validation.js +36 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# HIRMOS CLI
|
|
2
|
+
|
|
3
|
+
Product-facing HIRMOS CLI.
|
|
4
|
+
|
|
5
|
+
Implemented command:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
hirmos init [project-path] [--integration <ids>] [--source <path>]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The CLI makes an installed HIRMOS project discoverable to selected agent-native tools by generating thin bootstrap integration files from `_hirmos/integrations/agent-tools/`.
|
|
12
|
+
|
|
13
|
+
It does not invoke agents, run HIRMOS workflows, install extensions, download remote packages, or generate slash-command packs.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
From a project that already contains `_hirmos/`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
hirmos init
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This defaults to:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
project-path = .
|
|
27
|
+
integration = agents
|
|
28
|
+
source = existing _hirmos/ in the project
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Generate specific integrations:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
hirmos init --integration claude,cursor,copilot
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Initialize another project path:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
hirmos init /path/to/project --integration agents,claude
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Install `_hirmos/` from a local source folder or release zip when the target project does not already contain `_hirmos/`:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
hirmos init /path/to/project --source /path/to/hirmos-framework.zip --integration agents
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Supported integrations
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
agents
|
|
53
|
+
claude
|
|
54
|
+
cursor
|
|
55
|
+
copilot
|
|
56
|
+
codex
|
|
57
|
+
opencode
|
|
58
|
+
gemini
|
|
59
|
+
windsurf
|
|
60
|
+
kiro
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Some integrations share the same target file. For example, `agents`, `codex`, and `opencode` all use `AGENTS.md` as the safe generic bootstrap target.
|
|
64
|
+
|
|
65
|
+
## Managed-block behavior
|
|
66
|
+
|
|
67
|
+
For existing files, the CLI updates only the HIRMOS managed block:
|
|
68
|
+
|
|
69
|
+
```md
|
|
70
|
+
<!-- HIRMOS:START -->
|
|
71
|
+
...
|
|
72
|
+
<!-- HIRMOS:END -->
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If the file exists and has no HIRMOS block, the CLI appends one. User-owned content outside the block is preserved.
|
|
76
|
+
|
|
77
|
+
## Project config behavior
|
|
78
|
+
|
|
79
|
+
The CLI merges selected integration IDs into:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
_hirmos/project.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
under:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"integrations": {
|
|
90
|
+
"installed": []
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Re-running `hirmos init --integration ...` is additive and does not remove previously recorded integrations.
|
|
96
|
+
|
|
97
|
+
## Workflow commands are not CLI commands
|
|
98
|
+
|
|
99
|
+
These are HIRMOS workflow commands to use inside the AI tool after HIRMOS Core has loaded:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
hirmos requirements
|
|
103
|
+
hirmos system-design
|
|
104
|
+
hirmos implementation
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The CLI currently implements only `hirmos init`.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runInit = runInit;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const filesystem_1 = require("../lib/filesystem");
|
|
10
|
+
const integration_registry_1 = require("../lib/integration-registry");
|
|
11
|
+
const managed_blocks_1 = require("../lib/managed-blocks");
|
|
12
|
+
const project_config_1 = require("../lib/project-config");
|
|
13
|
+
const source_install_1 = require("../lib/source-install");
|
|
14
|
+
const validation_1 = require("../lib/validation");
|
|
15
|
+
function runInit(options) {
|
|
16
|
+
const projectRoot = path_1.default.resolve(options.projectPath);
|
|
17
|
+
(0, validation_1.validateProjectRoot)(projectRoot);
|
|
18
|
+
(0, source_install_1.ensureHirmosInstalled)(projectRoot, options.source);
|
|
19
|
+
(0, validation_1.validateHirmosFramework)(projectRoot);
|
|
20
|
+
const hirmosDir = path_1.default.join(projectRoot, "_hirmos");
|
|
21
|
+
const registry = (0, integration_registry_1.loadRegistry)(hirmosDir);
|
|
22
|
+
const selected = (0, integration_registry_1.normalizeSelectedIntegrations)(options.integrations, registry);
|
|
23
|
+
const plannedTargets = (0, integration_registry_1.planTargetUpdates)(projectRoot, hirmosDir, selected, registry);
|
|
24
|
+
const currentConfig = (0, project_config_1.loadProjectConfig)(projectRoot);
|
|
25
|
+
const mergeResult = (0, project_config_1.mergeProjectConfig)(currentConfig, selected, registry);
|
|
26
|
+
const renderedWrites = plannedTargets.map((target) => {
|
|
27
|
+
(0, filesystem_1.assertInsideProject)(projectRoot, target.targetPath);
|
|
28
|
+
const existing = fs_1.default.existsSync(target.targetPath) ? (0, filesystem_1.readText)(target.targetPath) : null;
|
|
29
|
+
const nextContent = (0, managed_blocks_1.updateManagedBlock)(existing, target.templateContent, registry.managed_block, target.target);
|
|
30
|
+
return {
|
|
31
|
+
path: target.targetPath,
|
|
32
|
+
relative: target.target,
|
|
33
|
+
content: nextContent,
|
|
34
|
+
integrationIds: target.integrationIds
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
for (const write of renderedWrites) {
|
|
38
|
+
(0, filesystem_1.writeText)(write.path, write.content);
|
|
39
|
+
}
|
|
40
|
+
(0, project_config_1.writeProjectConfig)(projectRoot, mergeResult.config);
|
|
41
|
+
return formatInitSummary(mergeResult.wasFirstInitialization, selected, mergeResult.added, mergeResult.alreadyInstalled, renderedWrites.map((item) => item.relative));
|
|
42
|
+
}
|
|
43
|
+
function formatInitSummary(first, selected, added, alreadyInstalled, targets) {
|
|
44
|
+
const lines = [];
|
|
45
|
+
if (first) {
|
|
46
|
+
lines.push("HIRMOS initialized.", "");
|
|
47
|
+
lines.push("Installed integrations:");
|
|
48
|
+
for (const id of selected)
|
|
49
|
+
lines.push(`- ${id}`);
|
|
50
|
+
lines.push("", "Generated or updated files:");
|
|
51
|
+
for (const target of targets)
|
|
52
|
+
lines.push(`- ${target}`);
|
|
53
|
+
lines.push("", "Next step:");
|
|
54
|
+
lines.push("Open your AI coding tool and ask it to read and follow _hirmos/HIRMOS_CORE.md.");
|
|
55
|
+
lines.push("", "To add more integrations later:");
|
|
56
|
+
lines.push("hirmos init --integration claude,cursor,copilot");
|
|
57
|
+
return `${lines.join("\n")}\n`;
|
|
58
|
+
}
|
|
59
|
+
lines.push("HIRMOS integrations updated.", "");
|
|
60
|
+
if (added.length > 0) {
|
|
61
|
+
lines.push("Added:");
|
|
62
|
+
for (const id of added)
|
|
63
|
+
lines.push(`- ${id}`);
|
|
64
|
+
lines.push("");
|
|
65
|
+
}
|
|
66
|
+
if (alreadyInstalled.length > 0) {
|
|
67
|
+
lines.push("Already installed:");
|
|
68
|
+
for (const id of alreadyInstalled)
|
|
69
|
+
lines.push(`- ${id}`);
|
|
70
|
+
lines.push("");
|
|
71
|
+
}
|
|
72
|
+
lines.push("Generated or updated files:");
|
|
73
|
+
for (const target of targets)
|
|
74
|
+
lines.push(`- ${target}`);
|
|
75
|
+
return `${lines.join("\n").replace(/\n+$/u, "")}\n`;
|
|
76
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const args_1 = require("./lib/args");
|
|
5
|
+
const errors_1 = require("./lib/errors");
|
|
6
|
+
const init_1 = require("./commands/init");
|
|
7
|
+
function main() {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = (0, args_1.parseArgs)(process.argv.slice(2));
|
|
10
|
+
if (parsed.command === "help") {
|
|
11
|
+
process.stdout.write((0, args_1.helpText)());
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (parsed.command === "init") {
|
|
15
|
+
const summary = (0, init_1.runInit)({
|
|
16
|
+
projectPath: parsed.projectPath,
|
|
17
|
+
integrations: parsed.integrations,
|
|
18
|
+
source: parsed.source
|
|
19
|
+
});
|
|
20
|
+
process.stdout.write(summary);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof errors_1.HirmosCliError) {
|
|
26
|
+
process.stderr.write(`ERROR: ${error.message}\n`);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
process.stderr.write(`ERROR: ${error?.message ?? String(error)}\n`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (require.main === module) {
|
|
35
|
+
main();
|
|
36
|
+
}
|
package/dist/lib/args.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseArgs = parseArgs;
|
|
7
|
+
exports.parseIntegrationList = parseIntegrationList;
|
|
8
|
+
exports.helpText = helpText;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const errors_1 = require("./errors");
|
|
11
|
+
function parseArgs(argv) {
|
|
12
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
13
|
+
return { command: "help" };
|
|
14
|
+
}
|
|
15
|
+
const command = argv[0];
|
|
16
|
+
if (command !== "init") {
|
|
17
|
+
(0, errors_1.fail)(`Unsupported command: ${command}. DP-3B implements only: hirmos init`);
|
|
18
|
+
}
|
|
19
|
+
let projectPath = ".";
|
|
20
|
+
let projectPathSet = false;
|
|
21
|
+
let integrationValue;
|
|
22
|
+
let source;
|
|
23
|
+
for (let i = 1; i < argv.length; i += 1) {
|
|
24
|
+
const arg = argv[i];
|
|
25
|
+
if (arg === "--integration") {
|
|
26
|
+
const value = argv[++i];
|
|
27
|
+
if (!value)
|
|
28
|
+
(0, errors_1.fail)("Missing value for --integration");
|
|
29
|
+
integrationValue = value;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (arg.startsWith("--integration=")) {
|
|
33
|
+
integrationValue = arg.slice("--integration=".length);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (arg === "--source") {
|
|
37
|
+
const value = argv[++i];
|
|
38
|
+
if (!value)
|
|
39
|
+
(0, errors_1.fail)("Missing value for --source");
|
|
40
|
+
source = value;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg.startsWith("--source=")) {
|
|
44
|
+
source = arg.slice("--source=".length);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (arg === "--help" || arg === "-h") {
|
|
48
|
+
return { command: "help" };
|
|
49
|
+
}
|
|
50
|
+
if (arg.startsWith("-")) {
|
|
51
|
+
(0, errors_1.fail)(`Unsupported option: ${arg}`);
|
|
52
|
+
}
|
|
53
|
+
if (projectPathSet) {
|
|
54
|
+
(0, errors_1.fail)(`Unexpected extra positional argument: ${arg}`);
|
|
55
|
+
}
|
|
56
|
+
projectPath = arg;
|
|
57
|
+
projectPathSet = true;
|
|
58
|
+
}
|
|
59
|
+
const integrations = parseIntegrationList(integrationValue ?? "agents");
|
|
60
|
+
return {
|
|
61
|
+
command: "init",
|
|
62
|
+
projectPath: path_1.default.resolve(projectPath),
|
|
63
|
+
integrations,
|
|
64
|
+
source: source ? path_1.default.resolve(source) : undefined
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function parseIntegrationList(value) {
|
|
68
|
+
const ids = value
|
|
69
|
+
.split(",")
|
|
70
|
+
.map((item) => item.trim())
|
|
71
|
+
.filter(Boolean);
|
|
72
|
+
if (ids.length === 0) {
|
|
73
|
+
(0, errors_1.fail)("--integration must include at least one integration id");
|
|
74
|
+
}
|
|
75
|
+
return ids;
|
|
76
|
+
}
|
|
77
|
+
function helpText() {
|
|
78
|
+
return `HIRMOS CLI\n\nUsage:\n hirmos init [project-path] [--integration <ids>] [--source <path>]\n\nDefaults:\n project-path: .\n integration: agents\n source: existing _hirmos/ in the project\n\nDP-3B implements only hirmos init. Workflow commands such as hirmos requirements are interpreted by an agent after HIRMOS Core is loaded.\n`;
|
|
79
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HirmosCliError = void 0;
|
|
4
|
+
exports.fail = fail;
|
|
5
|
+
class HirmosCliError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "HirmosCliError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.HirmosCliError = HirmosCliError;
|
|
12
|
+
function fail(message) {
|
|
13
|
+
throw new HirmosCliError(message);
|
|
14
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureDir = ensureDir;
|
|
7
|
+
exports.readText = readText;
|
|
8
|
+
exports.writeText = writeText;
|
|
9
|
+
exports.readJson = readJson;
|
|
10
|
+
exports.writeJson = writeJson;
|
|
11
|
+
exports.assertInsideProject = assertInsideProject;
|
|
12
|
+
exports.pathExists = pathExists;
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const errors_1 = require("./errors");
|
|
16
|
+
function ensureDir(dirPath) {
|
|
17
|
+
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
function readText(filePath) {
|
|
20
|
+
return fs_1.default.readFileSync(filePath, "utf8");
|
|
21
|
+
}
|
|
22
|
+
function writeText(filePath, content) {
|
|
23
|
+
ensureDir(path_1.default.dirname(filePath));
|
|
24
|
+
fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
25
|
+
}
|
|
26
|
+
function readJson(filePath, label) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(readText(filePath));
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
(0, errors_1.fail)(`Invalid ${label} JSON at ${filePath}: ${error.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function writeJson(filePath, value) {
|
|
35
|
+
writeText(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
36
|
+
}
|
|
37
|
+
function assertInsideProject(projectRoot, targetPath) {
|
|
38
|
+
const relative = path_1.default.relative(projectRoot, targetPath);
|
|
39
|
+
if (relative.startsWith("..") || path_1.default.isAbsolute(relative)) {
|
|
40
|
+
(0, errors_1.fail)(`Refusing to write outside the project root: ${targetPath}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function pathExists(filePath) {
|
|
44
|
+
return fs_1.default.existsSync(filePath);
|
|
45
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadRegistry = loadRegistry;
|
|
7
|
+
exports.registryOrder = registryOrder;
|
|
8
|
+
exports.normalizeSelectedIntegrations = normalizeSelectedIntegrations;
|
|
9
|
+
exports.planTargetUpdates = planTargetUpdates;
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
const filesystem_1 = require("./filesystem");
|
|
13
|
+
const managed_blocks_1 = require("./managed-blocks");
|
|
14
|
+
function loadRegistry(hirmosDir) {
|
|
15
|
+
const registryPath = path_1.default.join(hirmosDir, "integrations/agent-tools", "registry.json");
|
|
16
|
+
const registry = (0, filesystem_1.readJson)(registryPath, "agent integration registry");
|
|
17
|
+
if (!registry || typeof registry !== "object")
|
|
18
|
+
(0, errors_1.fail)("Agent integration registry is malformed.");
|
|
19
|
+
if (registry.schema_version !== 1)
|
|
20
|
+
(0, errors_1.fail)(`Unsupported agent integration registry schema_version: ${registry.schema_version}`);
|
|
21
|
+
if (!registry.managed_block?.start || !registry.managed_block?.end)
|
|
22
|
+
(0, errors_1.fail)("Agent integration registry is missing managed block markers.");
|
|
23
|
+
if (!registry.integrations || typeof registry.integrations !== "object")
|
|
24
|
+
(0, errors_1.fail)("Agent integration registry is missing integrations.");
|
|
25
|
+
return registry;
|
|
26
|
+
}
|
|
27
|
+
function registryOrder(registry) {
|
|
28
|
+
return Object.keys(registry.integrations);
|
|
29
|
+
}
|
|
30
|
+
function normalizeSelectedIntegrations(selected, registry) {
|
|
31
|
+
const selectedSet = new Set();
|
|
32
|
+
for (const id of selected) {
|
|
33
|
+
if (!registry.integrations[id]) {
|
|
34
|
+
(0, errors_1.fail)(`Unknown integration id: ${id}. Valid integrations: ${registryOrder(registry).join(", ")}`);
|
|
35
|
+
}
|
|
36
|
+
selectedSet.add(id);
|
|
37
|
+
}
|
|
38
|
+
return registryOrder(registry).filter((id) => selectedSet.has(id));
|
|
39
|
+
}
|
|
40
|
+
function planTargetUpdates(projectRoot, hirmosDir, selected, registry) {
|
|
41
|
+
const grouped = new Map();
|
|
42
|
+
for (const id of selected) {
|
|
43
|
+
const integration = registry.integrations[id];
|
|
44
|
+
if (integration.mode !== "managed_block")
|
|
45
|
+
(0, errors_1.fail)(`Unsupported integration mode for ${id}: ${integration.mode}`);
|
|
46
|
+
const templatePath = path_1.default.join(hirmosDir, "integrations/agent-tools", integration.template);
|
|
47
|
+
const templateContent = (0, filesystem_1.readText)(templatePath);
|
|
48
|
+
(0, managed_blocks_1.validateTemplateManagedBlock)(templateContent, registry.managed_block, integration.template);
|
|
49
|
+
const existing = grouped.get(integration.target);
|
|
50
|
+
if (existing) {
|
|
51
|
+
if (existing.templateContent !== templateContent) {
|
|
52
|
+
(0, errors_1.fail)(`Integrations sharing target ${integration.target} use different templates. Refusing ambiguous update.`);
|
|
53
|
+
}
|
|
54
|
+
existing.integrationIds.push(id);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
grouped.set(integration.target, {
|
|
58
|
+
target: integration.target,
|
|
59
|
+
targetPath: path_1.default.resolve(projectRoot, integration.target),
|
|
60
|
+
templatePath,
|
|
61
|
+
templateContent,
|
|
62
|
+
integrationIds: [id]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return Array.from(grouped.values());
|
|
66
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateTemplateManagedBlock = validateTemplateManagedBlock;
|
|
4
|
+
exports.updateManagedBlock = updateManagedBlock;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
function countOccurrences(content, needle) {
|
|
7
|
+
if (!needle)
|
|
8
|
+
return 0;
|
|
9
|
+
let count = 0;
|
|
10
|
+
let index = 0;
|
|
11
|
+
while (true) {
|
|
12
|
+
const found = content.indexOf(needle, index);
|
|
13
|
+
if (found === -1)
|
|
14
|
+
return count;
|
|
15
|
+
count += 1;
|
|
16
|
+
index = found + needle.length;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function validateTemplateManagedBlock(template, markers, label) {
|
|
20
|
+
const starts = countOccurrences(template, markers.start);
|
|
21
|
+
const ends = countOccurrences(template, markers.end);
|
|
22
|
+
if (starts !== 1 || ends !== 1) {
|
|
23
|
+
(0, errors_1.fail)(`Template ${label} must contain exactly one complete HIRMOS managed block.`);
|
|
24
|
+
}
|
|
25
|
+
if (template.indexOf(markers.start) > template.indexOf(markers.end)) {
|
|
26
|
+
(0, errors_1.fail)(`Template ${label} has malformed HIRMOS managed block markers.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function updateManagedBlock(existing, renderedTemplate, markers, targetLabel) {
|
|
30
|
+
validateTemplateManagedBlock(renderedTemplate, markers, targetLabel);
|
|
31
|
+
if (existing === null) {
|
|
32
|
+
return ensureTrailingNewline(renderedTemplate);
|
|
33
|
+
}
|
|
34
|
+
const starts = countOccurrences(existing, markers.start);
|
|
35
|
+
const ends = countOccurrences(existing, markers.end);
|
|
36
|
+
if (starts !== ends) {
|
|
37
|
+
(0, errors_1.fail)(`Target ${targetLabel} has malformed HIRMOS managed block markers.`);
|
|
38
|
+
}
|
|
39
|
+
if (starts > 1) {
|
|
40
|
+
(0, errors_1.fail)(`Target ${targetLabel} contains multiple HIRMOS managed blocks. Refusing to modify it.`);
|
|
41
|
+
}
|
|
42
|
+
if (starts === 1) {
|
|
43
|
+
const startIndex = existing.indexOf(markers.start);
|
|
44
|
+
const endIndex = existing.indexOf(markers.end, startIndex);
|
|
45
|
+
if (endIndex === -1 || startIndex > endIndex) {
|
|
46
|
+
(0, errors_1.fail)(`Target ${targetLabel} has malformed HIRMOS managed block markers.`);
|
|
47
|
+
}
|
|
48
|
+
const afterEnd = endIndex + markers.end.length;
|
|
49
|
+
return ensureTrailingNewline(`${existing.slice(0, startIndex)}${trimTrailingNewline(renderedTemplate)}${existing.slice(afterEnd)}`);
|
|
50
|
+
}
|
|
51
|
+
const separator = existing.trim().length === 0 ? "" : "\n\n";
|
|
52
|
+
return ensureTrailingNewline(`${trimTrailingNewline(existing)}${separator}${trimTrailingNewline(renderedTemplate)}`);
|
|
53
|
+
}
|
|
54
|
+
function trimTrailingNewline(content) {
|
|
55
|
+
return content.replace(/\n+$/u, "");
|
|
56
|
+
}
|
|
57
|
+
function ensureTrailingNewline(content) {
|
|
58
|
+
return `${trimTrailingNewline(content)}\n`;
|
|
59
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadProjectConfig = loadProjectConfig;
|
|
7
|
+
exports.mergeProjectConfig = mergeProjectConfig;
|
|
8
|
+
exports.writeProjectConfig = writeProjectConfig;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const filesystem_1 = require("./filesystem");
|
|
11
|
+
const integration_registry_1 = require("./integration-registry");
|
|
12
|
+
function loadProjectConfig(projectRoot) {
|
|
13
|
+
return (0, filesystem_1.readJson)(path_1.default.join(projectRoot, "_hirmos", "project.json"), "_hirmos/project.json");
|
|
14
|
+
}
|
|
15
|
+
function mergeProjectConfig(config, selected, registry) {
|
|
16
|
+
const previousInstalled = Array.isArray(config.integrations?.installed)
|
|
17
|
+
? config.integrations.installed.filter((id) => typeof id === "string")
|
|
18
|
+
: [];
|
|
19
|
+
const previousSet = new Set(previousInstalled);
|
|
20
|
+
const selectedSet = new Set(selected);
|
|
21
|
+
const added = selected.filter((id) => !previousSet.has(id));
|
|
22
|
+
const alreadyInstalled = selected.filter((id) => previousSet.has(id));
|
|
23
|
+
const knownOrder = (0, integration_registry_1.registryOrder)(registry);
|
|
24
|
+
const allKnown = knownOrder.filter((id) => previousSet.has(id) || selectedSet.has(id));
|
|
25
|
+
const unknownExisting = previousInstalled.filter((id) => !registry.integrations[id]);
|
|
26
|
+
const installed = [...allKnown, ...unknownExisting];
|
|
27
|
+
const nextConfig = {
|
|
28
|
+
...config,
|
|
29
|
+
integrations: {
|
|
30
|
+
...(config.integrations ?? {}),
|
|
31
|
+
installed
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
config: nextConfig,
|
|
36
|
+
previousInstalled,
|
|
37
|
+
added,
|
|
38
|
+
alreadyInstalled,
|
|
39
|
+
installed,
|
|
40
|
+
wasFirstInitialization: previousInstalled.length === 0
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function writeProjectConfig(projectRoot, config) {
|
|
44
|
+
(0, filesystem_1.writeJson)(path_1.default.join(projectRoot, "_hirmos", "project.json"), config);
|
|
45
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureHirmosInstalled = ensureHirmosInstalled;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
const filesystem_1 = require("./filesystem");
|
|
13
|
+
function ensureHirmosInstalled(projectRoot, source) {
|
|
14
|
+
const hirmosDir = path_1.default.join(projectRoot, "_hirmos");
|
|
15
|
+
if ((0, filesystem_1.pathExists)(hirmosDir)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!source) {
|
|
19
|
+
(0, errors_1.fail)(`Missing _hirmos/ in ${projectRoot}. Provide --source <path-to-hirmos-framework.zip-or-folder>.`);
|
|
20
|
+
}
|
|
21
|
+
if (!(0, filesystem_1.pathExists)(source)) {
|
|
22
|
+
(0, errors_1.fail)(`Source path does not exist: ${source}`);
|
|
23
|
+
}
|
|
24
|
+
const stat = fs_1.default.statSync(source);
|
|
25
|
+
if (stat.isDirectory()) {
|
|
26
|
+
installFromDirectory(projectRoot, source);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (stat.isFile() && source.toLowerCase().endsWith(".zip")) {
|
|
30
|
+
installFromZip(projectRoot, source);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
(0, errors_1.fail)(`Unsupported --source. Expected a .zip, a folder containing _hirmos, or an _hirmos folder: ${source}`);
|
|
34
|
+
}
|
|
35
|
+
function installFromDirectory(projectRoot, sourceDir) {
|
|
36
|
+
const sourceHirmos = path_1.default.basename(sourceDir) === "_hirmos" ? sourceDir : path_1.default.join(sourceDir, "_hirmos");
|
|
37
|
+
if (!(0, filesystem_1.pathExists)(sourceHirmos) || !fs_1.default.statSync(sourceHirmos).isDirectory()) {
|
|
38
|
+
(0, errors_1.fail)(`Source folder does not contain _hirmos/: ${sourceDir}`);
|
|
39
|
+
}
|
|
40
|
+
copyHirmos(projectRoot, sourceHirmos);
|
|
41
|
+
}
|
|
42
|
+
function installFromZip(projectRoot, zipPath) {
|
|
43
|
+
const tmp = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "hirmos-cli-"));
|
|
44
|
+
try {
|
|
45
|
+
try {
|
|
46
|
+
extractZip(zipPath, tmp);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
(0, errors_1.fail)(`Unable to extract --source zip. Ensure the zip is valid and an extraction command is available: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
const sourceHirmos = findHirmosDir(tmp);
|
|
52
|
+
if (!sourceHirmos) {
|
|
53
|
+
(0, errors_1.fail)(`Source zip does not contain an _hirmos/ folder: ${zipPath}`);
|
|
54
|
+
}
|
|
55
|
+
copyHirmos(projectRoot, sourceHirmos);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function findHirmosDir(root) {
|
|
62
|
+
const direct = path_1.default.join(root, "_hirmos");
|
|
63
|
+
if ((0, filesystem_1.pathExists)(direct) && fs_1.default.statSync(direct).isDirectory())
|
|
64
|
+
return direct;
|
|
65
|
+
const entries = fs_1.default.readdirSync(root, { withFileTypes: true });
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (!entry.isDirectory())
|
|
68
|
+
continue;
|
|
69
|
+
const candidate = path_1.default.join(root, entry.name, "_hirmos");
|
|
70
|
+
if ((0, filesystem_1.pathExists)(candidate) && fs_1.default.statSync(candidate).isDirectory())
|
|
71
|
+
return candidate;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
function copyHirmos(projectRoot, sourceHirmos) {
|
|
76
|
+
const target = path_1.default.join(projectRoot, "_hirmos");
|
|
77
|
+
if ((0, filesystem_1.pathExists)(target)) {
|
|
78
|
+
(0, errors_1.fail)(`Refusing to overwrite existing _hirmos/: ${target}`);
|
|
79
|
+
}
|
|
80
|
+
(0, filesystem_1.ensureDir)(projectRoot);
|
|
81
|
+
fs_1.default.cpSync(sourceHirmos, target, { recursive: true, errorOnExist: true });
|
|
82
|
+
}
|
|
83
|
+
function extractZip(zipPath, destination) {
|
|
84
|
+
if (process.platform === "win32") {
|
|
85
|
+
(0, child_process_1.execFileSync)("powershell", ["-NoProfile", "-Command", "Expand-Archive", "-LiteralPath", zipPath, "-DestinationPath", destination, "-Force"], { stdio: "ignore" });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
(0, child_process_1.execFileSync)("unzip", ["-q", zipPath, "-d", destination], { stdio: "ignore" });
|
|
89
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateProjectRoot = validateProjectRoot;
|
|
7
|
+
exports.validateHirmosFramework = validateHirmosFramework;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const errors_1 = require("./errors");
|
|
11
|
+
const filesystem_1 = require("./filesystem");
|
|
12
|
+
function validateProjectRoot(projectRoot) {
|
|
13
|
+
if (!(0, filesystem_1.pathExists)(projectRoot)) {
|
|
14
|
+
(0, errors_1.fail)(`Project path does not exist: ${projectRoot}`);
|
|
15
|
+
}
|
|
16
|
+
if (!fs_1.default.statSync(projectRoot).isDirectory()) {
|
|
17
|
+
(0, errors_1.fail)(`Project path is not a directory: ${projectRoot}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function validateHirmosFramework(projectRoot) {
|
|
21
|
+
const required = [
|
|
22
|
+
"_hirmos/HIRMOS_CORE.md",
|
|
23
|
+
"_hirmos/project.json",
|
|
24
|
+
"_hirmos/integrations/agent-tools/registry.json"
|
|
25
|
+
];
|
|
26
|
+
for (const relative of required) {
|
|
27
|
+
const absolute = path_1.default.join(projectRoot, relative);
|
|
28
|
+
if (!(0, filesystem_1.pathExists)(absolute)) {
|
|
29
|
+
(0, errors_1.fail)(`Required HIRMOS file is missing: ${relative}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const templatesDir = path_1.default.join(projectRoot, "_hirmos", "integrations/agent-tools", "templates");
|
|
33
|
+
if (!(0, filesystem_1.pathExists)(templatesDir) || !fs_1.default.statSync(templatesDir).isDirectory()) {
|
|
34
|
+
(0, errors_1.fail)("Required HIRMOS directory is missing: _hirmos/integrations/agent-tools/templates/");
|
|
35
|
+
}
|
|
36
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hirmos",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "HIRMOS product-facing CLI.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hirmos": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"test": "npm run build && node --test test/*.test.js",
|
|
13
|
+
"prepack": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.0.0"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"package.json"
|
|
25
|
+
]
|
|
26
|
+
}
|