@hi-man/himan 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/LICENSE +201 -0
- package/README.md +145 -0
- package/dist/adapters/git/repo-manager.js +70 -0
- package/dist/adapters/resource/resource-scanner.js +54 -0
- package/dist/adapters/source/git-source-adapter.js +149 -0
- package/dist/adapters/source/registry-source-adapter.js +21 -0
- package/dist/adapters/source/resource-source-adapter.js +1 -0
- package/dist/adapters/version/version-resolver.js +13 -0
- package/dist/cli/index.js +318 -0
- package/dist/domain/resource.js +1 -0
- package/dist/index.js +17 -0
- package/dist/services/index.js +331 -0
- package/dist/state/index-cache-store.js +48 -0
- package/dist/state/project-lock-store.js +72 -0
- package/dist/state/state-store.js +55 -0
- package/dist/utils/errors.js +25 -0
- package/dist/utils/path-resolver.js +16 -0
- package/dist/utils/repo-id.js +4 -0
- package/dist/version.js +10 -0
- package/package.json +55 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { PathResolver } from "../utils/path-resolver.js";
|
|
4
|
+
export class IndexCacheStore {
|
|
5
|
+
paths = new PathResolver();
|
|
6
|
+
getIndexPath() {
|
|
7
|
+
return path.join(this.paths.getHimanRoot(), "index.json");
|
|
8
|
+
}
|
|
9
|
+
async get(repoId, type) {
|
|
10
|
+
const data = await this.load();
|
|
11
|
+
if (!data)
|
|
12
|
+
return null;
|
|
13
|
+
return data.entries.find((item) => item.repoId === repoId && item.type === type) ?? null;
|
|
14
|
+
}
|
|
15
|
+
async upsert(repoId, type, baseDirMtimeMs, resources) {
|
|
16
|
+
const now = new Date().toISOString();
|
|
17
|
+
const file = (await this.load()) ?? { version: 1, entries: [] };
|
|
18
|
+
const found = file.entries.find((item) => item.repoId === repoId && item.type === type);
|
|
19
|
+
if (found) {
|
|
20
|
+
found.baseDirMtimeMs = baseDirMtimeMs;
|
|
21
|
+
found.resources = resources;
|
|
22
|
+
found.updatedAt = now;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
file.entries.push({
|
|
26
|
+
repoId,
|
|
27
|
+
type,
|
|
28
|
+
baseDirMtimeMs,
|
|
29
|
+
resources,
|
|
30
|
+
updatedAt: now,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
await fs.mkdir(path.dirname(this.getIndexPath()), { recursive: true });
|
|
34
|
+
await fs.writeFile(this.getIndexPath(), JSON.stringify(file, null, 2), "utf8");
|
|
35
|
+
}
|
|
36
|
+
async load() {
|
|
37
|
+
try {
|
|
38
|
+
const raw = await fs.readFile(this.getIndexPath(), "utf8");
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.entries))
|
|
41
|
+
return null;
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class ProjectLockStore {
|
|
4
|
+
getLockPath(projectDir) {
|
|
5
|
+
return path.join(projectDir, "himan.lock");
|
|
6
|
+
}
|
|
7
|
+
async loadWithState(projectDir) {
|
|
8
|
+
const lockPath = this.getLockPath(projectDir);
|
|
9
|
+
try {
|
|
10
|
+
const raw = await fs.readFile(lockPath, "utf8");
|
|
11
|
+
const parsed = JSON.parse(raw);
|
|
12
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.resources)) {
|
|
13
|
+
return { lock: null, state: "invalid" };
|
|
14
|
+
}
|
|
15
|
+
return { lock: parsed, state: "ok" };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const err = error;
|
|
19
|
+
if (err?.code === "ENOENT") {
|
|
20
|
+
return { lock: null, state: "missing" };
|
|
21
|
+
}
|
|
22
|
+
return { lock: null, state: "invalid" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async load(projectDir) {
|
|
26
|
+
const result = await this.loadWithState(projectDir);
|
|
27
|
+
return result.lock;
|
|
28
|
+
}
|
|
29
|
+
async upsertResource(projectDir, source, resource) {
|
|
30
|
+
const now = new Date().toISOString();
|
|
31
|
+
const existing = await this.load(projectDir);
|
|
32
|
+
const lock = existing ?? {
|
|
33
|
+
version: 1,
|
|
34
|
+
source,
|
|
35
|
+
updatedAt: now,
|
|
36
|
+
resources: [],
|
|
37
|
+
};
|
|
38
|
+
lock.source = source;
|
|
39
|
+
lock.updatedAt = now;
|
|
40
|
+
const found = lock.resources.find((item) => item.type === resource.type && item.name === resource.name);
|
|
41
|
+
if (found) {
|
|
42
|
+
found.version = resource.version;
|
|
43
|
+
found.updatedAt = now;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
lock.resources.push({
|
|
47
|
+
type: resource.type,
|
|
48
|
+
name: resource.name,
|
|
49
|
+
version: resource.version,
|
|
50
|
+
updatedAt: now,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
lock.resources.sort((a, b) => {
|
|
54
|
+
if (a.type !== b.type)
|
|
55
|
+
return a.type.localeCompare(b.type);
|
|
56
|
+
return a.name.localeCompare(b.name);
|
|
57
|
+
});
|
|
58
|
+
await fs.writeFile(this.getLockPath(projectDir), JSON.stringify(lock, null, 2), "utf8");
|
|
59
|
+
}
|
|
60
|
+
async removeResource(projectDir, resource) {
|
|
61
|
+
const lock = await this.load(projectDir);
|
|
62
|
+
if (!lock)
|
|
63
|
+
return;
|
|
64
|
+
const nextResources = lock.resources.filter((item) => !(item.type === resource.type && item.name === resource.name));
|
|
65
|
+
if (nextResources.length === lock.resources.length) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
lock.resources = nextResources;
|
|
69
|
+
lock.updatedAt = new Date().toISOString();
|
|
70
|
+
await fs.writeFile(this.getLockPath(projectDir), JSON.stringify(lock, null, 2), "utf8");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { PathResolver } from "../utils/path-resolver.js";
|
|
4
|
+
export class StateStore {
|
|
5
|
+
paths = new PathResolver();
|
|
6
|
+
getConfigPath() {
|
|
7
|
+
return path.join(this.paths.getHimanRoot(), "config.json");
|
|
8
|
+
}
|
|
9
|
+
async ensureBaseDirs() {
|
|
10
|
+
await fs.mkdir(this.paths.getReposDir(), { recursive: true });
|
|
11
|
+
await fs.mkdir(this.paths.getStoreDir(), { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
async saveConfig(config) {
|
|
14
|
+
const normalized = this.normalizeConfig(config);
|
|
15
|
+
await fs.mkdir(path.dirname(this.getConfigPath()), { recursive: true });
|
|
16
|
+
await fs.writeFile(this.getConfigPath(), JSON.stringify(normalized, null, 2));
|
|
17
|
+
}
|
|
18
|
+
async loadConfig() {
|
|
19
|
+
try {
|
|
20
|
+
const raw = await fs.readFile(this.getConfigPath(), "utf8");
|
|
21
|
+
return this.normalizeConfig(JSON.parse(raw));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
normalizeConfig(input) {
|
|
28
|
+
if (input.sources?.default && input.sources.items) {
|
|
29
|
+
const defaultName = input.sources.default;
|
|
30
|
+
const defaultSource = input.sources.items[defaultName];
|
|
31
|
+
if (defaultSource) {
|
|
32
|
+
return {
|
|
33
|
+
source: defaultSource,
|
|
34
|
+
sources: {
|
|
35
|
+
default: defaultName,
|
|
36
|
+
items: input.sources.items,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const fallback = input.source;
|
|
42
|
+
if (!fallback) {
|
|
43
|
+
throw new Error("Invalid config: source is required.");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
source: fallback,
|
|
47
|
+
sources: {
|
|
48
|
+
default: "default",
|
|
49
|
+
items: {
|
|
50
|
+
default: fallback,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class HimanError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
details;
|
|
4
|
+
constructor(code, message, details) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.details = details;
|
|
8
|
+
this.name = "HimanError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export const errorCodes = {
|
|
12
|
+
CONFIG_NOT_FOUND: "E_CONFIG_NOT_FOUND",
|
|
13
|
+
NOT_IMPLEMENTED: "E_NOT_IMPLEMENTED",
|
|
14
|
+
INVALID_INPUT: "E_INVALID_INPUT",
|
|
15
|
+
RESOURCE_NOT_FOUND: "E_RESOURCE_NOT_FOUND",
|
|
16
|
+
VERSION_NOT_FOUND: "E_VERSION_NOT_FOUND",
|
|
17
|
+
INSTALL_NOT_FOUND: "E_INSTALL_NOT_FOUND",
|
|
18
|
+
LOCK_NOT_FOUND: "E_LOCK_NOT_FOUND",
|
|
19
|
+
LOCK_INVALID: "E_LOCK_INVALID",
|
|
20
|
+
CLI_USAGE: "E_CLI_USAGE",
|
|
21
|
+
RESOURCE_EXISTS: "E_RESOURCE_EXISTS",
|
|
22
|
+
TEMPLATE_NOT_FOUND: "E_TEMPLATE_NOT_FOUND",
|
|
23
|
+
INVALID_RESOURCE_NAME: "E_INVALID_RESOURCE_NAME",
|
|
24
|
+
UNSUPPORTED_RESOURCE_TYPE: "E_UNSUPPORTED_RESOURCE_TYPE",
|
|
25
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class PathResolver {
|
|
4
|
+
getHomeDir() {
|
|
5
|
+
return os.homedir();
|
|
6
|
+
}
|
|
7
|
+
getHimanRoot() {
|
|
8
|
+
return path.join(this.getHomeDir(), ".himan");
|
|
9
|
+
}
|
|
10
|
+
getReposDir() {
|
|
11
|
+
return path.join(this.getHimanRoot(), "repos");
|
|
12
|
+
}
|
|
13
|
+
getStoreDir() {
|
|
14
|
+
return path.join(this.getHimanRoot(), "store");
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const pkgPath = join(moduleDir, "../package.json");
|
|
6
|
+
const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
7
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0) {
|
|
8
|
+
throw new Error("Invalid or missing version in package.json");
|
|
9
|
+
}
|
|
10
|
+
export const PACKAGE_VERSION = parsed.version;
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hi-man/himan",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Prompt and agent asset management CLI",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"packageManager": "pnpm@10.32.1",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/lidetao/himan.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/lidetao/himan/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/lidetao/himan#readme",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://registry.npmjs.org/"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"himan": "dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.json",
|
|
30
|
+
"dev": "tsx src/index.ts",
|
|
31
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"verify": "pnpm run typecheck && pnpm run test && pnpm run build",
|
|
35
|
+
"release": "pnpm run verify && npm publish",
|
|
36
|
+
"release:dry": "pnpm run verify && npm publish --dry-run",
|
|
37
|
+
"release:test": "pnpm run verify && npm version prerelease --preid test --no-git-tag-version && npm publish --tag test",
|
|
38
|
+
"version:patch": "npm version patch --no-git-tag-version",
|
|
39
|
+
"version:minor": "npm version minor --no-git-tag-version",
|
|
40
|
+
"version:major": "npm version major --no-git-tag-version"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"commander": "^14.0.3",
|
|
44
|
+
"semver": "^7.7.4",
|
|
45
|
+
"simple-git": "^3.33.0",
|
|
46
|
+
"yaml": "^2.8.3"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^24.12.0",
|
|
50
|
+
"@types/semver": "^7.7.1",
|
|
51
|
+
"tsx": "^4.21.0",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vitest": "^4.1.2"
|
|
54
|
+
}
|
|
55
|
+
}
|