@frontera-sdk/cli 1.52.27 → 1.52.29

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 (59) hide show
  1. package/package.json +5 -4
  2. package/src/commands/local/data.ts +60 -0
  3. package/src/commands/local/dev.ts +361 -18
  4. package/src/commands/local/index-commands.ts +11 -3
  5. package/src/commands/local/init.ts +88 -7
  6. package/src/commands/local/sync-request.ts +750 -0
  7. package/src/commands/local/sync.ts +941 -0
  8. package/src/commands/skill/bundle-commands.ts +9 -6
  9. package/src/errors.ts +4 -1
  10. package/src/exit.ts +3 -0
  11. package/src/flag-help.ts +48 -0
  12. package/src/local/local-actions.ts +306 -0
  13. package/src/local/local-adapt.ts +216 -0
  14. package/src/local/local-agent-files.ts +9 -2
  15. package/src/local/local-agent-grants.ts +125 -0
  16. package/src/local/local-agent-name.ts +71 -0
  17. package/src/local/local-applied.ts +334 -3
  18. package/src/local/local-apps.ts +156 -0
  19. package/src/local/local-blueprint-outline.ts +465 -0
  20. package/src/local/local-brand.ts +123 -0
  21. package/src/local/local-build-summary.ts +106 -0
  22. package/src/local/local-chat-apps.ts +170 -0
  23. package/src/local/local-dev-references.ts +1132 -0
  24. package/src/local/local-dev-sync.ts +171 -7
  25. package/src/local/local-docs.ts +53 -5
  26. package/src/local/local-env.ts +2 -0
  27. package/src/local/local-function-draft.ts +33 -0
  28. package/src/local/local-function-shared.ts +65 -0
  29. package/src/local/local-function-state.ts +34 -0
  30. package/src/local/local-knowledge-documents.ts +302 -0
  31. package/src/local/local-lessons.ts +278 -0
  32. package/src/local/local-library.ts +124 -0
  33. package/src/local/local-media-sets.ts +31 -0
  34. package/src/local/local-object-sets.ts +181 -0
  35. package/src/local/local-objects.ts +159 -0
  36. package/src/local/local-operating.ts +9 -5
  37. package/src/local/local-org-settings.ts +118 -0
  38. package/src/local/local-portability.ts +346 -0
  39. package/src/local/local-project.ts +6 -2
  40. package/src/local/local-quality-checks.ts +171 -0
  41. package/src/local/local-scaffold.ts +14 -11
  42. package/src/local/local-schemas.ts +33 -2
  43. package/src/local/local-secrets.ts +306 -0
  44. package/src/local/local-stand-ins.ts +442 -0
  45. package/src/local/local-studio-server.ts +23 -2
  46. package/src/local/local-sync-base.ts +237 -0
  47. package/src/local/local-sync-client.ts +93 -0
  48. package/src/local/local-sync-link.ts +122 -0
  49. package/src/local/local-sync-merge.ts +395 -0
  50. package/src/local/local-sync-names.ts +169 -0
  51. package/src/local/local-sync-plan.ts +717 -0
  52. package/src/local/local-sync-push.ts +986 -0
  53. package/src/local/local-sync-refs.ts +221 -0
  54. package/src/local/local-sync-remote.ts +1482 -0
  55. package/src/local/local-sync-snapshot.ts +526 -0
  56. package/src/local/local-synthetic-data.ts +374 -0
  57. package/src/local/local-test-runner.ts +172 -14
  58. package/src/local/local-themes.ts +201 -0
  59. package/src/local/local-workflow-state.ts +131 -0
@@ -1,26 +1,52 @@
1
1
  import { spawnSync } from 'node:child_process'
2
- import { basename, resolve } from 'node:path'
2
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs'
3
+ import { basename, join, resolve } from 'node:path'
3
4
 
4
5
  import { UsageError } from '../../errors'
5
6
  import { writeHarnessFiles } from '../../harness'
6
7
  import { slugify } from '../../local/local-project'
7
8
  import { readProviderSetup } from '../../local/local-providers'
9
+ import { readLink } from '../../local/local-sync-link'
10
+ import { generateSyntheticData, addMissingBackings, loadScenario, SCENARIO_TEMPLATE } from '../../local/local-synthetic-data'
8
11
  import { writeSolutionScaffold } from '../../local/local-scaffold'
