hirmos 1.3.0 → 1.3.2

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 CHANGED
@@ -5,27 +5,30 @@ Product-facing HIRMOS CLI.
5
5
  Implemented command:
6
6
 
7
7
  ```bash
8
- hirmos init [project-path] [--integration <ids>] [--source <path>]
8
+ hirmos init [project-path] [--integration <ids>] [--source <path>] [--version <version>] [--offline]
9
9
  ```
10
10
 
11
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
12
 
13
- It does not invoke agents, run HIRMOS workflows, install extensions, download remote packages, or generate slash-command packs.
13
+ It does not invoke agents, run HIRMOS workflows, install extensions, or generate slash-command packs.
14
14
 
15
15
  ## Usage
16
16
 
17
- From a project that already contains `_hirmos/`:
17
+ From a project root:
18
18
 
19
19
  ```bash
20
20
  hirmos init
21
21
  ```
22
22
 
23
+ If `_hirmos/` is not present, the CLI downloads the latest HIRMOS framework release package and installs `_hirmos/` before generating integrations.
24
+
23
25
  This defaults to:
24
26
 
25
27
  ```text
26
28
  project-path = .
27
29
  integration = agents
28
- source = existing _hirmos/ in the project
30
+ source = existing _hirmos/ in the project, otherwise latest GitHub release
31
+ version = latest
29
32
  ```
30
33
 
31
34
  Generate specific integrations:
@@ -40,12 +43,41 @@ Initialize another project path:
40
43
  hirmos init /path/to/project --integration agents,claude
41
44
  ```
42
45
 
43
- Install `_hirmos/` from a local source folder or release zip when the target project does not already contain `_hirmos/`:
46
+ Install a specific HIRMOS release:
47
+
48
+ ```bash
49
+ hirmos init --version 1.3.1
50
+ ```
51
+
52
+ Install `_hirmos/` from a local source folder or release zip instead of downloading:
44
53
 
45
54
  ```bash
46
55
  hirmos init /path/to/project --source /path/to/hirmos-framework.zip --integration agents
47
56
  ```
48
57
 
58
+ Disable remote download and require an existing `_hirmos/` or local `--source`:
59
+
60
+ ```bash
61
+ hirmos init --offline
62
+ ```
63
+
64
+ ## CLI update notice
65
+
66
+ When `hirmos init` runs online, it checks whether a newer published HIRMOS CLI version is available on npm. If a newer version exists, the CLI prints a non-blocking message such as:
67
+
68
+ ```text
69
+ A newer HIRMOS CLI is available: 1.3.2.
70
+ Update with: npm install -g hirmos@latest
71
+ ```
72
+
73
+ The notice does not stop initialization. Network failures, registry errors, or timeouts are ignored.
74
+
75
+ Use `--offline` to disable both remote framework download and the update check. To disable only the update notice while keeping remote framework download available, run:
76
+
77
+ ```bash
78
+ HIRMOS_CLI_UPDATE_CHECK=0 hirmos init
79
+ ```
80
+
49
81
  ## Supported integrations
50
82
 
51
83
  ```text
@@ -104,4 +136,4 @@ hirmos system-design
104
136
  hirmos implementation
