@frontera-sdk/cli 1.53.22 → 1.53.24

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.
Files changed (52) hide show
  1. package/package.json +4 -4
  2. package/src/api/knowledge-base-api.ts +230 -0
  3. package/src/commands/knowledge/index-commands.ts +6 -6
  4. package/src/commands/knowledge-base/index-commands.ts +328 -0
  5. package/src/commands/local/check.ts +12 -0
  6. package/src/commands/local/dev.ts +64 -2
  7. package/src/commands/local/index-commands.ts +34 -10
  8. package/src/commands/local/init.ts +11 -2
  9. package/src/commands/local/schema.ts +1 -1
  10. package/src/commands/local/sync-request.ts +135 -29
  11. package/src/commands/local/sync.ts +40 -25
  12. package/src/commands/registry.ts +4 -1
  13. package/src/commands/types.ts +6 -0
  14. package/src/flag-help.ts +10 -4
  15. package/src/forge/touches.ts +29 -0
  16. package/src/local/local-applied.ts +28 -0
  17. package/src/local/local-build-summary.ts +1 -0
  18. package/src/local/local-component-health.ts +7 -2
  19. package/src/local/local-dev-references.ts +6 -2
  20. package/src/local/local-dev-sync.ts +43 -4
  21. package/src/local/local-docker.ts +16 -1
  22. package/src/local/local-env.ts +19 -0
  23. package/src/local/local-file-checks.ts +56 -8
  24. package/src/local/local-knowledge-base-apply.ts +714 -0
  25. package/src/local/local-knowledge-base-files.ts +195 -0
  26. package/src/local/local-knowledge-base-pull.ts +458 -0
  27. package/src/local/local-knowledge-base-sources-off.ts +67 -0
  28. package/src/local/local-person-steps.ts +2 -1
  29. package/src/local/local-personas.ts +64 -7
  30. package/src/local/local-portability.ts +27 -0
  31. package/src/local/local-ports.ts +4 -2
  32. package/src/local/local-project-secrets.ts +30 -5
  33. package/src/local/local-scaffold.ts +2 -0
  34. package/src/local/local-schema-check.ts +3 -1
  35. package/src/local/local-schemas.ts +11 -1
  36. package/src/local/local-search-index.ts +92 -0
  37. package/src/local/local-secrets.ts +7 -2
  38. package/src/local/local-seed.ts +17 -0
  39. package/src/local/local-state.ts +7 -5
  40. package/src/local/local-studio-server.ts +3 -3
  41. package/src/local/local-supervisor.ts +45 -8
  42. package/src/local/local-sync-link.ts +16 -0
  43. package/src/local/local-sync-names.ts +10 -1
  44. package/src/local/local-sync-plan.ts +44 -5
  45. package/src/local/local-sync-push.ts +165 -34
  46. package/src/local/local-sync-refs.ts +3 -1
  47. package/src/local/local-sync-remote.ts +44 -12
  48. package/src/local/local-sync-snapshot.ts +156 -12
  49. package/src/main.ts +13 -0
  50. package/src/mcp/mcp-server.ts +3 -0
  51. package/src/mcp/tools/docs-tools.ts +1 -1
  52. package/src/mcp/tools/knowledge-base-tools.ts +85 -0
@@ -65,6 +65,17 @@ export function dockerAvailable(): { ok: true } | { ok: false; reason: string }
65
65
  }
66
66
  }
67
67
 
68
+ /** The memory Docker's engine has for all its containers, in bytes; null when it does not say. */
69
+ export function dockerMemoryBytes(): number | null {
70
+ try {
71
+ const r = docker(['info', '--format', '{{.MemTotal}}'])
72
+ const bytes = Number(r.out)
73
+ return r.ok && r.out && Number.isFinite(bytes) && bytes > 0 ? bytes : null
74
+ } catch {
75
+ return null
76
+ }
77
+ }
78
+
68
79
  export function imagePresent(image: string): boolean {
69
80
  return docker(['image', 'inspect', image, '--format', '{{.Id}}']).ok
70
81
  }
@@ -242,13 +253,17 @@ export const dbVolumeName = (projectId: string) => `frontera-${projectId}-db`
242
253
  /** A local runtime's file storage volume, by its id. */
243
254
  export const storageVolumeName = (projectId: string) => `frontera-${projectId}-storage`
244
255
 
