@frontera-sdk/cli 1.48.8 → 1.50.0
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/automation-api.ts +24 -24
- package/src/automation-template.ts +8 -8
- package/src/commands/automation/bindings.ts +11 -11
- package/src/commands/automation/dev.ts +8 -8
- package/src/commands/automation/index-commands.ts +20 -20
- package/src/commands/automation/init.ts +4 -4
- package/src/commands/automation/pull.ts +8 -8
- package/src/commands/automation/run.ts +18 -18
- package/src/commands/registry.ts +15 -3
- package/src/heal.ts +1 -1
- package/src/vendor/kit-assets.json +6 -6
- package/src/vendor/sdk-sources.json +16 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontera-sdk/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.50.0",
|
|
4
4
|
"description": "The frontera CLI — scaffold, pull, save and deploy Frontera apps and automations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"frontera",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@anthropic-ai/claude-agent-sdk": "^0.3.251",
|
|
42
|
-
"@frontera-sdk/
|
|
43
|
-
"@frontera-sdk/core": "1.
|
|
42
|
+
"@frontera-sdk/functions": "1.50.0",
|
|
43
|
+
"@frontera-sdk/core": "1.50.0",
|
|
44
44
|
"ai": "^6.0.116",
|
|
45
45
|
"gray-matter": "^4.0.3",
|
|
46
46
|
"yaml": "^2.9.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@frontera-sdk/forge-contracts": "1.
|
|
49
|
+
"@frontera-sdk/forge-contracts": "1.50.0",
|
|
50
50
|
"@types/bun": "^1.3.14",
|
|
51
51
|
"typescript": "^5.9.3"
|
|
52
52
|
}
|
|
@@ -3,7 +3,7 @@ import { FronteraError } from '@frontera-sdk/core/errors'
|
|
|
3
3
|
|
|
4
4
|
import { CliError } from '../errors'
|
|
5
5
|
|
|
6
|
-
/** A row of `GET /v1/
|
|
6
|
+
/** A row of `GET /v1/functions/` — the shell, not any one version of it. */
|
|
7
7
|
export interface AutomationSummary {
|
|
8
8
|
id: string
|
|
9
9
|
slug: string
|
|
@@ -73,7 +73,7 @@ export interface AutomationDeployResult {
|
|
|
73
73
|
runnerLastSeenAt?: string | null
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
/** A row of `GET /v1/
|
|
76
|
+
/** A row of `GET /v1/functions/<slug>/runs`. */
|
|
77
77
|
export interface AutomationRunSummary {
|
|
78
78
|
id: string
|
|
79
79
|
status: string
|
|
@@ -103,7 +103,7 @@ export interface AutomationRunSummary {
|
|
|
103
103
|
input?: Record<string, unknown> | null
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
/** A row of `GET /v1/
|
|
106
|
+
/** A row of `GET /v1/functions/runs/<id>/steps`. */
|
|
107
107
|
export interface AutomationRunStep {
|
|
108
108
|
id: string
|
|
109
109
|
seq: number
|
|
@@ -127,7 +127,7 @@ export interface AutomationRunStep {
|
|
|
127
127
|
detail?: Record<string, unknown>
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
/** `GET /v1/
|
|
130
|
+
/** `GET /v1/functions/registry-status`. */
|
|
131
131
|
export interface RegistryStatus {
|
|
132
132
|
/** The generation the service has. */
|
|
133
133
|
generation: number
|
|
@@ -174,18 +174,18 @@ export class AutomationApi {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
list(): Promise<AutomationSummary[]> {
|
|
177
|
-
return this.client.request<AutomationSummary[]>('/v1/
|
|
177
|
+
return this.client.request<AutomationSummary[]>('/v1/functions/')
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
versions(slug: string): Promise<AutomationVersionSummary[]> {
|
|
181
181
|
return this.client.request<AutomationVersionSummary[]>(
|
|
182
|
-
`/v1/
|
|
182
|
+
`/v1/functions/${encodeURIComponent(slug)}/versions`,
|
|
183
183
|
)
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
promote(slug: string, version: number): Promise<{ slug: string; version: number }> {
|
|
187
187
|
return this.client.request<{ slug: string; version: number }>(
|
|
188
|
-
`/v1/
|
|
188
|
+
`/v1/functions/${encodeURIComponent(slug)}/promote/${version}`,
|
|
189
189
|
{ method: 'POST' },
|
|
190
190
|
)
|
|
191
191
|
}
|
|
@@ -220,7 +220,7 @@ export class AutomationApi {
|
|
|
220
220
|
: version === undefined && input === undefined
|
|
221
221
|
? undefined
|
|
222
222
|
: { ...(version === undefined ? {} : { version }), ...(input === undefined ? {} : { input }) }
|
|
223
|
-
return this.client.request(`/v1/
|
|
223
|
+
return this.client.request(`/v1/functions/${encodeURIComponent(slug)}/run`, {
|
|
224
224
|
method: 'POST',
|
|
225
225
|
body,
|
|
226
226
|
})
|
|
@@ -241,7 +241,7 @@ export class AutomationApi {
|
|
|
241
241
|
leaseMs: number
|
|
242
242
|
}> {
|
|
243
243
|
return this.client.request(
|
|
244
|
-
`/v1/
|
|
244
|
+
`/v1/functions/${encodeURIComponent(slug)}/dev/session`,
|
|
245
245
|
{ method: 'POST', body: { entryFile, holderId } },
|
|
246
246
|
)
|
|
247
247
|
}
|
|
@@ -278,7 +278,7 @@ export class AutomationApi {
|
|
|
278
278
|
}> {
|
|
279
279
|
const q = `sessionId=${encodeURIComponent(sessionId)}&holderId=${encodeURIComponent(holderId)}`
|
|
280
280
|
return this.client.request(
|
|
281
|
-
`/v1/
|
|
281
|
+
`/v1/functions/${encodeURIComponent(slug)}/dev/poll?${q}`,
|
|
282
282
|
{ method: 'POST', body: { manifest } },
|
|
283
283
|
)
|
|
284
284
|
}
|
|
@@ -290,13 +290,13 @@ export class AutomationApi {
|
|
|
290
290
|
): Promise<{ closed: boolean }> {
|
|
291
291
|
const q = `sessionId=${encodeURIComponent(sessionId)}&holderId=${encodeURIComponent(holderId)}`
|
|
292
292
|
return this.client.request(
|
|
293
|
-
`/v1/
|
|
293
|
+
`/v1/functions/${encodeURIComponent(slug)}/dev/session?${q}`,
|
|
294
294
|
{ method: 'DELETE' },
|
|
295
295
|
)
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
registryStatus(): Promise<RegistryStatus> {
|
|
299
|
-
return this.client.request<RegistryStatus>('/v1/
|
|
299
|
+
return this.client.request<RegistryStatus>('/v1/functions/registry-status')
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
/**
|
|
@@ -317,7 +317,7 @@ export class AutomationApi {
|
|
|
317
317
|
if (mode === 'dev') params.set('mode', 'dev')
|
|
318
318
|
const query = params.size > 0 ? `?${params}` : ''
|
|
319
319
|
return this.client.request<AutomationRunSummary[]>(
|
|
320
|
-
`/v1/
|
|
320
|
+
`/v1/functions/${encodeURIComponent(slug)}/runs${query}`,
|
|
321
321
|
)
|
|
322
322
|
}
|
|
323
323
|
|
|
@@ -332,7 +332,7 @@ export class AutomationApi {
|
|
|
332
332
|
*/
|
|
333
333
|
runById(slug: string, runId: string): Promise<AutomationRunSummary> {
|
|
334
334
|
return this.client.request<AutomationRunSummary>(
|
|
335
|
-
`/v1/
|
|
335
|
+
`/v1/functions/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`,
|
|
336
336
|
)
|
|
337
337
|
}
|
|
338
338
|
|
|
@@ -340,7 +340,7 @@ export class AutomationApi {
|
|
|
340
340
|
* nothing. */
|
|
341
341
|
runSteps(runId: string): Promise<AutomationRunStep[]> {
|
|
342
342
|
return this.client.request<AutomationRunStep[]>(
|
|
343
|
-
`/v1/
|
|
343
|
+
`/v1/functions/runs/${encodeURIComponent(runId)}/steps`,
|
|
344
344
|
)
|
|
345
345
|
}
|
|
346
346
|
|
|
@@ -353,7 +353,7 @@ export class AutomationApi {
|
|
|
353
353
|
*/
|
|
354
354
|
bindings(slug: string): Promise<AgentAutomationBinding[]> {
|
|
355
355
|
return this.client.request<AgentAutomationBinding[]>(
|
|
356
|
-
`/v1/
|
|
356
|
+
`/v1/functions/${encodeURIComponent(slug)}/bindings`,
|
|
357
357
|
)
|
|
358
358
|
}
|
|
359
359
|
|
|
@@ -363,7 +363,7 @@ export class AutomationApi {
|
|
|
363
363
|
options: { enabled?: boolean; requiresApproval?: boolean; waitSeconds?: number } = {},
|
|
364
364
|
): Promise<AgentAutomationBinding> {
|
|
365
365
|
return this.client.request<AgentAutomationBinding>(
|
|
366
|
-
`/v1/
|
|
366
|
+
`/v1/functions/${encodeURIComponent(slug)}/bindings/${encodeURIComponent(agentSlug)}`,
|
|
367
367
|
{ method: 'PUT', body: options },
|
|
368
368
|
)
|
|
369
369
|
}
|
|
@@ -371,20 +371,20 @@ export class AutomationApi {
|
|
|
371
371
|
/** The same question from the agent's side: what may this agent start? */
|
|
372
372
|
agentBindings(agentSlug: string): Promise<AgentBindingView[]> {
|
|
373
373
|
return this.client.request<AgentBindingView[]>(
|
|
374
|
-
`/v1/
|
|
374
|
+
`/v1/functions/agent-bindings/${encodeURIComponent(agentSlug)}`,
|
|
375
375
|
)
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
unbind(slug: string, agentSlug: string): Promise<{ unbound: boolean }> {
|
|
379
379
|
return this.client.request<{ unbound: boolean }>(
|
|
380
|
-
`/v1/
|
|
380
|
+
`/v1/functions/${encodeURIComponent(slug)}/bindings/${encodeURIComponent(agentSlug)}`,
|
|
381
381
|
{ method: 'DELETE' },
|
|
382
382
|
)
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
setEnabled(slug: string, enabled: boolean): Promise<{ slug: string; enabled: boolean }> {
|
|
386
386
|
return this.client.request<{ slug: string; enabled: boolean }>(
|
|
387
|
-
`/v1/
|
|
387
|
+
`/v1/functions/${encodeURIComponent(slug)}/enabled`,
|
|
388
388
|
{ method: 'POST', body: { enabled } },
|
|
389
389
|
)
|
|
390
390
|
}
|
|
@@ -401,7 +401,7 @@ export class AutomationApi {
|
|
|
401
401
|
async downloadSource(slug: string, version: number): Promise<Uint8Array> {
|
|
402
402
|
const credential = this.client.config.credential
|
|
403
403
|
const res = await fetch(
|
|
404
|
-
`${this.client.config.apiBaseUrl}/v1/
|
|
404
|
+
`${this.client.config.apiBaseUrl}/v1/functions/${encodeURIComponent(slug)}`
|
|
405
405
|
+ `/versions/${version}/source`,
|
|
406
406
|
{
|
|
407
407
|
headers: {
|
|
@@ -432,8 +432,8 @@ export class AutomationApi {
|
|
|
432
432
|
throw new CliError(message, {
|
|
433
433
|
code: res.status === 404 ? 'NOT_FOUND' : 'REQUEST_FAILED',
|
|
434
434
|
hint: res.status === 404
|
|
435
|
-
? `frontera
|
|
436
|
-
: `frontera
|
|
435
|
+
? `frontera function deploy <file> (from a local copy of ${slug})`
|
|
436
|
+
: `frontera function versions ${slug}`,
|
|
437
437
|
})
|
|
438
438
|
}
|
|
439
439
|
return new Uint8Array(await res.arrayBuffer())
|
|
@@ -457,7 +457,7 @@ export class AutomationApi {
|
|
|
457
457
|
|
|
458
458
|
const credential = this.client.config.credential
|
|
459
459
|
const res = await fetch(
|
|
460
|
-
`${this.client.config.apiBaseUrl}/v1/
|
|
460
|
+
`${this.client.config.apiBaseUrl}/v1/functions/${encodeURIComponent(slug)}/versions`,
|
|
461
461
|
{
|
|
462
462
|
method: 'POST',
|
|
463
463
|
headers: {
|
|
@@ -48,7 +48,7 @@ export const AUTOMATION_SDK_FILES = [
|
|
|
48
48
|
* The files a new automation project contains.
|
|
49
49
|
*
|
|
50
50
|
* The SDK is written into `src/frontera/` rather than declared as a dependency:
|
|
51
|
-
* `@frontera-sdk/
|
|
51
|
+
* `@frontera-sdk/functions` is `private: true` and `workspace:*`, so it resolves
|
|
52
52
|
* nowhere outside this monorepo — which is the entire problem Phase B exists to
|
|
53
53
|
* solve. `package.json` therefore asks for nothing but public npm.
|
|
54
54
|
*/
|
|
@@ -77,7 +77,7 @@ export function automationScaffoldFiles(name: string): Record<string, string> {
|
|
|
77
77
|
// A handler is testable without deploying — `createTestContext` is what
|
|
78
78
|
// the starter test uses — so the project ships with somewhere to run it.
|
|
79
79
|
test: 'bun test',
|
|
80
|
-
deploy: 'frontera
|
|
80
|
+
deploy: 'frontera function deploy src/index.ts',
|
|
81
81
|
},
|
|
82
82
|
dependencies: {
|
|
83
83
|
// The vendored `frontera/automation/manifest.ts` imports it to validate
|
|
@@ -107,8 +107,8 @@ export function automationScaffoldFiles(name: string): Record<string, string> {
|
|
|
107
107
|
// detail, so publishing the packages later is deleting these lines.
|
|
108
108
|
baseUrl: '.',
|
|
109
109
|
paths: {
|
|
110
|
-
'@frontera-sdk/
|
|
111
|
-
'@frontera-sdk/
|
|
110
|
+
'@frontera-sdk/functions': ['./src/frontera/automation/index.ts'],
|
|
111
|
+
'@frontera-sdk/functions/*': ['./src/frontera/automation/*'],
|
|
112
112
|
'@frontera-sdk/blueprint/types': ['./src/frontera/blueprint/types.ts'],
|
|
113
113
|
'@frontera-sdk/blueprint/*': ['./src/frontera/blueprint/*'],
|
|
114
114
|
},
|
|
@@ -116,7 +116,7 @@ export function automationScaffoldFiles(name: string): Record<string, string> {
|
|
|
116
116
|
include: ['src'],
|
|
117
117
|
}, null, 2) + '\n'
|
|
118
118
|
|
|
119
|
-
out['src/index.ts'] = `import { automation } from '@frontera-sdk/
|
|
119
|
+
out['src/index.ts'] = `import { automation } from '@frontera-sdk/functions'
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
122
|
* The object type this reads. CHANGE THIS to one of yours —
|
|
@@ -177,7 +177,7 @@ export default automation(
|
|
|
177
177
|
`
|
|
178
178
|
|
|
179
179
|
out['src/index.test.ts'] = `import { expect, test } from 'bun:test'
|
|
180
|
-
import { createTestContext } from '@frontera-sdk/
|
|
180
|
+
import { createTestContext } from '@frontera-sdk/functions'
|
|
181
181
|
|
|
182
182
|
import automation, { OBJECT_TYPE } from './index'
|
|
183
183
|
|
|
@@ -215,7 +215,7 @@ test('handles rows', async () => {
|
|
|
215
215
|
\`\`\`bash
|
|
216
216
|
bun install
|
|
217
217
|
bun run typecheck
|
|
218
|
-
frontera
|
|
218
|
+
frontera function deploy src/index.ts
|
|
219
219
|
\`\`\`
|
|
220
220
|
|
|
221
221
|
The SDK is vendored into \`src/frontera/\` and resolved by the \`paths\` in
|
|
@@ -223,7 +223,7 @@ The SDK is vendored into \`src/frontera/\` and resolved by the \`paths\` in
|
|
|
223
223
|
packages are unpublished, and the vendored copy is what makes this project build
|
|
224
224
|
on a machine that has never seen the Frontera monorepo.
|
|
225
225
|
|
|
226
|
-
\`frontera
|
|
226
|
+
\`frontera function pull ${name}\` recovers this project from any deployed
|
|
227
227
|
version. A \`.env\` here is never uploaded.
|
|
228
228
|
`
|
|
229
229
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `frontera
|
|
2
|
+
* `frontera function bind|unbind|bindings` — the operator's half of making an
|
|
3
3
|
* automation callable from a conversation.
|
|
4
4
|
*
|
|
5
5
|
* The author's half is in the manifest (`trigger: { agent: true }`, plus a
|
|
@@ -21,7 +21,7 @@ function requireSlug(ctx: CommandContext): string {
|
|
|
21
21
|
if (!slug) {
|
|
22
22
|
throw new UsageError(
|
|
23
23
|
'missing <slug>',
|
|
24
|
-
'run `frontera
|
|
24
|
+
'run `frontera function list` — then pass the slug of the one you mean',
|
|
25
25
|
)
|
|
26
26
|
}
|
|
27
27
|
return slug
|
|
@@ -40,15 +40,15 @@ function requireAgent(ctx: CommandContext): string {
|
|
|
40
40
|
|
|
41
41
|
const bind: Command = {
|
|
42
42
|
meta: {
|
|
43
|
-
noun: '
|
|
43
|
+
noun: 'function',
|
|
44
44
|
verb: 'bind',
|
|
45
45
|
args: [{ name: 'slug', required: true, description: 'automation slug' }],
|
|
46
46
|
flags: { agent: 'string', approval: 'string', wait: 'string' },
|
|
47
47
|
summary: 'Let one agent run this automation from a conversation',
|
|
48
48
|
examples: [
|
|
49
|
-
'frontera
|
|
50
|
-
'frontera
|
|
51
|
-
'frontera
|
|
49
|
+
'frontera function bind reconcile-invoices --agent finance-analyst',
|
|
50
|
+
'frontera function bind reconcile-invoices --agent analyst --approval auto',
|
|
51
|
+
'frontera function bind reconcile-invoices --agent analyst --wait 120',
|
|
52
52
|
],
|
|
53
53
|
},
|
|
54
54
|
|
|
@@ -93,12 +93,12 @@ const bind: Command = {
|
|
|
93
93
|
|
|
94
94
|
const unbind: Command = {
|
|
95
95
|
meta: {
|
|
96
|
-
noun: '
|
|
96
|
+
noun: 'function',
|
|
97
97
|
verb: 'unbind',
|
|
98
98
|
args: [{ name: 'slug', required: true, description: 'automation slug' }],
|
|
99
99
|
flags: { agent: 'string' },
|
|
100
100
|
summary: 'Stop one agent being able to run this automation',
|
|
101
|
-
examples: ['frontera
|
|
101
|
+
examples: ['frontera function unbind reconcile-invoices --agent finance-analyst'],
|
|
102
102
|
},
|
|
103
103
|
|
|
104
104
|
async run(ctx) {
|
|
@@ -117,7 +117,7 @@ const unbind: Command = {
|
|
|
117
117
|
*/
|
|
118
118
|
const bindings: Command = {
|
|
119
119
|
meta: {
|
|
120
|
-
noun: '
|
|
120
|
+
noun: 'function',
|
|
121
121
|
verb: 'bindings',
|
|
122
122
|
args: [
|
|
123
123
|
{
|
|
@@ -129,8 +129,8 @@ const bindings: Command = {
|
|
|
129
129
|
flags: { agent: 'string' },
|
|
130
130
|
summary: 'List the agents that may run an automation, or what one agent may run',
|
|
131
131
|
examples: [
|
|
132
|
-
'frontera
|
|
133
|
-
'frontera
|
|
132
|
+
'frontera function bindings reconcile-invoices',
|
|
133
|
+
'frontera function bindings --agent finance-analyst',
|
|
134
134
|
],
|
|
135
135
|
},
|
|
136
136
|
|
|
@@ -2,15 +2,15 @@ import { mkdtempSync, statSync } from 'node:fs'
|
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
3
|
import { join, resolve } from 'node:path'
|
|
4
4
|
|
|
5
|
-
import { buildContext } from '@frontera-sdk/
|
|
5
|
+
import { buildContext } from '@frontera-sdk/functions/runtime'
|
|
6
6
|
import { AutomationApi } from '../../api/automation-api'
|
|
7
7
|
import { CliError, UsageError } from '../../errors'
|
|
8
8
|
import { type Command, type CommandContext } from '../types'
|
|
9
|
-
import { validateInputValue, validateManifest, type InputsSchema } from '@frontera-sdk/
|
|
9
|
+
import { validateInputValue, validateManifest, type InputsSchema } from '@frontera-sdk/functions'
|
|
10
10
|
import { buildAndExtract } from './build-entry'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* `frontera
|
|
13
|
+
* `frontera function dev` — run the handler in THIS file, from this machine,
|
|
14
14
|
* triggered from the Console.
|
|
15
15
|
*
|
|
16
16
|
* The loop it replaces is a deploy: build, upload, mint a version, bump the
|
|
@@ -272,7 +272,7 @@ async function finishDevRun(
|
|
|
272
272
|
runToken: string,
|
|
273
273
|
body: { status: 'succeeded' | 'failed'; result?: unknown; errorMessage?: string },
|
|
274
274
|
): Promise<void> {
|
|
275
|
-
await fetch(`${apiUrl}/v1/
|
|
275
|
+
await fetch(`${apiUrl}/v1/functions/runner/runs/${runId}/finish`, {
|
|
276
276
|
method: 'POST',
|
|
277
277
|
headers: { 'content-type': 'application/json', 'x-automation-run-token': runToken },
|
|
278
278
|
body: JSON.stringify(body),
|
|
@@ -322,7 +322,7 @@ export function devStartupManifestGate(errors: string[]): { blocking: string[];
|
|
|
322
322
|
|
|
323
323
|
export const automationDev: Command = {
|
|
324
324
|
meta: {
|
|
325
|
-
noun: '
|
|
325
|
+
noun: 'function',
|
|
326
326
|
verb: 'dev',
|
|
327
327
|
args: [
|
|
328
328
|
{
|
|
@@ -333,13 +333,13 @@ export const automationDev: Command = {
|
|
|
333
333
|
],
|
|
334
334
|
flags: {},
|
|
335
335
|
summary: 'Serve this file to dev runs — no deploy, no version',
|
|
336
|
-
examples: ['frontera
|
|
336
|
+
examples: ['frontera function dev src/index.ts'],
|
|
337
337
|
},
|
|
338
338
|
|
|
339
339
|
async run(ctx: CommandContext) {
|
|
340
340
|
const file = ctx.positional[0]
|
|
341
341
|
if (!file) {
|
|
342
|
-
throw new UsageError('missing <file>', 'frontera
|
|
342
|
+
throw new UsageError('missing <file>', 'frontera function dev <file>')
|
|
343
343
|
}
|
|
344
344
|
const entry = resolve(file)
|
|
345
345
|
const out = ctx.output
|
|
@@ -403,7 +403,7 @@ export const automationDev: Command = {
|
|
|
403
403
|
// came back empty.
|
|
404
404
|
out.note('[dev] runs are DRY — ctx.agent, ctx.plugin, ctx.http and ctx.blueprint return empty and send nothing')
|
|
405
405
|
if (session.created) out.note(`[dev] created the automation "${slug}" (no version yet)`)
|
|
406
|
-
out.note(`[dev] waiting — press Run dev in the Console, or \`frontera
|
|
406
|
+
out.note(`[dev] waiting — press Run dev in the Console, or \`frontera function run ${slug} --dev\``)
|
|
407
407
|
|
|
408
408
|
const workDir = mkdtempSync(join(tmpdir(), 'frontera-dev-'))
|
|
409
409
|
let builds = 0
|
|
@@ -2,7 +2,7 @@ import { existsSync, mkdtempSync, rmSync } from 'node:fs'
|
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
3
|
import { join, resolve } from 'node:path'
|
|
4
4
|
|
|
5
|
-
import { validateManifest } from '@frontera-sdk/
|
|
5
|
+
import { validateManifest } from '@frontera-sdk/functions'
|
|
6
6
|
|
|
7
7
|
import { AutomationApi } from '../../api/automation-api'
|
|
8
8
|
import { UsageError } from '../../errors'
|
|
@@ -26,7 +26,7 @@ function requireSlug(ctx: CommandContext): string {
|
|
|
26
26
|
if (!slug) {
|
|
27
27
|
throw new UsageError(
|
|
28
28
|
'missing <slug>',
|
|
29
|
-
'run `frontera
|
|
29
|
+
'run `frontera function list` — then pass the slug of the one you mean',
|
|
30
30
|
)
|
|
31
31
|
}
|
|
32
32
|
return slug
|
|
@@ -64,7 +64,7 @@ export function runnerWarningFor(
|
|
|
64
64
|
|
|
65
65
|
const deploy: Command = {
|
|
66
66
|
meta: {
|
|
67
|
-
noun: '
|
|
67
|
+
noun: 'function',
|
|
68
68
|
verb: 'deploy',
|
|
69
69
|
args: [
|
|
70
70
|
{
|
|
@@ -76,15 +76,15 @@ const deploy: Command = {
|
|
|
76
76
|
flags: { 'no-promote': 'boolean' },
|
|
77
77
|
summary: 'Build and deploy an automation, promoting it live by default',
|
|
78
78
|
examples: [
|
|
79
|
-
'frontera
|
|
80
|
-
'frontera
|
|
79
|
+
'frontera function deploy src/daily-digest.ts',
|
|
80
|
+
'frontera function deploy src/daily-digest.ts --no-promote',
|
|
81
81
|
],
|
|
82
82
|
},
|
|
83
83
|
|
|
84
84
|
async run(ctx) {
|
|
85
85
|
const file = ctx.positional[0]
|
|
86
86
|
if (!file) {
|
|
87
|
-
throw new UsageError('missing <file>', 'frontera
|
|
87
|
+
throw new UsageError('missing <file>', 'frontera function deploy <file>')
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
const { manifest, code } = await buildAndExtract(file)
|
|
@@ -128,8 +128,8 @@ const deploy: Command = {
|
|
|
128
128
|
ctx.output.note(
|
|
129
129
|
' warning: no automation project found (no package.json above '
|
|
130
130
|
+ `${file}, or the nearest one is a workspace root) — deploying WITHOUT `
|
|
131
|
-
+ 'source, so `frontera
|
|
132
|
-
+ 'Run `frontera
|
|
131
|
+
+ 'source, so `frontera function pull` will not work for this version. '
|
|
132
|
+
+ 'Run `frontera function init` to author in a project.',
|
|
133
133
|
)
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -158,19 +158,19 @@ const deploy: Command = {
|
|
|
158
158
|
`Deployed ${slug} v${result.version}` +
|
|
159
159
|
(result.promoted
|
|
160
160
|
? ' and promoted it'
|
|
161
|
-
: ` (not promoted — use \`frontera
|
|
161
|
+
: ` (not promoted — use \`frontera function promote ${slug} ${result.version}\`)`),
|
|
162
162
|
}
|
|
163
163
|
},
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
const list: Command = {
|
|
167
167
|
meta: {
|
|
168
|
-
noun: '
|
|
168
|
+
noun: 'function',
|
|
169
169
|
verb: 'list',
|
|
170
170
|
args: [],
|
|
171
171
|
flags: {},
|
|
172
172
|
summary: 'List automations in this workspace',
|
|
173
|
-
examples: ['frontera
|
|
173
|
+
examples: ['frontera function list', 'frontera function list --json'],
|
|
174
174
|
},
|
|
175
175
|
|
|
176
176
|
async run(ctx) {
|
|
@@ -200,12 +200,12 @@ const list: Command = {
|
|
|
200
200
|
|
|
201
201
|
const versions: Command = {
|
|
202
202
|
meta: {
|
|
203
|
-
noun: '
|
|
203
|
+
noun: 'function',
|
|
204
204
|
verb: 'versions',
|
|
205
|
-
args: [{ name: 'slug', required: true, description: 'automation slug, from `frontera
|
|
205
|
+
args: [{ name: 'slug', required: true, description: 'automation slug, from `frontera function list`' }],
|
|
206
206
|
flags: {},
|
|
207
207
|
summary: 'List deployed versions, newest first; * marks the live one',
|
|
208
|
-
examples: ['frontera
|
|
208
|
+
examples: ['frontera function versions daily-digest'],
|
|
209
209
|
},
|
|
210
210
|
|
|
211
211
|
async run(ctx) {
|
|
@@ -245,15 +245,15 @@ const versions: Command = {
|
|
|
245
245
|
|
|
246
246
|
const promote: Command = {
|
|
247
247
|
meta: {
|
|
248
|
-
noun: '
|
|
248
|
+
noun: 'function',
|
|
249
249
|
verb: 'promote',
|
|
250
250
|
args: [
|
|
251
251
|
{ name: 'slug', required: true, description: 'automation slug' },
|
|
252
|
-
{ name: 'version', required: true, description: 'version number, from `frontera
|
|
252
|
+
{ name: 'version', required: true, description: 'version number, from `frontera function versions`' },
|
|
253
253
|
],
|
|
254
254
|
flags: {},
|
|
255
255
|
summary: 'Move the live pointer to an already-deployed version',
|
|
256
|
-
examples: ['frontera
|
|
256
|
+
examples: ['frontera function promote daily-digest 3'],
|
|
257
257
|
},
|
|
258
258
|
|
|
259
259
|
async run(ctx) {
|
|
@@ -265,7 +265,7 @@ const promote: Command = {
|
|
|
265
265
|
if (raw === undefined || !Number.isInteger(version)) {
|
|
266
266
|
throw new UsageError(
|
|
267
267
|
`version must be an integer, got ${raw === undefined ? '(none)' : `"${raw}"`}`,
|
|
268
|
-
`run \`frontera
|
|
268
|
+
`run \`frontera function versions ${slug}\` to see the versions that exist`,
|
|
269
269
|
)
|
|
270
270
|
}
|
|
271
271
|
|
|
@@ -283,12 +283,12 @@ function enabledCommand(verb: 'enable' | 'disable', summary: string): Command {
|
|
|
283
283
|
const enabled = verb === 'enable'
|
|
284
284
|
return {
|
|
285
285
|
meta: {
|
|
286
|
-
noun: '
|
|
286
|
+
noun: 'function',
|
|
287
287
|
verb,
|
|
288
288
|
args: [{ name: 'slug', required: true, description: 'automation slug' }],
|
|
289
289
|
flags: {},
|
|
290
290
|
summary,
|
|
291
|
-
examples: [`frontera
|
|
291
|
+
examples: [`frontera function ${verb} daily-digest`],
|
|
292
292
|
},
|
|
293
293
|
|
|
294
294
|
async run(ctx) {
|
|
@@ -6,7 +6,7 @@ import type { Command } from '../types'
|
|
|
6
6
|
|
|
7
7
|
export const automationInit: Command = {
|
|
8
8
|
meta: {
|
|
9
|
-
noun: '
|
|
9
|
+
noun: 'function',
|
|
10
10
|
verb: 'init',
|
|
11
11
|
args: [{
|
|
12
12
|
name: 'name',
|
|
@@ -15,14 +15,14 @@ export const automationInit: Command = {
|
|
|
15
15
|
}],
|
|
16
16
|
flags: { dir: 'string' },
|
|
17
17
|
summary: 'Scaffold a new automation project',
|
|
18
|
-
examples: ['frontera
|
|
18
|
+
examples: ['frontera function init daily-digest'],
|
|
19
19
|
// Scaffolding must work before a credential exists.
|
|
20
20
|
offline: true,
|
|
21
21
|
},
|
|
22
22
|
|
|
23
23
|
async run(ctx) {
|
|
24
24
|
const name = ctx.positional[0]
|
|
25
|
-
if (!name) throw new UsageError('missing <name>', 'frontera
|
|
25
|
+
if (!name) throw new UsageError('missing <name>', 'frontera function init <name>')
|
|
26
26
|
|
|
27
27
|
const base = typeof ctx.flags.dir === 'string' ? ctx.flags.dir : ctx.cwd
|
|
28
28
|
const target = join(base, name)
|
|
@@ -45,7 +45,7 @@ export const automationInit: Command = {
|
|
|
45
45
|
` cd ${name}`,
|
|
46
46
|
' bun install',
|
|
47
47
|
' bun run typecheck',
|
|
48
|
-
' frontera
|
|
48
|
+
' frontera function deploy src/index.ts',
|
|
49
49
|
].join('\n'),
|
|
50
50
|
}
|
|
51
51
|
},
|
|
@@ -10,18 +10,18 @@ import { flagBool, type Command } from '../types'
|
|
|
10
10
|
export function parseTarget(raw: string): { slug: string; version: number | null } {
|
|
11
11
|
const at = raw.lastIndexOf('@')
|
|
12
12
|
if (at === -1) {
|
|
13
|
-
if (!raw) throw new UsageError('missing slug', 'frontera
|
|
13
|
+
if (!raw) throw new UsageError('missing slug', 'frontera function pull <slug>[@version]')
|
|
14
14
|
return { slug: raw, version: null }
|
|
15
15
|
}
|
|
16
16
|
const slug = raw.slice(0, at)
|
|
17
17
|
const rest = raw.slice(at + 1)
|
|
18
|
-
if (!slug) throw new UsageError('missing slug', 'frontera
|
|
18
|
+
if (!slug) throw new UsageError('missing slug', 'frontera function pull <slug>[@version]')
|
|
19
19
|
if (!/^\d+$/.test(rest)) {
|
|
20
20
|
// Not silently falling back to latest: handing back a different version than
|
|
21
21
|
// the one named is the failure mode this phase exists to prevent.
|
|
22
22
|
throw new UsageError(
|
|
23
23
|
`version must be a number, got "${rest}"`,
|
|
24
|
-
'frontera
|
|
24
|
+
'frontera function pull <slug>@3',
|
|
25
25
|
)
|
|
26
26
|
}
|
|
27
27
|
return { slug, version: Number(rest) }
|
|
@@ -65,7 +65,7 @@ export function resolveTargetDir(
|
|
|
65
65
|
|
|
66
66
|
export const automationPull: Command = {
|
|
67
67
|
meta: {
|
|
68
|
-
noun: '
|
|
68
|
+
noun: 'function',
|
|
69
69
|
verb: 'pull',
|
|
70
70
|
args: [{
|
|
71
71
|
name: 'slug[@version]',
|
|
@@ -75,14 +75,14 @@ export const automationPull: Command = {
|
|
|
75
75
|
flags: { dir: 'string', force: 'boolean' },
|
|
76
76
|
summary: 'Hydrate an editable project from a deployed version',
|
|
77
77
|
examples: [
|
|
78
|
-
'frontera
|
|
79
|
-
'frontera
|
|
78
|
+
'frontera function pull daily-digest',
|
|
79
|
+
'frontera function pull daily-digest@3 --dir ./recovered',
|
|
80
80
|
],
|
|
81
81
|
},
|
|
82
82
|
|
|
83
83
|
async run(ctx) {
|
|
84
84
|
const raw = ctx.positional[0]
|
|
85
|
-
if (!raw) throw new UsageError('missing <slug>', 'frontera
|
|
85
|
+
if (!raw) throw new UsageError('missing <slug>', 'frontera function pull <slug>[@version]')
|
|
86
86
|
const { slug, version } = parseTarget(raw)
|
|
87
87
|
|
|
88
88
|
const client = new AutomationApi(ctx.apiUrl, ctx.token, ctx.workspaceId)
|
|
@@ -117,7 +117,7 @@ export const automationPull: Command = {
|
|
|
117
117
|
'',
|
|
118
118
|
` cd ${target}`,
|
|
119
119
|
' bun install',
|
|
120
|
-
' frontera
|
|
120
|
+
' frontera function deploy src/index.ts',
|
|
121
121
|
].join('\n'),
|
|
122
122
|
}
|
|
123
123
|
},
|