@inkeep/agents-cli 0.60.0 → 0.61.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.
Files changed (28) hide show
  1. package/dist/agents-cli/package.js +1 -1
  2. package/dist/api.js +74 -2
  3. package/dist/api.js.map +1 -1
  4. package/dist/commands/pull-v4/generators/agent-generator.js +2 -2
  5. package/dist/commands/pull-v4/generators/agent-generator.js.map +1 -1
  6. package/dist/commands/pull-v4/generators/context-config-generator.js.map +1 -1
  7. package/dist/commands/pull-v4/introspect/index.js +70 -17
  8. package/dist/commands/pull-v4/introspect/index.js.map +1 -1
  9. package/dist/commands/pull-v4/merge-conflicts.js +37 -0
  10. package/dist/commands/pull-v4/merge-conflicts.js.map +1 -0
  11. package/dist/commands/pull-v4/merge-ui/column-row.js +63 -0
  12. package/dist/commands/pull-v4/merge-ui/column-row.js.map +1 -0
  13. package/dist/commands/pull-v4/merge-ui/conflict-view.js +135 -0
  14. package/dist/commands/pull-v4/merge-ui/conflict-view.js.map +1 -0
  15. package/dist/commands/pull-v4/merge-ui/help-bar.js +79 -0
  16. package/dist/commands/pull-v4/merge-ui/help-bar.js.map +1 -0
  17. package/dist/commands/pull-v4/merge-ui/merge-app.js +239 -0
  18. package/dist/commands/pull-v4/merge-ui/merge-app.js.map +1 -0
  19. package/dist/commands/pull-v4/merge-ui/resolution-summary.js +106 -0
  20. package/dist/commands/pull-v4/merge-ui/resolution-summary.js.map +1 -0
  21. package/dist/commands/pull-v4/merge-ui/types.js +1 -0
  22. package/dist/commands/pull-v4/merge-ui/utils.js +43 -0
  23. package/dist/commands/pull-v4/merge-ui/utils.js.map +1 -0
  24. package/dist/index.js +2 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/utils/state.js +46 -0
  27. package/dist/utils/state.js.map +1 -0
  28. package/package.json +7 -4
@@ -2,7 +2,9 @@ import { ManagementApiClient } from "../../../api.js";
2
2
  import { initializeCommand } from "../../../utils/cli-pipeline.js";
3
3
  import { performBackgroundVersionCheck } from "../../../utils/background-version-check.js";
4
4
  import { loadProject } from "../../../utils/project-loader.js";
5
+ import { readProjectState, writeProjectState } from "../../../utils/state.js";
5
6
  import { introspectGenerate } from "../introspect-generator.js";
7
+ import { getTempBranchSuffix } from "@inkeep/agents-core";
6
8
  import { join, resolve } from "node:path";
7
9
  import * as p from "@clack/prompts";
8
10
  import { existsSync, mkdirSync } from "node:fs";
