@inkeep/agents-cli 0.39.5 → 0.40.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/dist/_virtual/rolldown_runtime.js +7 -0
- package/dist/api.js +185 -0
- package/dist/commands/add.js +139 -0
- package/dist/commands/config.js +86 -0
- package/dist/commands/dev.js +259 -0
- package/dist/commands/init.js +360 -0
- package/dist/commands/list-agents.js +56 -0
- package/dist/commands/login.js +179 -0
- package/dist/commands/logout.js +56 -0
- package/dist/commands/profile.js +276 -0
- package/dist/{component-parser2.js → commands/pull-v3/component-parser.js} +16 -3
- package/dist/commands/pull-v3/component-updater.js +710 -0
- package/dist/commands/pull-v3/components/agent-generator.js +241 -0
- package/dist/commands/pull-v3/components/artifact-component-generator.js +127 -0
- package/dist/commands/pull-v3/components/context-config-generator.js +190 -0
- package/dist/commands/pull-v3/components/credential-generator.js +89 -0
- package/dist/commands/pull-v3/components/data-component-generator.js +102 -0
- package/dist/commands/pull-v3/components/environment-generator.js +170 -0
- package/dist/commands/pull-v3/components/external-agent-generator.js +75 -0
- package/dist/commands/pull-v3/components/function-tool-generator.js +94 -0
- package/dist/commands/pull-v3/components/mcp-tool-generator.js +86 -0
- package/dist/commands/pull-v3/components/project-generator.js +145 -0
- package/dist/commands/pull-v3/components/status-component-generator.js +92 -0
- package/dist/commands/pull-v3/components/sub-agent-generator.js +285 -0
- package/dist/commands/pull-v3/index.js +510 -0
- package/dist/commands/pull-v3/introspect-generator.js +278 -0
- package/dist/commands/pull-v3/llm-content-merger.js +192 -0
- package/dist/{new-component-generator.js → commands/pull-v3/new-component-generator.js} +14 -3
- package/dist/commands/pull-v3/project-comparator.js +914 -0
- package/dist/{project-index-generator.js → commands/pull-v3/project-index-generator.js} +1 -2
- package/dist/{project-validator.js → commands/pull-v3/project-validator.js} +4 -4
- package/dist/commands/pull-v3/targeted-typescript-placeholders.js +173 -0
- package/dist/commands/pull-v3/utils/component-registry.js +369 -0
- package/dist/commands/pull-v3/utils/component-tracker.js +165 -0
- package/dist/commands/pull-v3/utils/generator-utils.js +146 -0
- package/dist/commands/pull-v3/utils/model-provider-detector.js +44 -0
- package/dist/commands/push.js +326 -0
- package/dist/commands/status.js +89 -0
- package/dist/commands/update.js +97 -0
- package/dist/commands/whoami.js +38 -0
- package/dist/config.js +0 -1
- package/dist/env.js +30 -0
- package/dist/exports.js +3 -0
- package/dist/index.js +28 -196514
- package/dist/instrumentation.js +47 -0
- package/dist/types/agent.js +1 -0
- package/dist/types/tsx.d.d.ts +1 -0
- package/dist/utils/background-version-check.js +19 -0
- package/dist/utils/ci-environment.js +87 -0
- package/dist/utils/cli-pipeline.js +158 -0
- package/dist/utils/config.js +290 -0
- package/dist/utils/credentials.js +132 -0
- package/dist/utils/environment-loader.js +28 -0
- package/dist/utils/file-finder.js +62 -0
- package/dist/utils/json-comparator.js +185 -0
- package/dist/utils/json-comparison.js +232 -0
- package/dist/utils/mcp-runner.js +120 -0
- package/dist/utils/model-config.js +182 -0
- package/dist/utils/package-manager.js +58 -0
- package/dist/utils/profile-config.js +85 -0
- package/dist/utils/profiles/index.js +4 -0
- package/dist/utils/profiles/profile-manager.js +219 -0
- package/dist/utils/profiles/types.js +62 -0
- package/dist/utils/project-directory.js +33 -0
- package/dist/utils/project-loader.js +29 -0
- package/dist/utils/schema-introspection.js +44 -0
- package/dist/utils/templates.js +198 -0
- package/dist/utils/tsx-loader.js +27 -0
- package/dist/utils/url.js +26 -0
- package/dist/utils/version-check.js +79 -0
- package/package.json +4 -19
- package/dist/component-parser.js +0 -4
- package/dist/component-updater.js +0 -4
- package/dist/config2.js +0 -4
- package/dist/credential-stores.js +0 -4
- package/dist/environment-generator.js +0 -4
- package/dist/nodefs.js +0 -27
- package/dist/opfs-ahp.js +0 -368
- package/dist/project-loader.js +0 -4
- package/dist/tsx-loader.js +0 -4
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import http from "node:http";
|
|
6
|
+
|
|
7
|
+
//#region src/utils/mcp-runner.ts
|
|
8
|
+
/**
|
|
9
|
+
* MCP Runner - Loads and starts MCP servers from an agent file
|
|
10
|
+
* This is executed as a subprocess by the CLI
|
|
11
|
+
*/
|
|
12
|
+
const MCP_DIR = join(homedir(), ".inkeep", "mcp");
|
|
13
|
+
const REGISTRY_FILE = join(MCP_DIR, "servers.json");
|
|
14
|
+
if (!existsSync(MCP_DIR)) mkdirSync(MCP_DIR, { recursive: true });
|
|
15
|
+
async function startServers(agentPath$1) {
|
|
16
|
+
try {
|
|
17
|
+
const module = await import(agentPath$1);
|
|
18
|
+
const servers = module.servers || module.tools || [];
|
|
19
|
+
let agentId = "unknown";
|
|
20
|
+
if (module.agent && typeof module.agent.getId === "function") agentId = module.agent.getId();
|
|
21
|
+
const registeredServers = [];
|
|
22
|
+
let nextPort = 3100;
|
|
23
|
+
for (const server of servers) {
|
|
24
|
+
if (!server) continue;
|
|
25
|
+
const name = server.name || "unnamed";
|
|
26
|
+
const id = server.id || name;
|
|
27
|
+
const description = server.description || "";
|
|
28
|
+
if (typeof server.execute === "function" || typeof server.init === "function" || !server.serverUrl) {
|
|
29
|
+
const port = server.port || nextPort++;
|
|
30
|
+
if (typeof server.init === "function") await server.init();
|
|
31
|
+
if (typeof server.execute === "function") http.createServer(async (req, res) => {
|
|
32
|
+
if (req.method === "POST" && req.url === "/mcp") {
|
|
33
|
+
let body = "";
|
|
34
|
+
req.on("data", (chunk) => body += chunk);
|
|
35
|
+
req.on("end", async () => {
|
|
36
|
+
try {
|
|
37
|
+
const params = JSON.parse(body);
|
|
38
|
+
const result = await server.execute(params);
|
|
39
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
40
|
+
res.end(JSON.stringify({ result }));
|
|
41
|
+
} catch (error) {
|
|
42
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
43
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
res.writeHead(404);
|
|
48
|
+
res.end("Not Found");
|
|
49
|
+
}
|
|
50
|
+
}).listen(port, () => {
|
|
51
|
+
console.log(JSON.stringify({
|
|
52
|
+
type: "server_started",
|
|
53
|
+
name,
|
|
54
|
+
port,
|
|
55
|
+
deployment: "local"
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
registeredServers.push({
|
|
59
|
+
pid: process.pid,
|
|
60
|
+
agentId,
|
|
61
|
+
toolId: id,
|
|
62
|
+
name,
|
|
63
|
+
port,
|
|
64
|
+
deployment: "local",
|
|
65
|
+
transport: "http",
|
|
66
|
+
command: agentPath$1,
|
|
67
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
68
|
+
description
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
registeredServers.push({
|
|
72
|
+
pid: process.pid,
|
|
73
|
+
agentId,
|
|
74
|
+
toolId: id,
|
|
75
|
+
name,
|
|
76
|
+
serverUrl: server.serverUrl || server.getServerUrl?.(),
|
|
77
|
+
deployment: "remote",
|
|
78
|
+
transport: server.transport || "http",
|
|
79
|
+
command: agentPath$1,
|
|
80
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
81
|
+
description
|
|
82
|
+
});
|
|
83
|
+
console.log(JSON.stringify({
|
|
84
|
+
type: "server_registered",
|
|
85
|
+
name,
|
|
86
|
+
serverUrl: server.serverUrl,
|
|
87
|
+
deployment: "remote"
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
writeFileSync(REGISTRY_FILE, JSON.stringify({ servers: registeredServers }, null, 2));
|
|
92
|
+
console.log(JSON.stringify({
|
|
93
|
+
type: "all_started",
|
|
94
|
+
count: registeredServers.length
|
|
95
|
+
}));
|
|
96
|
+
process.stdin.resume();
|
|
97
|
+
process.on("SIGINT", () => {
|
|
98
|
+
console.log(JSON.stringify({ type: "shutting_down" }));
|
|
99
|
+
process.exit(0);
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(JSON.stringify({
|
|
103
|
+
type: "error",
|
|
104
|
+
message: error.message
|
|
105
|
+
}));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const agentPath = process.argv[2];
|
|
110
|
+
if (!agentPath) {
|
|
111
|
+
console.error(JSON.stringify({
|
|
112
|
+
type: "error",
|
|
113
|
+
message: "Agent path is required"
|
|
114
|
+
}));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
startServers(agentPath);
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
export { };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { ANTHROPIC_MODELS, GOOGLE_MODELS, OPENAI_MODELS } from "@inkeep/agents-core";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/model-config.ts
|
|
5
|
+
const defaultGeminiModelConfigurations = {
|
|
6
|
+
base: { model: GOOGLE_MODELS.GEMINI_2_5_FLASH },
|
|
7
|
+
structuredOutput: { model: GOOGLE_MODELS.GEMINI_2_5_FLASH_LITE },
|
|
8
|
+
summarizer: { model: GOOGLE_MODELS.GEMINI_2_5_FLASH_LITE }
|
|
9
|
+
};
|
|
10
|
+
const defaultOpenaiModelConfigurations = {
|
|
11
|
+
base: { model: OPENAI_MODELS.GPT_5_2 },
|
|
12
|
+
structuredOutput: { model: OPENAI_MODELS.GPT_4_1_MINI },
|
|
13
|
+
summarizer: { model: OPENAI_MODELS.GPT_4_1_NANO }
|
|
14
|
+
};
|
|
15
|
+
const defaultAnthropicModelConfigurations = {
|
|
16
|
+
base: { model: ANTHROPIC_MODELS.CLAUDE_SONNET_4_5 },
|
|
17
|
+
structuredOutput: { model: ANTHROPIC_MODELS.CLAUDE_SONNET_4_5 },
|
|
18
|
+
summarizer: { model: ANTHROPIC_MODELS.CLAUDE_SONNET_4_5 }
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Prompt user for model configuration (providers and model selection)
|
|
22
|
+
* This is shared between init and push commands
|
|
23
|
+
*/
|
|
24
|
+
async function promptForModelConfiguration() {
|
|
25
|
+
const providers = await p.multiselect({
|
|
26
|
+
message: "Which AI providers would you like to configure?",
|
|
27
|
+
options: [
|
|
28
|
+
{
|
|
29
|
+
value: "anthropic",
|
|
30
|
+
label: "Anthropic (Claude)"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: "openai",
|
|
34
|
+
label: "OpenAI (GPT)"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: "google",
|
|
38
|
+
label: "Google (Gemini)"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
required: true
|
|
42
|
+
});
|
|
43
|
+
if (p.isCancel(providers)) {
|
|
44
|
+
p.cancel("Operation cancelled");
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
const anthropicModels = [
|
|
48
|
+
{
|
|
49
|
+
label: "Claude Opus 4.5",
|
|
50
|
+
value: ANTHROPIC_MODELS.CLAUDE_OPUS_4_5
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: "Claude Opus 4.1",
|
|
54
|
+
value: ANTHROPIC_MODELS.CLAUDE_OPUS_4_1
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: "Claude Sonnet 4.5",
|
|
58
|
+
value: ANTHROPIC_MODELS.CLAUDE_SONNET_4_5
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: "Claude Sonnet 4",
|
|
62
|
+
value: ANTHROPIC_MODELS.CLAUDE_SONNET_4
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
label: "Claude Haiku 4.5",
|
|
66
|
+
value: ANTHROPIC_MODELS.CLAUDE_HAIKU_4_5
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: "Claude Haiku 3.5",
|
|
70
|
+
value: ANTHROPIC_MODELS.CLAUDE_3_5_HAIKU
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
const openaiModels = [
|
|
74
|
+
{
|
|
75
|
+
label: "GPT-5.2",
|
|
76
|
+
value: OPENAI_MODELS.GPT_5_2
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: "GPT-5.1",
|
|
80
|
+
value: OPENAI_MODELS.GPT_5_1
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
label: "GPT-4.1",
|
|
84
|
+
value: OPENAI_MODELS.GPT_4_1
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
label: "GPT-4.1 Mini",
|
|
88
|
+
value: OPENAI_MODELS.GPT_4_1_MINI
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
label: "GPT-4.1 Nano",
|
|
92
|
+
value: OPENAI_MODELS.GPT_4_1_NANO
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
label: "GPT-5",
|
|
96
|
+
value: OPENAI_MODELS.GPT_5
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: "GPT-5 Mini",
|
|
100
|
+
value: OPENAI_MODELS.GPT_5_MINI
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
label: "GPT-5 Nano",
|
|
104
|
+
value: OPENAI_MODELS.GPT_5_NANO
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
const googleModels = [
|
|
108
|
+
{
|
|
109
|
+
label: "Gemini 3 Pro Preview",
|
|
110
|
+
value: GOOGLE_MODELS.GEMINI_3_PRO_PREVIEW
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
label: "Gemini 3 Flash Preview",
|
|
114
|
+
value: GOOGLE_MODELS.GEMINI_3_FLASH_PREVIEW
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
label: "Gemini 2.5 Pro",
|
|
118
|
+
value: GOOGLE_MODELS.GEMINI_2_5_PRO
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
label: "Gemini 2.5 Flash",
|
|
122
|
+
value: GOOGLE_MODELS.GEMINI_2_5_FLASH
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
label: "Gemini 2.5 Flash Lite",
|
|
126
|
+
value: GOOGLE_MODELS.GEMINI_2_5_FLASH_LITE
|
|
127
|
+
}
|
|
128
|
+
];
|
|
129
|
+
const availableModels = [];
|
|
130
|
+
if (providers.includes("anthropic")) availableModels.push(...anthropicModels);
|
|
131
|
+
if (providers.includes("openai")) availableModels.push(...openaiModels);
|
|
132
|
+
if (providers.includes("google")) availableModels.push(...googleModels);
|
|
133
|
+
const baseModel = await p.select({
|
|
134
|
+
message: "Select your default model for general tasks (required):",
|
|
135
|
+
options: availableModels
|
|
136
|
+
});
|
|
137
|
+
if (p.isCancel(baseModel)) {
|
|
138
|
+
p.cancel("Operation cancelled");
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
const configureOptionalModels = await p.confirm({
|
|
142
|
+
message: "Would you like to configure optional models for structured output and summaries?",
|
|
143
|
+
initialValue: false
|
|
144
|
+
});
|
|
145
|
+
if (p.isCancel(configureOptionalModels)) {
|
|
146
|
+
p.cancel("Operation cancelled");
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
let structuredOutputModel = null;
|
|
150
|
+
let summarizerModel = null;
|
|
151
|
+
if (configureOptionalModels) {
|
|
152
|
+
const optionalChoices = [...availableModels, {
|
|
153
|
+
label: "Use base model",
|
|
154
|
+
value: null
|
|
155
|
+
}];
|
|
156
|
+
const structuredOutputResponse = await p.select({
|
|
157
|
+
message: "Select your model for structured output tasks (or use base model):",
|
|
158
|
+
options: optionalChoices
|
|
159
|
+
});
|
|
160
|
+
if (p.isCancel(structuredOutputResponse)) {
|
|
161
|
+
p.cancel("Operation cancelled");
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
structuredOutputModel = structuredOutputResponse;
|
|
165
|
+
const summarizerResponse = await p.select({
|
|
166
|
+
message: "Select your model for summaries and quick tasks (or use base model):",
|
|
167
|
+
options: optionalChoices
|
|
168
|
+
});
|
|
169
|
+
if (p.isCancel(summarizerResponse)) {
|
|
170
|
+
p.cancel("Operation cancelled");
|
|
171
|
+
process.exit(0);
|
|
172
|
+
}
|
|
173
|
+
summarizerModel = summarizerResponse;
|
|
174
|
+
}
|
|
175
|
+
const modelSettings = { base: { model: baseModel } };
|
|
176
|
+
if (structuredOutputModel) modelSettings.structuredOutput = { model: structuredOutputModel };
|
|
177
|
+
if (summarizerModel) modelSettings.summarizer = { model: summarizerModel };
|
|
178
|
+
return { modelSettings };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
export { defaultAnthropicModelConfigurations, defaultGeminiModelConfigurations, defaultOpenaiModelConfigurations, promptForModelConfiguration };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DEFAULT_PACKAGE_NAME } from "./version-check.js";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/package-manager.ts
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
/**
|
|
8
|
+
* Detect which package manager was used to install the CLI globally
|
|
9
|
+
*/
|
|
10
|
+
async function detectPackageManager() {
|
|
11
|
+
for (const manager of [
|
|
12
|
+
"pnpm",
|
|
13
|
+
"bun",
|
|
14
|
+
"npm",
|
|
15
|
+
"yarn"
|
|
16
|
+
]) try {
|
|
17
|
+
if (manager === "npm") {
|
|
18
|
+
const { stdout } = await execAsync(`npm list -g ${DEFAULT_PACKAGE_NAME} --depth=0`);
|
|
19
|
+
if (stdout.includes(DEFAULT_PACKAGE_NAME)) return "npm";
|
|
20
|
+
} else if (manager === "pnpm") {
|
|
21
|
+
const { stdout } = await execAsync(`pnpm list -g ${DEFAULT_PACKAGE_NAME} --depth=0`);
|
|
22
|
+
if (stdout.includes(DEFAULT_PACKAGE_NAME)) return "pnpm";
|
|
23
|
+
} else if (manager === "bun") {
|
|
24
|
+
const { stdout } = await execAsync("bun pm ls -g");
|
|
25
|
+
if (stdout.includes(DEFAULT_PACKAGE_NAME)) return "bun";
|
|
26
|
+
} else if (manager === "yarn") {
|
|
27
|
+
const { stdout } = await execAsync("yarn global list");
|
|
28
|
+
if (stdout.includes(DEFAULT_PACKAGE_NAME)) return "yarn";
|
|
29
|
+
}
|
|
30
|
+
} catch {}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the update command for a specific package manager
|
|
35
|
+
*/
|
|
36
|
+
function getUpdateCommand(manager, packageName = DEFAULT_PACKAGE_NAME) {
|
|
37
|
+
switch (manager) {
|
|
38
|
+
case "npm": return `npm install -g ${packageName}@latest`;
|
|
39
|
+
case "pnpm": return `pnpm add -g ${packageName}@latest`;
|
|
40
|
+
case "bun": return `bun add -g ${packageName}@latest`;
|
|
41
|
+
case "yarn": return `yarn global add ${packageName}@latest`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Execute update command
|
|
46
|
+
*/
|
|
47
|
+
async function executeUpdate(manager) {
|
|
48
|
+
if (![
|
|
49
|
+
"npm",
|
|
50
|
+
"pnpm",
|
|
51
|
+
"bun",
|
|
52
|
+
"yarn"
|
|
53
|
+
].includes(manager)) throw new Error(`Unsupported package manager: ${manager}`);
|
|
54
|
+
await execAsync(getUpdateCommand(manager));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { detectPackageManager, executeUpdate, getUpdateCommand };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getCredentialExpiryInfo, loadCredentials } from "./credentials.js";
|
|
2
|
+
import { ProfileManager } from "./profiles/profile-manager.js";
|
|
3
|
+
import "./profiles/index.js";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import dotenv from "dotenv";
|
|
8
|
+
|
|
9
|
+
//#region src/utils/profile-config.ts
|
|
10
|
+
async function resolveProfileConfig(options = {}) {
|
|
11
|
+
const profileManager = new ProfileManager();
|
|
12
|
+
let profile;
|
|
13
|
+
let profileName;
|
|
14
|
+
try {
|
|
15
|
+
if (options.profileName) {
|
|
16
|
+
const foundProfile = profileManager.getProfile(options.profileName);
|
|
17
|
+
if (!foundProfile) {
|
|
18
|
+
console.error(chalk.red(`Profile '${options.profileName}' not found.`));
|
|
19
|
+
console.log(chalk.gray("Run \"inkeep profile list\" to see available profiles."));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
profile = foundProfile;
|
|
23
|
+
profileName = options.profileName;
|
|
24
|
+
} else {
|
|
25
|
+
profile = profileManager.getActiveProfile();
|
|
26
|
+
profileName = profile.name;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
return {
|
|
30
|
+
profileName: "default",
|
|
31
|
+
agentsManageApiUrl: "http://localhost:3002",
|
|
32
|
+
agentsRunApiUrl: "http://localhost:3003",
|
|
33
|
+
manageUiUrl: "http://localhost:3000",
|
|
34
|
+
environment: "development",
|
|
35
|
+
credentialKey: "auth-credentials",
|
|
36
|
+
isAuthenticated: false
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const credentials = await loadCredentials(profile.credential);
|
|
40
|
+
let accessToken;
|
|
41
|
+
let isAuthenticated = false;
|
|
42
|
+
let authExpiry;
|
|
43
|
+
if (credentials) {
|
|
44
|
+
const expiryInfo = getCredentialExpiryInfo(credentials);
|
|
45
|
+
if (!expiryInfo.isExpired) {
|
|
46
|
+
accessToken = credentials.accessToken;
|
|
47
|
+
isAuthenticated = true;
|
|
48
|
+
authExpiry = expiryInfo.expiresIn;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (options.projectDir && profile.environment) {
|
|
52
|
+
const envFile = join(options.projectDir, `.env.${profile.environment}`);
|
|
53
|
+
if (existsSync(envFile)) dotenv.config({ path: envFile });
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
profileName,
|
|
57
|
+
tenantId: credentials?.organizationId,
|
|
58
|
+
agentsManageApiUrl: profile.remote.manageApi,
|
|
59
|
+
agentsRunApiUrl: profile.remote.runApi,
|
|
60
|
+
manageUiUrl: profile.remote.manageUi,
|
|
61
|
+
environment: profile.environment,
|
|
62
|
+
credentialKey: profile.credential,
|
|
63
|
+
accessToken,
|
|
64
|
+
isAuthenticated,
|
|
65
|
+
authExpiry
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function logProfileConfig(config, quiet = false) {
|
|
69
|
+
if (quiet) return;
|
|
70
|
+
console.log(chalk.gray(`Using profile: ${chalk.cyan(config.profileName)}`));
|
|
71
|
+
console.log(chalk.gray(` Remote: ${config.agentsManageApiUrl}`));
|
|
72
|
+
console.log(chalk.gray(` Environment: ${config.environment}`));
|
|
73
|
+
if (config.isAuthenticated) {
|
|
74
|
+
const expiryText = config.authExpiry ? ` (expires in ${config.authExpiry})` : "";
|
|
75
|
+
console.log(chalk.gray(` Auth: ${chalk.green("authenticated")}${expiryText}`));
|
|
76
|
+
} else console.log(chalk.gray(` Auth: ${chalk.yellow("not authenticated")}`));
|
|
77
|
+
}
|
|
78
|
+
function getAuthHeaders(config) {
|
|
79
|
+
const headers = {};
|
|
80
|
+
if (config.accessToken) headers["Authorization"] = `Bearer ${config.accessToken}`;
|
|
81
|
+
return headers;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
export { getAuthHeaders, logProfileConfig, resolveProfileConfig };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { CLOUD_REMOTE, DEFAULT_CLOUD_PROFILE, DEFAULT_PROFILES_CONFIG, explicitRemoteSchema, profileNameSchema, profileSchema, profilesConfigSchema, remoteSchema } from "./types.js";
|
|
2
|
+
import { ProfileError, ProfileManager, profileManager } from "./profile-manager.js";
|
|
3
|
+
|
|
4
|
+
export { CLOUD_REMOTE, DEFAULT_CLOUD_PROFILE, DEFAULT_PROFILES_CONFIG, ProfileError, ProfileManager, explicitRemoteSchema, profileManager, profileNameSchema, profileSchema, profilesConfigSchema, remoteSchema };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { __require } from "../../_virtual/rolldown_runtime.js";
|
|
2
|
+
import { CLOUD_REMOTE, DEFAULT_PROFILES_CONFIG, profileNameSchema, profilesConfigSchema } from "./types.js";
|
|
3
|
+
import { getLogger } from "@inkeep/agents-core";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { homedir, tmpdir } from "node:os";
|
|
7
|
+
import * as yaml from "yaml";
|
|
8
|
+
|
|
9
|
+
//#region src/utils/profiles/profile-manager.ts
|
|
10
|
+
const logger = getLogger("profile-manager");
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown when profile operations fail
|
|
13
|
+
*/
|
|
14
|
+
var ProfileError = class extends Error {
|
|
15
|
+
constructor(message, code) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.name = "ProfileError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Format Zod errors into a human-readable message
|
|
23
|
+
* Compatible with both Zod v3 (errors) and Zod v4 (issues)
|
|
24
|
+
*/
|
|
25
|
+
function formatZodErrors(error) {
|
|
26
|
+
const issues = error.issues ?? error.errors ?? [];
|
|
27
|
+
if (!Array.isArray(issues) || issues.length === 0) return error.message || "Validation failed";
|
|
28
|
+
return issues.map((err) => {
|
|
29
|
+
return `${err.path && err.path.length > 0 ? `at '${err.path.join(".")}'` : ""}: ${err.message || "Invalid value"}`.trim();
|
|
30
|
+
}).join("\n ");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* ProfileManager - handles loading, saving, and managing CLI profiles
|
|
34
|
+
*
|
|
35
|
+
* Profiles are stored in ~/.inkeep/profiles.yaml and allow users to switch
|
|
36
|
+
* between different Inkeep deployments (cloud, local, self-hosted).
|
|
37
|
+
*/
|
|
38
|
+
var ProfileManager = class {
|
|
39
|
+
profilesDir;
|
|
40
|
+
profilesPath;
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.profilesDir = options?.profilesDir ?? join(homedir(), ".inkeep");
|
|
43
|
+
this.profilesPath = join(this.profilesDir, "profiles.yaml");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get the path to the profiles.yaml file
|
|
47
|
+
* Creates the ~/.inkeep directory if it doesn't exist
|
|
48
|
+
*/
|
|
49
|
+
getProfilePath() {
|
|
50
|
+
if (!existsSync(this.profilesDir)) {
|
|
51
|
+
logger.info({ dir: this.profilesDir }, "Creating profiles directory");
|
|
52
|
+
mkdirSync(this.profilesDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
return this.profilesPath;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if the profiles file exists
|
|
58
|
+
*/
|
|
59
|
+
profilesFileExists() {
|
|
60
|
+
return existsSync(this.profilesPath);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Load and validate profiles from YAML file
|
|
64
|
+
* Creates default config if file doesn't exist
|
|
65
|
+
*/
|
|
66
|
+
loadProfiles() {
|
|
67
|
+
const profilePath = this.getProfilePath();
|
|
68
|
+
if (!existsSync(profilePath)) {
|
|
69
|
+
logger.info({}, "Profiles file not found, creating default");
|
|
70
|
+
this.saveProfiles(DEFAULT_PROFILES_CONFIG);
|
|
71
|
+
return DEFAULT_PROFILES_CONFIG;
|
|
72
|
+
}
|
|
73
|
+
let content;
|
|
74
|
+
try {
|
|
75
|
+
content = readFileSync(profilePath, "utf-8");
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new ProfileError(`Failed to read profiles file: ${profilePath}\n${error instanceof Error ? error.message : String(error)}`, "FILE_NOT_FOUND");
|
|
78
|
+
}
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = yaml.parse(content);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw new ProfileError(`Failed to parse profiles.yaml: ${error instanceof Error ? error.message : String(error)}`, "PARSE_ERROR");
|
|
84
|
+
}
|
|
85
|
+
const result = profilesConfigSchema.safeParse(parsed);
|
|
86
|
+
if (!result.success) throw new ProfileError(`Invalid profiles.yaml:\n ${formatZodErrors(result.error)}`, "VALIDATION_ERROR");
|
|
87
|
+
const config = result.data;
|
|
88
|
+
if (!config.profiles[config.activeProfile]) throw new ProfileError(`Active profile '${config.activeProfile}' does not exist in profiles`, "VALIDATION_ERROR");
|
|
89
|
+
logger.info({ activeProfile: config.activeProfile }, "Profiles loaded");
|
|
90
|
+
return config;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Save profiles to YAML file atomically (write to temp, then rename)
|
|
94
|
+
*/
|
|
95
|
+
saveProfiles(profiles) {
|
|
96
|
+
const result = profilesConfigSchema.safeParse(profiles);
|
|
97
|
+
if (!result.success) throw new ProfileError(`Invalid profiles configuration:\n ${formatZodErrors(result.error)}`, "VALIDATION_ERROR");
|
|
98
|
+
if (!profiles.profiles[profiles.activeProfile]) throw new ProfileError(`Active profile '${profiles.activeProfile}' does not exist in profiles`, "VALIDATION_ERROR");
|
|
99
|
+
const profilePath = this.getProfilePath();
|
|
100
|
+
const tempPath = join(tmpdir(), `inkeep-profiles-${Date.now()}.yaml`);
|
|
101
|
+
const yamlContent = yaml.stringify(profiles, {
|
|
102
|
+
indent: 2,
|
|
103
|
+
lineWidth: 0
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
writeFileSync(tempPath, yamlContent, "utf-8");
|
|
107
|
+
renameSync(tempPath, profilePath);
|
|
108
|
+
logger.info({ path: profilePath }, "Profiles saved");
|
|
109
|
+
} catch (error) {
|
|
110
|
+
try {
|
|
111
|
+
if (existsSync(tempPath)) __require("node:fs").unlinkSync(tempPath);
|
|
112
|
+
} catch {}
|
|
113
|
+
throw new ProfileError(`Failed to save profiles: ${error instanceof Error ? error.message : String(error)}`, "WRITE_ERROR");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get the currently active profile configuration
|
|
118
|
+
*/
|
|
119
|
+
getActiveProfile() {
|
|
120
|
+
const config = this.loadProfiles();
|
|
121
|
+
const profileName = config.activeProfile;
|
|
122
|
+
const profile = config.profiles[profileName];
|
|
123
|
+
if (!profile) throw new ProfileError(`Active profile '${profileName}' not found in profiles`, "PROFILE_NOT_FOUND");
|
|
124
|
+
return {
|
|
125
|
+
name: profileName,
|
|
126
|
+
remote: this.resolveRemoteUrls(profile),
|
|
127
|
+
credential: profile.credential,
|
|
128
|
+
environment: profile.environment
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Resolve remote URLs from profile configuration
|
|
133
|
+
* If remote is 'cloud', returns baked-in cloud URLs
|
|
134
|
+
* Otherwise returns the explicit URLs
|
|
135
|
+
*/
|
|
136
|
+
resolveRemoteUrls(profile) {
|
|
137
|
+
if (profile.remote === "cloud") return { ...CLOUD_REMOTE };
|
|
138
|
+
return { ...profile.remote };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get a specific profile by name
|
|
142
|
+
*/
|
|
143
|
+
getProfile(name) {
|
|
144
|
+
const profile = this.loadProfiles().profiles[name];
|
|
145
|
+
if (!profile) return null;
|
|
146
|
+
return {
|
|
147
|
+
name,
|
|
148
|
+
remote: this.resolveRemoteUrls(profile),
|
|
149
|
+
credential: profile.credential,
|
|
150
|
+
environment: profile.environment
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* List all profiles with their resolved URLs
|
|
155
|
+
*/
|
|
156
|
+
listProfiles() {
|
|
157
|
+
const config = this.loadProfiles();
|
|
158
|
+
return {
|
|
159
|
+
profiles: Object.entries(config.profiles).map(([name, profile]) => ({
|
|
160
|
+
name,
|
|
161
|
+
remote: this.resolveRemoteUrls(profile),
|
|
162
|
+
credential: profile.credential,
|
|
163
|
+
environment: profile.environment
|
|
164
|
+
})),
|
|
165
|
+
activeProfile: config.activeProfile
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Add a new profile
|
|
170
|
+
*/
|
|
171
|
+
addProfile(name, profile) {
|
|
172
|
+
const nameResult = profileNameSchema.safeParse(name);
|
|
173
|
+
if (!nameResult.success) throw new ProfileError(`Invalid profile name: ${formatZodErrors(nameResult.error)}`, "VALIDATION_ERROR");
|
|
174
|
+
const config = this.loadProfiles();
|
|
175
|
+
if (config.profiles[name]) throw new ProfileError(`Profile '${name}' already exists`, "PROFILE_EXISTS");
|
|
176
|
+
config.profiles[name] = profile;
|
|
177
|
+
this.saveProfiles(config);
|
|
178
|
+
logger.info({ name }, "Profile added");
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Set the active profile
|
|
182
|
+
*/
|
|
183
|
+
setActiveProfile(name) {
|
|
184
|
+
const config = this.loadProfiles();
|
|
185
|
+
if (!config.profiles[name]) throw new ProfileError(`Profile '${name}' does not exist`, "PROFILE_NOT_FOUND");
|
|
186
|
+
config.activeProfile = name;
|
|
187
|
+
this.saveProfiles(config);
|
|
188
|
+
logger.info({ name }, "Active profile set");
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Remove a profile
|
|
192
|
+
* Cannot remove the currently active profile
|
|
193
|
+
*/
|
|
194
|
+
removeProfile(name) {
|
|
195
|
+
const config = this.loadProfiles();
|
|
196
|
+
if (!config.profiles[name]) throw new ProfileError(`Profile '${name}' does not exist`, "PROFILE_NOT_FOUND");
|
|
197
|
+
if (config.activeProfile === name) throw new ProfileError(`Cannot remove active profile '${name}'. Switch to a different profile first.`, "ACTIVE_PROFILE_DELETE");
|
|
198
|
+
delete config.profiles[name];
|
|
199
|
+
this.saveProfiles(config);
|
|
200
|
+
logger.info({ name }, "Profile removed");
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Check if a credential reference exists in the keychain
|
|
204
|
+
* Returns true if it exists, false otherwise
|
|
205
|
+
* This is a warning check - missing credentials don't block operations
|
|
206
|
+
*/
|
|
207
|
+
async checkCredentialExists(credentialRef) {
|
|
208
|
+
try {
|
|
209
|
+
const { KeyChainStore } = await import("@inkeep/agents-core/credential-stores");
|
|
210
|
+
return await new KeyChainStore("auth", "inkeep-cli").get(credentialRef) !== null;
|
|
211
|
+
} catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const profileManager = new ProfileManager();
|
|
217
|
+
|
|
218
|
+
//#endregion
|
|
219
|
+
export { ProfileError, ProfileManager, profileManager };
|