105
137
  ```
106
138
 
107
- The CLI currently implements only `hirmos init`.
139
+ The CLI implements `hirmos init`.
@@ -15,7 +15,11 @@ const validation_1 = require("../lib/validation");
15
15
  function runInit(options) {
16
16
  const projectRoot = path_1.default.resolve(options.projectPath);
17
17
  (0, validation_1.validateProjectRoot)(projectRoot);
18
- (0, source_install_1.ensureHirmosInstalled)(projectRoot, options.source);
18
+ const installResult = (0, source_install_1.ensureHirmosInstalled)(projectRoot, {
19
+ source: options.source,
20
+ version: options.version,
21
+ offline: options.offline
22
+ });
19
23
  (0, validation_1.validateHirmosFramework)(projectRoot);
20
24
  const hirmosDir = path_1.default.join(projectRoot, "_hirmos");
21
25
  const registry = (0, integration_registry_1.loadRegistry)(hirmosDir);
@@ -38,12 +42,15 @@ function runInit(options) {
38
42
  (0, filesystem_1.writeText)(write.path, write.content);
39
43
  }
40
44
  (0, project_config_1.writeProjectConfig)(projectRoot, mergeResult.config);
41
- return formatInitSummary(mergeResult.wasFirstInitialization, selected, mergeResult.added, mergeResult.alreadyInstalled, renderedWrites.map((item) => item.relative));
45
+ return formatInitSummary(mergeResult.wasFirstInitialization, selected, mergeResult.added, mergeResult.alreadyInstalled, renderedWrites.map((item) => item.relative), installResult);
42
46
  }
43
- function formatInitSummary(first, selected, added, alreadyInstalled, targets) {
47
+ function formatInitSummary(first, selected, added, alreadyInstalled, targets, installResult) {
44
48
  const lines = [];
45
49
  if (first) {
46
50
  lines.push("HIRMOS initialized.", "");
51
+ if (installResult.installed && installResult.sourceDescription) {
52
+ lines.push(`Installed framework from ${installResult.sourceDescription}.`, "");
53
+ }
47
54
  lines.push("Installed integrations:");
48
55
  for (const id of selected)
49
56
  lines.push(`- ${id}`);
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const args_1 = require("./lib/args");
5
5
  const errors_1 = require("./lib/errors");
6
6
  const init_1 = require("./commands/init");
7
+ const update_notice_1 = require("./lib/update-notice");
7
8
  function main() {
8
9
  try {
9
10
  const parsed = (0, args_1.parseArgs)(process.argv.slice(2));
@@ -12,10 +13,19 @@ function main() {
12
13
  return;
13
14
  }
14
15
  if (parsed.command === "init") {
16
+ if (!parsed.offline) {
17
+ const updateNotice = (0, update_notice_1.getUpdateNotice)();
18
+ if (updateNotice)
19
+ process.stderr.write(`${(0, update_notice_1.formatUpdateNotice)(updateNotice)}
20
+
21
+ `);
22
+ }
15
23
  const summary = (0, init_1.runInit)({
16
24
  projectPath: parsed.projectPath,
17
25
  integrations: parsed.integrations,
18
- source: parsed.source
26
+ source: parsed.source,
27
+ version: parsed.version,
28
+ offline: parsed.offline
19
29
  });
20
30
  process.stdout.write(summary);
21
31
  return;
package/dist/lib/args.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.parseArgs = parseArgs;
7
+ exports.normalizeVersionOption = normalizeVersionOption;
7
8
  exports.parseIntegrationList = parseIntegrationList;
8
9
  exports.helpText = helpText;
9
10
  const path_1 = __importDefault(require("path"));
@@ -14,12 +15,14 @@ function parseArgs(argv) {
14
15
  }
15
16
  const command = argv[0];
16
17
  if (command !== "init") {
17
- (0, errors_1.fail)(`Unsupported command: ${command}. DP-3B implements only: hirmos init`);
18
+ (0, errors_1.fail)(`Unsupported command: ${command}. This CLI currently supports: hirmos init`);
18
19
  }
19
20
  let projectPath = ".";
20
21
  let projectPathSet = false;
21
22
  let integrationValue;
22
23
  let source;
24
+ let version = "latest";
25
+ let offline = false;
23
26
  for (let i = 1; i < argv.length; i += 1) {
24
27
  const arg = argv[i];
25
28
  if (arg === "--integration") {
@@ -44,6 +47,21 @@ function parseArgs(argv) {
44
47
  source = arg.slice("--source=".length);
45
48
  continue;
46
49
  }
50
+ if (arg === "--version") {
51
+ const value = argv[++i];
52
+ if (!value)
53
+ (0, errors_1.fail)("Missing value for --version");
54
+ version = value;
55
+ continue;
56
+ }
57
+ if (arg.startsWith("--version=")) {
58
+ version = arg.slice("--version=".length);
59
+ continue;
60
+ }
61
+ if (arg === "--offline") {
62
+ offline = true;
63
+ continue;
64
+ }
47
65
  if (arg === "--help" || arg === "-h") {
48
66
  return { command: "help" };
49
67
  }
@@ -61,9 +79,17 @@ function parseArgs(argv) {
61
79
  command: "init",
62
80
  projectPath: path_1.default.resolve(projectPath),
63
81
  integrations,
64
- source: source ? path_1.default.resolve(source) : undefined
82
+ source: source ? path_1.default.resolve(source) : undefined,
83
+ version: normalizeVersionOption(version),
84
+ offline
65
85
  };
66
86
  }
87
+ function normalizeVersionOption(value) {
88
+ const normalized = value.trim();
89
+ if (!normalized)
90
+ (0, errors_1.fail)("--version must not be empty");
91
+ return normalized;
92
+ }
67
93
  function parseIntegrationList(value) {
68
94
  const ids = value
69
95
  .split(",")
@@ -75,5 +101,5 @@ function parseIntegrationList(value) {
75
101
  return ids;
76
102
  }
77
103
  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`;
