@uluops/setup 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -0
- package/assets/agents/api-contract-validator-agent.md +960 -0
- package/assets/agents/aristotle-analyst-agent.md +705 -0
- package/assets/agents/aristotle-explorer-agent.md +152 -0
- package/assets/agents/aristotle-forecaster-agent.md +666 -0
- package/assets/agents/aristotle-validator-agent.md +667 -0
- package/assets/agents/assumption-excavator-agent.md +1354 -0
- package/assets/agents/code-auditor-agent.md +1061 -0
- package/assets/agents/code-optimizer-agent.md +876 -0
- package/assets/agents/code-validator-agent.md +846 -0
- package/assets/agents/docs-validator-agent.md +490 -0
- package/assets/agents/frontend-validator-agent.md +844 -0
- package/assets/agents/mcp-validator-agent.md +827 -0
- package/assets/agents/pre-implementation-architect-agent.md +1036 -0
- package/assets/agents/prompt-engineer-agent.md +1158 -0
- package/assets/agents/prompt-pattern-analyzer-agent.md +907 -0
- package/assets/agents/prompt-quality-validator-agent.md +1018 -0
- package/assets/agents/public-interface-validator-agent.md +951 -0
- package/assets/agents/release-readiness-agent.md +482 -0
- package/assets/agents/security-analyst-agent.md +1093 -0
- package/assets/agents/test-architect-agent.md +861 -0
- package/assets/agents/type-safety-validator-agent.md +932 -0
- package/assets/agents/workflow-synthesis-agent.md +836 -0
- package/assets/commands/agents/api-contract.md +135 -0
- package/assets/commands/agents/architect.md +135 -0
- package/assets/commands/agents/aristotle-analyst.md +115 -0
- package/assets/commands/agents/aristotle-explorer.md +92 -0
- package/assets/commands/agents/aristotle-forecaster.md +114 -0
- package/assets/commands/agents/aristotle-validator.md +114 -0
- package/assets/commands/agents/assumption-excavator.md +114 -0
- package/assets/commands/agents/audit.md +136 -0
- package/assets/commands/agents/docs-validate.md +133 -0
- package/assets/commands/agents/frontend.md +135 -0
- package/assets/commands/agents/mcp-validate.md +136 -0
- package/assets/commands/agents/optimize.md +133 -0
- package/assets/commands/agents/pattern-analyzer.md +126 -0
- package/assets/commands/agents/prompt-quality.md +134 -0
- package/assets/commands/agents/prompt-validate.md +135 -0
- package/assets/commands/agents/public-interface.md +134 -0
- package/assets/commands/agents/release.md +135 -0
- package/assets/commands/agents/security.md +137 -0
- package/assets/commands/agents/test-review.md +136 -0
- package/assets/commands/agents/type-safety.md +135 -0
- package/assets/commands/agents/validate.md +134 -0
- package/assets/commands/agents/workflow-synthesis.md +101 -0
- package/assets/commands/workflows/aristotle.md +543 -0
- package/assets/commands/workflows/post-implementation.md +577 -0
- package/assets/commands/workflows/pre-implementation.md +670 -0
- package/assets/commands/workflows/prompt-audit.md +754 -0
- package/assets/commands/workflows/ship.md +721 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +436 -0
- package/dist/lib/config-merger.d.ts +26 -0
- package/dist/lib/config-merger.js +63 -0
- package/dist/lib/file-ops.d.ts +23 -0
- package/dist/lib/file-ops.js +86 -0
- package/dist/lib/hash.d.ts +1 -0
- package/dist/lib/hash.js +4 -0
- package/dist/lib/manifest.d.ts +16 -0
- package/dist/lib/manifest.js +34 -0
- package/dist/lib/paths.d.ts +14 -0
- package/dist/lib/paths.js +49 -0
- package/dist/lib/settings-merger.d.ts +43 -0
- package/dist/lib/settings-merger.js +91 -0
- package/dist/steps/agents.d.ts +8 -0
- package/dist/steps/agents.js +14 -0
- package/dist/steps/auth.d.ts +12 -0
- package/dist/steps/auth.js +80 -0
- package/dist/steps/commands.d.ts +9 -0
- package/dist/steps/commands.js +69 -0
- package/dist/steps/detect.d.ts +9 -0
- package/dist/steps/detect.js +30 -0
- package/dist/steps/mcp.d.ts +6 -0
- package/dist/steps/mcp.js +40 -0
- package/dist/steps/metrics.d.ts +22 -0
- package/dist/steps/metrics.js +176 -0
- package/dist/steps/shell.d.ts +2 -0
- package/dist/steps/shell.js +48 -0
- package/dist/steps/signup.d.ts +13 -0
- package/dist/steps/signup.js +92 -0
- package/dist/steps/verify.d.ts +10 -0
- package/dist/steps/verify.js +184 -0
- package/dist/test/auth.test.d.ts +1 -0
- package/dist/test/auth.test.js +43 -0
- package/dist/test/config-io.test.d.ts +1 -0
- package/dist/test/config-io.test.js +56 -0
- package/dist/test/config-merger.test.d.ts +1 -0
- package/dist/test/config-merger.test.js +94 -0
- package/dist/test/detect.test.d.ts +1 -0
- package/dist/test/detect.test.js +25 -0
- package/dist/test/file-ops.test.d.ts +1 -0
- package/dist/test/file-ops.test.js +100 -0
- package/dist/test/hash.test.d.ts +1 -0
- package/dist/test/hash.test.js +14 -0
- package/dist/test/manifest.test.d.ts +1 -0
- package/dist/test/manifest.test.js +78 -0
- package/dist/test/paths.test.d.ts +1 -0
- package/dist/test/paths.test.js +30 -0
- package/dist/test/settings-merger.test.d.ts +1 -0
- package/dist/test/settings-merger.test.js +167 -0
- package/dist/test/shell-profile.test.d.ts +1 -0
- package/dist/test/shell-profile.test.js +40 -0
- package/dist/test/shell.test.d.ts +1 -0
- package/dist/test/shell.test.js +71 -0
- package/dist/test/signup.test.d.ts +1 -0
- package/dist/test/signup.test.js +83 -0
- package/package.json +36 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFile, writeFile, unlink } from "node:fs/promises";
|
|
2
|
+
import { getManifestPath } from "./paths.js";
|
|
3
|
+
function isManifest(obj) {
|
|
4
|
+
if (typeof obj !== "object" || obj === null)
|
|
5
|
+
return false;
|
|
6
|
+
const m = obj;
|
|
7
|
+
return (typeof m["version"] === "string" &&
|
|
8
|
+
typeof m["installedAt"] === "string" &&
|
|
9
|
+
typeof m["mcpConfigPath"] === "string" &&
|
|
10
|
+
typeof m["defsPath"] === "string" &&
|
|
11
|
+
Array.isArray(m["agents"]) &&
|
|
12
|
+
Array.isArray(m["commands"]));
|
|
13
|
+
}
|
|
14
|
+
export async function loadManifest() {
|
|
15
|
+
try {
|
|
16
|
+
const raw = await readFile(getManifestPath(), "utf-8");
|
|
17
|
+
const parsed = JSON.parse(raw);
|
|
18
|
+
return isManifest(parsed) ? parsed : null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function saveManifest(manifest) {
|
|
25
|
+
await writeFile(getManifestPath(), JSON.stringify(manifest, null, 2) + "\n");
|
|
26
|
+
}
|
|
27
|
+
export async function deleteManifest() {
|
|
28
|
+
try {
|
|
29
|
+
await unlink(getManifestPath());
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Already gone
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Root of the npm package (where assets/ lives) */
|
|
2
|
+
export declare const PACKAGE_ROOT: string;
|
|
3
|
+
/** Assets directory containing pre-rendered .md files */
|
|
4
|
+
export declare const ASSETS_DIR: string;
|
|
5
|
+
export declare function getClaudeHome(): string;
|
|
6
|
+
export declare function getClaudeJsonPath(): string;
|
|
7
|
+
export declare function getLocalMcpPath(): string;
|
|
8
|
+
export declare function getManifestPath(): string;
|
|
9
|
+
export declare function getAgentsDir(localDefs: boolean): string;
|
|
10
|
+
export declare function getCommandsDir(localDefs: boolean): string;
|
|
11
|
+
export declare function getShellProfile(): {
|
|
12
|
+
shell: string;
|
|
13
|
+
path: string;
|
|
14
|
+
} | null;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { homedir, platform } from "node:os";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
/** Root of the npm package (where assets/ lives) */
|
|
6
|
+
export const PACKAGE_ROOT = join(__dirname, "..", "..");
|
|
7
|
+
/** Assets directory containing pre-rendered .md files */
|
|
8
|
+
export const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
|
|
9
|
+
export function getClaudeHome() {
|
|
10
|
+
return join(homedir(), ".claude");
|
|
11
|
+
}
|
|
12
|
+
export function getClaudeJsonPath() {
|
|
13
|
+
return join(homedir(), ".claude.json");
|
|
14
|
+
}
|
|
15
|
+
export function getLocalMcpPath() {
|
|
16
|
+
return join(process.cwd(), ".mcp.json");
|
|
17
|
+
}
|
|
18
|
+
export function getManifestPath() {
|
|
19
|
+
return join(getClaudeHome(), "uluops-manifest.json");
|
|
20
|
+
}
|
|
21
|
+
export function getAgentsDir(localDefs) {
|
|
22
|
+
if (localDefs)
|
|
23
|
+
return join(process.cwd(), "uluops", "agents");
|
|
24
|
+
return join(getClaudeHome(), "agents");
|
|
25
|
+
}
|
|
26
|
+
export function getCommandsDir(localDefs) {
|
|
27
|
+
if (localDefs)
|
|
28
|
+
return join(process.cwd(), "uluops", "commands");
|
|
29
|
+
return join(getClaudeHome(), "commands");
|
|
30
|
+
}
|
|
31
|
+
export function getShellProfile() {
|
|
32
|
+
const shell = process.env["SHELL"] ?? "";
|
|
33
|
+
const home = homedir();
|
|
34
|
+
const os = platform();
|
|
35
|
+
if (shell.endsWith("/zsh")) {
|
|
36
|
+
return { shell: "zsh", path: join(home, ".zshrc") };
|
|
37
|
+
}
|
|
38
|
+
if (shell.endsWith("/bash")) {
|
|
39
|
+
const file = os === "darwin" ? ".bash_profile" : ".bashrc";
|
|
40
|
+
return { shell: "bash", path: join(home, file) };
|
|
41
|
+
}
|
|
42
|
+
if (shell.endsWith("/fish")) {
|
|
43
|
+
return {
|
|
44
|
+
shell: "fish",
|
|
45
|
+
path: join(home, ".config", "fish", "config.fish"),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Merger
|
|
3
|
+
*
|
|
4
|
+
* Safe read/merge/remove for Claude Code's settings.json.
|
|
5
|
+
* Only touches UluOps-managed hook entries — all other settings preserved.
|
|
6
|
+
*/
|
|
7
|
+
interface HookEntry {
|
|
8
|
+
type: string;
|
|
9
|
+
command: string;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
}
|
|
12
|
+
interface HookMatcher {
|
|
13
|
+
matcher?: string;
|
|
14
|
+
hooks: HookEntry[];
|
|
15
|
+
}
|
|
16
|
+
interface ClaudeSettings {
|
|
17
|
+
permissions?: Record<string, unknown>;
|
|
18
|
+
hooks?: Record<string, HookMatcher[]>;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read an existing settings.json, or return empty object if it doesn't exist.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readSettings(path: string): Promise<ClaudeSettings>;
|
|
25
|
+
/**
|
|
26
|
+
* Write settings back to file with stable formatting.
|
|
27
|
+
*/
|
|
28
|
+
export declare function writeSettings(path: string, settings: ClaudeSettings): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Merge the UluOps SubagentStop hook into settings, preserving all other
|
|
31
|
+
* hooks and settings. If a UluOps hook already exists, it is replaced.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mergeUluopsHook(settings: ClaudeSettings, hookCommand: string): ClaudeSettings;
|
|
34
|
+
/**
|
|
35
|
+
* Remove UluOps hook entries from settings. If SubagentStop becomes empty,
|
|
36
|
+
* the key is removed. If hooks becomes empty, the key is removed.
|
|
37
|
+
*/
|
|
38
|
+
export declare function removeUluopsHook(settings: ClaudeSettings): ClaudeSettings;
|
|
39
|
+
/**
|
|
40
|
+
* Check if a UluOps hook is configured in settings.
|
|
41
|
+
*/
|
|
42
|
+
export declare function hasUluopsHook(settings: ClaudeSettings): boolean;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Merger
|
|
3
|
+
*
|
|
4
|
+
* Safe read/merge/remove for Claude Code's settings.json.
|
|
5
|
+
* Only touches UluOps-managed hook entries — all other settings preserved.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
8
|
+
/** Marker embedded in hook commands to identify UluOps-managed entries */
|
|
9
|
+
const ULUOPS_HOOK_MARKER = "tools/agent-metrics";
|
|
10
|
+
/**
|
|
11
|
+
* Read an existing settings.json, or return empty object if it doesn't exist.
|
|
12
|
+
*/
|
|
13
|
+
export async function readSettings(path) {
|
|
14
|
+
try {
|
|
15
|
+
const raw = await readFile(path, "utf-8");
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Write settings back to file with stable formatting.
|
|
24
|
+
*/
|
|
25
|
+
export async function writeSettings(path, settings) {
|
|
26
|
+
await writeFile(path, JSON.stringify(settings, null, 2) + "\n");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Merge the UluOps SubagentStop hook into settings, preserving all other
|
|
30
|
+
* hooks and settings. If a UluOps hook already exists, it is replaced.
|
|
31
|
+
*/
|
|
32
|
+
export function mergeUluopsHook(settings, hookCommand) {
|
|
33
|
+
const hooks = settings.hooks ?? {};
|
|
34
|
+
const existing = hooks["SubagentStop"] ?? [];
|
|
35
|
+
// Remove any existing UluOps hook entries
|
|
36
|
+
const filtered = existing.filter((m) => !m.hooks.some((h) => h.command.includes(ULUOPS_HOOK_MARKER)));
|
|
37
|
+
// Add the new UluOps hook
|
|
38
|
+
const uluopsHook = {
|
|
39
|
+
hooks: [
|
|
40
|
+
{
|
|
41
|
+
type: "command",
|
|
42
|
+
command: hookCommand,
|
|
43
|
+
timeout: 30,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
...settings,
|
|
49
|
+
hooks: {
|
|
50
|
+
...hooks,
|
|
51
|
+
SubagentStop: [...filtered, uluopsHook],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Remove UluOps hook entries from settings. If SubagentStop becomes empty,
|
|
57
|
+
* the key is removed. If hooks becomes empty, the key is removed.
|
|
58
|
+
*/
|
|
59
|
+
export function removeUluopsHook(settings) {
|
|
60
|
+
const hooks = settings.hooks;
|
|
61
|
+
if (!hooks)
|
|
62
|
+
return settings;
|
|
63
|
+
const subagentStop = hooks["SubagentStop"];
|
|
64
|
+
if (!subagentStop)
|
|
65
|
+
return settings;
|
|
66
|
+
const filtered = subagentStop.filter((m) => !m.hooks.some((h) => h.command.includes(ULUOPS_HOOK_MARKER)));
|
|
67
|
+
const updatedHooks = { ...hooks };
|
|
68
|
+
if (filtered.length === 0) {
|
|
69
|
+
delete updatedHooks["SubagentStop"];
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
updatedHooks["SubagentStop"] = filtered;
|
|
73
|
+
}
|
|
74
|
+
const result = { ...settings };
|
|
75
|
+
if (Object.keys(updatedHooks).length === 0) {
|
|
76
|
+
delete result.hooks;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
result.hooks = updatedHooks;
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if a UluOps hook is configured in settings.
|
|
85
|
+
*/
|
|
86
|
+
export function hasUluopsHook(settings) {
|
|
87
|
+
const subagentStop = settings.hooks?.["SubagentStop"];
|
|
88
|
+
if (!subagentStop)
|
|
89
|
+
return false;
|
|
90
|
+
return subagentStop.some((m) => m.hooks.some((h) => h.command.includes(ULUOPS_HOOK_MARKER)));
|
|
91
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface AgentsResult {
|
|
2
|
+
copied: number;
|
|
3
|
+
skipped: number;
|
|
4
|
+
removed: number;
|
|
5
|
+
files: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function installAgents(localDefs: boolean, dryRun: boolean, existingManifestAgents?: string[]): Promise<AgentsResult>;
|
|
8
|
+
export declare function uninstallAgents(files: string[], defsPath: string): Promise<number>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { ASSETS_DIR, getAgentsDir } from "../lib/paths.js";
|
|
3
|
+
import { syncAssets, unlinkFiles } from "../lib/file-ops.js";
|
|
4
|
+
export async function installAgents(localDefs, dryRun, existingManifestAgents) {
|
|
5
|
+
return syncAssets({
|
|
6
|
+
srcDir: join(ASSETS_DIR, "agents"),
|
|
7
|
+
destDir: getAgentsDir(localDefs),
|
|
8
|
+
dryRun,
|
|
9
|
+
oldManifestFiles: existingManifestAgents,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export async function uninstallAgents(files, defsPath) {
|
|
13
|
+
return unlinkFiles(join(defsPath, "agents"), files);
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface AuthResult {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
email: string | null;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve API key from flags, env, credentials file, or interactive prompt.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveApiKey(options: {
|
|
9
|
+
apiKeyFlag?: string;
|
|
10
|
+
skipValidation?: boolean;
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
}): Promise<AuthResult>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve API key from flags, env, credentials file, or interactive prompt.
|
|
6
|
+
*/
|
|
7
|
+
export async function resolveApiKey(options) {
|
|
8
|
+
let apiKey = options.apiKeyFlag;
|
|
9
|
+
// 1. Flag
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
// 2. Env var
|
|
12
|
+
apiKey = process.env["ULUOPS_API_KEY"];
|
|
13
|
+
}
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
// 3. Credentials file
|
|
16
|
+
apiKey = await readCredentialsFile();
|
|
17
|
+
}
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
// 4. Interactive prompt
|
|
20
|
+
if (!options.interactive) {
|
|
21
|
+
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
|
+
}
|
|
23
|
+
const { input } = await import("@inquirer/prompts");
|
|
24
|
+
apiKey = await input({
|
|
25
|
+
message: "Enter your UluOps API key",
|
|
26
|
+
validate: (val) => {
|
|
27
|
+
if (!val.trim())
|
|
28
|
+
return "Get a key at app.uluops.ai/settings/api-keys";
|
|
29
|
+
if (!val.startsWith("ulr_"))
|
|
30
|
+
return "API keys start with ulr_ — get one at app.uluops.ai/settings/api-keys";
|
|
31
|
+
return true;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (!apiKey?.startsWith("ulr_")) {
|
|
36
|
+
throw new Error("API keys start with ulr_ — get one at app.uluops.ai/settings/api-keys");
|
|
37
|
+
}
|
|
38
|
+
// Validate against server
|
|
39
|
+
if (!options.skipValidation) {
|
|
40
|
+
const result = await validateKey(apiKey);
|
|
41
|
+
return { apiKey, email: result.email };
|
|
42
|
+
}
|
|
43
|
+
return { apiKey, email: null };
|
|
44
|
+
}
|
|
45
|
+
async function readCredentialsFile() {
|
|
46
|
+
try {
|
|
47
|
+
const credsPath = join(homedir(), ".uluops", "credentials.json");
|
|
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;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function validateKey(apiKey) {
|
|
58
|
+
const url = "https://api.uluops.ai/api/v1/registry/users/me";
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(url, {
|
|
61
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
62
|
+
});
|
|
63
|
+
if (res.status === 401) {
|
|
64
|
+
throw new Error("Invalid API key — generate a new one at app.uluops.ai/settings/api-keys");
|
|
65
|
+
}
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
throw new Error(`API returned ${res.status}. Try --skip-validation to continue offline.`);
|
|
68
|
+
}
|
|
69
|
+
const data = (await res.json());
|
|
70
|
+
return { email: data.email ?? null };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
// fetch() throws TypeError for network failures (ENOTFOUND, ECONNREFUSED).
|
|
74
|
+
// Re-thrown errors from the res.status checks above are plain Error instances.
|
|
75
|
+
if (err instanceof TypeError) {
|
|
76
|
+
throw new Error("Can't reach api.uluops.ai — check your connection. Use --skip-validation to continue offline.");
|
|
77
|
+
}
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface CommandsResult {
|
|
2
|
+
agentCommands: number;
|
|
3
|
+
workflowCommands: number;
|
|
4
|
+
skipped: number;
|
|
5
|
+
removed: number;
|
|
6
|
+
files: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function installCommands(localDefs: boolean, dryRun: boolean, existingManifestCommands?: string[]): Promise<CommandsResult>;
|
|
9
|
+
export declare function uninstallCommands(files: string[], defsPath: string): Promise<number>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readdir, mkdir, unlink } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { ASSETS_DIR, getCommandsDir } from "../lib/paths.js";
|
|
4
|
+
import { copyIfChanged } from "../lib/file-ops.js";
|
|
5
|
+
const SUBDIRS = ["agents", "workflows"];
|
|
6
|
+
export async function installCommands(localDefs, dryRun, existingManifestCommands) {
|
|
7
|
+
const srcBase = join(ASSETS_DIR, "commands");
|
|
8
|
+
const destBase = getCommandsDir(localDefs);
|
|
9
|
+
let agentCommands = 0;
|
|
10
|
+
let workflowCommands = 0;
|
|
11
|
+
let skipped = 0;
|
|
12
|
+
const allFiles = [];
|
|
13
|
+
for (const subdir of SUBDIRS) {
|
|
14
|
+
const srcDir = join(srcBase, subdir);
|
|
15
|
+
const destDir = join(destBase, subdir);
|
|
16
|
+
if (!dryRun) {
|
|
17
|
+
await mkdir(destDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
let files;
|
|
20
|
+
try {
|
|
21
|
+
files = (await readdir(srcDir)).filter((f) => f.endsWith(".md"));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
const relativePath = `${subdir}/${file}`;
|
|
28
|
+
const result = await copyIfChanged(join(srcDir, file), join(destDir, file), dryRun);
|
|
29
|
+
if (result === "copied") {
|
|
30
|
+
if (subdir === "agents")
|
|
31
|
+
agentCommands++;
|
|
32
|
+
else
|
|
33
|
+
workflowCommands++;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
skipped++;
|
|
37
|
+
}
|
|
38
|
+
allFiles.push(relativePath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Remove files that were in the old manifest but no longer in the package
|
|
42
|
+
let removed = 0;
|
|
43
|
+
if (existingManifestCommands) {
|
|
44
|
+
for (const oldFile of existingManifestCommands) {
|
|
45
|
+
if (!allFiles.includes(oldFile)) {
|
|
46
|
+
if (!dryRun) {
|
|
47
|
+
try {
|
|
48
|
+
await unlink(join(destBase, oldFile));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Already gone
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
removed++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
agentCommands,
|
|
60
|
+
workflowCommands,
|
|
61
|
+
skipped,
|
|
62
|
+
removed,
|
|
63
|
+
files: allFiles,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export async function uninstallCommands(files, defsPath) {
|
|
67
|
+
const { unlinkFiles } = await import("../lib/file-ops.js");
|
|
68
|
+
return unlinkFiles(join(defsPath, "commands"), files);
|
|
69
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { platform, release } from "node:os";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { getClaudeHome, getShellProfile } from "../lib/paths.js";
|
|
4
|
+
const SUPPORTED_PLATFORMS = new Set(["linux", "darwin", "win32"]);
|
|
5
|
+
export async function detect() {
|
|
6
|
+
const p = platform();
|
|
7
|
+
if (!SUPPORTED_PLATFORMS.has(p)) {
|
|
8
|
+
throw new Error(`Unsupported platform: ${p}. Expected linux, darwin, or win32.`);
|
|
9
|
+
}
|
|
10
|
+
const os = p;
|
|
11
|
+
const isWsl = os === "linux" && release().toLowerCase().includes("microsoft");
|
|
12
|
+
const profile = getShellProfile();
|
|
13
|
+
const nodeVersion = process.version;
|
|
14
|
+
let claudeHomeExists = false;
|
|
15
|
+
try {
|
|
16
|
+
await access(getClaudeHome());
|
|
17
|
+
claudeHomeExists = true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Does not exist
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
os,
|
|
24
|
+
isWsl,
|
|
25
|
+
shell: profile?.shell ?? null,
|
|
26
|
+
shellProfile: profile?.path ?? null,
|
|
27
|
+
nodeVersion,
|
|
28
|
+
claudeHomeExists,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface McpResult {
|
|
2
|
+
configPath: string;
|
|
3
|
+
scope: "global" | "local";
|
|
4
|
+
}
|
|
5
|
+
export declare function installMcp(apiKey: string, scope: "global" | "local", dryRun: boolean): Promise<McpResult>;
|
|
6
|
+
export declare function uninstallMcp(configPath: string): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFile, writeFile, access } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readConfig, mergeUluopsMcp, removeUluopsMcp, writeConfig, } from "../lib/config-merger.js";
|
|
4
|
+
import { getClaudeJsonPath, getLocalMcpPath } from "../lib/paths.js";
|
|
5
|
+
export async function installMcp(apiKey, scope, dryRun) {
|
|
6
|
+
const configPath = scope === "global" ? getClaudeJsonPath() : getLocalMcpPath();
|
|
7
|
+
const config = await readConfig(configPath);
|
|
8
|
+
const merged = mergeUluopsMcp(config, apiKey);
|
|
9
|
+
if (!dryRun) {
|
|
10
|
+
await writeConfig(configPath, merged);
|
|
11
|
+
}
|
|
12
|
+
// If local scope in a git repo, add .mcp.json to .gitignore
|
|
13
|
+
if (scope === "local" && !dryRun) {
|
|
14
|
+
await addToGitignore();
|
|
15
|
+
}
|
|
16
|
+
return { configPath, scope };
|
|
17
|
+
}
|
|
18
|
+
export async function uninstallMcp(configPath) {
|
|
19
|
+
const config = await readConfig(configPath);
|
|
20
|
+
const cleaned = removeUluopsMcp(config);
|
|
21
|
+
await writeConfig(configPath, cleaned);
|
|
22
|
+
}
|
|
23
|
+
async function addToGitignore() {
|
|
24
|
+
const gitignorePath = join(process.cwd(), ".gitignore");
|
|
25
|
+
try {
|
|
26
|
+
await access(join(process.cwd(), ".git"));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return; // Not a git repo
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const content = await readFile(gitignorePath, "utf-8");
|
|
33
|
+
if (content.includes(".mcp.json"))
|
|
34
|
+
return;
|
|
35
|
+
await writeFile(gitignorePath, content.trimEnd() + "\n.mcp.json\n");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
await writeFile(gitignorePath, ".mcp.json\n");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics Step
|
|
3
|
+
*
|
|
4
|
+
* Installs agent-metrics tool files to ~/.claude/tools/agent-metrics/
|
|
5
|
+
* and configures the SubagentStop hook in settings.json for auto-capture.
|
|
6
|
+
*/
|
|
7
|
+
/** Where agent-metrics dist files are installed */
|
|
8
|
+
export declare function getMetricsToolDir(): string;
|
|
9
|
+
/** Path to Claude Code's settings.json */
|
|
10
|
+
export declare function getSettingsPath(): string;
|
|
11
|
+
export interface MetricsResult {
|
|
12
|
+
toolFilesCopied: number;
|
|
13
|
+
hookConfigured: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Install agent-metrics: copy tool files and configure SubagentStop hook.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installMetrics(dryRun: boolean): Promise<MetricsResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Uninstall agent-metrics: remove hook from settings and optionally remove tool files.
|
|
21
|
+
*/
|
|
22
|
+
export declare function uninstallMetrics(dryRun: boolean): Promise<void>;
|