9
- import { flagBool, type Command } from '../types'
12
+ import { flagBool, flagString, type Command, type CommandContext } from '../types'
13
+ import { localSync } from './sync'
10
14
 
11
15
  /**
12
16
  * `frontera local init [name]`: a new project in the layout `local dev`
13
17
  * applies, like `supabase init`. Into `<name>/`, or this folder when no name
14
18
  * is given; files already there are kept unless `--force`.
19
+ *
20
+ * `--from-workspace` starts it from a platform workspace instead of the
21
+ * examples: the project files, the link (keys on stdin, as `sync connect`
22
+ * takes them, or the link the folder already has), a pull of the chosen
23
+ * kinds, and sample data for the pulled Blueprint. `frontera local start`
24
+ * then runs it, and `local dev` applies what was pulled.
15
25
  */
16
26
  export const localInit: Command = {
17
27
  meta: {
18
28
  noun: 'local',
19
29
  verb: 'init',
20
30
  args: [{ name: 'name', required: false, description: 'folder to create the project in (default: this folder)' }],
21
- flags: { force: 'boolean', 'no-install': 'boolean' },
31
+ flags: {
32
+ force: 'boolean',
33
+ 'no-install': 'boolean',
34
+ 'from-workspace': 'boolean',
35
+ platform: 'string',
36
+ 'workspace-key': 'string',
37
+ 'org-key': 'string',
38
+ kinds: 'string',
39
+ 'no-sample-data': 'boolean',
40
+ rows: 'string',
41
+ seed: 'string',
42
+ },
22
43
  summary: 'Create a project: an Agent with a test case, a workflow, a Function and a Blueprint object type with its data',
23
- examples: ['frontera local init claims-intake', 'frontera local init', 'frontera local init acme-support --no-install'],
44
+ examples: [
45
+ 'frontera local init claims-intake',
46
+ 'frontera local init',
47
+ 'frontera local init acme-support --no-install',
48
+ 'frontera local init acme --from-workspace --platform $URL --workspace-key -',
49
+ ],
24
50
  offline: true,
25
51
  internal: true,
26
52
  },
@@ -32,12 +58,21 @@ export const localInit: Command = {
32
58
  if (!name) throw new UsageError('the project needs a name with letters or digits', 'frontera local init <name>')
33
59
 
34
60
  const force = flagBool(ctx, 'force')
61
+ const fromWorkspace = flagBool(ctx, 'from-workspace')
62
+ if (fromWorkspace && existsSync(target) && !force) {
63
+ // A folder with files in it is someone's work: start from a workspace in an empty one, or link an existing project.
64
+ const present = readdirSync(target).filter((f) => !f.startsWith('.') || f === '.frontera')
65
+ const project = present.some((f) => f === 'frontera.yaml' || f === 'frontera.solution.yaml')
66
+ if (present.length && !project) throw new UsageError(`${target} is not empty`, 'start from a workspace in a new or empty folder, or connect this project with `frontera local sync connect`')
67
+ }
68
+ mkdirSync(target, { recursive: true })
35
69
  // The CLI skill and CLAUDE.md, so a coding agent here knows the CLI. First,
36
70
  // so the scaffold's AGENTS.md (the generic one plus this project's loop)
37
71
  // replaces the generic one rather than the other way round.
38
72
  const harness = writeHarnessFiles(target, { force })
39
- // The starter Agent takes the model the FDE chose for new Agents (`frontera local model-default`).
40
- const scaffold = writeSolutionScaffold(target, name, force, { agentModel: readProviderSetup().defaults.agentModel })
73
+ // The starter Agent takes the model the FDE chose for new Agents (`frontera local model-default`);
74
+ // a project started from a workspace gets the workspace's Agents instead of the examples.
75
+ const scaffold = writeSolutionScaffold(target, name, force, { agentModel: readProviderSetup().defaults.agentModel, samples: !fromWorkspace })
41
76
  const listed = (files: string[]) => files.filter((f) => !f.startsWith('vendor/'))
42
77
  const written = [...new Set(listed([...scaffold.written, ...harness.written]))]
43
78
  const skipped = listed([...scaffold.skipped, ...harness.skipped]).filter((f) => !written.includes(f))
@@ -54,15 +89,24 @@ export const localInit: Command = {
54
89
  }
55
90
  }
56
91
 
92
+ const pulled = fromWorkspace ? await startFromWorkspace(ctx, target) : null
93
+
57
94
  const cd = given ? [` cd ${given}`] : []
58
95
  const sdkWritten = scaffold.written.some((f) => f.startsWith('vendor/'))
59
96
  const heading = written.length
60
97
  ? `Created the project ${name}`
61
98
  : `${name} already has every file${sdkWritten ? '; the vendored Functions SDK was completed' : ''}`
62
99
  return {
63
- data: { name, path: target, written, skipped, installed },
100
+ data: { name, path: target, written, skipped, installed, ...(pulled ? { workspace: pulled } : {}) },
64
101
  text: [
65
102
  heading,
103
+ ...(pulled
104
+ ? [
105
+ pulled.pulled
106
+ ? ` from ${pulled.workspace}: ${pulled.pulled} items pulled${pulled.sampleRows ? `, ${pulled.sampleRows} sample rows` : ''}`
107
+ : ` linked to ${pulled.workspace}; see what it has with \`frontera local sync pull --preview\``,
108
+ ]
109
+ : []),
66
110
  ...written.map((f) => ` + ${f}`),
67
111
  ...(written.length && sdkWritten ? [' + vendor/frontera/ (the Functions SDK)'] : []),
68
112
  ...skipped.map((f) => ` · ${f} (exists; --force replaces it)`),
@@ -76,3 +120,40 @@ export const localInit: Command = {
76
120
  }
77
121
  },
78
122
  }
123
+
124
+ /** Link, pull and fill the new project; each step says what it does, as Forge shows it in a terminal. */
125
+ async function startFromWorkspace(ctx: CommandContext, target: string): Promise<{ workspace: string; pulled: number; sampleRows: number }> {
126
+ const sub: CommandContext = { ...ctx, cwd: target }
127
+ const keysGiven = flagString(ctx, 'workspace-key') !== undefined
128
+ if (keysGiven) {
129
+ ctx.output.note('Linking the project to the workspace')
130
+ await localSync.run({ ...sub, positional: ['connect'], flags: { ...ctx.flags } })
131
+ } else if (!readLink(target)) {
132
+ throw new UsageError('no workspace to start from', 'pass --platform <url> --workspace-key - with the key on stdin')
133
+ }
134
+ const kinds = flagString(ctx, 'kinds') ?? 'blueprint,skills,functions,plugins,knowledge,workflows,agents,evaluations'
135
+ // `none` links the project and pulls nothing, for someone who picks items next (`local sync pull <kind/name>…`).
136
+ if (kinds === 'none') {
137
+ const link = readLink(target)
138
+ return { workspace: link?.workspaceName ?? link?.workspace ?? 'the workspace', pulled: 0, sampleRows: 0 }
139
+ }
140
+ ctx.output.note(`Pulling ${kinds.split(',').join(', ')}`)
141
+ const pull = (await localSync.run({ ...sub, positional: ['pull'], flags: { kinds } })).data as { pulled: string[]; warnings: string[] }
142
+ for (const w of pull.warnings) ctx.output.note(` could not read ${w}`)
143
+ let sampleRows = 0
144
+ const wantsData = !flagBool(ctx, 'no-sample-data') && existsSync(join(target, 'blueprint', 'object-types'))
145
+ if (wantsData) {
146
+ ctx.output.note('Writing sample data for the Blueprint')
147
+ const seed = Number(flagString(ctx, 'seed') ?? 7)
148
+ const scenarioFile = join(target, 'data', 'generate.ts')
149
+ if (!existsSync(scenarioFile)) {
150
+ mkdirSync(join(target, 'data'), { recursive: true })
151
+ writeFileSync(scenarioFile, SCENARIO_TEMPLATE)
152
+ }
153
+ addMissingBackings(target)
154
+ const scenario = await loadScenario(target, seed)
155
+ sampleRows = generateSyntheticData(target, { rows: Number(flagString(ctx, 'rows') ?? 200), seed, scenario: scenario ?? undefined }).reduce((n, d) => n + d.rows, 0)
156
+ }
157
+ const link = readLink(target)
158
+ return { workspace: link?.workspaceName ?? link?.workspace ?? 'the workspace', pulled: pull.pulled.length, sampleRows }
159
+ }