@uluops/setup 0.2.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 +109 -89
- package/assets/auto-tracker-save.mjs +142 -0
- package/assets/claude-code/agents/anxiety-reader-agent.md +464 -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/claude-code/commands/agents/anxiety-reader.md +157 -0
- package/assets/{commands → claude-code/commands}/agents/api-contract.md +156 -135
- package/assets/{commands → claude-code/commands}/agents/architect.md +156 -135
- 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 -6
- package/assets/{commands → claude-code/commands}/agents/audit.md +156 -136
- package/assets/{commands → claude-code/commands}/agents/docs-validate.md +156 -133
- package/assets/{commands → claude-code/commands}/agents/frontend.md +156 -135
- package/assets/{commands → claude-code/commands}/agents/mcp-validate.md +156 -136
- package/assets/{commands → claude-code/commands}/agents/optimize.md +156 -133
- package/assets/{commands → claude-code/commands}/agents/pattern-analyzer.md +150 -126
- package/assets/{commands → claude-code/commands}/agents/prompt-quality.md +155 -134
- package/assets/claude-code/commands/agents/prompt-validate.md +155 -0
- package/assets/{commands → claude-code/commands}/agents/public-interface.md +156 -134
- package/assets/{commands → claude-code/commands}/agents/release.md +156 -135
- package/assets/{commands → claude-code/commands}/agents/security.md +156 -137
- package/assets/{commands → claude-code/commands}/agents/test-review.md +156 -136
- package/assets/{commands → claude-code/commands}/agents/type-safety.md +156 -135
- package/assets/{commands → claude-code/commands}/agents/validate.md +156 -134
- package/assets/claude-code/commands/agents/workflow-synthesis.md +157 -0
- package/assets/claude-code/commands/pipelines/aristotle.md +143 -0
- package/assets/claude-code/commands/pipelines/ship.md +188 -0
- package/assets/claude-code/commands/workflows/post-implementation.md +60 -0
- package/assets/claude-code/commands/workflows/pre-implementation.md +46 -0
- package/assets/claude-code/commands/workflows/prompt-audit.md +44 -0
- 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 +22 -380
- 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 +8 -0
- package/dist/harnesses/claude-code.js +74 -0
- package/dist/harnesses/codex.d.ts +15 -0
- package/dist/harnesses/codex.js +54 -0
- package/dist/harnesses/gemini-cli.d.ts +12 -0
- package/dist/harnesses/gemini-cli.js +80 -0
- package/dist/harnesses/index.d.ts +27 -0
- package/dist/harnesses/index.js +54 -0
- package/dist/harnesses/opencode.d.ts +14 -0
- package/dist/harnesses/opencode.js +139 -0
- package/dist/harnesses/types.d.ts +106 -0
- package/dist/harnesses/types.js +26 -0
- package/dist/lib/agent-transform.d.ts +12 -0
- package/dist/lib/agent-transform.js +129 -0
- package/dist/lib/asset-catalog.d.ts +9 -0
- package/dist/lib/asset-catalog.js +56 -0
- package/dist/lib/atomic-write.d.ts +11 -0
- package/dist/lib/atomic-write.js +28 -0
- package/dist/lib/config-merger.d.ts +9 -2
- package/dist/lib/config-merger.js +44 -7
- package/dist/lib/display.d.ts +14 -0
- package/dist/lib/display.js +66 -0
- package/dist/lib/file-ops.d.ts +11 -0
- package/dist/lib/file-ops.js +40 -4
- package/dist/lib/hash.d.ts +1 -0
- package/dist/lib/hash.js +2 -1
- package/dist/lib/health.d.ts +2 -0
- package/dist/lib/health.js +10 -0
- package/dist/lib/manifest.d.ts +51 -5
- package/dist/lib/manifest.js +146 -13
- package/dist/lib/paths.d.ts +30 -3
- package/dist/lib/paths.js +98 -12
- package/dist/lib/settings-merger.d.ts +31 -8
- package/dist/lib/settings-merger.js +87 -24
- package/dist/lib/version.d.ts +2 -0
- package/dist/lib/version.js +10 -0
- package/dist/steps/agents.d.ts +4 -1
- package/dist/steps/agents.js +48 -9
- package/dist/steps/auth.js +26 -10
- package/dist/steps/cli.d.ts +53 -0
- package/dist/steps/cli.js +90 -0
- package/dist/steps/commands.d.ts +6 -1
- package/dist/steps/commands.js +36 -9
- package/dist/steps/detect.d.ts +3 -0
- package/dist/steps/detect.js +11 -0
- package/dist/steps/mcp.d.ts +6 -2
- package/dist/steps/mcp.js +39 -22
- package/dist/steps/metrics.d.ts +26 -10
- package/dist/steps/metrics.js +108 -108
- package/dist/steps/shell.d.ts +2 -0
- package/dist/steps/shell.js +26 -9
- package/dist/steps/signup.d.ts +7 -4
- package/dist/steps/signup.js +29 -20
- package/dist/steps/verify.d.ts +2 -2
- package/dist/steps/verify.js +118 -112
- package/package.json +40 -14
- 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 -115
- package/assets/commands/agents/aristotle-explorer.md +0 -92
- package/assets/commands/agents/aristotle-forecaster.md +0 -114
- package/assets/commands/agents/aristotle-validator.md +0 -114
- package/assets/commands/agents/prompt-validate.md +0 -135
- package/assets/commands/agents/workflow-synthesis.md +0 -101
- package/assets/commands/workflows/aristotle.md +0 -543
- package/assets/commands/workflows/post-implementation.md +0 -577
- package/assets/commands/workflows/pre-implementation.md +0 -670
- package/assets/commands/workflows/prompt-audit.md +0 -754
- package/assets/commands/workflows/ship.md +0 -721
- package/dist/test/auth.test.d.ts +0 -1
- package/dist/test/auth.test.js +0 -43
- package/dist/test/config-io.test.d.ts +0 -1
- package/dist/test/config-io.test.js +0 -56
- package/dist/test/config-merger.test.d.ts +0 -1
- package/dist/test/config-merger.test.js +0 -94
- package/dist/test/detect.test.d.ts +0 -1
- package/dist/test/detect.test.js +0 -25
- package/dist/test/file-ops.test.d.ts +0 -1
- package/dist/test/file-ops.test.js +0 -100
- package/dist/test/hash.test.d.ts +0 -1
- package/dist/test/hash.test.js +0 -14
- package/dist/test/manifest.test.d.ts +0 -1
- package/dist/test/manifest.test.js +0 -78
- package/dist/test/paths.test.d.ts +0 -1
- package/dist/test/paths.test.js +0 -30
- package/dist/test/settings-merger.test.d.ts +0 -1
- package/dist/test/settings-merger.test.js +0 -167
- package/dist/test/shell-profile.test.d.ts +0 -1
- package/dist/test/shell-profile.test.js +0 -40
- package/dist/test/shell.test.d.ts +0 -1
- package/dist/test/shell.test.js +0 -71
- package/dist/test/signup.test.d.ts +0 -1
- package/dist/test/signup.test.js +0 -83
package/dist/lib/paths.d.ts
CHANGED
|
@@ -2,12 +2,39 @@
|
|
|
2
2
|
export declare const PACKAGE_ROOT: string;
|
|
3
3
|
/** Assets directory containing pre-rendered .md files */
|
|
4
4
|
export declare const ASSETS_DIR: string;
|
|
5
|
+
export declare function setProjectRoot(path: string | null): void;
|
|
6
|
+
/** Walk upward from cwd to find the nearest directory containing .git or package.json. Falls back to cwd. */
|
|
7
|
+
export declare function findProjectRoot(): Promise<string>;
|
|
8
|
+
/** Return the Claude config home directory (~/.claude by default, or CLAUDE_HOME env override). */
|
|
5
9
|
export declare function getClaudeHome(): string;
|
|
10
|
+
/** Return the path to Claude's global config file (~/.claude.json by default, or CLAUDE_JSON_PATH env override). */
|
|
6
11
|
export declare function getClaudeJsonPath(): string;
|
|
7
|
-
|
|
12
|
+
/** Return the path to the project-local MCP config file (.mcp.json in project root). */
|
|
13
|
+
export declare function getLocalMcpPath(): Promise<string>;
|
|
14
|
+
/** Return the UluOps state directory (~/.uluops/). Harness-neutral. */
|
|
15
|
+
export declare function getUluopsDir(): string;
|
|
16
|
+
/** Return the path to the UluOps install manifest file (new location). */
|
|
8
17
|
export declare function getManifestPath(): string;
|
|
9
|
-
|
|
10
|
-
export declare function
|
|
18
|
+
/** Return the legacy manifest path for migration. */
|
|
19
|
+
export declare function getLegacyManifestPath(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Return the backup directory for a harness's **config** files.
|
|
22
|
+
*
|
|
23
|
+
* Scope is deliberately narrow: this directory holds copies of mutable
|
|
24
|
+
* user-owned config surfaces (the MCP config file, the shell profile),
|
|
25
|
+
* NOT vendor-owned tool files in `~/.claude/tools/agent-metrics/`. Tool
|
|
26
|
+
* files are treated as disposable — they can be regenerated by re-running
|
|
27
|
+
* setup, and the source of truth lives in the npm-installed
|
|
28
|
+
* `@uluops/agent-metrics` package. Backing them up would require a
|
|
29
|
+
* different ritual (versioned snapshots tied to the manifest's
|
|
30
|
+
* hooksInstalledVersion) and is not provided here.
|
|
31
|
+
*
|
|
32
|
+
* Renaming this to `getConfigBackupDir` would be more honest but is a
|
|
33
|
+
* public surface change deferred until we have a tool-file backup to
|
|
34
|
+
* disambiguate against.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getBackupDir(harnessName: string): string;
|
|
37
|
+
/** Detect the user's shell and return its name and profile path, or null if unsupported. */
|
|
11
38
|
export declare function getShellProfile(): {
|
|
12
39
|
shell: string;
|
|
13
40
|
path: string;
|
package/dist/lib/paths.js
CHANGED
|
@@ -1,33 +1,119 @@
|
|
|
1
1
|
import { homedir, platform } from "node:os";
|
|
2
|
-
import { join, dirname } from "node:path";
|
|
2
|
+
import { join, dirname, isAbsolute } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { access } from "node:fs/promises";
|
|
4
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
6
|
/** Root of the npm package (where assets/ lives) */
|
|
6
7
|
export const PACKAGE_ROOT = join(__dirname, "..", "..");
|
|
7
8
|
/** Assets directory containing pre-rendered .md files */
|
|
8
9
|
export const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
|
|
10
|
+
/** Explicit project root override via --project-root flag or env var. */
|
|
11
|
+
let projectRootOverride = null;
|
|
12
|
+
export function setProjectRoot(path) {
|
|
13
|
+
projectRootOverride = path;
|
|
14
|
+
}
|
|
15
|
+
/** Walk upward from cwd to find the nearest directory containing .git or package.json. Falls back to cwd. */
|
|
16
|
+
export async function findProjectRoot() {
|
|
17
|
+
if (projectRootOverride)
|
|
18
|
+
return projectRootOverride;
|
|
19
|
+
const envRoot = process.env["ULUOPS_PROJECT_ROOT"];
|
|
20
|
+
if (envRoot) {
|
|
21
|
+
if (!isAbsolute(envRoot)) {
|
|
22
|
+
throw new Error(`ULUOPS_PROJECT_ROOT must be an absolute path, got: ${envRoot}`);
|
|
23
|
+
}
|
|
24
|
+
if (envRoot.includes("..")) {
|
|
25
|
+
throw new Error(`ULUOPS_PROJECT_ROOT must not contain traversal sequences: ${envRoot}`);
|
|
26
|
+
}
|
|
27
|
+
return envRoot;
|
|
28
|
+
}
|
|
29
|
+
let dir = process.cwd();
|
|
30
|
+
const root = dirname(dir);
|
|
31
|
+
while (dir !== root) {
|
|
32
|
+
if (await isProjectMarker(dir))
|
|
33
|
+
return dir;
|
|
34
|
+
dir = dirname(dir);
|
|
35
|
+
}
|
|
36
|
+
if (await isProjectMarker(dir))
|
|
37
|
+
return dir;
|
|
38
|
+
return process.cwd();
|
|
39
|
+
}
|
|
40
|
+
async function isProjectMarker(dir) {
|
|
41
|
+
try {
|
|
42
|
+
await access(join(dir, ".git"));
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// continue
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
await access(join(dir, "package.json"));
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// continue
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
/** Validate an env-var-supplied path: must be absolute and contain no traversal sequences. */
|
|
58
|
+
function validateEnvPath(value, varName) {
|
|
59
|
+
if (!isAbsolute(value)) {
|
|
60
|
+
throw new Error(`${varName} must be an absolute path, got: ${value}`);
|
|
61
|
+
}
|
|
62
|
+
if (value.includes("..")) {
|
|
63
|
+
throw new Error(`${varName} must not contain traversal sequences: ${value}`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
/** Return the Claude config home directory (~/.claude by default, or CLAUDE_HOME env override). */
|
|
9
68
|
export function getClaudeHome() {
|
|
69
|
+
const envHome = process.env["CLAUDE_HOME"];
|
|
70
|
+
if (envHome)
|
|
71
|
+
return validateEnvPath(envHome, "CLAUDE_HOME");
|
|
10
72
|
return join(homedir(), ".claude");
|
|
11
73
|
}
|
|
74
|
+
/** Return the path to Claude's global config file (~/.claude.json by default, or CLAUDE_JSON_PATH env override). */
|
|
12
75
|
export function getClaudeJsonPath() {
|
|
76
|
+
const envPath = process.env["CLAUDE_JSON_PATH"];
|
|
77
|
+
if (envPath)
|
|
78
|
+
return validateEnvPath(envPath, "CLAUDE_JSON_PATH");
|
|
13
79
|
return join(homedir(), ".claude.json");
|
|
14
80
|
}
|
|
15
|
-
|
|
16
|
-
|
|
81
|
+
/** Return the path to the project-local MCP config file (.mcp.json in project root). */
|
|
82
|
+
export async function getLocalMcpPath() {
|
|
83
|
+
return join(await findProjectRoot(), ".mcp.json");
|
|
17
84
|
}
|
|
85
|
+
/** Return the UluOps state directory (~/.uluops/). Harness-neutral. */
|
|
86
|
+
export function getUluopsDir() {
|
|
87
|
+
return join(homedir(), ".uluops");
|
|
88
|
+
}
|
|
89
|
+
/** Return the path to the UluOps install manifest file (new location). */
|
|
18
90
|
export function getManifestPath() {
|
|
19
|
-
return join(
|
|
91
|
+
return join(getUluopsDir(), "manifest.json");
|
|
20
92
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return join(getClaudeHome(), "agents");
|
|
93
|
+
/** Return the legacy manifest path for migration. */
|
|
94
|
+
export function getLegacyManifestPath() {
|
|
95
|
+
return join(getClaudeHome(), "uluops-manifest.json");
|
|
25
96
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Return the backup directory for a harness's **config** files.
|
|
99
|
+
*
|
|
100
|
+
* Scope is deliberately narrow: this directory holds copies of mutable
|
|
101
|
+
* user-owned config surfaces (the MCP config file, the shell profile),
|
|
102
|
+
* NOT vendor-owned tool files in `~/.claude/tools/agent-metrics/`. Tool
|
|
103
|
+
* files are treated as disposable — they can be regenerated by re-running
|
|
104
|
+
* setup, and the source of truth lives in the npm-installed
|
|
105
|
+
* `@uluops/agent-metrics` package. Backing them up would require a
|
|
106
|
+
* different ritual (versioned snapshots tied to the manifest's
|
|
107
|
+
* hooksInstalledVersion) and is not provided here.
|
|
108
|
+
*
|
|
109
|
+
* Renaming this to `getConfigBackupDir` would be more honest but is a
|
|
110
|
+
* public surface change deferred until we have a tool-file backup to
|
|
111
|
+
* disambiguate against.
|
|
112
|
+
*/
|
|
113
|
+
export function getBackupDir(harnessName) {
|
|
114
|
+
return join(getUluopsDir(), "backups", harnessName);
|
|
30
115
|
}
|
|
116
|
+
/** Detect the user's shell and return its name and profile path, or null if unsupported. */
|
|
31
117
|
export function getShellProfile() {
|
|
32
118
|
const shell = process.env["SHELL"] ?? "";
|
|
33
119
|
const home = homedir();
|
|
@@ -13,31 +13,54 @@ interface HookMatcher {
|
|
|
13
13
|
matcher?: string;
|
|
14
14
|
hooks: HookEntry[];
|
|
15
15
|
}
|
|
16
|
-
interface
|
|
16
|
+
export interface HarnessSettings {
|
|
17
17
|
permissions?: Record<string, unknown>;
|
|
18
18
|
hooks?: Record<string, HookMatcher[]>;
|
|
19
19
|
[key: string]: unknown;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Supported hook event types in Claude Code's settings.json schema.
|
|
23
|
+
*
|
|
24
|
+
* This set is a snapshot of the harness's vocabulary. When Claude Code
|
|
25
|
+
* adds, renames, or removes hook types, this set rots — the `probeHookSupport`
|
|
26
|
+
* warning will fire on legitimate user configs and (worse) train users to
|
|
27
|
+
* ignore it. The snapshot test in `settings-merger.test.ts` exists to make
|
|
28
|
+
* any change to the set visible in PR review so the warning logic can be
|
|
29
|
+
* re-evaluated.
|
|
30
|
+
*
|
|
31
|
+
* Exported for that test only — runtime callers should use `probeHookSupport`.
|
|
32
|
+
*/
|
|
33
|
+
export declare const CLAUDE_HOOK_TYPES: Set<string>;
|
|
34
|
+
/** Default Claude Code hook event used when no override is configured. */
|
|
35
|
+
export declare const DEFAULT_CLAUDE_HOOK_TYPE = "SubagentStop";
|
|
36
|
+
export interface HookProbeResult {
|
|
37
|
+
hookType: string;
|
|
38
|
+
supported: boolean;
|
|
39
|
+
warning?: string;
|
|
40
|
+
}
|
|
41
|
+
/** Check whether the configured hook event type is in the known supported set. Returns the resolved hook type and a warning if unsupported. */
|
|
42
|
+
export declare function probeHookSupport(hookTypeOverride?: string): HookProbeResult;
|
|
21
43
|
/**
|
|
22
44
|
* Read an existing settings.json, or return empty object if it doesn't exist.
|
|
45
|
+
* Throws on malformed JSON to prevent silent data loss during merge+write.
|
|
23
46
|
*/
|
|
24
|
-
export declare function readSettings(path: string): Promise<
|
|
47
|
+
export declare function readSettings(path: string): Promise<HarnessSettings>;
|
|
25
48
|
/**
|
|
26
49
|
* Write settings back to file with stable formatting.
|
|
27
50
|
*/
|
|
28
|
-
export declare function writeSettings(path: string, settings:
|
|
51
|
+
export declare function writeSettings(path: string, settings: HarnessSettings): Promise<void>;
|
|
29
52
|
/**
|
|
30
|
-
* Merge the UluOps
|
|
53
|
+
* Merge the UluOps hook into settings, preserving all other
|
|
31
54
|
* hooks and settings. If a UluOps hook already exists, it is replaced.
|
|
32
55
|
*/
|
|
33
|
-
export declare function mergeUluopsHook(settings:
|
|
56
|
+
export declare function mergeUluopsHook(settings: HarnessSettings, hookCommand: string, hookTypeOverride?: string, matcher?: string): HarnessSettings;
|
|
34
57
|
/**
|
|
35
|
-
* Remove UluOps hook entries from settings. If
|
|
58
|
+
* Remove UluOps hook entries from settings. If a hook type becomes empty,
|
|
36
59
|
* the key is removed. If hooks becomes empty, the key is removed.
|
|
37
60
|
*/
|
|
38
|
-
export declare function removeUluopsHook(settings:
|
|
61
|
+
export declare function removeUluopsHook(settings: HarnessSettings, hookTypeOverride?: string): HarnessSettings;
|
|
39
62
|
/**
|
|
40
63
|
* Check if a UluOps hook is configured in settings.
|
|
41
64
|
*/
|
|
42
|
-
export declare function hasUluopsHook(settings:
|
|
65
|
+
export declare function hasUluopsHook(settings: HarnessSettings, hookTypeOverride?: string): boolean;
|
|
43
66
|
export {};
|
|
@@ -4,37 +4,95 @@
|
|
|
4
4
|
* Safe read/merge/remove for Claude Code's settings.json.
|
|
5
5
|
* Only touches UluOps-managed hook entries — all other settings preserved.
|
|
6
6
|
*/
|
|
7
|
-
import { readFile
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import { atomicWrite } from "./atomic-write.js";
|
|
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([
|
|
38
|
+
"SubagentStop",
|
|
39
|
+
"PreToolUse",
|
|
40
|
+
"PostToolUse",
|
|
41
|
+
"Notification",
|
|
42
|
+
"Stop",
|
|
43
|
+
]);
|
|
44
|
+
/** Default Claude Code hook event used when no override is configured. */
|
|
45
|
+
export const DEFAULT_CLAUDE_HOOK_TYPE = "SubagentStop";
|
|
46
|
+
/** Configurable hook type via env var. Falls back to SubagentStop. */
|
|
47
|
+
function getDefaultHookEventType() {
|
|
48
|
+
return process.env["ULUOPS_HOOK_TYPE"] ?? DEFAULT_CLAUDE_HOOK_TYPE;
|
|
49
|
+
}
|
|
50
|
+
/** Check whether the configured hook event type is in the known supported set. Returns the resolved hook type and a warning if unsupported. */
|
|
51
|
+
export function probeHookSupport(hookTypeOverride) {
|
|
52
|
+
const hookType = hookTypeOverride ?? getDefaultHookEventType();
|
|
53
|
+
if (CLAUDE_HOOK_TYPES.has(hookType) || hookType === "AfterTool") {
|
|
54
|
+
return { hookType, supported: true };
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
hookType,
|
|
58
|
+
supported: false,
|
|
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.`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
10
62
|
/**
|
|
11
63
|
* Read an existing settings.json, or return empty object if it doesn't exist.
|
|
64
|
+
* Throws on malformed JSON to prevent silent data loss during merge+write.
|
|
12
65
|
*/
|
|
13
66
|
export async function readSettings(path) {
|
|
67
|
+
let raw;
|
|
68
|
+
try {
|
|
69
|
+
raw = await readFile(path, "utf-8");
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return {}; // File doesn't exist — fresh config
|
|
73
|
+
}
|
|
14
74
|
try {
|
|
15
|
-
const raw = await readFile(path, "utf-8");
|
|
16
75
|
return JSON.parse(raw);
|
|
17
76
|
}
|
|
18
77
|
catch {
|
|
19
|
-
|
|
78
|
+
throw new Error(`Failed to parse settings at ${path} — file contains invalid JSON`);
|
|
20
79
|
}
|
|
21
80
|
}
|
|
22
81
|
/**
|
|
23
82
|
* Write settings back to file with stable formatting.
|
|
24
83
|
*/
|
|
25
84
|
export async function writeSettings(path, settings) {
|
|
26
|
-
await
|
|
85
|
+
await atomicWrite(path, JSON.stringify(settings, null, 2) + "\n");
|
|
27
86
|
}
|
|
28
87
|
/**
|
|
29
|
-
* Merge the UluOps
|
|
88
|
+
* Merge the UluOps hook into settings, preserving all other
|
|
30
89
|
* hooks and settings. If a UluOps hook already exists, it is replaced.
|
|
31
90
|
*/
|
|
32
|
-
export function mergeUluopsHook(settings, hookCommand) {
|
|
91
|
+
export function mergeUluopsHook(settings, hookCommand, hookTypeOverride, matcher) {
|
|
92
|
+
const hookType = hookTypeOverride ?? getDefaultHookEventType();
|
|
33
93
|
const hooks = settings.hooks ?? {};
|
|
34
|
-
const existing = hooks[
|
|
35
|
-
|
|
36
|
-
const filtered = existing.filter((m) => !m.hooks.some((h) => h.command.includes(ULUOPS_HOOK_MARKER)));
|
|
37
|
-
// Add the new UluOps hook
|
|
94
|
+
const existing = hooks[hookType] ?? [];
|
|
95
|
+
const filtered = existing.filter((m) => !m.hooks.some((h) => h.command.includes(HOOK_OWNERSHIP_SIGNATURE)));
|
|
38
96
|
const uluopsHook = {
|
|
39
97
|
hooks: [
|
|
40
98
|
{
|
|
@@ -44,32 +102,36 @@ export function mergeUluopsHook(settings, hookCommand) {
|
|
|
44
102
|
},
|
|
45
103
|
],
|
|
46
104
|
};
|
|
105
|
+
if (matcher) {
|
|
106
|
+
uluopsHook.matcher = matcher;
|
|
107
|
+
}
|
|
47
108
|
return {
|
|
48
109
|
...settings,
|
|
49
110
|
hooks: {
|
|
50
111
|
...hooks,
|
|
51
|
-
|
|
112
|
+
[hookType]: [...filtered, uluopsHook],
|
|
52
113
|
},
|
|
53
114
|
};
|
|
54
115
|
}
|
|
55
116
|
/**
|
|
56
|
-
* Remove UluOps hook entries from settings. If
|
|
117
|
+
* Remove UluOps hook entries from settings. If a hook type becomes empty,
|
|
57
118
|
* the key is removed. If hooks becomes empty, the key is removed.
|
|
58
119
|
*/
|
|
59
|
-
export function removeUluopsHook(settings) {
|
|
120
|
+
export function removeUluopsHook(settings, hookTypeOverride) {
|
|
121
|
+
const hookType = hookTypeOverride ?? getDefaultHookEventType();
|
|
60
122
|
const hooks = settings.hooks;
|
|
61
123
|
if (!hooks)
|
|
62
124
|
return settings;
|
|
63
|
-
const
|
|
64
|
-
if (!
|
|
125
|
+
const hookEntries = hooks[hookType];
|
|
126
|
+
if (!hookEntries)
|
|
65
127
|
return settings;
|
|
66
|
-
const filtered =
|
|
128
|
+
const filtered = hookEntries.filter((m) => !m.hooks.some((h) => h.command.includes(HOOK_OWNERSHIP_SIGNATURE)));
|
|
67
129
|
const updatedHooks = { ...hooks };
|
|
68
130
|
if (filtered.length === 0) {
|
|
69
|
-
delete updatedHooks[
|
|
131
|
+
delete updatedHooks[hookType];
|
|
70
132
|
}
|
|
71
133
|
else {
|
|
72
|
-
updatedHooks[
|
|
134
|
+
updatedHooks[hookType] = filtered;
|
|
73
135
|
}
|
|
74
136
|
const result = { ...settings };
|
|
75
137
|
if (Object.keys(updatedHooks).length === 0) {
|
|
@@ -83,9 +145,10 @@ export function removeUluopsHook(settings) {
|
|
|
83
145
|
/**
|
|
84
146
|
* Check if a UluOps hook is configured in settings.
|
|
85
147
|
*/
|
|
86
|
-
export function hasUluopsHook(settings) {
|
|
87
|
-
const
|
|
88
|
-
|
|
148
|
+
export function hasUluopsHook(settings, hookTypeOverride) {
|
|
149
|
+
const hookType = hookTypeOverride ?? getDefaultHookEventType();
|
|
150
|
+
const hookEntries = settings.hooks?.[hookType];
|
|
151
|
+
if (!hookEntries)
|
|
89
152
|
return false;
|
|
90
|
-
return
|
|
153
|
+
return hookEntries.some((m) => m.hooks.some((h) => h.command.includes(HOOK_OWNERSHIP_SIGNATURE)));
|
|
91
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
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { HarnessProfile } from "../harnesses/index.js";
|
|
1
2
|
export interface AgentsResult {
|
|
2
3
|
copied: number;
|
|
3
4
|
skipped: number;
|
|
4
5
|
removed: number;
|
|
5
6
|
files: string[];
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
+
/** Copy pre-rendered agent definitions from harness-specific assets to the target directory. */
|
|
9
|
+
export declare function installAgents(profile: HarnessProfile, localDefs: boolean, dryRun: boolean, existingManifestAgents?: string[]): Promise<AgentsResult>;
|
|
10
|
+
/** Remove previously installed agent files by name. */
|
|
8
11
|
export declare function uninstallAgents(files: string[], defsPath: string): Promise<number>;
|
package/dist/steps/agents.js
CHANGED
|
@@ -1,14 +1,53 @@
|
|
|
1
|
+
import { readdir, mkdir, unlink } from "node:fs/promises";
|
|
1
2
|
import { join } from "node:path";
|
|
2
|
-
import { ASSETS_DIR,
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import { ASSETS_DIR, findProjectRoot } from "../lib/paths.js";
|
|
4
|
+
import { copyIfChanged, unlinkFiles } from "../lib/file-ops.js";
|
|
5
|
+
/** Copy pre-rendered agent definitions from harness-specific assets to the target directory. */
|
|
6
|
+
export async function installAgents(profile, localDefs, dryRun, existingManifestAgents) {
|
|
7
|
+
const srcDir = join(ASSETS_DIR, profile.name, "agents");
|
|
8
|
+
const destDir = localDefs
|
|
9
|
+
? join(await findProjectRoot(), "uluops", "agents")
|
|
10
|
+
: profile.paths.agentsDir;
|
|
11
|
+
if (!dryRun) {
|
|
12
|
+
await mkdir(destDir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
const ext = profile.agentExtension;
|
|
15
|
+
let files;
|
|
16
|
+
try {
|
|
17
|
+
files = (await readdir(srcDir)).filter((f) => f.endsWith(ext));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { copied: 0, skipped: 0, removed: 0, files: [] };
|
|
21
|
+
}
|
|
22
|
+
let copied = 0;
|
|
23
|
+
let skipped = 0;
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
const result = await copyIfChanged(join(srcDir, file), join(destDir, file), dryRun);
|
|
26
|
+
if (result === "copied")
|
|
27
|
+
copied++;
|
|
28
|
+
else
|
|
29
|
+
skipped++;
|
|
30
|
+
}
|
|
31
|
+
// Remove files that were in the old manifest but no longer in the package
|
|
32
|
+
let removed = 0;
|
|
33
|
+
if (existingManifestAgents) {
|
|
34
|
+
for (const oldFile of existingManifestAgents) {
|
|
35
|
+
if (!files.includes(oldFile)) {
|
|
36
|
+
if (!dryRun) {
|
|
37
|
+
try {
|
|
38
|
+
await unlink(join(destDir, oldFile));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Already gone
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
removed++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { copied, skipped, removed, files };
|
|
11
49
|
}
|
|
50
|
+
/** Remove previously installed agent files by name. */
|
|
12
51
|
export async function uninstallAgents(files, defsPath) {
|
|
13
52
|
return unlinkFiles(join(defsPath, "agents"), files);
|
|
14
53
|
}
|
package/dist/steps/auth.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
+
function getKeyPrefix() {
|
|
5
|
+
return process.env["ULUOPS_KEY_PREFIX"] ?? "ulr_";
|
|
6
|
+
}
|
|
4
7
|
/**
|
|
5
8
|
* Resolve API key from flags, env, credentials file, or interactive prompt.
|
|
6
9
|
*/
|
|
@@ -20,20 +23,22 @@ export async function resolveApiKey(options) {
|
|
|
20
23
|
if (!options.interactive) {
|
|
21
24
|
throw new Error("No API key found. Pass --api-key or set ULUOPS_API_KEY. Get one at app.uluops.ai/settings/api-keys");
|
|
22
25
|
}
|
|
26
|
+
const prefix = getKeyPrefix();
|
|
23
27
|
const { input } = await import("@inquirer/prompts");
|
|
24
28
|
apiKey = await input({
|
|
25
29
|
message: "Enter your UluOps API key",
|
|
26
30
|
validate: (val) => {
|
|
27
31
|
if (!val.trim())
|
|
28
32
|
return "Get a key at app.uluops.ai/settings/api-keys";
|
|
29
|
-
if (!val.startsWith(
|
|
30
|
-
return
|
|
33
|
+
if (!val.startsWith(prefix))
|
|
34
|
+
return `API keys typically start with ${prefix} — if your key has a different format, just paste it and server validation will check it`;
|
|
31
35
|
return true;
|
|
32
36
|
},
|
|
33
37
|
});
|
|
34
38
|
}
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
const prefix = getKeyPrefix();
|
|
40
|
+
if (!apiKey.startsWith(prefix) && !options.skipValidation) {
|
|
41
|
+
process.stderr.write(` ⚠ Key does not start with expected prefix "${prefix}" — proceeding with server validation\n`);
|
|
37
42
|
}
|
|
38
43
|
// Validate against server
|
|
39
44
|
if (!options.skipValidation) {
|
|
@@ -43,22 +48,33 @@ export async function resolveApiKey(options) {
|
|
|
43
48
|
return { apiKey, email: null };
|
|
44
49
|
}
|
|
45
50
|
async function readCredentialsFile() {
|
|
51
|
+
const credsPath = join(homedir(), ".uluops", "credentials.json");
|
|
52
|
+
let raw;
|
|
46
53
|
try {
|
|
47
|
-
|
|
48
|
-
const raw = await readFile(credsPath, "utf-8");
|
|
49
|
-
const creds = JSON.parse(raw);
|
|
50
|
-
const defaultProfile = creds["default"];
|
|
51
|
-
return defaultProfile?.apiKey ?? defaultProfile?.api_key;
|
|
54
|
+
raw = await readFile(credsPath, "utf-8");
|
|
52
55
|
}
|
|
53
56
|
catch {
|
|
54
|
-
return undefined;
|
|
57
|
+
return undefined; // File doesn't exist
|
|
58
|
+
}
|
|
59
|
+
let creds;
|
|
60
|
+
try {
|
|
61
|
+
creds = JSON.parse(raw);
|
|
55
62
|
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
throw new Error(`Malformed credentials file at ${credsPath}: ${err instanceof Error ? err.message : "invalid JSON"}`);
|
|
65
|
+
}
|
|
66
|
+
if (typeof creds !== "object" || creds === null)
|
|
67
|
+
return undefined;
|
|
68
|
+
const profiles = creds;
|
|
69
|
+
const defaultProfile = profiles["default"];
|
|
70
|
+
return defaultProfile?.apiKey ?? defaultProfile?.api_key;
|
|
56
71
|
}
|
|
57
72
|
async function validateKey(apiKey) {
|
|
58
73
|
const url = "https://api.uluops.ai/api/v1/registry/users/me";
|
|
59
74
|
try {
|
|
60
75
|
const res = await fetch(url, {
|
|
61
76
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
77
|
+
signal: AbortSignal.timeout(15000),
|
|
62
78
|
});
|
|
63
79
|
if (res.status === 401) {
|
|
64
80
|
throw new Error("Invalid API key — generate a new one at app.uluops.ai/settings/api-keys");
|
|
@@ -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>;
|