104
+ return `HIRMOS CLI\n\nUsage:\n hirmos init [project-path] [--integration <ids>] [--source <path>] [--version <version>] [--offline]\n\nDefaults:\n project-path: .\n integration: agents\n source: existing _hirmos/ in the project, otherwise the latest GitHub release\n version: latest\n\nNotes:\n --source installs from a local _hirmos folder, framework folder, or hirmos-framework.zip.\n --version selects a GitHub release when _hirmos/ is missing and --source is not provided.\n --offline disables remote release download and requires an existing _hirmos/ or --source.\n\nThis CLI implements hirmos init. Workflow commands such as hirmos requirements are interpreted by an agent after HIRMOS Core is loaded.\n`;
79
105
  }
@@ -4,20 +4,61 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ensureHirmosInstalled = ensureHirmosInstalled;
7
+ exports.buildReleaseDownloadUrl = buildReleaseDownloadUrl;
8
+ exports.normalizeReleaseVersion = normalizeReleaseVersion;
7
9
  const fs_1 = __importDefault(require("fs"));
8
10
  const os_1 = __importDefault(require("os"));
9
11
  const path_1 = __importDefault(require("path"));
10
12
  const child_process_1 = require("child_process");
11
13
  const errors_1 = require("./errors");
12
14
  const filesystem_1 = require("./filesystem");
