@agentic-kit/db-tools 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +138 -0
- package/app-workspace.d.ts +16 -0
- package/app-workspace.js +145 -0
- package/context.d.ts +62 -0
- package/context.js +282 -0
- package/db-probe.d.ts +30 -0
- package/db-probe.js +50 -0
- package/esm/app-workspace.d.ts +16 -0
- package/esm/app-workspace.js +134 -0
- package/esm/context.d.ts +62 -0
- package/esm/context.js +270 -0
- package/esm/db-probe.d.ts +30 -0
- package/esm/db-probe.js +47 -0
- package/esm/host.d.ts +115 -0
- package/esm/host.js +23 -0
- package/esm/index.d.ts +20 -0
- package/esm/index.js +52 -0
- package/esm/policy/add-policies-to-table.d.ts +26 -0
- package/esm/policy/add-policies-to-table.js +66 -0
- package/esm/policy/provision-helpers.d.ts +38 -0
- package/esm/policy/provision-helpers.js +50 -0
- package/esm/provision-database/credential.d.ts +14 -0
- package/esm/provision-database/credential.js +19 -0
- package/esm/provision-database/env-file.d.ts +37 -0
- package/esm/provision-database/env-file.js +85 -0
- package/esm/provision-database/manifest.d.ts +10 -0
- package/esm/provision-database/manifest.js +48 -0
- package/esm/provision-database/pg-fixups.d.ts +16 -0
- package/esm/provision-database/pg-fixups.js +132 -0
- package/esm/provision-database/preset-match.d.ts +13 -0
- package/esm/provision-database/preset-match.js +66 -0
- package/esm/provision-database/presets.d.ts +4 -0
- package/esm/provision-database/presets.js +13 -0
- package/esm/provision-database/request-database.d.ts +61 -0
- package/esm/provision-database/request-database.js +130 -0
- package/esm/provision-database/resolve.d.ts +27 -0
- package/esm/provision-database/resolve.js +49 -0
- package/esm/records/meta.d.ts +41 -0
- package/esm/records/meta.js +167 -0
- package/esm/run-codegen/barrels.d.ts +3 -0
- package/esm/run-codegen/barrels.js +48 -0
- package/esm/run-codegen/endpoints.d.ts +8 -0
- package/esm/run-codegen/endpoints.js +46 -0
- package/esm/schema-resolve.d.ts +23 -0
- package/esm/schema-resolve.js +69 -0
- package/esm/tool-schema.d.ts +3 -0
- package/esm/tool-schema.js +10 -0
- package/esm/tools/add-policies.d.ts +24 -0
- package/esm/tools/add-policies.js +94 -0
- package/esm/tools/add-records.d.ts +18 -0
- package/esm/tools/add-records.js +122 -0
- package/esm/tools/add-relation-schema.d.ts +29 -0
- package/esm/tools/add-relation-schema.js +56 -0
- package/esm/tools/add-relation.d.ts +10 -0
- package/esm/tools/add-relation.js +91 -0
- package/esm/tools/create-api-key.d.ts +25 -0
- package/esm/tools/create-api-key.js +214 -0
- package/esm/tools/describe-schema.d.ts +19 -0
- package/esm/tools/describe-schema.js +130 -0
- package/esm/tools/manage-entity-types.d.ts +40 -0
- package/esm/tools/manage-entity-types.js +201 -0
- package/esm/tools/mutations.d.ts +35 -0
- package/esm/tools/mutations.js +230 -0
- package/esm/tools/provision-blueprint.d.ts +21 -0
- package/esm/tools/provision-blueprint.js +109 -0
- package/esm/tools/provision-database.d.ts +18 -0
- package/esm/tools/provision-database.js +260 -0
- package/esm/tools/run-codegen.d.ts +12 -0
- package/esm/tools/run-codegen.js +137 -0
- package/esm/tools/templates.d.ts +71 -0
- package/esm/tools/templates.js +331 -0
- package/host.d.ts +115 -0
- package/host.js +28 -0
- package/index.d.ts +20 -0
- package/index.js +76 -0
- package/package.json +49 -0
- package/policy/add-policies-to-table.d.ts +26 -0
- package/policy/add-policies-to-table.js +71 -0
- package/policy/provision-helpers.d.ts +38 -0
- package/policy/provision-helpers.js +58 -0
- package/provision-database/credential.d.ts +14 -0
- package/provision-database/credential.js +22 -0
- package/provision-database/env-file.d.ts +37 -0
- package/provision-database/env-file.js +91 -0
- package/provision-database/manifest.d.ts +10 -0
- package/provision-database/manifest.js +56 -0
- package/provision-database/pg-fixups.d.ts +16 -0
- package/provision-database/pg-fixups.js +170 -0
- package/provision-database/preset-match.d.ts +13 -0
- package/provision-database/preset-match.js +70 -0
- package/provision-database/presets.d.ts +4 -0
- package/provision-database/presets.js +17 -0
- package/provision-database/request-database.d.ts +61 -0
- package/provision-database/request-database.js +134 -0
- package/provision-database/resolve.d.ts +27 -0
- package/provision-database/resolve.js +53 -0
- package/records/meta.d.ts +41 -0
- package/records/meta.js +172 -0
- package/run-codegen/barrels.d.ts +3 -0
- package/run-codegen/barrels.js +54 -0
- package/run-codegen/endpoints.d.ts +8 -0
- package/run-codegen/endpoints.js +51 -0
- package/schema-resolve.d.ts +23 -0
- package/schema-resolve.js +75 -0
- package/tool-schema.d.ts +3 -0
- package/tool-schema.js +13 -0
- package/tools/add-policies.d.ts +24 -0
- package/tools/add-policies.js +97 -0
- package/tools/add-records.d.ts +18 -0
- package/tools/add-records.js +125 -0
- package/tools/add-relation-schema.d.ts +29 -0
- package/tools/add-relation-schema.js +60 -0
- package/tools/add-relation.d.ts +10 -0
- package/tools/add-relation.js +94 -0
- package/tools/create-api-key.d.ts +25 -0
- package/tools/create-api-key.js +220 -0
- package/tools/describe-schema.d.ts +19 -0
- package/tools/describe-schema.js +133 -0
- package/tools/manage-entity-types.d.ts +40 -0
- package/tools/manage-entity-types.js +206 -0
- package/tools/mutations.d.ts +35 -0
- package/tools/mutations.js +233 -0
- package/tools/provision-blueprint.d.ts +21 -0
- package/tools/provision-blueprint.js +112 -0
- package/tools/provision-database.d.ts +18 -0
- package/tools/provision-database.js +266 -0
- package/tools/run-codegen.d.ts +12 -0
- package/tools/run-codegen.js +141 -0
- package/tools/templates.d.ts +71 -0
- package/tools/templates.js +335 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deleteFieldTool = exports.updateFieldTool = exports.createFieldTool = exports.deleteTableTool = void 0;
|
|
4
|
+
const harness_1 = require("@agentic-kit/harness");
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const context_1 = require("../context");
|
|
7
|
+
const schema_resolve_1 = require("../schema-resolve");
|
|
8
|
+
function ok(message) {
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: 'text', text: message }],
|
|
11
|
+
details: { success: true, message },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function fail(message) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: 'text', text: message }],
|
|
17
|
+
details: { success: false, message },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// ── delete_table ──────────────────────────────────────────────────────────
|
|
21
|
+
const DeleteTableZod = zod_1.z.object({
|
|
22
|
+
table_name: zod_1.z.string().describe('Name of the table to delete (snake_case)'),
|
|
23
|
+
});
|
|
24
|
+
exports.deleteTableTool = {
|
|
25
|
+
name: 'delete_table',
|
|
26
|
+
label: 'Delete table',
|
|
27
|
+
description: 'Permanently delete a table and its data from the project database.',
|
|
28
|
+
promptSnippet: 'delete_table: drop an existing table by name. Destructive — gated.',
|
|
29
|
+
parameters: DeleteTableZod,
|
|
30
|
+
async execute(params, ctx) {
|
|
31
|
+
const resolved = await (0, context_1.resolveProjectContext)(ctx.cwd);
|
|
32
|
+
if (!resolved.context)
|
|
33
|
+
return fail(resolved.reason);
|
|
34
|
+
try {
|
|
35
|
+
const schema = await (0, schema_resolve_1.resolveSchema)(resolved.context);
|
|
36
|
+
const table = (0, schema_resolve_1.resolveTable)(schema, params.table_name);
|
|
37
|
+
await resolved.context.api.table
|
|
38
|
+
.delete({ where: { id: table.id }, select: { id: true } })
|
|
39
|
+
.unwrap();
|
|
40
|
+
return ok(`Deleted table ${params.table_name}`);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return fail(err instanceof Error ? err.message : 'Failed to delete table');
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
// ── create_field ──────────────────────────────────────────────────────────
|
|
48
|
+
const CreateFieldZod = zod_1.z.object({
|
|
49
|
+
table_name: zod_1.z.string().describe('Name of the table to add the field to'),
|
|
50
|
+
field_name: zod_1.z.string().describe('Field name in snake_case'),
|
|
51
|
+
type: zod_1.z
|
|
52
|
+
.string()
|
|
53
|
+
.describe('PostgreSQL type: text, integer, smallint, numeric, boolean, date, timestamp, timestamptz, time, json, jsonb, inet, email, url, image, upload'),
|
|
54
|
+
is_required: zod_1.z.boolean().describe('Whether the field is NOT NULL. Defaults to false.').optional(),
|
|
55
|
+
default_value: zod_1.z
|
|
56
|
+
.union([zod_1.z.string(), zod_1.z.null()])
|
|
57
|
+
.describe('SQL default expression, e.g. "now()", "\'draft\'"')
|
|
58
|
+
.optional(),
|
|
59
|
+
is_unique: zod_1.z
|
|
60
|
+
.boolean()
|
|
61
|
+
.describe('Whether the field has a unique constraint. Defaults to false.')
|
|
62
|
+
.optional(),
|
|
63
|
+
});
|
|
64
|
+
exports.createFieldTool = {
|
|
65
|
+
name: 'create_field',
|
|
66
|
+
label: 'Create field',
|
|
67
|
+
description: 'Add a new field (column) to an existing table in the project database.',
|
|
68
|
+
promptSnippet: 'create_field: add a column to an existing table. Gated.',
|
|
69
|
+
parameters: CreateFieldZod,
|
|
70
|
+
async execute(params, ctx) {
|
|
71
|
+
const resolved = await (0, context_1.resolveProjectContext)(ctx.cwd);
|
|
72
|
+
if (!resolved.context)
|
|
73
|
+
return fail(resolved.reason);
|
|
74
|
+
const { api, databaseId } = resolved.context;
|
|
75
|
+
try {
|
|
76
|
+
const schema = await (0, schema_resolve_1.resolveSchema)(resolved.context);
|
|
77
|
+
const table = (0, schema_resolve_1.resolveTable)(schema, params.table_name);
|
|
78
|
+
const result = await api.field
|
|
79
|
+
.create({
|
|
80
|
+
data: {
|
|
81
|
+
name: params.field_name,
|
|
82
|
+
type: (0, harness_1.toFieldType)(params.type),
|
|
83
|
+
tableId: table.id,
|
|
84
|
+
databaseId,
|
|
85
|
+
isRequired: params.is_required ?? false,
|
|
86
|
+
defaultValue: (0, harness_1.toFieldDefault)(params.default_value),
|
|
87
|
+
},
|
|
88
|
+
select: { id: true, name: true, type: true },
|
|
89
|
+
})
|
|
90
|
+
.unwrap();
|
|
91
|
+
const createdId = result.createField?.field?.id;
|
|
92
|
+
if (params.is_unique && createdId) {
|
|
93
|
+
try {
|
|
94
|
+
await api.uniqueConstraint
|
|
95
|
+
.create({
|
|
96
|
+
data: {
|
|
97
|
+
tableId: table.id,
|
|
98
|
+
databaseId,
|
|
99
|
+
fieldIds: [createdId],
|
|
100
|
+
name: `${table.name}_${params.field_name}_key`,
|
|
101
|
+
type: 'u',
|
|
102
|
+
},
|
|
103
|
+
select: { id: true },
|
|
104
|
+
})
|
|
105
|
+
.unwrap();
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.warn('Failed to create unique constraint:', err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return ok(`Added field ${params.table_name}.${params.field_name}`);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
return fail(err instanceof Error ? err.message : 'Failed to create field');
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
// ── update_field ──────────────────────────────────────────────────────────
|
|
119
|
+
const UpdateFieldZod = zod_1.z.object({
|
|
120
|
+
table_name: zod_1.z.string().describe('Name of the table containing the field'),
|
|
121
|
+
field_name: zod_1.z.string().describe('Current field name'),
|
|
122
|
+
new_name: zod_1.z.string().describe('New field name in snake_case (if renaming)').optional(),
|
|
123
|
+
new_type: zod_1.z.string().describe('New PostgreSQL type (if changing type)').optional(),
|
|
124
|
+
is_required: zod_1.z.boolean().describe('Set NOT NULL constraint').optional(),
|
|
125
|
+
default_value: zod_1.z
|
|
126
|
+
.union([zod_1.z.string(), zod_1.z.null()])
|
|
127
|
+
.describe('New SQL default expression, or null to remove')
|
|
128
|
+
.optional(),
|
|
129
|
+
is_unique: zod_1.z.boolean().describe('Set or remove unique constraint').optional(),
|
|
130
|
+
});
|
|
131
|
+
exports.updateFieldTool = {
|
|
132
|
+
name: 'update_field',
|
|
133
|
+
label: 'Update field',
|
|
134
|
+
description: 'Modify an existing field: rename, change type, toggle required/unique, or set default.',
|
|
135
|
+
promptSnippet: 'update_field: rename/retype/toggle an existing column. Gated.',
|
|
136
|
+
parameters: UpdateFieldZod,
|
|
137
|
+
async execute(params, ctx) {
|
|
138
|
+
const resolved = await (0, context_1.resolveProjectContext)(ctx.cwd);
|
|
139
|
+
if (!resolved.context)
|
|
140
|
+
return fail(resolved.reason);
|
|
141
|
+
const { api, databaseId } = resolved.context;
|
|
142
|
+
try {
|
|
143
|
+
const schema = await (0, schema_resolve_1.resolveSchema)(resolved.context);
|
|
144
|
+
const table = (0, schema_resolve_1.resolveTable)(schema, params.table_name);
|
|
145
|
+
const field = (0, schema_resolve_1.resolveField)(table, params.field_name);
|
|
146
|
+
const patch = {};
|
|
147
|
+
if (params.new_name)
|
|
148
|
+
patch.name = params.new_name;
|
|
149
|
+
if (params.new_type)
|
|
150
|
+
patch.type = (0, harness_1.toFieldType)(params.new_type);
|
|
151
|
+
if (params.is_required !== undefined)
|
|
152
|
+
patch.isRequired = params.is_required;
|
|
153
|
+
if (params.default_value !== undefined) {
|
|
154
|
+
patch.defaultValue =
|
|
155
|
+
params.default_value === null ? null : ((0, harness_1.toFieldDefault)(params.default_value) ?? null);
|
|
156
|
+
}
|
|
157
|
+
if (Object.keys(patch).length > 0) {
|
|
158
|
+
await api.field
|
|
159
|
+
.update({
|
|
160
|
+
where: { id: field.id },
|
|
161
|
+
data: patch,
|
|
162
|
+
select: { id: true, name: true, type: true },
|
|
163
|
+
})
|
|
164
|
+
.unwrap();
|
|
165
|
+
}
|
|
166
|
+
if (params.is_unique !== undefined) {
|
|
167
|
+
const existingUniqueId = (0, schema_resolve_1.findUniqueConstraintId)(table, field.id);
|
|
168
|
+
if (params.is_unique && !existingUniqueId) {
|
|
169
|
+
try {
|
|
170
|
+
await api.uniqueConstraint
|
|
171
|
+
.create({
|
|
172
|
+
data: {
|
|
173
|
+
tableId: table.id,
|
|
174
|
+
databaseId,
|
|
175
|
+
fieldIds: [field.id],
|
|
176
|
+
name: `${table.name}_${params.new_name ?? field.name}_key`,
|
|
177
|
+
type: 'u',
|
|
178
|
+
},
|
|
179
|
+
select: { id: true },
|
|
180
|
+
})
|
|
181
|
+
.unwrap();
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
console.warn('Failed to create unique constraint:', err);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (!params.is_unique && existingUniqueId) {
|
|
188
|
+
try {
|
|
189
|
+
await api.uniqueConstraint
|
|
190
|
+
.delete({ where: { id: existingUniqueId }, select: { id: true } })
|
|
191
|
+
.unwrap();
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
console.warn('Failed to delete unique constraint:', err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return ok(`Updated field ${params.table_name}.${params.field_name}`);
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
return fail(err instanceof Error ? err.message : 'Failed to update field');
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
// ── delete_field ──────────────────────────────────────────────────────────
|
|
206
|
+
const DeleteFieldZod = zod_1.z.object({
|
|
207
|
+
table_name: zod_1.z.string().describe('Name of the table containing the field'),
|
|
208
|
+
field_name: zod_1.z.string().describe('Name of the field to delete'),
|
|
209
|
+
});
|
|
210
|
+
exports.deleteFieldTool = {
|
|
211
|
+
name: 'delete_field',
|
|
212
|
+
label: 'Delete field',
|
|
213
|
+
description: 'Permanently delete a field (column) from an existing table.',
|
|
214
|
+
promptSnippet: 'delete_field: drop a column from a table by name. Destructive — gated.',
|
|
215
|
+
parameters: DeleteFieldZod,
|
|
216
|
+
async execute(params, ctx) {
|
|
217
|
+
const resolved = await (0, context_1.resolveProjectContext)(ctx.cwd);
|
|
218
|
+
if (!resolved.context)
|
|
219
|
+
return fail(resolved.reason);
|
|
220
|
+
try {
|
|
221
|
+
const schema = await (0, schema_resolve_1.resolveSchema)(resolved.context);
|
|
222
|
+
const table = (0, schema_resolve_1.resolveTable)(schema, params.table_name);
|
|
223
|
+
const field = (0, schema_resolve_1.resolveField)(table, params.field_name);
|
|
224
|
+
await resolved.context.api.field
|
|
225
|
+
.delete({ where: { id: field.id }, select: { id: true } })
|
|
226
|
+
.unwrap();
|
|
227
|
+
return ok(`Deleted field ${params.table_name}.${params.field_name}`);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
return fail(err instanceof Error ? err.message : 'Failed to delete field');
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BlueprintZod, type HarnessTool } from '@agentic-kit/harness';
|
|
2
|
+
type ProvisionedField = {
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
isRequired: boolean;
|
|
6
|
+
defaultValue: string | null;
|
|
7
|
+
};
|
|
8
|
+
type ProvisionedTable = {
|
|
9
|
+
name: string;
|
|
10
|
+
fields: ProvisionedField[];
|
|
11
|
+
policies: string[];
|
|
12
|
+
relationCount: number;
|
|
13
|
+
};
|
|
14
|
+
export type ProvisionBlueprintDetails = {
|
|
15
|
+
created: number;
|
|
16
|
+
total: number;
|
|
17
|
+
tables: ProvisionedTable[];
|
|
18
|
+
error: string | null;
|
|
19
|
+
};
|
|
20
|
+
export declare const provisionBlueprintTool: HarnessTool<typeof BlueprintZod, ProvisionBlueprintDetails>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.provisionBlueprintTool = void 0;
|
|
4
|
+
const harness_1 = require("@agentic-kit/harness");
|
|
5
|
+
const context_1 = require("../context");
|
|
6
|
+
function formatText(name, details) {
|
|
7
|
+
if (details.error)
|
|
8
|
+
return `Failed to provision "${name}": ${details.error}`;
|
|
9
|
+
const lines = [`Created ${details.created} table${details.created === 1 ? '' : 's'} from "${name}":`];
|
|
10
|
+
for (const table of details.tables) {
|
|
11
|
+
const parts = [`${table.fields.length} field${table.fields.length === 1 ? '' : 's'}`];
|
|
12
|
+
if (table.relationCount > 0) {
|
|
13
|
+
parts.push(`${table.relationCount} relation${table.relationCount === 1 ? '' : 's'}`);
|
|
14
|
+
}
|
|
15
|
+
if (table.policies.length > 0) {
|
|
16
|
+
parts.push(`policies: ${table.policies.map((policy) => policy.replace(/^Authz/, '')).join(', ')}`);
|
|
17
|
+
}
|
|
18
|
+
lines.push(` - ${table.name} (${parts.join(', ')})`);
|
|
19
|
+
}
|
|
20
|
+
return lines.join('\n');
|
|
21
|
+
}
|
|
22
|
+
exports.provisionBlueprintTool = {
|
|
23
|
+
name: 'provision_blueprint',
|
|
24
|
+
label: 'Provision blueprint',
|
|
25
|
+
description: 'Create one or more related tables (with fields, RLS policies, and relations) in the project database from a blueprint definition. Always put related tables in a SINGLE call so relations are created together.',
|
|
26
|
+
promptSnippet: 'provision_blueprint: create new tables from scratch in a single call (fields + policies + relations). Call describe_schema first.',
|
|
27
|
+
parameters: harness_1.BlueprintZod,
|
|
28
|
+
async execute(params, ctx) {
|
|
29
|
+
const empty = { created: 0, total: 0, tables: [], error: null };
|
|
30
|
+
const resolved = await (0, context_1.resolveProjectContext)(ctx.cwd);
|
|
31
|
+
if (!resolved.context) {
|
|
32
|
+
return { content: [{ type: 'text', text: resolved.reason }], details: empty };
|
|
33
|
+
}
|
|
34
|
+
const { modules, databaseId, schemaId, ownerId } = resolved.context;
|
|
35
|
+
if (!ownerId) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: 'text', text: 'Not authenticated (missing OWNER_ID in .env).' }],
|
|
38
|
+
details: empty,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const definition = (0, harness_1.expandBlueprintDefaults)(params.definition);
|
|
42
|
+
const uniqueName = `${params.name}_${Date.now()}`;
|
|
43
|
+
try {
|
|
44
|
+
const createResult = await modules.blueprint
|
|
45
|
+
.create({
|
|
46
|
+
data: {
|
|
47
|
+
ownerId,
|
|
48
|
+
databaseId,
|
|
49
|
+
name: uniqueName,
|
|
50
|
+
displayName: params.name,
|
|
51
|
+
description: params.description,
|
|
52
|
+
definition,
|
|
53
|
+
},
|
|
54
|
+
select: { id: true, name: true },
|
|
55
|
+
})
|
|
56
|
+
.unwrap();
|
|
57
|
+
const blueprintId = createResult.createBlueprint?.blueprint?.id;
|
|
58
|
+
if (!blueprintId)
|
|
59
|
+
throw new Error('Failed to create blueprint');
|
|
60
|
+
const constructResult = await modules.mutation
|
|
61
|
+
.constructBlueprint({ input: { blueprintId, schemaId } }, { select: { result: true } })
|
|
62
|
+
.unwrap();
|
|
63
|
+
const result = constructResult.constructBlueprint?.result;
|
|
64
|
+
if (!result) {
|
|
65
|
+
const constructions = await modules.blueprintConstruction
|
|
66
|
+
.findMany({
|
|
67
|
+
where: { blueprintId: { equalTo: blueprintId } },
|
|
68
|
+
orderBy: ['ID_DESC'],
|
|
69
|
+
first: 1,
|
|
70
|
+
select: { status: true, errorDetails: true },
|
|
71
|
+
})
|
|
72
|
+
.unwrap();
|
|
73
|
+
const error = constructions?.blueprintConstructions?.nodes?.[0]?.errorDetails ?? 'Unknown error';
|
|
74
|
+
const details = {
|
|
75
|
+
created: 0,
|
|
76
|
+
total: params.definition.tables.length,
|
|
77
|
+
tables: [],
|
|
78
|
+
error: typeof error === 'string' ? error : JSON.stringify(error),
|
|
79
|
+
};
|
|
80
|
+
return { content: [{ type: 'text', text: formatText(params.name, details) }], details };
|
|
81
|
+
}
|
|
82
|
+
const relations = params.definition.relations ?? [];
|
|
83
|
+
const details = {
|
|
84
|
+
created: params.definition.tables.length,
|
|
85
|
+
total: params.definition.tables.length,
|
|
86
|
+
tables: params.definition.tables.map((t) => ({
|
|
87
|
+
name: t.table_name,
|
|
88
|
+
fields: (t.fields ?? []).map((f) => ({
|
|
89
|
+
name: f.name,
|
|
90
|
+
type: f.type,
|
|
91
|
+
isRequired: f.is_required ?? false,
|
|
92
|
+
defaultValue: f.default ?? null,
|
|
93
|
+
})),
|
|
94
|
+
policies: (0, harness_1.filterInternalPolicies)(t.policies.map((p) => p.$type)),
|
|
95
|
+
relationCount: relations.filter((r) => r.source_table === t.table_name).length,
|
|
96
|
+
})),
|
|
97
|
+
error: null,
|
|
98
|
+
};
|
|
99
|
+
return { content: [{ type: 'text', text: formatText(params.name, details) }], details };
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
const message = err instanceof Error ? err.message : 'Failed to provision blueprint';
|
|
103
|
+
const details = {
|
|
104
|
+
created: 0,
|
|
105
|
+
total: params.definition.tables.length,
|
|
106
|
+
tables: [],
|
|
107
|
+
error: message,
|
|
108
|
+
};
|
|
109
|
+
return { content: [{ type: 'text', text: formatText(params.name, details) }], details };
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HarnessTool } from '@agentic-kit/harness';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
declare const ProvisionDatabaseZod: z.ZodObject<{
|
|
4
|
+
database_name: z.ZodString;
|
|
5
|
+
reprovision: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export type ProvisionDatabaseDetails = {
|
|
8
|
+
success: boolean;
|
|
9
|
+
message: string;
|
|
10
|
+
databaseId?: string;
|
|
11
|
+
databaseName?: string;
|
|
12
|
+
ownerId?: string;
|
|
13
|
+
fixupNote?: string;
|
|
14
|
+
/** True when an existing live binding was kept — nothing changed. */
|
|
15
|
+
skipped?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare const provisionDatabaseTool: HarnessTool<typeof ProvisionDatabaseZod, ProvisionDatabaseDetails>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.provisionDatabaseTool = void 0;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const promises_2 = require("node:fs/promises");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const zod_1 = require("zod");
|
|
11
|
+
const app_workspace_1 = require("../app-workspace");
|
|
12
|
+
const db_probe_1 = require("../db-probe");
|
|
13
|
+
const host_1 = require("../host");
|
|
14
|
+
const credential_1 = require("../provision-database/credential");
|
|
15
|
+
const env_file_1 = require("../provision-database/env-file");
|
|
16
|
+
const manifest_1 = require("../provision-database/manifest");
|
|
17
|
+
const pg_fixups_1 = require("../provision-database/pg-fixups");
|
|
18
|
+
const preset_match_1 = require("../provision-database/preset-match");
|
|
19
|
+
const request_database_1 = require("../provision-database/request-database");
|
|
20
|
+
const resolve_1 = require("../provision-database/resolve");
|
|
21
|
+
const DEFAULT_API_ENDPOINT = 'http://api.localhost:3000/graphql';
|
|
22
|
+
const DEFAULT_MODULES_ENDPOINT = 'http://modules.localhost:3000/graphql';
|
|
23
|
+
// The per-DB endpoints are subdomain.<domain>; the provisioning domain is the api
|
|
24
|
+
// endpoint's host minus its first label (api.localhost -> localhost,
|
|
25
|
+
// api.launchql.dev -> launchql.dev), so dev and devnet both provision correctly.
|
|
26
|
+
function provisionDomain(apiEndpoint) {
|
|
27
|
+
try {
|
|
28
|
+
const { hostname } = new URL(apiEndpoint);
|
|
29
|
+
// A bare IP host has no domain hierarchy to strip: dropping its first label
|
|
30
|
+
// ('192.168.1.10' -> '168.1.10') would yield a garbage domain. Use it verbatim.
|
|
31
|
+
if (hostname.includes(':') || /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname))
|
|
32
|
+
return hostname;
|
|
33
|
+
const parts = hostname.split('.');
|
|
34
|
+
return parts.length > 1 ? parts.slice(1).join('.') : parts.join('.');
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return 'localhost';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const ProvisionDatabaseZod = zod_1.z.object({
|
|
41
|
+
database_name: zod_1.z
|
|
42
|
+
.string()
|
|
43
|
+
.describe('Name for the app database (lowercase, alphanumeric + underscores). Doubles as the deterministic provisioning subdomain (api-<name>/auth-<name>/app-<name>.localhost), so keep it short and URL-safe.'),
|
|
44
|
+
reprovision: zod_1.z
|
|
45
|
+
.boolean()
|
|
46
|
+
.describe('Mint a fresh database even though the project already carries a binding — use when the bound database no longer exists on this backend (refreshed backend, deleted database), when it belongs to a different account than the one signed in, or when the user asks for a clean rebuild. The old .env keys are archived as comments and the old database is never deleted server-side. The schema must be rebuilt afterwards (provision_blueprint + run_codegen); records do not carry over.')
|
|
47
|
+
.optional(),
|
|
48
|
+
});
|
|
49
|
+
function fail(message) {
|
|
50
|
+
return { content: [{ type: 'text', text: message }], details: { success: false, message } };
|
|
51
|
+
}
|
|
52
|
+
// Minimal .env reader for the idempotency check (full parsing lives in context.ts).
|
|
53
|
+
function parseEnvKeys(source) {
|
|
54
|
+
const env = {};
|
|
55
|
+
for (const rawLine of source.split('\n')) {
|
|
56
|
+
const line = rawLine.trim();
|
|
57
|
+
if (!line || line.startsWith('#'))
|
|
58
|
+
continue;
|
|
59
|
+
const eq = line.indexOf('=');
|
|
60
|
+
if (eq === -1)
|
|
61
|
+
continue;
|
|
62
|
+
env[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
|
|
63
|
+
}
|
|
64
|
+
return env;
|
|
65
|
+
}
|
|
66
|
+
exports.provisionDatabaseTool = {
|
|
67
|
+
name: 'provision_database',
|
|
68
|
+
label: 'Provision database',
|
|
69
|
+
description: 'Bootstrap a new Constructive database for the project under your account: provision the standard module set, enable membership defaults, and write credentials (DATABASE_ID, ACCESS_TOKEN, etc.) to the project .env. Run this ONCE before any schema/record tools. If the project is already bound to a live database under the signed-in account it skips; if the bound database no longer exists on this backend (refreshed/deleted) or belongs to a different account, pass reprovision: true to mint a fresh one (old keys archived in .env, old database kept; rebuild the schema afterwards).',
|
|
70
|
+
promptSnippet: 'provision_database: one-time bootstrap of the project database (owner + modules + .env). Run before describe_schema/provision_blueprint. Skips when the existing binding is live under the signed-in account; reprovision: true replaces a dead or foreign-account binding (archives old keys, never deletes the old db). Gated.',
|
|
71
|
+
parameters: ProvisionDatabaseZod,
|
|
72
|
+
async execute(params, ctx) {
|
|
73
|
+
const databaseName = params.database_name.trim();
|
|
74
|
+
if (!databaseName)
|
|
75
|
+
return fail('database_name is required.');
|
|
76
|
+
const envPath = node_path_1.default.join(ctx.cwd, '.env');
|
|
77
|
+
let existing = '';
|
|
78
|
+
try {
|
|
79
|
+
existing = await (0, promises_2.readFile)(envPath, 'utf8');
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
/* no existing .env — fine */
|
|
83
|
+
}
|
|
84
|
+
// Unify endpoints onto the app's backend-config store (environment-aware:
|
|
85
|
+
// *.localhost:3000 in dev, *.launchql.dev packaged). A process.env override
|
|
86
|
+
// still wins for ad-hoc dev; the hardcoded default is a last resort if the
|
|
87
|
+
// store isn't ready.
|
|
88
|
+
const host = (0, host_1.getHost)();
|
|
89
|
+
const backend = host.backendConfig();
|
|
90
|
+
const apiEndpoint = process.env.API_ENDPOINT || backend?.apiEndpoint || DEFAULT_API_ENDPOINT;
|
|
91
|
+
const modulesEndpoint = process.env.MODULES_ENDPOINT || backend?.modulesEndpoint || DEFAULT_MODULES_ENDPOINT;
|
|
92
|
+
// Every new project provisions UNDER the account (requestDatabase owns the
|
|
93
|
+
// database to the JWT user), so it is account-owned and enumerable. The
|
|
94
|
+
// account bearer is the single credential: it authenticates the provision
|
|
95
|
+
// mutation, the idempotency
|
|
96
|
+
// probe below, and is the ACCESS_TOKEN written to .env for project-local
|
|
97
|
+
// scripts. It expires with the login session — a relogin plus any provision
|
|
98
|
+
// run (including the skip path) refreshes the .env copy. A signed-in session
|
|
99
|
+
// with no usable bearer errors out here rather than silently minting a
|
|
100
|
+
// throwaway owner. Gate BEFORE prewarm so an error return doesn't leak a
|
|
101
|
+
// detached background scaffold/install.
|
|
102
|
+
const credential = (0, credential_1.selectProvisionCredential)(host.account(), host.signInHint);
|
|
103
|
+
if (credential.mode === 'error') {
|
|
104
|
+
return fail(`Cannot provision a database: ${credential.reason}`);
|
|
105
|
+
}
|
|
106
|
+
const ownerId = credential.ownerId;
|
|
107
|
+
// Idempotency keyed on binding liveness AND ownership: a binding is done only
|
|
108
|
+
// when its database still answers and belongs to the signed-in account
|
|
109
|
+
// (schemas are account-scoped). A dead binding (backend refreshed, database
|
|
110
|
+
// deleted, key revoked) or a live one under another account points at the
|
|
111
|
+
// reprovision path; an unreachable backend is a retry, never a reprovision.
|
|
112
|
+
const existingEnv = parseEnvKeys(existing);
|
|
113
|
+
const hasBinding = Boolean(existingEnv.DATABASE_ID && existingEnv.ACCESS_TOKEN);
|
|
114
|
+
if (hasBinding && !params.reprovision) {
|
|
115
|
+
// The probe carries the account bearer, so it only ever targets the
|
|
116
|
+
// app-configured backend — never a .env-pinned endpoint, which an
|
|
117
|
+
// untrusted cloned project controls.
|
|
118
|
+
const probe = await (0, db_probe_1.probeDatabase)({
|
|
119
|
+
endpoint: apiEndpoint,
|
|
120
|
+
bearer: credential.bearer,
|
|
121
|
+
databaseId: existingEnv.DATABASE_ID,
|
|
122
|
+
signInHint: host.signInHint,
|
|
123
|
+
});
|
|
124
|
+
if (probe.outcome === 'unreachable') {
|
|
125
|
+
return fail(`Cannot verify the existing database binding: backend unreachable (${probe.detail}). Check the Constructive backend is running, then retry — do not reprovision on an unreachable backend.`);
|
|
126
|
+
}
|
|
127
|
+
if (probe.outcome === 'missing') {
|
|
128
|
+
return fail(`Project carries a binding (DATABASE_ID=${existingEnv.DATABASE_ID}) but that database no longer exists on this backend. Re-run with reprovision: true to mint a fresh database (old keys archived in .env, old database untouched), then rebuild the schema (provision_blueprint + run_codegen); records do not carry over.`);
|
|
129
|
+
}
|
|
130
|
+
const sessionUserId = host.account()?.userId;
|
|
131
|
+
if (probe.ownerId && sessionUserId && probe.ownerId !== sessionUserId) {
|
|
132
|
+
return fail(`Project carries a binding (DATABASE_ID=${existingEnv.DATABASE_ID}) but that database was provisioned under a different account than the one signed in. Re-run with reprovision: true to mint a fresh database under this account (old keys archived in .env, old database untouched), then rebuild the schema (provision_blueprint + run_codegen); records do not carry over.`);
|
|
133
|
+
}
|
|
134
|
+
// Heal .env on the skip path: a stale token (relogin) or missing/stale
|
|
135
|
+
// endpoint pins (project provisioned before pins existed, or backend
|
|
136
|
+
// config changed) refresh to the current session + backend.
|
|
137
|
+
const refresh = {};
|
|
138
|
+
if (existingEnv.ACCESS_TOKEN !== credential.bearer)
|
|
139
|
+
refresh.ACCESS_TOKEN = credential.bearer;
|
|
140
|
+
if (existingEnv.API_ENDPOINT !== apiEndpoint)
|
|
141
|
+
refresh.API_ENDPOINT = apiEndpoint;
|
|
142
|
+
if (existingEnv.MODULES_ENDPOINT !== modulesEndpoint) {
|
|
143
|
+
refresh.MODULES_ENDPOINT = modulesEndpoint;
|
|
144
|
+
}
|
|
145
|
+
let tokenNote = '';
|
|
146
|
+
if (Object.keys(refresh).length > 0) {
|
|
147
|
+
const keys = Object.keys(refresh).join(', ');
|
|
148
|
+
try {
|
|
149
|
+
await (0, promises_1.writeFile)(envPath, (0, env_file_1.mergeEnv)(existing, refresh));
|
|
150
|
+
tokenNote = ` Refreshed in .env from the signed-in session: ${keys}.`;
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
tokenNote = ` Could not refresh ${keys} in .env: ${err instanceof Error ? err.message : String(err)}.`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const message = `Project already provisioned (DATABASE_ID=${existingEnv.DATABASE_ID}) and the database is live under this account. Skipping — pass reprovision: true only for an explicit clean rebuild.${tokenNote}`;
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: 'text', text: message }],
|
|
159
|
+
details: {
|
|
160
|
+
success: true,
|
|
161
|
+
message,
|
|
162
|
+
databaseId: existingEnv.DATABASE_ID,
|
|
163
|
+
databaseName: existingEnv.DATABASE_NAME,
|
|
164
|
+
ownerId: existingEnv.OWNER_ID,
|
|
165
|
+
skipped: true,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (hasBinding && params.reprovision) {
|
|
170
|
+
existing = (0, env_file_1.archiveBindingKeys)(existing, env_file_1.ARCHIVED_BINDING_KEYS, new Date().toISOString().slice(0, 10));
|
|
171
|
+
}
|
|
172
|
+
// The packages/app scaffold + pnpm install are database-INDEPENDENT, so kick
|
|
173
|
+
// them off now to run concurrently with the provisioning request below
|
|
174
|
+
// (near-instant on a warm-pool hit, up to minutes on the cold path).
|
|
175
|
+
// This keeps clone + install off run_codegen's critical path. Best-effort:
|
|
176
|
+
// run_codegen re-runs the same idempotent steps, so a prewarm failure is
|
|
177
|
+
// harmless. Never let it reject (we await it before returning).
|
|
178
|
+
const prewarm = (0, app_workspace_1.prewarmAppWorkspace)(ctx.cwd).catch((err) => ({
|
|
179
|
+
ok: false,
|
|
180
|
+
out: err instanceof Error ? err.message : String(err),
|
|
181
|
+
}));
|
|
182
|
+
// Resolve the module set: pinned base preset from node-type-registry, then
|
|
183
|
+
// ordered overlays (host default < project-local provision.json). No list is
|
|
184
|
+
// hand-copied here; overlays are pure data. A bad preset/manifest fails the
|
|
185
|
+
// run rather than provisioning a wrong module set.
|
|
186
|
+
let modules;
|
|
187
|
+
try {
|
|
188
|
+
const layers = [];
|
|
189
|
+
const hostOverlay = await host.provisionOverlay?.();
|
|
190
|
+
if (hostOverlay)
|
|
191
|
+
layers.push(hostOverlay);
|
|
192
|
+
const projectOverlay = await (0, manifest_1.loadProvisionManifest)(ctx.cwd);
|
|
193
|
+
if (projectOverlay)
|
|
194
|
+
layers.push(projectOverlay);
|
|
195
|
+
modules = (0, resolve_1.resolveProvisionModules)(layers);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return fail(`Cannot resolve provision modules: ${err instanceof Error ? err.message : String(err)}`);
|
|
199
|
+
}
|
|
200
|
+
const physicalDb = process.env.CONSTRUCTIVE_DB || 'constructive';
|
|
201
|
+
// Provision on the API endpoint: requestDatabase claims a warm-pool
|
|
202
|
+
// database when the resolved module set matches a cataloged preset
|
|
203
|
+
// (near-instant) and cold-provisions asynchronously otherwise; the ticket
|
|
204
|
+
// is polled on the modules endpoint until the database AND its deferred
|
|
205
|
+
// owner bootstrap complete. No retry wrapper: a pending ticket is the
|
|
206
|
+
// normal first response, and transient poll failures are absorbed inside
|
|
207
|
+
// the poll loop. The domain still derives from the api endpoint's host
|
|
208
|
+
// (per-DB endpoints live under it), and an explicit subdomain
|
|
209
|
+
// (= databaseName) keeps them deterministic (SUBDOMAIN-001).
|
|
210
|
+
let databaseId;
|
|
211
|
+
try {
|
|
212
|
+
({ databaseId } = await (0, request_database_1.requestDatabaseProvision)({
|
|
213
|
+
apiEndpoint,
|
|
214
|
+
modulesEndpoint,
|
|
215
|
+
bearer: credential.bearer,
|
|
216
|
+
databaseName,
|
|
217
|
+
domain: provisionDomain(apiEndpoint),
|
|
218
|
+
request: (0, preset_match_1.selectProvisionRequest)(modules),
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
223
|
+
const nameTakenHint = /already exists|already taken|already in use|duplicate/i.test(detail)
|
|
224
|
+
? ' The database name is already taken on the backend — re-run with a different database_name.'
|
|
225
|
+
: '';
|
|
226
|
+
return fail(`Database provisioning failed: ${detail}.${nameTakenHint}`);
|
|
227
|
+
}
|
|
228
|
+
// Enable membership defaults + naming settings at the SQL level. Best-effort:
|
|
229
|
+
// provisioning already succeeded, so a fixup failure is a warning, not an error.
|
|
230
|
+
const fixup = await (0, pg_fixups_1.applySqlFixups)({ databaseName, physicalDb });
|
|
231
|
+
// Persist the binding to the project .env (upsert, preserving other keys).
|
|
232
|
+
const merged = (0, env_file_1.mergeEnv)(existing, (0, env_file_1.provisionEnvVars)({
|
|
233
|
+
databaseId,
|
|
234
|
+
databaseName,
|
|
235
|
+
ownerId,
|
|
236
|
+
accessToken: credential.bearer,
|
|
237
|
+
apiEndpoint,
|
|
238
|
+
modulesEndpoint,
|
|
239
|
+
}));
|
|
240
|
+
try {
|
|
241
|
+
await (0, promises_1.writeFile)(envPath, merged);
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
return fail(`Database provisioned (ID: ${databaseId}) but writing .env failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
245
|
+
}
|
|
246
|
+
// Settle the concurrent prewarm so the scaffold + install are in place before
|
|
247
|
+
// run_codegen runs. A failure here is non-fatal — run_codegen redoes the work.
|
|
248
|
+
const prewarmResult = await prewarm;
|
|
249
|
+
const prewarmNote = prewarmResult.ok ? ' packages/app prewarmed.' : '';
|
|
250
|
+
const reprovisionNote = hasBinding && params.reprovision
|
|
251
|
+
? ' Previous binding archived in .env (old database kept); rebuild the schema with provision_blueprint + run_codegen.'
|
|
252
|
+
: '';
|
|
253
|
+
const message = `Provisioned database "${databaseName}" (ID: ${databaseId}). Credentials written to .env. ${fixup.note}${prewarmNote}${reprovisionNote}`;
|
|
254
|
+
return {
|
|
255
|
+
content: [{ type: 'text', text: message }],
|
|
256
|
+
details: {
|
|
257
|
+
success: true,
|
|
258
|
+
message,
|
|
259
|
+
databaseId,
|
|
260
|
+
databaseName,
|
|
261
|
+
ownerId,
|
|
262
|
+
fixupNote: fixup.note,
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HarnessTool } from '@agentic-kit/harness';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { prewarmAppWorkspace } from '../app-workspace';
|
|
4
|
+
declare const RunCodegenZod: z.ZodObject<{}, z.core.$strip>;
|
|
5
|
+
export type RunCodegenDetails = {
|
|
6
|
+
success: boolean;
|
|
7
|
+
message: string;
|
|
8
|
+
barrelsRewritten?: number;
|
|
9
|
+
generatedSubmodules?: string[];
|
|
10
|
+
};
|
|
11
|
+
export declare const runCodegenTool: HarnessTool<typeof RunCodegenZod, RunCodegenDetails>;
|
|
12
|
+
export { prewarmAppWorkspace };
|