@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
@@ -0,0 +1,221 @@
1
+ /**
2
+ * References between a project's files, by name instead of runtime id.
3
+ *
4
+ * An operating config names the Function a step calls by its id, a workflow
5
+ * names the Agent or Plugin a node runs by its id, and ids differ on every
6
+ * runtime. A project file writes `function/refund-eligibility` instead (the
7
+ * kind, a slash, the resource's name), and an instruction link writes
8
+ * `[[function:refund-eligibility]]`. Pulling from a workspace rewrites ids to
9
+ * names (`toNames`); applying to a runtime rewrites names to that runtime's ids
10
+ * (`toIds`). The rewrite walks the whole document, so a new field that holds
11
+ * a reference needs no change here.
12
+ *
13
+ * A value that is not a reference (a procedure's own id, a variable key) is
14
+ * left as it is: only a whole string of the form `kind/name`, or an id the
15
+ * directory knows, is rewritten.
16
+ */
17
+
18
+ /**
19
+ * `object` is an object definition an Agent's object tools work on
20
+ * (`object/claim_ticket`), by slug; its fields travel in the Agent's file.
21
+ */
22
+ export const REF_KINDS = ['function', 'workflow', 'agent', 'knowledge', 'plugin', 'objectType', 'action', 'model', 'skill', 'objectSet', 'object'] as const
23
+ export type RefKind = (typeof REF_KINDS)[number]
24
+
25
+ /** Names as resources are named: slugs, API names, install names. */
26
+ const NAME = '[A-Za-z0-9][A-Za-z0-9._-]*'
27
+ const REF = new RegExp(`^(${REF_KINDS.join('|')})/(${NAME})$`)
28
+ /** `[[function:<id or name>]]` in guidance text, the platform's link form. */
29
+ const LINK = new RegExp(`\\[\\[(${REF_KINDS.join('|')}):([^\\]\\s]+)\\]\\]`, 'g')
30
+ /** `function:<id or name>` as a whole value: a procedure's tool list names its tools this way. */
31
+ const PREFIXED = new RegExp(`^(${REF_KINDS.join('|')}):([^\\s:]+)$`)
32
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
33
+
34
+ export function isUuid(value: string): boolean {
35
+ return UUID.test(value)
36
+ }
37
+
38
+ export function parseRef(value: string): { kind: RefKind; name: string } | null {
39
+ const m = REF.exec(value)
40
+ return m ? { kind: m[1] as RefKind, name: m[2]! } : null
41
+ }
42
+
43
+ export function formatRef(kind: RefKind, name: string): string {
44
+ return `${kind}/${name}`
45
+ }
46
+
47
+ /**
48
+ * The pulled item a reference needs: an object type, a governed Action and a
49
+ * saved object set are Blueprint items (`blueprint/object-types/Customer`,
50
+ * `blueprint/actions/resetLine`, `blueprint/object-sets/open-cases`),
51
+ * everything else its own ref.
52
+ */
53
+ export function itemRef(kind: RefKind, name: string): string {
54
+ if (kind === 'objectType') return `blueprint/object-types/${name}`
55
+ if (kind === 'action') return `blueprint/actions/${name}`
56
+ if (kind === 'objectSet') return `blueprint/object-sets/${name}`
57
+ return formatRef(kind, name)
58
+ }
59
+
60
+ /**
61
+ * One runtime's resources: for each kind, name → id. Built from the runtime's
62
+ * own lists (`local-sync-directory.ts`), for the platform workspace when
63
+ * pulling and for the local runtime when applying.
64
+ */
65
+ export type Directory = Partial<Record<RefKind, Map<string, string>>>
66
+
67
+ /** The same directory the other way round: id → reference. */
68
+ export function invert(directory: Directory): Map<string, { kind: RefKind; name: string }> {
69
+ const out = new Map<string, { kind: RefKind; name: string }>()
70
+ for (const kind of REF_KINDS) {
71
+ for (const [name, id] of directory[kind] ?? []) {
72
+ if (!out.has(id)) out.set(id, { kind, name })
73
+ }
74
+ }
75
+ return out
76
+ }
77
+
78
+ export interface Unresolved {
79
+ /** Where in the document: `procedures.0.steps.2.functionId`. */
80
+ path: string
81
+ /** The value that could not be rewritten: an id the directory does not know, or a name it does not have. */
82
+ value: string
83
+ kind?: RefKind
84
+ /** It is a `[[kind:name]]` link inside guidance text, not a whole value. */
85
+ link?: boolean
86
+ }
87
+
88
+ /** Keys whose value looks like `kind/name` but is a format, not a resource (`apiVersion: workflow/v1`). */
89
+ const NOT_REFERENCES = new Set(['apiVersion', '$schema'])
90
+
91
+ function walk(value: unknown, path: string[], visit: (s: string, path: string[]) => string): unknown {
92
+ if (typeof value === 'string') return NOT_REFERENCES.has(path[path.length - 1] ?? '') ? value : visit(value, path)
93
+ if (Array.isArray(value)) return value.map((v, i) => walk(v, [...path, String(i)], visit))
94
+ if (value && typeof value === 'object') {
95
+ return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([k, v]) => [k, walk(v, [...path, k], visit)]))
96
+ }
97
+ return value
98
+ }
99
+
100
+ /** Keys whose value is another resource's id, where an unknown id is worth reporting. */
101
+ const REFERENCE_KEY = /(^|[a-z])(Id|Ids|id|ids)$|^resourceId$|^ref$/
102
+
103
+ /**
104
+ * Ids → `kind/name`, for a document read from a runtime. An id the directory
105
+ * does not know stays as it is and is reported when it sits where a reference
106
+ * would (`functionId`, `resourceId`, a list of `ids`): the pull plan shows it,
107
+ * and nothing writes a name that points nowhere.
108
+ */
109
+ export function toNames<T>(doc: T, directory: Directory): { doc: T; unresolved: Unresolved[] } {
110
+ const byId = invert(directory)
111
+ const unresolved: Unresolved[] = []
112
+ const out = walk(doc, [], (s, path) => {
113
+ const known = byId.get(s)
114
+ if (known) return formatRef(known.kind, known.name)
115
+ const prefixed = PREFIXED.exec(s)
116
+ if (prefixed && isUuid(prefixed[2]!)) {
117
+ const named = byId.get(prefixed[2]!)
118
+ if (named) return `${prefixed[1]}:${named.name}`
119
+ unresolved.push({ path: path.join('.'), value: prefixed[2]!, kind: prefixed[1] as RefKind })
120
+ return s
121
+ }
122
+ if (isUuid(s)) {
123
+ const key = path.findLast((p) => !/^\d+$/.test(p)) ?? ''
124
+ if (REFERENCE_KEY.test(key)) unresolved.push({ path: path.join('.'), value: s })
125
+ return s
126
+ }
127
+ if (!s.includes('[[')) return s
128
+ return s.replace(LINK, (whole, kind: RefKind, target: string) => {
129
+ const named = byId.get(target)
130
+ if (named) return `[[${kind}:${named.name}]]`
131
+ if (isUuid(target)) unresolved.push({ path: path.join('.'), value: target, kind })
132
+ return whole
133
+ })
134
+ }) as T
135
+ return { doc: out, unresolved }
136
+ }
137
+
138
+ /**
139
+ * `kind/name` → ids, for a document about to be applied to a runtime. A name
140
+ * the runtime does not have is reported with where it is, and the document
141
+ * is not applied: a missing Function is a missing file, not a blank field.
142
+ * Ids already in the document pass through, so files written before names
143
+ * existed still apply.
144
+ */
145
+ export function toIds<T>(doc: T, directory: Directory): { doc: T; unresolved: Unresolved[] } {
146
+ const unresolved: Unresolved[] = []
147
+ const out = walk(doc, [], (s, path) => {
148
+ const ref = parseRef(s)
149
+ if (ref) {
150
+ const id = directory[ref.kind]?.get(ref.name)
151
+ if (id) return id
152
+ unresolved.push({ path: path.join('.'), value: s, kind: ref.kind })
153
+ return s
154
+ }
155
+ const prefixed = PREFIXED.exec(s)
156
+ if (prefixed && !isUuid(prefixed[2]!) && directory[prefixed[1] as RefKind] !== undefined) {
157
+ const id = directory[prefixed[1] as RefKind]?.get(prefixed[2]!)
158
+ if (id) return `${prefixed[1]}:${id}`
159
+ unresolved.push({ path: path.join('.'), value: `${prefixed[1]}/${prefixed[2]}`, kind: prefixed[1] as RefKind })
160
+ return s
161
+ }
162
+ if (!s.includes('[[')) return s
163
+ return s.replace(LINK, (whole, kind: RefKind, target: string) => {
164
+ if (isUuid(target)) return whole
165
+ const id = directory[kind]?.get(target)
166
+ if (id) return `[[${kind}:${id}]]`
167
+ unresolved.push({ path: path.join('.'), value: `${kind}/${target}`, kind, link: true })
168
+ return whole
169
+ })
170
+ }) as T
171
+ return { doc: out, unresolved }
172
+ }
173
+
174
+ /** Every `kind/name` a document refers to, for listing what a pulled item needs. */
175
+ export function referencesIn(doc: unknown): Array<{ kind: RefKind; name: string }> {
176
+ const seen = new Map<string, { kind: RefKind; name: string }>()
177
+ walk(doc, [], (s) => {
178
+ const ref = parseRef(s)
179
+ if (ref) seen.set(formatRef(ref.kind, ref.name), ref)
180
+ const prefixed = PREFIXED.exec(s)
181
+ if (prefixed && !isUuid(prefixed[2]!)) seen.set(formatRef(prefixed[1] as RefKind, prefixed[2]!), { kind: prefixed[1] as RefKind, name: prefixed[2]! })
182
+ for (const m of s.matchAll(LINK)) {
183
+ if (!isUuid(m[2]!)) seen.set(formatRef(m[1] as RefKind, m[2]!), { kind: m[1] as RefKind, name: m[2]! })
184
+ }
185
+ return s
186
+ })
187
+ return [...seen.values()]
188
+ }
189
+
190
+ /** Which kinds a document refers to by name, so a resolver lists only those. */
191
+ export function kindsIn(doc: unknown): Set<RefKind> {
192
+ return new Set(referencesIn(doc).map((r) => r.kind))
193
+ }
194
+
195
+ /**
196
+ * The workflow nodes that run a governed Action still named by its platform id
197
+ * (`kind: action`, `resourceId: <uuid>`), branches included: a file pulled
198
+ * before Actions were named, or by a key that could not name them.
199
+ */
200
+ export function governedActionIds(doc: unknown): Array<{ path: string; id: string }> {
201
+ const out: Array<{ path: string; id: string }> = []
202
+ const visit = (v: unknown, path: string[]) => {
203
+ if (Array.isArray(v)) return v.forEach((x, i) => visit(x, [...path, String(i)]))
204
+ if (!v || typeof v !== 'object') return
205
+ const o = v as Record<string, unknown>
206
+ if (o.kind === 'action' && typeof o.resourceId === 'string' && isUuid(o.resourceId)) out.push({ path: [...path, 'resourceId'].join('.'), id: o.resourceId })
207
+ for (const [k, x] of Object.entries(o)) visit(x, [...path, k])
208
+ }
209
+ visit(doc, [])
210
+ return out
211
+ }
212
+
213
+ /** One line per unresolved reference, for an error or a plan. */
214
+ export function describeUnresolved(items: Unresolved[], file?: string): string[] {
215
+ return items.map((u) => {
216
+ const where = `${file ? `${file}: ` : ''}${u.path || '(document)'}`
217
+ if (u.kind === 'action' && !isUuid(u.value)) return `${where}: the governed Action ${parseRef(u.value)?.name ?? u.value} is not on this runtime`
218
+ if (u.kind === 'object' && !isUuid(u.value)) return `${where}: the object definition ${parseRef(u.value)?.name ?? u.value} is not on this runtime`
219
+ return u.kind && !isUuid(u.value) ? `${where}: ${u.value} is not on this runtime` : `${where}: ${u.value} could not be named (deleted, outside the workspace, or not readable with this key)`
220
+ })
221
+ }