@usenaive-sdk/blueprints 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1674 @@
1
+ // ../core/src/id.ts
2
+ var ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
3
+ var SUFFIX_LENGTH = 26;
4
+ var SUFFIX_PATTERN = new RegExp(`^[${ALPHABET}]{${SUFFIX_LENGTH},}$`);
5
+ function isId(prefix, value) {
6
+ const head = `${prefix}_`;
7
+ if (!value.startsWith(head)) return false;
8
+ return SUFFIX_PATTERN.test(value.slice(head.length).toLowerCase());
9
+ }
10
+
11
+ // ../core/src/session-token.ts
12
+ var SESSION_TTL_SECONDS = 30 * 24 * 60 * 60;
13
+ var encoder = new TextEncoder();
14
+
15
+ // ../core/src/schema/audit-actions.ts
16
+ var AUDIT_ACTIONS_SHIPPED = [
17
+ "agent.updated",
18
+ "agent.rolled_back",
19
+ // deprecated alias of `agent.rollback`; kept so historic rows still validate
20
+ "api_key.created",
21
+ "api_key.rotated",
22
+ "api_key.revoked",
23
+ "member.role_changed",
24
+ "member.removed",
25
+ "policy.denied",
26
+ "credits.topped_up",
27
+ "webhook.secret_rotated",
28
+ "session.interrupted"
29
+ ];
30
+ var AUDIT_ACTIONS_P1 = [
31
+ "agent.created",
32
+ "agent.deleted",
33
+ "agent.rollback",
34
+ "computer.created",
35
+ "computer.deleted",
36
+ "computer.paused",
37
+ "computer.resumed",
38
+ "computer.snapshot_created",
39
+ "deployment.created",
40
+ "deployment.updated",
41
+ "deployment.deleted",
42
+ "deployment.paused",
43
+ "deployment.resumed",
44
+ "deployment.run",
45
+ "file.created",
46
+ "file.published",
47
+ "file.deleted",
48
+ "member.invited",
49
+ "org.updated",
50
+ "subscription.updated",
51
+ "subscription.canceled",
52
+ "session.budget_updated",
53
+ "session.cancelled",
54
+ "skill.created",
55
+ "skill.version_created",
56
+ "skill.deleted",
57
+ "webhook.created",
58
+ "webhook.updated",
59
+ "webhook.deleted"
60
+ ];
61
+ var AUDIT_ACTIONS_P2 = [
62
+ "identity.created",
63
+ "identity.updated",
64
+ "identity.deleted",
65
+ "identity.granted",
66
+ "identity.revoked",
67
+ "vault.created",
68
+ "vault.deleted",
69
+ "credential.sealed",
70
+ "credential.destroyed",
71
+ "connection.created",
72
+ "connection.disconnected",
73
+ "connection.auth_config_created",
74
+ "connection.auth_config_deleted",
75
+ "domain.created",
76
+ "domain.verified",
77
+ "domain.deleted",
78
+ "email.created",
79
+ "email.deleted",
80
+ "phone.created",
81
+ "phone.deleted",
82
+ "social.activated",
83
+ "social.post_created",
84
+ "social.post_deleted"
85
+ ];
86
+ var AUDIT_ACTIONS_P3 = [
87
+ "app.created",
88
+ "app.updated",
89
+ "app.deleted",
90
+ "app.deployed",
91
+ "app.secret_set",
92
+ "app.secret_deleted",
93
+ "app.domain_connected",
94
+ "app.domain_disconnected",
95
+ "app.db_queried"
96
+ ];
97
+ var AUDIT_ACTIONS = [
98
+ ...AUDIT_ACTIONS_SHIPPED,
99
+ ...AUDIT_ACTIONS_P1,
100
+ ...AUDIT_ACTIONS_P2,
101
+ ...AUDIT_ACTIONS_P3
102
+ ];
103
+
104
+ // ../core/src/schema/common.ts
105
+ import { z } from "zod";
106
+ var idOf = (prefix) => z.string().refine((value) => isId(prefix, value), { error: `expected a ${prefix}_ id` });
107
+ var Timestamp = z.iso.datetime({ offset: true });
108
+ var MicroUsdField = z.number().int();
109
+ var Metadata = z.record(z.string(), z.unknown());
110
+ var JsonSchemaDocument = z.record(z.string(), z.unknown()).refine((schema) => schema.type === "object", {
111
+ error: 'an output schema must be a JSON Schema with "type": "object"'
112
+ }).refine(
113
+ (schema) => typeof schema.properties === "object" && schema.properties !== null && Object.keys(schema.properties).length > 0,
114
+ { error: "an output schema must declare at least one property" }
115
+ );
116
+ var WindowSchema = z.enum(["immediate", "priority", "loose"]);
117
+ var TOKEN_TIERS = ["input", "cache_write", "cache_read", "output", "reasoning"];
118
+ var TokenTierSchema = z.enum(TOKEN_TIERS);
119
+ var TOOL_TIERS = ["search", "media", "social"];
120
+ var LINE_ITEM_TIERS = [...TOKEN_TIERS, "computer", ...TOOL_TIERS];
121
+ var LineItemTierSchema = z.enum(LINE_ITEM_TIERS);
122
+ var TokenUsageSchema = z.object({
123
+ input: z.number().int().nonnegative(),
124
+ cache_write: z.number().int().nonnegative(),
125
+ cache_read: z.number().int().nonnegative(),
126
+ output: z.number().int().nonnegative(),
127
+ reasoning: z.number().int().nonnegative()
128
+ });
129
+ var PrincipalSchema = z.discriminatedUnion("type", [
130
+ z.object({ type: z.literal("user"), id: idOf("usr") }),
131
+ z.object({ type: z.literal("key"), id: idOf("key") }),
132
+ z.object({ type: z.literal("system"), id: z.string().min(1) })
133
+ ]);
134
+ var AuditActionSchema = z.enum(AUDIT_ACTIONS);
135
+
136
+ // ../core/src/schema/harness.ts
137
+ import { z as z2 } from "zod";
138
+ var HARNESS_IDS = ["pi", "claude_code", "vetta", "hermes"];
139
+ var HarnessIdSchema = z2.enum(HARNESS_IDS);
140
+
141
+ // ../core/src/schema/agent.ts
142
+ import { z as z3 } from "zod";
143
+ var ToolPermissionSchema = z3.enum(["allow", "ask", "deny"]);
144
+ var ToolConfigSchema = z3.object({
145
+ enabled: z3.boolean(),
146
+ permission: ToolPermissionSchema,
147
+ config: z3.record(z3.string(), z3.unknown()).optional()
148
+ });
149
+ var ToolsetSchema = z3.object({
150
+ default_config: z3.object({ permission: ToolPermissionSchema }),
151
+ configs: z3.record(z3.string(), ToolConfigSchema)
152
+ });
153
+ var BudgetSchema = z3.object({
154
+ cap_micro_usd: MicroUsdField.nonnegative(),
155
+ max_task_micro_usd: MicroUsdField.nonnegative(),
156
+ period: z3.enum(["day", "week", "month"])
157
+ });
158
+ var SkillRefSchema = z3.string().regex(/^[a-z0-9][a-z0-9-]*(@\d+)?$/, {
159
+ error: "expected `slug` or `slug@N`"
160
+ });
161
+ var McpServerSchema = z3.object({
162
+ name: z3.string(),
163
+ url: z3.url(),
164
+ headers: z3.record(z3.string(), z3.string()).optional()
165
+ });
166
+ var RosterEntrySchema = z3.union([
167
+ idOf("agt").transform((id) => ({ type: "agent", id })),
168
+ z3.object({
169
+ type: z3.literal("agent"),
170
+ id: idOf("agt"),
171
+ version: z3.number().int().positive().optional()
172
+ }),
173
+ z3.object({ type: z3.literal("self") }),
174
+ z3.object({ type: z3.literal("advisor"), model: z3.string().min(1) })
175
+ ]);
176
+ var SINGLETON_ENTRIES = ["self", "advisor"];
177
+ var MultiagentSchema = z3.object({
178
+ type: z3.literal("coordinator"),
179
+ agents: z3.array(RosterEntrySchema).min(1).max(20).refine(
180
+ (roster) => SINGLETON_ENTRIES.every((t) => roster.filter((e) => e.type === t).length <= 1),
181
+ { error: "a roster carries at most one `self` and at most one `advisor` entry" }
182
+ ),
183
+ board: z3.boolean().optional()
184
+ });
185
+ var MultiagentField = z3.union([z3.boolean(), MultiagentSchema, z3.null()]);
186
+ var agentConfigShape = {
187
+ model: z3.string(),
188
+ window: WindowSchema,
189
+ system: z3.string(),
190
+ tools: ToolsetSchema,
191
+ mcp_servers: z3.array(McpServerSchema),
192
+ skills: z3.array(SkillRefSchema),
193
+ multiagent: MultiagentField,
194
+ budget: BudgetSchema,
195
+ output_schema: JsonSchemaDocument.nullable(),
196
+ structured_output_required: z3.boolean(),
197
+ description: z3.string().nullable(),
198
+ metadata: Metadata
199
+ };
200
+ var AgentVersionSchema = z3.object({
201
+ version: z3.number().int().positive(),
202
+ created_at: Timestamp,
203
+ created_by: PrincipalSchema,
204
+ ...agentConfigShape
205
+ });
206
+ var AgentSchema = z3.object({
207
+ id: idOf("agt"),
208
+ object: z3.literal("agent"),
209
+ name: z3.string(),
210
+ /** The agent loop this agent runs (§19). Chosen at create; the catalog lives in `harness.ts`. */
211
+ harness: HarnessIdSchema,
212
+ current_version: z3.number().int().positive(),
213
+ created_at: Timestamp,
214
+ ...agentConfigShape
215
+ });
216
+
217
+ // ../core/src/schema/session.ts
218
+ import { z as z4 } from "zod";
219
+ var SessionStatusSchema = z4.enum([
220
+ "queued",
221
+ "running",
222
+ "idle",
223
+ "completed",
224
+ "failed",
225
+ "cancelled"
226
+ ]);
227
+ var StopReasonSchema = z4.enum([
228
+ "end_turn",
229
+ "awaiting_input",
230
+ "awaiting_approval",
231
+ "budget_paused",
232
+ "interrupted",
233
+ "error",
234
+ "max_iterations",
235
+ /** A turn produced no message and no tool call — honest failure, never `end_turn` (entry 4). */
236
+ "context_exhausted",
237
+ /**
238
+ * A coordinator yielded because every delegated child it is waiting on is still running (§28).
239
+ * Distinct from `awaiting_input`: nobody is being asked for anything, so no human can unblock it —
240
+ * the child's completion does. Reporting `idle`/`end_turn` here would claim the task was finished.
241
+ */
242
+ "awaiting_delegation"
243
+ ]);
244
+ var ThreadRoleSchema = z4.enum(["coordinator", "member"]);
245
+ var MessageSchema = z4.object({
246
+ role: z4.enum(["system", "user", "assistant", "tool"]),
247
+ content: z4.string(),
248
+ tool_call_id: z4.string().optional()
249
+ });
250
+ var ToolResultSchema = z4.object({
251
+ output: z4.unknown(),
252
+ is_error: z4.boolean().optional(),
253
+ content: z4.string().optional()
254
+ });
255
+ var PendingActionSchema = z4.object({
256
+ tool_call_id: z4.string(),
257
+ name: z4.string(),
258
+ args: z4.record(z4.string(), z4.unknown())
259
+ });
260
+ var SessionSchema = z4.object({
261
+ id: idOf("ses"),
262
+ object: z4.literal("session"),
263
+ agent_id: idOf("agt"),
264
+ agent_version: z4.number().int().positive(),
265
+ deployment_id: idOf("dep").nullable(),
266
+ /**
267
+ * The persona this session acts as (`canonical-spec §22`). Chosen per task from the personas the
268
+ * agent holds a grant to, and `null` when the caller named none — an agent with several
269
+ * identities is not silently given one.
270
+ */
271
+ identity_id: idOf("idn").nullable(),
272
+ /**
273
+ * The sandbox this session provisioned, or `null` when its toolset needs none. It is a real
274
+ * `Computer`: it lists under `GET /v1/computers`, takes `exec`/`snapshot`, and is destroyed when
275
+ * the session can no longer run a turn (ADR-0160).
276
+ */
277
+ computer_id: idOf("cmp").nullable(),
278
+ /**
279
+ * The delegation lineage (§28). `parent_session_id` is the thread that delegated this one and
280
+ * `root_session_id` the coordinator at the top of the tree; both `null` on a session a customer
281
+ * started. A child is addressable on its own but is excluded from the default `GET /v1/sessions`
282
+ * listing, so a team of ten does not bury the ten tasks the customer actually asked for.
283
+ */
284
+ parent_session_id: idOf("ses").nullable(),
285
+ root_session_id: idOf("ses").nullable(),
286
+ thread_role: ThreadRoleSchema.nullable(),
287
+ status: SessionStatusSchema,
288
+ stop_reason: StopReasonSchema.nullable(),
289
+ window: WindowSchema,
290
+ budget_micro_usd: MicroUsdField.nonnegative(),
291
+ consumed_micro_usd: MicroUsdField.nonnegative(),
292
+ token_usage: TokenUsageSchema,
293
+ pending_actions: z4.array(PendingActionSchema),
294
+ output_schema: JsonSchemaDocument.nullable(),
295
+ structured_output: z4.record(z4.string(), z4.unknown()).nullable(),
296
+ last_seq: z4.number().int().nonnegative(),
297
+ metadata: Metadata,
298
+ started_at: Timestamp.nullable(),
299
+ ended_at: Timestamp.nullable(),
300
+ created_at: Timestamp
301
+ });
302
+ var SendMessageSchema = z4.object({
303
+ message: z4.string(),
304
+ interrupt: z4.boolean().optional(),
305
+ idempotency_key: z4.string().optional()
306
+ });
307
+ var SendAcceptedSchema = z4.object({
308
+ session_id: z4.string(),
309
+ status: SessionStatusSchema,
310
+ accepted_seq: z4.number().int().nonnegative()
311
+ });
312
+ var ToolConfirmationSchema = z4.object({
313
+ tool_call_id: z4.string(),
314
+ decision: z4.enum(["allow", "deny"]),
315
+ reason: z4.string().optional()
316
+ });
317
+
318
+ // ../core/src/schema/event.ts
319
+ import { z as z5 } from "zod";
320
+ var EventTypeSchema = z5.enum([
321
+ "message.delta",
322
+ "message.completed",
323
+ "tool.started",
324
+ "tool.completed",
325
+ "tool.confirm",
326
+ "session.running",
327
+ "session.usage",
328
+ "session.idle",
329
+ "budget.exceeded",
330
+ "span.model_request.start",
331
+ "span.model_request.end",
332
+ /**
333
+ * A delegated thread's lifecycle, on the **coordinator's** stream (§28). The child's own
334
+ * `ses_…` rides inside `data.thread_id` — the event schema gains no field and `vetta.event` gains
335
+ * no column, so one reader of `{session_id, seq}` still sees every event in one order.
336
+ */
337
+ "thread.created",
338
+ "thread.running",
339
+ "thread.idle",
340
+ "thread.terminated"
341
+ ]);
342
+ var EventSchema = z5.object({
343
+ id: idOf("evt"),
344
+ seq: z5.number().int().positive(),
345
+ type: EventTypeSchema,
346
+ session_id: idOf("ses"),
347
+ data: z5.record(z5.string(), z5.unknown()),
348
+ created_at: Timestamp
349
+ });
350
+ var ThreadEventDataSchema = z5.looseObject({ thread_id: idOf("ses") });
351
+ var SessionUsageDataSchema = z5.object({
352
+ token_usage: TokenUsageSchema,
353
+ consumed_micro_usd: MicroUsdField.nonnegative(),
354
+ active_seconds: z5.number().nonnegative(),
355
+ tool_calls: z5.number().int().nonnegative()
356
+ });
357
+
358
+ // ../core/src/schema/board.ts
359
+ import { z as z6 } from "zod";
360
+ var BOARD_STATUSES = ["todo", "doing", "blocked", "done"];
361
+ var BoardStatusSchema = z6.enum(BOARD_STATUSES);
362
+ var BoardCountsSchema = z6.object({
363
+ todo: z6.number().int().nonnegative(),
364
+ doing: z6.number().int().nonnegative(),
365
+ blocked: z6.number().int().nonnegative(),
366
+ done: z6.number().int().nonnegative()
367
+ });
368
+ var BoardSchema = z6.object({
369
+ id: idOf("brd"),
370
+ object: z6.literal("board"),
371
+ agent_id: idOf("agt"),
372
+ cards: BoardCountsSchema,
373
+ created_at: Timestamp
374
+ });
375
+ var BoardCardSchema = z6.object({
376
+ id: idOf("crd"),
377
+ object: z6.literal("board_card"),
378
+ board_id: idOf("brd"),
379
+ title: z6.string().min(1),
380
+ body: z6.string(),
381
+ status: BoardStatusSchema,
382
+ assignee: z6.string().nullable(),
383
+ /** The cards this one waits on. A `blocked` card with an empty list is stuck on the world. */
384
+ blocked_by: z6.array(idOf("crd")),
385
+ created_at: Timestamp,
386
+ updated_at: Timestamp
387
+ });
388
+ var BoardCommentSchema = z6.object({
389
+ id: idOf("bcm"),
390
+ object: z6.literal("board_comment"),
391
+ card_id: idOf("crd"),
392
+ /**
393
+ * Who wrote it, as an id the server derived — never a name the caller supplied. A member's
394
+ * comment carries the `ses_` id of the thread that wrote it; a person's carries their `usr_` or
395
+ * `key_` principal. The prefix is what lets a reader tell an agent's comment from a person's,
396
+ * which is the distinction a free-text author field could not make and could be used to forge.
397
+ */
398
+ author: z6.string(),
399
+ body: z6.string(),
400
+ created_at: Timestamp
401
+ });
402
+
403
+ // ../core/src/schema/identity.ts
404
+ import { z as z7 } from "zod";
405
+ var IDENTITY_NAME_MAX = 128;
406
+ var IDENTITY_DESCRIPTION_MAX = 1024;
407
+ var IdentitySchema = z7.object({
408
+ id: idOf("idn"),
409
+ object: z7.literal("identity"),
410
+ /** Human-facing persona name. Surfaced to the model so it acts in character. */
411
+ name: z7.string().min(1).max(IDENTITY_NAME_MAX),
412
+ /** What the persona is for. The model reads it to choose tone, scope and endpoint. */
413
+ description: z7.string().max(IDENTITY_DESCRIPTION_MAX).nullable(),
414
+ /** Inbox addresses (`localpart@domain`) this identity owns. */
415
+ emails: z7.array(z7.string()),
416
+ /** Provisioned numbers in E.164. */
417
+ phones: z7.array(z7.string()),
418
+ /** Verified web domains reachable through this identity's inboxes. */
419
+ domains: z7.array(z7.string()),
420
+ /** `conn_` ids of authorized third-party connections. */
421
+ connections: z7.array(z7.string()),
422
+ metadata: Metadata,
423
+ created_at: Timestamp,
424
+ updated_at: Timestamp
425
+ });
426
+
427
+ // ../core/src/schema/resource.ts
428
+ import { z as z8 } from "zod";
429
+ var ComputerStatusSchema = z8.enum(["creating", "running", "paused", "failed", "destroyed"]);
430
+ var VolumeSchema = z8.object({
431
+ id: idOf("vol"),
432
+ size_gb: z8.number().int().positive(),
433
+ mount_path: z8.string()
434
+ });
435
+ var ComputerSchema = z8.object({
436
+ id: idOf("cmp"),
437
+ object: z8.literal("computer"),
438
+ name: z8.string(),
439
+ vcpu: z8.number().int().positive(),
440
+ memory_mb: z8.number().int().positive(),
441
+ disk_gb: z8.number().int().positive(),
442
+ status: ComputerStatusSchema,
443
+ browser: z8.object({ allowed_domains: z8.array(z8.string()) }).nullable(),
444
+ volume: VolumeSchema.nullable(),
445
+ snapshot_id: idOf("snp").nullable(),
446
+ created_at: Timestamp
447
+ });
448
+ var SnapshotSchema = z8.object({
449
+ snapshot_id: idOf("snp"),
450
+ computer_id: idOf("cmp"),
451
+ created_at: Timestamp
452
+ });
453
+ var SkillSchema = z8.object({
454
+ id: idOf("skl"),
455
+ object: z8.literal("skill"),
456
+ name: z8.string(),
457
+ description: z8.string().nullable(),
458
+ latest_version: z8.number().int().positive(),
459
+ created_at: Timestamp
460
+ });
461
+ var ResolvedSkillRefSchema = z8.object({
462
+ /** The reference exactly as the agent version wrote it: `slug` or `slug@N`. */
463
+ ref: z8.string().min(1),
464
+ slug: z8.string().min(1),
465
+ version: z8.number().int().positive(),
466
+ description: z8.string(),
467
+ storage_key: z8.string().min(1)
468
+ });
469
+ var SkillVersionSchema = z8.object({
470
+ version: z8.number().int().positive(),
471
+ sha256: z8.string().regex(/^[0-9a-f]{64}$/),
472
+ size_bytes: z8.number().int().nonnegative(),
473
+ created_at: Timestamp
474
+ });
475
+ var FileSchema = z8.object({
476
+ id: idOf("fil"),
477
+ object: z8.literal("file"),
478
+ name: z8.string(),
479
+ content_type: z8.string(),
480
+ size_bytes: z8.number().int().nonnegative(),
481
+ scope: z8.enum(["session", "published"]),
482
+ session_id: idOf("ses").nullable(),
483
+ sha256: z8.string().regex(/^[0-9a-f]{64}$/),
484
+ created_at: Timestamp
485
+ });
486
+ var ScheduleStatusSchema = z8.enum(["scheduled", "paused", "unscheduled"]);
487
+ var DeploymentSchema = z8.object({
488
+ id: idOf("dep"),
489
+ object: z8.literal("deployment"),
490
+ agent_id: idOf("agt"),
491
+ /** A pin. `null` tracks the agent's `current_version` on every fire (§12). */
492
+ agent_version: z8.number().int().positive().nullable(),
493
+ cron: z8.string(),
494
+ /** IANA zone the `cron` is read in. "0 9 * * 1" means 09:00 *here*, across DST (ADR-0074). */
495
+ timezone: z8.string(),
496
+ input_template: z8.string(),
497
+ budget_micro_usd: MicroUsdField.nonnegative(),
498
+ window: WindowSchema,
499
+ /**
500
+ * URL POSTed with the `session.idle` envelope when a fire goes idle; `null` pushes nothing.
501
+ *
502
+ * `https` for the same reason `POST /v1/webhooks` requires it — setting this materialises one of
503
+ * those endpoints, and a signed delivery over cleartext is not a delivery. Until the push was
504
+ * wired this field went nowhere, so nothing can be relying on an `http://` value (ADR-0161).
505
+ */
506
+ on_idle: z8.url().startsWith("https://", { error: "a webhook URL must be https" }).nullable(),
507
+ /**
508
+ * The persona every fire speaks as, or `null` to run as nobody (§22). Written as an `idn_` id
509
+ * **or** the persona's name, the way a session names one, and always echoed as the id: a fire at
510
+ * 3am must not be silenced by someone renaming the persona at noon.
511
+ */
512
+ identity: idOf("idn").nullable(),
513
+ enabled: z8.boolean(),
514
+ /** Derived per deploy, never stored — the row cannot know whether a clock is watching it. */
515
+ schedule_status: ScheduleStatusSchema,
516
+ /** The next fire, or `null` when nothing will fire it (`schedule_status` says which). */
517
+ next_run_at: Timestamp.nullable(),
518
+ created_at: Timestamp
519
+ });
520
+ var DeploymentRunSchema = z8.object({
521
+ id: idOf("run"),
522
+ session_id: idOf("ses"),
523
+ status: SessionStatusSchema,
524
+ started_at: Timestamp.nullable()
525
+ });
526
+
527
+ // ../core/src/schema/org.ts
528
+ import { z as z9 } from "zod";
529
+ var OrganizationSchema = z9.object({
530
+ id: idOf("org"),
531
+ object: z9.literal("organization"),
532
+ name: z9.string(),
533
+ balance_micro_usd: MicroUsdField,
534
+ created_at: Timestamp
535
+ });
536
+ var RoleSchema = z9.enum(["owner", "admin", "member"]);
537
+ var MemberSchema = z9.object({
538
+ id: z9.string(),
539
+ object: z9.literal("member"),
540
+ email: z9.email(),
541
+ role: RoleSchema,
542
+ invited_by: z9.string().nullable(),
543
+ created_at: Timestamp
544
+ });
545
+ var SCOPES = [
546
+ "agents:read",
547
+ "agents:write",
548
+ "sessions:read",
549
+ "sessions:write",
550
+ /**
551
+ * THE MODEL DOOR AND NOTHING ELSE. `/v1/proxy/*` used to take `sessions:write`, which also opens
552
+ * session create and cancel — so a credential handed to a customer's micro-VM, if it leaked, could
553
+ * start sessions for the whole organization. This scope buys exactly one capability, so a sandbox
554
+ * token can be minted with it alone.
555
+ *
556
+ * The proxy routes accept EITHER this or `sessions:write`: narrowing an already-published route
557
+ * would break every key that reaches it today (§17).
558
+ */
559
+ "proxy:write",
560
+ "computers:write",
561
+ "files:read",
562
+ "files:write",
563
+ "billing:read",
564
+ "billing:write",
565
+ "webhooks:*",
566
+ "audit:read",
567
+ "admin"
568
+ ];
569
+ var ScopeSchema = z9.enum(SCOPES);
570
+ var ApiKeySchema = z9.object({
571
+ id: idOf("key"),
572
+ object: z9.literal("api_key"),
573
+ name: z9.string(),
574
+ prefix: z9.string(),
575
+ scopes: z9.array(ScopeSchema),
576
+ created_by: PrincipalSchema,
577
+ last_used_at: Timestamp.nullable(),
578
+ created_at: Timestamp
579
+ });
580
+ var LedgerEntrySchema = z9.object({
581
+ id: idOf("led"),
582
+ type: z9.enum(["debit", "credit"]),
583
+ amount_micro_usd: MicroUsdField,
584
+ balance_after_micro_usd: MicroUsdField,
585
+ actor: PrincipalSchema,
586
+ session_id: idOf("ses").nullable(),
587
+ agent_id: idOf("agt").nullable(),
588
+ deployment_id: idOf("dep").nullable(),
589
+ line_items: z9.array(z9.object({ tier: LineItemTierSchema, amount_micro_usd: MicroUsdField })),
590
+ created_at: Timestamp
591
+ });
592
+ var TopupSchema = z9.object({
593
+ id: z9.string(),
594
+ object: z9.literal("topup"),
595
+ amount_micro_usd: MicroUsdField.positive(),
596
+ status: z9.enum(["pending", "requires_action", "succeeded", "failed"]),
597
+ action_url: z9.url().nullable(),
598
+ balance_after_micro_usd: MicroUsdField.nullable(),
599
+ created_at: Timestamp
600
+ });
601
+ var WebhookEndpointSchema = z9.object({
602
+ id: idOf("whk"),
603
+ object: z9.literal("webhook_endpoint"),
604
+ url: z9.url(),
605
+ events: z9.array(z9.string()),
606
+ enabled: z9.boolean(),
607
+ created_at: Timestamp
608
+ });
609
+ var WebhookDeliverySchema = z9.object({
610
+ id: idOf("whd"),
611
+ event_id: idOf("evt"),
612
+ event_type: z9.string(),
613
+ status: z9.enum(["pending", "succeeded", "failed"]),
614
+ attempts: z9.number().int().nonnegative(),
615
+ response_status: z9.number().int().nullable(),
616
+ response_snippet: z9.string().nullable(),
617
+ delivered_at: Timestamp.nullable(),
618
+ next_retry_at: Timestamp.nullable()
619
+ });
620
+ var AuditEntrySchema = z9.object({
621
+ id: idOf("aud"),
622
+ object: z9.literal("audit_entry"),
623
+ actor: PrincipalSchema,
624
+ action: AuditActionSchema,
625
+ resource_type: z9.string(),
626
+ resource_id: z9.string(),
627
+ request_id: z9.string(),
628
+ metadata: Metadata,
629
+ created_at: Timestamp
630
+ });
631
+
632
+ // ../core/src/schema/comms.ts
633
+ import { z as z10 } from "zod";
634
+ var DomainKindSchema = z10.enum(["system", "external", "purchased"]);
635
+ var DomainStatusSchema = z10.enum(["pending_dns", "active", "failed"]);
636
+ var DomainDnsStatusSchema = z10.enum(["pending_verification", "provisioned", "failed"]);
637
+ var DomainInboundStatusSchema = z10.enum(["pending", "verified", "failed"]);
638
+ var DomainAppConnectStatusSchema = z10.enum(["pending", "connected", "failed"]);
639
+ var DnsRecordSchema = z10.object({
640
+ type: z10.enum(["TXT", "CNAME", "MX"]),
641
+ host: z10.string(),
642
+ value: z10.string(),
643
+ priority: z10.number().int().nullable(),
644
+ /** Vetta owns this record: `_dmarc`, DKIM, inbound MX. The agent may read it, never write it. */
645
+ managed: z10.boolean()
646
+ });
647
+ var DomainSchema = z10.object({
648
+ id: idOf("dom"),
649
+ object: z10.literal("domain"),
650
+ /** A domain belongs to the **organization**, not to one identity: two identities share one. */
651
+ name: z10.string(),
652
+ kind: DomainKindSchema,
653
+ status: DomainStatusSchema,
654
+ dns_status: DomainDnsStatusSchema,
655
+ inbound_status: DomainInboundStatusSchema,
656
+ app_connect_status: DomainAppConnectStatusSchema,
657
+ records: z10.array(DnsRecordSchema),
658
+ last_verified_at: Timestamp.nullable(),
659
+ created_at: Timestamp
660
+ });
661
+ var EmailInboxSchema = z10.object({
662
+ id: idOf("eml"),
663
+ object: z10.literal("email_inbox"),
664
+ identity_id: idOf("idn"),
665
+ domain_id: idOf("dom"),
666
+ address: z10.string(),
667
+ localpart: z10.string(),
668
+ /** Inbound routing keys off this: mail to the address wakes exactly this agent. */
669
+ owning_agent_id: idOf("agt"),
670
+ status: z10.enum(["active", "disabled"]),
671
+ created_at: Timestamp
672
+ });
673
+ var PhoneTierSchema = z10.enum(["sole_prop", "standard"]);
674
+ var BrandStatusSchema = z10.enum(["none", "pending_verification", "verified", "rejected"]);
675
+ var CampaignStatusSchema = z10.enum(["none", "pending", "approved", "rejected"]);
676
+ var PhoneNumberSchema = z10.object({
677
+ id: idOf("pho"),
678
+ object: z10.literal("phone_number"),
679
+ identity_id: idOf("idn"),
680
+ e164: z10.string(),
681
+ country: z10.string(),
682
+ capabilities: z10.object({
683
+ /** Live the moment the number is bought — inbound never waits on carrier registration. */
684
+ sms_inbound: z10.boolean(),
685
+ /** True **iff** `campaign_status === "approved"`. Nothing else may set it. */
686
+ sms_outbound: z10.boolean(),
687
+ /** Voice is not a P2 capability; the field exists so its absence is explicit. */
688
+ voice: z10.literal(false)
689
+ }),
690
+ tier: PhoneTierSchema,
691
+ brand_status: BrandStatusSchema,
692
+ campaign_status: CampaignStatusSchema,
693
+ provisioned_at: Timestamp.nullable(),
694
+ created_at: Timestamp
695
+ });
696
+ var InboundMessageSchema = z10.object({
697
+ id: idOf("inb"),
698
+ object: z10.literal("inbound_message"),
699
+ channel: z10.enum(["email", "sms"]),
700
+ identity_id: idOf("idn"),
701
+ /** The `eml_`/`pho_` endpoint it arrived at — the persona is decided by *where*, not by *who*. */
702
+ endpoint_id: z10.string(),
703
+ from: z10.string(),
704
+ to: z10.string(),
705
+ subject: z10.string().nullable(),
706
+ text: z10.string(),
707
+ /** SPF/DKIM/DMARC for mail, carrier origin for SMS. Kept for the audit of why it was accepted. */
708
+ auth: z10.record(z10.string(), z10.string()),
709
+ /** The session this message woke. Null until the wake seam lands (see ADR-0120). */
710
+ session_id: idOf("ses").nullable(),
711
+ received_at: Timestamp
712
+ });
713
+
714
+ // ../core/src/schema/app.ts
715
+ import { z as z11 } from "zod";
716
+ var AppTypeSchema = z11.enum(["frontend_only", "fullstack"]);
717
+ var AppStatusSchema = z11.enum(["provisioning", "active", "error"]);
718
+ var AppMcpPathSchema = z11.string().regex(/^\/\S*$/, "must be an absolute path beginning with /").max(128);
719
+ var AppSchema = z11.object({
720
+ id: idOf("app"),
721
+ object: z11.literal("app"),
722
+ name: z11.string(),
723
+ description: z11.string().nullable(),
724
+ type: AppTypeSchema,
725
+ /**
726
+ * The customer's own project name that made this app, or `null` for one made outside a project
727
+ * (ADR-0375). Customer-visible, unlike the provider handles: it is the word the customer typed,
728
+ * and the refusal that guards a name quotes it.
729
+ */
730
+ project: z11.string().nullable(),
731
+ status: AppStatusSchema,
732
+ /** The address the app answers on — platform hostname until a custom domain connects. */
733
+ url: z11.string().nullable(),
734
+ /** White-label words when `status` is `error`; never the provider's. */
735
+ error: z11.string().nullable(),
736
+ /** Where the app serves MCP, or `null`. The bearer token for it is a server column. */
737
+ mcp: AppMcpPathSchema.nullable(),
738
+ created_at: Timestamp
739
+ });
740
+ var AppDeploymentStatusSchema = z11.enum(["queued", "building", "ready", "error"]);
741
+ var AppDeploymentSchema = z11.object({
742
+ id: idOf("apd"),
743
+ object: z11.literal("app_deployment"),
744
+ app_id: idOf("app"),
745
+ status: AppDeploymentStatusSchema,
746
+ url: z11.string().nullable(),
747
+ error: z11.string().nullable(),
748
+ /** The client's opaque digest of the files, echoed verbatim so it can skip an unchanged deploy. */
749
+ content_hash: z11.string().nullable(),
750
+ created_at: Timestamp
751
+ });
752
+ var AppSecretSchema = z11.object({
753
+ object: z11.literal("app_secret"),
754
+ name: z11.string(),
755
+ /** The client's opaque digest of the value, echoed verbatim so it can skip an unchanged write. */
756
+ value_hash: z11.string().nullable(),
757
+ created_at: Timestamp
758
+ });
759
+ var AppDomainStatusSchema = z11.enum(["pending", "connected", "failed"]);
760
+ var AppDomainSchema = z11.object({
761
+ object: z11.literal("app_domain"),
762
+ app_id: idOf("app"),
763
+ domain_id: idOf("dom"),
764
+ domain: z11.string(),
765
+ status: AppDomainStatusSchema,
766
+ created_at: Timestamp
767
+ });
768
+ var ENV_NAME = /^[A-Z_][A-Z0-9_]*$/;
769
+ var APP_NAME = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
770
+
771
+ // ../core/src/schema/vault.ts
772
+ import { z as z12 } from "zod";
773
+ var VaultCredentialKindSchema = z12.enum(["env_var", "static_bearer", "mcp_oauth"]);
774
+ var VaultSchema = z12.object({
775
+ id: idOf("vlt"),
776
+ object: z12.literal("vault"),
777
+ identity_id: idOf("idn").nullable(),
778
+ display_name: z12.string().min(1).max(128),
779
+ credential_count: z12.number().int().nonnegative(),
780
+ created_at: Timestamp
781
+ });
782
+ var VaultCredentialSchema = z12.object({
783
+ id: idOf("vcr"),
784
+ object: z12.literal("vault_credential"),
785
+ vault_id: idOf("vlt"),
786
+ kind: VaultCredentialKindSchema,
787
+ /** For `env_var`, the environment variable name. For the MCP kinds, a display label. */
788
+ key: z12.string().min(1).max(128),
789
+ /** The exact destination host an `env_var` credential is bound to. Never a wildcard. */
790
+ host: z12.string().nullable(),
791
+ /** The exact MCP server URL an `mcp_*` token is matched to. */
792
+ mcp_server_url: z12.url().nullable(),
793
+ placeholder_ref: z12.string(),
794
+ expires_at: Timestamp.nullable(),
795
+ last_injected_at: Timestamp.nullable(),
796
+ created_at: Timestamp
797
+ });
798
+
799
+ // ../core/src/schema/social.ts
800
+ import { z as z13 } from "zod";
801
+ var SOCIAL_PLATFORMS = [
802
+ "bluesky",
803
+ "facebook",
804
+ "instagram",
805
+ "linkedin",
806
+ "mastodon",
807
+ "pinterest",
808
+ "reddit",
809
+ "threads",
810
+ "tiktok",
811
+ "x",
812
+ "youtube"
813
+ ];
814
+ var SOCIAL_TEXT_PLATFORMS = [
815
+ "bluesky",
816
+ "facebook",
817
+ "linkedin",
818
+ "mastodon",
819
+ "threads",
820
+ "x"
821
+ ];
822
+ var SocialPlatformSchema = z13.enum(SOCIAL_PLATFORMS);
823
+ var SocialTextPlatformSchema = z13.enum(SOCIAL_TEXT_PLATFORMS);
824
+ var SocialAccountSchema = z13.object({
825
+ id: z13.string().min(1),
826
+ object: z13.literal("social_account"),
827
+ identity_id: idOf("idn"),
828
+ platform: SocialPlatformSchema,
829
+ username: z13.string().nullable(),
830
+ display_name: z13.string().nullable(),
831
+ avatar_url: z13.url().nullable(),
832
+ connected_at: Timestamp.nullable()
833
+ });
834
+ var SocialPostStatusSchema = z13.enum([
835
+ "draft",
836
+ "scheduled",
837
+ "processing",
838
+ "posted",
839
+ "failed",
840
+ "deleted"
841
+ ]);
842
+ var SocialPostSchema = z13.object({
843
+ id: z13.string().min(1),
844
+ object: z13.literal("social_post"),
845
+ identity_id: idOf("idn"),
846
+ title: z13.string(),
847
+ platforms: z13.array(SocialTextPlatformSchema),
848
+ status: SocialPostStatusSchema,
849
+ scheduled_at: Timestamp.nullable(),
850
+ posted_at: Timestamp.nullable(),
851
+ error: z13.string().nullable()
852
+ });
853
+ var SocialUploadSchema = z13.object({
854
+ id: z13.string().min(1),
855
+ object: z13.literal("social_upload"),
856
+ identity_id: idOf("idn"),
857
+ type: z13.enum(["image", "video", "document"]),
858
+ url: z13.url().nullable()
859
+ });
860
+ var SocialAnalyticsSchema = z13.object({
861
+ object: z13.literal("social_analytics"),
862
+ identity_id: idOf("idn"),
863
+ resource_id: z13.string().min(1),
864
+ platform: SocialPlatformSchema.nullable(),
865
+ data: z13.unknown()
866
+ });
867
+ var SocialCommentSchema = z13.object({
868
+ id: z13.string().min(1),
869
+ object: z13.literal("social_comment"),
870
+ identity_id: idOf("idn"),
871
+ post_id: z13.string().min(1),
872
+ platforms: z13.array(SocialPlatformSchema),
873
+ status: SocialPostStatusSchema,
874
+ posted_at: Timestamp.nullable(),
875
+ data: z13.unknown()
876
+ });
877
+
878
+ // src/define.ts
879
+ import { z as z14 } from "zod";
880
+
881
+ // src/template.ts
882
+ var BLUEPRINTS = {
883
+ /** Clients, pipeline, deliverables, calendar. */
884
+ agency: { templates: ["blank", "seo-geo"], default: "blank" },
885
+ /** Post queue, accounts, calendar, analytics. */
886
+ media: { templates: ["faceless", "clipping"] }
887
+ };
888
+ var list = (names) => names.join(", ");
889
+ function chooseTemplate(blueprint, template) {
890
+ if (blueprint === void 0) {
891
+ if (template === void 0) return { ok: true, template: void 0 };
892
+ return {
893
+ ok: false,
894
+ message: `template "${template}" names no blueprint \u2014 a template belongs to one, so add \`blueprint: "\u2026"\` (${list(Object.keys(BLUEPRINTS))})`
895
+ };
896
+ }
897
+ const known = BLUEPRINTS[blueprint];
898
+ if (known === void 0) {
899
+ return { ok: false, message: `blueprint "${blueprint}" is not a blueprint \u2014 the blueprints are ${list(Object.keys(BLUEPRINTS))}` };
900
+ }
901
+ if (template === void 0) {
902
+ if (known.default !== void 0) return { ok: true, template: known.default };
903
+ return {
904
+ ok: false,
905
+ message: `blueprint "${blueprint}" needs a template \u2014 choose one of ${list(known.templates)}. Its crews spend money in different ways, so there is no default to fall back to.`
906
+ };
907
+ }
908
+ if (!known.templates.includes(template)) {
909
+ return { ok: false, message: `template "${template}" is not a template of blueprint "${blueprint}" \u2014 its templates are ${list(known.templates)}` };
910
+ }
911
+ return { ok: true, template };
912
+ }
913
+
914
+ // src/define.ts
915
+ var BlueprintRefusal = class extends Error {
916
+ name = "BlueprintRefusal";
917
+ };
918
+ var EnvValueSchema = z14.union([z14.string(), z14.object({ from_env: z14.string().min(1) })]);
919
+ var AppDeclSchema = z14.object({
920
+ /** Becomes a hostname, so the grammar is the platform's `APP_NAME` (a DNS label). */
921
+ name: z14.string().regex(APP_NAME, "a lowercase DNS label, 63 characters or fewer"),
922
+ type: AppTypeSchema,
923
+ description: z14.string().optional(),
924
+ /** A directory whose files are collected and shipped as the app's file-map deployment. */
925
+ deploy_dir: z14.string().min(1).optional(),
926
+ /** The path the app itself serves MCP on (§29.5); `fullstack` only. Absent means none. */
927
+ mcp: AppMcpPathSchema.optional(),
928
+ /** Written as the app's write-only secrets (§29.2) on every apply; names follow the platform's `ENV_NAME`. */
929
+ env: z14.record(z14.string().regex(ENV_NAME), EnvValueSchema, { error: "keyed by environment variable name (A-Z, 0-9, _)" }).optional()
930
+ });
931
+ var ScheduleDeclSchema = z14.object({
932
+ cron: z14.string().min(1),
933
+ timezone: z14.string().min(1).optional(),
934
+ /** The message every fire opens its session with (`input_template` on the wire). */
935
+ input: z14.string().min(1),
936
+ budget_micro_usd: MicroUsdField.nonnegative(),
937
+ identity: z14.string().min(1).optional(),
938
+ enabled: z14.boolean().optional()
939
+ });
940
+ var AgentDeclSchema = z14.object({
941
+ name: z14.string().min(1),
942
+ model: z14.string().min(1),
943
+ budget: BudgetSchema,
944
+ description: z14.string().optional(),
945
+ system: z14.string().optional(),
946
+ tools: ToolsetSchema.optional(),
947
+ skills: z14.array(SkillRefSchema).optional(),
948
+ mcp_servers: z14.array(McpServerSchema).optional(),
949
+ /** Declared app *names*; `up` resolves them to `app_` ids in `tools.configs.apps` (§29.3). */
950
+ allowed_apps: z14.array(z14.string()).optional(),
951
+ /** A declared identity *name*; `up` attaches it after the agent upsert (§22). */
952
+ identity: z14.string().min(1).optional(),
953
+ /** The agent's deployments as a complete set: declared → owned, and the rest are pruned (ADR-0355). */
954
+ schedules: z14.array(ScheduleDeclSchema).optional()
955
+ });
956
+ var CredentialDeclSchema = z14.object({
957
+ kind: VaultCredentialKindSchema,
958
+ key: z14.string().min(1).max(128),
959
+ /** The one exact destination host an `env_var` is bound to. */
960
+ host: z14.string().min(1).optional(),
961
+ /** The MCP server URL a `static_bearer` / `mcp_oauth` token is matched to. */
962
+ mcp_server_url: z14.url().optional(),
963
+ value: z14.object({ from_env: z14.string().min(1) })
964
+ });
965
+ var VaultDeclSchema = z14.object({
966
+ name: VaultSchema.shape.display_name,
967
+ identity: z14.string().min(1).optional(),
968
+ credentials: z14.array(CredentialDeclSchema)
969
+ });
970
+ var SkillDeclSchema = z14.object({
971
+ slug: z14.string().regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters, digits and dashes"),
972
+ file: z14.string().min(1)
973
+ });
974
+ var IdentityDeclSchema = IdentitySchema.pick({ name: true, description: true }).partial({
975
+ description: true
976
+ });
977
+ var RemovedSchema = z14.object({
978
+ apps: z14.array(z14.string().min(1)).default([]),
979
+ agents: z14.array(z14.string().min(1)).default([]),
980
+ skills: z14.array(z14.string().min(1)).default([]),
981
+ identities: z14.array(z14.string().min(1)).default([]),
982
+ vaults: z14.array(z14.string().min(1)).default([])
983
+ });
984
+ var KeptSchema = z14.object({ agents: z14.array(z14.string().min(1)).default([]) });
985
+ var ProjectSchema = z14.object({
986
+ name: z14.string().min(1),
987
+ /** The machine this project runs: its repo, its screens, its `/api/*` (`template.ts`). */
988
+ blueprint: z14.string().min(1).optional(),
989
+ /** Which of that blueprint's templates provides the crew; a blueprint with a default may omit it. */
990
+ template: z14.string().min(1).optional(),
991
+ skills: z14.array(SkillDeclSchema).default([]),
992
+ identities: z14.array(IdentityDeclSchema).default([]),
993
+ vaults: z14.array(VaultDeclSchema).default([]),
994
+ apps: z14.array(AppDeclSchema).default([]),
995
+ agents: z14.array(AgentDeclSchema).default([]),
996
+ removed: RemovedSchema.default({ apps: [], agents: [], skills: [], identities: [], vaults: [] }),
997
+ kept: KeptSchema.default({ agents: [] })
998
+ });
999
+ function refuse(message3) {
1000
+ throw new BlueprintRefusal(message3);
1001
+ }
1002
+ function assertUniqueNames(kind, names) {
1003
+ const seen = /* @__PURE__ */ new Set();
1004
+ for (const name of names) {
1005
+ if (seen.has(name)) {
1006
+ refuse(`duplicate ${kind} name "${name}" \u2014 resources are keyed by name, so each ${kind} must be declared exactly once`);
1007
+ }
1008
+ seen.add(name);
1009
+ }
1010
+ }
1011
+ function deepFreeze(value) {
1012
+ if (value !== null && typeof value === "object") {
1013
+ for (const child of Object.values(value)) deepFreeze(child);
1014
+ Object.freeze(value);
1015
+ }
1016
+ return value;
1017
+ }
1018
+ function applyChoice(config) {
1019
+ const chosen = chooseTemplate(config.blueprint, config.template);
1020
+ if (!chosen.ok) refuse(chosen.message);
1021
+ if (chosen.template !== void 0) config.template = chosen.template;
1022
+ }
1023
+ function parseProject(input) {
1024
+ const parsed = ProjectSchema.safeParse(input);
1025
+ if (!parsed.success) {
1026
+ const lines = parsed.error.issues.map((i) => `${i.path.join(".") || "config"}: ${i.message}`);
1027
+ refuse(`invalid project config \u2014 ${lines.join("; ")}`);
1028
+ }
1029
+ const config = parsed.data;
1030
+ applyChoice(config);
1031
+ assertUniqueNames("app", [...config.apps.map((a) => a.name), ...config.removed.apps]);
1032
+ assertUniqueNames("agent", [...config.agents.map((a) => a.name), ...config.removed.agents, ...config.kept.agents]);
1033
+ assertUniqueNames("skill", [...config.skills.map((s) => s.slug), ...config.removed.skills]);
1034
+ assertUniqueNames("identity", [...config.identities.map((i) => i.name), ...config.removed.identities]);
1035
+ assertUniqueNames("vault", [...config.vaults.map((v) => v.name), ...config.removed.vaults]);
1036
+ const appNames = new Set(config.apps.map((a) => a.name));
1037
+ for (const app of config.apps) {
1038
+ if (app.type === "frontend_only" && app.mcp !== void 0) {
1039
+ refuse(`app "${app.name}": mcp is set, but a frontend_only app has no server to answer on it \u2014 declare type "fullstack" or drop mcp`);
1040
+ }
1041
+ }
1042
+ const identityNames = new Set(config.identities.map((i) => i.name));
1043
+ for (const vault of config.vaults) {
1044
+ if (vault.identity !== void 0 && !identityNames.has(vault.identity)) {
1045
+ refuse(`vault "${vault.name}": identity names "${vault.identity}", but no identity with that name is declared in this project`);
1046
+ }
1047
+ const seen = /* @__PURE__ */ new Set();
1048
+ for (const credential of vault.credentials) {
1049
+ if (credential.kind === "env_var") {
1050
+ refuse(
1051
+ `vault "${vault.name}": credential "${credential.key}" is an env_var, which needs substitution at the sandbox egress boundary \u2014 not available on this platform, so the API would refuse it; use a static_bearer or mcp_oauth credential with an mcp_server_url, which is injected before the sandbox sees it`
1052
+ );
1053
+ }
1054
+ const at = [credential.kind, credential.key, credential.host ?? credential.mcp_server_url ?? ""].join(" ");
1055
+ if (seen.has(at)) {
1056
+ refuse(`vault "${vault.name}": credential ${credential.kind} "${credential.key}" is declared twice for the same destination \u2014 credentials are matched to live rows by kind, key and destination, so each must be unique per vault`);
1057
+ }
1058
+ seen.add(at);
1059
+ }
1060
+ }
1061
+ for (const agent of config.agents) {
1062
+ for (const name of agent.allowed_apps ?? []) {
1063
+ if (!appNames.has(name)) {
1064
+ refuse(`agent "${agent.name}": allowed_apps names "${name}", but no app with that name is declared in this project`);
1065
+ }
1066
+ }
1067
+ if (agent.identity !== void 0 && !identityNames.has(agent.identity)) {
1068
+ refuse(`agent "${agent.name}": identity names "${agent.identity}", but no identity with that name is declared in this project`);
1069
+ }
1070
+ const crons = /* @__PURE__ */ new Set();
1071
+ for (const schedule of agent.schedules ?? []) {
1072
+ if (crons.has(schedule.cron)) {
1073
+ refuse(`agent "${agent.name}": schedule cron "${schedule.cron}" is declared twice \u2014 schedules are matched to live deployments by cron, so each must be unique per agent`);
1074
+ }
1075
+ crons.add(schedule.cron);
1076
+ if (schedule.identity !== void 0 && !identityNames.has(schedule.identity)) {
1077
+ refuse(`agent "${agent.name}": schedule "${schedule.cron}" identity names "${schedule.identity}", but no identity with that name is declared in this project`);
1078
+ }
1079
+ }
1080
+ }
1081
+ return deepFreeze(config);
1082
+ }
1083
+ function withTemplate(input, templates) {
1084
+ const chosen = chooseTemplate(input.blueprint, input.template);
1085
+ if (!chosen.ok) refuse(chosen.message);
1086
+ if (chosen.template === void 0) refuse('templates are declared but no blueprint is \u2014 add `blueprint: "\u2026"`');
1087
+ const declared = BLUEPRINTS[input.blueprint].templates;
1088
+ const carried = templates.map((one) => one.name);
1089
+ if ([...declared].sort().join(" ") !== [...carried].sort().join(" ")) {
1090
+ refuse(
1091
+ `blueprint "${input.blueprint}" has templates ${declared.join(", ")}, but this repo carries ${carried.join(", ") || "none"} \u2014 one repo carries every template of its blueprint, so switching is an edit and never a re-clone`
1092
+ );
1093
+ }
1094
+ const picked = templates.find((one) => one.name === chosen.template);
1095
+ const agents = [...picked.agents, ...input.agents ?? []];
1096
+ const mine = new Set(agents.map((one) => one.name));
1097
+ const removed = new Set(input.removed?.agents ?? []);
1098
+ const kept = templates.filter((one) => one.name !== chosen.template).flatMap((one) => one.agents.map((agent) => agent.name)).filter((name) => !mine.has(name) && !removed.has(name));
1099
+ return { ...input, template: chosen.template, agents, kept: { agents: [.../* @__PURE__ */ new Set([...input.kept?.agents ?? [], ...kept])] } };
1100
+ }
1101
+ function defineProject({ templates, ...input }) {
1102
+ return parseProject(templates === void 0 ? input : withTemplate(input, templates));
1103
+ }
1104
+
1105
+ // src/load.ts
1106
+ import { existsSync, readFileSync } from "node:fs";
1107
+ import { join, resolve } from "node:path";
1108
+ import { pathToFileURL } from "node:url";
1109
+ var CONFIG_CANDIDATES = [
1110
+ "naive.config.ts",
1111
+ "naive.config.js",
1112
+ "naive.config.mjs",
1113
+ "naive.config.json"
1114
+ ];
1115
+ function isMissingModule(error) {
1116
+ const code = error?.code;
1117
+ return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
1118
+ }
1119
+ function message(error) {
1120
+ return error instanceof Error ? error.message : String(error);
1121
+ }
1122
+ function validate(path, raw) {
1123
+ try {
1124
+ return { ok: true, path, config: parseProject(raw) };
1125
+ } catch (error) {
1126
+ if (error instanceof BlueprintRefusal) return { ok: false, reason: "invalid", message: error.message };
1127
+ throw error;
1128
+ }
1129
+ }
1130
+ async function loadConfig(dir) {
1131
+ const path = CONFIG_CANDIDATES.map((name) => resolve(join(dir, name))).find(existsSync);
1132
+ if (!path) {
1133
+ return {
1134
+ ok: false,
1135
+ reason: "not_found",
1136
+ message: `no ${CONFIG_CANDIDATES.join(" / ")} in ${dir}`
1137
+ };
1138
+ }
1139
+ if (path.endsWith(".json")) {
1140
+ try {
1141
+ return validate(path, JSON.parse(readFileSync(path, "utf8")));
1142
+ } catch (error) {
1143
+ if (error instanceof SyntaxError) return { ok: false, reason: "invalid", message: message(error) };
1144
+ throw error;
1145
+ }
1146
+ }
1147
+ let importFile;
1148
+ try {
1149
+ const { createJiti } = await import("jiti");
1150
+ importFile = createJiti(pathToFileURL(path).href).import;
1151
+ } catch (error) {
1152
+ return { ok: false, reason: "unloadable", message: message(error) };
1153
+ }
1154
+ let raw;
1155
+ try {
1156
+ const mod = await importFile(path);
1157
+ raw = mod?.default ?? mod;
1158
+ } catch (error) {
1159
+ return { ok: false, reason: isMissingModule(error) ? "unloadable" : "invalid", message: message(error) };
1160
+ }
1161
+ if (raw === null || typeof raw !== "object") {
1162
+ return {
1163
+ ok: false,
1164
+ reason: "invalid",
1165
+ message: `${path} evaluated to ${raw === void 0 ? "undefined" : typeof raw}; it must \`export default defineProject({...})\``
1166
+ };
1167
+ }
1168
+ return validate(path, raw);
1169
+ }
1170
+
1171
+ // src/up.ts
1172
+ import { createHash as createHash3 } from "node:crypto";
1173
+ import { readFileSync as readFileSync3 } from "node:fs";
1174
+ import { resolve as resolve3 } from "node:path";
1175
+
1176
+ // src/report.ts
1177
+ import { ApiError, send } from "@usenaive-sdk/vetta";
1178
+ import { z as z15 } from "zod";
1179
+ function message2(error) {
1180
+ return error instanceof Error ? error.message : String(error);
1181
+ }
1182
+ async function attempt(into, name, work) {
1183
+ let line;
1184
+ try {
1185
+ line = await work();
1186
+ } catch (error) {
1187
+ line = { name, action: "refused", reason: message2(error) };
1188
+ }
1189
+ into.push(line);
1190
+ return line;
1191
+ }
1192
+ var canonical = (value) => {
1193
+ if (Array.isArray(value)) return value.map(canonical);
1194
+ if (value === null || typeof value !== "object") return value;
1195
+ return Object.fromEntries(
1196
+ Object.keys(value).sort().map((key) => [key, canonical(value[key])])
1197
+ );
1198
+ };
1199
+ var changed = (declared, live) => declared !== void 0 && JSON.stringify(canonical(declared)) !== JSON.stringify(canonical(live));
1200
+ async function absent(work) {
1201
+ try {
1202
+ return await work;
1203
+ } catch (error) {
1204
+ if (error instanceof ApiError && error.status === 404) return null;
1205
+ throw error;
1206
+ }
1207
+ }
1208
+ var PageShape = z15.object({
1209
+ data: z15.array(z15.unknown()),
1210
+ has_more: z15.boolean(),
1211
+ next_cursor: z15.string().nullable()
1212
+ });
1213
+ function rowLabel(row) {
1214
+ const { object, id } = row ?? {};
1215
+ const words = [object, id].filter((one) => typeof one === "string");
1216
+ return words.length > 0 ? words.join(" ") : "a row";
1217
+ }
1218
+ var why = (error) => error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
1219
+ async function listAll(config, path, item, skipped, query = {}) {
1220
+ const rows = [];
1221
+ let after;
1222
+ for (; ; ) {
1223
+ const response = await send(config, { method: "GET", path, query: { ...query, limit: 100, after } }, "application/json");
1224
+ const text = await response.text();
1225
+ if (!response.ok) throw new Error(`could not list ${path} (HTTP ${response.status})`);
1226
+ const body = PageShape.parse(JSON.parse(text));
1227
+ for (const row of body.data) {
1228
+ const parsed = item.safeParse(row);
1229
+ if (parsed.success) rows.push(parsed.data);
1230
+ else skipped.push(`${rowLabel(row)} in ${path} could not be read and was skipped: ${why(parsed.error)}`);
1231
+ }
1232
+ if (!body.has_more || body.next_cursor === null) return rows;
1233
+ after = body.next_cursor;
1234
+ }
1235
+ }
1236
+ async function tombstone(name, current, remove, dryRun) {
1237
+ if (!current) return { name, action: "unchanged", reason: "already absent" };
1238
+ if (!dryRun) await remove(current.id);
1239
+ return { name, action: "deleted", id: current.id };
1240
+ }
1241
+
1242
+ // src/agents.ts
1243
+ function desiredTools(decl, current, ids) {
1244
+ if (decl.allowed_apps === void 0) return decl.tools;
1245
+ const base = decl.tools ?? current?.tools ?? { default_config: { permission: "allow" }, configs: {} };
1246
+ const appsTool = base.configs["apps"] ?? { enabled: true, permission: base.default_config.permission };
1247
+ return {
1248
+ default_config: base.default_config,
1249
+ configs: {
1250
+ ...base.configs,
1251
+ apps: { ...appsTool, config: { ...appsTool.config, allowed_apps: ids } }
1252
+ }
1253
+ };
1254
+ }
1255
+ async function createAgent(client, decl, tools, identityId) {
1256
+ const created = await client.agents.create({
1257
+ name: decl.name,
1258
+ model: decl.model,
1259
+ budget: decl.budget,
1260
+ ...decl.description !== void 0 ? { description: decl.description } : {},
1261
+ ...decl.system !== void 0 ? { system: decl.system } : {},
1262
+ ...decl.skills !== void 0 ? { skills: decl.skills } : {},
1263
+ ...decl.mcp_servers !== void 0 ? { mcp_servers: decl.mcp_servers } : {},
1264
+ ...tools !== void 0 ? { tools } : {}
1265
+ });
1266
+ if (identityId !== void 0) await client.identities.attach(created.id, identityId);
1267
+ return { name: decl.name, action: "created", id: created.id };
1268
+ }
1269
+ async function upsertAgent(client, decl, existing, ids, dryRun) {
1270
+ const unresolved = (decl.allowed_apps ?? []).filter((name) => !ids.apps.has(name));
1271
+ const identityId = decl.identity === void 0 ? void 0 : ids.identities.get(decl.identity);
1272
+ const missing = unresolved.length > 0 ? `allowed_apps needs app "${unresolved[0]}"` : decl.identity !== void 0 && identityId === void 0 ? `identity needs "${decl.identity}"` : null;
1273
+ if (missing !== null && !dryRun) {
1274
+ return { name: decl.name, action: "refused", reason: `${missing}, which was not provisioned in this run` };
1275
+ }
1276
+ const appIds = (decl.allowed_apps ?? []).flatMap((name) => ids.apps.get(name) ?? []);
1277
+ const current = existing.find((agent) => agent.name === decl.name) ?? null;
1278
+ const tools = desiredTools(decl, current, appIds);
1279
+ if (!current) return dryRun ? { name: decl.name, action: "created" } : createAgent(client, decl, tools, identityId);
1280
+ const patch = {};
1281
+ if (changed(decl.model, current.model)) patch.model = decl.model;
1282
+ if (changed(decl.budget, current.budget)) patch.budget = decl.budget;
1283
+ if (changed(decl.description, current.description)) patch.description = decl.description;
1284
+ if (changed(decl.system, current.system)) patch.system = decl.system;
1285
+ if (changed(decl.skills, current.skills)) patch.skills = decl.skills;
1286
+ if (changed(decl.mcp_servers, current.mcp_servers)) patch.mcp_servers = decl.mcp_servers;
1287
+ if (changed(tools, current.tools)) patch.tools = tools;
1288
+ const held = identityId === void 0 ? [] : (await client.identities.held(current.id)).data;
1289
+ const grant = identityId === void 0 || held.some((one) => one.id === identityId) ? null : identityId;
1290
+ const drifted = Object.keys(patch).length > 0 || missing !== null || grant !== null;
1291
+ if (!drifted) return { name: decl.name, action: "unchanged", id: current.id };
1292
+ if (!dryRun) {
1293
+ if (Object.keys(patch).length > 0) await client.agents.update(current.id, patch);
1294
+ if (grant !== null) await client.identities.attach(current.id, grant);
1295
+ }
1296
+ return { name: decl.name, action: "updated", id: current.id };
1297
+ }
1298
+ async function upsertSchedule(client, agent, decl, current, identityIds, dryRun) {
1299
+ const name = `${agent.name} @ ${decl.cron}`;
1300
+ if (agent.action === "refused") throw new Error(`agent "${agent.name}" was not provisioned in this run`);
1301
+ const identity = decl.identity === void 0 ? void 0 : identityIds.get(decl.identity);
1302
+ const unresolved = decl.identity !== void 0 && identity === void 0;
1303
+ if (unresolved && !dryRun) throw new Error(`identity needs "${decl.identity}", which was not provisioned in this run`);
1304
+ if (!current) {
1305
+ if (dryRun || agent.id === void 0) return { name, action: "created" };
1306
+ const created = await client.deployments.create({
1307
+ agent_id: agent.id,
1308
+ cron: decl.cron,
1309
+ input_template: decl.input,
1310
+ budget_micro_usd: decl.budget_micro_usd,
1311
+ ...decl.timezone !== void 0 ? { timezone: decl.timezone } : {},
1312
+ ...identity !== void 0 ? { identity } : {},
1313
+ ...decl.enabled !== void 0 ? { enabled: decl.enabled } : {}
1314
+ });
1315
+ return { name, action: "created", id: created.id };
1316
+ }
1317
+ const patch = {};
1318
+ if (changed(decl.timezone, current.timezone)) patch.timezone = decl.timezone;
1319
+ if (changed(decl.input, current.input_template)) patch.input_template = decl.input;
1320
+ if (changed(decl.budget_micro_usd, current.budget_micro_usd)) patch.budget_micro_usd = decl.budget_micro_usd;
1321
+ if (changed(identity, current.identity)) patch.identity = identity;
1322
+ if (changed(decl.enabled, current.enabled)) patch.enabled = decl.enabled;
1323
+ if (Object.keys(patch).length === 0 && !unresolved) return { name, action: "unchanged", id: current.id };
1324
+ if (!dryRun) await client.deployments.update(current.id, patch);
1325
+ return { name, action: "updated", id: current.id };
1326
+ }
1327
+ async function reconcileSchedules(client, agent, declared, live, identityIds, dryRun, into) {
1328
+ const rows = live.filter((row) => row.agent_id === agent.id);
1329
+ for (const decl of declared) {
1330
+ const current = rows.find((row) => row.cron === decl.cron);
1331
+ await attempt(into, `${agent.name} @ ${decl.cron}`, () => upsertSchedule(client, agent, decl, current, identityIds, dryRun));
1332
+ }
1333
+ for (const row of rows.filter((row2) => !declared.some((decl) => decl.cron === row2.cron))) {
1334
+ const name = `${agent.name} @ ${row.cron}`;
1335
+ await attempt(into, name, () => tombstone(name, row, (id) => client.deployments.delete(id), dryRun));
1336
+ }
1337
+ }
1338
+
1339
+ // src/apps.ts
1340
+ import { createHash as createHash2 } from "node:crypto";
1341
+ import { resolve as resolve2 } from "node:path";
1342
+
1343
+ // src/files.ts
1344
+ import { createHash } from "node:crypto";
1345
+ import { readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
1346
+ import { join as join2, relative, sep } from "node:path";
1347
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
1348
+ var MAX_FILE_BYTES = 8 * 1024 * 1024;
1349
+ function collectFiles(root) {
1350
+ const files = {};
1351
+ const walk = (dir) => {
1352
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1353
+ if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".env")) continue;
1354
+ const full = join2(dir, entry.name);
1355
+ if (entry.isDirectory()) walk(full);
1356
+ else if (entry.isFile() && statSync(full).size <= MAX_FILE_BYTES) {
1357
+ files[relative(root, full).split(sep).join("/")] = readFileSync2(full).toString("base64");
1358
+ }
1359
+ }
1360
+ };
1361
+ walk(root);
1362
+ return files;
1363
+ }
1364
+ function contentHash(files) {
1365
+ const hash = createHash("sha256");
1366
+ for (const [path, base64] of Object.entries(files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
1367
+ const bytes = Buffer.from(base64, "base64");
1368
+ hash.update(`${path}\0${bytes.length}\0`).update(bytes);
1369
+ }
1370
+ return hash.digest("hex");
1371
+ }
1372
+
1373
+ // src/apps.ts
1374
+ async function findApp(client, name, skipped) {
1375
+ const rows = await listAll(client.config, "/v1/apps", AppSchema, skipped, { name });
1376
+ return rows.find((app) => app.name === name) ?? null;
1377
+ }
1378
+ function ownershipRefusal(name, project, owner) {
1379
+ const held = owner === null ? "no project (it was created outside a blueprint)" : `project "${owner}"`;
1380
+ const verb = owner === null ? "take" : "transfer";
1381
+ return `app "${name}" is owned by ${held}; this project is "${project}". Rename the app in your config, or re-run \`naive up --adopt\` to ${verb} it.`;
1382
+ }
1383
+ var ensureApp = (client, body) => client.apps.create(body);
1384
+ function resolveEnv(decl) {
1385
+ const env = {};
1386
+ for (const [name, value] of Object.entries(decl.env ?? {})) {
1387
+ if (typeof value === "string") {
1388
+ env[name] = value;
1389
+ continue;
1390
+ }
1391
+ const local = process.env[value.from_env];
1392
+ if (local === void 0) throw new Error(`env ${name} reads local variable ${value.from_env}, which is not set`);
1393
+ env[name] = local;
1394
+ }
1395
+ return env;
1396
+ }
1397
+ function secretHash(name, value) {
1398
+ return createHash2("sha256").update(`${name}\0`).update(value).digest("hex");
1399
+ }
1400
+ function localTree(decl, dir) {
1401
+ if (!decl.deploy_dir) return null;
1402
+ const files = collectFiles(resolve2(dir, decl.deploy_dir));
1403
+ return { files, hash: contentHash(files) };
1404
+ }
1405
+ async function writeApp(client, id, env, tree) {
1406
+ for (const [name, value] of Object.entries(env)) {
1407
+ await client.apps.setSecret(id, { name, value, value_hash: secretHash(name, value) });
1408
+ }
1409
+ if (!tree) return;
1410
+ await client.apps.deploy(id, tree.files, tree.hash);
1411
+ await settleDeploy(client, id);
1412
+ }
1413
+ var SETTLE_TRIES = 30;
1414
+ var SETTLE_MS = 4e3;
1415
+ async function settleDeploy(client, id) {
1416
+ for (let poll = 0; poll < SETTLE_TRIES; poll++) {
1417
+ const newest = (await client.apps.deployments(id, { limit: 1 })).data[0];
1418
+ if (newest === void 0 || newest.status === "ready" || newest.status === "error") return;
1419
+ await new Promise((resolve4) => setTimeout(resolve4, SETTLE_MS));
1420
+ }
1421
+ }
1422
+ async function createApp(client, decl, env, opts, appIds) {
1423
+ const created = await ensureApp(client, {
1424
+ name: decl.name,
1425
+ type: decl.type,
1426
+ project: opts.project,
1427
+ ...decl.description !== void 0 ? { description: decl.description } : {},
1428
+ ...decl.mcp !== void 0 ? { mcp: decl.mcp } : {}
1429
+ });
1430
+ appIds.set(decl.name, created.id);
1431
+ await writeApp(client, created.id, env, localTree(decl, opts.dir));
1432
+ return { name: decl.name, action: "created", id: created.id, url: (await client.apps.get(created.id)).url };
1433
+ }
1434
+ async function upsertApp(client, decl, opts, appIds) {
1435
+ const env = resolveEnv(decl);
1436
+ const names = Object.keys(env);
1437
+ const note = (notes) => notes.length > 0 ? { reason: notes.join("; ") } : {};
1438
+ const mcpPlan = decl.mcp !== void 0 ? [`will set mcp ${decl.mcp}`] : [];
1439
+ const current = await findApp(client, decl.name, opts.skipped);
1440
+ if (!current) {
1441
+ const willSet = names.length > 0 ? [`will set ${names.join(", ")}`] : [];
1442
+ if (opts.dryRun) {
1443
+ return {
1444
+ name: decl.name,
1445
+ action: "created",
1446
+ ...note([...willSet, ...mcpPlan, ...decl.deploy_dir ? [`will deploy ${decl.deploy_dir}`] : []])
1447
+ };
1448
+ }
1449
+ return createApp(client, decl, env, opts, appIds);
1450
+ }
1451
+ const owned = current.project === opts.project;
1452
+ if (!owned && !opts.adopt) {
1453
+ return {
1454
+ name: decl.name,
1455
+ action: "refused",
1456
+ id: current.id,
1457
+ reason: ownershipRefusal(decl.name, opts.project, current.project)
1458
+ };
1459
+ }
1460
+ appIds.set(decl.name, current.id);
1461
+ if (current.type !== decl.type) {
1462
+ return {
1463
+ name: decl.name,
1464
+ action: "refused",
1465
+ id: current.id,
1466
+ reason: `app "${decl.name}" exists as ${current.type} and type cannot change; delete it or declare type "${current.type}"`
1467
+ };
1468
+ }
1469
+ const from = current.project === null ? "(previously unowned)" : `from project "${current.project}"`;
1470
+ const adoption = owned ? [] : [`${opts.dryRun ? "will adopt" : "adopted"} ${from}`];
1471
+ if (!owned && !opts.dryRun) {
1472
+ await ensureApp(client, { name: decl.name, type: decl.type, project: opts.project, adopt: true });
1473
+ }
1474
+ const live = decl.env === void 0 ? [] : (await client.apps.secrets(current.id)).data;
1475
+ const unmanaged = live.map((secret) => secret.name).filter((name) => !(name in env));
1476
+ const held = new Map(live.map((secret) => [secret.name, secret.value_hash]));
1477
+ const staleEnv = Object.fromEntries(
1478
+ Object.entries(env).filter(([name, value]) => held.get(name) !== secretHash(name, value))
1479
+ );
1480
+ const staleNames = Object.keys(staleEnv);
1481
+ const patch = {
1482
+ ...decl.description !== void 0 && decl.description !== current.description ? { description: decl.description } : {},
1483
+ ...(decl.mcp ?? null) !== current.mcp ? { mcp: decl.mcp ?? null } : {}
1484
+ };
1485
+ const drifted = Object.keys(patch).length > 0;
1486
+ const tree = localTree(decl, opts.dir);
1487
+ const newest = tree ? (await client.apps.deployments(current.id, { limit: 1 })).data[0] : void 0;
1488
+ const stale = tree !== null && newest?.content_hash !== tree.hash;
1489
+ const action = !owned || drifted || stale || staleNames.length > 0 ? "updated" : "unchanged";
1490
+ const plan = opts.dryRun ? [
1491
+ ...staleNames.length > 0 ? [`will set ${staleNames.join(", ")}`] : [],
1492
+ ..."mcp" in patch ? patch.mcp === null ? ["will clear mcp"] : mcpPlan : [],
1493
+ ...decl.deploy_dir ? [stale ? `will deploy ${decl.deploy_dir}` : `${decl.deploy_dir} unchanged`] : []
1494
+ ] : [];
1495
+ if (!opts.dryRun) {
1496
+ if (drifted) await client.apps.update(current.id, { ...patch, project: opts.project });
1497
+ await writeApp(client, current.id, staleEnv, stale ? tree : null);
1498
+ }
1499
+ return {
1500
+ name: decl.name,
1501
+ action,
1502
+ id: current.id,
1503
+ // Re-read only when a build actually ran: a dry run issues no call at all.
1504
+ url: !opts.dryRun && stale ? (await client.apps.get(current.id)).url : current.url,
1505
+ ...note([...adoption, ...plan, ...unmanaged.length > 0 ? [`unmanaged secrets: ${unmanaged.join(", ")}`] : []])
1506
+ };
1507
+ }
1508
+
1509
+ // src/vaults.ts
1510
+ async function upsertVault(client, decl, existing, identityIds, dryRun) {
1511
+ const identityId = decl.identity === void 0 ? void 0 : identityIds.get(decl.identity);
1512
+ if (decl.identity !== void 0 && identityId === void 0 && !dryRun) {
1513
+ return { name: decl.name, action: "refused", reason: `identity needs "${decl.identity}", which was not provisioned in this run` };
1514
+ }
1515
+ const current = existing.find((vault) => vault.display_name === decl.name);
1516
+ if (!current) {
1517
+ if (dryRun) return { name: decl.name, action: "created" };
1518
+ const created = await client.vaults.create({
1519
+ display_name: decl.name,
1520
+ ...identityId !== void 0 ? { identity_id: identityId } : {}
1521
+ });
1522
+ return { name: decl.name, action: "created", id: created.id };
1523
+ }
1524
+ if (identityId !== void 0 && current.identity_id !== identityId) {
1525
+ return {
1526
+ name: decl.name,
1527
+ action: "refused",
1528
+ id: current.id,
1529
+ reason: `vault "${decl.name}" belongs to another identity and that cannot change; remove it or declare the identity it has`
1530
+ };
1531
+ }
1532
+ return { name: decl.name, action: "unchanged", id: current.id };
1533
+ }
1534
+ async function upsertCredential(client, vault, decl, existing, dryRun) {
1535
+ const name = `${vault.name} / ${decl.key}`;
1536
+ if (vault.action === "refused") throw new Error(`vault "${vault.name}" was not provisioned in this run`);
1537
+ const current = existing.find(
1538
+ (row) => row.kind === decl.kind && row.key === decl.key && row.host === (decl.host ?? null) && row.mcp_server_url === (decl.mcp_server_url ?? null)
1539
+ );
1540
+ if (current) return { name, action: "unchanged", id: current.id };
1541
+ const value = process.env[decl.value.from_env];
1542
+ if (value === void 0) throw new Error(`credential ${decl.key} reads local variable ${decl.value.from_env}, which is not set`);
1543
+ if (dryRun || vault.id === void 0) return { name, action: "created" };
1544
+ const created = await client.vaults.credentials.create(vault.id, {
1545
+ kind: decl.kind,
1546
+ key: decl.key,
1547
+ value,
1548
+ ...decl.host !== void 0 ? { host: decl.host } : {},
1549
+ ...decl.mcp_server_url !== void 0 ? { mcp_server_url: decl.mcp_server_url } : {}
1550
+ });
1551
+ return { name, action: "created", id: created.id };
1552
+ }
1553
+
1554
+ // src/up.ts
1555
+ async function upsertSkill(client, decl, opts) {
1556
+ const content = readFileSync3(resolve3(opts.dir, decl.file), "utf8");
1557
+ const sha256 = createHash3("sha256").update(content).digest("hex");
1558
+ const current = await absent(client.skills.get(decl.slug));
1559
+ const latest = current ? (await client.skills.listVersions(decl.slug, { limit: 1 })).data[0] : void 0;
1560
+ const action = !current ? "created" : latest?.sha256 === sha256 ? "unchanged" : "updated";
1561
+ if (action === "unchanged" || opts.dryRun) {
1562
+ return { name: decl.slug, action, ...current ? { id: current.id } : {} };
1563
+ }
1564
+ const pushed = await client.skills.push({ slug: decl.slug, content, sha256 });
1565
+ return { name: decl.slug, action, id: pushed.id };
1566
+ }
1567
+ async function upsertIdentity(client, decl, dryRun, identityIds) {
1568
+ const current = await absent(client.identities.get(decl.name));
1569
+ if (!current) {
1570
+ if (dryRun) return { name: decl.name, action: "created" };
1571
+ const created = await client.identities.create({
1572
+ name: decl.name,
1573
+ ...decl.description !== void 0 ? { description: decl.description } : {}
1574
+ });
1575
+ identityIds.set(decl.name, created.id);
1576
+ return { name: decl.name, action: "created", id: created.id };
1577
+ }
1578
+ identityIds.set(decl.name, current.id);
1579
+ const drifted = decl.description !== void 0 && decl.description !== current.description;
1580
+ if (drifted && !dryRun) await client.identities.update(current.id, { description: decl.description ?? null });
1581
+ return { name: decl.name, action: drifted ? "updated" : "unchanged", id: current.id };
1582
+ }
1583
+ async function applyRemoved(client, removed, live, owner, report) {
1584
+ const dryRun = report.dry_run;
1585
+ for (const name of removed.agents) {
1586
+ const current = live.agents.find((agent) => agent.name === name);
1587
+ await attempt(report.agents, name, () => tombstone(name, current, (id) => client.agents.delete(id), dryRun));
1588
+ }
1589
+ for (const name of removed.apps) {
1590
+ await attempt(report.apps, name, async () => {
1591
+ const current = await findApp(client, name, owner.skipped);
1592
+ if (current !== null && current.project !== owner.project && !owner.adopt) {
1593
+ return { name, action: "refused", id: current.id, reason: ownershipRefusal(name, owner.project, current.project) };
1594
+ }
1595
+ return tombstone(name, current, (id) => client.apps.delete(id), dryRun);
1596
+ });
1597
+ }
1598
+ for (const slug of removed.skills) {
1599
+ await attempt(
1600
+ report.skills,
1601
+ slug,
1602
+ async () => tombstone(slug, await absent(client.skills.get(slug)), (id) => client.skills.delete(id), dryRun)
1603
+ );
1604
+ }
1605
+ for (const name of removed.vaults) {
1606
+ const current = live.vaults.find((vault) => vault.display_name === name);
1607
+ await attempt(report.vaults, name, () => tombstone(name, current, (id) => client.vaults.delete(id), dryRun));
1608
+ }
1609
+ for (const name of removed.identities) {
1610
+ await attempt(
1611
+ report.identities,
1612
+ name,
1613
+ async () => tombstone(name, await absent(client.identities.get(name)), (id) => client.identities.delete(id), dryRun)
1614
+ );
1615
+ }
1616
+ }
1617
+ var applied = (config) => ({
1618
+ ...config.blueprint !== void 0 ? { blueprint: config.blueprint } : {},
1619
+ ...config.template !== void 0 ? { template: config.template } : {}
1620
+ });
1621
+ function reportKept(config, live, into) {
1622
+ const by = config.template === void 0 ? "this template" : `template "${config.template}"`;
1623
+ for (const name of config.kept.agents) {
1624
+ const current = live.find((agent) => agent.name === name);
1625
+ if (current !== void 0) into.push({ name, action: "unchanged", id: current.id, reason: `not declared by ${by}; kept, never deleted` });
1626
+ }
1627
+ }
1628
+ async function up(config, client, opts = {}) {
1629
+ const dryRun = opts.dry_run ?? false;
1630
+ const dir = opts.dir ?? process.cwd();
1631
+ const ids = { apps: /* @__PURE__ */ new Map(), identities: /* @__PURE__ */ new Map() };
1632
+ const report = { dry_run: dryRun, ...applied(config), skills: [], identities: [], vaults: [], apps: [], agents: [], schedules: [], skipped: [] };
1633
+ const owner = { project: config.name, adopt: opts.adopt ?? false, skipped: report.skipped };
1634
+ for (const decl of config.skills) {
1635
+ await attempt(report.skills, decl.slug, () => upsertSkill(client, decl, { dryRun, dir }));
1636
+ }
1637
+ for (const decl of config.identities) {
1638
+ await attempt(report.identities, decl.name, () => upsertIdentity(client, decl, dryRun, ids.identities));
1639
+ }
1640
+ const vaults = config.vaults.length > 0 || config.removed.vaults.length > 0 ? await listAll(client.config, "/v1/vaults", VaultSchema, report.skipped) : [];
1641
+ for (const decl of config.vaults) {
1642
+ const line = await attempt(report.vaults, decl.name, () => upsertVault(client, decl, vaults, ids.identities, dryRun));
1643
+ const vaultId = line.action === "unchanged" ? line.id : void 0;
1644
+ const rows = vaultId === void 0 ? [] : await listAll(client.config, `/v1/vaults/${vaultId}/credentials`, VaultCredentialSchema, report.skipped);
1645
+ for (const cred of decl.credentials) {
1646
+ await attempt(report.vaults, `${decl.name} / ${cred.key}`, () => upsertCredential(client, line, cred, rows, dryRun));
1647
+ }
1648
+ }
1649
+ for (const decl of config.apps) {
1650
+ await attempt(report.apps, decl.name, () => upsertApp(client, decl, { ...owner, dryRun, dir }, ids.apps));
1651
+ }
1652
+ const existing = config.agents.length > 0 || config.removed.agents.length > 0 || config.kept.agents.length > 0 ? await listAll(client.config, "/v1/agents", AgentSchema, report.skipped) : [];
1653
+ const deployments = config.agents.some((decl) => decl.schedules !== void 0) ? await listAll(client.config, "/v1/deployments", DeploymentSchema, report.skipped) : [];
1654
+ for (const decl of config.agents) {
1655
+ const line = await attempt(report.agents, decl.name, () => upsertAgent(client, decl, existing, ids, dryRun));
1656
+ if (decl.schedules !== void 0) {
1657
+ await reconcileSchedules(client, line, decl.schedules, deployments, ids.identities, dryRun, report.schedules);
1658
+ }
1659
+ }
1660
+ reportKept(config, existing, report.agents);
1661
+ await applyRemoved(client, config.removed, { agents: existing, vaults }, owner, report);
1662
+ return report;
1663
+ }
1664
+ export {
1665
+ BLUEPRINTS,
1666
+ BlueprintRefusal,
1667
+ CONFIG_CANDIDATES,
1668
+ chooseTemplate,
1669
+ defineProject,
1670
+ loadConfig,
1671
+ parseProject,
1672
+ up
1673
+ };
1674
+ //# sourceMappingURL=index.js.map