@agentic-kit/pi 0.17.1 → 0.18.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/README.md +5 -7
- package/confirm-gate.d.ts +1 -2
- package/esm/confirm-gate.d.ts +1 -2
- package/esm/host.d.ts +5 -110
- package/esm/host.js +5 -18
- package/esm/index.d.ts +17 -9
- package/esm/index.js +20 -38
- package/esm/pi-tool.d.ts +16 -0
- package/esm/pi-tool.js +32 -0
- package/host.d.ts +5 -110
- package/host.js +10 -22
- package/index.d.ts +17 -9
- package/index.js +47 -62
- package/package.json +6 -10
- package/pi-tool.d.ts +16 -0
- package/pi-tool.js +36 -0
- package/app-workspace.d.ts +0 -16
- package/app-workspace.js +0 -145
- package/context.d.ts +0 -62
- package/context.js +0 -282
- package/db-probe.d.ts +0 -30
- package/db-probe.js +0 -50
- package/esm/app-workspace.d.ts +0 -16
- package/esm/app-workspace.js +0 -134
- package/esm/context.d.ts +0 -62
- package/esm/context.js +0 -270
- package/esm/db-probe.d.ts +0 -30
- package/esm/db-probe.js +0 -47
- package/esm/policy/add-policies-to-table.d.ts +0 -26
- package/esm/policy/add-policies-to-table.js +0 -66
- package/esm/policy/provision-helpers.d.ts +0 -38
- package/esm/policy/provision-helpers.js +0 -50
- package/esm/provision-database/credential.d.ts +0 -14
- package/esm/provision-database/credential.js +0 -19
- package/esm/provision-database/env-file.d.ts +0 -37
- package/esm/provision-database/env-file.js +0 -85
- package/esm/provision-database/manifest.d.ts +0 -10
- package/esm/provision-database/manifest.js +0 -48
- package/esm/provision-database/pg-fixups.d.ts +0 -16
- package/esm/provision-database/pg-fixups.js +0 -132
- package/esm/provision-database/preset-match.d.ts +0 -13
- package/esm/provision-database/preset-match.js +0 -66
- package/esm/provision-database/presets.d.ts +0 -4
- package/esm/provision-database/presets.js +0 -13
- package/esm/provision-database/request-database.d.ts +0 -61
- package/esm/provision-database/request-database.js +0 -130
- package/esm/provision-database/resolve.d.ts +0 -27
- package/esm/provision-database/resolve.js +0 -49
- package/esm/records/meta.d.ts +0 -41
- package/esm/records/meta.js +0 -167
- package/esm/run-codegen/barrels.d.ts +0 -3
- package/esm/run-codegen/barrels.js +0 -48
- package/esm/run-codegen/endpoints.d.ts +0 -8
- package/esm/run-codegen/endpoints.js +0 -46
- package/esm/schema-resolve.d.ts +0 -23
- package/esm/schema-resolve.js +0 -69
- package/esm/tool-schema.d.ts +0 -3
- package/esm/tool-schema.js +0 -10
- package/esm/tools/add-policies.d.ts +0 -8
- package/esm/tools/add-policies.js +0 -96
- package/esm/tools/add-records.d.ts +0 -14
- package/esm/tools/add-records.js +0 -124
- package/esm/tools/add-relation-schema.d.ts +0 -30
- package/esm/tools/add-relation-schema.js +0 -58
- package/esm/tools/add-relation.d.ts +0 -10
- package/esm/tools/add-relation.js +0 -91
- package/esm/tools/create-api-key.d.ts +0 -18
- package/esm/tools/create-api-key.js +0 -216
- package/esm/tools/describe-schema.d.ts +0 -18
- package/esm/tools/describe-schema.js +0 -132
- package/esm/tools/manage-entity-types.d.ts +0 -41
- package/esm/tools/manage-entity-types.js +0 -203
- package/esm/tools/mutations.d.ts +0 -14
- package/esm/tools/mutations.js +0 -235
- package/esm/tools/provision-blueprint.d.ts +0 -22
- package/esm/tools/provision-blueprint.js +0 -111
- package/esm/tools/provision-database.d.ts +0 -14
- package/esm/tools/provision-database.js +0 -262
- package/esm/tools/run-codegen.d.ts +0 -11
- package/esm/tools/run-codegen.js +0 -139
- package/esm/tools/templates.d.ts +0 -49
- package/esm/tools/templates.js +0 -337
- package/policy/add-policies-to-table.d.ts +0 -26
- package/policy/add-policies-to-table.js +0 -71
- package/policy/provision-helpers.d.ts +0 -38
- package/policy/provision-helpers.js +0 -58
- package/provision-database/credential.d.ts +0 -14
- package/provision-database/credential.js +0 -22
- package/provision-database/env-file.d.ts +0 -37
- package/provision-database/env-file.js +0 -91
- package/provision-database/manifest.d.ts +0 -10
- package/provision-database/manifest.js +0 -56
- package/provision-database/pg-fixups.d.ts +0 -16
- package/provision-database/pg-fixups.js +0 -170
- package/provision-database/preset-match.d.ts +0 -13
- package/provision-database/preset-match.js +0 -70
- package/provision-database/presets.d.ts +0 -4
- package/provision-database/presets.js +0 -17
- package/provision-database/request-database.d.ts +0 -61
- package/provision-database/request-database.js +0 -134
- package/provision-database/resolve.d.ts +0 -27
- package/provision-database/resolve.js +0 -53
- package/records/meta.d.ts +0 -41
- package/records/meta.js +0 -172
- package/run-codegen/barrels.d.ts +0 -3
- package/run-codegen/barrels.js +0 -54
- package/run-codegen/endpoints.d.ts +0 -8
- package/run-codegen/endpoints.js +0 -51
- package/schema-resolve.d.ts +0 -23
- package/schema-resolve.js +0 -75
- package/tool-schema.d.ts +0 -3
- package/tool-schema.js +0 -13
- package/tools/add-policies.d.ts +0 -8
- package/tools/add-policies.js +0 -99
- package/tools/add-records.d.ts +0 -14
- package/tools/add-records.js +0 -127
- package/tools/add-relation-schema.d.ts +0 -30
- package/tools/add-relation-schema.js +0 -62
- package/tools/add-relation.d.ts +0 -10
- package/tools/add-relation.js +0 -94
- package/tools/create-api-key.d.ts +0 -18
- package/tools/create-api-key.js +0 -222
- package/tools/describe-schema.d.ts +0 -18
- package/tools/describe-schema.js +0 -135
- package/tools/manage-entity-types.d.ts +0 -41
- package/tools/manage-entity-types.js +0 -208
- package/tools/mutations.d.ts +0 -14
- package/tools/mutations.js +0 -238
- package/tools/provision-blueprint.d.ts +0 -22
- package/tools/provision-blueprint.js +0 -114
- package/tools/provision-database.d.ts +0 -14
- package/tools/provision-database.js +0 -268
- package/tools/run-codegen.d.ts +0 -11
- package/tools/run-codegen.js +0 -143
- package/tools/templates.d.ts +0 -49
- package/tools/templates.js +0 -341
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { BlueprintDefinition } from '@agentic-kit/harness';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
export declare const DeleteAction: z.ZodEnum<{
|
|
4
|
-
c: "c";
|
|
5
|
-
r: "r";
|
|
6
|
-
n: "n";
|
|
7
|
-
a: "a";
|
|
8
|
-
d: "d";
|
|
9
|
-
}>;
|
|
10
|
-
export declare const AddRelationZod: z.ZodObject<{
|
|
11
|
-
relation_type: z.ZodOptional<z.ZodEnum<{
|
|
12
|
-
belongs_to: "belongs_to";
|
|
13
|
-
many_to_many: "many_to_many";
|
|
14
|
-
}>>;
|
|
15
|
-
source_table: z.ZodString;
|
|
16
|
-
target_table: z.ZodString;
|
|
17
|
-
field_name: z.ZodOptional<z.ZodString>;
|
|
18
|
-
junction_table_name: z.ZodOptional<z.ZodString>;
|
|
19
|
-
delete_action: z.ZodOptional<z.ZodEnum<{
|
|
20
|
-
c: "c";
|
|
21
|
-
r: "r";
|
|
22
|
-
n: "n";
|
|
23
|
-
a: "a";
|
|
24
|
-
d: "d";
|
|
25
|
-
}>>;
|
|
26
|
-
is_required: z.ZodOptional<z.ZodBoolean>;
|
|
27
|
-
}, z.core.$strip>;
|
|
28
|
-
export declare const AddRelationSchema: import("typebox/type").TSchema;
|
|
29
|
-
export type AddRelationParams = z.infer<typeof AddRelationZod>;
|
|
30
|
-
export declare function buildRelation(params: AddRelationParams): BlueprintDefinition['relations'];
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { toolSchema } from '../tool-schema';
|
|
3
|
-
// delete_action is intentionally always sent: the backend REJECTS a
|
|
4
|
-
// RelationBelongsTo whose delete_action is absent ("PROVISION_RELATION:
|
|
5
|
-
// delete_action is required"). We default it to 'a' (NO ACTION) so the model
|
|
6
|
-
// never has to supply it.
|
|
7
|
-
export const DeleteAction = z
|
|
8
|
-
.enum(['c', 'r', 'n', 'a', 'd'])
|
|
9
|
-
.describe('FK delete action: c=CASCADE, r=RESTRICT, n=SET NULL, a=NO ACTION (default), d=SET DEFAULT');
|
|
10
|
-
export const AddRelationZod = z.object({
|
|
11
|
-
relation_type: z
|
|
12
|
-
.enum(['belongs_to', 'many_to_many'])
|
|
13
|
-
.describe('belongs_to adds an FK column on the source table; many_to_many adds a junction table. Defaults to belongs_to.')
|
|
14
|
-
.optional(),
|
|
15
|
-
source_table: z.string().describe('Existing source table name (snake_case).'),
|
|
16
|
-
target_table: z.string().describe('Existing target table name (snake_case).'),
|
|
17
|
-
field_name: z
|
|
18
|
-
.string()
|
|
19
|
-
.describe('belongs_to only: FK column name to add on the source table, e.g. "author_id".')
|
|
20
|
-
.optional(),
|
|
21
|
-
junction_table_name: z
|
|
22
|
-
.string()
|
|
23
|
-
.describe('many_to_many only: name of the junction table to create, e.g. "post_tags".')
|
|
24
|
-
.optional(),
|
|
25
|
-
delete_action: DeleteAction.optional(),
|
|
26
|
-
is_required: z
|
|
27
|
-
.boolean()
|
|
28
|
-
.describe('belongs_to only: make the FK NOT NULL. Defaults to false — a NOT NULL FK fails if the source table already has rows.')
|
|
29
|
-
.optional(),
|
|
30
|
-
});
|
|
31
|
-
export const AddRelationSchema = toolSchema(AddRelationZod);
|
|
32
|
-
// Build a relation-only blueprint definition (no tables): the spike proved
|
|
33
|
-
// constructBlueprint applies relations to PRE-EXISTING tables without recreating
|
|
34
|
-
// them, as long as delete_action is supplied.
|
|
35
|
-
export function buildRelation(params) {
|
|
36
|
-
const deleteAction = params.delete_action ?? 'a';
|
|
37
|
-
if ((params.relation_type ?? 'belongs_to') === 'many_to_many') {
|
|
38
|
-
return [
|
|
39
|
-
{
|
|
40
|
-
$type: 'RelationManyToMany',
|
|
41
|
-
source_table: params.source_table,
|
|
42
|
-
target_table: params.target_table,
|
|
43
|
-
junction_table_name: params.junction_table_name,
|
|
44
|
-
delete_action: deleteAction,
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
}
|
|
48
|
-
return [
|
|
49
|
-
{
|
|
50
|
-
$type: 'RelationBelongsTo',
|
|
51
|
-
source_table: params.source_table,
|
|
52
|
-
target_table: params.target_table,
|
|
53
|
-
field_name: params.field_name,
|
|
54
|
-
delete_action: deleteAction,
|
|
55
|
-
is_required: params.is_required ?? false,
|
|
56
|
-
},
|
|
57
|
-
];
|
|
58
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
2
|
-
import { AddRelationSchema } from './add-relation-schema';
|
|
3
|
-
export type AddRelationDetails = {
|
|
4
|
-
success: boolean;
|
|
5
|
-
message: string;
|
|
6
|
-
sourceTable?: string;
|
|
7
|
-
targetTable?: string;
|
|
8
|
-
fieldName?: string;
|
|
9
|
-
};
|
|
10
|
-
export declare const addRelationTool: ToolDefinition<typeof AddRelationSchema, AddRelationDetails>;
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { expandBlueprintDefaults } from '@agentic-kit/harness';
|
|
2
|
-
import { resolveProjectContext } from '../context';
|
|
3
|
-
import { resolveSchema, resolveTable } from '../schema-resolve';
|
|
4
|
-
import { AddRelationSchema, buildRelation } from './add-relation-schema';
|
|
5
|
-
function fail(message) {
|
|
6
|
-
return { content: [{ type: 'text', text: message }], details: { success: false, message } };
|
|
7
|
-
}
|
|
8
|
-
export const addRelationTool = {
|
|
9
|
-
name: 'add_relation',
|
|
10
|
-
label: 'Add relation',
|
|
11
|
-
description: 'Add a relation between two EXISTING tables without recreating them: belongs_to adds a foreign-key column on the source table; many_to_many creates a junction table. Use this for relations discovered after the tables were provisioned. For brand-new related tables, prefer a single provision_blueprint call.',
|
|
12
|
-
promptSnippet: 'add_relation: link two existing tables (belongs_to FK column, or many_to_many junction). Gated.',
|
|
13
|
-
parameters: AddRelationSchema,
|
|
14
|
-
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
15
|
-
const type = params.relation_type ?? 'belongs_to';
|
|
16
|
-
if (type === 'belongs_to' && !params.field_name) {
|
|
17
|
-
return fail('field_name is required for a belongs_to relation (the FK column name on the source table).');
|
|
18
|
-
}
|
|
19
|
-
if (type === 'many_to_many' && !params.junction_table_name) {
|
|
20
|
-
return fail('junction_table_name is required for a many_to_many relation.');
|
|
21
|
-
}
|
|
22
|
-
const resolved = await resolveProjectContext(ctx.cwd);
|
|
23
|
-
if (!resolved.context)
|
|
24
|
-
return fail(resolved.reason);
|
|
25
|
-
const { modules, databaseId, schemaId, ownerId } = resolved.context;
|
|
26
|
-
if (!ownerId)
|
|
27
|
-
return fail('Not authenticated (missing OWNER_ID in .env).');
|
|
28
|
-
// Validate both tables exist before constructing — a clearer error than the
|
|
29
|
-
// backend's, and avoids a no-op blueprint.
|
|
30
|
-
let schema;
|
|
31
|
-
try {
|
|
32
|
-
schema = await resolveSchema(resolved.context);
|
|
33
|
-
resolveTable(schema, params.source_table);
|
|
34
|
-
resolveTable(schema, params.target_table);
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
return fail(err instanceof Error ? err.message : 'Failed to resolve schema.');
|
|
38
|
-
}
|
|
39
|
-
const definition = expandBlueprintDefaults({ tables: [], relations: buildRelation(params) });
|
|
40
|
-
const uniqueName = `add_relation_${Date.now()}`;
|
|
41
|
-
try {
|
|
42
|
-
const created = await modules.blueprint
|
|
43
|
-
.create({
|
|
44
|
-
data: {
|
|
45
|
-
ownerId,
|
|
46
|
-
databaseId,
|
|
47
|
-
name: uniqueName,
|
|
48
|
-
displayName: `${params.source_table} → ${params.target_table}`,
|
|
49
|
-
description: `Add ${type} relation`,
|
|
50
|
-
definition,
|
|
51
|
-
},
|
|
52
|
-
select: { id: true },
|
|
53
|
-
})
|
|
54
|
-
.unwrap();
|
|
55
|
-
const blueprintId = created.createBlueprint?.blueprint?.id;
|
|
56
|
-
if (!blueprintId)
|
|
57
|
-
throw new Error('Failed to create relation blueprint');
|
|
58
|
-
const constructResult = await modules.mutation
|
|
59
|
-
.constructBlueprint({ input: { blueprintId, schemaId } }, { select: { result: true } })
|
|
60
|
-
.unwrap();
|
|
61
|
-
if (!constructResult.constructBlueprint?.result) {
|
|
62
|
-
const constructions = await modules.blueprintConstruction
|
|
63
|
-
.findMany({
|
|
64
|
-
where: { blueprintId: { equalTo: blueprintId } },
|
|
65
|
-
orderBy: ['ID_DESC'],
|
|
66
|
-
first: 1,
|
|
67
|
-
select: { status: true, errorDetails: true },
|
|
68
|
-
})
|
|
69
|
-
.unwrap();
|
|
70
|
-
const error = constructions?.blueprintConstructions?.nodes?.[0]?.errorDetails ?? 'Unknown error';
|
|
71
|
-
return fail(`Failed to add relation: ${typeof error === 'string' ? error : JSON.stringify(error)}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
return fail(`Failed to add relation: ${err instanceof Error ? err.message : String(err)}`);
|
|
76
|
-
}
|
|
77
|
-
const message = type === 'many_to_many'
|
|
78
|
-
? `Created junction table "${params.junction_table_name}" linking ${params.source_table} ↔ ${params.target_table}.`
|
|
79
|
-
: `Added FK "${params.field_name}" on ${params.source_table} → ${params.target_table}.`;
|
|
80
|
-
return {
|
|
81
|
-
content: [{ type: 'text', text: message }],
|
|
82
|
-
details: {
|
|
83
|
-
success: true,
|
|
84
|
-
message,
|
|
85
|
-
sourceTable: params.source_table,
|
|
86
|
-
targetTable: params.target_table,
|
|
87
|
-
fieldName: params.field_name,
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
},
|
|
91
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
2
|
-
declare const CreateApiKeySchema: import("typebox/type").TSchema;
|
|
3
|
-
export type CreateApiKeyDetails = {
|
|
4
|
-
success: boolean;
|
|
5
|
-
message: string;
|
|
6
|
-
keyId?: string;
|
|
7
|
-
envVar?: string;
|
|
8
|
-
expiresAt?: string;
|
|
9
|
-
principalId?: string;
|
|
10
|
-
principalName?: string;
|
|
11
|
-
scope?: string;
|
|
12
|
-
needsAuth?: boolean;
|
|
13
|
-
};
|
|
14
|
-
export declare function toEnvVar(keyName: string): string;
|
|
15
|
-
export declare function isStepUpError(messages: string[]): boolean;
|
|
16
|
-
export declare function describeScope(entityIds: string[] | undefined, readOnly: boolean): string;
|
|
17
|
-
export declare const createApiKeyTool: ToolDefinition<typeof CreateApiKeySchema, CreateApiKeyDetails>;
|
|
18
|
-
export {};
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { auth } from '@constructive-io/sdk';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { deriveSubdomainEndpoint, resolveDataToken, resolveProjectContext } from '../context';
|
|
4
|
-
import { getHost } from '../host';
|
|
5
|
-
import { toolSchema } from '../tool-schema';
|
|
6
|
-
const CreateApiKeyZod = z.object({
|
|
7
|
-
key_name: z
|
|
8
|
-
.string()
|
|
9
|
-
.describe('Name for the API key (e.g. "deploy bot"). The .env variable name derives from it (UPPER_SNAKE + _API_KEY).'),
|
|
10
|
-
principal_name: z
|
|
11
|
-
.string()
|
|
12
|
-
.describe('Machine identity (principal) that owns the key. Defaults to key_name. An existing principal with this name is reused for unscoped keys.')
|
|
13
|
-
.optional(),
|
|
14
|
-
entity_ids: z
|
|
15
|
-
.array(z.string())
|
|
16
|
-
.describe('Entity row UUIDs to scope the principal to. Omit for a personal (unscoped) key that acts as the signed-in app user.')
|
|
17
|
-
.optional(),
|
|
18
|
-
read_only: z
|
|
19
|
-
.boolean()
|
|
20
|
-
.describe('Restrict the principal and key to read-only operations.')
|
|
21
|
-
.optional(),
|
|
22
|
-
expires_in_days: z
|
|
23
|
-
.number()
|
|
24
|
-
.int()
|
|
25
|
-
.positive()
|
|
26
|
-
.describe('Key lifetime in days. Default 90.')
|
|
27
|
-
.optional(),
|
|
28
|
-
});
|
|
29
|
-
const CreateApiKeySchema = toolSchema(CreateApiKeyZod);
|
|
30
|
-
function fail(message, extra) {
|
|
31
|
-
return {
|
|
32
|
-
content: [{ type: 'text', text: message }],
|
|
33
|
-
details: { success: false, message, ...extra },
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
export function toEnvVar(keyName) {
|
|
37
|
-
const base = keyName
|
|
38
|
-
.trim()
|
|
39
|
-
.toUpperCase()
|
|
40
|
-
.replace(/[^A-Z0-9]+/g, '_')
|
|
41
|
-
.replace(/^_+|_+$/g, '');
|
|
42
|
-
return base.endsWith('_API_KEY') ? base : `${base}_API_KEY`;
|
|
43
|
-
}
|
|
44
|
-
export function isStepUpError(messages) {
|
|
45
|
-
return messages.some((m) => /step[\s_-]?up/i.test(m));
|
|
46
|
-
}
|
|
47
|
-
export function describeScope(entityIds, readOnly) {
|
|
48
|
-
const parts = [];
|
|
49
|
-
parts.push(entityIds?.length
|
|
50
|
-
? `scoped to ${entityIds.length} entit${entityIds.length === 1 ? 'y' : 'ies'}`
|
|
51
|
-
: 'unscoped (acts as the signed-in app user)');
|
|
52
|
-
if (readOnly)
|
|
53
|
-
parts.push('read-only');
|
|
54
|
-
return parts.join(', ');
|
|
55
|
-
}
|
|
56
|
-
async function rawGraphql(endpoint, token, query, variables) {
|
|
57
|
-
const res = await fetch(endpoint, {
|
|
58
|
-
method: 'POST',
|
|
59
|
-
headers: {
|
|
60
|
-
'Content-Type': 'application/json',
|
|
61
|
-
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
62
|
-
},
|
|
63
|
-
body: JSON.stringify({ query, variables }),
|
|
64
|
-
});
|
|
65
|
-
return (await res.json());
|
|
66
|
-
}
|
|
67
|
-
async function createPrincipalInputFields(endpoint) {
|
|
68
|
-
const probe = await rawGraphql(endpoint, undefined, '{ __type(name: "CreatePrincipalInput") { inputFields { name } } }');
|
|
69
|
-
const type = probe.data?.__type;
|
|
70
|
-
return new Set((type?.inputFields ?? []).map((f) => f.name));
|
|
71
|
-
}
|
|
72
|
-
export const createApiKeyTool = {
|
|
73
|
-
name: 'create_api_key',
|
|
74
|
-
label: 'Create API key',
|
|
75
|
-
description: 'Mint an API key for the project database, owned by a machine principal. Optionally scope it to specific entity rows and/or read-only. The human confirms the mint, completes MFA step-up in the app when required, and receives the key via .env + a one-time reveal — the plaintext key never appears in this conversation. Requires the user to be signed in to their app in the Preview.',
|
|
76
|
-
promptSnippet: 'create_api_key: mint an entity-scoped API key for the project database. Gated; the plaintext goes to .env + a one-time reveal, never into the chat — reference it as its env var.',
|
|
77
|
-
parameters: CreateApiKeySchema,
|
|
78
|
-
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
79
|
-
const keyName = params.key_name?.trim();
|
|
80
|
-
if (!keyName)
|
|
81
|
-
return fail('create_api_key requires "key_name".');
|
|
82
|
-
const resolved = await resolveProjectContext(ctx.cwd, { plane: 'data' });
|
|
83
|
-
if (!resolved.context)
|
|
84
|
-
return fail(resolved.reason);
|
|
85
|
-
const { databaseId, databaseName, apiEndpoint } = resolved.context;
|
|
86
|
-
if (!databaseName) {
|
|
87
|
-
return fail('Cannot resolve the app auth endpoint (DATABASE_NAME missing from .env). Re-provision the database, then retry.');
|
|
88
|
-
}
|
|
89
|
-
const host = getHost();
|
|
90
|
-
if (!host.deliverSecret) {
|
|
91
|
-
return fail('This host cannot deliver secrets (no .env write + reveal flow), so no key was minted. Create the key from a host that supports secret delivery.');
|
|
92
|
-
}
|
|
93
|
-
const token = await resolveDataToken(resolved.context);
|
|
94
|
-
if (!token.token) {
|
|
95
|
-
return fail(token.reason ?? 'Sign in to the app database to create an API key.', {
|
|
96
|
-
needsAuth: true,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
const authEndpoint = deriveSubdomainEndpoint(apiEndpoint, `auth-${databaseName}`);
|
|
100
|
-
if (!authEndpoint)
|
|
101
|
-
return fail('Cannot derive the app auth endpoint.');
|
|
102
|
-
const scoped = Boolean(params.entity_ids?.length) || params.read_only === true;
|
|
103
|
-
try {
|
|
104
|
-
if (scoped) {
|
|
105
|
-
const fields = await createPrincipalInputFields(authEndpoint);
|
|
106
|
-
const missing = [
|
|
107
|
-
...(params.entity_ids?.length && !fields.has('entityIds') ? ['entityIds'] : []),
|
|
108
|
-
...(params.read_only === true && !fields.has('isReadOnly') ? ['isReadOnly'] : []),
|
|
109
|
-
];
|
|
110
|
-
if (missing.length) {
|
|
111
|
-
return fail(`This deployment does not support scoping a principal at create time (no ${missing.join(', ')} on createPrincipal). No key was minted. If an unscoped key that acts as the signed-in user is acceptable, ask for one explicitly.`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const dbAuth = auth.createClient({
|
|
115
|
-
endpoint: authEndpoint,
|
|
116
|
-
headers: { Authorization: `Bearer ${token.token}` },
|
|
117
|
-
});
|
|
118
|
-
const principalName = params.principal_name?.trim() || keyName;
|
|
119
|
-
const existing = await dbAuth.principal
|
|
120
|
-
.findFirst({
|
|
121
|
-
where: { name: { equalTo: principalName } },
|
|
122
|
-
select: { id: true, name: true, userId: true, isReadOnly: true },
|
|
123
|
-
})
|
|
124
|
-
.unwrap();
|
|
125
|
-
// createApiKey (like createPrincipal.result) takes the principal's
|
|
126
|
-
// identity userId; principal_entity.principalId references the row id.
|
|
127
|
-
let principalId = existing.principal?.userId;
|
|
128
|
-
if (existing.principal && scoped) {
|
|
129
|
-
return fail(`Principal "${principalName}" already exists, and its scope cannot be verified against the requested one. No key was minted. Use a new principal_name for a scoped key, or mint an unscoped key on the existing principal explicitly.`);
|
|
130
|
-
}
|
|
131
|
-
if (existing.principal) {
|
|
132
|
-
const scopeRow = await dbAuth.principalEntity
|
|
133
|
-
.findFirst({
|
|
134
|
-
where: { principalId: { equalTo: existing.principal.id } },
|
|
135
|
-
select: { id: true },
|
|
136
|
-
})
|
|
137
|
-
.unwrap();
|
|
138
|
-
if (scopeRow.principalEntity || existing.principal?.isReadOnly) {
|
|
139
|
-
return fail(`Principal "${principalName}" already exists with a narrower scope (entity-scoped or read-only), so a key minted under it would not act as the signed-in user. No key was minted. Use a new principal_name for an unscoped key.`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (!principalId) {
|
|
143
|
-
// The SDK's PrincipalModel.create sends a nested {principal} input, but
|
|
144
|
-
// the live CreatePrincipalInput is flat and its payload only exposes
|
|
145
|
-
// `result: UUID` — raw GraphQL is the only working surface.
|
|
146
|
-
const created = await rawGraphql(authEndpoint, token.token, 'mutation ($input: CreatePrincipalInput!) { createPrincipal(input: $input) { result } }', {
|
|
147
|
-
input: {
|
|
148
|
-
name: principalName,
|
|
149
|
-
...(params.entity_ids?.length && { entityIds: params.entity_ids }),
|
|
150
|
-
...(params.read_only !== undefined && { isReadOnly: params.read_only }),
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
if (created.errors?.length) {
|
|
154
|
-
return fail(`createPrincipal failed: ${created.errors.map((e) => e.message).join('; ')}`);
|
|
155
|
-
}
|
|
156
|
-
const payload = created.data?.createPrincipal;
|
|
157
|
-
principalId = payload?.result;
|
|
158
|
-
if (!principalId)
|
|
159
|
-
return fail('createPrincipal returned no principal id.');
|
|
160
|
-
}
|
|
161
|
-
const mint = () => dbAuth.mutation
|
|
162
|
-
.createApiKey({
|
|
163
|
-
input: {
|
|
164
|
-
principalId: principalId,
|
|
165
|
-
keyName,
|
|
166
|
-
...(params.read_only && { accessLevel: 'read_only' }),
|
|
167
|
-
expiresIn: { days: params.expires_in_days ?? 90 },
|
|
168
|
-
},
|
|
169
|
-
}, { select: { result: { select: { apiKey: true, keyId: true, expiresAt: true } } } })
|
|
170
|
-
.execute();
|
|
171
|
-
let minted = await mint();
|
|
172
|
-
if (!minted.ok && isStepUpError(minted.errors.map((e) => e.message))) {
|
|
173
|
-
if (!host.requestStepUp) {
|
|
174
|
-
return fail('Creating this key requires MFA step-up, and this host has no step-up flow. Complete step-up in the app (verify your password), then retry.');
|
|
175
|
-
}
|
|
176
|
-
const verified = await host.requestStepUp({ databaseId, databaseName, apiEndpoint });
|
|
177
|
-
if (!verified)
|
|
178
|
-
return fail('Step-up verification was not completed. No key was minted.');
|
|
179
|
-
minted = await mint();
|
|
180
|
-
}
|
|
181
|
-
if (!minted.ok) {
|
|
182
|
-
return fail(`createApiKey failed: ${minted.errors.map((e) => e.message).join('; ')}`);
|
|
183
|
-
}
|
|
184
|
-
const record = minted.data?.createApiKey?.result;
|
|
185
|
-
if (!record?.apiKey || !record.keyId)
|
|
186
|
-
return fail('createApiKey returned no key.');
|
|
187
|
-
const envVar = toEnvVar(keyName);
|
|
188
|
-
await host.deliverSecret({
|
|
189
|
-
databaseId,
|
|
190
|
-
cwd: ctx.cwd,
|
|
191
|
-
envVar,
|
|
192
|
-
plaintext: record.apiKey,
|
|
193
|
-
keyId: record.keyId,
|
|
194
|
-
expiresAt: record.expiresAt ?? undefined,
|
|
195
|
-
});
|
|
196
|
-
const scope = describeScope(params.entity_ids, params.read_only === true);
|
|
197
|
-
const message = `Created API key "${keyName}" (keyId: ${record.keyId}) for principal "${principalName}" — ${scope}. The key was written to .env as ${envVar} and revealed once to the user; it is not in this conversation. Reference it as ${envVar}.${record.expiresAt ? ` Expires ${record.expiresAt}.` : ''}`;
|
|
198
|
-
return {
|
|
199
|
-
content: [{ type: 'text', text: message }],
|
|
200
|
-
details: {
|
|
201
|
-
success: true,
|
|
202
|
-
message,
|
|
203
|
-
keyId: record.keyId,
|
|
204
|
-
envVar,
|
|
205
|
-
expiresAt: record.expiresAt ?? undefined,
|
|
206
|
-
principalId,
|
|
207
|
-
principalName,
|
|
208
|
-
scope,
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
catch (err) {
|
|
213
|
-
return fail(err instanceof Error ? err.message : 'Failed to create the API key.');
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
2
|
-
declare const ParamSchema: import("typebox/type").TSchema;
|
|
3
|
-
type SchemaField = {
|
|
4
|
-
name: string;
|
|
5
|
-
type: string;
|
|
6
|
-
isRequired: boolean;
|
|
7
|
-
defaultValue: string | null;
|
|
8
|
-
};
|
|
9
|
-
type SchemaTable = {
|
|
10
|
-
name: string;
|
|
11
|
-
fields: SchemaField[];
|
|
12
|
-
policies: string[];
|
|
13
|
-
};
|
|
14
|
-
export type DescribeSchemaDetails = {
|
|
15
|
-
tables: SchemaTable[];
|
|
16
|
-
};
|
|
17
|
-
export declare const describeSchemaTool: ToolDefinition<typeof ParamSchema, DescribeSchemaDetails>;
|
|
18
|
-
export {};
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { fieldTypeToTypeName, isInternalPolicy } from '@agentic-kit/harness';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { resolveProjectContext } from '../context';
|
|
4
|
-
import { toolSchema } from '../tool-schema';
|
|
5
|
-
const ParamZod = z.object({});
|
|
6
|
-
const ParamSchema = toolSchema(ParamZod);
|
|
7
|
-
function toDefaultValueString(value) {
|
|
8
|
-
if (value == null)
|
|
9
|
-
return null;
|
|
10
|
-
if (typeof value === 'string')
|
|
11
|
-
return value;
|
|
12
|
-
return JSON.stringify(value);
|
|
13
|
-
}
|
|
14
|
-
function formatText(details) {
|
|
15
|
-
const lines = [];
|
|
16
|
-
if (details.tables.length === 0) {
|
|
17
|
-
lines.push('No application tables yet.');
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
lines.push(`Tables (${details.tables.length}):`);
|
|
21
|
-
for (const table of details.tables) {
|
|
22
|
-
lines.push(` ${table.name}`);
|
|
23
|
-
for (const field of table.fields) {
|
|
24
|
-
const req = field.isRequired ? ' required' : '';
|
|
25
|
-
const def = field.defaultValue != null ? ` default=${field.defaultValue}` : '';
|
|
26
|
-
lines.push(` - ${field.name}: ${field.type}${req}${def}`);
|
|
27
|
-
}
|
|
28
|
-
if (table.policies.length > 0) {
|
|
29
|
-
lines.push(` policies: ${table.policies.join(', ')}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return lines.join('\n');
|
|
34
|
-
}
|
|
35
|
-
export const describeSchemaTool = {
|
|
36
|
-
name: 'describe_schema',
|
|
37
|
-
label: 'Describe schema',
|
|
38
|
-
description: 'Inspect the current Constructive database schema for this project: application tables with their fields (name, type, required, default). Read-only.',
|
|
39
|
-
promptSnippet: 'describe_schema: list the project database tables and their fields. Read-only — call before modifying schema.',
|
|
40
|
-
parameters: ParamSchema,
|
|
41
|
-
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
42
|
-
const resolved = await resolveProjectContext(ctx.cwd);
|
|
43
|
-
if (!resolved.context) {
|
|
44
|
-
return {
|
|
45
|
-
content: [{ type: 'text', text: resolved.reason }],
|
|
46
|
-
details: { tables: [] },
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
const { api, databaseId } = resolved.context;
|
|
50
|
-
const tablesResult = await api.table
|
|
51
|
-
.findMany({
|
|
52
|
-
select: { id: true, name: true, category: true },
|
|
53
|
-
where: {
|
|
54
|
-
databaseId: { equalTo: databaseId },
|
|
55
|
-
or: [{ category: { equalTo: 'APP' } }, { name: { equalTo: 'users' } }],
|
|
56
|
-
},
|
|
57
|
-
orderBy: ['NAME_ASC'],
|
|
58
|
-
})
|
|
59
|
-
.execute();
|
|
60
|
-
if (!tablesResult.ok) {
|
|
61
|
-
const message = tablesResult.errors.map((e) => e.message).join('; ');
|
|
62
|
-
return {
|
|
63
|
-
content: [{ type: 'text', text: `Failed to read schema: ${message}` }],
|
|
64
|
-
details: { tables: [] },
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const tableNodes = tablesResult.data.tables?.nodes ?? [];
|
|
68
|
-
const tableIds = tableNodes.map((t) => t.id);
|
|
69
|
-
const fieldsByTable = new Map();
|
|
70
|
-
const policiesByTable = new Map();
|
|
71
|
-
if (tableIds.length > 0) {
|
|
72
|
-
const [fieldsResult, policiesResult] = await Promise.all([
|
|
73
|
-
api.field
|
|
74
|
-
.findMany({
|
|
75
|
-
select: {
|
|
76
|
-
name: true,
|
|
77
|
-
type: true,
|
|
78
|
-
isRequired: true,
|
|
79
|
-
defaultValue: true,
|
|
80
|
-
tableId: true,
|
|
81
|
-
},
|
|
82
|
-
where: { tableId: { in: tableIds } },
|
|
83
|
-
})
|
|
84
|
-
.execute(),
|
|
85
|
-
api.policy
|
|
86
|
-
.findMany({
|
|
87
|
-
select: { tableId: true, policyType: true },
|
|
88
|
-
where: { tableId: { in: tableIds } },
|
|
89
|
-
})
|
|
90
|
-
.execute(),
|
|
91
|
-
]);
|
|
92
|
-
if (!fieldsResult.ok) {
|
|
93
|
-
const message = fieldsResult.errors.map((e) => e.message).join('; ');
|
|
94
|
-
return {
|
|
95
|
-
content: [{ type: 'text', text: `Failed to read fields: ${message}` }],
|
|
96
|
-
details: { tables: [] },
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
for (const field of fieldsResult.data.fields?.nodes ?? []) {
|
|
100
|
-
const list = fieldsByTable.get(field.tableId) ?? [];
|
|
101
|
-
list.push({
|
|
102
|
-
name: field.name,
|
|
103
|
-
type: fieldTypeToTypeName(field.type),
|
|
104
|
-
isRequired: Boolean(field.isRequired),
|
|
105
|
-
defaultValue: toDefaultValueString(field.defaultValue),
|
|
106
|
-
});
|
|
107
|
-
fieldsByTable.set(field.tableId, list);
|
|
108
|
-
}
|
|
109
|
-
// One policy spans CRUD as several rows; collapse to distinct types per table.
|
|
110
|
-
if (policiesResult.ok) {
|
|
111
|
-
for (const policy of policiesResult.data.policies?.nodes ?? []) {
|
|
112
|
-
if (isInternalPolicy(policy.policyType))
|
|
113
|
-
continue;
|
|
114
|
-
const list = policiesByTable.get(policy.tableId) ?? [];
|
|
115
|
-
if (!list.includes(policy.policyType))
|
|
116
|
-
list.push(policy.policyType);
|
|
117
|
-
policiesByTable.set(policy.tableId, list);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
const tables = tableNodes.map((table) => ({
|
|
122
|
-
name: table.name,
|
|
123
|
-
fields: fieldsByTable.get(table.id) ?? [],
|
|
124
|
-
policies: policiesByTable.get(table.id) ?? [],
|
|
125
|
-
}));
|
|
126
|
-
const details = { tables };
|
|
127
|
-
return {
|
|
128
|
-
content: [{ type: 'text', text: formatText(details) }],
|
|
129
|
-
details,
|
|
130
|
-
};
|
|
131
|
-
},
|
|
132
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { ToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
declare const ManageEntityTypesZod: z.ZodObject<{
|
|
4
|
-
action: z.ZodEnum<{
|
|
5
|
-
create: "create";
|
|
6
|
-
delete: "delete";
|
|
7
|
-
list: "list";
|
|
8
|
-
}>;
|
|
9
|
-
name: z.ZodOptional<z.ZodString>;
|
|
10
|
-
entity_type_id: z.ZodOptional<z.ZodString>;
|
|
11
|
-
description: z.ZodOptional<z.ZodString>;
|
|
12
|
-
prefix: z.ZodOptional<z.ZodString>;
|
|
13
|
-
parent_entity: z.ZodOptional<z.ZodString>;
|
|
14
|
-
is_visible: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
-
has_invites: z.ZodOptional<z.ZodBoolean>;
|
|
16
|
-
has_profiles: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
-
has_limits: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
-
has_levels: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
-
has_storage: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
-
}, z.core.$strip>;
|
|
21
|
-
declare const ManageEntityTypesSchema: import("typebox/type").TSchema;
|
|
22
|
-
export type EntityTypeSummary = {
|
|
23
|
-
id: string;
|
|
24
|
-
name: string | null;
|
|
25
|
-
description: string | null;
|
|
26
|
-
prefix: string | null;
|
|
27
|
-
parentEntity: string | null;
|
|
28
|
-
isVisible: boolean | null;
|
|
29
|
-
entityTableName: string | null;
|
|
30
|
-
installedModules: string[] | null;
|
|
31
|
-
};
|
|
32
|
-
export type ManageEntityTypesDetails = {
|
|
33
|
-
success: boolean;
|
|
34
|
-
message: string;
|
|
35
|
-
entityTypes?: EntityTypeSummary[];
|
|
36
|
-
};
|
|
37
|
-
export declare function defaultPrefix(name: string): string;
|
|
38
|
-
type Params = z.infer<typeof ManageEntityTypesZod>;
|
|
39
|
-
export declare function validateManageEntityTypes(params: Params): string | null;
|
|
40
|
-
export declare const manageEntityTypesTool: ToolDefinition<typeof ManageEntityTypesSchema, ManageEntityTypesDetails>;
|
|
41
|
-
export {};
|