@hi-man/himan 0.1.0 → 0.2.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 +54 -11
- package/dist/adapters/resource/resource-scanner.js +3 -1
- package/dist/adapters/source/git-source-adapter.js +2 -2
- package/dist/bin/himan-project.js +7 -0
- package/dist/bin/himan-resource.js +7 -0
- package/dist/bin/himan-source.js +7 -0
- package/dist/bin/himan.js +7 -0
- package/dist/{index.js → bin/shared.js} +3 -5
- package/dist/cli/agent-commands.js +93 -0
- package/dist/cli/builders.js +72 -0
- package/dist/cli/index.js +3 -318
- package/dist/cli/project-commands.js +127 -0
- package/dist/cli/resource-commands.js +104 -0
- package/dist/cli/shared.js +69 -0
- package/dist/cli/source-commands.js +58 -0
- package/dist/services/index.js +200 -44
- package/dist/state/project-config-store.js +43 -0
- package/dist/state/project-lock-store.js +4 -0
- package/dist/state/state-store.js +5 -1
- package/dist/utils/agent-configs.js +71 -0
- package/dist/{version.js → utils/version.js} +1 -1
- package/package.json +6 -3
package/dist/services/index.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import { GitSourceAdapter } from "../adapters/source/git-source-adapter.js";
|
|
2
2
|
import { RegistrySourceAdapter } from "../adapters/source/registry-source-adapter.js";
|
|
3
3
|
import { StateStore } from "../state/state-store.js";
|
|
4
|
+
import { ProjectConfigStore } from "../state/project-config-store.js";
|
|
4
5
|
import { ProjectLockStore } from "../state/project-lock-store.js";
|
|
5
6
|
import { PathResolver } from "../utils/path-resolver.js";
|
|
6
7
|
import { toRepoId } from "../utils/repo-id.js";
|
|
7
8
|
import { HimanError, errorCodes } from "../utils/errors.js";
|
|
9
|
+
import { getProjectResourcePaths, getSupportedAgentNames, normalizeAgents, } from "../utils/agent-configs.js";
|
|
8
10
|
import path from "node:path";
|
|
9
11
|
import { promises as fs } from "node:fs";
|
|
10
12
|
import { VersionResolver } from "../adapters/version/version-resolver.js";
|
|
13
|
+
import YAML from "yaml";
|
|
11
14
|
export class ServiceFactory {
|
|
12
15
|
stateStore = new StateStore();
|
|
16
|
+
projectConfigStore = new ProjectConfigStore();
|
|
13
17
|
lockStore = new ProjectLockStore();
|
|
14
18
|
paths = new PathResolver();
|
|
15
19
|
versions = new VersionResolver();
|
|
16
20
|
async initSource(type, repo) {
|
|
17
21
|
await this.stateStore.ensureBaseDirs();
|
|
22
|
+
const current = await this.stateStore.loadConfig();
|
|
18
23
|
const sourceConfig = this.buildSourceConfig(type, repo);
|
|
19
24
|
const source = this.createSource(type);
|
|
20
25
|
await source.init(sourceConfig);
|
|
@@ -29,6 +34,7 @@ export class ServiceFactory {
|
|
|
29
34
|
default: "default",
|
|
30
35
|
items: { default: stateSource },
|
|
31
36
|
},
|
|
37
|
+
agents: current?.agents,
|
|
32
38
|
});
|
|
33
39
|
return {
|
|
34
40
|
sourceType: type,
|
|
@@ -60,6 +66,7 @@ export class ServiceFactory {
|
|
|
60
66
|
default: defaultName,
|
|
61
67
|
items,
|
|
62
68
|
},
|
|
69
|
+
agents: current?.agents,
|
|
63
70
|
});
|
|
64
71
|
return { name, type, repo: sourceConfig.repo, repoId: sourceConfig.repoId };
|
|
65
72
|
}
|
|
@@ -78,6 +85,7 @@ export class ServiceFactory {
|
|
|
78
85
|
default: name,
|
|
79
86
|
items: config.sources.items,
|
|
80
87
|
},
|
|
88
|
+
agents: config.agents,
|
|
81
89
|
});
|
|
82
90
|
return { name };
|
|
83
91
|
}
|
|
@@ -93,15 +101,63 @@ export class ServiceFactory {
|
|
|
93
101
|
isDefault: name === config.sources?.default,
|
|
94
102
|
}));
|
|
95
103
|
}
|
|
96
|
-
async
|
|
104
|
+
async setAgents(agents, scope, projectDir) {
|
|
105
|
+
const normalized = normalizeAgents(agents);
|
|
106
|
+
if (scope === "project") {
|
|
107
|
+
await this.projectConfigStore.saveAgents(projectDir, normalized);
|
|
108
|
+
return { scope, agents: normalized };
|
|
109
|
+
}
|
|
110
|
+
await this.stateStore.ensureBaseDirs();
|
|
111
|
+
const current = await this.stateStore.loadConfig();
|
|
112
|
+
await this.stateStore.saveConfig({
|
|
113
|
+
...(current ?? {}),
|
|
114
|
+
agents: normalized,
|
|
115
|
+
});
|
|
116
|
+
return { scope, agents: normalized };
|
|
117
|
+
}
|
|
118
|
+
async getAgentSettings(projectDir) {
|
|
119
|
+
const [globalConfig, projectConfig] = await Promise.all([
|
|
120
|
+
this.stateStore.loadConfig(),
|
|
121
|
+
this.projectConfigStore.load(projectDir),
|
|
122
|
+
]);
|
|
123
|
+
const global = globalConfig?.agents?.length
|
|
124
|
+
? normalizeAgents(globalConfig.agents)
|
|
125
|
+
: undefined;
|
|
126
|
+
const project = projectConfig?.agents?.length
|
|
127
|
+
? normalizeAgents(projectConfig.agents)
|
|
128
|
+
: undefined;
|
|
129
|
+
return {
|
|
130
|
+
global,
|
|
131
|
+
project,
|
|
132
|
+
effective: project ?? global ?? normalizeAgents(),
|
|
133
|
+
supported: getSupportedAgentNames(),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async clearAgents(scope, projectDir) {
|
|
137
|
+
if (scope === "project") {
|
|
138
|
+
await this.projectConfigStore.clearAgents(projectDir);
|
|
139
|
+
return { scope };
|
|
140
|
+
}
|
|
141
|
+
const current = await this.stateStore.loadConfig();
|
|
142
|
+
await this.stateStore.saveConfig({
|
|
143
|
+
...(current ?? {}),
|
|
144
|
+
agents: undefined,
|
|
145
|
+
});
|
|
146
|
+
return { scope };
|
|
147
|
+
}
|
|
148
|
+
async list(type, agents) {
|
|
97
149
|
const source = await this.loadSourceFromConfig();
|
|
98
|
-
|
|
150
|
+
const resources = await source.list(type);
|
|
151
|
+
if (!agents?.length)
|
|
152
|
+
return resources;
|
|
153
|
+
const selected = normalizeAgents(agents);
|
|
154
|
+
return resources.filter((resource) => normalizeAgents(resource.agents).some((agent) => selected.includes(agent)));
|
|
99
155
|
}
|
|
100
156
|
async history(type, name) {
|
|
101
157
|
const source = await this.loadSourceFromConfig();
|
|
102
158
|
return source.history(type, name);
|
|
103
159
|
}
|
|
104
|
-
async install(type, name, version, projectDir) {
|
|
160
|
+
async install(type, name, version, projectDir, agents, mode = "link") {
|
|
105
161
|
const source = await this.loadSourceFromConfig();
|
|
106
162
|
const sourceInfo = await this.getLockSourceInfo();
|
|
107
163
|
const history = await source.history(type, name);
|
|
@@ -110,37 +166,53 @@ export class ServiceFactory {
|
|
|
110
166
|
}
|
|
111
167
|
const resolvedVersion = this.resolveVersion(history, version);
|
|
112
168
|
const storePath = this.getStorePath(type, name, resolvedVersion);
|
|
113
|
-
const linkPath = this.getProjectResourcePath(projectDir, type, name);
|
|
114
169
|
if (!(await this.exists(storePath))) {
|
|
115
170
|
await source.pull(type, name, resolvedVersion, storePath);
|
|
116
171
|
}
|
|
117
|
-
await this.
|
|
172
|
+
const resourceMeta = await this.readResourceMetaFromDir(storePath);
|
|
173
|
+
const effectiveTargets = await this.resolveEffectiveAgents(projectDir, agents, resourceMeta?.agents);
|
|
174
|
+
const linkPaths = getProjectResourcePaths(projectDir, type, name, effectiveTargets);
|
|
175
|
+
for (const linkPath of linkPaths) {
|
|
176
|
+
await this.materializeResource(storePath, linkPath, mode);
|
|
177
|
+
}
|
|
118
178
|
await this.lockStore.upsertResource(projectDir, sourceInfo, {
|
|
119
179
|
type,
|
|
120
180
|
name,
|
|
121
181
|
version: resolvedVersion,
|
|
182
|
+
agents: effectiveTargets,
|
|
183
|
+
mode,
|
|
122
184
|
});
|
|
123
|
-
return { type, name, version: resolvedVersion, linkPath };
|
|
185
|
+
return { type, name, version: resolvedVersion, linkPath: linkPaths[0], mode };
|
|
124
186
|
}
|
|
125
187
|
async dev(type, name, projectDir) {
|
|
126
|
-
const
|
|
127
|
-
const installedPath =
|
|
188
|
+
const installInfo = await this.resolveInstalledResource(projectDir, type, name);
|
|
189
|
+
const installedPath = installInfo.installedPath;
|
|
128
190
|
const devPath = this.getProjectDevPath(projectDir, type, name);
|
|
129
191
|
if (!(await this.exists(devPath))) {
|
|
130
192
|
await fs.mkdir(path.dirname(devPath), { recursive: true });
|
|
131
193
|
await fs.cp(installedPath, devPath, { recursive: true });
|
|
132
194
|
}
|
|
133
|
-
|
|
134
|
-
|
|
195
|
+
for (const linkPath of installInfo.linkPaths) {
|
|
196
|
+
await this.materializeResource(devPath, linkPath, installInfo.mode);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
type,
|
|
200
|
+
name,
|
|
201
|
+
devPath,
|
|
202
|
+
linkPath: installInfo.linkPaths[0],
|
|
203
|
+
mode: installInfo.mode,
|
|
204
|
+
};
|
|
135
205
|
}
|
|
136
206
|
async uninstall(type, name, projectDir) {
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
throw new HimanError(errorCodes.INSTALL_NOT_FOUND, `Installed resource link not found
|
|
207
|
+
const installInfo = await this.resolveInstalledResource(projectDir, type, name);
|
|
208
|
+
if (installInfo.linkPaths.length === 0) {
|
|
209
|
+
throw new HimanError(errorCodes.INSTALL_NOT_FOUND, `Installed resource link not found for ${type}/${name}.`);
|
|
210
|
+
}
|
|
211
|
+
for (const linkPath of installInfo.linkPaths) {
|
|
212
|
+
await fs.rm(linkPath, { recursive: true, force: true });
|
|
140
213
|
}
|
|
141
|
-
await fs.rm(linkPath, { recursive: true, force: true });
|
|
142
214
|
await this.lockStore.removeResource(projectDir, { type, name });
|
|
143
|
-
return { type, name, linkPath };
|
|
215
|
+
return { type, name, linkPath: installInfo.linkPaths[0] };
|
|
144
216
|
}
|
|
145
217
|
async publish(type, name, releaseType, projectDir) {
|
|
146
218
|
const source = await this.loadSourceFromConfig();
|
|
@@ -155,33 +227,43 @@ export class ServiceFactory {
|
|
|
155
227
|
if (!(await this.exists(storePath))) {
|
|
156
228
|
await source.pull(type, name, nextVersion, storePath);
|
|
157
229
|
}
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
230
|
+
const agentsFromMeta = normalizeAgents((await this.readResourceMetaFromDir(storePath))?.agents);
|
|
231
|
+
const locked = await this.getLockedResource(projectDir, type, name);
|
|
232
|
+
const nextAgents = locked?.agents?.length
|
|
233
|
+
? normalizeAgents(locked.agents)
|
|
234
|
+
: agentsFromMeta;
|
|
235
|
+
const installMode = this.resolveInstallMode(locked?.mode);
|
|
236
|
+
const linkPaths = getProjectResourcePaths(projectDir, type, name, nextAgents);
|
|
237
|
+
for (const linkPath of linkPaths) {
|
|
238
|
+
if (await this.exists(linkPath)) {
|
|
239
|
+
await this.materializeResource(storePath, linkPath, installMode);
|
|
240
|
+
}
|
|
161
241
|
}
|
|
162
|
-
if (
|
|
242
|
+
if (locked) {
|
|
163
243
|
const sourceInfo = await this.getLockSourceInfo();
|
|
164
244
|
await this.lockStore.upsertResource(projectDir, sourceInfo, {
|
|
165
245
|
type,
|
|
166
246
|
name,
|
|
167
247
|
version: nextVersion,
|
|
248
|
+
agents: nextAgents,
|
|
249
|
+
mode: installMode,
|
|
168
250
|
});
|
|
169
251
|
}
|
|
170
252
|
return { type, name, version: result.version, tag: result.tag };
|
|
171
253
|
}
|
|
172
|
-
async create(type, name, options) {
|
|
254
|
+
async create(type, name, options, projectDir) {
|
|
173
255
|
this.validateCreateInput(type, name, options);
|
|
174
256
|
const source = await this.loadSourceFromConfig();
|
|
175
257
|
return source.create(type, name, {
|
|
176
258
|
description: options.description,
|
|
177
|
-
|
|
259
|
+
agents: await this.resolveEffectiveAgents(projectDir, options.agents),
|
|
178
260
|
entry: options.entry,
|
|
179
261
|
template: options.template ?? "basic",
|
|
180
262
|
force: options.force,
|
|
181
263
|
dryRun: options.dryRun,
|
|
182
264
|
});
|
|
183
265
|
}
|
|
184
|
-
async installFromLock(projectDir) {
|
|
266
|
+
async installFromLock(projectDir, agents, mode) {
|
|
185
267
|
const { lock, state } = await this.lockStore.loadWithState(projectDir);
|
|
186
268
|
if (state === "missing") {
|
|
187
269
|
throw new HimanError(errorCodes.LOCK_NOT_FOUND, `Lock file not found: ${this.lockStore.getLockPath(projectDir)}`);
|
|
@@ -194,14 +276,14 @@ export class ServiceFactory {
|
|
|
194
276
|
}
|
|
195
277
|
const results = [];
|
|
196
278
|
for (const item of lock.resources) {
|
|
197
|
-
const result = await this.install(item.type, item.name, item.version, projectDir);
|
|
279
|
+
const result = await this.install(item.type, item.name, item.version, projectDir, agents ?? item.agents, mode ?? this.resolveInstallMode(item.mode));
|
|
198
280
|
results.push(result);
|
|
199
281
|
}
|
|
200
282
|
return results;
|
|
201
283
|
}
|
|
202
284
|
async loadSourceFromConfig() {
|
|
203
285
|
const config = await this.stateStore.loadConfig();
|
|
204
|
-
if (!config) {
|
|
286
|
+
if (!config?.source) {
|
|
205
287
|
throw new HimanError(errorCodes.CONFIG_NOT_FOUND, "Source config not found. Please run `himan init <git_repo>` first.");
|
|
206
288
|
}
|
|
207
289
|
const sourceConfig = this.buildSourceConfig(config.source.type, config.source.repo, config.source.repoId);
|
|
@@ -216,7 +298,7 @@ export class ServiceFactory {
|
|
|
216
298
|
}
|
|
217
299
|
async getLockSourceInfo() {
|
|
218
300
|
const config = await this.stateStore.loadConfig();
|
|
219
|
-
if (!config) {
|
|
301
|
+
if (!config?.source) {
|
|
220
302
|
throw new HimanError(errorCodes.CONFIG_NOT_FOUND, "Source config not found. Please run `himan init <git_repo>` first.");
|
|
221
303
|
}
|
|
222
304
|
return {
|
|
@@ -225,11 +307,11 @@ export class ServiceFactory {
|
|
|
225
307
|
repoId: config.source.repoId,
|
|
226
308
|
};
|
|
227
309
|
}
|
|
228
|
-
async
|
|
310
|
+
async getLockedResource(projectDir, type, name) {
|
|
229
311
|
const lock = await this.lockStore.load(projectDir);
|
|
230
312
|
if (!lock)
|
|
231
|
-
return
|
|
232
|
-
return lock.resources.
|
|
313
|
+
return undefined;
|
|
314
|
+
return lock.resources.find((item) => item.type === type && item.name === name);
|
|
233
315
|
}
|
|
234
316
|
buildSourceConfig(type, repo, repoId) {
|
|
235
317
|
if (type === "registry") {
|
|
@@ -258,26 +340,100 @@ export class ServiceFactory {
|
|
|
258
340
|
getStorePath(type, name, version) {
|
|
259
341
|
return path.join(this.paths.getStoreDir(), type, name, version);
|
|
260
342
|
}
|
|
261
|
-
getProjectResourcePath(projectDir, type, name) {
|
|
262
|
-
if (type === "rule")
|
|
263
|
-
return path.join(projectDir, ".cursor", "rules", name);
|
|
264
|
-
if (type === "command")
|
|
265
|
-
return path.join(projectDir, ".cursor", "commands", name);
|
|
266
|
-
return path.join(projectDir, ".cursor", "skills", name);
|
|
267
|
-
}
|
|
268
343
|
getProjectDevPath(projectDir, type, name) {
|
|
269
344
|
return path.join(projectDir, ".himan", "dev", type, name);
|
|
270
345
|
}
|
|
271
|
-
async
|
|
272
|
-
await fs.mkdir(path.dirname(
|
|
273
|
-
await fs.rm(
|
|
274
|
-
|
|
346
|
+
async materializeResource(sourcePath, targetPath, mode) {
|
|
347
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
348
|
+
await fs.rm(targetPath, { recursive: true, force: true });
|
|
349
|
+
if (mode === "copy") {
|
|
350
|
+
await fs.cp(sourcePath, targetPath, { recursive: true });
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
await fs.symlink(sourcePath, targetPath, "dir");
|
|
354
|
+
}
|
|
355
|
+
resolveInstallMode(mode) {
|
|
356
|
+
return mode === "copy" ? "copy" : "link";
|
|
357
|
+
}
|
|
358
|
+
async resolveInstalledResource(projectDir, type, name) {
|
|
359
|
+
const locked = await this.getLockedResource(projectDir, type, name);
|
|
360
|
+
const configuredAgents = await this.getConfiguredAgents(projectDir);
|
|
361
|
+
const lockedTargets = locked?.agents?.length
|
|
362
|
+
? normalizeAgents(locked.agents)
|
|
363
|
+
: configuredAgents ?? normalizeAgents();
|
|
364
|
+
const expectedFromLock = getProjectResourcePaths(projectDir, type, name, lockedTargets);
|
|
365
|
+
const existingFromLock = [];
|
|
366
|
+
for (const candidate of expectedFromLock) {
|
|
367
|
+
if (await this.exists(candidate))
|
|
368
|
+
existingFromLock.push(candidate);
|
|
369
|
+
}
|
|
370
|
+
if (existingFromLock.length > 0) {
|
|
371
|
+
const installedPath = await fs.realpath(existingFromLock[0]);
|
|
372
|
+
return {
|
|
373
|
+
installedPath,
|
|
374
|
+
agents: lockedTargets,
|
|
375
|
+
linkPaths: getProjectResourcePaths(projectDir, type, name, lockedTargets),
|
|
376
|
+
mode: this.resolveInstallMode(locked?.mode),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const allCandidates = getSupportedAgentNames().map((agent) => ({
|
|
380
|
+
agent,
|
|
381
|
+
path: getProjectResourcePaths(projectDir, type, name, [agent])[0],
|
|
382
|
+
}));
|
|
383
|
+
const existingCandidates = [];
|
|
384
|
+
for (const candidate of allCandidates) {
|
|
385
|
+
if (await this.exists(candidate.path))
|
|
386
|
+
existingCandidates.push(candidate);
|
|
387
|
+
}
|
|
388
|
+
if (existingCandidates.length === 0) {
|
|
389
|
+
throw new HimanError(errorCodes.INSTALL_NOT_FOUND, `Installed resource link not found for ${type}/${name}. Run install first.`);
|
|
390
|
+
}
|
|
391
|
+
const installedPath = await fs.realpath(existingCandidates[0].path);
|
|
392
|
+
const resourceMeta = await this.readResourceMetaFromDir(installedPath);
|
|
393
|
+
const agentsFromMeta = resourceMeta?.agents?.length
|
|
394
|
+
? normalizeAgents(resourceMeta.agents)
|
|
395
|
+
: undefined;
|
|
396
|
+
const existingAgents = normalizeAgents(existingCandidates.map((candidate) => candidate.agent));
|
|
397
|
+
const effectiveAgents = configuredAgents ?? agentsFromMeta ?? existingAgents;
|
|
398
|
+
return {
|
|
399
|
+
installedPath,
|
|
400
|
+
agents: effectiveAgents,
|
|
401
|
+
linkPaths: getProjectResourcePaths(projectDir, type, name, effectiveAgents),
|
|
402
|
+
mode: "link",
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
async resolveEffectiveAgents(projectDir, explicitAgents, fallbackAgents) {
|
|
406
|
+
if (explicitAgents?.length) {
|
|
407
|
+
return normalizeAgents(explicitAgents);
|
|
408
|
+
}
|
|
409
|
+
const configuredAgents = await this.getConfiguredAgents(projectDir);
|
|
410
|
+
if (configuredAgents?.length) {
|
|
411
|
+
return configuredAgents;
|
|
412
|
+
}
|
|
413
|
+
return normalizeAgents(fallbackAgents);
|
|
275
414
|
}
|
|
276
|
-
async
|
|
277
|
-
|
|
278
|
-
|
|
415
|
+
async getConfiguredAgents(projectDir) {
|
|
416
|
+
const [globalConfig, projectConfig] = await Promise.all([
|
|
417
|
+
this.stateStore.loadConfig(),
|
|
418
|
+
this.projectConfigStore.load(projectDir),
|
|
419
|
+
]);
|
|
420
|
+
if (projectConfig?.agents?.length) {
|
|
421
|
+
return normalizeAgents(projectConfig.agents);
|
|
279
422
|
}
|
|
280
|
-
|
|
423
|
+
if (globalConfig?.agents?.length) {
|
|
424
|
+
return normalizeAgents(globalConfig.agents);
|
|
425
|
+
}
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
async readResourceMetaFromDir(resourceDir) {
|
|
429
|
+
const yamlPath = path.join(resourceDir, "himan.yaml");
|
|
430
|
+
if (!(await this.exists(yamlPath)))
|
|
431
|
+
return null;
|
|
432
|
+
const raw = await fs.readFile(yamlPath, "utf8");
|
|
433
|
+
const parsed = YAML.parse(raw) ?? null;
|
|
434
|
+
if (!parsed)
|
|
435
|
+
return null;
|
|
436
|
+
return { agents: parsed.agents ?? parsed.targets };
|
|
281
437
|
}
|
|
282
438
|
async exists(targetPath) {
|
|
283
439
|
try {
|
|
@@ -301,7 +457,7 @@ export class ServiceFactory {
|
|
|
301
457
|
}
|
|
302
458
|
async getRepoResourceDir(type, name) {
|
|
303
459
|
const config = await this.stateStore.loadConfig();
|
|
304
|
-
if (!config) {
|
|
460
|
+
if (!config?.source) {
|
|
305
461
|
throw new HimanError(errorCodes.CONFIG_NOT_FOUND, "Source config not found. Please run `himan init <git_repo>` first.");
|
|
306
462
|
}
|
|
307
463
|
const sourceConfig = this.buildSourceConfig(config.source.type, config.source.repo, config.source.repoId);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class ProjectConfigStore {
|
|
4
|
+
getConfigPath(projectDir) {
|
|
5
|
+
return path.join(projectDir, ".himan", "config.json");
|
|
6
|
+
}
|
|
7
|
+
async load(projectDir) {
|
|
8
|
+
try {
|
|
9
|
+
const raw = await fs.readFile(this.getConfigPath(projectDir), "utf8");
|
|
10
|
+
const parsed = JSON.parse(raw);
|
|
11
|
+
if (parsed.version !== 1)
|
|
12
|
+
return null;
|
|
13
|
+
return parsed;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async saveAgents(projectDir, agents) {
|
|
20
|
+
const now = new Date().toISOString();
|
|
21
|
+
const existing = await this.load(projectDir);
|
|
22
|
+
const config = {
|
|
23
|
+
version: 1,
|
|
24
|
+
...existing,
|
|
25
|
+
agents,
|
|
26
|
+
updatedAt: now,
|
|
27
|
+
};
|
|
28
|
+
await fs.mkdir(path.dirname(this.getConfigPath(projectDir)), { recursive: true });
|
|
29
|
+
await fs.writeFile(this.getConfigPath(projectDir), JSON.stringify(config, null, 2), "utf8");
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
async clearAgents(projectDir) {
|
|
33
|
+
const existing = await this.load(projectDir);
|
|
34
|
+
if (!existing)
|
|
35
|
+
return;
|
|
36
|
+
const config = {
|
|
37
|
+
...existing,
|
|
38
|
+
agents: undefined,
|
|
39
|
+
updatedAt: new Date().toISOString(),
|
|
40
|
+
};
|
|
41
|
+
await fs.writeFile(this.getConfigPath(projectDir), JSON.stringify(config, null, 2), "utf8");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -40,6 +40,8 @@ export class ProjectLockStore {
|
|
|
40
40
|
const found = lock.resources.find((item) => item.type === resource.type && item.name === resource.name);
|
|
41
41
|
if (found) {
|
|
42
42
|
found.version = resource.version;
|
|
43
|
+
found.agents = resource.agents;
|
|
44
|
+
found.mode = resource.mode;
|
|
43
45
|
found.updatedAt = now;
|
|
44
46
|
}
|
|
45
47
|
else {
|
|
@@ -47,6 +49,8 @@ export class ProjectLockStore {
|
|
|
47
49
|
type: resource.type,
|
|
48
50
|
name: resource.name,
|
|
49
51
|
version: resource.version,
|
|
52
|
+
agents: resource.agents,
|
|
53
|
+
mode: resource.mode,
|
|
50
54
|
updatedAt: now,
|
|
51
55
|
});
|
|
52
56
|
}
|
|
@@ -35,12 +35,15 @@ export class StateStore {
|
|
|
35
35
|
default: defaultName,
|
|
36
36
|
items: input.sources.items,
|
|
37
37
|
},
|
|
38
|
+
agents: input.agents,
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
const fallback = input.source;
|
|
42
43
|
if (!fallback) {
|
|
43
|
-
|
|
44
|
+
return {
|
|
45
|
+
agents: input.agents,
|
|
46
|
+
};
|
|
44
47
|
}
|
|
45
48
|
return {
|
|
46
49
|
source: fallback,
|
|
@@ -50,6 +53,7 @@ export class StateStore {
|
|
|
50
53
|
default: fallback,
|
|
51
54
|
},
|
|
52
55
|
},
|
|
56
|
+
agents: input.agents,
|
|
53
57
|
};
|
|
54
58
|
}
|
|
55
59
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const AGENT_CONFIGS = [
|
|
3
|
+
{
|
|
4
|
+
name: "cursor",
|
|
5
|
+
aliases: ["cursor"],
|
|
6
|
+
baseDir: ".cursor",
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: "claude-code",
|
|
10
|
+
aliases: ["claude", "claude-code", "claude code", "claude_code"],
|
|
11
|
+
baseDir: ".claude",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "codex",
|
|
15
|
+
aliases: ["codex"],
|
|
16
|
+
baseDir: ".agents",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "openclaw",
|
|
20
|
+
aliases: ["openclaw", "open-claw", "open claw"],
|
|
21
|
+
baseDir: ".openclaw",
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
const DEFAULT_AGENT = "cursor";
|
|
25
|
+
const AGENT_ALIASES = buildAgentAliases();
|
|
26
|
+
function getTypeDir(type) {
|
|
27
|
+
if (type === "rule")
|
|
28
|
+
return "rules";
|
|
29
|
+
if (type === "command")
|
|
30
|
+
return "commands";
|
|
31
|
+
return "skills";
|
|
32
|
+
}
|
|
33
|
+
function getAgentBaseDir(agent) {
|
|
34
|
+
return getAgentConfig(agent).baseDir;
|
|
35
|
+
}
|
|
36
|
+
export function normalizeAgents(agents) {
|
|
37
|
+
const normalized = (agents ?? [])
|
|
38
|
+
.map((item) => normalizeAgent(item))
|
|
39
|
+
.filter((item) => Boolean(item));
|
|
40
|
+
if (normalized.length === 0) {
|
|
41
|
+
return [DEFAULT_AGENT];
|
|
42
|
+
}
|
|
43
|
+
return [...new Set(normalized)];
|
|
44
|
+
}
|
|
45
|
+
export function normalizeAgent(input) {
|
|
46
|
+
return AGENT_ALIASES.get(input.trim().toLowerCase());
|
|
47
|
+
}
|
|
48
|
+
export function getProjectResourcePaths(projectDir, type, name, agents) {
|
|
49
|
+
const typeDir = getTypeDir(type);
|
|
50
|
+
return normalizeAgents(agents).map((agent) => path.join(projectDir, getAgentBaseDir(agent), typeDir, name));
|
|
51
|
+
}
|
|
52
|
+
export function getSupportedAgentNames() {
|
|
53
|
+
return AGENT_CONFIGS.map((config) => config.name);
|
|
54
|
+
}
|
|
55
|
+
function getAgentConfig(agent) {
|
|
56
|
+
const config = AGENT_CONFIGS.find((item) => item.name === agent);
|
|
57
|
+
if (!config) {
|
|
58
|
+
throw new Error(`Unsupported agent config: ${agent}`);
|
|
59
|
+
}
|
|
60
|
+
return config;
|
|
61
|
+
}
|
|
62
|
+
function buildAgentAliases() {
|
|
63
|
+
const aliases = new Map();
|
|
64
|
+
for (const config of AGENT_CONFIGS) {
|
|
65
|
+
aliases.set(config.name, config.name);
|
|
66
|
+
for (const alias of config.aliases) {
|
|
67
|
+
aliases.set(alias, config.name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return aliases;
|
|
71
|
+
}
|
|
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
5
|
-
const pkgPath = join(moduleDir, "
|
|
5
|
+
const pkgPath = join(moduleDir, "../../package.json");
|
|
6
6
|
const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
7
7
|
if (typeof parsed.version !== "string" || parsed.version.length === 0) {
|
|
8
8
|
throw new Error("Invalid or missing version in package.json");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hi-man/himan",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Prompt and agent asset management CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,11 +23,14 @@
|
|
|
23
23
|
"registry": "https://registry.npmjs.org/"
|
|
24
24
|
},
|
|
25
25
|
"bin": {
|
|
26
|
-
"himan": "dist/
|
|
26
|
+
"himan": "dist/bin/himan.js",
|
|
27
|
+
"himan-source": "dist/bin/himan-source.js",
|
|
28
|
+
"himan-resource": "dist/bin/himan-resource.js",
|
|
29
|
+
"himan-project": "dist/bin/himan-project.js"
|
|
27
30
|
},
|
|
28
31
|
"scripts": {
|
|
29
32
|
"build": "tsc -p tsconfig.json",
|
|
30
|
-
"dev": "tsx src/
|
|
33
|
+
"dev": "tsx src/bin/himan.ts",
|
|
31
34
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
35
|
"test": "vitest run",
|
|
33
36
|
"test:watch": "vitest",
|