@@ -78,7 +80,6 @@ async function pullV4Command(options) {
78
80
  console.log(styleText("blue", "\nInkeep Pull:"));
79
81
  if (options.introspect) console.log(styleText("gray", " Introspect mode • Complete regeneration • No comparison needed"));
80
82
  else console.log(styleText("gray", " Smart comparison • Detect all changes • Targeted updates"));
81
- const s = p.spinner();
82
83
  try {
83
84
  const { config, isCI } = await initializeCommand({
84
85
  configPath: options.config,
@@ -89,20 +90,17 @@ async function pullV4Command(options) {
89
90
  logConfig: true,
90
91
  quiet: options.quiet
91
92
  });
92
- s.start("Detecting project...");
93
93
  let projectDir;
94
94
  let projectId;
95
95
  let localProjectForId = null;
96
96
  const currentDir = process.cwd();
97
97
  if (existsSync(join(currentDir, "index.ts"))) {
98
98
  projectDir = currentDir;
99
- s.start("Loading local project...");
100
99
  try {
101
100
  localProjectForId = await loadProject(projectDir);
102
101
  const localProjectId = localProjectForId.getId();
103
102
  if (options.project) {
104
103
  if (localProjectId !== options.project) {
105
- s.stop("Project ID mismatch");
106
104
  console.error(styleText("red", `Local project ID "${localProjectId}" doesn't match --project "${options.project}"`));
107
105
  console.error(styleText("yellow", "Either remove --project flag or ensure it matches the local project ID"));
108
106
  if (batchMode) return {
@@ -113,14 +111,12 @@ async function pullV4Command(options) {
113
111
  }
114
112
  }
115
113
  projectId = localProjectId;
116
- s.stop(`Using local project: ${projectId}`);
114
+ console.log(styleText("green", `◆ Using local project: ${projectId}`));
117
115
  } catch (error) {
118
- s.stop("Failed to load local project");
119
116
  throw new Error(`Could not load local project: ${error instanceof Error ? error.message : String(error)}`);
120
117
  }
121
118
  } else {
122
119
  if (!options.project) {
123
- s.stop("No index.ts found in current directory");
124
120
  console.error(styleText("yellow", "Please run this command from a directory containing index.ts or use --project <project-id>"));
125
121
  if (batchMode) return {
126
122
  success: false,
@@ -131,23 +127,74 @@ async function pullV4Command(options) {
131
127
  const projectPath = resolve(currentDir, options.project);
132
128
  if (existsSync(join(projectPath, "index.ts"))) {
133
129
  projectDir = projectPath;
134
- s.start("Loading project from specified path...");
135
130
  try {
136
131
  localProjectForId = await loadProject(projectDir);
137
132
  projectId = localProjectForId.getId();
138
- s.stop(`Using project from path: ${projectId}`);
133
+ console.log(styleText("green", `◆ Using project from path: ${projectId}`));
139
134
  } catch (error) {
140
- s.stop("Failed to load project from path");
141
135
  throw new Error(`Could not load project from ${projectPath}: ${error instanceof Error ? error.message : String(error)}`);
142
136
  }
143
137
  } else {
144
138
  projectId = options.project;
145
139
  projectDir = join(currentDir, projectId);
146
- s.stop(`Creating new project directory: ${projectDir}`);
140
+ console.log(styleText("green", `◆ Creating new project directory: ${projectDir}`));
147
141
  }
148
142
  }
149
- s.start(`Fetching project: ${projectId}`);
150
- const remoteProject = await (await ManagementApiClient.create(config.agentsApiUrl, options.config, config.tenantId, projectId, isCI, config.agentsApiKey)).getFullProject(projectId);
143
+ const lastPulledHash = readProjectState(projectId)?.lastPulledHash;
144
+ if (options.debug && lastPulledHash) console.log(styleText("gray", ` Last pulled hash: ${lastPulledHash}`));
145
+ const apiClient = await ManagementApiClient.create(config.agentsApiUrl, options.config, config.tenantId, projectId, isCI, config.agentsApiKey);
146
+ let currentMainHash;
147
+ try {
148
+ currentMainHash = (await apiClient.getBranch(projectId, "main")).hash;
149
+ } catch (error) {
150
+ if (options.debug) console.log(styleText("gray", ` Could not fetch main branch hash: ${error instanceof Error ? error.message : String(error)}`));
151
+ }
152
+ if (options.debug && currentMainHash) console.log(styleText("gray", ` Current main hash: ${currentMainHash}`));
153
+ let remoteProject;
154
+ if (localProjectForId && lastPulledHash) {
155
+ const tempBranchName = getTempBranchSuffix("cli-pull");
156
+ try {
157
+ await apiClient.createBranch(projectId, {
158
+ name: tempBranchName,
159
+ fromCommit: lastPulledHash
160
+ });
161
+ const localProjectDefinition = await localProjectForId.getFullDefinition();
162
+ await apiClient.pushFullProject(projectId, tempBranchName, localProjectDefinition);
163
+ const preview = await apiClient.mergePreview(projectId, {
164
+ sourceBranch: "main",
165
+ targetBranch: tempBranchName
166
+ });
167
+ if (preview.hasConflicts) {
168
+ const { resolveConflictsInteractive } = await import("../merge-conflicts.js");
169
+ const resolutions = await resolveConflictsInteractive(preview.conflicts, options);
170
+ if (resolutions === null) {
171
+ console.log(styleText("yellow", "Pull cancelled"));
172
+ return;
173
+ }
174
+ await apiClient.mergeExecute(projectId, {
175
+ sourceBranch: "main",
176
+ targetBranch: tempBranchName,
177
+ sourceHash: preview.sourceHash,
178
+ targetHash: preview.targetHash,
179
+ resolutions,
180
+ message: "CLI pull: merge main into local state"
181
+ });
182
+ } else await apiClient.mergeExecute(projectId, {
183
+ sourceBranch: "main",
184
+ targetBranch: tempBranchName,
185
+ sourceHash: preview.sourceHash,
186
+ targetHash: preview.targetHash,
187
+ message: "CLI pull: merge main into local state"
188
+ });
189
+ remoteProject = await apiClient.getFullProject(projectId, tempBranchName);
190
+ } finally {
191
+ try {
192
+ await apiClient.deleteBranch(projectId, tempBranchName, true);
193
+ } catch (cleanupError) {
194
+ if (options.debug) console.log(styleText("gray", ` Warning: Could not delete temp branch ${tempBranchName}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`));
195
+ }
196
+ }
197
+ } else remoteProject = await apiClient.getFullProject(projectId);
151
198
  if (options.debug && remoteProject.functions) {
152
199
  console.log(styleText("gray", " 📋 Project-level functions from API:"), Object.keys(remoteProject.functions));
153
200
  Object.entries(remoteProject.functions).forEach(([id, data]) => {
@@ -184,7 +231,7 @@ async function pullV4Command(options) {
184
231
  }
185
232
  }
186
233
  enrichCanDelegateToWithTypes(remoteProject);
187
- s.message("Project data fetched");
234
+ console.log(styleText("green", "◆ Project data fetched"));
188
235
  if (options.json) {
189
236
  console.log(JSON.stringify(remoteProject, null, 2));
190
237
  restoreLogLevel();
@@ -192,13 +239,20 @@ async function pullV4Command(options) {
192
239
  }
193
240
  const paths = createProjectStructure(projectDir);
194
241
  await generateProjectSkillsIfPresent(remoteProject, paths.skillsDir);
195
- s.start("Starting generating files...");
242
+ console.log(styleText("gray", "Generating files..."));
196
243
  await introspectGenerate({
197
244
  project: remoteProject,
198
245
  paths,
199
246
  debug: options.debug
200
247
  });
201
- s.stop("All files generated");
248
+ console.log(styleText("green", "◆ All files generated"));
249
+ try {
250
+ const mainBranch = await apiClient.getBranch(projectId, "main");
251
+ writeProjectState(projectId, mainBranch.hash);
252
+ } catch (error) {
253
+ console.warn(styleText("yellow", `Warning: Could not save pull state: ${error instanceof Error ? error.message : String(error)}`));
254
+ console.warn(styleText("yellow", "Future pulls may re-prompt for conflict resolution."));
255
+ }
202
256
  console.log(styleText("green", "\nProject synced successfully!"));
203
257
  console.log(styleText("gray", ` Location: ${paths.projectRoot}`));
204
258
  console.log(styleText("gray", ` Environment: ${options.env || "development"}`));
@@ -208,7 +262,6 @@ async function pullV4Command(options) {
208
262
  process.exit(0);
209
263
  } catch (error) {
210
264
  const message = error instanceof Error ? error.stack : String(error);
211
- s.stop();
212
265
  console.error(styleText("red", `\nError: ${message}`));
213
266
  if (options.debug && error instanceof Error) console.error(styleText("red", error.stack || ""));
214
267
  restoreLogLevel();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/commands/pull-v4/introspect/index.ts"],"sourcesContent":["/**\n * Pull v3 - Clean, efficient project generation\n *\n * Step 1: Validate and compile existing code\n * Step 2: Compare project with DB to detect ALL changes\n * Step 3: Classify changes as new vs modified components\n * Step 4: Generate new components deterministically\n * Step 5: Use LLM to correct modified components\n */\n\nimport { EventEmitter } from 'node:events';\nimport { existsSync, mkdirSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { styleText } from 'node:util';\nimport * as p from '@clack/prompts';\nimport type { FullProjectDefinition } from '@inkeep/agents-core';\n\n// Increase max listeners to prevent warnings during complex CLI flows\n// This is needed because @clack/prompts + multiple interactive prompts + spinners all add listeners\nEventEmitter.defaultMaxListeners = 20;\n\nimport { ManagementApiClient } from '../../../api';\nimport { performBackgroundVersionCheck } from '../../../utils/background-version-check';\nimport { initializeCommand } from '../../../utils/cli-pipeline';\nimport { loadProject } from '../../../utils/project-loader';\nimport { introspectGenerate } from '../introspect-generator';\n\nexport interface PullV3Options {\n project?: string;\n config?: string;\n profile?: string;\n env?: string;\n json?: boolean;\n debug?: boolean;\n verbose?: boolean;\n force?: boolean;\n introspect?: boolean;\n all?: boolean;\n tag?: string;\n quiet?: boolean;\n /** Internal: used for batch operations to return results instead of calling process.exit() */\n _batchMode?: boolean;\n}\n\nexport interface PullResult {\n success: boolean;\n skipped?: boolean;\n upToDate?: boolean;\n error?: string;\n}\n\ninterface BatchPullResult {\n projectId: string;\n projectName?: string;\n targetDir: string;\n success: boolean;\n error?: string;\n}\n\ninterface ProjectPaths {\n projectRoot: string;\n agentsDir: string;\n toolsDir: string;\n dataComponentsDir: string;\n artifactComponentsDir: string;\n statusComponentsDir: string;\n environmentsDir: string;\n credentialsDir: string;\n contextConfigsDir: string;\n externalAgentsDir: string;\n skillsDir: string;\n}\n\n/**\n * Create project directory structure\n */\nexport function createProjectStructure(projectRoot: string): ProjectPaths {\n mkdirSync(projectRoot, { recursive: true });\n return {\n projectRoot,\n agentsDir: join(projectRoot, 'agents'),\n toolsDir: join(projectRoot, 'tools'),\n dataComponentsDir: join(projectRoot, 'data-components'),\n artifactComponentsDir: join(projectRoot, 'artifact-components'),\n statusComponentsDir: join(projectRoot, 'status-components'),\n environmentsDir: join(projectRoot, 'environments'),\n credentialsDir: join(projectRoot, 'credentials'),\n contextConfigsDir: join(projectRoot, 'context-configs'),\n externalAgentsDir: join(projectRoot, 'external-agents'),\n skillsDir: join(projectRoot, 'skills'),\n };\n}\n\n/**\n * Enrich canDelegateTo references with component type information\n */\nexport function enrichCanDelegateToWithTypes(project: FullProjectDefinition): void {\n const { agents } = project;\n // Get all available component IDs by type\n const agentsIdSet = new Set(Object.keys(agents));\n const subAgentsIdSet = new Set(\n Object.values(agents).flatMap((agentData) => Object.keys(agentData.subAgents))\n );\n const externalAgentsIdSet = new Set(\n project.externalAgents ? Object.keys(project.externalAgents) : []\n );\n\n // Function to enrich a canDelegateTo array\n\n function enrichCanDelegateToArray(canDelegateTo: unknown[]): unknown[] {\n return canDelegateTo.map((item) => {\n // Skip if it's already an object (already has type info)\n if (typeof item !== 'string') return item;\n if (agentsIdSet.has(item)) return { agentId: item };\n if (subAgentsIdSet.has(item)) return { subAgentId: item };\n if (externalAgentsIdSet.has(item)) return { externalAgentId: item };\n return item;\n });\n }\n\n // Process all agents\n for (const { subAgents } of Object.values(project.agents)) {\n // Process subAgents within agents\n for (const subAgentData of Object.values(subAgents)) {\n if (Array.isArray(subAgentData.canDelegateTo)) {\n // @ts-expect-error\n subAgentData.canDelegateTo = enrichCanDelegateToArray(subAgentData.canDelegateTo);\n }\n }\n }\n}\n\n/**\n * Main pull v4 command\n * @returns PullResult when in batch mode, otherwise void (exits process)\n */\nexport async function pullV4Command(options: PullV3Options): Promise<PullResult | undefined> {\n // Handle --all flag for batch operations\n if (options.all) {\n await pullAllProjects(options);\n return;\n }\n\n const batchMode = options._batchMode ?? false;\n\n // Suppress SDK logging for cleaner output\n const originalLogLevel = process.env.LOG_LEVEL;\n process.env.LOG_LEVEL = 'silent';\n\n const restoreLogLevel = () => {\n if (originalLogLevel !== undefined) {\n process.env.LOG_LEVEL = originalLogLevel;\n } else {\n delete process.env.LOG_LEVEL;\n }\n };\n\n // Background version check (skip in batch mode - already done)\n if (!batchMode) {\n performBackgroundVersionCheck();\n }\n\n console.log(styleText('blue', '\\nInkeep Pull:'));\n if (options.introspect) {\n console.log(\n styleText('gray', ' Introspect mode • Complete regeneration • No comparison needed')\n );\n } else {\n console.log(styleText('gray', ' Smart comparison • Detect all changes • Targeted updates'));\n }\n\n const s = p.spinner();\n\n try {\n // Step 1: Load configuration (same as push command)\n const { config, isCI } = await initializeCommand({\n configPath: options.config,\n profileName: options.profile,\n tag: options.tag,\n showSpinner: true,\n spinnerText: 'Loading configuration...',\n logConfig: true,\n quiet: options.quiet,\n });\n\n // Step 2: Determine project directory and ID\n s.start('Detecting project...');\n let projectDir: string;\n let projectId: string;\n let localProjectForId: any = null;\n\n const currentDir = process.cwd();\n const hasIndexInCurrent = existsSync(join(currentDir, 'index.ts'));\n\n if (hasIndexInCurrent) {\n // We're in a project directory\n projectDir = currentDir;\n\n s.start('Loading local project...');\n try {\n localProjectForId = await loadProject(projectDir);\n const localProjectId = localProjectForId.getId();\n\n if (options.project) {\n // Validate that --project matches local project ID\n if (localProjectId !== options.project) {\n s.stop('Project ID mismatch');\n console.error(\n styleText(\n 'red',\n `Local project ID \"${localProjectId}\" doesn't match --project \"${options.project}\"`\n )\n );\n console.error(\n styleText(\n 'yellow',\n 'Either remove --project flag or ensure it matches the local project ID'\n )\n );\n if (batchMode) {\n return { success: false, error: 'Project ID mismatch' };\n }\n process.exit(1);\n }\n }\n\n projectId = localProjectId;\n s.stop(`Using local project: ${projectId}`);\n } catch (error) {\n s.stop('Failed to load local project');\n throw new Error(\n `Could not load local project: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n } else {\n // No index.ts in current directory\n if (!options.project) {\n s.stop('No index.ts found in current directory');\n console.error(\n styleText(\n 'yellow',\n 'Please run this command from a directory containing index.ts or use --project <project-id>'\n )\n );\n if (batchMode) {\n return { success: false, error: 'No index.ts found and no --project specified' };\n }\n process.exit(1);\n }\n\n // Try --project as directory path first\n const projectPath = resolve(currentDir, options.project);\n const hasIndexInPath = existsSync(join(projectPath, 'index.ts'));\n\n if (hasIndexInPath) {\n // --project is a valid directory path\n projectDir = projectPath;\n s.start('Loading project from specified path...');\n try {\n localProjectForId = await loadProject(projectDir);\n projectId = localProjectForId.getId();\n s.stop(`Using project from path: ${projectId}`);\n } catch (error) {\n s.stop('Failed to load project from path');\n throw new Error(\n `Could not load project from ${projectPath}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n } else {\n // Treat --project as project ID, create subdirectory\n projectId = options.project;\n projectDir = join(currentDir, projectId);\n s.stop(`Creating new project directory: ${projectDir}`);\n }\n }\n\n // Step 4: Fetch project data from API\n s.start(`Fetching project: ${projectId}`);\n\n const apiClient = await ManagementApiClient.create(\n config.agentsApiUrl,\n options.config,\n config.tenantId,\n projectId,\n isCI,\n config.agentsApiKey\n );\n\n const remoteProject = await apiClient.getFullProject(projectId);\n\n if (options.debug && remoteProject.functions) {\n console.log(\n styleText('gray', ' 📋 Project-level functions from API:'),\n Object.keys(remoteProject.functions)\n );\n Object.entries(remoteProject.functions).forEach(([id, data]: [string, any]) => {\n console.log(\n styleText(\n 'gray',\n ` ${id}: has name=${!!data.name}, has description=${!!data.description}`\n )\n );\n });\n }\n\n // Normalize remote project (same as pull-v2 - hoist agent-level functionTools)\n if (remoteProject.agents) {\n for (const [agentId, agentData] of Object.entries(remoteProject.agents) as any[]) {\n if (agentData.functionTools) {\n remoteProject.functionTools = remoteProject.functionTools || {};\n Object.assign(remoteProject.functionTools, agentData.functionTools);\n if (options.debug) {\n console.log(\n styleText(\n 'gray',\n ` Hoisted functionTools from agent ${agentId}: ${Object.keys(agentData.functionTools).join(', ')}`\n )\n );\n }\n }\n if (agentData.functions) {\n remoteProject.functions ||= {};\n const { functions } = remoteProject;\n // Only hoist agent functions if project-level functions don't already exist (clean function data)\n Object.entries(agentData.functions).forEach(([funcId, funcData]: [string, any]) => {\n // Clean function data - remove functionTool metadata that shouldn't be in functions collection\n functions[funcId] ||= {\n id: funcData.id,\n inputSchema: funcData.inputSchema,\n executeCode: funcData.executeCode,\n dependencies: funcData.dependencies,\n createdAt: '',\n updatedAt: '',\n };\n });\n }\n }\n }\n\n // Filter out project-level tools from individual agents\n // The API includes project-level tools in each agent's tools field, but our generated\n // code structure keeps tools separate and imports them via canUse relationships\n if (remoteProject.agents && remoteProject.tools) {\n const projectToolIds = Object.keys(remoteProject.tools);\n\n for (const agentData of Object.values(remoteProject.agents) as any[]) {\n if (agentData.tools) {\n // Filter out any tools that are defined at project level\n const agentSpecificTools = Object.fromEntries(\n Object.entries(agentData.tools).filter(([toolId]) => !projectToolIds.includes(toolId))\n );\n\n // Only keep tools field if there are agent-specific tools remaining\n if (Object.keys(agentSpecificTools).length > 0) {\n agentData.tools = agentSpecificTools;\n } else {\n // Remove the tools field entirely if all tools were project-level\n delete agentData.tools;\n }\n }\n }\n }\n\n // Enrich canDelegateTo references with component type information\n // @ts-expect-error -- fixme Types of property `models` are incompatible.\n enrichCanDelegateToWithTypes(remoteProject);\n\n s.message('Project data fetched');\n\n if (options.json) {\n console.log(JSON.stringify(remoteProject, null, 2));\n restoreLogLevel();\n return;\n }\n\n // Step 5: Set up project structure\n const paths = createProjectStructure(projectDir);\n\n await generateProjectSkillsIfPresent(remoteProject, paths.skillsDir);\n\n s.start('Starting generating files...');\n await introspectGenerate({\n // @ts-expect-error -- ignore Types of property 'models' are incompatible.\n project: remoteProject,\n paths,\n debug: options.debug,\n });\n s.stop('All files generated');\n\n console.log(styleText('green', '\\nProject synced successfully!'));\n console.log(styleText('gray', ` Location: ${paths.projectRoot}`));\n console.log(styleText('gray', ` Environment: ${options.env || 'development'}`));\n console.log(\n styleText(\n 'yellow',\n '⚠️ If you encounter broken code after running `inkeep pull`, please report it at https://github.com/inkeep/agents/issues.'\n )\n );\n\n restoreLogLevel();\n if (batchMode) {\n return { success: true };\n }\n process.exit(0);\n } catch (error) {\n const message = error instanceof Error ? error.stack : String(error);\n s.stop();\n console.error(styleText('red', `\\nError: ${message}`));\n if (options.debug && error instanceof Error) {\n console.error(styleText('red', error.stack || ''));\n }\n restoreLogLevel();\n if (batchMode) {\n return { success: false, error: message };\n }\n process.exit(1);\n }\n}\n\n/**\n * Pull all projects for the current tenant\n * Uses smart comparison with LLM merging for existing projects, introspect for new projects\n */\nasync function pullAllProjects(options: PullV3Options): Promise<void> {\n console.log(styleText('blue', '\\n🔄 Batch Pull: Sequential processing with smart comparison\\n'));\n console.log(\n styleText(\n 'gray',\n ' • Existing projects: Smart comparison + AST merging + confirmation prompts'\n )\n );\n console.log(styleText('gray', ' • New projects: Fresh generation with introspect mode\\n'));\n\n // Background version check (only once for batch)\n performBackgroundVersionCheck();\n\n // Load configuration first\n const { config, isCI } = await initializeCommand({\n configPath: options.config,\n profileName: options.profile,\n tag: options.tag,\n showSpinner: true,\n spinnerText: 'Loading configuration...',\n logConfig: true,\n quiet: options.quiet,\n });\n\n const s = p.spinner();\n\n try {\n // Fetch all projects from the API\n s.start('Fetching project list from API...');\n const apiClient = await ManagementApiClient.create(\n config.agentsApiUrl,\n options.config,\n config.tenantId,\n undefined,\n isCI,\n config.agentsApiKey\n );\n\n const projects = await apiClient.listAllProjects();\n s.stop(`Found ${projects.length} project(s)`);\n\n if (!projects.length) {\n console.log(styleText('yellow', 'No projects found for this tenant.'));\n process.exit(0);\n }\n\n // Categorize projects\n const existingProjects: typeof projects = [];\n const newProjects: typeof projects = [];\n\n for (const project of projects) {\n const targetDir = join(process.cwd(), project.id);\n if (existsSync(join(targetDir, 'index.ts'))) {\n existingProjects.push(project);\n } else {\n newProjects.push(project);\n }\n }\n\n console.log(styleText('gray', '\\nProjects to pull:\\n'));\n if (existingProjects.length > 0) {\n console.log(styleText('cyan', ' Existing (smart comparison):'));\n for (const project of existingProjects) {\n console.log(styleText('gray', ` • ${project.name || project.id} (${project.id})`));\n }\n }\n if (newProjects.length > 0) {\n console.log(styleText('cyan', ' New (introspect):'));\n for (const project of newProjects) {\n console.log(styleText('gray', ` • ${project.name || project.id} (${project.id})`));\n }\n }\n console.log();\n\n const results: BatchPullResult[] = [];\n const total = projects.length;\n\n for (let i = 0; i < projects.length; i++) {\n const project = projects[i];\n const progress = `[${i + 1}/${total}]`;\n\n console.log(styleText('cyan', `\\n${'─'.repeat(60)}`));\n console.log(styleText('cyan', `${progress} Pulling ${project.name || project.id}...`));\n\n const result = await pullSingleProject(project.id, project.name, options, config, isCI);\n results.push(result);\n\n if (result.success) {\n console.log(\n styleText(\n 'green',\n `\\n ✓ ${result.projectName || result.projectId} → ${result.targetDir}`\n )\n );\n } else {\n console.log(\n styleText('red', `\\n ✗ ${result.projectName || result.projectId}: ${result.error}`)\n );\n }\n }\n\n // Print summary\n const succeeded = results.filter((r) => r.success).length;\n const failed = results.filter((r) => !r.success).length;\n\n console.log(styleText('cyan', `\\n${'═'.repeat(60)}`));\n console.log(styleText('cyan', '📊 Batch Pull Summary:'));\n console.log(styleText('green', ` ✓ Succeeded: ${succeeded}`));\n if (failed > 0) {\n console.log(styleText('red', ` ✗ Failed: ${failed}`));\n\n console.log(styleText('red', '\\nFailed projects:'));\n for (const result of results) {\n if (!result.success) {\n console.log(styleText('red', ` • ${result.projectId}: ${result.error}`));\n }\n }\n }\n\n process.exit(failed > 0 ? 1 : 0);\n } catch (error) {\n s.stop();\n console.error(\n styleText('red', `\\nError: ${error instanceof Error ? error.message : String(error)}`)\n );\n process.exit(1);\n }\n}\n\n/**\n * Pull a single project (used by batch operations)\n * Uses smart comparison flow for existing projects, introspect for new projects\n */\nexport async function pullSingleProject(\n projectId: string,\n projectName: string | undefined,\n options: PullV3Options,\n config: any,\n isCI?: boolean\n): Promise<BatchPullResult> {\n const targetDir = join(process.cwd(), projectId);\n const hasExistingProject = existsSync(join(targetDir, 'index.ts'));\n\n try {\n if (hasExistingProject) {\n // Project exists locally - use smart comparison flow with LLM merging and user prompts\n console.log(styleText('gray', ` 📂 Existing project found - using smart comparison mode`));\n\n // Save current directory and change to project directory\n const originalDir = process.cwd();\n process.chdir(targetDir);\n\n try {\n // Call the main pull command in batch mode (returns results instead of exiting)\n const result = await pullV4Command({\n ...options,\n project: projectId,\n all: false, // Don't recurse into batch mode\n _batchMode: true,\n });\n\n // Restore original directory\n process.chdir(originalDir);\n\n if (result && typeof result === 'object') {\n return {\n projectId,\n projectName,\n targetDir,\n success: result.success,\n error: result.error,\n };\n }\n\n return {\n projectId,\n projectName,\n targetDir,\n success: true,\n };\n } catch (error) {\n // Restore original directory even on error\n process.chdir(originalDir);\n throw error;\n }\n }\n console.log(styleText('gray', ' New project'));\n\n // Suppress SDK logging\n const originalLogLevel = process.env.LOG_LEVEL;\n process.env.LOG_LEVEL = 'silent';\n\n const restoreLogLevel = () => {\n if (originalLogLevel !== undefined) {\n process.env.LOG_LEVEL = originalLogLevel;\n } else {\n delete process.env.LOG_LEVEL;\n }\n };\n\n // Fetch project data from API\n const apiClient = await ManagementApiClient.create(\n config.agentsApiUrl,\n options.config,\n config.tenantId,\n projectId,\n isCI,\n config.agentsApiKey\n );\n\n const remoteProject = await apiClient.getFullProject(projectId);\n // Create project structure\n const paths = createProjectStructure(targetDir);\n await generateProjectSkillsIfPresent(remoteProject, paths.skillsDir);\n\n // Generate all files using introspect mode for new projects\n await introspectGenerate({\n // @ts-expect-error -- ignore Types of property 'models' are incompatible.\n project: remoteProject,\n paths,\n });\n\n restoreLogLevel();\n\n return {\n projectId,\n projectName: projectName || remoteProject.name,\n targetDir,\n success: true,\n };\n } catch (error) {\n return {\n projectId,\n projectName,\n targetDir,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nasync function generateProjectSkillsIfPresent(\n remoteProject: any,\n skillsDir: string\n): Promise<void> {\n const skills = remoteProject.skills ?? {};\n if (!Object.keys(skills).length) {\n return;\n }\n\n const { generateSkills } = await import('../skill');\n await generateSkills(skills, skillsDir);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/commands/pull-v4/introspect/index.ts"],"sourcesContent":["/**\n * Pull v3 - Clean, efficient project generation\n *\n * Step 1: Validate and compile existing code\n * Step 2: Compare project with DB to detect ALL changes\n * Step 3: Classify changes as new vs modified components\n * Step 4: Generate new components deterministically\n * Step 5: Use LLM to correct modified components\n */\n\nimport { EventEmitter } from 'node:events';\nimport { existsSync, mkdirSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { styleText } from 'node:util';\nimport * as p from '@clack/prompts';\nimport { type FullProjectDefinition, getTempBranchSuffix } from '@inkeep/agents-core';\n\n// Increase max listeners to prevent warnings during complex CLI flows\n// This is needed because @clack/prompts + multiple interactive prompts + spinners all add listeners\nEventEmitter.defaultMaxListeners = 20;\n\nimport { ManagementApiClient } from '../../../api';\nimport { performBackgroundVersionCheck } from '../../../utils/background-version-check';\nimport { initializeCommand } from '../../../utils/cli-pipeline';\nimport { loadProject } from '../../../utils/project-loader';\nimport { readProjectState, writeProjectState } from '../../../utils/state';\nimport { introspectGenerate } from '../introspect-generator';\n\nexport interface PullV3Options {\n project?: string;\n config?: string;\n profile?: string;\n env?: string;\n json?: boolean;\n debug?: boolean;\n verbose?: boolean;\n force?: boolean;\n introspect?: boolean;\n all?: boolean;\n tag?: string;\n quiet?: boolean;\n conflictStrategy?: 'ours' | 'theirs';\n /** Internal: used for batch operations to return results instead of calling process.exit() */\n _batchMode?: boolean;\n}\n\nexport interface PullResult {\n success: boolean;\n skipped?: boolean;\n upToDate?: boolean;\n error?: string;\n}\n\ninterface BatchPullResult {\n projectId: string;\n projectName?: string;\n targetDir: string;\n success: boolean;\n error?: string;\n}\n\ninterface ProjectPaths {\n projectRoot: string;\n agentsDir: string;\n toolsDir: string;\n dataComponentsDir: string;\n artifactComponentsDir: string;\n statusComponentsDir: string;\n environmentsDir: string;\n credentialsDir: string;\n contextConfigsDir: string;\n externalAgentsDir: string;\n skillsDir: string;\n}\n\n/**\n * Create project directory structure\n */\nexport function createProjectStructure(projectRoot: string): ProjectPaths {\n mkdirSync(projectRoot, { recursive: true });\n return {\n projectRoot,\n agentsDir: join(projectRoot, 'agents'),\n toolsDir: join(projectRoot, 'tools'),\n dataComponentsDir: join(projectRoot, 'data-components'),\n artifactComponentsDir: join(projectRoot, 'artifact-components'),\n statusComponentsDir: join(projectRoot, 'status-components'),\n environmentsDir: join(projectRoot, 'environments'),\n credentialsDir: join(projectRoot, 'credentials'),\n contextConfigsDir: join(projectRoot, 'context-configs'),\n externalAgentsDir: join(projectRoot, 'external-agents'),\n skillsDir: join(projectRoot, 'skills'),\n };\n}\n\n/**\n * Enrich canDelegateTo references with component type information\n */\nexport function enrichCanDelegateToWithTypes(project: FullProjectDefinition): void {\n const { agents } = project;\n // Get all available component IDs by type\n const agentsIdSet = new Set(Object.keys(agents));\n const subAgentsIdSet = new Set(\n Object.values(agents).flatMap((agentData) => Object.keys(agentData.subAgents))\n );\n const externalAgentsIdSet = new Set(\n project.externalAgents ? Object.keys(project.externalAgents) : []\n );\n\n // Function to enrich a canDelegateTo array\n\n function enrichCanDelegateToArray(canDelegateTo: unknown[]): unknown[] {\n return canDelegateTo.map((item) => {\n // Skip if it's already an object (already has type info)\n if (typeof item !== 'string') return item;\n if (agentsIdSet.has(item)) return { agentId: item };\n if (subAgentsIdSet.has(item)) return { subAgentId: item };\n if (externalAgentsIdSet.has(item)) return { externalAgentId: item };\n return item;\n });\n }\n\n // Process all agents\n for (const { subAgents } of Object.values(project.agents)) {\n // Process subAgents within agents\n for (const subAgentData of Object.values(subAgents)) {\n if (Array.isArray(subAgentData.canDelegateTo)) {\n // @ts-expect-error\n subAgentData.canDelegateTo = enrichCanDelegateToArray(subAgentData.canDelegateTo);\n }\n }\n }\n}\n\n/**\n * Main pull v4 command\n * @returns PullResult when in batch mode, otherwise void (exits process)\n */\nexport async function pullV4Command(options: PullV3Options): Promise<PullResult | undefined> {\n // Handle --all flag for batch operations\n if (options.all) {\n await pullAllProjects(options);\n return;\n }\n\n const batchMode = options._batchMode ?? false;\n\n // Suppress SDK logging for cleaner output\n const originalLogLevel = process.env.LOG_LEVEL;\n process.env.LOG_LEVEL = 'silent';\n\n const restoreLogLevel = () => {\n if (originalLogLevel !== undefined) {\n process.env.LOG_LEVEL = originalLogLevel;\n } else {\n delete process.env.LOG_LEVEL;\n }\n };\n\n // Background version check (skip in batch mode - already done)\n if (!batchMode) {\n performBackgroundVersionCheck();\n }\n\n console.log(styleText('blue', '\\nInkeep Pull:'));\n if (options.introspect) {\n console.log(\n styleText('gray', ' Introspect mode • Complete regeneration • No comparison needed')\n );\n } else {\n console.log(styleText('gray', ' Smart comparison • Detect all changes • Targeted updates'));\n }\n\n try {\n // Step 1: Load configuration (same as push command)\n const { config, isCI } = await initializeCommand({\n configPath: options.config,\n profileName: options.profile,\n tag: options.tag,\n showSpinner: true,\n spinnerText: 'Loading configuration...',\n logConfig: true,\n quiet: options.quiet,\n });\n let projectDir: string;\n let projectId: string;\n let localProjectForId: any = null;\n\n const currentDir = process.cwd();\n const hasIndexInCurrent = existsSync(join(currentDir, 'index.ts'));\n\n if (hasIndexInCurrent) {\n // We're in a project directory\n projectDir = currentDir;\n\n try {\n localProjectForId = await loadProject(projectDir);\n const localProjectId = localProjectForId.getId();\n\n if (options.project) {\n // Validate that --project matches local project ID\n if (localProjectId !== options.project) {\n console.error(\n styleText(\n 'red',\n `Local project ID \"${localProjectId}\" doesn't match --project \"${options.project}\"`\n )\n );\n console.error(\n styleText(\n 'yellow',\n 'Either remove --project flag or ensure it matches the local project ID'\n )\n );\n if (batchMode) {\n return { success: false, error: 'Project ID mismatch' };\n }\n process.exit(1);\n }\n }\n\n projectId = localProjectId;\n console.log(styleText('green', `◆ Using local project: ${projectId}`));\n } catch (error) {\n throw new Error(\n `Could not load local project: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n } else {\n // No index.ts in current directory\n if (!options.project) {\n console.error(\n styleText(\n 'yellow',\n 'Please run this command from a directory containing index.ts or use --project <project-id>'\n )\n );\n if (batchMode) {\n return { success: false, error: 'No index.ts found and no --project specified' };\n }\n process.exit(1);\n }\n\n // Try --project as directory path first\n const projectPath = resolve(currentDir, options.project);\n const hasIndexInPath = existsSync(join(projectPath, 'index.ts'));\n\n if (hasIndexInPath) {\n // --project is a valid directory path\n projectDir = projectPath;\n try {\n localProjectForId = await loadProject(projectDir);\n projectId = localProjectForId.getId();\n console.log(styleText('green', `◆ Using project from path: ${projectId}`));\n } catch (error) {\n throw new Error(\n `Could not load project from ${projectPath}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n } else {\n // Treat --project as project ID, create subdirectory\n projectId = options.project;\n projectDir = join(currentDir, projectId);\n console.log(styleText('green', `◆ Creating new project directory: ${projectDir}`));\n }\n }\n\n const existingState = readProjectState(projectId);\n const lastPulledHash = existingState?.lastPulledHash;\n\n if (options.debug && lastPulledHash) {\n console.log(styleText('gray', ` Last pulled hash: ${lastPulledHash}`));\n }\n\n const apiClient = await ManagementApiClient.create(\n config.agentsApiUrl,\n options.config,\n config.tenantId,\n projectId,\n isCI,\n config.agentsApiKey\n );\n\n let currentMainHash: string | undefined;\n try {\n const mainBranch = await apiClient.getBranch(projectId, 'main');\n currentMainHash = mainBranch.hash;\n } catch (error) {\n if (options.debug) {\n console.log(\n styleText(\n 'gray',\n ` Could not fetch main branch hash: ${error instanceof Error ? error.message : String(error)}`\n )\n );\n }\n // Non-fatal: if we can't get the hash, fall through to direct pull\n }\n\n if (options.debug && currentMainHash) {\n console.log(styleText('gray', ` Current main hash: ${currentMainHash}`));\n }\n\n let remoteProject: Awaited<ReturnType<typeof apiClient.getFullProject>> | undefined;\n\n if (localProjectForId && lastPulledHash) {\n const tempBranchName = getTempBranchSuffix('cli-pull');\n\n try {\n await apiClient.createBranch(projectId, {\n name: tempBranchName,\n fromCommit: lastPulledHash,\n });\n\n const localProjectDefinition = await localProjectForId.getFullDefinition();\n await apiClient.pushFullProject(projectId, tempBranchName, localProjectDefinition);\n\n // Merge main INTO temp branch so the temp branch gets a reconciled result\n // (main's changes + user's local changes). We then pull from the temp branch.\n // We must NOT merge temp into main — that would push local edits to main.\n const preview = await apiClient.mergePreview(projectId, {\n sourceBranch: 'main',\n targetBranch: tempBranchName,\n });\n\n if (preview.hasConflicts) {\n const { resolveConflictsInteractive } = await import('../merge-conflicts');\n const resolutions = await resolveConflictsInteractive(preview.conflicts, options);\n\n if (resolutions === null) {\n console.log(styleText('yellow', 'Pull cancelled'));\n return;\n }\n\n await apiClient.mergeExecute(projectId, {\n sourceBranch: 'main',\n targetBranch: tempBranchName,\n sourceHash: preview.sourceHash,\n targetHash: preview.targetHash,\n resolutions,\n message: 'CLI pull: merge main into local state',\n });\n } else {\n await apiClient.mergeExecute(projectId, {\n sourceBranch: 'main',\n targetBranch: tempBranchName,\n sourceHash: preview.sourceHash,\n targetHash: preview.targetHash,\n message: 'CLI pull: merge main into local state',\n });\n }\n // Fetch the reconciled project from the temp branch before cleanup\n remoteProject = await apiClient.getFullProject(projectId, tempBranchName);\n } finally {\n try {\n await apiClient.deleteBranch(projectId, tempBranchName, true);\n } catch (cleanupError) {\n if (options.debug) {\n console.log(\n styleText(\n 'gray',\n ` Warning: Could not delete temp branch ${tempBranchName}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`\n )\n );\n }\n }\n }\n } else {\n // Todo: we can probably just exit here because there is nothing new to pull\n remoteProject = await apiClient.getFullProject(projectId);\n }\n\n if (options.debug && remoteProject.functions) {\n console.log(\n styleText('gray', ' 📋 Project-level functions from API:'),\n Object.keys(remoteProject.functions)\n );\n Object.entries(remoteProject.functions).forEach(([id, data]: [string, any]) => {\n console.log(\n styleText(\n 'gray',\n ` ${id}: has name=${!!data.name}, has description=${!!data.description}`\n )\n );\n });\n }\n\n // Normalize remote project (same as pull-v2 - hoist agent-level functionTools)\n if (remoteProject.agents) {\n for (const [agentId, agentData] of Object.entries(remoteProject.agents) as any[]) {\n if (agentData.functionTools) {\n remoteProject.functionTools = remoteProject.functionTools || {};\n Object.assign(remoteProject.functionTools, agentData.functionTools);\n if (options.debug) {\n console.log(\n styleText(\n 'gray',\n ` Hoisted functionTools from agent ${agentId}: ${Object.keys(agentData.functionTools).join(', ')}`\n )\n );\n }\n }\n if (agentData.functions) {\n remoteProject.functions ||= {};\n const { functions } = remoteProject;\n // Only hoist agent functions if project-level functions don't already exist (clean function data)\n Object.entries(agentData.functions).forEach(([funcId, funcData]: [string, any]) => {\n // Clean function data - remove functionTool metadata that shouldn't be in functions collection\n functions[funcId] ||= {\n id: funcData.id,\n inputSchema: funcData.inputSchema,\n executeCode: funcData.executeCode,\n dependencies: funcData.dependencies,\n createdAt: '',\n updatedAt: '',\n };\n });\n }\n }\n }\n\n // Filter out project-level tools from individual agents\n // The API includes project-level tools in each agent's tools field, but our generated\n // code structure keeps tools separate and imports them via canUse relationships\n if (remoteProject.agents && remoteProject.tools) {\n const projectToolIds = Object.keys(remoteProject.tools);\n\n for (const agentData of Object.values(remoteProject.agents) as any[]) {\n if (agentData.tools) {\n // Filter out any tools that are defined at project level\n const agentSpecificTools = Object.fromEntries(\n Object.entries(agentData.tools).filter(([toolId]) => !projectToolIds.includes(toolId))\n );\n\n // Only keep tools field if there are agent-specific tools remaining\n if (Object.keys(agentSpecificTools).length > 0) {\n agentData.tools = agentSpecificTools;\n } else {\n // Remove the tools field entirely if all tools were project-level\n delete agentData.tools;\n }\n }\n }\n }\n\n // Enrich canDelegateTo references with component type information\n // @ts-expect-error -- fixme Types of property `models` are incompatible.\n enrichCanDelegateToWithTypes(remoteProject);\n\n console.log(styleText('green', '◆ Project data fetched'));\n\n if (options.json) {\n console.log(JSON.stringify(remoteProject, null, 2));\n restoreLogLevel();\n return;\n }\n\n // Step 5: Set up project structure\n const paths = createProjectStructure(projectDir);\n\n await generateProjectSkillsIfPresent(remoteProject, paths.skillsDir);\n\n console.log(styleText('gray', 'Generating files...'));\n await introspectGenerate({\n // @ts-expect-error -- ignore Types of property 'models' are incompatible.\n project: remoteProject,\n paths,\n debug: options.debug,\n });\n console.log(styleText('green', '◆ All files generated'));\n\n try {\n const mainBranch = await apiClient.getBranch(projectId, 'main');\n writeProjectState(projectId, mainBranch.hash);\n } catch (error) {\n console.warn(\n styleText(\n 'yellow',\n `Warning: Could not save pull state: ${error instanceof Error ? error.message : String(error)}`\n )\n );\n console.warn(styleText('yellow', 'Future pulls may re-prompt for conflict resolution.'));\n }\n\n console.log(styleText('green', '\\nProject synced successfully!'));\n console.log(styleText('gray', ` Location: ${paths.projectRoot}`));\n console.log(styleText('gray', ` Environment: ${options.env || 'development'}`));\n console.log(\n styleText(\n 'yellow',\n '⚠️ If you encounter broken code after running `inkeep pull`, please report it at https://github.com/inkeep/agents/issues.'\n )\n );\n\n restoreLogLevel();\n if (batchMode) {\n return { success: true };\n }\n process.exit(0);\n } catch (error) {\n const message = error instanceof Error ? error.stack : String(error);\n console.error(styleText('red', `\\nError: ${message}`));\n if (options.debug && error instanceof Error) {\n console.error(styleText('red', error.stack || ''));\n }\n restoreLogLevel();\n if (batchMode) {\n return { success: false, error: message };\n }\n process.exit(1);\n }\n}\n\n/**\n * Pull all projects for the current tenant\n * Uses smart comparison with LLM merging for existing projects, introspect for new projects\n */\nasync function pullAllProjects(options: PullV3Options): Promise<void> {\n console.log(styleText('blue', '\\n🔄 Batch Pull: Sequential processing with smart comparison\\n'));\n console.log(\n styleText(\n 'gray',\n ' • Existing projects: Smart comparison + AST merging + confirmation prompts'\n )\n );\n console.log(styleText('gray', ' • New projects: Fresh generation with introspect mode\\n'));\n\n // Background version check (only once for batch)\n performBackgroundVersionCheck();\n\n // Load configuration first\n const { config, isCI } = await initializeCommand({\n configPath: options.config,\n profileName: options.profile,\n tag: options.tag,\n showSpinner: true,\n spinnerText: 'Loading configuration...',\n logConfig: true,\n quiet: options.quiet,\n });\n\n const s = p.spinner();\n\n try {\n // Fetch all projects from the API\n s.start('Fetching project list from API...');\n const apiClient = await ManagementApiClient.create(\n config.agentsApiUrl,\n options.config,\n config.tenantId,\n undefined,\n isCI,\n config.agentsApiKey\n );\n\n const projects = await apiClient.listAllProjects();\n s.stop(`Found ${projects.length} project(s)`);\n\n if (!projects.length) {\n console.log(styleText('yellow', 'No projects found for this tenant.'));\n process.exit(0);\n }\n\n // Categorize projects\n const existingProjects: typeof projects = [];\n const newProjects: typeof projects = [];\n\n for (const project of projects) {\n const targetDir = join(process.cwd(), project.id);\n if (existsSync(join(targetDir, 'index.ts'))) {\n existingProjects.push(project);\n } else {\n newProjects.push(project);\n }\n }\n\n console.log(styleText('gray', '\\nProjects to pull:\\n'));\n if (existingProjects.length > 0) {\n console.log(styleText('cyan', ' Existing (smart comparison):'));\n for (const project of existingProjects) {\n console.log(styleText('gray', ` • ${project.name || project.id} (${project.id})`));\n }\n }\n if (newProjects.length > 0) {\n console.log(styleText('cyan', ' New (introspect):'));\n for (const project of newProjects) {\n console.log(styleText('gray', ` • ${project.name || project.id} (${project.id})`));\n }\n }\n console.log();\n\n const results: BatchPullResult[] = [];\n const total = projects.length;\n\n for (let i = 0; i < projects.length; i++) {\n const project = projects[i];\n const progress = `[${i + 1}/${total}]`;\n\n console.log(styleText('cyan', `\\n${'─'.repeat(60)}`));\n console.log(styleText('cyan', `${progress} Pulling ${project.name || project.id}...`));\n\n const result = await pullSingleProject(project.id, project.name, options, config, isCI);\n results.push(result);\n\n if (result.success) {\n console.log(\n styleText(\n 'green',\n `\\n ✓ ${result.projectName || result.projectId} → ${result.targetDir}`\n )\n );\n } else {\n console.log(\n styleText('red', `\\n ✗ ${result.projectName || result.projectId}: ${result.error}`)\n );\n }\n }\n\n // Print summary\n const succeeded = results.filter((r) => r.success).length;\n const failed = results.filter((r) => !r.success).length;\n\n console.log(styleText('cyan', `\\n${'═'.repeat(60)}`));\n console.log(styleText('cyan', '📊 Batch Pull Summary:'));\n console.log(styleText('green', ` ✓ Succeeded: ${succeeded}`));\n if (failed > 0) {\n console.log(styleText('red', ` ✗ Failed: ${failed}`));\n\n console.log(styleText('red', '\\nFailed projects:'));\n for (const result of results) {\n if (!result.success) {\n console.log(styleText('red', ` • ${result.projectId}: ${result.error}`));\n }\n }\n }\n\n process.exit(failed > 0 ? 1 : 0);\n } catch (error) {\n s.stop();\n console.error(\n styleText('red', `\\nError: ${error instanceof Error ? error.message : String(error)}`)\n );\n process.exit(1);\n }\n}\n\n/**\n * Pull a single project (used by batch operations)\n * Uses smart comparison flow for existing projects, introspect for new projects\n */\nexport async function pullSingleProject(\n projectId: string,\n projectName: string | undefined,\n options: PullV3Options,\n config: any,\n isCI?: boolean\n): Promise<BatchPullResult> {\n const targetDir = join(process.cwd(), projectId);\n const hasExistingProject = existsSync(join(targetDir, 'index.ts'));\n\n try {\n if (hasExistingProject) {\n // Project exists locally - use smart comparison flow with LLM merging and user prompts\n console.log(styleText('gray', ` 📂 Existing project found - using smart comparison mode`));\n\n // Save current directory and change to project directory\n const originalDir = process.cwd();\n process.chdir(targetDir);\n\n try {\n // Call the main pull command in batch mode (returns results instead of exiting)\n const result = await pullV4Command({\n ...options,\n project: projectId,\n all: false, // Don't recurse into batch mode\n _batchMode: true,\n });\n\n // Restore original directory\n process.chdir(originalDir);\n\n if (result && typeof result === 'object') {\n return {\n projectId,\n projectName,\n targetDir,\n success: result.success,\n error: result.error,\n };\n }\n\n return {\n projectId,\n projectName,\n targetDir,\n success: true,\n };\n } catch (error) {\n // Restore original directory even on error\n process.chdir(originalDir);\n throw error;\n }\n }\n console.log(styleText('gray', ' New project'));\n\n // Suppress SDK logging\n const originalLogLevel = process.env.LOG_LEVEL;\n process.env.LOG_LEVEL = 'silent';\n\n const restoreLogLevel = () => {\n if (originalLogLevel !== undefined) {\n process.env.LOG_LEVEL = originalLogLevel;\n } else {\n delete process.env.LOG_LEVEL;\n }\n };\n\n // Fetch project data from API\n const apiClient = await ManagementApiClient.create(\n config.agentsApiUrl,\n options.config,\n config.tenantId,\n projectId,\n isCI,\n config.agentsApiKey\n );\n\n const remoteProject = await apiClient.getFullProject(projectId);\n // Create project structure\n const paths = createProjectStructure(targetDir);\n await generateProjectSkillsIfPresent(remoteProject, paths.skillsDir);\n\n // Generate all files using introspect mode for new projects\n await introspectGenerate({\n // @ts-expect-error -- ignore Types of property 'models' are incompatible.\n project: remoteProject,\n paths,\n });\n\n restoreLogLevel();\n\n return {\n projectId,\n projectName: projectName || remoteProject.name,\n targetDir,\n success: true,\n };\n } catch (error) {\n return {\n projectId,\n projectName,\n targetDir,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nasync function generateProjectSkillsIfPresent(\n remoteProject: any,\n skillsDir: string\n): Promise<void> {\n const skills = remoteProject.skills ?? {};\n if (!Object.keys(skills).length) {\n return;\n }\n\n const { generateSkills } = await import('../skill');\n await generateSkills(skills, skillsDir);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmBA,aAAa,sBAAsB;;;;AA2DnC,SAAgB,uBAAuB,aAAmC;AACxE,WAAU,aAAa,EAAE,WAAW,MAAM,CAAC;AAC3C,QAAO;EACL;EACA,WAAW,KAAK,aAAa,SAAS;EACtC,UAAU,KAAK,aAAa,QAAQ;EACpC,mBAAmB,KAAK,aAAa,kBAAkB;EACvD,uBAAuB,KAAK,aAAa,sBAAsB;EAC/D,qBAAqB,KAAK,aAAa,oBAAoB;EAC3D,iBAAiB,KAAK,aAAa,eAAe;EAClD,gBAAgB,KAAK,aAAa,cAAc;EAChD,mBAAmB,KAAK,aAAa,kBAAkB;EACvD,mBAAmB,KAAK,aAAa,kBAAkB;EACvD,WAAW,KAAK,aAAa,SAAS;EACvC;;;;;AAMH,SAAgB,6BAA6B,SAAsC;CACjF,MAAM,EAAE,WAAW;CAEnB,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,OAAO,CAAC;CAChD,MAAM,iBAAiB,IAAI,IACzB,OAAO,OAAO,OAAO,CAAC,SAAS,cAAc,OAAO,KAAK,UAAU,UAAU,CAAC,CAC/E;CACD,MAAM,sBAAsB,IAAI,IAC9B,QAAQ,iBAAiB,OAAO,KAAK,QAAQ,eAAe,GAAG,EAAE,CAClE;CAID,SAAS,yBAAyB,eAAqC;AACrE,SAAO,cAAc,KAAK,SAAS;AAEjC,OAAI,OAAO,SAAS,SAAU,QAAO;AACrC,OAAI,YAAY,IAAI,KAAK,CAAE,QAAO,EAAE,SAAS,MAAM;AACnD,OAAI,eAAe,IAAI,KAAK,CAAE,QAAO,EAAE,YAAY,MAAM;AACzD,OAAI,oBAAoB,IAAI,KAAK,CAAE,QAAO,EAAE,iBAAiB,MAAM;AACnE,UAAO;IACP;;AAIJ,MAAK,MAAM,EAAE,eAAe,OAAO,OAAO,QAAQ,OAAO,CAEvD,MAAK,MAAM,gBAAgB,OAAO,OAAO,UAAU,CACjD,KAAI,MAAM,QAAQ,aAAa,cAAc,CAE3C,cAAa,gBAAgB,yBAAyB,aAAa,cAAc;;;;;;AAUzF,eAAsB,cAAc,SAAyD;AAE3F,KAAI,QAAQ,KAAK;AACf,QAAM,gBAAgB,QAAQ;AAC9B;;CAGF,MAAM,YAAY,QAAQ,cAAc;CAGxC,MAAM,mBAAmB,QAAQ,IAAI;AACrC,SAAQ,IAAI,YAAY;CAExB,MAAM,wBAAwB;AAC5B,MAAI,qBAAqB,OACvB,SAAQ,IAAI,YAAY;MAExB,QAAO,QAAQ,IAAI;;AAKvB,KAAI,CAAC,UACH,gCAA+B;AAGjC,SAAQ,IAAI,UAAU,QAAQ,iBAAiB,CAAC;AAChD,KAAI,QAAQ,WACV,SAAQ,IACN,UAAU,QAAQ,mEAAmE,CACtF;KAED,SAAQ,IAAI,UAAU,QAAQ,6DAA6D,CAAC;AAG9F,KAAI;EAEF,MAAM,EAAE,QAAQ,SAAS,MAAM,kBAAkB;GAC/C,YAAY,QAAQ;GACpB,aAAa,QAAQ;GACrB,KAAK,QAAQ;GACb,aAAa;GACb,aAAa;GACb,WAAW;GACX,OAAO,QAAQ;GAChB,CAAC;EACF,IAAI;EACJ,IAAI;EACJ,IAAI,oBAAyB;EAE7B,MAAM,aAAa,QAAQ,KAAK;AAGhC,MAF0B,WAAW,KAAK,YAAY,WAAW,CAAC,EAE3C;AAErB,gBAAa;AAEb,OAAI;AACF,wBAAoB,MAAM,YAAY,WAAW;IACjD,MAAM,iBAAiB,kBAAkB,OAAO;AAEhD,QAAI,QAAQ,SAEV;SAAI,mBAAmB,QAAQ,SAAS;AACtC,cAAQ,MACN,UACE,OACA,qBAAqB,eAAe,6BAA6B,QAAQ,QAAQ,GAClF,CACF;AACD,cAAQ,MACN,UACE,UACA,yEACD,CACF;AACD,UAAI,UACF,QAAO;OAAE,SAAS;OAAO,OAAO;OAAuB;AAEzD,cAAQ,KAAK,EAAE;;;AAInB,gBAAY;AACZ,YAAQ,IAAI,UAAU,SAAS,0BAA0B,YAAY,CAAC;YAC/D,OAAO;AACd,UAAM,IAAI,MACR,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACxF;;SAEE;AAEL,OAAI,CAAC,QAAQ,SAAS;AACpB,YAAQ,MACN,UACE,UACA,6FACD,CACF;AACD,QAAI,UACF,QAAO;KAAE,SAAS;KAAO,OAAO;KAAgD;AAElF,YAAQ,KAAK,EAAE;;GAIjB,MAAM,cAAc,QAAQ,YAAY,QAAQ,QAAQ;AAGxD,OAFuB,WAAW,KAAK,aAAa,WAAW,CAAC,EAE5C;AAElB,iBAAa;AACb,QAAI;AACF,yBAAoB,MAAM,YAAY,WAAW;AACjD,iBAAY,kBAAkB,OAAO;AACrC,aAAQ,IAAI,UAAU,SAAS,8BAA8B,YAAY,CAAC;aACnE,OAAO;AACd,WAAM,IAAI,MACR,+BAA+B,YAAY,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACtG;;UAEE;AAEL,gBAAY,QAAQ;AACpB,iBAAa,KAAK,YAAY,UAAU;AACxC,YAAQ,IAAI,UAAU,SAAS,qCAAqC,aAAa,CAAC;;;EAKtF,MAAM,iBADgB,iBAAiB,UAAU,EACX;AAEtC,MAAI,QAAQ,SAAS,eACnB,SAAQ,IAAI,UAAU,QAAQ,wBAAwB,iBAAiB,CAAC;EAG1E,MAAM,YAAY,MAAM,oBAAoB,OAC1C,OAAO,cACP,QAAQ,QACR,OAAO,UACP,WACA,MACA,OAAO,aACR;EAED,IAAI;AACJ,MAAI;AAEF,sBADmB,MAAM,UAAU,UAAU,WAAW,OAAO,EAClC;WACtB,OAAO;AACd,OAAI,QAAQ,MACV,SAAQ,IACN,UACE,QACA,wCAAwC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAC/F,CACF;;AAKL,MAAI,QAAQ,SAAS,gBACnB,SAAQ,IAAI,UAAU,QAAQ,yBAAyB,kBAAkB,CAAC;EAG5E,IAAI;AAEJ,MAAI,qBAAqB,gBAAgB;GACvC,MAAM,iBAAiB,oBAAoB,WAAW;AAEtD,OAAI;AACF,UAAM,UAAU,aAAa,WAAW;KACtC,MAAM;KACN,YAAY;KACb,CAAC;IAEF,MAAM,yBAAyB,MAAM,kBAAkB,mBAAmB;AAC1E,UAAM,UAAU,gBAAgB,WAAW,gBAAgB,uBAAuB;IAKlF,MAAM,UAAU,MAAM,UAAU,aAAa,WAAW;KACtD,cAAc;KACd,cAAc;KACf,CAAC;AAEF,QAAI,QAAQ,cAAc;KACxB,MAAM,EAAE,gCAAgC,MAAM,OAAO;KACrD,MAAM,cAAc,MAAM,4BAA4B,QAAQ,WAAW,QAAQ;AAEjF,SAAI,gBAAgB,MAAM;AACxB,cAAQ,IAAI,UAAU,UAAU,iBAAiB,CAAC;AAClD;;AAGF,WAAM,UAAU,aAAa,WAAW;MACtC,cAAc;MACd,cAAc;MACd,YAAY,QAAQ;MACpB,YAAY,QAAQ;MACpB;MACA,SAAS;MACV,CAAC;UAEF,OAAM,UAAU,aAAa,WAAW;KACtC,cAAc;KACd,cAAc;KACd,YAAY,QAAQ;KACpB,YAAY,QAAQ;KACpB,SAAS;KACV,CAAC;AAGJ,oBAAgB,MAAM,UAAU,eAAe,WAAW,eAAe;aACjE;AACR,QAAI;AACF,WAAM,UAAU,aAAa,WAAW,gBAAgB,KAAK;aACtD,cAAc;AACrB,SAAI,QAAQ,MACV,SAAQ,IACN,UACE,QACA,4CAA4C,eAAe,IAAI,wBAAwB,QAAQ,aAAa,UAAU,OAAO,aAAa,GAC3I,CACF;;;QAMP,iBAAgB,MAAM,UAAU,eAAe,UAAU;AAG3D,MAAI,QAAQ,SAAS,cAAc,WAAW;AAC5C,WAAQ,IACN,UAAU,QAAQ,0CAA0C,EAC5D,OAAO,KAAK,cAAc,UAAU,CACrC;AACD,UAAO,QAAQ,cAAc,UAAU,CAAC,SAAS,CAAC,IAAI,UAAyB;AAC7E,YAAQ,IACN,UACE,QACA,SAAS,GAAG,aAAa,CAAC,CAAC,KAAK,KAAK,oBAAoB,CAAC,CAAC,KAAK,cACjE,CACF;KACD;;AAIJ,MAAI,cAAc,OAChB,MAAK,MAAM,CAAC,SAAS,cAAc,OAAO,QAAQ,cAAc,OAAO,EAAW;AAChF,OAAI,UAAU,eAAe;AAC3B,kBAAc,gBAAgB,cAAc,iBAAiB,EAAE;AAC/D,WAAO,OAAO,cAAc,eAAe,UAAU,cAAc;AACnE,QAAI,QAAQ,MACV,SAAQ,IACN,UACE,QACA,uCAAuC,QAAQ,IAAI,OAAO,KAAK,UAAU,cAAc,CAAC,KAAK,KAAK,GACnG,CACF;;AAGL,OAAI,UAAU,WAAW;AACvB,kBAAc,cAAc,EAAE;IAC9B,MAAM,EAAE,cAAc;AAEtB,WAAO,QAAQ,UAAU,UAAU,CAAC,SAAS,CAAC,QAAQ,cAA6B;AAEjF,eAAU,YAAY;MACpB,IAAI,SAAS;MACb,aAAa,SAAS;MACtB,aAAa,SAAS;MACtB,cAAc,SAAS;MACvB,WAAW;MACX,WAAW;MACZ;MACD;;;AAQR,MAAI,cAAc,UAAU,cAAc,OAAO;GAC/C,MAAM,iBAAiB,OAAO,KAAK,cAAc,MAAM;AAEvD,QAAK,MAAM,aAAa,OAAO,OAAO,cAAc,OAAO,CACzD,KAAI,UAAU,OAAO;IAEnB,MAAM,qBAAqB,OAAO,YAChC,OAAO,QAAQ,UAAU,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,eAAe,SAAS,OAAO,CAAC,CACvF;AAGD,QAAI,OAAO,KAAK,mBAAmB,CAAC,SAAS,EAC3C,WAAU,QAAQ;QAGlB,QAAO,UAAU;;;AAQzB,+BAA6B,cAAc;AAE3C,UAAQ,IAAI,UAAU,SAAS,yBAAyB,CAAC;AAEzD,MAAI,QAAQ,MAAM;AAChB,WAAQ,IAAI,KAAK,UAAU,eAAe,MAAM,EAAE,CAAC;AACnD,oBAAiB;AACjB;;EAIF,MAAM,QAAQ,uBAAuB,WAAW;AAEhD,QAAM,+BAA+B,eAAe,MAAM,UAAU;AAEpE,UAAQ,IAAI,UAAU,QAAQ,sBAAsB,CAAC;AACrD,QAAM,mBAAmB;GAEvB,SAAS;GACT;GACA,OAAO,QAAQ;GAChB,CAAC;AACF,UAAQ,IAAI,UAAU,SAAS,wBAAwB,CAAC;AAExD,MAAI;GACF,MAAM,aAAa,MAAM,UAAU,UAAU,WAAW,OAAO;AAC/D,qBAAkB,WAAW,WAAW,KAAK;WACtC,OAAO;AACd,WAAQ,KACN,UACE,UACA,uCAAuC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAC9F,CACF;AACD,WAAQ,KAAK,UAAU,UAAU,sDAAsD,CAAC;;AAG1F,UAAQ,IAAI,UAAU,SAAS,iCAAiC,CAAC;AACjE,UAAQ,IAAI,UAAU,QAAQ,gBAAgB,MAAM,cAAc,CAAC;AACnE,UAAQ,IAAI,UAAU,QAAQ,mBAAmB,QAAQ,OAAO,gBAAgB,CAAC;AACjF,UAAQ,IACN,UACE,UACA,6HACD,CACF;AAED,mBAAiB;AACjB,MAAI,UACF,QAAO,EAAE,SAAS,MAAM;AAE1B,UAAQ,KAAK,EAAE;UACR,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,QAAQ,OAAO,MAAM;AACpE,UAAQ,MAAM,UAAU,OAAO,YAAY,UAAU,CAAC;AACtD,MAAI,QAAQ,SAAS,iBAAiB,MACpC,SAAQ,MAAM,UAAU,OAAO,MAAM,SAAS,GAAG,CAAC;AAEpD,mBAAiB;AACjB,MAAI,UACF,QAAO;GAAE,SAAS;GAAO,OAAO;GAAS;AAE3C,UAAQ,KAAK,EAAE;;;;;;;AAQnB,eAAe,gBAAgB,SAAuC;AACpE,SAAQ,IAAI,UAAU,QAAQ,iEAAiE,CAAC;AAChG,SAAQ,IACN,UACE,QACA,+EACD,CACF;AACD,SAAQ,IAAI,UAAU,QAAQ,4DAA4D,CAAC;AAG3F,gCAA+B;CAG/B,MAAM,EAAE,QAAQ,SAAS,MAAM,kBAAkB;EAC/C,YAAY,QAAQ;EACpB,aAAa,QAAQ;EACrB,KAAK,QAAQ;EACb,aAAa;EACb,aAAa;EACb,WAAW;EACX,OAAO,QAAQ;EAChB,CAAC;CAEF,MAAM,IAAI,EAAE,SAAS;AAErB,KAAI;AAEF,IAAE,MAAM,oCAAoC;EAU5C,MAAM,WAAW,OATC,MAAM,oBAAoB,OAC1C,OAAO,cACP,QAAQ,QACR,OAAO,UACP,QACA,MACA,OAAO,aACR,EAEgC,iBAAiB;AAClD,IAAE,KAAK,SAAS,SAAS,OAAO,aAAa;AAE7C,MAAI,CAAC,SAAS,QAAQ;AACpB,WAAQ,IAAI,UAAU,UAAU,qCAAqC,CAAC;AACtE,WAAQ,KAAK,EAAE;;EAIjB,MAAM,mBAAoC,EAAE;EAC5C,MAAM,cAA+B,EAAE;AAEvC,OAAK,MAAM,WAAW,SAEpB,KAAI,WAAW,KADG,KAAK,QAAQ,KAAK,EAAE,QAAQ,GAAG,EAClB,WAAW,CAAC,CACzC,kBAAiB,KAAK,QAAQ;MAE9B,aAAY,KAAK,QAAQ;AAI7B,UAAQ,IAAI,UAAU,QAAQ,wBAAwB,CAAC;AACvD,MAAI,iBAAiB,SAAS,GAAG;AAC/B,WAAQ,IAAI,UAAU,QAAQ,iCAAiC,CAAC;AAChE,QAAK,MAAM,WAAW,iBACpB,SAAQ,IAAI,UAAU,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,GAAG,IAAI,QAAQ,GAAG,GAAG,CAAC;;AAGzF,MAAI,YAAY,SAAS,GAAG;AAC1B,WAAQ,IAAI,UAAU,QAAQ,sBAAsB,CAAC;AACrD,QAAK,MAAM,WAAW,YACpB,SAAQ,IAAI,UAAU,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,GAAG,IAAI,QAAQ,GAAG,GAAG,CAAC;;AAGzF,UAAQ,KAAK;EAEb,MAAM,UAA6B,EAAE;EACrC,MAAM,QAAQ,SAAS;AAEvB,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;GACxC,MAAM,UAAU,SAAS;GACzB,MAAM,WAAW,IAAI,IAAI,EAAE,GAAG,MAAM;AAEpC,WAAQ,IAAI,UAAU,QAAQ,KAAK,IAAI,OAAO,GAAG,GAAG,CAAC;AACrD,WAAQ,IAAI,UAAU,QAAQ,GAAG,SAAS,WAAW,QAAQ,QAAQ,QAAQ,GAAG,KAAK,CAAC;GAEtF,MAAM,SAAS,MAAM,kBAAkB,QAAQ,IAAI,QAAQ,MAAM,SAAS,QAAQ,KAAK;AACvF,WAAQ,KAAK,OAAO;AAEpB,OAAI,OAAO,QACT,SAAQ,IACN,UACE,SACA,SAAS,OAAO,eAAe,OAAO,UAAU,KAAK,OAAO,YAC7D,CACF;OAED,SAAQ,IACN,UAAU,OAAO,SAAS,OAAO,eAAe,OAAO,UAAU,IAAI,OAAO,QAAQ,CACrF;;EAKL,MAAM,YAAY,QAAQ,QAAQ,MAAM,EAAE,QAAQ,CAAC;EACnD,MAAM,SAAS,QAAQ,QAAQ,MAAM,CAAC,EAAE,QAAQ,CAAC;AAEjD,UAAQ,IAAI,UAAU,QAAQ,KAAK,IAAI,OAAO,GAAG,GAAG,CAAC;AACrD,UAAQ,IAAI,UAAU,QAAQ,yBAAyB,CAAC;AACxD,UAAQ,IAAI,UAAU,SAAS,kBAAkB,YAAY,CAAC;AAC9D,MAAI,SAAS,GAAG;AACd,WAAQ,IAAI,UAAU,OAAO,eAAe,SAAS,CAAC;AAEtD,WAAQ,IAAI,UAAU,OAAO,qBAAqB,CAAC;AACnD,QAAK,MAAM,UAAU,QACnB,KAAI,CAAC,OAAO,QACV,SAAQ,IAAI,UAAU,OAAO,OAAO,OAAO,UAAU,IAAI,OAAO,QAAQ,CAAC;;AAK/E,UAAQ,KAAK,SAAS,IAAI,IAAI,EAAE;UACzB,OAAO;AACd,IAAE,MAAM;AACR,UAAQ,MACN,UAAU,OAAO,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAAG,CACvF;AACD,UAAQ,KAAK,EAAE;;;;;;;AAQnB,eAAsB,kBACpB,WACA,aACA,SACA,QACA,MAC0B;CAC1B,MAAM,YAAY,KAAK,QAAQ,KAAK,EAAE,UAAU;CAChD,MAAM,qBAAqB,WAAW,KAAK,WAAW,WAAW,CAAC;AAElE,KAAI;AACF,MAAI,oBAAoB;AAEtB,WAAQ,IAAI,UAAU,QAAQ,6DAA6D,CAAC;GAG5F,MAAM,cAAc,QAAQ,KAAK;AACjC,WAAQ,MAAM,UAAU;AAExB,OAAI;IAEF,MAAM,SAAS,MAAM,cAAc;KACjC,GAAG;KACH,SAAS;KACT,KAAK;KACL,YAAY;KACb,CAAC;AAGF,YAAQ,MAAM,YAAY;AAE1B,QAAI,UAAU,OAAO,WAAW,SAC9B,QAAO;KACL;KACA;KACA;KACA,SAAS,OAAO;KAChB,OAAO,OAAO;KACf;AAGH,WAAO;KACL;KACA;KACA;KACA,SAAS;KACV;YACM,OAAO;AAEd,YAAQ,MAAM,YAAY;AAC1B,UAAM;;;AAGV,UAAQ,IAAI,UAAU,QAAQ,iBAAiB,CAAC;EAGhD,MAAM,mBAAmB,QAAQ,IAAI;AACrC,UAAQ,IAAI,YAAY;EAExB,MAAM,wBAAwB;AAC5B,OAAI,qBAAqB,OACvB,SAAQ,IAAI,YAAY;OAExB,QAAO,QAAQ,IAAI;;EAcvB,MAAM,gBAAgB,OATJ,MAAM,oBAAoB,OAC1C,OAAO,cACP,QAAQ,QACR,OAAO,UACP,WACA,MACA,OAAO,aACR,EAEqC,eAAe,UAAU;EAE/D,MAAM,QAAQ,uBAAuB,UAAU;AAC/C,QAAM,+BAA+B,eAAe,MAAM,UAAU;AAGpE,QAAM,mBAAmB;GAEvB,SAAS;GACT;GACD,CAAC;AAEF,mBAAiB;AAEjB,SAAO;GACL;GACA,aAAa,eAAe,cAAc;GAC1C;GACA,SAAS;GACV;UACM,OAAO;AACd,SAAO;GACL;GACA;GACA;GACA,SAAS;GACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC9D;;;AAIL,eAAe,+BACb,eACA,WACe;CACf,MAAM,SAAS,cAAc,UAAU,EAAE;AACzC,KAAI,CAAC,OAAO,KAAK,OAAO,CAAC,OACvB;CAGF,MAAM,EAAE,mBAAmB,MAAM,OAAO;AACxC,OAAM,eAAe,QAAQ,UAAU"}
@@ -0,0 +1,37 @@
1
+ import { formatEntityId } from "./merge-ui/utils.js";
2
+ import { MergeApp } from "./merge-ui/merge-app.js";
3
+ import { styleText } from "node:util";
4
+ import { render } from "ink";
5
+ import { createElement } from "react";
6
+
7
+ //#region src/commands/pull-v4/merge-conflicts.ts
8
+ async function resolveConflictsInteractive(conflicts, options) {
9
+ if (options.conflictStrategy) {
10
+ const pick = options.conflictStrategy;
11
+ console.log(styleText("gray", `\nAuto-resolving ${conflicts.length} conflict(s) with strategy: ${pick}`));
12
+ return conflicts.map((conflict) => ({
13
+ table: conflict.table,
14
+ primaryKey: conflict.primaryKey,
15
+ rowDefaultPick: pick
16
+ }));
17
+ }
18
+ const result = await render(createElement(MergeApp, { conflicts })).waitUntilExit();
19
+ if (result === null) {
20
+ console.log(styleText("yellow", "\nMerge cancelled."));
21
+ return null;
22
+ }
23
+ const resolutions = result;
24
+ console.log(styleText("green", `\n✓ Resolved ${resolutions.length} conflict(s):`));
25
+ for (const res of resolutions) {
26
+ const entity = formatEntityId(res.primaryKey);
27
+ const overrides = Object.values(res.columns ?? {}).filter((pick) => pick !== res.rowDefaultPick);
28
+ const overrideNote = overrides.length > 0 ? ` (${overrides.length} column override(s))` : "";
29
+ console.log(` ${styleText("cyan", res.table)} ${styleText("bold", entity)} → ${res.rowDefaultPick}${overrideNote}`);
30
+ }
31
+ console.log();
32
+ return resolutions;
33
+ }
34
+
35
+ //#endregion
36
+ export { resolveConflictsInteractive };
37
+ //# sourceMappingURL=merge-conflicts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-conflicts.js","names":[],"sources":["../../../src/commands/pull-v4/merge-conflicts.ts"],"sourcesContent":["import { styleText } from 'node:util';\nimport type { ConflictItem, ConflictResolution } from '@inkeep/agents-core';\nimport { render } from 'ink';\nimport { createElement } from 'react';\nimport { MergeApp } from './merge-ui/merge-app';\nimport { formatEntityId } from './merge-ui/utils';\n\nexport interface ResolveConflictsOptions {\n conflictStrategy?: 'ours' | 'theirs';\n}\n\nexport async function resolveConflictsInteractive(\n conflicts: ConflictItem[],\n options: ResolveConflictsOptions\n): Promise<ConflictResolution[] | null> {\n if (options.conflictStrategy) {\n const pick = options.conflictStrategy;\n console.log(\n styleText('gray', `\\nAuto-resolving ${conflicts.length} conflict(s) with strategy: ${pick}`)\n );\n return conflicts.map((conflict) => ({\n table: conflict.table,\n primaryKey: conflict.primaryKey,\n rowDefaultPick: pick,\n }));\n }\n\n const instance = render(createElement(MergeApp, { conflicts }));\n const result = await instance.waitUntilExit();\n\n if (result === null) {\n console.log(styleText('yellow', '\\nMerge cancelled.'));\n return null;\n }\n\n const resolutions = result as ConflictResolution[];\n\n console.log(styleText('green', `\\n✓ Resolved ${resolutions.length} conflict(s):`));\n for (const res of resolutions) {\n const entity = formatEntityId(res.primaryKey);\n const overrides = Object.values(res.columns ?? {}).filter(\n (pick) => pick !== res.rowDefaultPick\n );\n const overrideNote = overrides.length > 0 ? ` (${overrides.length} column override(s))` : '';\n console.log(\n ` ${styleText('cyan', res.table)} ${styleText('bold', entity)} → ${res.rowDefaultPick}${overrideNote}`\n );\n }\n console.log();\n\n return resolutions;\n}\n"],"mappings":";;;;;;;AAWA,eAAsB,4BACpB,WACA,SACsC;AACtC,KAAI,QAAQ,kBAAkB;EAC5B,MAAM,OAAO,QAAQ;AACrB,UAAQ,IACN,UAAU,QAAQ,oBAAoB,UAAU,OAAO,8BAA8B,OAAO,CAC7F;AACD,SAAO,UAAU,KAAK,cAAc;GAClC,OAAO,SAAS;GAChB,YAAY,SAAS;GACrB,gBAAgB;GACjB,EAAE;;CAIL,MAAM,SAAS,MADE,OAAO,cAAc,UAAU,EAAE,WAAW,CAAC,CAAC,CACjC,eAAe;AAE7C,KAAI,WAAW,MAAM;AACnB,UAAQ,IAAI,UAAU,UAAU,qBAAqB,CAAC;AACtD,SAAO;;CAGT,MAAM,cAAc;AAEpB,SAAQ,IAAI,UAAU,SAAS,gBAAgB,YAAY,OAAO,eAAe,CAAC;AAClF,MAAK,MAAM,OAAO,aAAa;EAC7B,MAAM,SAAS,eAAe,IAAI,WAAW;EAC7C,MAAM,YAAY,OAAO,OAAO,IAAI,WAAW,EAAE,CAAC,CAAC,QAChD,SAAS,SAAS,IAAI,eACxB;EACD,MAAM,eAAe,UAAU,SAAS,IAAI,KAAK,UAAU,OAAO,wBAAwB;AAC1F,UAAQ,IACN,KAAK,UAAU,QAAQ,IAAI,MAAM,CAAC,GAAG,UAAU,QAAQ,OAAO,CAAC,KAAK,IAAI,iBAAiB,eAC1F;;AAEH,SAAQ,KAAK;AAEb,QAAO"}
@@ -0,0 +1,63 @@
1
+ import { formatValue } from "./utils.js";
2
+ import { Box, Text } from "ink";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+
5
+ //#region src/commands/pull-v4/merge-ui/column-row.tsx
6
+ function ValueLine({ label, value, isPicked }) {
7
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, {
8
+ color: isPicked ? "green" : void 0,
9
+ dimColor: !isPicked,
10
+ children: [
11
+ " ",
12
+ label,
13
+ " ",
14
+ formatValue(value)
15
+ ]
16
+ }) });
17
+ }
18
+ function ColumnRow({ columnName, oursValue, theirsValue, pick, isFocused, isLast }) {
19
+ return /* @__PURE__ */ jsxs(Box, {
20
+ flexDirection: "column",
21
+ marginBottom: isLast ? 0 : 1,
22
+ children: [
23
+ /* @__PURE__ */ jsxs(Box, {
24
+ justifyContent: "space-between",
25
+ children: [/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
26
+ color: "cyan",
27
+ children: isFocused ? "▸ " : " "
28
+ }), /* @__PURE__ */ jsx(Text, {
29
+ bold: isFocused,
30
+ children: columnName
31
+ })] }), /* @__PURE__ */ jsxs(Box, { children: [
32
+ /* @__PURE__ */ jsx(Text, {
33
+ dimColor: !isFocused,
34
+ children: "pick: "
35
+ }),
36
+ /* @__PURE__ */ jsx(Text, {
37
+ bold: isFocused,
38
+ color: pick === "ours" ? "blue" : "magenta",
39
+ children: pick
40
+ }),
41
+ isFocused ? /* @__PURE__ */ jsx(Text, {
42
+ color: "green",
43
+ children: " ◀"
44
+ }) : null
45
+ ] })]
46
+ }),
47
+ /* @__PURE__ */ jsx(ValueLine, {
48
+ label: "ours: ",
49
+ value: oursValue,
50
+ isPicked: pick === "ours"
51
+ }),
52
+ /* @__PURE__ */ jsx(ValueLine, {
53
+ label: "theirs:",
54
+ value: theirsValue,
55
+ isPicked: pick === "theirs"
56
+ })
57
+ ]
58
+ });
59
+ }
60
+
61
+ //#endregion
62
+ export { ColumnRow };
63
+ //# sourceMappingURL=column-row.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"column-row.js","names":[],"sources":["../../../../src/commands/pull-v4/merge-ui/column-row.tsx"],"sourcesContent":["import { Box, Text } from 'ink';\nimport type { Side } from './types';\nimport { formatValue } from './utils';\n\ninterface ColumnRowProps {\n columnName: string;\n oursValue: unknown;\n theirsValue: unknown;\n pick: Side;\n isFocused: boolean;\n isLast: boolean;\n}\n\nfunction ValueLine({\n label,\n value,\n isPicked,\n}: {\n label: string;\n value: unknown;\n isPicked: boolean;\n}) {\n return (\n <Box>\n <Text color={isPicked ? 'green' : undefined} dimColor={!isPicked}>\n {' '}\n {label} {formatValue(value)}\n </Text>\n </Box>\n );\n}\n\nexport function ColumnRow({\n columnName,\n oursValue,\n theirsValue,\n pick,\n isFocused,\n isLast,\n}: ColumnRowProps) {\n return (\n <Box flexDirection=\"column\" marginBottom={isLast ? 0 : 1}>\n <Box justifyContent=\"space-between\">\n <Box>\n <Text color=\"cyan\">{isFocused ? '▸ ' : ' '}</Text>\n <Text bold={isFocused}>{columnName}</Text>\n </Box>\n <Box>\n <Text dimColor={!isFocused}>pick: </Text>\n <Text bold={isFocused} color={pick === 'ours' ? 'blue' : 'magenta'}>\n {pick}\n </Text>\n {isFocused ? <Text color=\"green\"> ◀</Text> : null}\n </Box>\n </Box>\n\n <ValueLine label=\"ours: \" value={oursValue} isPicked={pick === 'ours'} />\n <ValueLine label=\"theirs:\" value={theirsValue} isPicked={pick === 'theirs'} />\n </Box>\n );\n}\n"],"mappings":";;;;;AAaA,SAAS,UAAU,EACjB,OACA,OACA,YAKC;AACD,QACE,oBAAC,iBACC,qBAAC;EAAK,OAAO,WAAW,UAAU;EAAW,UAAU,CAAC;;GACrD;GACA;GAAM;GAAE,YAAY,MAAM;;GACtB,GACH;;AAIV,SAAgB,UAAU,EACxB,YACA,WACA,aACA,MACA,WACA,UACiB;AACjB,QACE,qBAAC;EAAI,eAAc;EAAS,cAAc,SAAS,IAAI;;GACrD,qBAAC;IAAI,gBAAe;eAClB,qBAAC,kBACC,oBAAC;KAAK,OAAM;eAAQ,YAAY,OAAO;MAAY,EACnD,oBAAC;KAAK,MAAM;eAAY;MAAkB,IACtC,EACN,qBAAC;KACC,oBAAC;MAAK,UAAU,CAAC;gBAAW;OAAa;KACzC,oBAAC;MAAK,MAAM;MAAW,OAAO,SAAS,SAAS,SAAS;gBACtD;OACI;KACN,YAAY,oBAAC;MAAK,OAAM;gBAAQ;OAAS,GAAG;QACzC;KACF;GAEN,oBAAC;IAAU,OAAM;IAAU,OAAO;IAAW,UAAU,SAAS;KAAU;GAC1E,oBAAC;IAAU,OAAM;IAAU,OAAO;IAAa,UAAU,SAAS;KAAY;;GAC1E"}
@@ -0,0 +1,135 @@
1
+ import { diffTypeColor, formatDiffType, formatEntityId } from "./utils.js";
2
+ import { ColumnRow } from "./column-row.js";
3
+ import { Box, Text } from "ink";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+
6
+ //#region src/commands/pull-v4/merge-ui/conflict-view.tsx
7
+ function ConflictView({ conflict, resolution, changedColumns, focusedColumnIndex, conflictIndex, totalConflicts }) {
8
+ const entityId = formatEntityId(conflict.primaryKey);
9
+ const isNullOurs = conflict.ours === null;
10
+ const isNullTheirs = conflict.theirs === null;
11
+ function getPickForColumn(col) {
12
+ return resolution.columnOverrides[col] ?? resolution.rowDefaultPick;
13
+ }
14
+ return /* @__PURE__ */ jsx(Box, {
15
+ flexDirection: "column",
16
+ children: /* @__PURE__ */ jsxs(Box, {
17
+ borderStyle: "round",
18
+ borderColor: "cyan",
19
+ paddingX: 1,
20
+ flexDirection: "column",
21
+ children: [/* @__PURE__ */ jsxs(Box, {
22
+ marginBottom: 1,
23
+ children: [
24
+ /* @__PURE__ */ jsxs(Text, {
25
+ bold: true,
26
+ children: [
27
+ "Conflict ",
28
+ conflictIndex + 1,
29
+ "/",
30
+ totalConflicts,
31
+ ":",
32
+ " "
33
+ ]
34
+ }),
35
+ /* @__PURE__ */ jsx(Text, {
36
+ color: "cyan",
37
+ children: conflict.table
38
+ }),
39
+ /* @__PURE__ */ jsxs(Text, {
40
+ bold: true,
41
+ children: [
42
+ " \"",
43
+ entityId,
44
+ "\""
45
+ ]
46
+ }),
47
+ /* @__PURE__ */ jsx(Text, { children: " — local: " }),
48
+ /* @__PURE__ */ jsx(Text, {
49
+ color: diffTypeColor(conflict.ourDiffType),
50
+ children: formatDiffType(conflict.ourDiffType)
51
+ }),
52
+ /* @__PURE__ */ jsx(Text, { children: " | remote: " }),
53
+ /* @__PURE__ */ jsx(Text, {
54
+ color: diffTypeColor(conflict.theirDiffType),
55
+ children: formatDiffType(conflict.theirDiffType)
56
+ })
57
+ ]
58
+ }), isNullOurs || isNullTheirs ? /* @__PURE__ */ jsxs(Box, {
59
+ flexDirection: "column",
60
+ children: [/* @__PURE__ */ jsxs(Box, {
61
+ marginBottom: 1,
62
+ children: [
63
+ /* @__PURE__ */ jsx(Text, { children: " Pick: " }),
64
+ /* @__PURE__ */ jsxs(Text, {
65
+ bold: true,
66
+ color: "cyan",
67
+ children: [
68
+ "[",
69
+ resolution.rowDefaultPick,
70
+ "]"
71
+ ]
72
+ }),
73
+ /* @__PURE__ */ jsx(Text, {
74
+ dimColor: true,
75
+ children: " (← for ours, → for theirs)"
76
+ }),
77
+ /* @__PURE__ */ jsx(Text, {
78
+ color: "cyan",
79
+ children: " ◂"
80
+ })
81
+ ]
82
+ }), /* @__PURE__ */ jsxs(Box, {
83
+ flexDirection: "column",
84
+ marginLeft: 2,
85
+ children: [/* @__PURE__ */ jsxs(Text, {
86
+ color: resolution.rowDefaultPick === "ours" ? "green" : void 0,
87
+ dimColor: resolution.rowDefaultPick !== "ours",
88
+ children: ["ours: ", isNullOurs ? "Delete this row (keep local deletion)" : "Keep local version"]
89
+ }), /* @__PURE__ */ jsxs(Text, {
90
+ color: resolution.rowDefaultPick === "theirs" ? "green" : void 0,
91
+ dimColor: resolution.rowDefaultPick !== "theirs",
92
+ children: [
93
+ "theirs:",
94
+ " ",
95
+ isNullTheirs ? "Delete this row (keep remote deletion)" : "Keep remote version"
96
+ ]
97
+ })]
98
+ })]
99
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Box, {
100
+ marginBottom: 1,
101
+ children: [
102
+ /* @__PURE__ */ jsx(Text, { children: " Row default: " }),
103
+ /* @__PURE__ */ jsxs(Text, {
104
+ bold: true,
105
+ color: focusedColumnIndex === -1 ? "cyan" : void 0,
106
+ children: [
107
+ "[",
108
+ resolution.rowDefaultPick,
109
+ "]"
110
+ ]
111
+ }),
112
+ /* @__PURE__ */ jsx(Text, {
113
+ dimColor: true,
114
+ children: " (← for ours, → for theirs)"
115
+ }),
116
+ focusedColumnIndex === -1 && /* @__PURE__ */ jsx(Text, {
117
+ color: "cyan",
118
+ children: " ◂"
119
+ })
120
+ ]
121
+ }), changedColumns.map((col, i) => /* @__PURE__ */ jsx(ColumnRow, {
122
+ columnName: col,
123
+ oursValue: conflict.ours?.[col],
124
+ theirsValue: conflict.theirs?.[col],
125
+ pick: getPickForColumn(col),
126
+ isFocused: focusedColumnIndex === i,
127
+ isLast: i === changedColumns.length - 1
128
+ }, col))] })]
129
+ })
130
+ });
131
+ }
132
+
133
+ //#endregion
134
+ export { ConflictView };
135
+ //# sourceMappingURL=conflict-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-view.js","names":[],"sources":["../../../../src/commands/pull-v4/merge-ui/conflict-view.tsx"],"sourcesContent":["import type { ConflictItem } from '@inkeep/agents-core';\nimport { Box, Text } from 'ink';\nimport { ColumnRow } from './column-row';\nimport type { ConflictResolutionState, Side } from './types';\nimport { diffTypeColor, formatDiffType, formatEntityId } from './utils';\n\ninterface ConflictViewProps {\n conflict: ConflictItem;\n resolution: ConflictResolutionState;\n changedColumns: string[];\n focusedColumnIndex: number;\n conflictIndex: number;\n totalConflicts: number;\n}\n\nexport function ConflictView({\n conflict,\n resolution,\n changedColumns,\n focusedColumnIndex,\n conflictIndex,\n totalConflicts,\n}: ConflictViewProps) {\n const entityId = formatEntityId(conflict.primaryKey);\n const isNullOurs = conflict.ours === null;\n const isNullTheirs = conflict.theirs === null;\n\n function getPickForColumn(col: string): Side {\n return resolution.columnOverrides[col] ?? resolution.rowDefaultPick;\n }\n\n return (\n <Box flexDirection=\"column\">\n <Box borderStyle=\"round\" borderColor=\"cyan\" paddingX={1} flexDirection=\"column\">\n <Box marginBottom={1}>\n <Text bold>\n Conflict {conflictIndex + 1}/{totalConflicts}:{' '}\n </Text>\n <Text color=\"cyan\">{conflict.table}</Text>\n <Text bold> &quot;{entityId}&quot;</Text>\n <Text> — local: </Text>\n <Text color={diffTypeColor(conflict.ourDiffType)}>\n {formatDiffType(conflict.ourDiffType)}\n </Text>\n <Text> | remote: </Text>\n <Text color={diffTypeColor(conflict.theirDiffType)}>\n {formatDiffType(conflict.theirDiffType)}\n </Text>\n </Box>\n\n {isNullOurs || isNullTheirs ? (\n <Box flexDirection=\"column\">\n <Box marginBottom={1}>\n <Text> Pick: </Text>\n <Text bold color=\"cyan\">\n [{resolution.rowDefaultPick}]\n </Text>\n <Text dimColor> (← for ours, → for theirs)</Text>\n <Text color=\"cyan\"> ◂</Text>\n </Box>\n <Box flexDirection=\"column\" marginLeft={2}>\n <Text\n color={resolution.rowDefaultPick === 'ours' ? 'green' : undefined}\n dimColor={resolution.rowDefaultPick !== 'ours'}\n >\n ours: {isNullOurs ? 'Delete this row (keep local deletion)' : 'Keep local version'}\n </Text>\n <Text\n color={resolution.rowDefaultPick === 'theirs' ? 'green' : undefined}\n dimColor={resolution.rowDefaultPick !== 'theirs'}\n >\n theirs:{' '}\n {isNullTheirs ? 'Delete this row (keep remote deletion)' : 'Keep remote version'}\n </Text>\n </Box>\n </Box>\n ) : (\n <>\n <Box marginBottom={1}>\n <Text> Row default: </Text>\n <Text bold color={focusedColumnIndex === -1 ? 'cyan' : undefined}>\n [{resolution.rowDefaultPick}]\n </Text>\n <Text dimColor> (← for ours, → for theirs)</Text>\n {focusedColumnIndex === -1 && <Text color=\"cyan\"> ◂</Text>}\n </Box>\n\n {changedColumns.map((col, i) => (\n <ColumnRow\n key={col}\n columnName={col}\n oursValue={conflict.ours?.[col]}\n theirsValue={conflict.theirs?.[col]}\n pick={getPickForColumn(col)}\n isFocused={focusedColumnIndex === i}\n isLast={i === changedColumns.length - 1}\n />\n ))}\n </>\n )}\n </Box>\n </Box>\n );\n}\n"],"mappings":";;;;;;AAeA,SAAgB,aAAa,EAC3B,UACA,YACA,gBACA,oBACA,eACA,kBACoB;CACpB,MAAM,WAAW,eAAe,SAAS,WAAW;CACpD,MAAM,aAAa,SAAS,SAAS;CACrC,MAAM,eAAe,SAAS,WAAW;CAEzC,SAAS,iBAAiB,KAAmB;AAC3C,SAAO,WAAW,gBAAgB,QAAQ,WAAW;;AAGvD,QACE,oBAAC;EAAI,eAAc;YACjB,qBAAC;GAAI,aAAY;GAAQ,aAAY;GAAO,UAAU;GAAG,eAAc;cACrE,qBAAC;IAAI,cAAc;;KACjB,qBAAC;MAAK;;OAAK;OACC,gBAAgB;OAAE;OAAE;OAAe;OAAE;;OAC1C;KACP,oBAAC;MAAK,OAAM;gBAAQ,SAAS;OAAa;KAC1C,qBAAC;MAAK;;OAAK;OAAQ;OAAS;;OAAa;KACzC,oBAAC,kBAAK,eAAiB;KACvB,oBAAC;MAAK,OAAO,cAAc,SAAS,YAAY;gBAC7C,eAAe,SAAS,YAAY;OAChC;KACP,oBAAC,kBAAK,gBAAkB;KACxB,oBAAC;MAAK,OAAO,cAAc,SAAS,cAAc;gBAC/C,eAAe,SAAS,cAAc;OAClC;;KACH,EAEL,cAAc,eACb,qBAAC;IAAI,eAAc;eACjB,qBAAC;KAAI,cAAc;;MACjB,oBAAC,kBAAK,YAAc;MACpB,qBAAC;OAAK;OAAK,OAAM;;QAAO;QACpB,WAAW;QAAe;;QACvB;MACP,oBAAC;OAAK;iBAAS;QAAkC;MACjD,oBAAC;OAAK,OAAM;iBAAO;QAAS;;MACxB,EACN,qBAAC;KAAI,eAAc;KAAS,YAAY;gBACtC,qBAAC;MACC,OAAO,WAAW,mBAAmB,SAAS,UAAU;MACxD,UAAU,WAAW,mBAAmB;iBACzC,UACQ,aAAa,0CAA0C;OACzD,EACP,qBAAC;MACC,OAAO,WAAW,mBAAmB,WAAW,UAAU;MAC1D,UAAU,WAAW,mBAAmB;;OACzC;OACS;OACP,eAAe,2CAA2C;;OACtD;MACH;KACF,GAEN,4CACE,qBAAC;IAAI,cAAc;;KACjB,oBAAC,kBAAK,mBAAqB;KAC3B,qBAAC;MAAK;MAAK,OAAO,uBAAuB,KAAK,SAAS;;OAAW;OAC9D,WAAW;OAAe;;OACvB;KACP,oBAAC;MAAK;gBAAS;OAAkC;KAChD,uBAAuB,MAAM,oBAAC;MAAK,OAAM;gBAAO;OAAS;;KACtD,EAEL,eAAe,KAAK,KAAK,MACxB,oBAAC;IAEC,YAAY;IACZ,WAAW,SAAS,OAAO;IAC3B,aAAa,SAAS,SAAS;IAC/B,MAAM,iBAAiB,IAAI;IAC3B,WAAW,uBAAuB;IAClC,QAAQ,MAAM,eAAe,SAAS;MANjC,IAOL,CACF,IACD;IAED;GACF"}