@soleri/cli 1.2.0 → 1.4.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 +30 -1
- package/dist/commands/create.js +6 -3
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/governance.d.ts +2 -0
- package/dist/commands/governance.js +97 -0
- package/dist/commands/governance.js.map +1 -0
- package/dist/commands/hooks.js +40 -11
- package/dist/commands/hooks.js.map +1 -1
- package/dist/hook-packs/a11y/hookify.focus-ring-required.local.md +1 -0
- package/dist/hook-packs/a11y/hookify.semantic-html.local.md +1 -0
- package/dist/hook-packs/a11y/hookify.ux-touch-targets.local.md +1 -0
- package/dist/hook-packs/a11y/manifest.json +1 -0
- package/dist/hook-packs/clean-commits/hookify.no-ai-attribution.local.md +1 -0
- package/dist/hook-packs/clean-commits/manifest.json +1 -0
- package/dist/hook-packs/css-discipline/hookify.no-important.local.md +1 -0
- package/dist/hook-packs/css-discipline/hookify.no-inline-styles.local.md +1 -0
- package/dist/hook-packs/css-discipline/manifest.json +1 -0
- package/dist/hook-packs/full/manifest.json +1 -0
- package/dist/hook-packs/installer.d.ts +18 -5
- package/dist/hook-packs/installer.js +29 -10
- package/dist/hook-packs/installer.js.map +1 -1
- package/dist/hook-packs/installer.ts +42 -10
- package/dist/hook-packs/registry.d.ts +6 -2
- package/dist/hook-packs/registry.js +46 -13
- package/dist/hook-packs/registry.js.map +1 -1
- package/dist/hook-packs/registry.ts +49 -13
- package/dist/hook-packs/typescript-safety/hookify.no-any-types.local.md +1 -0
- package/dist/hook-packs/typescript-safety/hookify.no-console-log.local.md +1 -0
- package/dist/hook-packs/typescript-safety/manifest.json +1 -0
- package/dist/main.js +3 -1
- package/dist/main.js.map +1 -1
- package/dist/utils/checks.d.ts +1 -0
- package/dist/utils/checks.js +17 -0
- package/dist/utils/checks.js.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/create.test.ts +110 -1
- package/src/commands/create.ts +5 -3
- package/src/commands/governance.ts +114 -0
- package/src/commands/hooks.ts +54 -11
- package/src/hook-packs/a11y/hookify.focus-ring-required.local.md +1 -0
- package/src/hook-packs/a11y/hookify.semantic-html.local.md +1 -0
- package/src/hook-packs/a11y/hookify.ux-touch-targets.local.md +1 -0
- package/src/hook-packs/a11y/manifest.json +1 -0
- package/src/hook-packs/clean-commits/hookify.no-ai-attribution.local.md +1 -0
- package/src/hook-packs/clean-commits/manifest.json +1 -0
- package/src/hook-packs/css-discipline/hookify.no-important.local.md +1 -0
- package/src/hook-packs/css-discipline/hookify.no-inline-styles.local.md +1 -0
- package/src/hook-packs/css-discipline/manifest.json +1 -0
- package/src/hook-packs/full/manifest.json +1 -0
- package/src/hook-packs/installer.ts +42 -10
- package/src/hook-packs/registry.ts +49 -13
- package/src/hook-packs/typescript-safety/hookify.no-any-types.local.md +1 -0
- package/src/hook-packs/typescript-safety/hookify.no-console-log.local.md +1 -0
- package/src/hook-packs/typescript-safety/manifest.json +1 -0
- package/src/main.ts +3 -1
- package/src/utils/checks.ts +18 -0
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hook pack installer — copies hookify files to ~/.claude/
|
|
2
|
+
* Hook pack installer — copies hookify files to ~/.claude/ (global) or project .claude/ (local).
|
|
3
3
|
*/
|
|
4
|
-
import { existsSync, copyFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { existsSync, copyFileSync, unlinkSync, mkdirSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { homedir } from 'node:os';
|
|
7
7
|
import { getPack } from './registry.js';
|
|
8
8
|
|
|
9
|
+
/** Resolve the target .claude/ directory. */
|
|
10
|
+
function resolveClaudeDir(projectDir?: string): string {
|
|
11
|
+
if (projectDir) return join(projectDir, '.claude');
|
|
12
|
+
return join(homedir(), '.claude');
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
/**
|
|
10
16
|
* Resolve all hookify file paths for a pack, handling composed packs.
|
|
11
17
|
* Returns a map of hook name → source file path.
|
|
@@ -38,16 +44,19 @@ function resolveHookFiles(packName: string): Map<string, string> {
|
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
/**
|
|
41
|
-
* Install a hook pack
|
|
47
|
+
* Install a hook pack to ~/.claude/ (default) or project .claude/ (--project).
|
|
42
48
|
* Skips files that already exist (idempotent).
|
|
43
49
|
*/
|
|
44
|
-
export function installPack(
|
|
50
|
+
export function installPack(
|
|
51
|
+
packName: string,
|
|
52
|
+
options?: { projectDir?: string },
|
|
53
|
+
): { installed: string[]; skipped: string[] } {
|
|
45
54
|
const pack = getPack(packName);
|
|
46
55
|
if (!pack) {
|
|
47
56
|
throw new Error(`Unknown hook pack: "${packName}"`);
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
const claudeDir =
|
|
59
|
+
const claudeDir = resolveClaudeDir(options?.projectDir);
|
|
51
60
|
mkdirSync(claudeDir, { recursive: true });
|
|
52
61
|
|
|
53
62
|
const hookFiles = resolveHookFiles(packName);
|
|
@@ -68,15 +77,18 @@ export function installPack(packName: string): { installed: string[]; skipped: s
|
|
|
68
77
|
}
|
|
69
78
|
|
|
70
79
|
/**
|
|
71
|
-
* Remove a hook pack's files from
|
|
80
|
+
* Remove a hook pack's files from target directory.
|
|
72
81
|
*/
|
|
73
|
-
export function removePack(
|
|
82
|
+
export function removePack(
|
|
83
|
+
packName: string,
|
|
84
|
+
options?: { projectDir?: string },
|
|
85
|
+
): { removed: string[] } {
|
|
74
86
|
const pack = getPack(packName);
|
|
75
87
|
if (!pack) {
|
|
76
88
|
throw new Error(`Unknown hook pack: "${packName}"`);
|
|
77
89
|
}
|
|
78
90
|
|
|
79
|
-
const claudeDir =
|
|
91
|
+
const claudeDir = resolveClaudeDir(options?.projectDir);
|
|
80
92
|
const removed: string[] = [];
|
|
81
93
|
|
|
82
94
|
for (const hook of pack.manifest.hooks) {
|
|
@@ -94,11 +106,14 @@ export function removePack(packName: string): { removed: string[] } {
|
|
|
94
106
|
* Check if a pack is installed.
|
|
95
107
|
* Returns true (all hooks present), false (none present), or 'partial'.
|
|
96
108
|
*/
|
|
97
|
-
export function isPackInstalled(
|
|
109
|
+
export function isPackInstalled(
|
|
110
|
+
packName: string,
|
|
111
|
+
options?: { projectDir?: string },
|
|
112
|
+
): boolean | 'partial' {
|
|
98
113
|
const pack = getPack(packName);
|
|
99
114
|
if (!pack) return false;
|
|
100
115
|
|
|
101
|
-
const claudeDir =
|
|
116
|
+
const claudeDir = resolveClaudeDir(options?.projectDir);
|
|
102
117
|
let present = 0;
|
|
103
118
|
|
|
104
119
|
for (const hook of pack.manifest.hooks) {
|
|
@@ -111,3 +126,20 @@ export function isPackInstalled(packName: string): boolean | 'partial' {
|
|
|
111
126
|
if (present === pack.manifest.hooks.length) return true;
|
|
112
127
|
return 'partial';
|
|
113
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get the installed version of a hook from its file header.
|
|
132
|
+
* Returns null if no version found or file doesn't exist.
|
|
133
|
+
*/
|
|
134
|
+
export function getInstalledHookVersion(
|
|
135
|
+
hook: string,
|
|
136
|
+
options?: { projectDir?: string },
|
|
137
|
+
): string | null {
|
|
138
|
+
const claudeDir = resolveClaudeDir(options?.projectDir);
|
|
139
|
+
const filePath = join(claudeDir, `hookify.${hook}.local.md`);
|
|
140
|
+
if (!existsSync(filePath)) return null;
|
|
141
|
+
|
|
142
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
143
|
+
const match = content.match(/^# Version: (.+)$/m);
|
|
144
|
+
return match ? match[1] : null;
|
|
145
|
+
}
|
|
@@ -3,13 +3,17 @@ export interface HookPackManifest {
|
|
|
3
3
|
description: string;
|
|
4
4
|
hooks: string[];
|
|
5
5
|
composedFrom?: string[];
|
|
6
|
+
version?: string;
|
|
7
|
+
/** Whether this pack is built-in or user-defined */
|
|
8
|
+
source?: 'built-in' | 'local';
|
|
6
9
|
}
|
|
7
10
|
/**
|
|
8
|
-
* List all available built-in
|
|
11
|
+
* List all available hook packs (built-in + local custom).
|
|
12
|
+
* Local packs in .soleri/hook-packs/ override built-in packs with the same name.
|
|
9
13
|
*/
|
|
10
14
|
export declare function listPacks(): HookPackManifest[];
|
|
11
15
|
/**
|
|
12
|
-
* Get a specific pack by name.
|
|
16
|
+
* Get a specific pack by name. Local packs take precedence.
|
|
13
17
|
*/
|
|
14
18
|
export declare function getPack(name: string): {
|
|
15
19
|
manifest: HookPackManifest;
|
|
@@ -8,14 +8,17 @@ import { homedir } from 'node:os';
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = dirname(__filename);
|
|
10
10
|
/** Root directory containing all built-in hook packs. */
|
|
11
|
-
function
|
|
11
|
+
function getBuiltinRoot() {
|
|
12
12
|
return __dirname;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
/** Local custom packs directory. */
|
|
15
|
+
function getLocalRoot() {
|
|
16
|
+
return join(process.cwd(), '.soleri', 'hook-packs');
|
|
17
|
+
}
|
|
18
|
+
/** Scan a directory for pack manifests. */
|
|
19
|
+
function scanPacksDir(root, source) {
|
|
20
|
+
if (!existsSync(root))
|
|
21
|
+
return [];
|
|
19
22
|
const entries = readdirSync(root, { withFileTypes: true });
|
|
20
23
|
const packs = [];
|
|
21
24
|
for (const entry of entries) {
|
|
@@ -26,6 +29,7 @@ export function listPacks() {
|
|
|
26
29
|
continue;
|
|
27
30
|
try {
|
|
28
31
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
32
|
+
manifest.source = source;
|
|
29
33
|
packs.push(manifest);
|
|
30
34
|
}
|
|
31
35
|
catch {
|
|
@@ -35,17 +39,46 @@ export function listPacks() {
|
|
|
35
39
|
return packs;
|
|
36
40
|
}
|
|
37
41
|
/**
|
|
38
|
-
*
|
|
42
|
+
* List all available hook packs (built-in + local custom).
|
|
43
|
+
* Local packs in .soleri/hook-packs/ override built-in packs with the same name.
|
|
44
|
+
*/
|
|
45
|
+
export function listPacks() {
|
|
46
|
+
const builtIn = scanPacksDir(getBuiltinRoot(), 'built-in');
|
|
47
|
+
const local = scanPacksDir(getLocalRoot(), 'local');
|
|
48
|
+
// Local packs override built-in packs with same name
|
|
49
|
+
const byName = new Map();
|
|
50
|
+
for (const pack of builtIn)
|
|
51
|
+
byName.set(pack.name, pack);
|
|
52
|
+
for (const pack of local)
|
|
53
|
+
byName.set(pack.name, pack);
|
|
54
|
+
return Array.from(byName.values());
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get a specific pack by name. Local packs take precedence.
|
|
39
58
|
*/
|
|
40
59
|
export function getPack(name) {
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
60
|
+
// Check local first
|
|
61
|
+
const localDir = join(getLocalRoot(), name);
|
|
62
|
+
const localManifest = join(localDir, 'manifest.json');
|
|
63
|
+
if (existsSync(localManifest)) {
|
|
64
|
+
try {
|
|
65
|
+
const manifest = JSON.parse(readFileSync(localManifest, 'utf-8'));
|
|
66
|
+
manifest.source = 'local';
|
|
67
|
+
return { manifest, dir: localDir };
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Fall through to built-in
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Then built-in
|
|
74
|
+
const builtinDir = join(getBuiltinRoot(), name);
|
|
75
|
+
const builtinManifest = join(builtinDir, 'manifest.json');
|
|
76
|
+
if (!existsSync(builtinManifest))
|
|
45
77
|
return null;
|
|
46
78
|
try {
|
|
47
|
-
const manifest = JSON.parse(readFileSync(
|
|
48
|
-
|
|
79
|
+
const manifest = JSON.parse(readFileSync(builtinManifest, 'utf-8'));
|
|
80
|
+
manifest.source = 'built-in';
|
|
81
|
+
return { manifest, dir: builtinDir };
|
|
49
82
|
}
|
|
50
83
|
catch {
|
|
51
84
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/hook-packs/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/hook-packs/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,21 +11,27 @@ export interface HookPackManifest {
|
|
|
11
11
|
description: string;
|
|
12
12
|
hooks: string[];
|
|
13
13
|
composedFrom?: string[];
|
|
14
|
+
version?: string;
|
|
15
|
+
/** Whether this pack is built-in or user-defined */
|
|
16
|
+
source?: 'built-in' | 'local';
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
20
|
const __dirname = dirname(__filename);
|
|
18
21
|
|
|
19
22
|
/** Root directory containing all built-in hook packs. */
|
|
20
|
-
function
|
|
23
|
+
function getBuiltinRoot(): string {
|
|
21
24
|
return __dirname;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/** Local custom packs directory. */
|
|
28
|
+
function getLocalRoot(): string {
|
|
29
|
+
return join(process.cwd(), '.soleri', 'hook-packs');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Scan a directory for pack manifests. */
|
|
33
|
+
function scanPacksDir(root: string, source: 'built-in' | 'local'): HookPackManifest[] {
|
|
34
|
+
if (!existsSync(root)) return [];
|
|
29
35
|
const entries = readdirSync(root, { withFileTypes: true });
|
|
30
36
|
const packs: HookPackManifest[] = [];
|
|
31
37
|
|
|
@@ -36,6 +42,7 @@ export function listPacks(): HookPackManifest[] {
|
|
|
36
42
|
|
|
37
43
|
try {
|
|
38
44
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as HookPackManifest;
|
|
45
|
+
manifest.source = source;
|
|
39
46
|
packs.push(manifest);
|
|
40
47
|
} catch {
|
|
41
48
|
// Skip malformed manifests
|
|
@@ -46,18 +53,47 @@ export function listPacks(): HookPackManifest[] {
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
/**
|
|
49
|
-
*
|
|
56
|
+
* List all available hook packs (built-in + local custom).
|
|
57
|
+
* Local packs in .soleri/hook-packs/ override built-in packs with the same name.
|
|
58
|
+
*/
|
|
59
|
+
export function listPacks(): HookPackManifest[] {
|
|
60
|
+
const builtIn = scanPacksDir(getBuiltinRoot(), 'built-in');
|
|
61
|
+
const local = scanPacksDir(getLocalRoot(), 'local');
|
|
62
|
+
|
|
63
|
+
// Local packs override built-in packs with same name
|
|
64
|
+
const byName = new Map<string, HookPackManifest>();
|
|
65
|
+
for (const pack of builtIn) byName.set(pack.name, pack);
|
|
66
|
+
for (const pack of local) byName.set(pack.name, pack);
|
|
67
|
+
|
|
68
|
+
return Array.from(byName.values());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get a specific pack by name. Local packs take precedence.
|
|
50
73
|
*/
|
|
51
74
|
export function getPack(name: string): { manifest: HookPackManifest; dir: string } | null {
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
75
|
+
// Check local first
|
|
76
|
+
const localDir = join(getLocalRoot(), name);
|
|
77
|
+
const localManifest = join(localDir, 'manifest.json');
|
|
78
|
+
if (existsSync(localManifest)) {
|
|
79
|
+
try {
|
|
80
|
+
const manifest = JSON.parse(readFileSync(localManifest, 'utf-8')) as HookPackManifest;
|
|
81
|
+
manifest.source = 'local';
|
|
82
|
+
return { manifest, dir: localDir };
|
|
83
|
+
} catch {
|
|
84
|
+
// Fall through to built-in
|
|
85
|
+
}
|
|
86
|
+
}
|
|
55
87
|
|
|
56
|
-
|
|
88
|
+
// Then built-in
|
|
89
|
+
const builtinDir = join(getBuiltinRoot(), name);
|
|
90
|
+
const builtinManifest = join(builtinDir, 'manifest.json');
|
|
91
|
+
if (!existsSync(builtinManifest)) return null;
|
|
57
92
|
|
|
58
93
|
try {
|
|
59
|
-
const manifest = JSON.parse(readFileSync(
|
|
60
|
-
|
|
94
|
+
const manifest = JSON.parse(readFileSync(builtinManifest, 'utf-8')) as HookPackManifest;
|
|
95
|
+
manifest.source = 'built-in';
|
|
96
|
+
return { manifest, dir: builtinDir };
|
|
61
97
|
} catch {
|
|
62
98
|
return null;
|
|
63
99
|
}
|
package/dist/main.js
CHANGED
|
@@ -7,11 +7,12 @@ import { registerInstallKnowledge } from './commands/install-knowledge.js';
|
|
|
7
7
|
import { registerDev } from './commands/dev.js';
|
|
8
8
|
import { registerDoctor } from './commands/doctor.js';
|
|
9
9
|
import { registerHooks } from './commands/hooks.js';
|
|
10
|
+
import { registerGovernance } from './commands/governance.js';
|
|
10
11
|
const program = new Command();
|
|
11
12
|
program
|
|
12
13
|
.name('soleri')
|
|
13
14
|
.description('Developer CLI for creating and managing Soleri AI agents')
|
|
14
|
-
.version('1.
|
|
15
|
+
.version('1.4.0');
|
|
15
16
|
registerCreate(program);
|
|
16
17
|
registerList(program);
|
|
17
18
|
registerAddDomain(program);
|
|
@@ -19,5 +20,6 @@ registerInstallKnowledge(program);
|
|
|
19
20
|
registerDev(program);
|
|
20
21
|
registerDoctor(program);
|
|
21
22
|
registerHooks(program);
|
|
23
|
+
registerGovernance(program);
|
|
22
24
|
program.parse();
|
|
23
25
|
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,0DAA0D,CAAC;KACvE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAC3B,wBAAwB,CAAC,OAAO,CAAC,CAAC;AAClC,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,aAAa,CAAC,OAAO,CAAC,CAAC;AACvB,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAE5B,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/dist/utils/checks.d.ts
CHANGED
|
@@ -8,5 +8,6 @@ export declare function checkNpm(): CheckResult;
|
|
|
8
8
|
export declare function checkAgentProject(dir?: string): CheckResult;
|
|
9
9
|
export declare function checkAgentBuild(dir?: string): CheckResult;
|
|
10
10
|
export declare function checkNodeModules(dir?: string): CheckResult;
|
|
11
|
+
export declare function checkHookPacks(): CheckResult;
|
|
11
12
|
export declare function runAllChecks(dir?: string): CheckResult[];
|
|
12
13
|
export {};
|
package/dist/utils/checks.js
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { execFileSync } from 'node:child_process';
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import { detectAgent } from './agent-context.js';
|
|
9
|
+
import { getInstalledPacks } from '../hook-packs/registry.js';
|
|
9
10
|
export function checkNodeVersion() {
|
|
10
11
|
const [major] = process.versions.node.split('.').map(Number);
|
|
11
12
|
if (major >= 18) {
|
|
@@ -123,6 +124,21 @@ function checkCognee() {
|
|
|
123
124
|
};
|
|
124
125
|
}
|
|
125
126
|
}
|
|
127
|
+
export function checkHookPacks() {
|
|
128
|
+
const installed = getInstalledPacks();
|
|
129
|
+
if (installed.length === 0) {
|
|
130
|
+
return {
|
|
131
|
+
status: 'warn',
|
|
132
|
+
label: 'Hook packs',
|
|
133
|
+
detail: 'none installed — run soleri hooks list-packs',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
status: 'pass',
|
|
138
|
+
label: 'Hook packs',
|
|
139
|
+
detail: installed.join(', '),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
126
142
|
export function runAllChecks(dir) {
|
|
127
143
|
return [
|
|
128
144
|
checkNodeVersion(),
|
|
@@ -132,6 +148,7 @@ export function runAllChecks(dir) {
|
|
|
132
148
|
checkNodeModules(dir),
|
|
133
149
|
checkAgentBuild(dir),
|
|
134
150
|
checkMcpRegistration(dir),
|
|
151
|
+
checkHookPacks(),
|
|
135
152
|
checkCognee(),
|
|
136
153
|
];
|
|
137
154
|
}
|
package/dist/utils/checks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/utils/checks.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/utils/checks.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soleri/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Developer CLI for creating and managing Soleri AI agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@clack/prompts": "^1.0.0",
|
|
40
|
+
"@soleri/core": "^2.0.0",
|
|
40
41
|
"@soleri/forge": "^5.0.0",
|
|
41
42
|
"commander": "^13.0.0"
|
|
42
43
|
},
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { previewScaffold, scaffold } from '@soleri/forge/lib';
|
|
6
6
|
import type { AgentConfig } from '@soleri/forge/lib';
|
|
7
|
+
import { installPack } from '../hook-packs/installer.js';
|
|
7
8
|
|
|
8
9
|
describe('create command', () => {
|
|
9
10
|
let tempDir: string;
|
|
@@ -91,4 +92,112 @@ describe('create command', () => {
|
|
|
91
92
|
expect(raw.id).toBe('test-agent');
|
|
92
93
|
expect(raw.domains).toEqual(['testing', 'quality']);
|
|
93
94
|
});
|
|
95
|
+
|
|
96
|
+
// ─── Hook pack integration tests ──────────────────────────────
|
|
97
|
+
|
|
98
|
+
it('should create .claude/ directory when hookPacks specified', () => {
|
|
99
|
+
const configWithHooks: AgentConfig = {
|
|
100
|
+
...testConfig,
|
|
101
|
+
hookPacks: ['typescript-safety'],
|
|
102
|
+
};
|
|
103
|
+
const result = scaffold(configWithHooks);
|
|
104
|
+
|
|
105
|
+
expect(result.success).toBe(true);
|
|
106
|
+
expect(existsSync(join(tempDir, 'test-agent', '.claude'))).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should install hookify files to agent .claude/ via installPack', () => {
|
|
110
|
+
const configWithHooks: AgentConfig = {
|
|
111
|
+
...testConfig,
|
|
112
|
+
hookPacks: ['typescript-safety'],
|
|
113
|
+
};
|
|
114
|
+
const result = scaffold(configWithHooks);
|
|
115
|
+
expect(result.success).toBe(true);
|
|
116
|
+
|
|
117
|
+
// Simulate what create.ts does: install packs into agent dir
|
|
118
|
+
const { installed } = installPack('typescript-safety', { projectDir: result.agentDir });
|
|
119
|
+
expect(installed.length).toBeGreaterThan(0);
|
|
120
|
+
|
|
121
|
+
// Verify hookify files exist in agent .claude/
|
|
122
|
+
const claudeDir = join(result.agentDir, '.claude');
|
|
123
|
+
const hookFiles = readdirSync(claudeDir).filter(
|
|
124
|
+
(f) => f.startsWith('hookify.') && f.endsWith('.local.md'),
|
|
125
|
+
);
|
|
126
|
+
expect(hookFiles.length).toBeGreaterThan(0);
|
|
127
|
+
expect(hookFiles.some((f) => f.includes('no-any-types'))).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should not create .claude/ when hookPacks is empty or undefined', () => {
|
|
131
|
+
const result = scaffold(testConfig);
|
|
132
|
+
|
|
133
|
+
expect(result.success).toBe(true);
|
|
134
|
+
expect(existsSync(join(tempDir, 'test-agent', '.claude'))).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should include hook packs in preview when hookPacks specified', () => {
|
|
138
|
+
const configWithHooks: AgentConfig = {
|
|
139
|
+
...testConfig,
|
|
140
|
+
hookPacks: ['typescript-safety'],
|
|
141
|
+
};
|
|
142
|
+
const preview = previewScaffold(configWithHooks);
|
|
143
|
+
|
|
144
|
+
const hookEntry = preview.files.find((f) => f.path === '.claude/');
|
|
145
|
+
expect(hookEntry).toBeDefined();
|
|
146
|
+
expect(hookEntry!.description).toContain('typescript-safety');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should include Hook Packs section in CLAUDE.md when hookPacks specified', () => {
|
|
150
|
+
const configWithHooks: AgentConfig = {
|
|
151
|
+
...testConfig,
|
|
152
|
+
hookPacks: ['typescript-safety'],
|
|
153
|
+
};
|
|
154
|
+
scaffold(configWithHooks);
|
|
155
|
+
|
|
156
|
+
const claudeMd = readFileSync(
|
|
157
|
+
join(tempDir, 'test-agent', 'src', 'activation', 'claude-md-content.ts'),
|
|
158
|
+
'utf-8',
|
|
159
|
+
);
|
|
160
|
+
expect(claudeMd).toContain('Hook Packs');
|
|
161
|
+
expect(claudeMd).toContain('no-any-types');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should not include Hook Packs section in CLAUDE.md when hookPacks undefined', () => {
|
|
165
|
+
scaffold(testConfig);
|
|
166
|
+
|
|
167
|
+
const claudeMd = readFileSync(
|
|
168
|
+
join(tempDir, 'test-agent', 'src', 'activation', 'claude-md-content.ts'),
|
|
169
|
+
'utf-8',
|
|
170
|
+
);
|
|
171
|
+
expect(claudeMd).not.toContain('Hook Packs');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should include hook copy logic in setup.sh when hookPacks specified', () => {
|
|
175
|
+
const configWithHooks: AgentConfig = {
|
|
176
|
+
...testConfig,
|
|
177
|
+
hookPacks: ['typescript-safety'],
|
|
178
|
+
};
|
|
179
|
+
scaffold(configWithHooks);
|
|
180
|
+
|
|
181
|
+
const setupSh = readFileSync(join(tempDir, 'test-agent', 'scripts', 'setup.sh'), 'utf-8');
|
|
182
|
+
expect(setupSh).toContain('Installing hook packs');
|
|
183
|
+
expect(setupSh).toContain('hookify.');
|
|
184
|
+
expect(setupSh).toContain('GLOBAL_CLAUDE_DIR');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should not include hook copy logic in setup.sh when hookPacks undefined', () => {
|
|
188
|
+
scaffold(testConfig);
|
|
189
|
+
|
|
190
|
+
const setupSh = readFileSync(join(tempDir, 'test-agent', 'scripts', 'setup.sh'), 'utf-8');
|
|
191
|
+
expect(setupSh).not.toContain('Installing hook packs');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should mention hook packs in scaffold summary', () => {
|
|
195
|
+
const configWithHooks: AgentConfig = {
|
|
196
|
+
...testConfig,
|
|
197
|
+
hookPacks: ['typescript-safety', 'a11y'],
|
|
198
|
+
};
|
|
199
|
+
const result = scaffold(configWithHooks);
|
|
200
|
+
|
|
201
|
+
expect(result.summary).toContain('2 hook pack(s) bundled in .claude/');
|
|
202
|
+
});
|
|
94
203
|
});
|
package/src/commands/create.ts
CHANGED
|
@@ -40,9 +40,11 @@ export function registerCreate(program: Command): void {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// Hook packs
|
|
43
|
+
// Hook packs — from config file or interactive prompt
|
|
44
44
|
let selectedPacks: string[] = [];
|
|
45
|
-
if (
|
|
45
|
+
if (config.hookPacks && config.hookPacks.length > 0) {
|
|
46
|
+
selectedPacks = config.hookPacks;
|
|
47
|
+
} else if (!opts?.config) {
|
|
46
48
|
const packs = listPacks();
|
|
47
49
|
const packChoices = packs.map((pk) => ({
|
|
48
50
|
value: pk.name,
|
|
@@ -91,7 +93,7 @@ export function registerCreate(program: Command): void {
|
|
|
91
93
|
// Install selected hook packs
|
|
92
94
|
if (selectedPacks.length > 0) {
|
|
93
95
|
for (const packName of selectedPacks) {
|
|
94
|
-
const { installed } = installPack(packName);
|
|
96
|
+
const { installed } = installPack(packName, { projectDir: result.agentDir });
|
|
95
97
|
if (installed.length > 0) {
|
|
96
98
|
p.log.success(`Hook pack "${packName}" installed (${installed.length} hooks)`);
|
|
97
99
|
} else {
|