@frontera-sdk/cli 1.52.39 → 1.52.40
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 +9 -0
- package/package.json +6 -4
- package/src/adopt.ts +6 -2
- package/src/api/apps-api.ts +86 -4
- package/src/api/blueprint-authoring-api.ts +35 -11
- package/src/api/dataset-api.ts +29 -0
- package/src/api/governed-action-api.ts +90 -2
- package/src/api/platform-api.ts +87 -2
- package/src/api/workflow-api.ts +33 -2
- package/src/app-contract.ts +156 -0
- package/src/app-lock.ts +125 -0
- package/src/args.ts +14 -3
- package/src/auth-verify.ts +10 -0
- package/src/automation-template.ts +12 -1
- package/src/blueprint/action-rules.ts +516 -0
- package/src/blueprint/action-semantic-fit.ts +205 -0
- package/src/blueprint/compile.ts +51 -2
- package/src/blueprint/diff.ts +34 -2
- package/src/blueprint/metric-query.ts +215 -0
- package/src/blueprint/metric-rules.ts +122 -0
- package/src/blueprint/object-sets.ts +116 -0
- package/src/blueprint/ontology-edit-plan.ts +48 -4
- package/src/blueprint/projection.ts +24 -1
- package/src/blueprint/scaffold.ts +84 -42
- package/src/commands/action/available.ts +1 -1
- package/src/commands/action/criteria-refusal.ts +84 -0
- package/src/commands/action/decide.ts +5 -2
- package/src/commands/action/deploy.ts +13 -4
- package/src/commands/action/list.ts +134 -19
- package/src/commands/action/requests.ts +34 -6
- package/src/commands/action/submit.ts +28 -13
- package/src/commands/agent/index-commands.ts +69 -7
- package/src/commands/agent/resolve.ts +2 -0
- package/src/commands/app/checks.ts +44 -0
- package/src/commands/app/deploy.ts +243 -60
- package/src/commands/app/deployments.ts +97 -0
- package/src/commands/app/dev.ts +103 -11
- package/src/commands/app/init.ts +36 -3
- package/src/commands/app/promote.ts +45 -19
- package/src/commands/app/pull.ts +3 -1
- package/src/commands/app/rollback.ts +46 -0
- package/src/commands/app/sdk.ts +54 -8
- package/src/commands/app/unpin.ts +27 -0
- package/src/commands/app/versions.ts +1 -1
- package/src/commands/automation/bindings.ts +4 -2
- package/src/commands/automation/build-entry.ts +4 -1
- package/src/commands/automation/index-commands.ts +3 -1
- package/src/commands/automation/pull.ts +6 -1
- package/src/commands/automation/run.ts +34 -9
- package/src/commands/blueprint/bind.ts +2 -1
- package/src/commands/blueprint/declarative.ts +331 -31
- package/src/commands/blueprint/get.ts +6 -2
- package/src/commands/blueprint/grants.ts +64 -10
- package/src/commands/blueprint/metric.ts +108 -0
- package/src/commands/blueprint/object-sets.ts +71 -0
- package/src/commands/blueprint/query.ts +155 -31
- package/src/commands/dataset/index-commands.ts +156 -7
- package/src/commands/knowledge/index-commands.ts +122 -20
- package/src/commands/local/check.ts +657 -44
- package/src/commands/local/data.ts +33 -12
- package/src/commands/local/dev.ts +875 -88
- package/src/commands/local/docs-mcp.ts +10 -18
- package/src/commands/local/index-commands.ts +300 -63
- package/src/commands/local/init.ts +18 -5
- package/src/commands/local/key.ts +0 -3
- package/src/commands/local/providers.ts +0 -7
- package/src/commands/local/remove.ts +116 -0
- package/src/commands/local/schema.ts +3 -4
- package/src/commands/local/sdk-refresh.ts +59 -0
- package/src/commands/local/snapshot.ts +100 -0
- package/src/commands/local/sync-request.ts +320 -13
- package/src/commands/local/sync.ts +102 -5
- package/src/commands/mcp/serve.ts +34 -0
- package/src/commands/registry.ts +52 -8
- package/src/commands/types.ts +29 -0
- package/src/commands/workflow/archive.ts +46 -0
- package/src/commands/workflow/get.ts +2 -1
- package/src/commands/workflow/index-commands.ts +5 -0
- package/src/commands/workflow/push.ts +49 -2
- package/src/commands/workflow/run.ts +65 -7
- package/src/commands/workflow/runs.ts +9 -6
- package/src/commands/workflow/webhook-test.ts +166 -0
- package/src/config.ts +43 -6
- package/src/dev-broker.ts +52 -0
- package/src/exit.ts +103 -4
- package/src/flag-help.ts +77 -17
- package/src/forge/touches.ts +15 -12
- package/src/function-outside-imports.ts +89 -0
- package/src/harness.ts +42 -24
- package/src/heal.ts +112 -1
- package/src/local/fix-words.ts +12 -0
- package/src/local/function-bundle-recovery.ts +775 -0
- package/src/local/local-actions.ts +103 -10
- package/src/local/local-agent-action-grants.ts +210 -0
- package/src/local/local-agent-checks.ts +232 -0
- package/src/local/local-agent-grants.ts +29 -7
- package/src/local/local-agent-lean-plugins.ts +68 -0
- package/src/local/local-agent-missing-tools.ts +68 -0
- package/src/local/local-agent-name.ts +27 -0
- package/src/local/local-app-code.ts +191 -0
- package/src/local/local-app-dev-files.ts +186 -0
- package/src/local/local-app-dev-session.ts +88 -0
- package/src/local/local-app-lock-install.ts +114 -0
- package/src/local/local-app-pulled.ts +167 -0
- package/src/local/local-app-restore.ts +111 -0
- package/src/local/local-app-runtime.ts +68 -0
- package/src/local/local-app-sdk-paths.ts +149 -0
- package/src/local/local-app-sdk.ts +509 -0
- package/src/local/local-applied.ts +211 -21
- package/src/local/local-apply-history.ts +84 -0
- package/src/local/local-apply-summary.ts +86 -0
- package/src/local/local-apply-watchdog.ts +95 -0
- package/src/local/local-apps.ts +180 -7
- package/src/local/local-blueprint-errors.ts +198 -0
- package/src/local/local-blueprint-outline.ts +31 -12
- package/src/local/local-blueprint-schemas.ts +582 -0
- package/src/local/local-build-inputs.ts +48 -0
- package/src/local/local-build-summary.ts +495 -28
- package/src/local/local-case-evidence.ts +120 -0
- package/src/local/local-case-not-run.ts +153 -0
- package/src/local/local-chat-apps.ts +93 -16
- package/src/local/local-check-inherited.ts +83 -0
- package/src/local/local-cities.ts +89 -0
- package/src/local/local-component-health.ts +119 -0
- package/src/local/local-control.ts +34 -7
- package/src/local/local-db-snapshot.ts +348 -0
- package/src/local/local-dev-references.ts +346 -35
- package/src/local/local-dev-sync.ts +136 -7
- package/src/local/local-docker.ts +35 -1
- package/src/local/local-docs-search.ts +1 -1
- package/src/local/local-docs.ts +281 -2
- package/src/local/local-empty-workflows.ts +92 -0
- package/src/local/local-env.ts +16 -1
- package/src/local/local-file-checks.ts +104 -0
- package/src/local/local-function-ids.ts +141 -0
- package/src/local/local-function-recovery.ts +141 -0
- package/src/local/local-function-state.ts +87 -0
- package/src/local/local-keys.ts +7 -5
- package/src/local/local-knowledge-ingestion.ts +225 -0
- package/src/local/local-knowledge-uploads.ts +217 -0
- package/src/local/local-media-placeholders.ts +182 -0
- package/src/local/local-media-sets.ts +90 -10
- package/src/local/local-model-parameters.ts +89 -0
- package/src/local/local-object-sets.ts +18 -6
- package/src/local/local-operating.ts +35 -1
- package/src/local/local-org-settings.ts +205 -7
- package/src/local/local-orphans.ts +321 -0
- package/src/local/local-personas.ts +296 -0
- package/src/local/local-portability.ts +141 -16
- package/src/local/local-ports.ts +99 -7
- package/src/local/local-project-notices.ts +234 -0
- package/src/local/local-project-secrets.ts +133 -0
- package/src/local/local-project.ts +12 -0
- package/src/local/local-provider-refusal.ts +66 -0
- package/src/local/local-request-evidence.ts +112 -0
- package/src/local/local-runtime-health.ts +123 -0
- package/src/local/local-runtime-history.ts +50 -0
- package/src/local/local-runtime-target.ts +42 -0
- package/src/local/local-sample-data.ts +141 -0
- package/src/local/local-sample-remake.ts +138 -0
- package/src/local/local-scaffold.ts +336 -53
- package/src/local/local-schema-check.ts +148 -0
- package/src/local/local-schemas.ts +742 -25
- package/src/local/local-secret-carry.ts +107 -0
- package/src/local/local-secrets.ts +38 -8
- package/src/local/local-seed.ts +102 -26
- package/src/local/local-stand-ins.ts +1 -1
- package/src/local/local-state.ts +43 -2
- package/src/local/local-studio-server.ts +170 -7
- package/src/local/local-supervisor.ts +44 -6
- package/src/local/local-sync-names.ts +2 -0
- package/src/local/local-sync-plan.ts +11 -0
- package/src/local/local-sync-push.ts +10 -7
- package/src/local/local-sync-refs.ts +49 -3
- package/src/local/local-sync-remote.ts +343 -32
- package/src/local/local-sync-snapshot.ts +15 -10
- package/src/local/local-synthetic-data.ts +2116 -99
- package/src/local/local-test-baseline.ts +113 -0
- package/src/local/local-test-cases.ts +76 -1
- package/src/local/local-test-runner.ts +582 -91
- package/src/local/local-test-runs.ts +13 -5
- package/src/local/local-vendored-sdk.ts +183 -0
- package/src/local/local-workflow-state.ts +16 -3
- package/src/local/local-workflow-tests.ts +482 -0
- package/src/local/local-workspace-grants.ts +81 -0
- package/src/main.ts +50 -9
- package/src/mcp/a11y-scan.ts +55 -0
- package/src/mcp/browser/ax-snapshot.ts +181 -0
- package/src/mcp/browser/credentials.ts +78 -0
- package/src/mcp/browser/frames.ts +336 -0
- package/src/mcp/browser/headless-session.ts +413 -0
- package/src/mcp/browser/headless-tab.ts +575 -0
- package/src/mcp/browser/keys.ts +70 -0
- package/src/mcp/browser/page-scripts.ts +129 -0
- package/src/mcp/browser/wait.ts +73 -0
- package/src/mcp/forge-link.ts +126 -0
- package/src/mcp/headless.ts +199 -0
- package/src/mcp/mcp-apps.ts +435 -0
- package/src/mcp/mcp-chat.ts +226 -0
- package/src/mcp/mcp-cli.ts +53 -0
- package/src/mcp/mcp-protocol.ts +122 -0
- package/src/mcp/mcp-runtime.ts +113 -0
- package/src/mcp/mcp-server.ts +108 -0
- package/src/mcp/mcp-traces.ts +409 -0
- package/src/mcp/page-observation.ts +193 -0
- package/src/mcp/tools/agent-tools.ts +700 -0
- package/src/mcp/tools/app-tools.ts +241 -0
- package/src/mcp/tools/browser-tools.ts +310 -0
- package/src/mcp/tools/docs-tools.ts +32 -0
- package/src/mcp/tools/project-tools.ts +205 -0
- package/src/mcp/tools/test-tools.ts +339 -0
- package/src/mcp/tools/workflow-tools.ts +611 -0
- package/src/output.ts +1 -1
- package/src/pack.ts +8 -3
- package/src/packaging.ts +1 -1
- package/src/project-bootstrap.ts +107 -4
- package/src/project-context.ts +18 -1
- package/src/project.ts +27 -2
- package/src/provenance.ts +25 -0
- package/src/render-evidence.ts +65 -7
- package/src/template.ts +14 -8
- package/src/templates/next-app-files.ts +35 -6
- package/src/templates/next-skills.ts +61 -27
- package/src/vendor/kit-assets.json +6 -6
- package/src/vendor/sdk-sources.json +24 -21
|
@@ -4,12 +4,13 @@ import { dirname, join } from 'node:path'
|
|
|
4
4
|
import { AUTOMATION_SDK_FILES } from '../automation-template'
|
|
5
5
|
import { AGENTS_MD } from '../harness'
|
|
6
6
|
import vendored from '../vendor/sdk-sources.json'
|
|
7
|
+
import { SDK_MANIFEST, sdkManifestText } from './local-vendored-sdk'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* A new project in the layout `frontera local dev` applies: one Agent with a
|
|
10
|
-
* test case, one workflow, one Function
|
|
11
|
-
* dataset it reads. Each is small enough to
|
|
12
|
-
*
|
|
11
|
+
* test case, one workflow with its test, one Function with its unit test, and
|
|
12
|
+
* a Blueprint object type with the dataset it reads. Each is small enough to
|
|
13
|
+
* read in a minute; the Agent's test case holds for any capable model.
|
|
13
14
|
*
|
|
14
15
|
* The Functions SDK is vendored into `vendor/frontera/` and resolved by the
|
|
15
16
|
* `paths` in `tsconfig.json`, the way `frontera function init` does it: the
|
|
@@ -41,7 +42,8 @@ const AGENT = (name: string, modelSlug?: string) =>
|
|
|
41
42
|
const OPERATING = JSON.stringify(
|
|
42
43
|
{
|
|
43
44
|
version: 1,
|
|
44
|
-
|
|
45
|
+
// `tool:blueprint` is the query tools alone; the Action tools are their own choice.
|
|
46
|
+
execution: { harness: 'open-ended', toolScopes: 2 },
|
|
45
47
|
variables: [
|
|
46
48
|
{
|
|
47
49
|
key: 'customer_name',
|
|
@@ -57,16 +59,61 @@ const OPERATING = JSON.stringify(
|
|
|
57
59
|
2,
|
|
58
60
|
) + '\n'
|
|
59
61
|
|
|
62
|
+
/**
|
|
63
|
+
* What the starter Agent may use: the word-count Function, called on its own
|
|
64
|
+
* (no approval), and the Ticket records through its Blueprint tools. The
|
|
65
|
+
* workflows it may start go in `workflows`, the same way.
|
|
66
|
+
*/
|
|
67
|
+
const BINDINGS = JSON.stringify(
|
|
68
|
+
{
|
|
69
|
+
functions: [{ function: 'function/word-count', requiresApproval: false }],
|
|
70
|
+
workflows: [],
|
|
71
|
+
blueprint: [{ objectType: 'objectType/Ticket', permissions: ['read'] }],
|
|
72
|
+
},
|
|
73
|
+
null,
|
|
74
|
+
2,
|
|
75
|
+
) + '\n'
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* What the starter case accepts as offering help: the ways a reply offers it
|
|
79
|
+
* ("I can look up support tickets for you… Tell me what you're looking for"
|
|
80
|
+
* matched none of help, assist or question). Each word starts a word, so
|
|
81
|
+
* "ask" does not match "task".
|
|
82
|
+
*/
|
|
83
|
+
export const STARTER_OFFER_PATTERN = String.raw`\b(help|assist|answer|question|ask|support|look up|find|explain|tell me|let me know|happy to|glad to|I can|able to|what would you like|how can)`
|
|
84
|
+
|
|
60
85
|
const EVALS = `# Test cases for the assistant. Each case: a conversation, then what a good reply does.
|
|
61
86
|
# \`frontera local test\` (or Evaluations in Studio) runs them against the draft.
|
|
62
87
|
#
|
|
63
|
-
# This one
|
|
64
|
-
#
|
|
88
|
+
# This one checks what any capable model does with a greeting: it offers help.
|
|
89
|
+
# A reply says so in many ways ("I can look up…", "happy to help", "tell me what
|
|
90
|
+
# you need"), so the pattern takes each of them, not one word.
|
|
91
|
+
# Write expectations about what the reply does, not its exact words.
|
|
65
92
|
cases:
|
|
66
|
-
- name:
|
|
93
|
+
- name: offers-help-when-greeted
|
|
67
94
|
conversation:
|
|
68
|
-
- user: Hello
|
|
69
|
-
|
|
95
|
+
- user: Hello! What can you help me with?
|
|
96
|
+
checks:
|
|
97
|
+
- { type: regex, value: '${STARTER_OFFER_PATTERN}', text: offers help }
|
|
98
|
+
`
|
|
99
|
+
|
|
100
|
+
const WORKFLOW_TESTS = `# Test cases for workflows/hello.yaml. \`frontera local test --workflow hello\`
|
|
101
|
+
# runs each one on the workflow's draft, waits for the run, then checks it.
|
|
102
|
+
# \`frontera local schema workflow-tests\` is this file's format.
|
|
103
|
+
cases:
|
|
104
|
+
- name: greets-by-name
|
|
105
|
+
input: { name: Sam }
|
|
106
|
+
expect:
|
|
107
|
+
status: succeeded
|
|
108
|
+
output:
|
|
109
|
+
- { path: steps.greet.output, equals: 'Hello, Sam' }
|
|
110
|
+
`
|
|
111
|
+
|
|
112
|
+
const FIXTURES_README = `# Fixtures
|
|
113
|
+
|
|
114
|
+
Sample inputs for tests: a payload a workflow test sends, a document an
|
|
115
|
+
Agent's test case refers to, the input a Function's unit test reads. Nothing
|
|
116
|
+
here is applied to the runtime; tests read these files by path.
|
|
70
117
|
`
|
|
71
118
|
|
|
72
119
|
const WORKFLOW = `apiVersion: workflow/v1
|
|
@@ -112,8 +159,10 @@ import wordCount from './word-count'
|
|
|
112
159
|
// A Function is a handler, so it is tested without a runtime: \`bun test\`.
|
|
113
160
|
// The context refuses what the platform refuses (a grant the manifest does not
|
|
114
161
|
// declare, a step name used twice), so a passing test means something.
|
|
162
|
+
// \`replay: true\` runs it as the platform does, again from the top after each
|
|
163
|
+
// step, so code outside a step that a step changes is caught here too.
|
|
115
164
|
test('counts the words', async () => {
|
|
116
|
-
const { ctx, logs } = createTestContext({ input: { text: 'four words right here' } })
|
|
165
|
+
const { ctx, logs } = createTestContext({ replay: true, input: { text: 'four words right here' } })
|
|
117
166
|
expect(await wordCount.handler(ctx)).toEqual({ words: 4 })
|
|
118
167
|
expect(logs.map((l) => l.message)).toEqual(['counted'])
|
|
119
168
|
})
|
|
@@ -128,11 +177,17 @@ A Frontera project. Every file here is applied to a local runtime by
|
|
|
128
177
|
|---|---|
|
|
129
178
|
| \`agents/<slug>/agent.json\` | An Agent's draft: model, instructions, tools |
|
|
130
179
|
| \`agents/<slug>/operating.json\` | Its behavior: procedures, variables, focus, reply checks |
|
|
180
|
+
| \`agents/<slug>/bindings.json\` | What it may use: Functions, workflows, Blueprint object types |
|
|
131
181
|
| \`agents/<slug>/evals/*.yaml\` | Its test cases |
|
|
132
182
|
| \`workflows/<slug>.yaml\` | A workflow |
|
|
183
|
+
| \`workflows/tests/<slug>.yaml\` | A workflow's test cases |
|
|
133
184
|
| \`functions/<name>.ts\` | A Function |
|
|
134
185
|
| \`datasets/<apiName>.json\` + \`.csv\` | Sample data the Blueprint reads |
|
|
135
|
-
| \`blueprint/\` | The Blueprint: object types, links, metrics, actions |
|
|
186
|
+
| \`blueprint/\` | The Blueprint: object types, links, metrics, actions, saved object sets |
|
|
187
|
+
| \`knowledge/<name>/knowledge.json\` | A knowledge base, its documents beside it |
|
|
188
|
+
| \`themes/<name>/theme.json\` | A slide theme, its images beside it |
|
|
189
|
+
| \`chat-apps/<slug>.json\` | A Chat App |
|
|
190
|
+
| \`data/generate.ts\` | How \`frontera local data --synthetic\` makes sample data |
|
|
136
191
|
| \`fixtures/\` | Sample data for tests |
|
|
137
192
|
|
|
138
193
|
## Develop
|
|
@@ -141,9 +196,9 @@ A Frontera project. Every file here is applied to a local runtime by
|
|
|
141
196
|
frontera local start # the runtimes, on this machine
|
|
142
197
|
frontera local dev # apply each file as you save it
|
|
143
198
|
frontera local ui # open Studio
|
|
144
|
-
frontera local test # run the Agents' test cases
|
|
145
|
-
bun test # run the Functions' unit tests, no runtime needed
|
|
146
|
-
frontera local check #
|
|
199
|
+
frontera local test # run the Agents' and workflows' test cases
|
|
200
|
+
bun test # run the Functions' unit tests (functions/), no runtime needed
|
|
201
|
+
frontera local check # the whole gate in one command, before pushing
|
|
147
202
|
\`\`\`
|
|
148
203
|
|
|
149
204
|
An Agent's model is the catalog slug in \`modelSlug\` in its \`agent.json\`.
|
|
@@ -188,6 +243,13 @@ T-2,Invoice total is wrong,closed
|
|
|
188
243
|
T-3,Refund not received,open
|
|
189
244
|
`
|
|
190
245
|
|
|
246
|
+
/**
|
|
247
|
+
* The sample rows the scaffold writes, by dataset: while a file still holds
|
|
248
|
+
* exactly these, it is sample data `frontera local data --synthetic` may
|
|
249
|
+
* remake. Once someone edits it, it is theirs and is kept.
|
|
250
|
+
*/
|
|
251
|
+
export const SCAFFOLD_SAMPLE_ROWS: Readonly<Record<string, string>> = { tickets: DATASET_ROWS }
|
|
252
|
+
|
|
191
253
|
const OBJECT_TYPE = `apiName: Ticket
|
|
192
254
|
displayName: Ticket
|
|
193
255
|
pluralDisplayName: Tickets
|
|
@@ -226,9 +288,8 @@ backing:
|
|
|
226
288
|
`
|
|
227
289
|
|
|
228
290
|
/**
|
|
229
|
-
* The CLI's AGENTS.md, plus what a coding agent needs for this project
|
|
230
|
-
*
|
|
231
|
-
* agent following the generic text alone would never find the loop.
|
|
291
|
+
* The CLI's AGENTS.md, plus what a coding agent needs for this project: the
|
|
292
|
+
* generic text does not describe the local loop.
|
|
232
293
|
*/
|
|
233
294
|
const SOLUTION_AGENTS_MD = `${AGENTS_MD}
|
|
234
295
|
## This project
|
|
@@ -241,50 +302,168 @@ Frontera deployment.
|
|
|
241
302
|
|---|---|
|
|
242
303
|
| \`agents/<slug>/agent.json\` | An Agent's draft: model, instructions, tools |
|
|
243
304
|
| \`agents/<slug>/operating.json\` | Its behavior: procedures, variables, focus, reply checks |
|
|
305
|
+
| \`agents/<slug>/bindings.json\` | What it may use: the Functions and workflows it may start, the Blueprint object types it may query |
|
|
244
306
|
| \`agents/<slug>/evals/*.yaml\` | Its test cases: a conversation and what a good reply does |
|
|
245
307
|
| \`workflows/<slug>.yaml\` | A workflow |
|
|
308
|
+
| \`workflows/tests/<slug>.yaml\` | Its test cases: an input and what the run must end with |
|
|
246
309
|
| \`functions/<name>.ts\` | A Function (the SDK is vendored in \`vendor/\`) |
|
|
247
310
|
| \`datasets/<apiName>.json\` + \`.csv\` | Sample data the Blueprint reads |
|
|
248
|
-
| \`blueprint/\` | The Blueprint: object types
|
|
311
|
+
| \`blueprint/\` | The Blueprint: object-types/, link-types/, shared-fields/, semantic-types/, metrics/, actions/, object-sets/ |
|
|
312
|
+
| \`knowledge/<name>/knowledge.json\` | A knowledge base, its documents beside it |
|
|
313
|
+
| \`skills/<name>/SKILL.md\` | A skill |
|
|
314
|
+
| \`themes/<name>/theme.json\` | A slide theme, its images beside it |
|
|
315
|
+
| \`chat-apps/<slug>.json\` | A Chat App |
|
|
316
|
+
| \`apps/<name>/\` | A Custom App's code (\`frontera app init\` from inside apps/) |
|
|
317
|
+
| \`data/generate.ts\` | How \`frontera local data --synthetic\` makes sample data |
|
|
318
|
+
|
|
319
|
+
\`frontera local dev\` says which files in these folders it does not apply
|
|
320
|
+
and where they belong; a file it skips is never applied.
|
|
321
|
+
|
|
322
|
+
### What goes where
|
|
323
|
+
|
|
324
|
+
- An Agent's tools are not in \`agent.json\`. The Functions and workflows it
|
|
325
|
+
may start and the object types it may query are in \`bindings.json\`
|
|
326
|
+
(\`frontera local schema bindings\`); without a \`blueprint\` entry it has no
|
|
327
|
+
Blueprint tools. A procedure that keeps its own tools (\`tools.inherit:
|
|
328
|
+
false\`) lists them in \`tools.ids\` as \`function:<slug>\`,
|
|
329
|
+
\`workflow:<slug>\` or \`tool:plugins\` / \`tool:blueprint\` (query tools
|
|
330
|
+
only) / \`tool:blueprint-actions\` (governed Action tools: list, submit,
|
|
331
|
+
read a request) / \`tool:web\`, each bound in \`bindings.json\`. That
|
|
332
|
+
meaning needs \`"toolScopes": 2\` in \`execution\` (new Agents have it);
|
|
333
|
+
without it \`tool:blueprint\` keeps the Action tools too. Its knowledge is \`knowledgeAttachments:
|
|
334
|
+
[{ "knowledgeConfigId": "knowledge/<name>" }]\` in \`agent.json\`.
|
|
335
|
+
- A governed Action that writes Blueprint records names
|
|
336
|
+
\`effect.authoritySourceSystem: active-catalog\`, the source system the
|
|
337
|
+
Blueprint's own records have (\`frontera blueprint new action <name>\`
|
|
338
|
+
writes it).
|
|
339
|
+
- A saved object set is \`blueprint/object-sets/<name>.yaml\`
|
|
340
|
+
(\`frontera local schema blueprint-object-set\`); other files name it
|
|
341
|
+
\`objectSet/<name>\`, by its name as people say it ("Open work orders") or
|
|
342
|
+
its file's name.
|
|
343
|
+
- A dataset's first load fixes its columns: declare \`nullable: true\` in its
|
|
344
|
+
\`columns\` for every column a later CSV may leave empty
|
|
345
|
+
(\`frontera local data --synthetic\` does). When a CSV has empty values a
|
|
346
|
+
column did not allow, \`local dev\` cuts a new revision that allows them; a
|
|
347
|
+
column added, dropped or retyped is a new dataset (another apiName, or
|
|
348
|
+
\`frontera local reset\`).
|
|
349
|
+
- \`frontera local data --synthetic\` writes sample data that keeps links and
|
|
350
|
+
states consistent; \`data/generate.ts\` adjusts it, and
|
|
351
|
+
\`frontera local data --help\` describes both (a field function is
|
|
352
|
+
\`(row, index, ctx) => value\`, \`row\` holding the whole generated row
|
|
353
|
+
and \`ctx.rowsOf(type)\` / \`ctx.lookup(type, key)\` the rows of the types
|
|
354
|
+
it links to; a function that reads another's value runs after it).
|
|
355
|
+
|
|
356
|
+
When your harness offers the \`frontera\` MCP server's tools (Claude Code
|
|
357
|
+
lists them as \`mcp__frontera__*\`), use them for the loop rather than the
|
|
358
|
+
shell: \`status\` rather than \`frontera local status --json\`, \`apply\`
|
|
359
|
+
rather than \`frontera local dev --once\`, \`test_run\` rather than
|
|
360
|
+
\`frontera local test\`, \`logs\` rather than \`frontera local logs\`, and
|
|
361
|
+
\`read_file_schema\` rather than \`frontera local schema\`. The person
|
|
362
|
+
approves the server once; each shell command can ask them again. The CLI
|
|
363
|
+
below is the fallback: when the tools aren't offered (the server isn't
|
|
364
|
+
connected, or the person chose not to use it), and for what has no tool
|
|
365
|
+
(\`local start\`, \`local check\`, \`local data\`, snapshots).
|
|
249
366
|
|
|
250
367
|
The loop:
|
|
251
368
|
|
|
252
369
|
1. \`frontera local start\` brings the runtime up (once; needs Docker).
|
|
253
370
|
2. Edit files. Then run \`frontera local dev --once\`: it applies what
|
|
254
371
|
changed and prints each refused file's error and validation messages.
|
|
255
|
-
Fix those before going on.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
372
|
+
Fix those before going on. It exits 0 when everything changed or added
|
|
373
|
+
applied: an item still as the pull wrote it that fails is listed apart,
|
|
374
|
+
as inherited from the workspace (\`inheritedFailures\` with \`--json\`).
|
|
375
|
+
(A person may keep \`frontera local dev\` running instead; it applies
|
|
376
|
+
on save.)
|
|
377
|
+
3. \`frontera local test\` runs the Agents' and the workflows' test cases and
|
|
378
|
+
exits non-zero when one fails (\`--json\` lists each case's result either
|
|
379
|
+
way). \`bun test\` here runs the Functions' unit tests offline: the
|
|
380
|
+
project's \`bunfig.toml\` keeps it to \`functions/\`. An App's own tests
|
|
381
|
+
run in its folder (\`bun run test\` in \`apps/<name>/\`, part of its
|
|
382
|
+
\`check\`).
|
|
259
383
|
4. Before handing work back, run \`frontera local check\`: it starts the
|
|
260
|
-
runtime if needed, applies everything
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
384
|
+
runtime if needed, applies everything, typechecks, runs the Functions'
|
|
385
|
+
unit tests, each App's own \`check\` script, and the Agent and workflow
|
|
386
|
+
test cases. Every step runs and is reported (the Agent and workflow
|
|
387
|
+
tests run on what applied; \`--json\` gives each step's result). What
|
|
388
|
+
fails the gate:
|
|
389
|
+
- A failure in anything you changed or added (a governed Action of
|
|
390
|
+
yours that does not deploy, a type error in your file, your test)
|
|
391
|
+
fails it: it exits non-zero, naming the first step that failed.
|
|
392
|
+
- A failure only in items or files unchanged since the pull
|
|
393
|
+
(\`frontera.lock\`) is listed as inherited from the workspace
|
|
394
|
+
(\`inherited\` in \`--json\`) and passes, unless \`--strict\`, which
|
|
395
|
+
fails on those too.
|
|
396
|
+
When you report back, give the gate's result (passed or failed, and
|
|
397
|
+
whether with \`--strict\`) and list every inherited failure with the fix
|
|
398
|
+
the check printed for it: the workspace has the same problem, and the
|
|
399
|
+
person decides whether it is fixed here.
|
|
400
|
+
5. \`frontera local status\` says what each item came to and why;
|
|
401
|
+
\`frontera local status -o env\` prints the API URL and keys for scripts.
|
|
402
|
+
|
|
403
|
+
The loop's commands are listed under Local development in \`frontera help\`;
|
|
404
|
+
\`frontera local <verb> --help\` documents each one. In this folder,
|
|
405
|
+
Blueprint and Action commands use the runtime's organization key, and every
|
|
406
|
+
other command its workspace key, with no \`--profile\`.
|
|
407
|
+
|
|
408
|
+
## The Frontera MCP server
|
|
409
|
+
|
|
410
|
+
\`frontera mcp\` gives you this project's local runtime, Frontera's
|
|
411
|
+
documentation, its Apps and, when the project is open in Forge, a browser
|
|
412
|
+
the person watches. It is registered as \`frontera\` in \`.mcp.json\`
|
|
413
|
+
(Claude Code), \`.cursor/mcp.json\` (Cursor), \`opencode.json\` (OpenCode)
|
|
414
|
+
and \`.codex/config.toml\` (Codex). Its documentation tools search and read
|
|
415
|
+
the same pages as the documentation in the platform, for the release the
|
|
416
|
+
local runtime runs. Before writing or changing an \`agent.json\`, an
|
|
275
417
|
\`operating.json\` (procedures, variables), a workflow, a Function or the
|
|
276
418
|
Blueprint, search the documentation for it and follow what it says; do not
|
|
277
|
-
guess a field or a step kind.
|
|
278
|
-
|
|
279
|
-
|
|
419
|
+
guess a field or a step kind. Start the runtime once so the documentation is
|
|
420
|
+
here; after that it needs no key and works while the runtime is stopped.
|
|
421
|
+
|
|
422
|
+
## See what you built
|
|
423
|
+
|
|
424
|
+
Check the part you changed, not everything:
|
|
425
|
+
|
|
426
|
+
- After changing an Agent: \`apply\`, then \`agent_chat\` with it and read
|
|
427
|
+
the trace (\`trace_get\`). \`test_run\` its cases: \`newlyFailing\` lists
|
|
428
|
+
what your change broke; \`couldNotRun\` lists cases the model provider or
|
|
429
|
+
the runtime kept from running (not a regression): tell the person its fix.
|
|
430
|
+
- After changing an App: \`app_render\` the pages you touched and read the
|
|
431
|
+
console and failed requests; \`browser_open\` to click through a flow.
|
|
432
|
+
- When you report back, attach the screenshot path or the trace link.
|
|
433
|
+
|
|
434
|
+
What you run here changes the local runtime's records, never the
|
|
435
|
+
workspace's: a workflow run (\`workflow_run\`, \`frontera workflow run\`),
|
|
436
|
+
a Function run, an App and an Agent conversation with real Actions submit
|
|
437
|
+
governed Actions for real here, whether from an \`action\` step or from a
|
|
438
|
+
Function's code, and an Action that creates, updates or deletes Blueprint
|
|
439
|
+
records changes them. An Action whose effect would reach another system (a
|
|
440
|
+
transition or a command) is switched off here and does not run. Those writes
|
|
441
|
+
stay: \`frontera local snapshot save\` before and \`frontera local snapshot
|
|
442
|
+
restore\` after take them back (\`frontera local check\` does this around its
|
|
443
|
+
own tests).
|
|
444
|
+
|
|
445
|
+
A change request runs each Agent's cases marked \`gate: true\` before it
|
|
446
|
+
applies, against the last applied run; the workspace can require them. Mark
|
|
447
|
+
the cases that guard an Agent's behavior \`gate: true\`, and run them with
|
|
448
|
+
\`test_run\` (\`gate: true\`) before you say a change is ready. Screenshots
|
|
449
|
+
you take are saved under \`.frontera/evidence/<session>/\`, where the
|
|
450
|
+
person can attach them to the request.
|
|
451
|
+
|
|
452
|
+
You cannot promote a deployment, apply a change request or read secrets.
|
|
453
|
+
Say what is ready and let the person decide.
|
|
280
454
|
|
|
281
455
|
The guides describe the product and its concepts, mostly through its
|
|
282
456
|
screens. The exact format of each file is a JSON Schema for the release the
|
|
283
457
|
runtime runs: \`frontera local schema <kind>\` prints it, for \`operating\`,
|
|
284
|
-
\`agent\`, \`workflow\`, \`evals
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
\`
|
|
458
|
+
\`agent\`, \`bindings\`, \`workflow\`, \`evals\`, \`workflow-tests\`,
|
|
459
|
+
\`dataset\`, \`theme\` and each Blueprint file (\`blueprint-object-type\`,
|
|
460
|
+
\`blueprint-action\`, \`blueprint-object-set\`…;
|
|
461
|
+
\`frontera local schema\` lists which file each one is). Where a file names
|
|
462
|
+
another resource the schema takes its reference (\`function/<slug>\`,
|
|
463
|
+
\`model/<slug>\` for an \`llm\` step), which \`local dev\` resolves to the
|
|
464
|
+
runtime's id; \`frontera local start\` keeps them in
|
|
465
|
+
\`.frontera/local/schemas/\`, and the MCP server's \`read_file_schema\`
|
|
466
|
+
tool returns them too. Read the schema before you write
|
|
288
467
|
a file, rather than guessing a field or reading Frontera's source, and do
|
|
289
468
|
not add a \`$schema\` key to a file: most files refuse a key they do not
|
|
290
469
|
know. A schema checks a file's shape; what the file refers to (the action a
|
|
@@ -300,22 +479,114 @@ the person's to set (\`frontera local key-set\`, \`provider-add\`,
|
|
|
300
479
|
\`model-add\`, or Forge's Models page): ask them rather than doing it, and
|
|
301
480
|
never handle a key yourself.
|
|
302
481
|
|
|
303
|
-
Change the files, not the runtime: \`local dev\` keeps the two in step
|
|
304
|
-
|
|
305
|
-
|
|
482
|
+
Change the files, not the runtime: \`local dev\` keeps the two in step (delete
|
|
483
|
+
an Agent's folder or a workflow's file and it is archived on the runtime, a
|
|
484
|
+
Function's and it is switched off; \`status\` lists what the runtime holds
|
|
485
|
+
with no file), and a change made only on the runtime is lost on the next
|
|
486
|
+
reset. A changed CSV must keep its dataset's columns.
|
|
306
487
|
`
|
|
307
488
|
|
|
308
489
|
/**
|
|
309
|
-
* Frontera
|
|
310
|
-
* a stdio
|
|
311
|
-
*
|
|
490
|
+
* The Frontera MCP server for the coding agents working in the project, one
|
|
491
|
+
* entry per harness: a stdio server the harness launches. It reads the
|
|
492
|
+
* runtime's state file for everything else, so there is no key and nothing
|
|
493
|
+
* secret in these files.
|
|
494
|
+
*/
|
|
495
|
+
export const MCP_ENTRY = { command: 'frontera', args: ['mcp'] } as const
|
|
496
|
+
const MCP_JSON = `${JSON.stringify({ mcpServers: { frontera: MCP_ENTRY } }, null, 2)}\n`
|
|
497
|
+
const OPENCODE_JSON = `${JSON.stringify({ $schema: 'https://opencode.ai/config.json', mcp: { frontera: { type: 'local', command: [MCP_ENTRY.command, ...MCP_ENTRY.args], enabled: true } } }, null, 2)}\n`
|
|
498
|
+
/**
|
|
499
|
+
* The project's own Claude Code settings: they approve the project's
|
|
500
|
+
* `frontera` server from `.mcp.json` (`enabledMcpjsonServers`), so a new
|
|
501
|
+
* session asks only once, to trust the folder; Claude Code applies a
|
|
502
|
+
* project's approvals after that. Nothing is written to the person's own
|
|
503
|
+
* settings (`~/.claude.json`, `~/.claude/settings.json`).
|
|
312
504
|
*/
|
|
313
|
-
const
|
|
505
|
+
export const CLAUDE_SETTINGS_JSON = `${JSON.stringify({ enabledMcpjsonServers: ['frontera'] }, null, 2)}\n`
|
|
506
|
+
/** Codex reads a project's own `.codex/config.toml` once the person trusts the project. */
|
|
507
|
+
export const CODEX_MCP_TOML = `# The Frontera MCP server for this project (\`frontera mcp\`).\n[mcp_servers.frontera]\ncommand = "frontera"\nargs = ["mcp"]\n`
|
|
508
|
+
|
|
509
|
+
/** The command projects registered before \`frontera mcp\` (the documentation server alone). */
|
|
510
|
+
const OLD_MCP_ARGS = ['local', 'docs-mcp']
|
|
511
|
+
|
|
512
|
+
const sameArgs = (a: unknown, b: readonly string[]) => Array.isArray(a) && a.length === b.length && a.every((x, i) => x === b[i])
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Point the project's coding-agent configs that still launch \`frontera local
|
|
516
|
+
* docs-mcp\` at \`frontera mcp\` instead, leaving every other server and
|
|
517
|
+
* setting in them as it is. Returns the files changed; never throws.
|
|
518
|
+
*/
|
|
519
|
+
export function migrateMcpConfigs(root: string): string[] {
|
|
520
|
+
const changed: string[] = []
|
|
521
|
+
const edit = (rel: string, change: (text: string) => string | null) => {
|
|
522
|
+
const full = join(root, rel)
|
|
523
|
+
if (!existsSync(full)) return
|
|
524
|
+
try {
|
|
525
|
+
const before = readFileSync(full, 'utf8')
|
|
526
|
+
const after = change(before)
|
|
527
|
+
if (after !== null && after !== before) {
|
|
528
|
+
writeFileSync(full, after)
|
|
529
|
+
changed.push(rel)
|
|
530
|
+
}
|
|
531
|
+
} catch {
|
|
532
|
+
// A config that is not ours to read is left alone.
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
for (const rel of ['.mcp.json', '.cursor/mcp.json']) {
|
|
536
|
+
edit(rel, (text) => {
|
|
537
|
+
const doc = JSON.parse(text) as { mcpServers?: Record<string, { command?: string; args?: unknown }> }
|
|
538
|
+
let moved = false
|
|
539
|
+
for (const server of Object.values(doc.mcpServers ?? {})) {
|
|
540
|
+
if (server?.command === 'frontera' && sameArgs(server.args, OLD_MCP_ARGS)) {
|
|
541
|
+
server.args = [...MCP_ENTRY.args]
|
|
542
|
+
moved = true
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return moved ? `${JSON.stringify(doc, null, 2)}\n` : null
|
|
546
|
+
})
|
|
547
|
+
}
|
|
548
|
+
edit('opencode.json', (text) => {
|
|
549
|
+
const doc = JSON.parse(text) as { mcp?: Record<string, { command?: unknown }> }
|
|
550
|
+
let moved = false
|
|
551
|
+
for (const server of Object.values(doc.mcp ?? {})) {
|
|
552
|
+
if (sameArgs(server?.command, ['frontera', ...OLD_MCP_ARGS])) {
|
|
553
|
+
server.command = [MCP_ENTRY.command, ...MCP_ENTRY.args]
|
|
554
|
+
moved = true
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return moved ? `${JSON.stringify(doc, null, 2)}\n` : null
|
|
558
|
+
})
|
|
559
|
+
edit('.codex/config.toml', (text) => text.replace(/^(args\s*=\s*)\[\s*"local"\s*,\s*"docs-mcp"\s*\]/m, '$1["mcp"]'))
|
|
560
|
+
return changed
|
|
561
|
+
}
|
|
314
562
|
|
|
315
563
|
const GITIGNORE = `node_modules/
|
|
316
564
|
.env
|
|
317
565
|
`
|
|
318
566
|
|
|
567
|
+
/**
|
|
568
|
+
* The project's `bunfig.toml`: `bun test` at its root runs the Functions'
|
|
569
|
+
* unit tests and nothing else. Without it Bun sweeps every folder, Apps'
|
|
570
|
+
* tests included, which need the App's own packages and run in its folder.
|
|
571
|
+
*/
|
|
572
|
+
export const PROJECT_BUNFIG = `# \`bun test\` here runs the Functions' unit tests (functions/), offline.
|
|
573
|
+
# An App's own tests run in its folder: \`bun run test\` in apps/<name>/.
|
|
574
|
+
[test]
|
|
575
|
+
root = "./functions"
|
|
576
|
+
`
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Write the project's `bunfig.toml` when it has none (an older CLI's
|
|
580
|
+
* project, or a pull): true when it wrote one. One that is there is the
|
|
581
|
+
* person's, never changed.
|
|
582
|
+
*/
|
|
583
|
+
export function ensureProjectBunfig(root: string): boolean {
|
|
584
|
+
const path = join(root, 'bunfig.toml')
|
|
585
|
+
if (existsSync(path) || !existsSync(join(root, 'package.json'))) return false
|
|
586
|
+
writeFileSync(path, PROJECT_BUNFIG)
|
|
587
|
+
return true
|
|
588
|
+
}
|
|
589
|
+
|
|
319
590
|
/** `samples: false` leaves out the example Agent, workflow, Function and object type: a project started from a workspace gets the workspace's own. */
|
|
320
591
|
export function solutionScaffoldFiles(name: string, opts: { agentModel?: string; samples?: boolean } = {}): Record<string, string> {
|
|
321
592
|
const sdk = vendored.files as Record<string, string>
|
|
@@ -325,18 +596,27 @@ export function solutionScaffoldFiles(name: string, opts: { agentModel?: string;
|
|
|
325
596
|
if (content === undefined) throw new Error(`vendored SDK is missing ${rel} — run \`bun run sync:sdk\` in packages/frontera-cli`)
|
|
326
597
|
out[`vendor/${rel}`] = content
|
|
327
598
|
}
|
|
599
|
+
// What the CLI wrote there, by hash: `local dev` keeps the copies nobody edited current (`local-vendored-sdk.ts`).
|
|
600
|
+
out[SDK_MANIFEST] = sdkManifestText(Object.fromEntries(Object.entries(out).filter(([rel]) => rel.startsWith('vendor/'))))
|
|
328
601
|
|
|
329
602
|
out['frontera.yaml'] = `# A Frontera project. \`frontera local dev\` applies agents/, workflows/, functions/ and blueprint/.\nname: ${name}\n`
|
|
330
603
|
out['README.md'] = README(name)
|
|
331
604
|
out['AGENTS.md'] = SOLUTION_AGENTS_MD
|
|
332
605
|
out['.gitignore'] = GITIGNORE
|
|
333
|
-
out['.mcp.json'] =
|
|
334
|
-
out['.
|
|
606
|
+
out['.mcp.json'] = MCP_JSON
|
|
607
|
+
out['.claude/settings.json'] = CLAUDE_SETTINGS_JSON
|
|
608
|
+
out['.cursor/mcp.json'] = MCP_JSON
|
|
609
|
+
out['opencode.json'] = OPENCODE_JSON
|
|
610
|
+
out['.codex/config.toml'] = CODEX_MCP_TOML
|
|
611
|
+
out['fixtures/README.md'] = FIXTURES_README
|
|
612
|
+
out['bunfig.toml'] = PROJECT_BUNFIG
|
|
335
613
|
if (opts.samples !== false) {
|
|
336
614
|
out['agents/assistant/agent.json'] = AGENT(name, opts.agentModel)
|
|
337
615
|
out['agents/assistant/operating.json'] = OPERATING
|
|
616
|
+
out['agents/assistant/bindings.json'] = BINDINGS
|
|
338
617
|
out['agents/assistant/evals/regression.yaml'] = EVALS
|
|
339
618
|
out['workflows/hello.yaml'] = WORKFLOW
|
|
619
|
+
out['workflows/tests/hello.yaml'] = WORKFLOW_TESTS
|
|
340
620
|
out['functions/word-count.ts'] = FUNCTION
|
|
341
621
|
out['functions/word-count.test.ts'] = FUNCTION_TEST
|
|
342
622
|
out['datasets/tickets.json'] = DATASET
|
|
@@ -392,8 +672,11 @@ export function solutionScaffoldFiles(name: string, opts: { agentModel?: string;
|
|
|
392
672
|
export function writeSolutionScaffold(target: string, name: string, force: boolean, opts: { agentModel?: string; samples?: boolean } = {}): { written: string[]; skipped: string[] } {
|
|
393
673
|
const written: string[] = []
|
|
394
674
|
const skipped: string[] = []
|
|
675
|
+
// A vendored SDK an older CLI wrote has no manifest, and one written now would vouch for files it didn't write.
|
|
676
|
+
const olderSdk = !force && existsSync(join(target, 'vendor', 'frontera')) && !existsSync(join(target, SDK_MANIFEST))
|
|
395
677
|
for (const [rel, content] of Object.entries(solutionScaffoldFiles(name, opts))) {
|
|
396
678
|
const full = join(target, rel)
|
|
679
|
+
if (rel === SDK_MANIFEST && olderSdk) continue
|
|
397
680
|
const generic = rel === 'AGENTS.md' && existsSync(full) && readFileSync(full, 'utf8') === AGENTS_MD
|
|
398
681
|
if (existsSync(full) && !force && !generic) {
|
|
399
682
|
skipped.push(rel)
|