@inkeep/agents-cli 0.39.4 → 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,360 @@
|
|
|
1
|
+
import { checkKeychainAvailability, loadCredentials } from "../utils/credentials.js";
|
|
2
|
+
import { DEFAULT_PROFILES_CONFIG } from "../utils/profiles/types.js";
|
|
3
|
+
import { ProfileManager } from "../utils/profiles/profile-manager.js";
|
|
4
|
+
import "../utils/profiles/index.js";
|
|
5
|
+
import { loginCommand } from "./login.js";
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
8
|
+
import * as p from "@clack/prompts";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
|
|
11
|
+
//#region src/commands/init.ts
|
|
12
|
+
/**
|
|
13
|
+
* Find the most appropriate directory for the config file by looking for
|
|
14
|
+
* common project root indicators
|
|
15
|
+
*/
|
|
16
|
+
function findProjectRoot(startPath) {
|
|
17
|
+
let currentPath = resolve(startPath);
|
|
18
|
+
const root = dirname(currentPath);
|
|
19
|
+
const rootIndicators = [
|
|
20
|
+
"package.json",
|
|
21
|
+
".git",
|
|
22
|
+
".gitignore",
|
|
23
|
+
"tsconfig.json",
|
|
24
|
+
"package-lock.json",
|
|
25
|
+
"yarn.lock",
|
|
26
|
+
"pnpm-lock.yaml"
|
|
27
|
+
];
|
|
28
|
+
while (currentPath !== root) {
|
|
29
|
+
const files = readdirSync(currentPath);
|
|
30
|
+
if (rootIndicators.some((indicator) => files.includes(indicator))) return currentPath;
|
|
31
|
+
const parentPath = dirname(currentPath);
|
|
32
|
+
if (parentPath === currentPath) break;
|
|
33
|
+
currentPath = parentPath;
|
|
34
|
+
}
|
|
35
|
+
return startPath;
|
|
36
|
+
}
|
|
37
|
+
async function initCommand(options) {
|
|
38
|
+
if (options?.local) {
|
|
39
|
+
await localInitCommand(options);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
await cloudInitCommand(options);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Full onboarding wizard for Inkeep Cloud customers
|
|
46
|
+
*/
|
|
47
|
+
async function cloudInitCommand(options) {
|
|
48
|
+
console.log();
|
|
49
|
+
console.log(chalk.bold("Welcome to Inkeep!"));
|
|
50
|
+
console.log();
|
|
51
|
+
const s = p.spinner();
|
|
52
|
+
const profileManager = new ProfileManager();
|
|
53
|
+
s.start("Checking authentication...");
|
|
54
|
+
let isAuthenticated = false;
|
|
55
|
+
let credentials = null;
|
|
56
|
+
const { available: keychainAvailable } = await checkKeychainAvailability();
|
|
57
|
+
if (keychainAvailable) try {
|
|
58
|
+
const existingCreds = await loadCredentials("inkeep-cloud");
|
|
59
|
+
if (existingCreds && existingCreds.accessToken && existingCreds.organizationId) {
|
|
60
|
+
credentials = {
|
|
61
|
+
accessToken: existingCreds.accessToken,
|
|
62
|
+
organizationId: existingCreds.organizationId,
|
|
63
|
+
userEmail: existingCreds.userEmail
|
|
64
|
+
};
|
|
65
|
+
isAuthenticated = true;
|
|
66
|
+
s.stop(`Logged in as ${chalk.cyan(existingCreds.userEmail)}`);
|
|
67
|
+
}
|
|
68
|
+
} catch {}
|
|
69
|
+
if (!isAuthenticated) {
|
|
70
|
+
s.stop("Not logged in");
|
|
71
|
+
console.log(chalk.yellow("→ Opening browser for login..."));
|
|
72
|
+
console.log();
|
|
73
|
+
await loginCommand({});
|
|
74
|
+
const newCreds = await loadCredentials("inkeep-cloud");
|
|
75
|
+
if (newCreds && newCreds.accessToken && newCreds.organizationId) {
|
|
76
|
+
credentials = {
|
|
77
|
+
accessToken: newCreds.accessToken,
|
|
78
|
+
organizationId: newCreds.organizationId,
|
|
79
|
+
userEmail: newCreds.userEmail
|
|
80
|
+
};
|
|
81
|
+
isAuthenticated = true;
|
|
82
|
+
} else {
|
|
83
|
+
console.error(chalk.red("Login failed. Please try again."));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
s.start("Fetching your organizations...");
|
|
88
|
+
let selectedTenantId;
|
|
89
|
+
let selectedTenantName;
|
|
90
|
+
try {
|
|
91
|
+
const response = await fetch("https://manage-api.inkeep.com/api/cli/me", { headers: { Authorization: `Bearer ${credentials.accessToken}` } });
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
s.stop("Failed to fetch organizations");
|
|
94
|
+
console.error(chalk.red("Could not fetch your organizations. Please try logging in again."));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const data = await response.json();
|
|
98
|
+
selectedTenantId = data.organization.id;
|
|
99
|
+
selectedTenantName = data.organization.name;
|
|
100
|
+
s.stop(`Organization: ${chalk.cyan(selectedTenantName)}`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
s.stop("Failed to fetch organizations");
|
|
103
|
+
console.error(chalk.red("Network error. Please check your connection."));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
s.start(`Fetching projects for ${selectedTenantName}...`);
|
|
107
|
+
let projects = [];
|
|
108
|
+
try {
|
|
109
|
+
const response = await fetch(`https://manage-api.inkeep.com/tenants/${selectedTenantId}/projects?limit=100`, { headers: { Authorization: `Bearer ${credentials.accessToken}` } });
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
s.stop("Failed to fetch projects");
|
|
112
|
+
console.error(chalk.red("Could not fetch projects."));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
projects = (await response.json()).data || [];
|
|
116
|
+
s.stop(`Found ${projects.length} project(s)`);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
s.stop("Failed to fetch projects");
|
|
119
|
+
console.error(chalk.red("Network error. Please check your connection."));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
let targetDir;
|
|
123
|
+
if (options?.path) targetDir = resolve(process.cwd(), options.path);
|
|
124
|
+
else {
|
|
125
|
+
const suggestedPath = "./inkeep-agents";
|
|
126
|
+
const confirmedPath = await p.text({
|
|
127
|
+
message: "Where should we create the project files?",
|
|
128
|
+
placeholder: suggestedPath,
|
|
129
|
+
initialValue: suggestedPath,
|
|
130
|
+
validate: (input) => {
|
|
131
|
+
if (!input || input.trim() === "") return "Path is required";
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
if (p.isCancel(confirmedPath)) {
|
|
135
|
+
p.cancel("Setup cancelled");
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
targetDir = resolve(process.cwd(), confirmedPath);
|
|
139
|
+
}
|
|
140
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
141
|
+
console.log();
|
|
142
|
+
console.log(chalk.bold("Creating directory structure..."));
|
|
143
|
+
const createdProjects = [];
|
|
144
|
+
if (projects.length === 0) {
|
|
145
|
+
const templateDir = join(targetDir, "my-agent");
|
|
146
|
+
mkdirSync(templateDir, { recursive: true });
|
|
147
|
+
const configContent = generateConfigFile(selectedTenantId, "my-agent");
|
|
148
|
+
writeFileSync(join(templateDir, "inkeep.config.ts"), configContent);
|
|
149
|
+
const indexContent = generateIndexFile("my-agent");
|
|
150
|
+
writeFileSync(join(templateDir, "index.ts"), indexContent);
|
|
151
|
+
console.log(chalk.gray(` ${targetDir}/`));
|
|
152
|
+
console.log(chalk.gray(` └── my-agent/`));
|
|
153
|
+
console.log(chalk.gray(` ├── inkeep.config.ts`));
|
|
154
|
+
console.log(chalk.gray(` └── index.ts`));
|
|
155
|
+
createdProjects.push("my-agent");
|
|
156
|
+
} else {
|
|
157
|
+
console.log(chalk.gray(` ${targetDir}/`));
|
|
158
|
+
for (const project of projects) {
|
|
159
|
+
const projectDir = join(targetDir, sanitizeProjectName(project.name || project.id));
|
|
160
|
+
mkdirSync(projectDir, { recursive: true });
|
|
161
|
+
const configContent = generateConfigFile(selectedTenantId, project.id);
|
|
162
|
+
writeFileSync(join(projectDir, "inkeep.config.ts"), configContent);
|
|
163
|
+
const indexContent = generateIndexFile(project.id);
|
|
164
|
+
writeFileSync(join(projectDir, "index.ts"), indexContent);
|
|
165
|
+
const displayName = sanitizeProjectName(project.name || project.id);
|
|
166
|
+
console.log(chalk.gray(` ├── ${displayName}/`));
|
|
167
|
+
console.log(chalk.gray(` │ ├── inkeep.config.ts`));
|
|
168
|
+
console.log(chalk.gray(` │ └── index.ts`));
|
|
169
|
+
createdProjects.push(displayName);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
console.log();
|
|
173
|
+
console.log(chalk.green(`✓ Created ${createdProjects.length} project(s)`));
|
|
174
|
+
console.log();
|
|
175
|
+
console.log(chalk.bold("Creating environment templates..."));
|
|
176
|
+
const envDevContent = generateEnvTemplate("development");
|
|
177
|
+
const envProdContent = generateEnvTemplate("production");
|
|
178
|
+
writeFileSync(join(targetDir, ".env.development"), envDevContent);
|
|
179
|
+
writeFileSync(join(targetDir, ".env.production"), envProdContent);
|
|
180
|
+
console.log(chalk.green(" ✓ .env.development"));
|
|
181
|
+
console.log(chalk.green(" ✓ .env.production"));
|
|
182
|
+
if (!profileManager.profilesFileExists()) {
|
|
183
|
+
console.log();
|
|
184
|
+
console.log(chalk.bold("Setting up profile..."));
|
|
185
|
+
profileManager.saveProfiles(DEFAULT_PROFILES_CONFIG);
|
|
186
|
+
console.log(chalk.green(" ✓ Created cloud profile"));
|
|
187
|
+
}
|
|
188
|
+
console.log();
|
|
189
|
+
console.log(chalk.green.bold("Setup complete!"));
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(chalk.bold("Next steps:"));
|
|
192
|
+
console.log(chalk.gray(` 1. cd ${targetDir}`));
|
|
193
|
+
console.log(chalk.gray(" 2. Add your API keys to .env.development"));
|
|
194
|
+
if (projects.length > 0) console.log(chalk.gray(" 3. Run: inkeep pull --all"));
|
|
195
|
+
else {
|
|
196
|
+
console.log(chalk.gray(" 3. Define your agent in index.ts"));
|
|
197
|
+
console.log(chalk.gray(" 4. Run: inkeep push"));
|
|
198
|
+
}
|
|
199
|
+
console.log();
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Simple local init for self-hosted deployments
|
|
203
|
+
*/
|
|
204
|
+
async function localInitCommand(options) {
|
|
205
|
+
let configPath;
|
|
206
|
+
if (options?.path) {
|
|
207
|
+
const resolvedPath = resolve(process.cwd(), options.path);
|
|
208
|
+
if (options.path.endsWith(".ts") || options.path.endsWith(".js")) configPath = resolvedPath;
|
|
209
|
+
else configPath = join(resolvedPath, "inkeep.config.ts");
|
|
210
|
+
} else {
|
|
211
|
+
const suggestedPath = join(findProjectRoot(process.cwd()), "inkeep.config.ts");
|
|
212
|
+
if (options?.interactive === false) configPath = suggestedPath;
|
|
213
|
+
else {
|
|
214
|
+
const confirmedPath = await p.text({
|
|
215
|
+
message: "Where should the config file be created?",
|
|
216
|
+
initialValue: suggestedPath,
|
|
217
|
+
validate: (input) => {
|
|
218
|
+
if (!input || input.trim() === "") return "Path is required";
|
|
219
|
+
const dir = input.endsWith(".ts") || input.endsWith(".js") ? dirname(input) : input;
|
|
220
|
+
const resolvedDir = resolve(process.cwd(), dir);
|
|
221
|
+
if (!existsSync(resolvedDir)) return `Directory does not exist: ${resolvedDir}`;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
if (p.isCancel(confirmedPath)) {
|
|
225
|
+
p.cancel("Operation cancelled");
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
const resolvedPath = resolve(process.cwd(), confirmedPath);
|
|
229
|
+
configPath = confirmedPath.endsWith(".ts") || confirmedPath.endsWith(".js") ? resolvedPath : join(resolvedPath, "inkeep.config.ts");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (existsSync(configPath)) {
|
|
233
|
+
const overwrite = await p.confirm({
|
|
234
|
+
message: `${basename(configPath)} already exists. Overwrite?`,
|
|
235
|
+
initialValue: false
|
|
236
|
+
});
|
|
237
|
+
if (p.isCancel(overwrite)) {
|
|
238
|
+
p.cancel("Operation cancelled");
|
|
239
|
+
process.exit(0);
|
|
240
|
+
}
|
|
241
|
+
if (!overwrite) {
|
|
242
|
+
console.log(chalk.yellow("Init cancelled."));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const tenantId = await p.text({
|
|
247
|
+
message: "Enter your tenant ID:",
|
|
248
|
+
validate: (input) => {
|
|
249
|
+
if (!input || input.trim() === "") return "Tenant ID is required";
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
if (p.isCancel(tenantId)) {
|
|
253
|
+
p.cancel("Operation cancelled");
|
|
254
|
+
process.exit(0);
|
|
255
|
+
}
|
|
256
|
+
const validateUrl = (input) => {
|
|
257
|
+
try {
|
|
258
|
+
if (input && input.trim() !== "") {
|
|
259
|
+
new URL(input);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
} catch {
|
|
264
|
+
return "Please enter a valid URL";
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const manageApiUrl = await p.text({
|
|
268
|
+
message: "Enter the Management API URL:",
|
|
269
|
+
placeholder: "http://localhost:3002",
|
|
270
|
+
initialValue: "http://localhost:3002",
|
|
271
|
+
validate: validateUrl
|
|
272
|
+
});
|
|
273
|
+
if (p.isCancel(manageApiUrl)) {
|
|
274
|
+
p.cancel("Operation cancelled");
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
const runApiUrl = await p.text({
|
|
278
|
+
message: "Enter the Run API URL:",
|
|
279
|
+
placeholder: "http://localhost:3003",
|
|
280
|
+
initialValue: "http://localhost:3003",
|
|
281
|
+
validate: validateUrl
|
|
282
|
+
});
|
|
283
|
+
if (p.isCancel(runApiUrl)) {
|
|
284
|
+
p.cancel("Operation cancelled");
|
|
285
|
+
process.exit(0);
|
|
286
|
+
}
|
|
287
|
+
const configContent = `import { defineConfig } from '@inkeep/agents-cli/config';
|
|
288
|
+
|
|
289
|
+
export default defineConfig({
|
|
290
|
+
tenantId: '${tenantId}',
|
|
291
|
+
agentsManageApi: {
|
|
292
|
+
url: '${manageApiUrl}',
|
|
293
|
+
},
|
|
294
|
+
agentsRunApi: {
|
|
295
|
+
url: '${runApiUrl}',
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
`;
|
|
299
|
+
try {
|
|
300
|
+
writeFileSync(configPath, configContent);
|
|
301
|
+
console.log(chalk.green("✓"), `Created ${chalk.cyan(configPath)}`);
|
|
302
|
+
console.log(chalk.gray("\nYou can now use the Inkeep CLI commands."));
|
|
303
|
+
const configDir = dirname(configPath);
|
|
304
|
+
if (configDir !== process.cwd()) {
|
|
305
|
+
console.log(chalk.gray(`\nNote: Config file created in ${configDir}`));
|
|
306
|
+
console.log(chalk.gray(`Use --config ${configPath} with commands, or run commands from that directory.`));
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error(chalk.red("Failed to create config file:"), error);
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function sanitizeProjectName(name) {
|
|
314
|
+
return name.toLowerCase().replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
315
|
+
}
|
|
316
|
+
function generateConfigFile(tenantId, projectId) {
|
|
317
|
+
return `import { defineConfig } from '@inkeep/agents-cli/config';
|
|
318
|
+
|
|
319
|
+
export default defineConfig({
|
|
320
|
+
tenantId: '${tenantId}',
|
|
321
|
+
projectId: '${projectId}',
|
|
322
|
+
agentsManageApi: {
|
|
323
|
+
url: 'https://manage-api.inkeep.com',
|
|
324
|
+
},
|
|
325
|
+
agentsRunApi: {
|
|
326
|
+
url: 'https://run-api.inkeep.com',
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
function generateIndexFile(projectId) {
|
|
332
|
+
return `import { project } from '@inkeep/agents-sdk';
|
|
333
|
+
|
|
334
|
+
// This file was auto-generated by 'inkeep init'
|
|
335
|
+
// Run 'inkeep pull' to sync with your remote project
|
|
336
|
+
|
|
337
|
+
export default project({
|
|
338
|
+
id: '${projectId}',
|
|
339
|
+
name: '${projectId}',
|
|
340
|
+
agents: {},
|
|
341
|
+
tools: {},
|
|
342
|
+
});
|
|
343
|
+
`;
|
|
344
|
+
}
|
|
345
|
+
function generateEnvTemplate(environment) {
|
|
346
|
+
return `# ${environment.charAt(0).toUpperCase() + environment.slice(1)} Environment
|
|
347
|
+
# Add your API keys and secrets here
|
|
348
|
+
|
|
349
|
+
# OpenAI API Key
|
|
350
|
+
OPENAI_API_KEY=sk-your-key-here
|
|
351
|
+
|
|
352
|
+
# Anthropic API Key
|
|
353
|
+
ANTHROPIC_API_KEY=sk-ant-your-key-here
|
|
354
|
+
|
|
355
|
+
# Add other provider keys as needed
|
|
356
|
+
`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
//#endregion
|
|
360
|
+
export { initCommand };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ManagementApiClient } from "../api.js";
|
|
2
|
+
import { initializeCommand } from "../utils/cli-pipeline.js";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import Table from "cli-table3";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/list-agents.ts
|
|
8
|
+
async function listAgentsCommand(options) {
|
|
9
|
+
const configPath = options.config || options.configFilePath;
|
|
10
|
+
const { config } = await initializeCommand({
|
|
11
|
+
configPath,
|
|
12
|
+
showSpinner: false,
|
|
13
|
+
logConfig: true
|
|
14
|
+
});
|
|
15
|
+
console.log();
|
|
16
|
+
const api = await ManagementApiClient.create(config.agentsManageApiUrl, configPath, config.tenantId, options.project);
|
|
17
|
+
const s = p.spinner();
|
|
18
|
+
s.start("Fetching agent...");
|
|
19
|
+
try {
|
|
20
|
+
const agents = await api.listAgents();
|
|
21
|
+
s.stop(`Found ${agents.length} agent(s) in project "${options.project}"`);
|
|
22
|
+
if (agents.length === 0) {
|
|
23
|
+
console.log(chalk.gray(`No agent found in project "${options.project}". Define agent in your project and run: inkeep push`));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const table = new Table({
|
|
27
|
+
head: [
|
|
28
|
+
chalk.cyan("Agent ID"),
|
|
29
|
+
chalk.cyan("Name"),
|
|
30
|
+
chalk.cyan("Default Agent"),
|
|
31
|
+
chalk.cyan("Created")
|
|
32
|
+
],
|
|
33
|
+
style: {
|
|
34
|
+
head: [],
|
|
35
|
+
border: []
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
for (const agent of agents) {
|
|
39
|
+
const createdDate = agent.createdAt ? new Date(agent.createdAt).toLocaleDateString() : "Unknown";
|
|
40
|
+
table.push([
|
|
41
|
+
agent.id || "",
|
|
42
|
+
agent.name || agent.id || "",
|
|
43
|
+
agent.defaultSubAgentId || chalk.gray("None"),
|
|
44
|
+
createdDate
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
console.log(`\n${table.toString()}`);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
s.stop("Failed to fetch agent");
|
|
50
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { listAgentsCommand };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { checkKeychainAvailability, getKeychainUnavailableMessage, loadCredentials, saveCredentials } from "../utils/credentials.js";
|
|
2
|
+
import { ProfileManager } from "../utils/profiles/profile-manager.js";
|
|
3
|
+
import "../utils/profiles/index.js";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import open from "open";
|
|
7
|
+
|
|
8
|
+
//#region src/commands/login.ts
|
|
9
|
+
/**
|
|
10
|
+
* Format user code as XXXX-XXXX for display
|
|
11
|
+
*/
|
|
12
|
+
function formatUserCode(code) {
|
|
13
|
+
const cleaned = code.replace(/[^A-Z0-9]/gi, "").toUpperCase();
|
|
14
|
+
if (cleaned.length === 8) return `${cleaned.slice(0, 4)}-${cleaned.slice(4)}`;
|
|
15
|
+
return cleaned;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sleep for a specified number of milliseconds
|
|
19
|
+
*/
|
|
20
|
+
function sleep(ms) {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Poll the device token endpoint until authorization is complete
|
|
25
|
+
*/
|
|
26
|
+
async function pollForToken(cloudUrl, deviceCode, clientId, initialInterval) {
|
|
27
|
+
let interval = initialInterval;
|
|
28
|
+
while (true) {
|
|
29
|
+
await sleep(interval * 1e3);
|
|
30
|
+
const data = await (await fetch(`${cloudUrl}/api/auth/device/token`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
35
|
+
device_code: deviceCode,
|
|
36
|
+
client_id: clientId
|
|
37
|
+
})
|
|
38
|
+
})).json();
|
|
39
|
+
if (data.access_token) return data.access_token;
|
|
40
|
+
if (data.error === "authorization_pending") continue;
|
|
41
|
+
if (data.error === "slow_down") {
|
|
42
|
+
interval += 5;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (data.error === "expired_token") throw new Error("Device code expired. Please try again.");
|
|
46
|
+
if (data.error === "access_denied") throw new Error("Authorization denied.");
|
|
47
|
+
throw new Error(data.error || data.message || "Unknown error during authorization");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fetch user info and organization after authentication
|
|
52
|
+
*/
|
|
53
|
+
async function fetchUserInfo(cloudUrl, accessToken) {
|
|
54
|
+
const sessionResponse = await fetch(`${cloudUrl}/api/auth/get-session`, { headers: { Authorization: `Bearer ${accessToken}` } });
|
|
55
|
+
if (!sessionResponse.ok) throw new Error("Failed to fetch user session");
|
|
56
|
+
const user = (await sessionResponse.json()).user;
|
|
57
|
+
if (!user) throw new Error("No user found in session");
|
|
58
|
+
const orgResponse = await fetch(`${cloudUrl}/api/cli/me`, { headers: { Authorization: `Bearer ${accessToken}` } });
|
|
59
|
+
if (!orgResponse.ok) throw new Error("Failed to fetch organization info. Please ensure that you are a member of an organization.");
|
|
60
|
+
const orgData = await orgResponse.json();
|
|
61
|
+
return {
|
|
62
|
+
user: {
|
|
63
|
+
id: user.id,
|
|
64
|
+
email: user.email,
|
|
65
|
+
name: user.name
|
|
66
|
+
},
|
|
67
|
+
organization: orgData.organization
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function loginCommand(options = {}) {
|
|
71
|
+
const profileManager = new ProfileManager();
|
|
72
|
+
let profileName;
|
|
73
|
+
let credentialKey;
|
|
74
|
+
let manageApiUrl;
|
|
75
|
+
let manageUiUrl;
|
|
76
|
+
try {
|
|
77
|
+
if (options.profile) {
|
|
78
|
+
const profile = profileManager.getProfile(options.profile);
|
|
79
|
+
if (!profile) {
|
|
80
|
+
console.error(chalk.red(`Profile '${options.profile}' not found.`));
|
|
81
|
+
console.log(chalk.gray("Run \"inkeep profile list\" to see available profiles."));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
profileName = options.profile;
|
|
85
|
+
credentialKey = profile.credential;
|
|
86
|
+
manageApiUrl = profile.remote.manageApi;
|
|
87
|
+
manageUiUrl = profile.remote.manageUi;
|
|
88
|
+
} else {
|
|
89
|
+
const activeProfile = profileManager.getActiveProfile();
|
|
90
|
+
profileName = activeProfile.name;
|
|
91
|
+
credentialKey = activeProfile.credential;
|
|
92
|
+
manageApiUrl = activeProfile.remote.manageApi;
|
|
93
|
+
manageUiUrl = activeProfile.remote.manageUi;
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
profileName = "default";
|
|
97
|
+
credentialKey = "inkeep-cloud";
|
|
98
|
+
manageApiUrl = "https://manage-api.inkeep.com";
|
|
99
|
+
manageUiUrl = "https://manage.inkeep.com";
|
|
100
|
+
}
|
|
101
|
+
console.log(chalk.gray(`Using profile: ${profileName}`));
|
|
102
|
+
const { available, reason } = await checkKeychainAvailability();
|
|
103
|
+
if (!available) {
|
|
104
|
+
console.error(chalk.red("Error:"), getKeychainUnavailableMessage(reason));
|
|
105
|
+
console.log();
|
|
106
|
+
console.log(chalk.yellow("For CI/CD environments without keychain access:"));
|
|
107
|
+
console.log(chalk.gray(" Set INKEEP_API_KEY environment variable instead of using login."));
|
|
108
|
+
console.log(chalk.gray(" See: https://docs.inkeep.com/cli/cicd"));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const existingCredentials = await loadCredentials(credentialKey);
|
|
112
|
+
if (existingCredentials) {
|
|
113
|
+
const continueLogin = await p.confirm({
|
|
114
|
+
message: `Already logged in as ${chalk.cyan(existingCredentials.userEmail)} for profile '${profileName}'. Continue with new login?`,
|
|
115
|
+
initialValue: false
|
|
116
|
+
});
|
|
117
|
+
if (p.isCancel(continueLogin)) {
|
|
118
|
+
p.cancel("Login cancelled");
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
if (!continueLogin) {
|
|
122
|
+
console.log(chalk.gray("Login cancelled. You are still logged in."));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const s = p.spinner();
|
|
127
|
+
try {
|
|
128
|
+
s.start("Requesting device code...");
|
|
129
|
+
const deviceCodeResponse = await fetch(`${manageApiUrl}/api/auth/device/code`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: { "Content-Type": "application/json" },
|
|
132
|
+
body: JSON.stringify({ client_id: "inkeep-cli" })
|
|
133
|
+
});
|
|
134
|
+
if (!deviceCodeResponse.ok) {
|
|
135
|
+
const errorData = await deviceCodeResponse.json().catch(() => ({}));
|
|
136
|
+
throw new Error(errorData.message || `Failed to get device code: ${deviceCodeResponse.statusText}`);
|
|
137
|
+
}
|
|
138
|
+
const { device_code, user_code, interval } = await deviceCodeResponse.json();
|
|
139
|
+
s.stop("Device code received");
|
|
140
|
+
console.log();
|
|
141
|
+
console.log(chalk.bold("To authenticate, visit:"));
|
|
142
|
+
console.log(chalk.cyan(` ${manageUiUrl}/device?user_code=${user_code}`));
|
|
143
|
+
console.log();
|
|
144
|
+
console.log(chalk.bold("And enter code:"));
|
|
145
|
+
console.log(chalk.yellow.bold(` ${formatUserCode(user_code)}`));
|
|
146
|
+
console.log();
|
|
147
|
+
try {
|
|
148
|
+
await open(`${manageUiUrl}/device?user_code=${user_code}`);
|
|
149
|
+
console.log(chalk.gray(" (Browser opened automatically)"));
|
|
150
|
+
console.log();
|
|
151
|
+
} catch {}
|
|
152
|
+
s.start("Waiting for authorization...");
|
|
153
|
+
const accessToken = await pollForToken(manageApiUrl, device_code, "inkeep-cli", interval || 5);
|
|
154
|
+
s.stop("Authorized!");
|
|
155
|
+
s.start("Fetching account info...");
|
|
156
|
+
const userInfo = await fetchUserInfo(manageApiUrl, accessToken);
|
|
157
|
+
s.stop("Account info retrieved");
|
|
158
|
+
await saveCredentials({
|
|
159
|
+
accessToken,
|
|
160
|
+
userId: userInfo.user.id,
|
|
161
|
+
userEmail: userInfo.user.email,
|
|
162
|
+
organizationId: userInfo.organization.id,
|
|
163
|
+
organizationName: userInfo.organization.name,
|
|
164
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
165
|
+
}, credentialKey);
|
|
166
|
+
console.log();
|
|
167
|
+
console.log(chalk.green("✓"), `Logged in as ${chalk.cyan(userInfo.user.email)}`);
|
|
168
|
+
console.log(chalk.green("✓"), `Organization: ${chalk.cyan(userInfo.organization.name)}`);
|
|
169
|
+
console.log(chalk.green("✓"), `Profile: ${chalk.cyan(profileName)}`);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
s.stop("Login failed");
|
|
172
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
173
|
+
console.error(chalk.red("Error:"), errorMessage);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
export { loginCommand };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { clearCredentials, loadCredentials } from "../utils/credentials.js";
|
|
2
|
+
import { ProfileManager } from "../utils/profiles/profile-manager.js";
|
|
3
|
+
import "../utils/profiles/index.js";
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/logout.ts
|
|
8
|
+
async function logoutCommand(options = {}) {
|
|
9
|
+
const profileManager = new ProfileManager();
|
|
10
|
+
const s = p.spinner();
|
|
11
|
+
let profileName;
|
|
12
|
+
let credentialKey;
|
|
13
|
+
try {
|
|
14
|
+
if (options.profile) {
|
|
15
|
+
const profile = profileManager.getProfile(options.profile);
|
|
16
|
+
if (!profile) {
|
|
17
|
+
console.error(chalk.red(`Profile '${options.profile}' not found.`));
|
|
18
|
+
console.log(chalk.gray("Run \"inkeep profile list\" to see available profiles."));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
profileName = options.profile;
|
|
22
|
+
credentialKey = profile.credential;
|
|
23
|
+
} else {
|
|
24
|
+
const activeProfile = profileManager.getActiveProfile();
|
|
25
|
+
profileName = activeProfile.name;
|
|
26
|
+
credentialKey = activeProfile.credential;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
profileName = "default";
|
|
30
|
+
credentialKey = "inkeep-cloud";
|
|
31
|
+
}
|
|
32
|
+
console.log(chalk.gray(`Using profile: ${profileName}`));
|
|
33
|
+
if (!await loadCredentials(credentialKey)) {
|
|
34
|
+
console.log(chalk.yellow(`Not logged in for profile '${profileName}'.`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
s.start("Logging out...");
|
|
38
|
+
try {
|
|
39
|
+
if (await clearCredentials(credentialKey)) {
|
|
40
|
+
s.stop("Logged out successfully");
|
|
41
|
+
console.log(chalk.green("✓"), `Logged out from profile '${chalk.cyan(profileName)}'.`);
|
|
42
|
+
console.log(chalk.gray(` • Credential '${credentialKey}' removed from keychain.`));
|
|
43
|
+
} else {
|
|
44
|
+
s.stop("Logout completed");
|
|
45
|
+
console.log(chalk.gray("No credentials were stored."));
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
s.stop("Logout failed");
|
|
49
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
50
|
+
console.error(chalk.red("Error:"), errorMessage);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { logoutCommand };
|