@frontera-sdk/cli 1.44.1 → 1.45.1
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 +65 -1
- package/package.json +4 -3
- package/src/api/automation-api.ts +15 -0
- package/src/api/dataset-api.ts +99 -0
- package/src/api/governed-action-api.ts +80 -0
- package/src/api/platform-api.ts +293 -0
- package/src/auth-verify.ts +105 -0
- package/src/binding-registry.ts +87 -0
- package/src/commands/action/deploy.ts +1 -0
- package/src/commands/action/grant.ts +1 -0
- package/src/commands/action/index-commands.ts +8 -0
- package/src/commands/action/prepare.ts +1 -0
- package/src/commands/action/requests.ts +111 -0
- package/src/commands/action/review.ts +1 -0
- package/src/commands/agent/index-commands.ts +189 -7
- package/src/commands/app/init.ts +1 -1
- package/src/commands/app/pull.ts +1 -1
- package/src/commands/auth/add.ts +145 -0
- package/src/commands/auth/current.ts +82 -0
- package/src/commands/auth/index-commands.ts +16 -0
- package/src/commands/auth/list.ts +71 -0
- package/src/commands/auth/remove.ts +80 -0
- package/src/commands/auth/use.ts +84 -0
- package/src/commands/auth/verify.ts +93 -0
- package/src/commands/automation/run.ts +41 -2
- package/src/commands/blueprint/query.ts +294 -0
- package/src/commands/capability/index-commands.ts +334 -0
- package/src/commands/dataset/index-commands.ts +103 -14
- package/src/commands/kit/doctor.ts +101 -0
- package/src/commands/kit/index-commands.ts +7 -0
- package/src/commands/kit/shared.ts +52 -0
- package/src/commands/kit/status.ts +92 -0
- package/src/commands/kit/sync.ts +106 -0
- package/src/commands/kit/vendor.ts +120 -0
- package/src/commands/knowledge/index-commands.ts +165 -0
- package/src/commands/login.ts +64 -84
- package/src/commands/plugin/index-commands.ts +284 -21
- package/src/commands/registry.ts +104 -1
- package/src/commands/setup.ts +248 -0
- package/src/commands/source/index-commands.ts +446 -0
- package/src/commands/types.ts +14 -0
- package/src/config.ts +197 -100
- package/src/credential-store.ts +273 -0
- package/src/dev-env.ts +3 -3
- package/src/exit.ts +29 -2
- package/src/flag-help.ts +65 -3
- package/src/fs-atomic.ts +44 -0
- package/src/harness.ts +155 -4
- package/src/kit.ts +431 -0
- package/src/main.ts +13 -1
- package/src/paths.ts +43 -0
- package/src/profile-migration.ts +101 -0
- package/src/profiles.ts +240 -0
- package/src/project-context.ts +178 -0
- package/src/prompt.ts +23 -0
- package/src/templates/next-app-files.ts +4 -1
- package/src/vendor/kit-assets.json +60 -0
- package/src/vendor/sdk-sources.json +1 -1
package/src/exit.ts
CHANGED
|
@@ -29,9 +29,32 @@ export interface ErrorEnvelope {
|
|
|
29
29
|
hint?: string
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const USAGE_CODES = new Set([
|
|
32
|
+
const USAGE_CODES = new Set([
|
|
33
|
+
'USAGE',
|
|
34
|
+
'VALIDATION_ERROR',
|
|
35
|
+
'BAD_REQUEST',
|
|
36
|
+
'NOT_FOUND',
|
|
37
|
+
// Profile selection. Every one of these is fixed by changing the command or
|
|
38
|
+
// the directory binding — never by retrying, which is what separates them
|
|
39
|
+
// from AUTH below: a wrong profile is a usage mistake, a revoked key is not.
|
|
40
|
+
'PROFILE_NOT_FOUND',
|
|
41
|
+
'PROFILE_NOT_SELECTED',
|
|
42
|
+
'PROFILE_ORIGIN_MISMATCH',
|
|
43
|
+
'PROFILE_INVALID',
|
|
44
|
+
'PROJECT_CONTEXT_NOT_FOUND',
|
|
45
|
+
'PROJECT_CONTEXT_CORRUPT',
|
|
46
|
+
'PROJECT_CONTEXT_UNTRUSTED',
|
|
47
|
+
// Authoring kit. Both are resolved by running a named command, not by retry.
|
|
48
|
+
'KIT_VERSION_MISMATCH',
|
|
49
|
+
'GENERATED_FILE_CONFLICT',
|
|
50
|
+
])
|
|
33
51
|
const CONFLICT_CODES = new Set(['DRAFT_CONFLICT', 'CONFLICT', 'VALIDATION_STALE'])
|
|
34
|
-
const AUTH_CODES = new Set([
|
|
52
|
+
const AUTH_CODES = new Set([
|
|
53
|
+
'UNAUTHORIZED',
|
|
54
|
+
'FORBIDDEN',
|
|
55
|
+
// The key is gone, not merely unselected — same recovery as a revoked one.
|
|
56
|
+
'PROFILE_SECRET_MISSING',
|
|
57
|
+
])
|
|
35
58
|
|
|
36
59
|
/**
|
|
37
60
|
* Unrecognised codes fall to FAILURE rather than throwing. The SDK does not
|
|
@@ -63,6 +86,10 @@ const SERVICE_HINTS: Record<string, string> = {
|
|
|
63
86
|
FORBIDDEN: 'this credential lacks permission for that resource — ask an admin, or use a different key',
|
|
64
87
|
NOT_FOUND: 'list the resource first to confirm the id',
|
|
65
88
|
RATE_LIMITED: 'wait and retry',
|
|
89
|
+
// Exit 1: the store may be locked or momentarily unreachable, so one retry is
|
|
90
|
+
// a reasonable move before reporting.
|
|
91
|
+
SECURE_STORE_UNAVAILABLE:
|
|
92
|
+
'unlock the OS credential store and retry, or set FRONTERA_SECRET_STORE=file',
|
|
66
93
|
SERVICE_UNAVAILABLE: 'the service is unavailable — retry shortly',
|
|
67
94
|
}
|
|
68
95
|
|
package/src/flag-help.ts
CHANGED
|
@@ -41,7 +41,8 @@ export const FLAG_HELP: Readonly<Record<string, string>> = {
|
|
|
41
41
|
quiet: 'suppress progress commentary on stderr',
|
|
42
42
|
yes: 'assume yes for confirmations',
|
|
43
43
|
help: 'show this help',
|
|
44
|
-
'api-url':
|
|
44
|
+
'api-url': "API origin to use — must match the selected profile's origin",
|
|
45
|
+
profile: 'credential profile to use for this command, overriding the directory selection',
|
|
45
46
|
output: 'project-relative .ts path for generated output',
|
|
46
47
|
check: 'verify generated output is current without writing it',
|
|
47
48
|
|
|
@@ -82,16 +83,64 @@ export const FLAG_HELP: Readonly<Record<string, string>> = {
|
|
|
82
83
|
// Packs
|
|
83
84
|
'remove-apps': 'also uninstall the apps the pack installed',
|
|
84
85
|
|
|
85
|
-
// Secrets
|
|
86
|
+
// Secrets, and the same rule for API keys in `auth add`
|
|
86
87
|
from: 'where to read the value from: `-` for stdin, or a file path. Never an inline value',
|
|
88
|
+
// Same rule as `--from`, named separately because a Source definition file
|
|
89
|
+
// carries every other field and only this one is refused inside it.
|
|
90
|
+
'password-from':
|
|
91
|
+
'where to read the connection password from: `-` for stdin, or a file path. Never an inline '
|
|
92
|
+
+ 'value, and never inside the definition file',
|
|
93
|
+
|
|
94
|
+
// Blueprint reads
|
|
95
|
+
select: 'comma-separated property API names to return (default: whatever the service returns)',
|
|
96
|
+
where:
|
|
97
|
+
'filter as <property>:<value>, <property>:<op>:<value>, or a JSON where-node for anything '
|
|
98
|
+
+ 'with and/or in it',
|
|
99
|
+
order: 'sort as <property>:asc or <property>:desc, comma-separated for more than one',
|
|
100
|
+
// No number here on purpose. This map is keyed by flag NAME across every
|
|
101
|
+
// command, and the three surfaces that take `--limit` cap a page at 1000,
|
|
102
|
+
// 200 and 100 — so any figure stated once is wrong for two of them. Each
|
|
103
|
+
// command's own refusal names its real ceiling.
|
|
104
|
+
limit: 'maximum rows to return in one page; each surface has its own ceiling',
|
|
105
|
+
'page-token': 'cursor from the previous response’s nextPageToken; omit for the first page',
|
|
106
|
+
// Named for the field the response carries, which differs by surface —
|
|
107
|
+
// Blueprint answers `nextPageToken`, data-integration answers `nextCursor`.
|
|
108
|
+
// Matching each flag to its own response beats one name that is wrong half
|
|
109
|
+
// the time.
|
|
110
|
+
cursor: 'cursor from the previous response’s nextCursor; omit for the first page',
|
|
111
|
+
lifecycle: 'only requests in this lifecycle state; comma-separate for more than one',
|
|
112
|
+
|
|
113
|
+
// Plugins and agent capabilities
|
|
114
|
+
enable: 'switch the install on',
|
|
115
|
+
disable: 'switch the install off, leaving it installed',
|
|
116
|
+
actions:
|
|
117
|
+
'Action policy for the install: allow_all, read_only or custom. read_only refuses every '
|
|
118
|
+
+ 'Action capability grant on it',
|
|
119
|
+
'new-capability': 'what a newly discovered capability defaults to: enabled or disabled',
|
|
120
|
+
'end-user':
|
|
121
|
+
'bind as end_user, so every caller connects their own account, instead of agent_owned, '
|
|
122
|
+
+ 'which uses one credential the workspace holds',
|
|
123
|
+
'accept-orphaned-knowledge':
|
|
124
|
+
'revert even though the old version names a knowledge base that no longer exists',
|
|
87
125
|
|
|
88
126
|
// Knowledge
|
|
89
127
|
description: 'one-line description stored on the resource',
|
|
128
|
+
'top-k': 'how many chunks to return (default 10)',
|
|
129
|
+
// A DISTANCE cutoff, not the similarity the output column shows — the query
|
|
130
|
+
// keeps chunks whose cosine distance is below it, so a HIGHER value is more
|
|
131
|
+
// permissive and 0 matches nothing at all. Described the wrong way round
|
|
132
|
+
// first, which made `--threshold 0` look like "return everything".
|
|
133
|
+
threshold:
|
|
134
|
+
'maximum cosine distance a chunk may have; higher is more permissive (default 0.9). '
|
|
135
|
+
+ 'Not the similarity score shown in the output — 0 matches nothing',
|
|
90
136
|
strategy:
|
|
91
137
|
'text extraction to use per file: auto (default), text, or ocr; ocr needs an OCR engine on the base',
|
|
92
138
|
|
|
93
139
|
// Auth and setup
|
|
94
|
-
|
|
140
|
+
// "key", not "workspace key": `login` verifies an organization key too, and
|
|
141
|
+
// naming only one kind here was the last place the CLI still implied that a
|
|
142
|
+
// holder of the other had no route in.
|
|
143
|
+
'token-stdin': 'read the key from stdin instead of prompting — sk-ws- or sk-org-',
|
|
95
144
|
'no-input': 'never prompt; fail instead, for use in scripts and CI',
|
|
96
145
|
config: 'print the resolved configuration and where each value came from',
|
|
97
146
|
}
|
|
@@ -103,6 +152,7 @@ export const FLAG_HELP: Readonly<Record<string, string>> = {
|
|
|
103
152
|
*/
|
|
104
153
|
const PLACEHOLDER: Readonly<Record<string, string>> = {
|
|
105
154
|
'api-url': 'origin',
|
|
155
|
+
profile: 'name',
|
|
106
156
|
output: 'path',
|
|
107
157
|
dataset: 'name',
|
|
108
158
|
plan: 'path',
|
|
@@ -116,8 +166,20 @@ const PLACEHOLDER: Readonly<Record<string, string>> = {
|
|
|
116
166
|
description: 'text',
|
|
117
167
|
strategy: 'auto|text|ocr',
|
|
118
168
|
from: '-|path',
|
|
169
|
+
'password-from': '-|path',
|
|
119
170
|
name: 'text',
|
|
120
171
|
kind: 'conversation|work',
|
|
172
|
+
select: 'a,b,c',
|
|
173
|
+
where: 'prop:value',
|
|
174
|
+
order: 'prop:asc',
|
|
175
|
+
limit: 'n',
|
|
176
|
+
'page-token': 'cursor',
|
|
177
|
+
cursor: 'cursor',
|
|
178
|
+
lifecycle: 'state',
|
|
179
|
+
actions: 'allow_all|read_only|custom',
|
|
180
|
+
'new-capability': 'enabled|disabled',
|
|
181
|
+
'top-k': 'n',
|
|
182
|
+
threshold: '0-2',
|
|
121
183
|
template: 'non-negative|percentage|status-enum|code-length',
|
|
122
184
|
}
|
|
123
185
|
|
package/src/fs-atomic.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Write a file so a reader never sees half of it.
|
|
6
|
+
*
|
|
7
|
+
* Profile metadata and directory context are read by every command, including
|
|
8
|
+
* the ones running concurrently in another terminal. A plain `writeFileSync`
|
|
9
|
+
* truncates first and fills after, so a crash — or a reader arriving in that
|
|
10
|
+
* window — finds an empty or partial JSON document, which the resolver would
|
|
11
|
+
* then have to treat as corrupt. Temp file, fsync, atomic rename: the reader
|
|
12
|
+
* sees the old bytes or the new ones and nothing between.
|
|
13
|
+
*
|
|
14
|
+
* Mode 0600 by default because both files this is used for are personal
|
|
15
|
+
* machine configuration, and one of them sits next to a credential store.
|
|
16
|
+
*/
|
|
17
|
+
export function writeFileAtomic(path: string, content: string, mode = 0o600): void {
|
|
18
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
19
|
+
// Same directory, so the rename stays within one filesystem — a cross-device
|
|
20
|
+
// rename is not atomic and not even permitted.
|
|
21
|
+
const tmp = join(dirname(path), `.${Date.now()}-${process.pid}.tmp`)
|
|
22
|
+
try {
|
|
23
|
+
writeFileSync(tmp, content, { mode })
|
|
24
|
+
// Explicitly, because the create mode is masked by the process umask — a
|
|
25
|
+
// file asked for as 0664 lands as 0644 under the usual 022, so preserving
|
|
26
|
+
// an existing file's mode across an atomic replace requires this.
|
|
27
|
+
chmodSync(tmp, mode)
|
|
28
|
+
const fd = openSync(tmp, 'r+')
|
|
29
|
+
try {
|
|
30
|
+
fsyncSync(fd)
|
|
31
|
+
} finally {
|
|
32
|
+
closeSync(fd)
|
|
33
|
+
}
|
|
34
|
+
renameSync(tmp, path)
|
|
35
|
+
} catch (err) {
|
|
36
|
+
try {
|
|
37
|
+
unlinkSync(tmp)
|
|
38
|
+
} catch {
|
|
39
|
+
// The temp file may never have been created. Losing it is not the failure
|
|
40
|
+
// worth reporting — the write is.
|
|
41
|
+
}
|
|
42
|
+
throw err
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/harness.ts
CHANGED
|
@@ -25,6 +25,22 @@ description: Use when reading or changing anything on the Frontera platform from
|
|
|
25
25
|
never out of date — read it instead of guessing, and instead of trusting this
|
|
26
26
|
file for syntax.
|
|
27
27
|
|
|
28
|
+
## Preflight: know which customer you are about to change
|
|
29
|
+
|
|
30
|
+
\`\`\`bash
|
|
31
|
+
frontera auth current --json
|
|
32
|
+
\`\`\`
|
|
33
|
+
|
|
34
|
+
The working directory selects the credential profile, so this is the only
|
|
35
|
+
reliable answer to "which workspace am I in". It reports the profile, its API
|
|
36
|
+
origin, the credential kind and where the selection came from — and no key
|
|
37
|
+
material, ever.
|
|
38
|
+
|
|
39
|
+
Exit 2 with \`PROFILE_NOT_SELECTED\` means this directory is bound to nothing.
|
|
40
|
+
Do not work around it with \`--profile\`; report it, and offer
|
|
41
|
+
\`frontera auth list\` and \`frontera auth use <profile>\`. Only the person can
|
|
42
|
+
add a profile, because it needs their API key.
|
|
43
|
+
|
|
28
44
|
## Discover the surface as data
|
|
29
45
|
|
|
30
46
|
\`\`\`bash
|
|
@@ -40,6 +56,52 @@ command with \`frontera <noun> <verb> --help --json\`.
|
|
|
40
56
|
Prefer this over parsing \`--help\` text. Everything \`--help\` shows is in it,
|
|
41
57
|
and it cannot drift, because both are projected from the same table.
|
|
42
58
|
|
|
59
|
+
Two fields decide whether a call can work at all:
|
|
60
|
+
|
|
61
|
+
- \`authLane\` — \`api-key\` reaches the command with \`sk-ws-\`/\`sk-org-\` or a
|
|
62
|
+
session. \`session\` reaches it with a session token ONLY: a valid key answers
|
|
63
|
+
401 there, which looks exactly like an expired credential. Run
|
|
64
|
+
\`frontera login\` before those.
|
|
65
|
+
- \`available\` — \`false\` means the verb is named but unbuilt, and
|
|
66
|
+
\`unavailableReason\` says why. Pick another route rather than retrying.
|
|
67
|
+
|
|
68
|
+
## Two kinds of credential
|
|
69
|
+
|
|
70
|
+
\`frontera login\` accepts either, stores both the same way, and verifies them
|
|
71
|
+
differently. One origin holds ONE key, so logging in again on the same
|
|
72
|
+
\`--api-url\` replaces whatever was there.
|
|
73
|
+
|
|
74
|
+
- **\`sk-ws-\`, a workspace key.** One workspace's slice. Reaches \`agent\`,
|
|
75
|
+
\`skill\`, \`plugin\`, \`capability\`, \`knowledge\`, \`pack\`, \`secret\`,
|
|
76
|
+
\`automation\`, \`app\`, and Blueprint READS through the granted slice.
|
|
77
|
+
- **\`sk-org-\`, an organization key.** Reaches the organization-level Blueprint
|
|
78
|
+
draft, which no workspace key can — that draft is shared by every workspace,
|
|
79
|
+
so a workspace credential is refused there by design, not by oversight.
|
|
80
|
+
Needed for Blueprint AUTHORING (\`adopt\`, \`create\`, \`update\`, \`publish\`,
|
|
81
|
+
\`apply\`, \`pull\`, \`plan\`, \`validate\`, \`rollback\`) and for \`dataset\` and
|
|
82
|
+
\`source\`.
|
|
83
|
+
|
|
84
|
+
A 403 on one of those nouns usually means the wrong KIND of key, not a missing
|
|
85
|
+
permission. Rather than re-logging in each time, pass the other per call:
|
|
86
|
+
|
|
87
|
+
\`\`\`bash
|
|
88
|
+
FRONTERA_TOKEN=sk-org-… frontera blueprint pull
|
|
89
|
+
FRONTERA_TOKEN=sk-org-… frontera source list
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
An organization key carries a second rule worth knowing: its effective grant is
|
|
93
|
+
its own permissions INTERSECTED with its creator's authority, re-resolved on
|
|
94
|
+
every request. Demoting whoever minted it shrinks the key; removing them kills
|
|
95
|
+
it.
|
|
96
|
+
|
|
97
|
+
## Where the CLI ends
|
|
98
|
+
|
|
99
|
+
Most of the platform has no CLI surface, on purpose — channels, workspaces,
|
|
100
|
+
members, API keys, sheets, tasks and the evaluation suite are Console-only. The
|
|
101
|
+
CLI says so by name: reaching for one of those nouns answers with what it is
|
|
102
|
+
and where it lives, not "unknown command". Read that hint instead of guessing a
|
|
103
|
+
synonym; there isn't one.
|
|
104
|
+
|
|
43
105
|
## The two shapes
|
|
44
106
|
|
|
45
107
|
**Apps are projects.** \`frontera app …\` resolves the project by walking up from
|
|
@@ -61,7 +123,7 @@ Pass \`--json\` for machine-readable output. The contract:
|
|
|
61
123
|
|------|---------|-----------|
|
|
62
124
|
| 0 | success | continue |
|
|
63
125
|
| 1 | transient or remote failure | retry, then report |
|
|
64
|
-
| 2 | usage or
|
|
126
|
+
| 2 | usage, input, or profile selection | fix the command |
|
|
65
127
|
| 3 | conflict — someone changed it first | re-fetch, reapply, retry |
|
|
66
128
|
| 4 | auth or permission | different credential, or ask an admin |
|
|
67
129
|
|
|
@@ -106,6 +168,72 @@ Three things about this are not visible from \`--help\`:
|
|
|
106
168
|
Naming a directory is the normal case: unsupported types inside it are skipped
|
|
107
169
|
and counted. Naming an unsupported file directly is an error and uploads nothing.
|
|
108
170
|
|
|
171
|
+
## Proving a write landed
|
|
172
|
+
|
|
173
|
+
A write verb reports what it SENT. Whether the thing is now readable is a
|
|
174
|
+
different question, and on this platform the two come apart often enough that
|
|
175
|
+
assuming they agree is how a broken binding ships:
|
|
176
|
+
|
|
177
|
+
\`\`\`bash
|
|
178
|
+
frontera knowledge search <base> "a question a user would ask"
|
|
179
|
+
frontera blueprint query <apiName> --limit 5
|
|
180
|
+
frontera blueprint instance <apiName> <pk>
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
- **An empty \`knowledge search\` right after an upload is normal.** Ingestion is
|
|
184
|
+
asynchronous. Check \`frontera knowledge sources <base>\` — anything still
|
|
185
|
+
\`processing\` is not searchable yet, and that is not a failed upload.
|
|
186
|
+
- **An empty \`blueprint query\` after a bind is not normal.** It usually means
|
|
187
|
+
the object type has no dataset binding on the ACTIVE release, not that the
|
|
188
|
+
table is empty. \`frontera blueprint get <apiName>\` shows the binding.
|
|
189
|
+
- Reads resolve through the granted slice, so what comes back is exactly what an
|
|
190
|
+
app or agent sees at runtime. Anything absent here is absent for them too.
|
|
191
|
+
|
|
192
|
+
\`--where\` takes \`<property>:<value>\` or \`<property>:<op>:<value>\` for one
|
|
193
|
+
condition; pass a JSON where-node for anything with and/or in it, or
|
|
194
|
+
\`--file\` a whole query document for unions and link traversals.
|
|
195
|
+
|
|
196
|
+
## Configuring an agent
|
|
197
|
+
|
|
198
|
+
\`\`\`bash
|
|
199
|
+
frontera agent get <agent> --json > agent.json # edit it
|
|
200
|
+
frontera agent apply <agent> -f agent.json # stages onto the draft
|
|
201
|
+
frontera agent skill-attach <agent> <skillId> # skill push does NOT bind
|
|
202
|
+
frontera capability available <installId> # what a plugin offers
|
|
203
|
+
frontera capability grant <agent> <capabilityId> # stages onto the draft
|
|
204
|
+
frontera agent diff <agent> # what is staged vs live
|
|
205
|
+
frontera agent publish <agent> # the only step that goes live
|
|
206
|
+
\`\`\`
|
|
207
|
+
|
|
208
|
+
- **Almost everything stages.** \`apply\`, \`capability grant\` and \`capability
|
|
209
|
+
revoke\` all write the DRAFT. A caller who grants, tests the live agent, sees
|
|
210
|
+
no change and grants again has skipped \`publish\` — that is the one step that
|
|
211
|
+
reaches the running agent.
|
|
212
|
+
- **\`agent revert\` is the exception.** It publishes an old version as a new live
|
|
213
|
+
one immediately, with no draft in between.
|
|
214
|
+
- **Granting a capability auto-binds its plugin install** if the agent did not
|
|
215
|
+
already hold it, so an install you never named appears in \`agent diff\`.
|
|
216
|
+
- **An install set to \`read_only\` refuses every Action capability grant.** The
|
|
217
|
+
conflict names the install, not the capability; \`frontera plugin policy
|
|
218
|
+
<installId> --actions allow_all\` is the fix, and retrying the grant is not.
|
|
219
|
+
- **Binding is not granting.** \`capability bind\` decides whose account the agent
|
|
220
|
+
reaches (\`agent_owned\` by default, \`--end-user\` for per-caller accounts) and
|
|
221
|
+
grants nothing on its own.
|
|
222
|
+
|
|
223
|
+
## Connecting a database
|
|
224
|
+
|
|
225
|
+
\`\`\`bash
|
|
226
|
+
printf %s "$PGPASSWORD" | frontera source create -f ./source.json --password-from -
|
|
227
|
+
frontera source list
|
|
228
|
+
frontera dataset test-source <sourceId>
|
|
229
|
+
\`\`\`
|
|
230
|
+
|
|
231
|
+
The password is never in the definition file — a file carrying \`config.password\`
|
|
232
|
+
is refused, not stripped. Everything else lives in the file so it can be
|
|
233
|
+
committed. \`source update\`, \`source revise\` and \`source disable\` re-read the
|
|
234
|
+
Source first and send its own concurrency tokens, so a write refused with exit 3
|
|
235
|
+
means someone else changed it: re-read and retry, never force.
|
|
236
|
+
|
|
109
237
|
## Building an app
|
|
110
238
|
|
|
111
239
|
\`\`\`bash
|
|
@@ -143,14 +271,29 @@ const AGENTS_MD = `# Frontera
|
|
|
143
271
|
|
|
144
272
|
This directory works with the Frontera platform through the \`frontera\` CLI.
|
|
145
273
|
|
|
146
|
-
Load the
|
|
147
|
-
|
|
148
|
-
|
|
274
|
+
Load the \`using-frontera-cli\` skill before running any \`frontera\` command. It
|
|
275
|
+
carries the workflow, the exit-code contract and the invariants that are not
|
|
276
|
+
obvious from \`--help\`. Codex finds it at
|
|
277
|
+
\`.agents/skills/using-frontera-cli/SKILL.md\`; Claude Code finds the same bytes
|
|
278
|
+
at \`.claude/skills/using-frontera-cli/SKILL.md\`.
|
|
279
|
+
|
|
280
|
+
Before anything that writes, run \`frontera auth current --json\` — the working
|
|
281
|
+
directory selects which customer's credential is in play.
|
|
149
282
|
|
|
150
283
|
For syntax, run \`frontera help\` or \`frontera <noun> <verb> --help\` — help is
|
|
151
284
|
generated from the command table and is always current.
|
|
152
285
|
`
|
|
153
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Claude Code imports with \`@AGENTS.md\`.
|
|
289
|
+
*
|
|
290
|
+
* \`./AGENTS.md\` is a relative path Claude does not import — it reads as prose,
|
|
291
|
+
* so the contract silently never loads. One character, and the difference
|
|
292
|
+
* between a harness that works in both hosts and one that works in one.
|
|
293
|
+
*/
|
|
294
|
+
const CLAUDE_MD = `@AGENTS.md
|
|
295
|
+
`
|
|
296
|
+
|
|
154
297
|
export interface HarnessResult {
|
|
155
298
|
written: string[]
|
|
156
299
|
skipped: string[]
|
|
@@ -159,11 +302,19 @@ export interface HarnessResult {
|
|
|
159
302
|
/**
|
|
160
303
|
* Idempotent: an existing file is left alone unless `force`, so re-running
|
|
161
304
|
* changes nothing and never clobbers a customised AGENTS.md.
|
|
305
|
+
*
|
|
306
|
+
* The same skill is written to BOTH host directories. Codex discovers
|
|
307
|
+
* `.agents/skills` and Claude Code discovers `.claude/skills`, and neither
|
|
308
|
+
* reads the other's — so a single tree is a harness that works in one host and
|
|
309
|
+
* silently does nothing in the other. `frontera kit vendor` is the fuller
|
|
310
|
+
* version of this, carrying the whole authoring kit rather than one skill.
|
|
162
311
|
*/
|
|
163
312
|
export function writeHarnessFiles(dir: string, opts: { force?: boolean } = {}): HarnessResult {
|
|
164
313
|
const files: Array<[string, string]> = [
|
|
165
314
|
['AGENTS.md', AGENTS_MD],
|
|
315
|
+
['CLAUDE.md', CLAUDE_MD],
|
|
166
316
|
[join('.agents', 'skills', 'using-frontera-cli', 'SKILL.md'), SKILL],
|
|
317
|
+
[join('.claude', 'skills', 'using-frontera-cli', 'SKILL.md'), SKILL],
|
|
167
318
|
]
|
|
168
319
|
|
|
169
320
|
const written: string[] = []
|