256
+ /** A local runtime's search index volume, by its id. */
257
+ export const searchIndexVolumeName = (projectId: string) => `frontera-${projectId}-search-index`
258
+
245
259
  /**
246
260
  * Delete a local runtime's containers and volumes, by its id: the containers
247
- * labelled for it, running or not, and its database and storage volumes. Its
261
+ * labelled for it, running or not, and its database, storage and search index volumes. Its
248
262
  * data is gone afterwards.
249
263
  */
250
264
  export function removeRuntimeResources(projectId: string): void {
251
265
  for (const name of containersForProject(projectId)) removeContainer(name)
252
266
  removeVolume(dbVolumeName(projectId))
253
267
  removeVolume(storageVolumeName(projectId))
268
+ removeVolume(searchIndexVolumeName(projectId))
254
269
  }
@@ -37,6 +37,8 @@ export interface LocalConfig {
37
37
  /** Where image mode pulls the platform images from: a registry host and namespace. */
38
38
  registry?: string
39
39
  exclude: string[]
40
+ /** Components that run only when asked (`--with`): the search index, in a project without `knowledge-base/`. */
41
+ with?: string[]
40
42
  }
41
43
 
42
44
  const secretsFile = (project: LocalProject) => join(project.stateDir, 'secrets.json')
@@ -116,6 +118,7 @@ export const SERVICE_OUTBOUND = [
116
118
  'S3_ENDPOINT',
117
119
  'INNGEST_BASE_URL',
118
120
  'AUTOMATION_RUNNER_URL',
121
+ 'KNOWLEDGE_SEARCH_URL',
119
122
  ] as const
120
123
 
121
124
  /** The service's settings for a hosted key custodian (OpenBao / Vault transit), blanked on the local runtime. */
