appilot-mcp 0.1.1 → 0.3.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/LICENSE +15 -0
- package/README.md +102 -24
- package/dist/appilot-configurator.mcpb +0 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.js +171 -0
- package/dist/client.d.ts +122 -3
- package/dist/client.js +306 -31
- package/dist/config.d.ts +14 -0
- package/dist/config.js +19 -0
- package/dist/contract/bundleSnapshot.js +8 -1
- package/dist/contract/healthContract.d.ts +1 -1
- package/dist/contract/healthContract.js +100 -10
- package/dist/contract/types.d.ts +37 -1
- package/dist/index.bundle.js +4487 -16520
- package/dist/index.js +7 -0
- package/dist/inspect.d.ts +88 -0
- package/dist/inspect.js +384 -0
- package/dist/manifest.d.ts +14 -2
- package/dist/manifest.js +31 -9
- package/dist/public-marketplace/.claude-plugin/marketplace.json +20 -0
- package/dist/public-marketplace/README.md +23 -0
- package/dist/public-marketplace/plugins/app-configurator/.claude-plugin/plugin.json +43 -0
- package/dist/public-marketplace/plugins/app-configurator/README.md +328 -0
- package/dist/public-marketplace/plugins/app-configurator/dist/index.bundle.js +57370 -0
- package/dist/public-marketplace/plugins/app-configurator/skills/app-configurator/SKILL.md +267 -0
- package/dist/public-marketplace/plugins/app-configurator/skills/app-configurator/agents/openai.yaml +13 -0
- package/dist/redaction.d.ts +18 -3
- package/dist/redaction.js +27 -3
- package/dist/remote/consent.d.ts +30 -20
- package/dist/remote/consent.js +114 -82
- package/dist/remote/consentMessages.d.ts +65 -0
- package/dist/remote/consentMessages.js +199 -0
- package/dist/remote/handoff.d.ts +10 -0
- package/dist/remote/handoff.js +44 -0
- package/dist/remote/httpServer.js +28 -5
- package/dist/remote/oauth.d.ts +39 -6
- package/dist/remote/oauth.js +281 -36
- package/dist/scaffold.d.ts +110 -1
- package/dist/scaffold.js +474 -39
- package/dist/server.js +425 -38
- package/dist/soak.js +21 -1
- package/dist/templates.d.ts +62 -0
- package/dist/templates.js +255 -0
- package/dist/verify.js +18 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/examples/app.appilot.json +212 -0
- package/mcpb/manifest.json +117 -15
- package/package.json +5 -3
- package/skills/app-configurator/SKILL.md +136 -25
package/dist/soak.js
CHANGED
|
@@ -27,7 +27,16 @@ export async function soakSelectors(opts) {
|
|
|
27
27
|
note: 'Playwright is not installed. Run `pnpm add -D playwright && npx playwright install chromium` in the MCP package to enable live DOM soak.',
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
let browser;
|
|
31
|
+
try {
|
|
32
|
+
browser = await playwright.chromium.launch({ headless: true });
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return {
|
|
36
|
+
available: false,
|
|
37
|
+
note: `A browser could not be launched: ${err instanceof Error ? err.message : String(err)}. Run npx playwright install chromium.`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
31
40
|
try {
|
|
32
41
|
const context = await browser.newContext(opts.storageStatePath ? { storageState: opts.storageStatePath } : undefined);
|
|
33
42
|
const page = await context.newPage();
|
|
@@ -45,6 +54,17 @@ export async function soakSelectors(opts) {
|
|
|
45
54
|
}
|
|
46
55
|
return { available: true, url: opts.url, selectors };
|
|
47
56
|
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// A soak that cannot reach the page is a reportable outcome, not a crash:
|
|
59
|
+
// the caller asked whether the selectors resolve and the answer is "the
|
|
60
|
+
// page did not load", which is actionable on its own.
|
|
61
|
+
return {
|
|
62
|
+
available: true,
|
|
63
|
+
url: opts.url,
|
|
64
|
+
note: `The page could not be loaded: ${err instanceof Error ? err.message : String(err)}`,
|
|
65
|
+
selectors: [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
48
68
|
finally {
|
|
49
69
|
await browser.close();
|
|
50
70
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid skeletons for each configurable entity.
|
|
3
|
+
*
|
|
4
|
+
* Pure: no SDK, no network. The instance's own vocabularies are passed in from
|
|
5
|
+
* `capabilities` rather than restated here, so a template can never advertise an
|
|
6
|
+
* enum value the write path rejects.
|
|
7
|
+
*
|
|
8
|
+
* Why this exists. Two authoring failures repeat, and both are answerable
|
|
9
|
+
* before the write rather than after it. A container field the entity kind
|
|
10
|
+
* declares is left out, and the import answers with a path the author then has
|
|
11
|
+
* to decode. And an enum value is guessed, because a brand-new app exports empty
|
|
12
|
+
* arrays and there was no example to copy: an agent reading a live page writes
|
|
13
|
+
* `locator_type: "css"`, which is not one of the kinds.
|
|
14
|
+
*/
|
|
15
|
+
import type { ConfigEntityKind } from './client.js';
|
|
16
|
+
/**
|
|
17
|
+
* The closed enums an instance publishes on `capabilities.entityVocabularies`.
|
|
18
|
+
*
|
|
19
|
+
* The group keys are the instance's own, and they are the PLURAL table-ish names
|
|
20
|
+
* (`controls`, `knowledge_content`, `action_plans`), not the singular entity
|
|
21
|
+
* kinds this module is keyed by. Getting that wrong is silent: every lookup
|
|
22
|
+
* misses, the template offers no vocabulary, and the agent guesses exactly the
|
|
23
|
+
* value the write path rejects, which is the failure this module exists to
|
|
24
|
+
* prevent. The `pick` calls below are the one place the two namings meet, and
|
|
25
|
+
* `test/authoring.test.ts` pins them against what a live instance answers.
|
|
26
|
+
*/
|
|
27
|
+
export interface EntityVocabularies {
|
|
28
|
+
controls?: {
|
|
29
|
+
locator_type?: string[];
|
|
30
|
+
scope?: string[];
|
|
31
|
+
};
|
|
32
|
+
knowledge_content?: {
|
|
33
|
+
scope?: string[];
|
|
34
|
+
visibility?: string[];
|
|
35
|
+
};
|
|
36
|
+
action_plans?: {
|
|
37
|
+
marker_kinds?: string[];
|
|
38
|
+
};
|
|
39
|
+
zones?: {
|
|
40
|
+
landmark_role?: string[];
|
|
41
|
+
};
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
export interface EntityTemplate {
|
|
45
|
+
kind: ConfigEntityKind;
|
|
46
|
+
/** The request body, with every container field present. */
|
|
47
|
+
body: Record<string, unknown>;
|
|
48
|
+
/** Fields the write path refuses without. */
|
|
49
|
+
required: string[];
|
|
50
|
+
/** Closed enums that apply to this kind, as THIS instance declares them. */
|
|
51
|
+
vocabularies: Record<string, string[]>;
|
|
52
|
+
/** What to get right, in the order it bites. */
|
|
53
|
+
notes: string[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build the skeleton for one kind.
|
|
57
|
+
*
|
|
58
|
+
* `appId` is filled in where the entity is app-scoped, because leaving it null
|
|
59
|
+
* produces a body that looks complete and is refused.
|
|
60
|
+
*/
|
|
61
|
+
export declare function entityTemplate(kind: ConfigEntityKind, appId: number | null, vocab?: EntityVocabularies): EntityTemplate;
|
|
62
|
+
export declare const TEMPLATE_KINDS: ConfigEntityKind[];
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid skeletons for each configurable entity.
|
|
3
|
+
*
|
|
4
|
+
* Pure: no SDK, no network. The instance's own vocabularies are passed in from
|
|
5
|
+
* `capabilities` rather than restated here, so a template can never advertise an
|
|
6
|
+
* enum value the write path rejects.
|
|
7
|
+
*
|
|
8
|
+
* Why this exists. Two authoring failures repeat, and both are answerable
|
|
9
|
+
* before the write rather than after it. A container field the entity kind
|
|
10
|
+
* declares is left out, and the import answers with a path the author then has
|
|
11
|
+
* to decode. And an enum value is guessed, because a brand-new app exports empty
|
|
12
|
+
* arrays and there was no example to copy: an agent reading a live page writes
|
|
13
|
+
* `locator_type: "css"`, which is not one of the kinds.
|
|
14
|
+
*/
|
|
15
|
+
function pick(vocab, path) {
|
|
16
|
+
const group = vocab[path[0]];
|
|
17
|
+
const values = group?.[path[1]];
|
|
18
|
+
return Array.isArray(values) && values.every(v => typeof v === 'string') ? values : null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build the skeleton for one kind.
|
|
22
|
+
*
|
|
23
|
+
* `appId` is filled in where the entity is app-scoped, because leaving it null
|
|
24
|
+
* produces a body that looks complete and is refused.
|
|
25
|
+
*/
|
|
26
|
+
export function entityTemplate(kind, appId, vocab = {}) {
|
|
27
|
+
const locatorTypes = pick(vocab, ['controls', 'locator_type']);
|
|
28
|
+
const controlScopes = pick(vocab, ['controls', 'scope']);
|
|
29
|
+
const knowledgeScopes = pick(vocab, ['knowledge_content', 'scope']);
|
|
30
|
+
const knowledgeVisibility = pick(vocab, ['knowledge_content', 'visibility']);
|
|
31
|
+
const markerKinds = pick(vocab, ['action_plans', 'marker_kinds']);
|
|
32
|
+
const landmarkRoles = pick(vocab, ['zones', 'landmark_role']);
|
|
33
|
+
switch (kind) {
|
|
34
|
+
case 'view':
|
|
35
|
+
return {
|
|
36
|
+
kind,
|
|
37
|
+
body: {
|
|
38
|
+
app_id: appId,
|
|
39
|
+
domain_id: null,
|
|
40
|
+
name: '',
|
|
41
|
+
path: '/',
|
|
42
|
+
slug: '',
|
|
43
|
+
description: '',
|
|
44
|
+
entry_path: null,
|
|
45
|
+
name_i18n: {},
|
|
46
|
+
description_i18n: {},
|
|
47
|
+
rules: [],
|
|
48
|
+
},
|
|
49
|
+
required: ['app_id', 'domain_id', 'name', 'path'],
|
|
50
|
+
vocabularies: {},
|
|
51
|
+
notes: [
|
|
52
|
+
'domain_id names one registered hostname. Read the app\'s domains first; a view on an unregistered hostname can never resolve a tenant.',
|
|
53
|
+
'path is the route as the browser shows it, and it is half the view\'s identity in a bundle.',
|
|
54
|
+
'entry_path, when set, must be path-absolute.',
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
case 'control':
|
|
58
|
+
return {
|
|
59
|
+
kind,
|
|
60
|
+
body: {
|
|
61
|
+
app_id: appId,
|
|
62
|
+
semantic_id: '',
|
|
63
|
+
locator: '',
|
|
64
|
+
locator_type: locatorTypes?.[0] ?? '',
|
|
65
|
+
label: '',
|
|
66
|
+
description: '',
|
|
67
|
+
llm_hint: null,
|
|
68
|
+
label_i18n: {},
|
|
69
|
+
description_i18n: {},
|
|
70
|
+
view_path: null,
|
|
71
|
+
scope: controlScopes?.[0] ?? '',
|
|
72
|
+
prerequisites: [],
|
|
73
|
+
form_id: null,
|
|
74
|
+
form_order: null,
|
|
75
|
+
},
|
|
76
|
+
required: ['app_id', 'semantic_id', 'locator', 'locator_type'],
|
|
77
|
+
vocabularies: {
|
|
78
|
+
...(locatorTypes ? { locator_type: locatorTypes } : {}),
|
|
79
|
+
...(controlScopes ? { scope: controlScopes } : {}),
|
|
80
|
+
},
|
|
81
|
+
notes: [
|
|
82
|
+
'locator_type is a closed set and `css` is not in it. A CSS selector list belongs under class_text.',
|
|
83
|
+
'A stable locator is a data attribute, an aria role or a placeholder. An auto-generated id or an :nth-child position passes every static check and is gone on the next render.',
|
|
84
|
+
'scope=global takes no view_path.',
|
|
85
|
+
'A control becomes a FIELD of a form by carrying form_id and form_order, together, on its own row. The form\'s required_field_ids does not create that membership. Create the form first, then patch each field control with the form\'s row id and its fill order counting from 0, or a plan that fills the form is refused with "field does not belong to form".',
|
|
86
|
+
'The control that OPENS a form is the form\'s entry_control and must not also be one of its fields.',
|
|
87
|
+
'semantic_id is a short English slug that survives a label change, not a slugified UI label.',
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
case 'form':
|
|
91
|
+
return {
|
|
92
|
+
kind,
|
|
93
|
+
body: {
|
|
94
|
+
app_id: appId,
|
|
95
|
+
semantic_id: '',
|
|
96
|
+
label: '',
|
|
97
|
+
domain_id: null,
|
|
98
|
+
description: '',
|
|
99
|
+
llm_hint: null,
|
|
100
|
+
label_i18n: {},
|
|
101
|
+
description_i18n: {},
|
|
102
|
+
view_paths: [],
|
|
103
|
+
required_field_ids: [],
|
|
104
|
+
submit_control_id: null,
|
|
105
|
+
entry_control_id: null,
|
|
106
|
+
visibility_rules: null,
|
|
107
|
+
field_defaults: null,
|
|
108
|
+
prerequisites: [],
|
|
109
|
+
is_active: true,
|
|
110
|
+
},
|
|
111
|
+
required: ['app_id', 'semantic_id', 'label'],
|
|
112
|
+
vocabularies: {},
|
|
113
|
+
notes: [
|
|
114
|
+
'The controls a form names must exist first: create them, then the form.',
|
|
115
|
+
'entry_control is what OPENS the form, so it cannot also be one of required_field_ids. The three roles are distinct controls: one opens, several are filled, one submits.',
|
|
116
|
+
'view_paths cannot be empty: a form nobody can reach from a view is refused.',
|
|
117
|
+
'required_field_ids records which of the form\'s EXISTING fields are mandatory, and it does not create membership. So the order is: create the form with an empty required_field_ids, patch each field control with form_id and form_order, then patch the form with required_field_ids.',
|
|
118
|
+
'A form without an entry control and a submit control cannot carry a create flow, and the health contract will say so.',
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
case 'tool':
|
|
122
|
+
return {
|
|
123
|
+
kind,
|
|
124
|
+
body: {
|
|
125
|
+
app_id: appId,
|
|
126
|
+
tool_name: '',
|
|
127
|
+
title: '',
|
|
128
|
+
description: '',
|
|
129
|
+
view_id: null,
|
|
130
|
+
parameters: {},
|
|
131
|
+
runtime_spec: {},
|
|
132
|
+
title_i18n: {},
|
|
133
|
+
description_i18n: {},
|
|
134
|
+
},
|
|
135
|
+
required: ['app_id', 'tool_name', 'title', 'description'],
|
|
136
|
+
vocabularies: {},
|
|
137
|
+
notes: [
|
|
138
|
+
'description is what the agent reads to decide whether to call this at all. Write the trigger, not the implementation.',
|
|
139
|
+
'runtime_spec is a discriminated union on `kind`. For `http_proxy` it needs `method` and `path_template`, a path on the host application origin; an absolute URL is rejected, because a server-side call is proxied to the app\'s own origin and nowhere else. For `client_action` the page owns the behaviour and registers it at runtime.',
|
|
140
|
+
'A server-side tool stores its credential through auth_secret and auth_header_name on this same body. That value is write-only and never comes back on a read.',
|
|
141
|
+
'auth_header_name defaults to Authorization and cannot be a hop-by-hop header.',
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
case 'zone':
|
|
145
|
+
return {
|
|
146
|
+
kind,
|
|
147
|
+
body: {
|
|
148
|
+
domain_id: null,
|
|
149
|
+
app_id: appId,
|
|
150
|
+
semantic_id: '',
|
|
151
|
+
label: '',
|
|
152
|
+
label_i18n: {},
|
|
153
|
+
description: '',
|
|
154
|
+
description_i18n: {},
|
|
155
|
+
selector: '',
|
|
156
|
+
landmark_role: null,
|
|
157
|
+
parent_zone_id: null,
|
|
158
|
+
view_paths: [],
|
|
159
|
+
},
|
|
160
|
+
required: ['domain_id', 'semantic_id', 'label', 'selector'],
|
|
161
|
+
vocabularies: landmarkRoles ? { landmark_role: landmarkRoles } : {},
|
|
162
|
+
notes: [
|
|
163
|
+
'A zone is a passive region the agent refers to. If it can be clicked, it is a control.',
|
|
164
|
+
'Zones are domain-scoped, so domain_id is required and app_id only narrows it.',
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
case 'action_plan':
|
|
168
|
+
return {
|
|
169
|
+
kind,
|
|
170
|
+
body: {
|
|
171
|
+
app_id: appId,
|
|
172
|
+
semantic_id: '',
|
|
173
|
+
name: '',
|
|
174
|
+
description: '',
|
|
175
|
+
sections: [],
|
|
176
|
+
form_values: {},
|
|
177
|
+
step_narratives_i18n: {},
|
|
178
|
+
is_active: true,
|
|
179
|
+
},
|
|
180
|
+
required: ['app_id', 'semantic_id'],
|
|
181
|
+
vocabularies: markerKinds ? { marker_kinds: markerKinds } : {},
|
|
182
|
+
notes: [
|
|
183
|
+
'description is the agent-facing trigger, written as "Use when the user wants to ...", and localized. A weak description means the plan is never adopted.',
|
|
184
|
+
'Each step carries exactly one executable marker. Values never go inside a marker; they ride form_values keyed by form.',
|
|
185
|
+
'A create flow must enter a value and submit. A plan that only opens or focuses an element does nothing, and that is the most common defect there is.',
|
|
186
|
+
'Validate the sections with validate_action_plan before writing them.',
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
case 'knowledge':
|
|
190
|
+
return {
|
|
191
|
+
kind,
|
|
192
|
+
body: {
|
|
193
|
+
application_id: appId,
|
|
194
|
+
title: '',
|
|
195
|
+
description: '',
|
|
196
|
+
general_info: '',
|
|
197
|
+
notes: '',
|
|
198
|
+
url_pattern: null,
|
|
199
|
+
domain: null,
|
|
200
|
+
language: 'en',
|
|
201
|
+
visibility: knowledgeVisibility?.[0] ?? '',
|
|
202
|
+
is_active: true,
|
|
203
|
+
},
|
|
204
|
+
required: ['title'],
|
|
205
|
+
vocabularies: {
|
|
206
|
+
...(knowledgeScopes ? { scope: knowledgeScopes } : {}),
|
|
207
|
+
...(knowledgeVisibility ? { visibility: knowledgeVisibility } : {}),
|
|
208
|
+
},
|
|
209
|
+
notes: [
|
|
210
|
+
'scope is derived from url_pattern, so narrow the article by giving it a pattern rather than by naming a scope.',
|
|
211
|
+
'Knowledge carries meaning: workflows, rules, terminology. A step-by-step article is a procedure in the wrong place, and it competes with the action plan that owns it.',
|
|
212
|
+
'is_active defaults to false on this endpoint. Set it explicitly when the article should be live.',
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
case 'session_template':
|
|
216
|
+
return {
|
|
217
|
+
kind,
|
|
218
|
+
body: {
|
|
219
|
+
app_id: appId,
|
|
220
|
+
semantic_id: '',
|
|
221
|
+
name: '',
|
|
222
|
+
description: '',
|
|
223
|
+
mission_prompt: '',
|
|
224
|
+
outcome_schema: { type: 'object', properties: {}, required: [] },
|
|
225
|
+
variables: [],
|
|
226
|
+
allowed_tools: [],
|
|
227
|
+
preflight: [],
|
|
228
|
+
presentation: null,
|
|
229
|
+
kb_doc_ids: null,
|
|
230
|
+
max_turns: null,
|
|
231
|
+
is_active: true,
|
|
232
|
+
name_i18n: {},
|
|
233
|
+
description_i18n: {},
|
|
234
|
+
mission_prompt_i18n: {},
|
|
235
|
+
},
|
|
236
|
+
required: ['app_id', 'semantic_id', 'name', 'mission_prompt', 'outcome_schema'],
|
|
237
|
+
vocabularies: {},
|
|
238
|
+
notes: [
|
|
239
|
+
'A preflight entry names a tool that must already hold a stored credential. A tool that runs in the page cannot be called before the session exists, and the write is refused. Create the tool with its credential first.',
|
|
240
|
+
'A field the reviewer must have belongs in outcome_schema.required. An optional field is a field the model renames or drops.',
|
|
241
|
+
'If the model must do something for the session to be valid, it is a precondition the runtime enforces through preflight, not a sentence in the mission.',
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
export const TEMPLATE_KINDS = [
|
|
247
|
+
'view',
|
|
248
|
+
'control',
|
|
249
|
+
'form',
|
|
250
|
+
'tool',
|
|
251
|
+
'zone',
|
|
252
|
+
'action_plan',
|
|
253
|
+
'knowledge',
|
|
254
|
+
'session_template',
|
|
255
|
+
];
|
package/dist/verify.js
CHANGED
|
@@ -61,7 +61,15 @@ async function checkWidgetBoots(url, storageStatePath) {
|
|
|
61
61
|
check('widget-boot', 'Widget boots on the page', 'skip', 'Playwright is not installed.', 'npm i -D playwright && npx playwright install chromium, then re-run to check the browser half.'),
|
|
62
62
|
];
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
let browser;
|
|
65
|
+
try {
|
|
66
|
+
browser = await chromium.launch();
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return [
|
|
70
|
+
check('widget-boot', 'Widget boots on the page', 'skip', `A browser could not be launched: ${error instanceof Error ? error.message : String(error)}`, 'Run npx playwright install chromium, then re-run to check the browser half.'),
|
|
71
|
+
];
|
|
72
|
+
}
|
|
65
73
|
try {
|
|
66
74
|
const context = await browser.newContext(storageStatePath ? { storageState: storageStatePath } : {});
|
|
67
75
|
const page = await context.newPage();
|
|
@@ -93,6 +101,15 @@ async function checkWidgetBoots(url, storageStatePath) {
|
|
|
93
101
|
}
|
|
94
102
|
return checks;
|
|
95
103
|
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
// The browser half must not take the run down with it. Navigation is the
|
|
106
|
+
// most likely thing to fail here (the page is not up yet, DNS does not
|
|
107
|
+
// resolve, TLS is wrong), and that is exactly the moment the caller needs
|
|
108
|
+
// the four network checks that already ran.
|
|
109
|
+
return [
|
|
110
|
+
check('widget-boot', 'Widget boots on the page', 'fail', `The page could not be loaded in a browser: ${error instanceof Error ? error.message : String(error)}`, 'Confirm the URL is reachable from this machine, then re-run. The network checks above still apply.'),
|
|
111
|
+
];
|
|
112
|
+
}
|
|
96
113
|
finally {
|
|
97
114
|
await browser.close();
|
|
98
115
|
}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "appilot.app-manifest",
|
|
3
|
+
"formatVersion": "1.0",
|
|
4
|
+
"app": {
|
|
5
|
+
"name": "Acme Booking",
|
|
6
|
+
"description": "The example manifest that ships with appilot-mcp. Copy it, change the names, keep the shape."
|
|
7
|
+
},
|
|
8
|
+
"domains": [
|
|
9
|
+
{ "domain": "app.acme.com", "default_language": "en", "configured_languages": ["en", "de"] }
|
|
10
|
+
],
|
|
11
|
+
"widgetKeys": [
|
|
12
|
+
{ "name": "production", "allowedDomains": ["app.acme.com"] }
|
|
13
|
+
],
|
|
14
|
+
"config": {
|
|
15
|
+
"kind": "appilot.config-bundle",
|
|
16
|
+
"formatVersion": "1.0",
|
|
17
|
+
"exportedAt": "2026-09-07T00:00:00.000Z",
|
|
18
|
+
"source": { "appId": 0, "appSlug": "acme-booking", "orgId": null },
|
|
19
|
+
"locales": ["en", "de"],
|
|
20
|
+
"entities": {
|
|
21
|
+
"views": [
|
|
22
|
+
{
|
|
23
|
+
"domain": "app.acme.com",
|
|
24
|
+
"path": "bookings",
|
|
25
|
+
"slug": "bookings",
|
|
26
|
+
"entry_path": "/bookings",
|
|
27
|
+
"is_active": true,
|
|
28
|
+
"source_locale": "en",
|
|
29
|
+
"translations": {
|
|
30
|
+
"en": { "name": "Bookings", "description": "The list of bookings and the form that creates one." },
|
|
31
|
+
"de": { "name": "Buchungen", "description": "Die Liste der Buchungen und das Formular, das eine anlegt." }
|
|
32
|
+
},
|
|
33
|
+
"detection_rules": [
|
|
34
|
+
{ "priority": 10, "type": "path_prefix", "config": { "value": "/bookings" }, "is_active": true }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"controls": [
|
|
39
|
+
{
|
|
40
|
+
"semantic_id": "btn-create-booking",
|
|
41
|
+
"locator": "[data-testid=\"new-booking\"]",
|
|
42
|
+
"locator_type": "class_text",
|
|
43
|
+
"llm_hint": "Opens the booking form.",
|
|
44
|
+
"view_path": "bookings",
|
|
45
|
+
"scope": "view",
|
|
46
|
+
"prerequisites": null,
|
|
47
|
+
"is_active": true,
|
|
48
|
+
"form": null,
|
|
49
|
+
"source_locale": "en",
|
|
50
|
+
"translations": {
|
|
51
|
+
"en": { "label": "New booking", "description": "Opens the form that creates a booking." },
|
|
52
|
+
"de": { "label": "Neue Buchung", "description": "Öffnet das Formular, das eine Buchung anlegt." }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"semantic_id": "field-booking-date",
|
|
57
|
+
"locator": "booking-date",
|
|
58
|
+
"locator_type": "id",
|
|
59
|
+
"llm_hint": "The day the booking is for.",
|
|
60
|
+
"view_path": "bookings",
|
|
61
|
+
"scope": "view",
|
|
62
|
+
"prerequisites": null,
|
|
63
|
+
"is_active": true,
|
|
64
|
+
"form": { "form": "form-create-booking", "order": 1 },
|
|
65
|
+
"source_locale": "en",
|
|
66
|
+
"translations": {
|
|
67
|
+
"en": { "label": "Date", "description": "The day the booking is for." },
|
|
68
|
+
"de": { "label": "Datum", "description": "Der Tag, für den gebucht wird." }
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"semantic_id": "submit-create-booking",
|
|
73
|
+
"locator": "[data-testid=\"save-booking\"]",
|
|
74
|
+
"locator_type": "class_text",
|
|
75
|
+
"llm_hint": "Saves the booking.",
|
|
76
|
+
"view_path": "bookings",
|
|
77
|
+
"scope": "view",
|
|
78
|
+
"prerequisites": null,
|
|
79
|
+
"is_active": true,
|
|
80
|
+
"form": null,
|
|
81
|
+
"source_locale": "en",
|
|
82
|
+
"translations": {
|
|
83
|
+
"en": { "label": "Save", "description": "Saves the booking." },
|
|
84
|
+
"de": { "label": "Speichern", "description": "Speichert die Buchung." }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"forms": [
|
|
89
|
+
{
|
|
90
|
+
"semantic_id": "form-create-booking",
|
|
91
|
+
"domain": "app.acme.com",
|
|
92
|
+
"llm_hint": "Creates one booking.",
|
|
93
|
+
"view_paths": ["bookings"],
|
|
94
|
+
"required_fields": ["field-booking-date"],
|
|
95
|
+
"submit_control": "submit-create-booking",
|
|
96
|
+
"entry_control": "btn-create-booking",
|
|
97
|
+
"visibility_rules": null,
|
|
98
|
+
"field_defaults": null,
|
|
99
|
+
"prerequisites": null,
|
|
100
|
+
"is_active": true,
|
|
101
|
+
"source_locale": "en",
|
|
102
|
+
"translations": {
|
|
103
|
+
"en": { "label": "New booking", "description": "Creates one booking." },
|
|
104
|
+
"de": { "label": "Neue Buchung", "description": "Legt eine Buchung an." }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"tools": [
|
|
109
|
+
{
|
|
110
|
+
"tool_name": "create_booking",
|
|
111
|
+
"view": null,
|
|
112
|
+
"parameters": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": { "date": { "type": "string", "description": "ISO date the booking is for." } },
|
|
115
|
+
"required": ["date"]
|
|
116
|
+
},
|
|
117
|
+
"runtime_spec": {
|
|
118
|
+
"kind": "http_proxy",
|
|
119
|
+
"method": "POST",
|
|
120
|
+
"path_template": "/api/bookings",
|
|
121
|
+
"body_template": { "date": "{{date}}" }
|
|
122
|
+
},
|
|
123
|
+
"auth_header_name": "Authorization",
|
|
124
|
+
"source_locale": "en",
|
|
125
|
+
"translations": {
|
|
126
|
+
"en": { "title": "Create a booking", "description": "Use when the user wants to create a booking." },
|
|
127
|
+
"de": { "title": "Buchung anlegen", "description": "Verwenden, wenn die Person eine Buchung anlegen möchte." }
|
|
128
|
+
},
|
|
129
|
+
"parameter_descriptions": {
|
|
130
|
+
"en": { "date": "ISO date the booking is for." },
|
|
131
|
+
"de": { "date": "ISO-Datum, für das gebucht wird." }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"zones": [],
|
|
136
|
+
"action_plans": [
|
|
137
|
+
{
|
|
138
|
+
"semantic_id": "plan-create-booking",
|
|
139
|
+
"sections": [
|
|
140
|
+
{
|
|
141
|
+
"view_path": "bookings",
|
|
142
|
+
"steps": [
|
|
143
|
+
"Open the form with [New booking]({{click:btn-create-booking}}).",
|
|
144
|
+
"Fill in [the booking form]({{form:form-create-booking}}).",
|
|
145
|
+
"Submit it with [Save]({{click:submit-create-booking}})."
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"form_values": { "form-create-booking": { "fields": [] } },
|
|
150
|
+
"provenance": "registry",
|
|
151
|
+
"is_active": true,
|
|
152
|
+
"source_locale": "en",
|
|
153
|
+
"translations": {
|
|
154
|
+
"en": {
|
|
155
|
+
"name": "Create a booking",
|
|
156
|
+
"description": "Use when the user wants to create a booking.",
|
|
157
|
+
"step_narratives": null
|
|
158
|
+
},
|
|
159
|
+
"de": {
|
|
160
|
+
"name": "Buchung anlegen",
|
|
161
|
+
"description": "Verwenden, wenn die Person eine Buchung anlegen möchte.",
|
|
162
|
+
"step_narratives": [[
|
|
163
|
+
"Öffne das Formular mit [Neue Buchung]({{click:btn-create-booking}}).",
|
|
164
|
+
"Fülle [das Buchungsformular]({{form:form-create-booking}}) aus.",
|
|
165
|
+
"Sende es mit [Speichern]({{click:submit-create-booking}}) ab."
|
|
166
|
+
]]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
"knowledge_content": [
|
|
172
|
+
{
|
|
173
|
+
"group": "booking-rules",
|
|
174
|
+
"scope": "app_global",
|
|
175
|
+
"url_pattern": null,
|
|
176
|
+
"domain": "app.acme.com",
|
|
177
|
+
"visibility": "internal",
|
|
178
|
+
"source": "manual",
|
|
179
|
+
"is_active": true,
|
|
180
|
+
"source_locale": "en",
|
|
181
|
+
"bodies": [
|
|
182
|
+
{
|
|
183
|
+
"language": "en",
|
|
184
|
+
"title": "What a booking is",
|
|
185
|
+
"description": "The rules and vocabulary behind a booking.",
|
|
186
|
+
"notes": null,
|
|
187
|
+
"general_info": "A booking reserves one resource for one day. Bookings are made by the account holder and cannot start in the past. A booking that has already started can be cancelled but not moved.\n\nThe procedure for creating one lives in the plan-create-booking action plan, not here."
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"language": "de",
|
|
191
|
+
"title": "Was eine Buchung ist",
|
|
192
|
+
"description": "Die Regeln und Begriffe hinter einer Buchung.",
|
|
193
|
+
"notes": null,
|
|
194
|
+
"general_info": "Eine Buchung reserviert eine Ressource für einen Tag. Buchungen legt die Kontoinhaberin an, und sie können nicht in der Vergangenheit beginnen. Eine bereits begonnene Buchung lässt sich stornieren, aber nicht verschieben.\n\nDas Vorgehen zum Anlegen steht im Action Plan plan-create-booking, nicht hier."
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
"views": [{ "domain": "app.acme.com", "path": "bookings", "priority": 10 }]
|
|
198
|
+
}
|
|
199
|
+
],
|
|
200
|
+
"session_templates": []
|
|
201
|
+
},
|
|
202
|
+
"secretRefs": [
|
|
203
|
+
{
|
|
204
|
+
"entity": "tools",
|
|
205
|
+
"id": "create_booking",
|
|
206
|
+
"secret": "tool_auth:create_booking",
|
|
207
|
+
"status": "needs-reconnect",
|
|
208
|
+
"action": "Open the tool in the Backoffice and paste the credential. Secrets never travel in a bundle."
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
}
|