@uluops/setup 0.4.0 → 0.6.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 +21 -0
- package/README.md +67 -50
- package/assets/auto-tracker-save.mjs +142 -0
- package/assets/{agents → claude-code/agents}/api-contract-validator-agent.md +9 -228
- package/assets/{agents → claude-code/agents}/aristotle-analyst-agent.md +51 -4
- package/assets/{agents → claude-code/agents}/aristotle-explorer-agent.md +6 -2
- package/assets/{agents → claude-code/agents}/aristotle-forecaster-agent.md +15 -230
- package/assets/{agents → claude-code/agents}/aristotle-validator-agent.md +12 -252
- package/assets/{agents → claude-code/agents}/assumption-excavator-agent.md +21 -247
- package/assets/{agents → claude-code/agents}/code-auditor-agent.md +12 -255
- package/assets/{agents → claude-code/agents}/code-optimizer-agent.md +15 -236
- package/assets/{agents → claude-code/agents}/code-validator-agent.md +31 -300
- package/assets/claude-code/agents/docs-validator-agent.md +472 -0
- package/assets/{agents → claude-code/agents}/frontend-validator-agent.md +15 -258
- package/assets/{agents → claude-code/agents}/mcp-validator-agent.md +8 -252
- package/assets/{agents → claude-code/agents}/pre-implementation-architect-agent.md +8 -224
- package/assets/{agents → claude-code/agents}/prompt-engineer-agent.md +57 -290
- package/assets/{agents → claude-code/agents}/prompt-pattern-analyzer-agent.md +10 -225
- package/assets/{agents → claude-code/agents}/prompt-quality-validator-agent.md +11 -249
- package/assets/{agents → claude-code/agents}/public-interface-validator-agent.md +15 -268
- package/assets/claude-code/agents/release-readiness-agent.md +495 -0
- package/assets/{agents → claude-code/agents}/security-analyst-agent.md +236 -480
- package/assets/{agents → claude-code/agents}/test-architect-agent.md +16 -259
- package/assets/{agents → claude-code/agents}/type-safety-validator-agent.md +23 -266
- package/assets/{agents → claude-code/agents}/workflow-synthesis-agent.md +23 -226
- package/assets/{commands → claude-code/commands}/agents/anxiety-reader.md +12 -15
- package/assets/{commands → claude-code/commands}/agents/api-contract.md +156 -136
- package/assets/{commands → claude-code/commands}/agents/architect.md +156 -136
- package/assets/claude-code/commands/agents/aristotle-analyst.md +157 -0
- package/assets/claude-code/commands/agents/aristotle-explorer.md +157 -0
- package/assets/claude-code/commands/agents/aristotle-forecaster.md +157 -0
- package/assets/claude-code/commands/agents/aristotle-validator.md +157 -0
- package/assets/{commands → claude-code/commands}/agents/assumption-excavator.md +49 -7
- package/assets/{commands → claude-code/commands}/agents/audit.md +156 -137
- package/assets/{commands → claude-code/commands}/agents/docs-validate.md +156 -134
- package/assets/{commands → claude-code/commands}/agents/frontend.md +156 -136
- package/assets/{commands → claude-code/commands}/agents/mcp-validate.md +156 -137
- package/assets/{commands → claude-code/commands}/agents/optimize.md +156 -134
- package/assets/{commands → claude-code/commands}/agents/pattern-analyzer.md +150 -127
- package/assets/{commands → claude-code/commands}/agents/prompt-quality.md +155 -135
- package/assets/claude-code/commands/agents/prompt-validate.md +155 -0
- package/assets/{commands → claude-code/commands}/agents/public-interface.md +156 -135
- package/assets/{commands → claude-code/commands}/agents/release.md +156 -136
- package/assets/{commands → claude-code/commands}/agents/security.md +156 -138
- package/assets/{commands → claude-code/commands}/agents/test-review.md +156 -137
- package/assets/{commands → claude-code/commands}/agents/type-safety.md +156 -136
- package/assets/{commands/agents/code-validate.md → claude-code/commands/agents/validate.md} +156 -135
- package/assets/claude-code/commands/agents/workflow-synthesis.md +157 -0
- package/assets/{commands → claude-code/commands}/pipelines/aristotle.md +8 -8
- package/assets/{commands → claude-code/commands}/pipelines/ship.md +8 -8
- package/assets/claude-code/commands/workflows/post-implementation.md +60 -0
- package/assets/claude-code/commands/workflows/pre-implementation.md +46 -0
- package/assets/{commands → claude-code/commands}/workflows/prompt-audit.md +2 -2
- package/assets/codex/agents/anxiety-reader-agent.toml +462 -0
- package/assets/codex/agents/api-contract-validator-agent.toml +738 -0
- package/assets/codex/agents/aristotle-analyst-agent.toml +750 -0
- package/assets/codex/agents/aristotle-explorer-agent.toml +155 -0
- package/assets/codex/agents/aristotle-forecaster-agent.toml +449 -0
- package/assets/codex/agents/aristotle-validator-agent.toml +424 -0
- package/assets/codex/agents/assumption-excavator-agent.toml +1126 -0
- package/assets/codex/agents/code-auditor-agent.toml +815 -0
- package/assets/codex/agents/code-optimizer-agent.toml +652 -0
- package/assets/codex/agents/code-validator-agent.toml +573 -0
- package/assets/codex/agents/docs-validator-agent.toml +468 -0
- package/assets/codex/agents/frontend-validator-agent.toml +598 -0
- package/assets/codex/agents/mcp-validator-agent.toml +580 -0
- package/assets/codex/agents/pre-implementation-architect-agent.toml +817 -0
- package/assets/codex/agents/prompt-engineer-agent.toml +922 -0
- package/assets/codex/agents/prompt-pattern-analyzer-agent.toml +689 -0
- package/assets/codex/agents/prompt-quality-validator-agent.toml +777 -0
- package/assets/codex/agents/public-interface-validator-agent.toml +695 -0
- package/assets/codex/agents/release-readiness-agent.toml +491 -0
- package/assets/codex/agents/security-analyst-agent.toml +847 -0
- package/assets/codex/agents/test-architect-agent.toml +615 -0
- package/assets/codex/agents/type-safety-validator-agent.toml +686 -0
- package/assets/codex/agents/workflow-synthesis-agent.toml +631 -0
- package/assets/gemini-cli/agents/anxiety-reader-agent.md +470 -0
- package/assets/gemini-cli/agents/api-contract-validator-agent.md +747 -0
- package/assets/gemini-cli/agents/aristotle-analyst-agent.md +758 -0
- package/assets/gemini-cli/agents/aristotle-explorer-agent.md +163 -0
- package/assets/gemini-cli/agents/aristotle-forecaster-agent.md +457 -0
- package/assets/gemini-cli/agents/aristotle-validator-agent.md +432 -0
- package/assets/gemini-cli/agents/assumption-excavator-agent.md +1134 -0
- package/assets/gemini-cli/agents/code-auditor-agent.md +827 -0
- package/assets/gemini-cli/agents/code-optimizer-agent.md +661 -0
- package/assets/gemini-cli/agents/code-validator-agent.md +582 -0
- package/assets/gemini-cli/agents/docs-validator-agent.md +477 -0
- package/assets/gemini-cli/agents/frontend-validator-agent.md +610 -0
- package/assets/gemini-cli/agents/mcp-validator-agent.md +589 -0
- package/assets/gemini-cli/agents/pre-implementation-architect-agent.md +826 -0
- package/assets/gemini-cli/agents/prompt-engineer-agent.md +931 -0
- package/assets/gemini-cli/agents/prompt-pattern-analyzer-agent.md +698 -0
- package/assets/gemini-cli/agents/prompt-quality-validator-agent.md +786 -0
- package/assets/gemini-cli/agents/public-interface-validator-agent.md +707 -0
- package/assets/gemini-cli/agents/release-readiness-agent.md +500 -0
- package/assets/gemini-cli/agents/security-analyst-agent.md +859 -0
- package/assets/gemini-cli/agents/test-architect-agent.md +624 -0
- package/assets/gemini-cli/agents/type-safety-validator-agent.md +695 -0
- package/assets/gemini-cli/agents/workflow-synthesis-agent.md +639 -0
- package/assets/gemini-cli/commands/agents/anxiety-reader.toml +155 -0
- package/assets/gemini-cli/commands/agents/api-contract.toml +154 -0
- package/assets/gemini-cli/commands/agents/architect.toml +154 -0
- package/assets/gemini-cli/commands/agents/aristotle-analyst.toml +155 -0
- package/assets/gemini-cli/commands/agents/aristotle-explorer.toml +155 -0
- package/assets/gemini-cli/commands/agents/aristotle-forecaster.toml +155 -0
- package/assets/gemini-cli/commands/agents/aristotle-validator.toml +155 -0
- package/assets/gemini-cli/commands/agents/assumption-excavator.toml +155 -0
- package/assets/gemini-cli/commands/agents/audit.toml +154 -0
- package/assets/gemini-cli/commands/agents/docs-validate.toml +154 -0
- package/assets/gemini-cli/commands/agents/frontend.toml +154 -0
- package/assets/gemini-cli/commands/agents/mcp-validate.toml +154 -0
- package/assets/gemini-cli/commands/agents/optimize.toml +154 -0
- package/assets/gemini-cli/commands/agents/pattern-analyzer.toml +148 -0
- package/assets/gemini-cli/commands/agents/prompt-quality.toml +153 -0
- package/assets/gemini-cli/commands/agents/prompt-validate.toml +153 -0
- package/assets/gemini-cli/commands/agents/public-interface.toml +154 -0
- package/assets/gemini-cli/commands/agents/release.toml +154 -0
- package/assets/gemini-cli/commands/agents/security.toml +154 -0
- package/assets/gemini-cli/commands/agents/test-review.toml +154 -0
- package/assets/gemini-cli/commands/agents/type-safety.toml +154 -0
- package/assets/gemini-cli/commands/agents/validate.toml +154 -0
- package/assets/gemini-cli/commands/agents/workflow-synthesis.toml +155 -0
- package/assets/gemini-cli/commands/pipelines/aristotle.toml +139 -0
- package/assets/gemini-cli/commands/pipelines/ship.toml +184 -0
- package/assets/gemini-cli/commands/workflows/post-implementation.toml +56 -0
- package/assets/gemini-cli/commands/workflows/pre-implementation.toml +42 -0
- package/assets/gemini-cli/commands/workflows/prompt-audit.toml +40 -0
- package/assets/opencode/agents/anxiety-reader-agent.md +472 -0
- package/assets/opencode/agents/api-contract-validator-agent.md +749 -0
- package/assets/opencode/agents/aristotle-analyst-agent.md +760 -0
- package/assets/opencode/agents/aristotle-explorer-agent.md +164 -0
- package/assets/opencode/agents/aristotle-forecaster-agent.md +459 -0
- package/assets/opencode/agents/aristotle-validator-agent.md +434 -0
- package/assets/opencode/agents/assumption-excavator-agent.md +1136 -0
- package/assets/opencode/agents/code-auditor-agent.md +826 -0
- package/assets/opencode/agents/code-optimizer-agent.md +663 -0
- package/assets/opencode/agents/code-validator-agent.md +584 -0
- package/assets/opencode/agents/docs-validator-agent.md +479 -0
- package/assets/opencode/agents/frontend-validator-agent.md +609 -0
- package/assets/opencode/agents/mcp-validator-agent.md +591 -0
- package/assets/opencode/agents/pre-implementation-architect-agent.md +828 -0
- package/assets/opencode/agents/prompt-engineer-agent.md +933 -0
- package/assets/opencode/agents/prompt-pattern-analyzer-agent.md +700 -0
- package/assets/opencode/agents/prompt-quality-validator-agent.md +788 -0
- package/assets/opencode/agents/public-interface-validator-agent.md +706 -0
- package/assets/opencode/agents/release-readiness-agent.md +502 -0
- package/assets/opencode/agents/security-analyst-agent.md +858 -0
- package/assets/opencode/agents/test-architect-agent.md +626 -0
- package/assets/opencode/agents/type-safety-validator-agent.md +697 -0
- package/assets/opencode/agents/workflow-synthesis-agent.md +641 -0
- package/dist/cli.js +12 -414
- package/dist/commands/helpers.d.ts +73 -0
- package/dist/commands/helpers.js +274 -0
- package/dist/commands/setup.d.ts +13 -0
- package/dist/commands/setup.js +93 -0
- package/dist/commands/uninstall.d.ts +3 -0
- package/dist/commands/uninstall.js +126 -0
- package/dist/commands/verify.d.ts +1 -0
- package/dist/commands/verify.js +28 -0
- package/dist/harnesses/claude-code.d.ts +1 -1
- package/dist/harnesses/claude-code.js +3 -1
- package/dist/harnesses/codex.js +6 -5
- package/dist/harnesses/gemini-cli.d.ts +4 -8
- package/dist/harnesses/gemini-cli.js +47 -21
- package/dist/harnesses/index.d.ts +10 -1
- package/dist/harnesses/index.js +11 -2
- package/dist/harnesses/opencode.d.ts +1 -1
- package/dist/harnesses/opencode.js +15 -6
- package/dist/harnesses/types.d.ts +19 -0
- package/dist/harnesses/types.js +2 -0
- package/dist/lib/asset-catalog.js +2 -2
- package/dist/lib/config-merger.d.ts +2 -1
- package/dist/lib/config-merger.js +12 -4
- package/dist/lib/file-ops.d.ts +5 -0
- package/dist/lib/file-ops.js +18 -3
- package/dist/lib/hash.d.ts +1 -1
- package/dist/lib/hash.js +2 -2
- package/dist/lib/manifest.d.ts +30 -1
- package/dist/lib/manifest.js +5 -7
- package/dist/lib/paths.d.ts +16 -1
- package/dist/lib/paths.js +31 -3
- package/dist/lib/settings-merger.d.ts +24 -9
- package/dist/lib/settings-merger.js +57 -22
- package/dist/lib/version.d.ts +2 -0
- package/dist/lib/version.js +10 -0
- package/dist/steps/agents.d.ts +1 -2
- package/dist/steps/agents.js +7 -18
- package/dist/steps/cli.d.ts +53 -0
- package/dist/steps/cli.js +90 -0
- package/dist/steps/commands.d.ts +1 -1
- package/dist/steps/commands.js +20 -71
- package/dist/steps/detect.js +4 -0
- package/dist/steps/mcp.js +7 -15
- package/dist/steps/metrics.d.ts +12 -0
- package/dist/steps/metrics.js +52 -22
- package/dist/steps/shell.js +11 -1
- package/dist/steps/signup.d.ts +2 -2
- package/dist/steps/signup.js +9 -12
- package/dist/steps/verify.js +47 -8
- package/package.json +12 -11
- package/assets/agents/docs-validator-agent.md +0 -490
- package/assets/agents/release-readiness-agent.md +0 -482
- package/assets/commands/agents/aristotle-analyst.md +0 -116
- package/assets/commands/agents/aristotle-explorer.md +0 -93
- package/assets/commands/agents/aristotle-forecaster.md +0 -115
- package/assets/commands/agents/aristotle-validator.md +0 -115
- package/assets/commands/agents/prompt-validate.md +0 -136
- package/assets/commands/agents/workflow-synthesis.md +0 -102
- package/assets/commands/workflows/post-implementation.md +0 -577
- package/assets/commands/workflows/pre-implementation.md +0 -670
- /package/assets/{agents → claude-code/agents}/anxiety-reader-agent.md +0 -0
|
@@ -6,30 +6,57 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { readFile } from "node:fs/promises";
|
|
8
8
|
import { atomicWrite } from "./atomic-write.js";
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Substring used purely as an *ownership sentinel* — present in every hook
|
|
11
|
+
* command we install, so we can identify our own entries in `settings.json`
|
|
12
|
+
* without false-positives on the user's hooks.
|
|
13
|
+
*
|
|
14
|
+
* This is intentionally NOT a path constant. The path where the hook lives
|
|
15
|
+
* is derived from each profile's `paths.toolsDir`; if those move (a harness
|
|
16
|
+
* restructure, a custom toolsDir, a future per-instance layout), the
|
|
17
|
+
* signature must remain stable so existing user settings.json entries
|
|
18
|
+
* keep being recognized as UluOps-managed.
|
|
19
|
+
*
|
|
20
|
+
* The current value `agent-metrics/dist/hook.js` is the suffix of every
|
|
21
|
+
* hook command we emit — discriminating enough to avoid colliding with
|
|
22
|
+
* user-named tools while surviving moves of the parent directory.
|
|
23
|
+
*/
|
|
24
|
+
const HOOK_OWNERSHIP_SIGNATURE = "agent-metrics/dist/hook.js";
|
|
25
|
+
/**
|
|
26
|
+
* Supported hook event types in Claude Code's settings.json schema.
|
|
27
|
+
*
|
|
28
|
+
* This set is a snapshot of the harness's vocabulary. When Claude Code
|
|
29
|
+
* adds, renames, or removes hook types, this set rots — the `probeHookSupport`
|
|
30
|
+
* warning will fire on legitimate user configs and (worse) train users to
|
|
31
|
+
* ignore it. The snapshot test in `settings-merger.test.ts` exists to make
|
|
32
|
+
* any change to the set visible in PR review so the warning logic can be
|
|
33
|
+
* re-evaluated.
|
|
34
|
+
*
|
|
35
|
+
* Exported for that test only — runtime callers should use `probeHookSupport`.
|
|
36
|
+
*/
|
|
37
|
+
export const CLAUDE_HOOK_TYPES = new Set([
|
|
13
38
|
"SubagentStop",
|
|
14
39
|
"PreToolUse",
|
|
15
40
|
"PostToolUse",
|
|
16
41
|
"Notification",
|
|
17
42
|
"Stop",
|
|
18
43
|
]);
|
|
44
|
+
/** Default Claude Code hook event used when no override is configured. */
|
|
45
|
+
export const DEFAULT_CLAUDE_HOOK_TYPE = "SubagentStop";
|
|
19
46
|
/** Configurable hook type via env var. Falls back to SubagentStop. */
|
|
20
|
-
function
|
|
21
|
-
return process.env["ULUOPS_HOOK_TYPE"] ??
|
|
47
|
+
function getDefaultHookEventType() {
|
|
48
|
+
return process.env["ULUOPS_HOOK_TYPE"] ?? DEFAULT_CLAUDE_HOOK_TYPE;
|
|
22
49
|
}
|
|
23
50
|
/** Check whether the configured hook event type is in the known supported set. Returns the resolved hook type and a warning if unsupported. */
|
|
24
|
-
export function probeHookSupport() {
|
|
25
|
-
const hookType =
|
|
26
|
-
if (
|
|
51
|
+
export function probeHookSupport(hookTypeOverride) {
|
|
52
|
+
const hookType = hookTypeOverride ?? getDefaultHookEventType();
|
|
53
|
+
if (CLAUDE_HOOK_TYPES.has(hookType) || hookType === "AfterTool") {
|
|
27
54
|
return { hookType, supported: true };
|
|
28
55
|
}
|
|
29
56
|
return {
|
|
30
57
|
hookType,
|
|
31
58
|
supported: false,
|
|
32
|
-
warning: `Hook type "${hookType}" is not in the known supported set {${[...
|
|
59
|
+
warning: `Hook type "${hookType}" is not in the known supported set {${[...CLAUDE_HOOK_TYPES].join(", ")}, AfterTool}. Metrics may silently fail if this hook type does not exist in the harness.`,
|
|
33
60
|
};
|
|
34
61
|
}
|
|
35
62
|
/**
|
|
@@ -44,7 +71,12 @@ export async function readSettings(path) {
|
|
|
44
71
|
catch {
|
|
45
72
|
return {}; // File doesn't exist — fresh config
|
|
46
73
|
}
|
|
47
|
-
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(raw);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new Error(`Failed to parse settings at ${path} — file contains invalid JSON`);
|
|
79
|
+
}
|
|
48
80
|
}
|
|
49
81
|
/**
|
|
50
82
|
* Write settings back to file with stable formatting.
|
|
@@ -53,14 +85,14 @@ export async function writeSettings(path, settings) {
|
|
|
53
85
|
await atomicWrite(path, JSON.stringify(settings, null, 2) + "\n");
|
|
54
86
|
}
|
|
55
87
|
/**
|
|
56
|
-
* Merge the UluOps
|
|
88
|
+
* Merge the UluOps hook into settings, preserving all other
|
|
57
89
|
* hooks and settings. If a UluOps hook already exists, it is replaced.
|
|
58
90
|
*/
|
|
59
|
-
export function mergeUluopsHook(settings, hookCommand) {
|
|
60
|
-
const hookType =
|
|
91
|
+
export function mergeUluopsHook(settings, hookCommand, hookTypeOverride, matcher) {
|
|
92
|
+
const hookType = hookTypeOverride ?? getDefaultHookEventType();
|
|
61
93
|
const hooks = settings.hooks ?? {};
|
|
62
94
|
const existing = hooks[hookType] ?? [];
|
|
63
|
-
const filtered = existing.filter((m) => !m.hooks.some((h) => h.command.includes(
|
|
95
|
+
const filtered = existing.filter((m) => !m.hooks.some((h) => h.command.includes(HOOK_OWNERSHIP_SIGNATURE)));
|
|
64
96
|
const uluopsHook = {
|
|
65
97
|
hooks: [
|
|
66
98
|
{
|
|
@@ -70,6 +102,9 @@ export function mergeUluopsHook(settings, hookCommand) {
|
|
|
70
102
|
},
|
|
71
103
|
],
|
|
72
104
|
};
|
|
105
|
+
if (matcher) {
|
|
106
|
+
uluopsHook.matcher = matcher;
|
|
107
|
+
}
|
|
73
108
|
return {
|
|
74
109
|
...settings,
|
|
75
110
|
hooks: {
|
|
@@ -79,18 +114,18 @@ export function mergeUluopsHook(settings, hookCommand) {
|
|
|
79
114
|
};
|
|
80
115
|
}
|
|
81
116
|
/**
|
|
82
|
-
* Remove UluOps hook entries from settings. If
|
|
117
|
+
* Remove UluOps hook entries from settings. If a hook type becomes empty,
|
|
83
118
|
* the key is removed. If hooks becomes empty, the key is removed.
|
|
84
119
|
*/
|
|
85
|
-
export function removeUluopsHook(settings) {
|
|
86
|
-
const hookType =
|
|
120
|
+
export function removeUluopsHook(settings, hookTypeOverride) {
|
|
121
|
+
const hookType = hookTypeOverride ?? getDefaultHookEventType();
|
|
87
122
|
const hooks = settings.hooks;
|
|
88
123
|
if (!hooks)
|
|
89
124
|
return settings;
|
|
90
125
|
const hookEntries = hooks[hookType];
|
|
91
126
|
if (!hookEntries)
|
|
92
127
|
return settings;
|
|
93
|
-
const filtered = hookEntries.filter((m) => !m.hooks.some((h) => h.command.includes(
|
|
128
|
+
const filtered = hookEntries.filter((m) => !m.hooks.some((h) => h.command.includes(HOOK_OWNERSHIP_SIGNATURE)));
|
|
94
129
|
const updatedHooks = { ...hooks };
|
|
95
130
|
if (filtered.length === 0) {
|
|
96
131
|
delete updatedHooks[hookType];
|
|
@@ -110,10 +145,10 @@ export function removeUluopsHook(settings) {
|
|
|
110
145
|
/**
|
|
111
146
|
* Check if a UluOps hook is configured in settings.
|
|
112
147
|
*/
|
|
113
|
-
export function hasUluopsHook(settings) {
|
|
114
|
-
const hookType =
|
|
148
|
+
export function hasUluopsHook(settings, hookTypeOverride) {
|
|
149
|
+
const hookType = hookTypeOverride ?? getDefaultHookEventType();
|
|
115
150
|
const hookEntries = settings.hooks?.[hookType];
|
|
116
151
|
if (!hookEntries)
|
|
117
152
|
return false;
|
|
118
|
-
return hookEntries.some((m) => m.hooks.some((h) => h.command.includes(
|
|
153
|
+
return hookEntries.some((m) => m.hooks.some((h) => h.command.includes(HOOK_OWNERSHIP_SIGNATURE)));
|
|
119
154
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
/** Read the package version from package.json. */
|
|
6
|
+
export async function getVersion() {
|
|
7
|
+
const pkgPath = join(__dirname, "..", "..", "package.json");
|
|
8
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
9
|
+
return pkg.version;
|
|
10
|
+
}
|
package/dist/steps/agents.d.ts
CHANGED
|
@@ -5,8 +5,7 @@ export interface AgentsResult {
|
|
|
5
5
|
removed: number;
|
|
6
6
|
files: string[];
|
|
7
7
|
}
|
|
8
|
-
/** Copy agent
|
|
9
|
-
* transforming frontmatter to match the target harness format. */
|
|
8
|
+
/** Copy pre-rendered agent definitions from harness-specific assets to the target directory. */
|
|
10
9
|
export declare function installAgents(profile: HarnessProfile, localDefs: boolean, dryRun: boolean, existingManifestAgents?: string[]): Promise<AgentsResult>;
|
|
11
10
|
/** Remove previously installed agent files by name. */
|
|
12
11
|
export declare function uninstallAgents(files: string[], defsPath: string): Promise<number>;
|
package/dist/steps/agents.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir, mkdir, unlink } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { ASSETS_DIR, findProjectRoot } from "../lib/paths.js";
|
|
4
|
-
import { copyIfChanged,
|
|
5
|
-
|
|
6
|
-
/** Source directory for agent assets (single set, Claude Code format). */
|
|
7
|
-
const AGENTS_SRC = join(ASSETS_DIR, "agents");
|
|
8
|
-
/** Copy agent definition files from assets to the harness agents directory,
|
|
9
|
-
* transforming frontmatter to match the target harness format. */
|
|
4
|
+
import { copyIfChanged, unlinkFiles } from "../lib/file-ops.js";
|
|
5
|
+
/** Copy pre-rendered agent definitions from harness-specific assets to the target directory. */
|
|
10
6
|
export async function installAgents(profile, localDefs, dryRun, existingManifestAgents) {
|
|
7
|
+
const srcDir = join(ASSETS_DIR, profile.name, "agents");
|
|
11
8
|
const destDir = localDefs
|
|
12
9
|
? join(await findProjectRoot(), "uluops", "agents")
|
|
13
10
|
: profile.paths.agentsDir;
|
|
14
11
|
if (!dryRun) {
|
|
15
12
|
await mkdir(destDir, { recursive: true });
|
|
16
13
|
}
|
|
17
|
-
const
|
|
14
|
+
const ext = profile.agentExtension;
|
|
18
15
|
let files;
|
|
19
16
|
try {
|
|
20
|
-
files = (await readdir(
|
|
17
|
+
files = (await readdir(srcDir)).filter((f) => f.endsWith(ext));
|
|
21
18
|
}
|
|
22
19
|
catch {
|
|
23
20
|
return { copied: 0, skipped: 0, removed: 0, files: [] };
|
|
@@ -25,15 +22,7 @@ export async function installAgents(profile, localDefs, dryRun, existingManifest
|
|
|
25
22
|
let copied = 0;
|
|
26
23
|
let skipped = 0;
|
|
27
24
|
for (const file of files) {
|
|
28
|
-
|
|
29
|
-
if (needsTransform) {
|
|
30
|
-
const markdown = await readFile(join(AGENTS_SRC, file), "utf-8");
|
|
31
|
-
const transformed = transformAgent(markdown, profile.name);
|
|
32
|
-
result = await writeIfChanged(join(destDir, file), transformed, dryRun);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
result = await copyIfChanged(join(AGENTS_SRC, file), join(destDir, file), dryRun);
|
|
36
|
-
}
|
|
25
|
+
const result = await copyIfChanged(join(srcDir, file), join(destDir, file), dryRun);
|
|
37
26
|
if (result === "copied")
|
|
38
27
|
copied++;
|
|
39
28
|
else
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export declare const CLI_PACKAGE = "@uluops/cli";
|
|
2
|
+
export declare const CLI_BIN = "ulu";
|
|
3
|
+
export interface CliExecutor {
|
|
4
|
+
/** Returns the installed CLI version, or null if `ulu` is not on PATH or fails to run. */
|
|
5
|
+
detect: () => string | null;
|
|
6
|
+
/** Best-effort `npm install -g`. Returns ok + captured error for surface display. */
|
|
7
|
+
install: () => {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
};
|
|
11
|
+
/** Best-effort `npm uninstall -g`. Returns ok + captured error. */
|
|
12
|
+
uninstall: () => {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Default executor — shells out to `ulu` and `npm`. */
|
|
18
|
+
export declare const defaultExecutor: CliExecutor;
|
|
19
|
+
export interface CliInstallResult {
|
|
20
|
+
/** `ulu` is on PATH after this step, regardless of how it got there. */
|
|
21
|
+
installed: boolean;
|
|
22
|
+
/** Version string from `ulu --version`, if detectable. */
|
|
23
|
+
version: string | null;
|
|
24
|
+
/** True when `ulu` was already on PATH before we did anything. */
|
|
25
|
+
alreadyPresent: boolean;
|
|
26
|
+
/** Set when our `npm install -g` attempt failed; caller decides how to surface. */
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Install `@uluops/cli` globally if not already present.
|
|
31
|
+
*
|
|
32
|
+
* Designed to never abort the parent setup flow:
|
|
33
|
+
* - If `ulu` is already on PATH, returns `{ installed: true, alreadyPresent: true }` without touching npm.
|
|
34
|
+
* - If `npm install -g` fails (permissions, network, nvm prefix surprise), returns
|
|
35
|
+
* `{ installed: false, error }` so the caller can warn-and-continue.
|
|
36
|
+
* - In dryRun mode, no executor calls happen.
|
|
37
|
+
*/
|
|
38
|
+
export declare function installCli(opts: {
|
|
39
|
+
dryRun: boolean;
|
|
40
|
+
executor?: CliExecutor;
|
|
41
|
+
}): Promise<CliInstallResult>;
|
|
42
|
+
export interface CliUninstallResult {
|
|
43
|
+
removed: boolean;
|
|
44
|
+
error?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Uninstall `@uluops/cli` globally. Best-effort: if the package isn't there,
|
|
48
|
+
* npm exits non-zero on some platforms — we treat that as success.
|
|
49
|
+
*/
|
|
50
|
+
export declare function uninstallCli(opts: {
|
|
51
|
+
dryRun: boolean;
|
|
52
|
+
executor?: CliExecutor;
|
|
53
|
+
}): Promise<CliUninstallResult>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
export const CLI_PACKAGE = "@uluops/cli";
|
|
3
|
+
export const CLI_BIN = "ulu";
|
|
4
|
+
/** Default executor — shells out to `ulu` and `npm`. */
|
|
5
|
+
export const defaultExecutor = {
|
|
6
|
+
detect: () => {
|
|
7
|
+
const r = spawnSync(CLI_BIN, ["--version"], {
|
|
8
|
+
encoding: "utf-8",
|
|
9
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
10
|
+
});
|
|
11
|
+
if (r.status !== 0 || !r.stdout)
|
|
12
|
+
return null;
|
|
13
|
+
return r.stdout.trim() || null;
|
|
14
|
+
},
|
|
15
|
+
install: () => {
|
|
16
|
+
const r = spawnSync("npm", ["install", "-g", CLI_PACKAGE], {
|
|
17
|
+
encoding: "utf-8",
|
|
18
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
19
|
+
});
|
|
20
|
+
if (r.status === 0)
|
|
21
|
+
return { ok: true };
|
|
22
|
+
const stderr = (r.stderr ?? "").toString().trim();
|
|
23
|
+
const stdout = (r.stdout ?? "").toString().trim();
|
|
24
|
+
return { ok: false, error: stderr || stdout || `exit ${r.status}` };
|
|
25
|
+
},
|
|
26
|
+
uninstall: () => {
|
|
27
|
+
const r = spawnSync("npm", ["uninstall", "-g", CLI_PACKAGE], {
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
30
|
+
});
|
|
31
|
+
if (r.status === 0)
|
|
32
|
+
return { ok: true };
|
|
33
|
+
const stderr = (r.stderr ?? "").toString().trim();
|
|
34
|
+
const stdout = (r.stdout ?? "").toString().trim();
|
|
35
|
+
return { ok: false, error: stderr || stdout || `exit ${r.status}` };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Install `@uluops/cli` globally if not already present.
|
|
40
|
+
*
|
|
41
|
+
* Designed to never abort the parent setup flow:
|
|
42
|
+
* - If `ulu` is already on PATH, returns `{ installed: true, alreadyPresent: true }` without touching npm.
|
|
43
|
+
* - If `npm install -g` fails (permissions, network, nvm prefix surprise), returns
|
|
44
|
+
* `{ installed: false, error }` so the caller can warn-and-continue.
|
|
45
|
+
* - In dryRun mode, no executor calls happen.
|
|
46
|
+
*/
|
|
47
|
+
export async function installCli(opts) {
|
|
48
|
+
const executor = opts.executor ?? defaultExecutor;
|
|
49
|
+
const existing = executor.detect();
|
|
50
|
+
if (existing !== null) {
|
|
51
|
+
return { installed: true, version: existing, alreadyPresent: true };
|
|
52
|
+
}
|
|
53
|
+
if (opts.dryRun) {
|
|
54
|
+
return { installed: false, version: null, alreadyPresent: false };
|
|
55
|
+
}
|
|
56
|
+
const res = executor.install();
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
return {
|
|
59
|
+
installed: false,
|
|
60
|
+
version: null,
|
|
61
|
+
alreadyPresent: false,
|
|
62
|
+
error: res.error,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const after = executor.detect();
|
|
66
|
+
return {
|
|
67
|
+
installed: after !== null,
|
|
68
|
+
version: after,
|
|
69
|
+
alreadyPresent: false,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Uninstall `@uluops/cli` globally. Best-effort: if the package isn't there,
|
|
74
|
+
* npm exits non-zero on some platforms — we treat that as success.
|
|
75
|
+
*/
|
|
76
|
+
export async function uninstallCli(opts) {
|
|
77
|
+
const executor = opts.executor ?? defaultExecutor;
|
|
78
|
+
if (opts.dryRun)
|
|
79
|
+
return { removed: true };
|
|
80
|
+
const before = executor.detect();
|
|
81
|
+
if (before === null)
|
|
82
|
+
return { removed: true };
|
|
83
|
+
const res = executor.uninstall();
|
|
84
|
+
if (res.ok)
|
|
85
|
+
return { removed: true };
|
|
86
|
+
const after = executor.detect();
|
|
87
|
+
if (after === null)
|
|
88
|
+
return { removed: true };
|
|
89
|
+
return { removed: false, error: res.error };
|
|
90
|
+
}
|
package/dist/steps/commands.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface CommandsResult {
|
|
|
8
8
|
files: string[];
|
|
9
9
|
skippedReason?: string;
|
|
10
10
|
}
|
|
11
|
-
/** Install
|
|
11
|
+
/** Install pre-rendered command files from harness-specific assets. */
|
|
12
12
|
export declare function installCommands(profile: HarnessProfile, localDefs: boolean, dryRun: boolean, existingManifestCommands?: string[]): Promise<CommandsResult>;
|
|
13
13
|
/** Remove previously installed command files by relative path. Returns count of successfully removed files. */
|
|
14
14
|
export declare function uninstallCommands(files: string[], defsPath: string): Promise<number>;
|
package/dist/steps/commands.js
CHANGED
|
@@ -1,57 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir, mkdir, unlink } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { ASSETS_DIR, findProjectRoot } from "../lib/paths.js";
|
|
4
|
-
import { copyIfChanged,
|
|
4
|
+
import { copyIfChanged, unlinkFiles } from "../lib/file-ops.js";
|
|
5
5
|
const SUBDIRS = ["agents", "workflows", "pipelines"];
|
|
6
|
-
/**
|
|
7
|
-
const SUPPORTED_HARNESSES = new Set(["claude-code", "gemini-cli"]);
|
|
8
|
-
// --- Gemini CLI transform ---
|
|
9
|
-
/**
|
|
10
|
-
* Strip YAML frontmatter from rendered markdown, returning just the body.
|
|
11
|
-
*/
|
|
12
|
-
function stripFrontmatter(markdown) {
|
|
13
|
-
const first = markdown.indexOf("---");
|
|
14
|
-
if (first === -1)
|
|
15
|
-
return markdown;
|
|
16
|
-
const second = markdown.indexOf("---", first + 3);
|
|
17
|
-
if (second === -1)
|
|
18
|
-
return markdown;
|
|
19
|
-
return markdown.substring(second + 3);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Escape a string for use in a TOML basic string (double-quoted).
|
|
23
|
-
*/
|
|
24
|
-
function escapeToml(value) {
|
|
25
|
-
return value
|
|
26
|
-
.replace(/\\/g, "\\\\")
|
|
27
|
-
.replace(/"/g, '\\"')
|
|
28
|
-
.replace(/\n/g, "\\n")
|
|
29
|
-
.replace(/\r/g, "\\r")
|
|
30
|
-
.replace(/\t/g, "\\t");
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Transform a Claude Code markdown command to a Gemini CLI TOML command.
|
|
34
|
-
* Strips frontmatter, substitutes $ARGUMENTS → {{args}}, wraps in TOML.
|
|
35
|
-
*/
|
|
36
|
-
function transformToGeminiToml(markdown, description) {
|
|
37
|
-
const body = stripFrontmatter(markdown)
|
|
38
|
-
.replace(/\$ARGUMENTS/g, "{{args}}")
|
|
39
|
-
.trim();
|
|
40
|
-
// Escape """ in body for TOML multi-line strings
|
|
41
|
-
const escaped = body.replace(/"""/g, '""\\"');
|
|
42
|
-
return `description = "${escapeToml(description)}"\nprompt = """\n${escaped}\n"""\n`;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Extract the description from a markdown command's YAML frontmatter.
|
|
46
|
-
*/
|
|
47
|
-
function extractDescription(markdown) {
|
|
48
|
-
const match = markdown.match(/^description:\s*(.+)$/m);
|
|
49
|
-
return match?.[1]?.trim() ?? "";
|
|
50
|
-
}
|
|
51
|
-
// --- Install ---
|
|
52
|
-
/** Install slash-command files, transforming to target format as needed. */
|
|
6
|
+
/** Install pre-rendered command files from harness-specific assets. */
|
|
53
7
|
export async function installCommands(profile, localDefs, dryRun, existingManifestCommands) {
|
|
54
|
-
|
|
8
|
+
const srcBase = join(ASSETS_DIR, profile.name, "commands");
|
|
9
|
+
const destBase = localDefs
|
|
10
|
+
? join(await findProjectRoot(), "uluops", "commands")
|
|
11
|
+
: profile.paths.commandsDir;
|
|
12
|
+
// If no commands directory exists for this harness, skip gracefully
|
|
13
|
+
let hasSrcDir;
|
|
14
|
+
try {
|
|
15
|
+
await readdir(srcBase);
|
|
16
|
+
hasSrcDir = true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
hasSrcDir = false;
|
|
20
|
+
}
|
|
21
|
+
if (!hasSrcDir) {
|
|
55
22
|
return {
|
|
56
23
|
agentCommands: 0,
|
|
57
24
|
workflowCommands: 0,
|
|
@@ -62,11 +29,6 @@ export async function installCommands(profile, localDefs, dryRun, existingManife
|
|
|
62
29
|
skippedReason: "not-supported",
|
|
63
30
|
};
|
|
64
31
|
}
|
|
65
|
-
const srcBase = join(ASSETS_DIR, "commands");
|
|
66
|
-
const destBase = localDefs
|
|
67
|
-
? join(await findProjectRoot(), "uluops", "commands")
|
|
68
|
-
: profile.paths.commandsDir;
|
|
69
|
-
const needsTransform = profile.name === "gemini-cli";
|
|
70
32
|
let agentCommands = 0;
|
|
71
33
|
let workflowCommands = 0;
|
|
72
34
|
let pipelineCommands = 0;
|
|
@@ -80,26 +42,14 @@ export async function installCommands(profile, localDefs, dryRun, existingManife
|
|
|
80
42
|
}
|
|
81
43
|
let files;
|
|
82
44
|
try {
|
|
83
|
-
files = (await readdir(srcDir)).filter((f) => f.endsWith(".md"));
|
|
45
|
+
files = (await readdir(srcDir)).filter((f) => f.endsWith(".md") || f.endsWith(".toml"));
|
|
84
46
|
}
|
|
85
47
|
catch {
|
|
86
48
|
continue;
|
|
87
49
|
}
|
|
88
50
|
for (const file of files) {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
: file;
|
|
92
|
-
const relativePath = `${subdir}/${destFile}`;
|
|
93
|
-
let result;
|
|
94
|
-
if (needsTransform) {
|
|
95
|
-
const markdown = await readFile(join(srcDir, file), "utf-8");
|
|
96
|
-
const description = extractDescription(markdown);
|
|
97
|
-
const toml = transformToGeminiToml(markdown, description);
|
|
98
|
-
result = await writeIfChanged(join(destDir, destFile), toml, dryRun);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
result = await copyIfChanged(join(srcDir, file), join(destDir, destFile), dryRun);
|
|
102
|
-
}
|
|
51
|
+
const relativePath = `${subdir}/${file}`;
|
|
52
|
+
const result = await copyIfChanged(join(srcDir, file), join(destDir, file), dryRun);
|
|
103
53
|
if (result === "copied") {
|
|
104
54
|
if (subdir === "agents")
|
|
105
55
|
agentCommands++;
|
|
@@ -142,6 +92,5 @@ export async function installCommands(profile, localDefs, dryRun, existingManife
|
|
|
142
92
|
}
|
|
143
93
|
/** Remove previously installed command files by relative path. Returns count of successfully removed files. */
|
|
144
94
|
export async function uninstallCommands(files, defsPath) {
|
|
145
|
-
const { unlinkFiles } = await import("../lib/file-ops.js");
|
|
146
95
|
return unlinkFiles(join(defsPath, "commands"), files);
|
|
147
96
|
}
|
package/dist/steps/detect.js
CHANGED
|
@@ -16,6 +16,10 @@ export async function detect() {
|
|
|
16
16
|
const isWsl = os === "linux" && release().toLowerCase().includes("microsoft");
|
|
17
17
|
const profile = getShellProfile();
|
|
18
18
|
const nodeVersion = process.version;
|
|
19
|
+
const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0] ?? "0", 10);
|
|
20
|
+
if (majorVersion < 20) {
|
|
21
|
+
throw new Error(`Unsupported Node.js version: ${nodeVersion}. @uluops/setup requires Node.js 20 or higher.`);
|
|
22
|
+
}
|
|
19
23
|
let claudeHomeExists = false;
|
|
20
24
|
try {
|
|
21
25
|
await access(getClaudeHome());
|
package/dist/steps/mcp.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { readFile, access
|
|
2
|
-
import { join
|
|
1
|
+
import { readFile, access } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
import { checkMcpPackageAvailability } from "../lib/config-merger.js";
|
|
4
4
|
import { findProjectRoot, getBackupDir } from "../lib/paths.js";
|
|
5
5
|
import { atomicWrite } from "../lib/atomic-write.js";
|
|
6
|
+
import { backupFile } from "../lib/file-ops.js";
|
|
6
7
|
/** Write UluOps MCP server entries into a harness's config file. */
|
|
7
8
|
export async function installMcp(profile, apiKey, scope, dryRun) {
|
|
8
9
|
const configPath = scope === "global"
|
|
@@ -33,22 +34,13 @@ export async function uninstallMcp(profile, configPath) {
|
|
|
33
34
|
await profile.mcpConfig.write(configPath, cleaned);
|
|
34
35
|
}
|
|
35
36
|
async function backupConfig(harnessName, configPath) {
|
|
36
|
-
|
|
37
|
-
await access(configPath);
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return; // Nothing to back up
|
|
41
|
-
}
|
|
42
|
-
const backupDir = getBackupDir(harnessName);
|
|
43
|
-
await mkdir(backupDir, { recursive: true });
|
|
44
|
-
const filename = basename(configPath);
|
|
45
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
46
|
-
await copyFile(configPath, join(backupDir, `${filename}.${timestamp}.bak`));
|
|
37
|
+
await backupFile(configPath, getBackupDir(harnessName));
|
|
47
38
|
}
|
|
48
39
|
async function addToGitignore(localConfigFilename) {
|
|
49
|
-
const
|
|
40
|
+
const root = await findProjectRoot();
|
|
41
|
+
const gitignorePath = join(root, ".gitignore");
|
|
50
42
|
try {
|
|
51
|
-
await access(join(
|
|
43
|
+
await access(join(root, ".git"));
|
|
52
44
|
}
|
|
53
45
|
catch {
|
|
54
46
|
return;
|
package/dist/steps/metrics.d.ts
CHANGED
|
@@ -13,8 +13,20 @@ export declare function getHookCommand(profile: HarnessProfile): string;
|
|
|
13
13
|
export interface MetricsResult {
|
|
14
14
|
toolFilesCopied: number;
|
|
15
15
|
hookConfigured: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Version of @uluops/agent-metrics whose dist was installed into the
|
|
18
|
+
* harness tree. Null when the metrics step was skipped or the source
|
|
19
|
+
* package.json could not be read.
|
|
20
|
+
*/
|
|
21
|
+
hooksInstalledVersion: string | null;
|
|
16
22
|
skippedReason?: string;
|
|
17
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Read the installed agent-metrics version from the harness tree.
|
|
26
|
+
* Returns null when the file is missing or unparseable.
|
|
27
|
+
* Exported so verify.ts can detect drift between installed and source.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readInstalledMetricsVersion(toolDir: string): Promise<string | null>;
|
|
18
30
|
/**
|
|
19
31
|
* Install agent-metrics: copy tool files and configure hook.
|
|
20
32
|
* Skips entirely if the harness doesn't support hooks.
|