@frontera-sdk/cli 0.1.0 → 1.43.5

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 (48) hide show
  1. package/package.json +4 -2
  2. package/src/api/apps-api.ts +13 -1
  3. package/src/api/automation-api.ts +129 -1
  4. package/src/api/blueprint-authoring-api.ts +574 -0
  5. package/src/api/dataset-api.ts +199 -0
  6. package/src/api/platform-api.ts +300 -0
  7. package/src/automation-template.ts +224 -0
  8. package/src/blueprint/compile.ts +371 -0
  9. package/src/blueprint/dataset-revision.ts +33 -0
  10. package/src/blueprint/diff.ts +223 -0
  11. package/src/blueprint/model.ts +227 -0
  12. package/src/blueprint/projection.ts +254 -0
  13. package/src/blueprint/render.ts +73 -0
  14. package/src/blueprint/scaffold.ts +79 -0
  15. package/src/blueprint/tree.ts +121 -0
  16. package/src/commands/agent/index-commands.ts +87 -1
  17. package/src/commands/app/deploy.ts +43 -3
  18. package/src/commands/app/init.ts +23 -1
  19. package/src/commands/app/pull.ts +12 -35
  20. package/src/commands/automation/index-commands.ts +42 -1
  21. package/src/commands/automation/init.ts +52 -0
  22. package/src/commands/automation/project-root.ts +58 -0
  23. package/src/commands/automation/pull.ts +124 -0
  24. package/src/commands/automation/run.ts +271 -0
  25. package/src/commands/blueprint/authoring.ts +410 -0
  26. package/src/commands/blueprint/bind.ts +228 -0
  27. package/src/commands/blueprint/declarative.ts +1052 -0
  28. package/src/commands/blueprint/grants.ts +164 -0
  29. package/src/commands/dataset/index-commands.ts +431 -0
  30. package/src/commands/knowledge/index-commands.ts +278 -27
  31. package/src/commands/knowledge/upload-batch.ts +146 -0
  32. package/src/commands/knowledge/upload-plan.ts +127 -0
  33. package/src/commands/login.ts +49 -11
  34. package/src/commands/pack/index-commands.ts +373 -0
  35. package/src/commands/registry.ts +19 -2
  36. package/src/commands/secret/index-commands.ts +195 -0
  37. package/src/commands/skill/bundle-commands.ts +327 -0
  38. package/src/commands/skill/index-commands.ts +36 -42
  39. package/src/commands/skill/resolve.ts +34 -0
  40. package/src/dev-env.ts +114 -0
  41. package/src/flag-help.ts +34 -0
  42. package/src/harness.ts +30 -3
  43. package/src/main.ts +10 -3
  44. package/src/render-evidence.ts +152 -0
  45. package/src/template.ts +4 -0
  46. package/src/untar.ts +44 -0
  47. package/src/vendor/sdk-sources.json +13 -11
  48. package/src/commands/blueprint/reserved.ts +0 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontera-sdk/cli",
3
- "version": "0.1.0",
3
+ "version": "1.43.5",
4
4
  "description": "The frontera CLI — scaffold, pull, save and deploy Frontera apps and automations.",
