@frontera-sdk/cli 1.52.45 → 1.52.46
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/package.json +4 -4
- package/src/api/apps-api.ts +3 -1
- package/src/api/automation-api.ts +32 -0
- package/src/api/credential-failure.ts +1 -1
- package/src/api/governed-action-api.ts +24 -0
- package/src/api/media-api.ts +24 -0
- package/src/api/platform-api.ts +13 -0
- package/src/binding-registry.ts +9 -2
- package/src/blueprint/compile.ts +3 -1
- package/src/blueprint/ontology-edit-plan.ts +71 -4
- package/src/blueprint/org-name-check.ts +64 -0
- package/src/blueprint/tree.ts +28 -0
- package/src/commands/action/deploy.ts +22 -5
- package/src/commands/action/index-commands.ts +2 -0
- package/src/commands/action/retire.ts +60 -0
- package/src/commands/agent/compose.ts +1 -0
- package/src/commands/agent/index-commands.ts +42 -0
- package/src/commands/app/deploy.ts +1 -1
- package/src/commands/app/dev.ts +2 -2
- package/src/commands/app/save.ts +1 -1
- package/src/commands/auth/add.ts +5 -1
- package/src/commands/auth/current.ts +6 -0
- package/src/commands/auth/index-commands.ts +3 -0
- package/src/commands/auth/login.ts +84 -0
- package/src/commands/auth/use.ts +21 -2
- package/src/commands/automation/bindings.ts +54 -10
- package/src/commands/automation/index-commands.ts +2 -3
- package/src/commands/blueprint/bind.ts +12 -1
- package/src/commands/blueprint/declarative.ts +6 -2
- package/src/commands/conversation/index-commands.ts +66 -0
- package/src/commands/conversation/local-runtime.ts +18 -0
- package/src/commands/local/check.ts +57 -11
- package/src/commands/local/data.ts +75 -7
- package/src/commands/local/dev.ts +77 -9
- package/src/commands/local/embeddings.ts +74 -0
- package/src/commands/local/index-commands.ts +133 -11
- package/src/commands/local/new.ts +78 -0
- package/src/commands/local/publish.ts +34 -0
- package/src/commands/local/steps.ts +105 -0
- package/src/commands/local/sync-request.ts +69 -23
- package/src/commands/local/sync.ts +2 -0
- package/src/commands/media/index-commands.ts +58 -3
- package/src/commands/registry.ts +11 -3
- package/src/commands/trace/index-commands.ts +56 -0
- package/src/commands/workspace/index-commands.ts +14 -7
- package/src/config.ts +4 -1
- package/src/dev-broker.ts +15 -1
- package/src/device-login.ts +143 -0
- package/src/exit.ts +36 -8
- package/src/flag-help.ts +30 -0
- package/src/forge/claude-adapter.ts +17 -4
- package/src/forge/permission-rules.ts +158 -0
- package/src/forge/touches.ts +7 -6
- package/src/function-outside-imports.ts +13 -2
- package/src/function-source.ts +90 -0
- package/src/harness.ts +4 -4
- package/src/key-reach.ts +64 -0
- package/src/local/local-actions.ts +15 -3
- package/src/local/local-apply-error.ts +26 -0
- package/src/local/local-apps.ts +9 -3
- package/src/local/local-blueprint-schemas.ts +15 -1
- package/src/local/local-confidential.ts +81 -0
- package/src/local/local-data-load.ts +130 -0
- package/src/local/local-dev-references.ts +24 -4
- package/src/local/local-embedding-setup.ts +62 -0
- package/src/local/local-jobs.ts +108 -0
- package/src/local/local-new.ts +148 -0
- package/src/local/local-org-settings.ts +1 -1
- package/src/local/local-person-steps.ts +340 -0
- package/src/local/local-personas.ts +16 -5
- package/src/local/local-portability.ts +22 -1
- package/src/local/local-publish.ts +26 -0
- package/src/local/local-request-evidence.ts +35 -6
- package/src/local/local-runtime-history.ts +4 -3
- package/src/local/local-said-once.ts +51 -0
- package/src/local/local-scaffold.ts +40 -2
- package/src/local/local-schemas.ts +60 -11
- package/src/local/local-stand-ins.ts +44 -0
- package/src/local/local-supervisor.ts +22 -0
- package/src/local/local-sync-models.ts +5 -2
- package/src/local/local-sync-names.ts +3 -0
- package/src/local/local-sync-portable.ts +64 -15
- package/src/local/local-sync-precheck.ts +30 -1
- package/src/local/local-sync-push.ts +81 -8
- package/src/local/local-sync-remote.ts +10 -7
- package/src/local/local-test-models.ts +173 -0
- package/src/local/local-test-runner.ts +232 -25
- package/src/local/local-test-scope.ts +174 -0
- package/src/local/local-ungranted-reads.ts +55 -0
- package/src/local/local-writers.ts +171 -0
- package/src/main.ts +75 -17
- package/src/mcp/browser/ax-snapshot.ts +13 -1
- package/src/mcp/browser/page-scripts.ts +1 -0
- package/src/mcp/mcp-conversations.ts +171 -0
- package/src/mcp/mcp-server.ts +4 -1
- package/src/mcp/mcp-traces.ts +7 -2
- package/src/mcp/tools/agent-tools.ts +94 -2
- package/src/mcp/tools/project-tools.ts +149 -13
- package/src/mcp/tools/test-tools.ts +64 -13
- package/src/output.ts +22 -7
- package/src/pack.ts +16 -0
- package/src/project-context.ts +18 -5
- package/src/project.ts +4 -0
- package/src/remote-guard.ts +52 -0
- package/src/vendor/kit-assets.json +11 -11
package/src/pack.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { collectSourceFiles, formatBytes, type SourceFile } from './packaging'
|
|
|
2
2
|
import { createTarGz } from './tar'
|
|
3
3
|
import type { Output } from './output'
|
|
4
4
|
import { tsconfigAsKept } from './local/local-app-sdk-paths'
|
|
5
|
+
import { functionSourceFiles, IGNORE_FILE, sourceSizeWarning } from './function-source'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Package a directory for upload, reporting its size.
|
|
@@ -21,3 +22,18 @@ export function packDirectory(dir: string, output: Output, label: string, extra:
|
|
|
21
22
|
output.note(` ${label} ${files.length} files, ${formatBytes(totalBytes)}`)
|
|
22
23
|
return { tgz: createTarGz(files), fileCount: files.length, totalBytes }
|
|
23
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A Function's source (`function-source.ts`): what it reaches, less what
|
|
28
|
+
* `.fronteraignore` leaves out, with the size line and, past the limits, a
|
|
29
|
+
* warning naming the largest files.
|
|
30
|
+
*/
|
|
31
|
+
export function packFunctionSource(packageRoot: string, entry: string, output: Output, label: string) {
|
|
32
|
+
const { files, reachedOnly, ignored } = functionSourceFiles(packageRoot, entry)
|
|
33
|
+
const totalBytes = files.reduce((n, f) => n + f.bytes.byteLength, 0)
|
|
34
|
+
const how = [reachedOnly ? 'the files it reaches' : null, ignored ? `${ignored} left out by ${IGNORE_FILE}` : null].filter(Boolean).join('; ')
|
|
35
|
+
output.note(` ${label} ${files.length} files, ${formatBytes(totalBytes)}${how ? ` (${how})` : ''}`)
|
|
36
|
+
const warning = sourceSizeWarning(label.replace(/^source of /, ''), files)
|
|
37
|
+
if (warning) output.note(` warning: ${warning}`)
|
|
38
|
+
return { tgz: createTarGz(files), fileCount: files.length, totalBytes, warning }
|
|
39
|
+
}
|
package/src/project-context.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs'
|
|
2
2
|
import { dirname, join, resolve } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import { isTrustedBinding, recordBinding } from './binding-registry'
|
|
4
|
+
import { isTrustedBinding, isTrustedOrganizationBinding, recordBinding } from './binding-registry'
|
|
5
5
|
import { findProjectRoot } from './context'
|
|
6
6
|
import { CliError } from './errors'
|
|
7
7
|
import { writeFileAtomic } from './fs-atomic'
|
|
@@ -28,6 +28,13 @@ export const CONTEXT_FILE = 'context.json'
|
|
|
28
28
|
export interface ProjectContext {
|
|
29
29
|
schemaVersion: 1
|
|
30
30
|
profile: string
|
|
31
|
+
/**
|
|
32
|
+
* The organization profile organization-scoped commands use here, beside a
|
|
33
|
+
* workspace `profile` (X3: `auth use <profile> --org-profile <name>`). Named,
|
|
34
|
+
* never inferred: two customers can share one host, so an organization key
|
|
35
|
+
* found by its origin could be another customer's.
|
|
36
|
+
*/
|
|
37
|
+
organizationProfile?: string
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
export interface FoundContext {
|
|
@@ -36,6 +43,10 @@ export interface FoundContext {
|
|
|
36
43
|
path: string
|
|
37
44
|
/** The directory that owns it — the binding root. */
|
|
38
45
|
root: string
|
|
46
|
+
/** The organization profile the binding names for organization-scoped commands, when it names one. */
|
|
47
|
+
organizationProfile?: string
|
|
48
|
+
/** This machine bound `organizationProfile` here too. */
|
|
49
|
+
organizationTrusted?: boolean
|
|
39
50
|
/**
|
|
40
51
|
* Did THIS machine bind this root to this profile?
|
|
41
52
|
*
|
|
@@ -65,7 +76,9 @@ export function readContextAt(root: string, env: Env): FoundContext | null {
|
|
|
65
76
|
if (typeof profile !== 'string' || profile.length === 0) {
|
|
66
77
|
throw corrupt(path, 'it names no profile')
|
|
67
78
|
}
|
|
68
|
-
|
|
79
|
+
const org = (parsed as { organizationProfile?: unknown }).organizationProfile
|
|
80
|
+
const organization = typeof org === 'string' && org ? { organizationProfile: org, organizationTrusted: isTrustedOrganizationBinding(root, org, env) } : {}
|
|
81
|
+
return { profile, path, root, trusted: isTrustedBinding(root, profile, env), ...organization }
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
/**
|
|
@@ -136,9 +149,9 @@ export function resolveBindingRoot(
|
|
|
136
149
|
* is a selection the resolver will refuse — a failure mode nobody would connect
|
|
137
150
|
* to having called only half of the pair.
|
|
138
151
|
*/
|
|
139
|
-
export function writeContext(root: string, profile: string, env: Env = process.env): string {
|
|
152
|
+
export function writeContext(root: string, profile: string, env: Env = process.env, organizationProfile?: string): string {
|
|
140
153
|
const path = contextPath(root)
|
|
141
|
-
const body: ProjectContext = { schemaVersion: 1, profile }
|
|
154
|
+
const body: ProjectContext = { schemaVersion: 1, profile, ...(organizationProfile ? { organizationProfile } : {}) }
|
|
142
155
|
try {
|
|
143
156
|
writeFileAtomic(path, `${JSON.stringify(body, null, 2)}\n`, 0o600)
|
|
144
157
|
} catch (err) {
|
|
@@ -161,7 +174,7 @@ export function writeContext(root: string, profile: string, env: Env = process.e
|
|
|
161
174
|
}
|
|
162
175
|
throw err
|
|
163
176
|
}
|
|
164
|
-
recordBinding(root, profile, env)
|
|
177
|
+
recordBinding(root, profile, env, organizationProfile)
|
|
165
178
|
return path
|
|
166
179
|
}
|
|
167
180
|
|
package/src/project.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface ProjectConfig {
|
|
|
11
11
|
slug: string
|
|
12
12
|
displayName: string
|
|
13
13
|
description?: string
|
|
14
|
+
/** Bootstrap icon for the first deploy, from the platform's set by name (X6); the platform owns it after, as the name. */
|
|
15
|
+
icon?: string
|
|
14
16
|
/**
|
|
15
17
|
* The version a deploy names as its parent: the last deployment from this
|
|
16
18
|
* folder, else the version the working tree was pulled from.
|
|
@@ -57,6 +59,7 @@ export function readProject(dir: string): ProjectConfig {
|
|
|
57
59
|
let config: {
|
|
58
60
|
displayName?: string
|
|
59
61
|
description?: string
|
|
62
|
+
icon?: unknown
|
|
60
63
|
connectDomains?: unknown
|
|
61
64
|
resourceDomains?: unknown
|
|
62
65
|
outputDirectory?: string
|
|
@@ -87,6 +90,7 @@ export function readProject(dir: string): ProjectConfig {
|
|
|
87
90
|
slug,
|
|
88
91
|
displayName: config.displayName ?? pkg.frontera?.displayName ?? slug,
|
|
89
92
|
description: config.description ?? pkg.frontera?.description,
|
|
93
|
+
...(typeof config.icon === 'string' && config.icon.trim() ? { icon: config.icon.trim() } : {}),
|
|
90
94
|
// A folder never deployed from keeps its lineage: the version it was pulled from is the parent.
|
|
91
95
|
parentVersion: state.parentVersion ?? state.pulledVersion ?? null,
|
|
92
96
|
connectDomains: stringList(config.connectDomains ?? pkg.frontera?.connectDomains, 'connectDomains'),
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { FRONTERA_READ_ONLY_COMMANDS } from './forge/permission-rules'
|
|
2
|
+
import { findSolutionRoot, localRuntimeProfileOf } from './local/local-project'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A command from a local project that would change a remote deployment it
|
|
6
|
+
* was not told to use (Forge improvement plan X3, T7; P4 2.3: "in the project
|
|
7
|
+
* folder the CLI defaults to the production profile", and only the local-only
|
|
8
|
+
* `--as` flag stopped an `action submit` reaching it).
|
|
9
|
+
*
|
|
10
|
+
* In a project with its own local runtime, a mutating command whose profile
|
|
11
|
+
* reaches a deployment other than the local runtime, and was not chosen with
|
|
12
|
+
* `--profile` on this command, names where it is going and asks first. Where
|
|
13
|
+
* no one can answer (an agent's shell, CI) it is refused, naming the flag that
|
|
14
|
+
* says so. Reads, offline commands, `auth`, `kit` and `local` (which choose
|
|
15
|
+
* their own profiles) are never stopped.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Verbs that only read, beside the exact read-only paths the Forge permission rules list. */
|
|
19
|
+
const READ_VERBS = new Set(['list', 'get', 'status', 'show', 'diff', 'current', 'whoami', 'logs', 'query', 'search', 'versions', 'runs', 'plan', 'validate', 'catalog', 'available', 'installs', 'sources', 'requests', 'deployments', 'checks', 'lineage', 'bindings', 'object-sets', 'agents', 'help'])
|
|
20
|
+
|
|
21
|
+
/** Nouns that pick their own profile, or touch no deployment. */
|
|
22
|
+
const OWN_PROFILE_NOUNS = new Set(['auth', 'kit', 'local', 'help', 'setup', 'init', 'completion', 'mcp', 'forge'])
|
|
23
|
+
|
|
24
|
+
export function isReadOnlyCommand(meta: { noun: string; verb?: string }): boolean {
|
|
25
|
+
const path = `${meta.noun}${meta.verb ? ` ${meta.verb}` : ''}`
|
|
26
|
+
return FRONTERA_READ_ONLY_COMMANDS.includes(path) || (!!meta.verb && READ_VERBS.has(meta.verb))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const LOOPBACK = /^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?(\/|$)/i
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Where a command is going when the guard should ask, or null when it runs
|
|
33
|
+
* as it is: the profile and its origin.
|
|
34
|
+
*/
|
|
35
|
+
export function remoteFromLocalProject(input: {
|
|
36
|
+
meta: { noun: string; verb?: string; offline?: boolean }
|
|
37
|
+
cwd: string
|
|
38
|
+
profile: string | null
|
|
39
|
+
profileSource: string
|
|
40
|
+
apiUrl: string
|
|
41
|
+
}): { profile: string; apiUrl: string; localProfile: string } | null {
|
|
42
|
+
if (input.meta.offline || OWN_PROFILE_NOUNS.has(input.meta.noun) || isReadOnlyCommand(input.meta)) return null
|
|
43
|
+
if (input.profileSource === 'flag' || !input.profile || LOOPBACK.test(input.apiUrl)) return null
|
|
44
|
+
const localProfile = localRuntimeProfileOf(findSolutionRoot(input.cwd))
|
|
45
|
+
if (!localProfile || input.profile === localProfile || input.profile.startsWith(`${localProfile}-`)) return null
|
|
46
|
+
return { profile: input.profile, apiUrl: input.apiUrl, localProfile }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The question, or the refusal's message: what runs where. */
|
|
50
|
+
export function remoteWords(label: string, target: { profile: string; apiUrl: string; localProfile: string }): string {
|
|
51
|
+
return `\`frontera ${label}\` would change ${target.apiUrl} (profile ${target.profile}), not this project's local runtime (${target.localProfile})`
|
|
52
|
+
}
|