forked from aws/agentcore-cli
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathBasePrimitive.ts
More file actions
165 lines (147 loc) · 6.2 KB
/
Copy pathBasePrimitive.ts
File metadata and controls
165 lines (147 loc) · 6.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { ConfigIO, findConfigRoot } from '../../lib';
import type { AgentCoreProjectSpec } from '../../schema';
import type { ResourceType } from '../commands/remove/types';
import { getErrorMessage } from '../errors';
import { SOURCE_CODE_NOTE } from './constants';
import type { AddResult, AddScreenComponent, RemovableResource, RemovalPreview, RemovalResult } from './types';
import type { Command } from '@commander-js/extra-typings';
import type { z } from 'zod';
/**
* Abstract base class for AgentCore CLI primitives.
*
* Each primitive (Agent, Memory, Credential, Gateway, GatewayTarget)
* extends this class and owns its add/remove logic entirely.
*
* The base provides shared helpers for reading/writing agentcore.json.
* All resource types (including gateways) now use agentcore.json.
*/
export abstract class BasePrimitive<
TAddOptions = Record<string, unknown>,
TRemovable extends RemovableResource = RemovableResource,
> {
/** Shared ConfigIO instance for agentcore.json operations. */
protected readonly configIO = new ConfigIO();
/** Resource kind identifier (e.g., 'agent', 'memory', 'credential', 'gateway', 'mcp-tool') */
abstract readonly kind: ResourceType;
/** Human-readable label (e.g., 'Agent', 'Memory', 'Credential') */
abstract readonly label: string;
/** Zod schema for validating the primitive's config */
abstract readonly primitiveSchema: z.ZodTypeAny;
/**
* Add a new resource of this type.
* Each primitive owns its implementation entirely.
*/
abstract add(options: TAddOptions): Promise<AddResult>;
/**
* Remove a resource by name.
*/
abstract remove(name: string): Promise<RemovalResult>;
/**
* Preview what will be removed.
*/
abstract previewRemove(name: string): Promise<RemovalPreview>;
/**
* Get list of resources available for removal.
*/
abstract getRemovable(): Promise<TRemovable[]>;
/**
* Register CLI commands for add/remove.
*/
abstract registerCommands(addCmd: Command, removeCmd: Command): void;
/**
* Return the TUI screen component for the add flow, or null if no TUI.
*/
abstract addScreen(): AddScreenComponent;
// ═══════════════════════════════════════════════════════════════════
// Shared helpers for primitives that work with agentcore.json
// ═══════════════════════════════════════════════════════════════════
/**
* Read the project spec from agentcore.json.
*/
protected async readProjectSpec(configIO?: ConfigIO): Promise<AgentCoreProjectSpec> {
return (configIO ?? this.configIO).readProjectSpec();
}
/**
* Write the project spec to agentcore.json.
*/
protected async writeProjectSpec(spec: AgentCoreProjectSpec, configIO?: ConfigIO): Promise<void> {
await (configIO ?? this.configIO).writeProjectSpec(spec);
}
/**
* Check for duplicate names in an array.
* Throws if a resource with the given name already exists.
*/
protected checkDuplicate(items: { name: string }[], name: string, label?: string): void {
if (items.some(item => item.name === name)) {
throw new Error(`${label ?? this.label} "${name}" already exists.`);
}
}
/** Indefinite article for the resource kind ('a' or 'an'). Override for 'an'. */
protected readonly article: string = 'a';
/**
* Register the standard remove subcommand for this primitive.
* Handles CLI mode (--name/--yes/--json) and TUI fallback identically.
*/
protected registerRemoveSubcommand(removeCmd: Command): void {
removeCmd
.command(this.kind)
.description(`Remove ${this.article} ${this.label.toLowerCase()} from the project`)
.option('--name <name>', 'Name of resource to remove [non-interactive]')
.option('-y, --yes', 'Skip confirmation prompt [non-interactive]')
.option('--json', 'Output as JSON [non-interactive]')
.action(async (cliOptions: { name?: string; yes?: boolean; json?: boolean }) => {
try {
if (!findConfigRoot()) {
console.error('No agentcore project found. Run `agentcore create` first.');
process.exit(1);
}
// Any flag triggers non-interactive CLI mode
if (cliOptions.name || cliOptions.yes || cliOptions.json) {
if (!cliOptions.name) {
console.log(JSON.stringify({ success: false, error: '--name is required' }));
process.exit(1);
}
const result = await this.remove(cliOptions.name);
console.log(
JSON.stringify({
success: result.success,
resourceType: this.kind,
resourceName: cliOptions.name,
message: result.success ? `Removed ${this.label.toLowerCase()} '${cliOptions.name}'` : undefined,
note: result.success ? SOURCE_CODE_NOTE : undefined,
error: !result.success ? result.error : undefined,
})
);
process.exit(result.success ? 0 : 1);
} else {
// TUI fallback — dynamic imports to avoid pulling ink (async) into registry
const [{ render }, { default: React }, { RemoveFlow }] = await Promise.all([
import('ink'),
import('react'),
import('../tui/screens/remove'),
]);
const { clear, unmount } = render(
React.createElement(RemoveFlow, {
isInteractive: false,
force: cliOptions.yes,
initialResourceType: this.kind,
initialResourceName: cliOptions.name,
onExit: () => {
clear();
unmount();
process.exit(0);
},
})
);
}
} catch (error) {
if (cliOptions.json) {
console.log(JSON.stringify({ success: false, error: getErrorMessage(error) }));
} else {
console.error(`Error: ${getErrorMessage(error)}`);
}
process.exit(1);
}
});
}
}