5
5
  "keywords": [
6
6
  "frontera",
@@ -38,7 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@frontera-sdk/automation": "0.1.0",
41
- "@frontera-sdk/core": "0.1.0"
41
+ "@frontera-sdk/core": "0.1.0",
42
+ "gray-matter": "^4.0.3",
43
+ "yaml": "^2.9.0"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@types/bun": "latest",
@@ -105,6 +105,8 @@ export class AppsApi {
105
105
  source: Uint8Array
106
106
  manifest?: unknown
107
107
  promote: boolean
108
+ /** §4.4 — whether this tree had been rendered since its last source change. */
109
+ renderEvidence?: unknown
108
110
  }) {
109
111
  const form = new FormData()
110
112
  form.set('version', args.version)
@@ -113,7 +115,17 @@ export class AppsApi {
113
115
  form.set('source', new Blob([args.source], { type: 'application/gzip' }), 'src.tgz')
114
116
  if (args.manifest) form.set('manifest', JSON.stringify(args.manifest))
115
117
  form.set('promote', args.promote ? 'true' : 'false')
116
- return this.multipart<{ version: string; promoted: boolean; fileCount: number; totalBytes: number }>(
118
+ if (args.renderEvidence) form.set('renderEvidence', JSON.stringify(args.renderEvidence))
119
+ return this.multipart<{
120
+ version: string
121
+ promoted: boolean
122
+ fileCount: number
123
+ totalBytes: number
124
+ /** Where a person opens this exact build — composed by the service,
125
+ * which is the only side that knows the platform origin. */
126
+ previewUrl?: string
127
+ liveUrl?: string
128
+ }>(
117
129
  `/v1/platform-apps/${appId}/versions`,
118
130
  form,
119
131
  )
@@ -1,6 +1,8 @@
1
1
  import { FronteraClient } from '@frontera-sdk/core/client'
2
2
  import { FronteraError } from '@frontera-sdk/core/errors'
3
3
 
4
+ import { CliError } from '../errors'
5
+
4
6
  /** A row of `GET /v1/automations/` — the shell, not any one version of it. */
5
7
  export interface AutomationSummary {
6
8
  id: string
@@ -40,6 +42,49 @@ export interface AutomationDeployResult {
40
42
  runnerLastSeenAt?: string | null
41
43
  }
42
44
 
45
+ /** A row of `GET /v1/automations/<slug>/runs`. */
46
+ export interface AutomationRunSummary {
47
+ id: string
48
+ status: string
49
+ triggerSource: string
50
+ triggeredBy: string | null
51
+ errorMessage: string | null
52
+ /** What the handler RETURNED. Null when it returned nothing, or has not finished. */
53
+ result: unknown
54
+ /** Billable ctx calls the run made. */
55
+ ctxCalls: number
56
+ startedAt: string
57
+ finishedAt: string | null
58
+ version: number
59
+ }
60
+
61
+ /** A row of `GET /v1/automations/runs/<id>/steps`. */
62
+ export interface AutomationRunStep {
63
+ id: string
64
+ seq: number
65
+ kind: string
66
+ label: string
67
+ status: string
68
+ durationMs: number | null
69
+ /** The author's step name, or null when the row is a `ctx` call. */
70
+ stepName: string | null
71
+ /** The step this row ran inside, or null at the top level. */
72
+ parentStepId: string | null
73
+ }
74
+
75
+ /** `GET /v1/automations/registry-status`. */
76
+ export interface RegistryStatus {
77
+ /** The generation the service has. */
78
+ generation: number
79
+ /**
80
+ * The generation a runner has REGISTERED with the broker, or null if none ever
81
+ * has. While this lags `generation`, a version deployed in between is not yet
82
+ * invocable.
83
+ */
84
+ registeredGeneration: number | null
85
+ lastPolledAt: string | null
86
+ }
87
+
43
88
  /**
44
89
  * Client for the automation routes.
45
90
  *
@@ -80,6 +125,41 @@ export class AutomationApi {
80
125
  )
81
126
  }
82
127
 
128
+ /**
129
+ * Queue a run of the live version.
130
+ *
131
+ * Returns as soon as the invoke event is accepted — the run itself is
132
+ * asynchronous, which is why the caller polls `runs()` rather than being
133
+ * handed a result here. Ignores `enabled` on purpose: disable is a schedule
134
+ * kill switch, not a lock, so an operator can test a fix without re-arming the
135
+ * cron.
136
+ */
137
+ run(slug: string): Promise<{ slug: string; version: number; queued: boolean }> {
138
+ return this.client.request<{ slug: string; version: number; queued: boolean }>(
139
+ `/v1/automations/${encodeURIComponent(slug)}/run`,
140
+ { method: 'POST' },
141
+ )
142
+ }
143
+
144
+ registryStatus(): Promise<RegistryStatus> {
145
+ return this.client.request<RegistryStatus>('/v1/automations/registry-status')
146
+ }
147
+
148
+ runs(slug: string, limit?: number): Promise<AutomationRunSummary[]> {
149
+ const query = limit === undefined ? '' : `?limit=${limit}`
150
+ return this.client.request<AutomationRunSummary[]>(
151
+ `/v1/automations/${encodeURIComponent(slug)}/runs${query}`,
152
+ )
153
+ }
154
+
155
+ /** One run's trail. Workspace-scoped server-side, so a guessed id reads
156
+ * nothing. */
157
+ runSteps(runId: string): Promise<AutomationRunStep[]> {
158
+ return this.client.request<AutomationRunStep[]>(
159
+ `/v1/automations/runs/${encodeURIComponent(runId)}/steps`,
160
+ )
161
+ }
162
+
83
163
  setEnabled(slug: string, enabled: boolean): Promise<{ slug: string; enabled: boolean }> {
84
164
  return this.client.request<{ slug: string; enabled: boolean }>(
85
165
  `/v1/automations/${encodeURIComponent(slug)}/enabled`,
@@ -95,13 +175,61 @@ export class AutomationApi {
95
175
  * way — its schema is `t.Unknown()` for exactly that reason — and sending the
96
176
  * string keeps this end honest about what a form field is.
97
177
  */
178
+ /** The stored source archive for a version, as gzip bytes. */
179
+ async downloadSource(slug: string, version: number): Promise<Uint8Array> {
180
+ const credential = this.client.config.credential
181
+ const res = await fetch(
182
+ `${this.client.config.apiBaseUrl}/v1/automations/${encodeURIComponent(slug)}`
183
+ + `/versions/${version}/source`,
184
+ {
185
+ headers: {
186
+ authorization: `Bearer ${credential.kind === 'apiKey' ? credential.key : credential.token}`,
187
+ },
188
+ },
189
+ )
190
+ if (!res.ok) {
191
+ // The service's message is the useful one — it distinguishes "no version"
192
+ // from "deployed before source retention", and the second names its remedy.
193
+ // Unwrapped from the envelope rather than passed through raw: a bare
194
+ // `throw new Error` renders as `[INTERNAL_ERROR] … {"error":true,…}`, which
195
+ // buries an actionable remedy inside JSON and mislabels a 404 the author
196
+ // caused as a platform fault. They would file a bug instead of reading the
197
+ // fix.
198
+ const body = await res.text()
199
+ let message = body
200
+ try {
201
+ const parsed = JSON.parse(body) as { message?: unknown }
202
+ if (typeof parsed.message === 'string') message = parsed.message
203
+ } catch {
204
+ // Not an envelope — keep the raw body, which is better than nothing.
205
+ }
206
+ // A hint is mandatory here — `error-hints.test.ts` scans for it, on the
207
+ // principle that every CLI-raised error names the next command. For a
208
+ // missing archive the next command is a redeploy from a local copy; for
209
+ // anything else, listing the versions tells the author what exists.
210
+ throw new CliError(message, {
211
+ code: res.status === 404 ? 'NOT_FOUND' : 'REQUEST_FAILED',
212
+ hint: res.status === 404
213
+ ? `frontera automation deploy <file> (from a local copy of ${slug})`
214
+ : `frontera automation versions ${slug}`,
215
+ })
216
+ }
217
+ return new Uint8Array(await res.arrayBuffer())
218
+ }
219
+
98
220
  async deploy(
99
221
  slug: string,
100
- args: { manifest: unknown; bundle: Uint8Array; promote: boolean },
222
+ args: { manifest: unknown; bundle: Uint8Array; source?: Uint8Array; promote: boolean },
101
223
  ): Promise<AutomationDeployResult> {
102
224
  const form = new FormData()
103
225
  form.set('manifest', JSON.stringify(args.manifest))
104
226
  form.set('bundle', new Blob([args.bundle], { type: 'application/javascript' }), 'bundle.js')
227
+ // The project the bundle was built FROM. Without it a deployed version can
228
+ // never be edited again — only the built output would survive. Absent when
229
+ // no project root was found; the caller has already warned about that.
230
+ if (args.source) {
231
+ form.set('source', new Blob([args.source], { type: 'application/gzip' }), 'src.tgz')
232
+ }
105
233
  // The service promotes unless the field is the literal "false".
106
234
  form.set('promote', args.promote ? 'true' : 'false')
107
235