13
- function ensureHirmosInstalled(projectRoot, source) {
15
+ const RELEASE_OWNER = "ymarrerot";
16
+ const RELEASE_REPO = "HIRMOS";
17
+ const RELEASE_ASSET = "hirmos-framework.zip";
18
+ function ensureHirmosInstalled(projectRoot, options) {
14
19
  const hirmosDir = path_1.default.join(projectRoot, "_hirmos");
15
20
  if ((0, filesystem_1.pathExists)(hirmosDir)) {
16
- return;
21
+ return { installed: false, sourceDescription: null };
22
+ }
23
+ if (options.source) {
24
+ installFromSource(projectRoot, options.source);
25
+ return { installed: true, sourceDescription: options.source };
26
+ }
27
+ if (options.offline) {
28
+ (0, errors_1.fail)(`Missing _hirmos/ in ${projectRoot}. Offline mode requires an existing _hirmos/ or --source <path-to-hirmos-framework.zip-or-folder>.`);
17
29
  }
18
- if (!source) {
19
- (0, errors_1.fail)(`Missing _hirmos/ in ${projectRoot}. Provide --source <path-to-hirmos-framework.zip-or-folder>.`);
30
+ const version = normalizeReleaseVersion(options.version);
31
+ const url = buildReleaseDownloadUrl(version);
32
+ const tmp = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "hirmos-release-"));
33
+ const zipPath = path_1.default.join(tmp, RELEASE_ASSET);
34
+ try {
35
+ downloadFile(url, zipPath);
36
+ installFromZip(projectRoot, zipPath);
37
+ return { installed: true, sourceDescription: version === "latest" ? "latest GitHub release" : `GitHub release ${version}` };
20
38
  }
39
+ finally {
40
+ fs_1.default.rmSync(tmp, { recursive: true, force: true });
41
+ }
42
+ }
43
+ function buildReleaseDownloadUrl(version) {
44
+ const normalized = normalizeReleaseVersion(version);
45
+ const overrideBase = process.env.HIRMOS_CLI_RELEASE_BASE_URL;
46
+ if (overrideBase) {
47
+ const trimmed = overrideBase.replace(/\/+$/u, "");
48
+ return `${trimmed}/${normalized}/${RELEASE_ASSET}`;
49
+ }
50
+ if (normalized === "latest") {
51
+ return `https://github.com/${RELEASE_OWNER}/${RELEASE_REPO}/releases/latest/download/${RELEASE_ASSET}`;
52
+ }
53
+ return `https://github.com/${RELEASE_OWNER}/${RELEASE_REPO}/releases/download/${normalized}/${RELEASE_ASSET}`;
54
+ }
55
+ function normalizeReleaseVersion(version) {
56
+ const trimmed = version.trim();
57
+ if (!trimmed || trimmed === "latest")
58
+ return "latest";
59
+ return trimmed.startsWith("v") ? trimmed : `v${trimmed}`;
60
+ }
61
+ function installFromSource(projectRoot, source) {
21
62
  if (!(0, filesystem_1.pathExists)(source)) {
22
63
  (0, errors_1.fail)(`Source path does not exist: ${source}`);
23
64
  }
@@ -46,11 +87,11 @@ function installFromZip(projectRoot, zipPath) {
46
87
  extractZip(zipPath, tmp);
47
88
  }
48
89
  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}`);
90
+ (0, errors_1.fail)(`Unable to extract framework zip. Ensure the zip is valid and an extraction command is available: ${error.message}`);
50
91
  }
51
92
  const sourceHirmos = findHirmosDir(tmp);
52
93
  if (!sourceHirmos) {
53
- (0, errors_1.fail)(`Source zip does not contain an _hirmos/ folder: ${zipPath}`);
94
+ (0, errors_1.fail)(`Framework zip does not contain an _hirmos/ folder: ${zipPath}`);
54
95
  }
55
96
  copyHirmos(projectRoot, sourceHirmos);
56
97
  }
@@ -79,6 +120,16 @@ function copyHirmos(projectRoot, sourceHirmos) {
79
120
  }
80
121
  (0, filesystem_1.ensureDir)(projectRoot);
81
122
  fs_1.default.cpSync(sourceHirmos, target, { recursive: true, errorOnExist: true });
123
+ validateCopiedHirmos(projectRoot);
124
+ }
125
+ function validateCopiedHirmos(projectRoot) {
126
+ const required = ["_hirmos/HIRMOS_CORE.md", "_hirmos/VERSION", "_hirmos/project.json"];
127
+ for (const relative of required) {
128
+ if (!(0, filesystem_1.pathExists)(path_1.default.join(projectRoot, relative))) {
129
+ fs_1.default.rmSync(path_1.default.join(projectRoot, "_hirmos"), { recursive: true, force: true });
130
+ (0, errors_1.fail)(`Installed framework is incomplete; missing ${relative}. Removed partial _hirmos/ install.`);
131
+ }
132
+ }
82
133
  }
83
134
  function extractZip(zipPath, destination) {
84
135
  if (process.platform === "win32") {
@@ -87,3 +138,26 @@ function extractZip(zipPath, destination) {
87
138
  }
88
139
  (0, child_process_1.execFileSync)("unzip", ["-q", zipPath, "-d", destination], { stdio: "ignore" });
89
140
  }
141
+ function downloadFile(url, destination) {
142
+ (0, filesystem_1.ensureDir)(path_1.default.dirname(destination));
143
+ try {
144
+ if (process.platform === "win32") {
145
+ (0, child_process_1.execFileSync)("powershell", [
146
+ "-NoProfile",
147
+ "-Command",
148
+ "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -Uri $args[0] -OutFile $args[1]",
149
+ url,
150
+ destination
151
+ ], { stdio: "ignore" });
152
+ }
153
+ else {
154
+ (0, child_process_1.execFileSync)("curl", ["-fL", "--retry", "2", "--connect-timeout", "20", "--max-time", "120", "-o", destination, url], { stdio: "ignore" });
155
+ }
156
+ }
157
+ catch (error) {
158
+ (0, errors_1.fail)(`Unable to download HIRMOS release from ${url}. Use --source for a local release package or --offline to disable downloads. ${error.message}`);
159
+ }
160
+ if (!(0, filesystem_1.pathExists)(destination) || fs_1.default.statSync(destination).size === 0) {
161
+ (0, errors_1.fail)(`Downloaded HIRMOS release is empty or missing: ${destination}`);
162
+ }
163
+ }
@@ -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.getInstalledCliVersion = getInstalledCliVersion;
7
+ exports.normalizeSemver = normalizeSemver;
8
+ exports.compareSemver = compareSemver;
9
+ exports.buildUpdateNotice = buildUpdateNotice;
10
+ exports.formatUpdateNotice = formatUpdateNotice;
11
+ exports.getLatestPublishedVersion = getLatestPublishedVersion;
12
+ exports.getUpdateNotice = getUpdateNotice;
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const child_process_1 = require("child_process");
16
+ const PACKAGE_NAME = "hirmos";
17
+ const DEFAULT_TIMEOUT_MS = 1500;
18
+ const ANSI_BOLD = "\u001b[1m";
19
+ const ANSI_YELLOW = "\u001b[33m";
20
+ const ANSI_RESET = "\u001b[0m";
21
+ function getInstalledCliVersion(cliRoot = path_1.default.resolve(__dirname, "../..")) {
22
+ try {
23
+ const packageJsonPath = path_1.default.join(cliRoot, "package.json");
24
+ const raw = fs_1.default.readFileSync(packageJsonPath, "utf8");
25
+ const parsed = JSON.parse(raw);
26
+ return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : null;
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ function normalizeSemver(value) {
33
+ return value.trim().replace(/^v/u, "").split("-")[0];
34
+ }
35
+ function compareSemver(left, right) {
36
+ const leftParts = normalizeSemver(left).split(".").map((part) => Number.parseInt(part, 10));
37
+ const rightParts = normalizeSemver(right).split(".").map((part) => Number.parseInt(part, 10));
38
+ for (let i = 0; i < 3; i += 1) {
39
+ const leftValue = Number.isFinite(leftParts[i]) ? leftParts[i] : 0;
40
+ const rightValue = Number.isFinite(rightParts[i]) ? rightParts[i] : 0;
41
+ if (leftValue > rightValue)
42
+ return 1;
43
+ if (leftValue < rightValue)
44
+ return -1;
45
+ }
46
+ return 0;
47
+ }
48
+ function buildUpdateNotice(currentVersion, latestVersion) {
49
+ if (!currentVersion || !latestVersion)
50
+ return null;
51
+ if (compareSemver(latestVersion, currentVersion) <= 0)
52
+ return null;
53
+ return `A newer HIRMOS CLI is available: ${latestVersion}.\nUpdate with: npm install -g ${PACKAGE_NAME}@latest`;
54
+ }
55
+ function formatUpdateNotice(message, useColor = Boolean(process.stderr.isTTY) && process.env.NO_COLOR === undefined) {
56
+ return useColor ? `${ANSI_BOLD}${ANSI_YELLOW}${message}${ANSI_RESET}` : message;
57
+ }
58
+ function getLatestPublishedVersion(timeoutMs = DEFAULT_TIMEOUT_MS) {
59
+ if (process.env.HIRMOS_CLI_UPDATE_CHECK === "0" || process.env.HIRMOS_NO_UPDATE_CHECK === "1") {
60
+ return null;
61
+ }
62
+ try {
63
+ const result = (0, child_process_1.spawnSync)("npm", ["view", PACKAGE_NAME, "version", "--silent"], {
64
+ encoding: "utf8",
65
+ stdio: ["ignore", "pipe", "ignore"],
66
+ timeout: timeoutMs
67
+ });
68
+ if (result.error || result.status !== 0)
69
+ return null;
70
+ const version = String(result.stdout ?? "").trim();
71
+ return /^v?\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/u.test(version) ? version : null;
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ function getUpdateNotice() {
78
+ return buildUpdateNotice(getInstalledCliVersion(), getLatestPublishedVersion());
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hirmos",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "HIRMOS product-facing CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "commonjs",