@@ -150,6 +153,8 @@ export function serviceEnv(args: {
150
153
  runnerEnabled: boolean
151
154
  jobsEnabled: boolean
152
155
  storageEnabled: boolean
156
+ /** The search index runs: the Knowledge app is on. */
157
+ searchIndexEnabled?: boolean
153
158
  /** Point the service's OpenAI client at the keyless local embeddings (no OpenAI key stored). */
154
159
  keylessEmbeddings?: boolean
155
160
  /** The customer documentation publication the service serves to coding agents. */
@@ -225,9 +230,23 @@ export function serviceEnv(args: {
225
230
  if (args.keylessEmbeddings) env.OPENAI_BASE_URL = `http://127.0.0.1:${ports.embeddings}/v1`
226
231
  if (args.runnerEnabled) env.AUTOMATION_RUNNER_URL = `http://127.0.0.1:${ports.runner}`
227
232
  if (args.storageEnabled) Object.assign(env, storageEnv(secrets, ports))
233
+ if (args.searchIndexEnabled) Object.assign(env, searchIndexEnv(ports))
228
234
  return env
229
235
  }
230
236
 
237
+ /**
238
+ * The Knowledge app: its search index, and its own bucket in the runtime's
239
+ * object storage, created on first use. The service refuses a Knowledge bucket
240
+ * that is the public uploads bucket (`S3_BUCKET`).
241
+ */
242
+ export function searchIndexEnv(ports: PortBlock): Record<string, string> {
243
+ return {
244
+ KNOWLEDGE_SEARCH_URL: `http://127.0.0.1:${ports['search-index']}`,
245
+ KNOWLEDGE_BUCKET: 'frontera-local-knowledge',
246
+ KNOWLEDGE_BUCKET_CREATE: 'true',
247
+ }
248
+ }
249
+
231
250
  /**
232
251
  * Object storage (RustFS): Function bundles, uploads, managed datasets. Buckets
233
252
  * are created on first write. Managed datasets also need their catalog
@@ -1,15 +1,17 @@
1
- import { readFileSync } from 'node:fs'
2
- import { join } from 'node:path'
1
+ import { readdirSync, readFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
3
 
4
4
  import matter from 'gray-matter'
5
5
 
6
+ import { KNOWLEDGE_BASE_SOURCES_DIR } from './local-knowledge-base-files'
7
+ import { agentPicks, hasKnowledgeBaseFolder } from './local-knowledge-base-apply'
6
8
  import { checkAgainstSchema, type SchemaProblem } from './local-schema-check'
7
9
  import { cliSchema, type SchemaKind } from './local-schemas'
8
10
 
9
11
  /**
10
- * A Chat App file, a knowledge base's `knowledge.json` and a skill's
11
- * `SKILL.md` front matter, checked against `frontera local schema <kind>`
12
- * before `local dev` applies them.
12
+ * A Chat App file, a knowledge base's `knowledge.json`, a skill's `SKILL.md`
13
+ * front matter, and the Knowledge app's files (`knowledge-base/`), checked
14
+ * against `frontera local schema <kind>` before `local dev` applies them.
13
15
  *
14
16
  * The platform keeps only the keys it knows and drops the rest without a word
15
17
  * (a Chat App's `experience` block applied "✓" and was gone), so a key the
@@ -82,10 +84,53 @@ export function skillFileChecks(file: string, text: string): FileChecks {
82
84
  return { errors, warnings }
83
85
  }
84
86
 
85
- /** The checks for one target's file, read from the project; nothing for a kind these do not cover. */
86
- export function projectFileChecks(root: string, target: { kind: string; file?: string }): FileChecks {
87
+ type JsonSchema = Record<string, unknown>
88
+
89
+ const refused = (file: string, problems: SchemaProblem[]) => problems.map((p) => `${file} ${p.path || '(the file)'}: ${p.message}`)
90
+
91
+ /**
92
+ * A Knowledge app base: its knowledge-base.json, and each source file against
93
+ * the service's format when `local start` kept it. Every key in these files
94
+ * means something (a workspace base's `sharing` would share nothing), so any
95
+ * problem refuses the base.
96
+ */
97
+ export function knowledgeBaseFileChecks(root: string, file: string, doc: unknown, sourceSchema: JsonSchema | null): FileChecks {
98
+ const errors = refused(file, checkAgainstSchema(doc, cliSchema('knowledge-base')!))
99
+ const sources = join(dirname(file), KNOWLEDGE_BASE_SOURCES_DIR)
100
+ let names: string[] = []
101
+ try {
102
+ names = readdirSync(join(root, sources)).filter((n) => n.endsWith('.json')).sort()
103
+ } catch {
104
+ // No sources/ folder: a base without API sources.
105
+ }
106
+ for (const name of names) {
107
+ const rel = join(sources, name)
108
+ try {
109
+ const spec = JSON.parse(readFileSync(join(root, rel), 'utf8')) as unknown
110
+ if (sourceSchema) errors.push(...refused(rel, checkAgainstSchema(spec, sourceSchema)))
111
+ } catch (err) {
112
+ errors.push(`${rel}: is not valid JSON: ${(err as Error).message}`)
113
+ }
114
+ }
115
+ return { errors, warnings: [] }
116
+ }
117
+
118
+ /** An Agent's picks (`knowledgeBases`): each names a base folder the project has, or the Agent goes without it. */
119
+ export function agentPickChecks(root: string, file: string, doc: unknown): FileChecks {
120
+ const warnings = (agentPicks(doc) ?? [])
121
+ .filter((ref) => !hasKnowledgeBaseFolder(root, ref))
122
+ .map((ref) => ` warning: ${file} knowledgeBases: ${ref} is not a knowledge base in this project (${ref}/knowledge-base.json), so the Agent goes without it`)
123
+ return { errors: [], warnings }
124
+ }
125
+
126
+ /**
127
+ * The checks for one target's file, read from the project; nothing for a kind
128
+ * these do not cover. `keptSchema` reads a service-owned format `local start`
129
+ * kept (a knowledge base's source); without it, the service checks those.
130
+ */
131
+ export function projectFileChecks(root: string, target: { kind: string; file?: string }, keptSchema: (kind: SchemaKind) => JsonSchema | null = () => null): FileChecks {
87
132
  const none = { errors: [], warnings: [] }
88
- if (!target.file || !['chat-app', 'knowledge', 'skill'].includes(target.kind)) return none
133
+ if (!target.file || !['chat-app', 'knowledge', 'skill', 'knowledge-base', 'knowledge-base-settings', 'agent'].includes(target.kind)) return none
89
134
  let text: string
90
135
  try {
91
136
  text = readFileSync(join(root, target.file), 'utf8')
@@ -100,5 +145,8 @@ export function projectFileChecks(root: string, target: { kind: string; file?: s
100
145
  // Unreadable: the apply says so with the file's own error.
101
146
  return none
102
147
  }
148
+ if (target.kind === 'knowledge-base') return knowledgeBaseFileChecks(root, target.file, doc, keptSchema('knowledge-base-source'))
149
+ if (target.kind === 'knowledge-base-settings') return { errors: refused(target.file, checkAgainstSchema(doc, cliSchema('knowledge-base-settings')!)), warnings: [] }
150
+ if (target.kind === 'agent') return agentPickChecks(root, target.file, doc)
103
151
  return target.kind === 'chat-app' ? chatAppFileChecks(target.file, doc) : knowledgeFileChecks(target.file, doc)
104
152
  }