@ekairos/domain 1.22.34-beta.development.0 → 1.22.35
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/README.md +260 -106
- package/SKILL.md +56 -0
- package/dist/cli/bin.d.ts +9 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +609 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/client-runtime.d.ts +25 -0
- package/dist/cli/client-runtime.d.ts.map +1 -0
- package/dist/cli/client-runtime.js +60 -0
- package/dist/cli/client-runtime.js.map +1 -0
- package/dist/cli/config.d.ts +5 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +44 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/create-app.d.ts +66 -0
- package/dist/cli/create-app.d.ts.map +1 -0
- package/dist/cli/create-app.js +2948 -0
- package/dist/cli/create-app.js.map +1 -0
- package/dist/cli/http.d.ts +28 -0
- package/dist/cli/http.d.ts.map +1 -0
- package/dist/cli/http.js +113 -0
- package/dist/cli/http.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/server.d.ts +3 -0
- package/dist/cli/server.d.ts.map +1 -0
- package/dist/cli/server.js +440 -0
- package/dist/cli/server.js.map +1 -0
- package/dist/cli/types.d.ts +61 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/ui.d.ts +3 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +138 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/context.test-runner.js +3 -1
- package/dist/context.test-runner.js.map +1 -1
- package/dist/domain-doc.d.ts +2 -0
- package/dist/domain-doc.d.ts.map +1 -1
- package/dist/domain-doc.js +14 -0
- package/dist/domain-doc.js.map +1 -1
- package/dist/index.d.ts +228 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +397 -118
- package/dist/index.js.map +1 -1
- package/dist/next.d.ts +21 -21
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +213 -345
- package/dist/next.js.map +1 -1
- package/dist/polyfills/dom-events.d.ts +2 -0
- package/dist/polyfills/dom-events.d.ts.map +1 -0
- package/dist/polyfills/dom-events.js +92 -0
- package/dist/polyfills/dom-events.js.map +1 -0
- package/dist/runtime-handle.d.ts +45 -0
- package/dist/runtime-handle.d.ts.map +1 -0
- package/dist/runtime-handle.js +84 -0
- package/dist/runtime-handle.js.map +1 -0
- package/dist/runtime-step.d.ts.map +1 -1
- package/dist/runtime-step.js +2 -0
- package/dist/runtime-step.js.map +1 -1
- package/dist/runtime.d.ts +9 -8
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +80 -24
- package/dist/runtime.js.map +1 -1
- package/package.json +44 -7
|
@@ -0,0 +1,2948 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { createServer } from "node:net";
|
|
3
|
+
import { access, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { PlatformApi } from "@instantdb/platform";
|
|
6
|
+
import { i } from "@instantdb/core";
|
|
7
|
+
import { domain } from "../index.js";
|
|
8
|
+
export const CREATE_DOMAIN_APP_TEMPLATES = ["empty", "supply-chain", "agent"];
|
|
9
|
+
const TEMPLATE_NEXT_VERSION = "16.2.6";
|
|
10
|
+
const TEMPLATE_REACT_VERSION = "19.2.6";
|
|
11
|
+
const TEMPLATE_TYPESCRIPT_VERSION = "^6.0.3";
|
|
12
|
+
const TEMPLATE_INSTANT_VERSION = "1.0.39";
|
|
13
|
+
const TEMPLATE_INSTANT_REACT_VERSION = "1.0.39";
|
|
14
|
+
const TEMPLATE_WORKFLOW_VERSION = "5.0.0-beta.6";
|
|
15
|
+
const TEMPLATE_VERCEL_OIDC_VERSION = "3.4.1";
|
|
16
|
+
const TEMPLATE_EVENTS_VERSION = "1.22.82-beta.development.0";
|
|
17
|
+
function trimOrEmpty(value) {
|
|
18
|
+
return typeof value === "string" ? value.trim() : "";
|
|
19
|
+
}
|
|
20
|
+
async function emitProgress(onProgress, event) {
|
|
21
|
+
if (!onProgress)
|
|
22
|
+
return;
|
|
23
|
+
await onProgress(event);
|
|
24
|
+
}
|
|
25
|
+
function toPosix(value) {
|
|
26
|
+
return value.replace(/\\/g, "/");
|
|
27
|
+
}
|
|
28
|
+
async function detectPackageManager(explicit, workspacePath) {
|
|
29
|
+
const normalized = trimOrEmpty(explicit).toLowerCase();
|
|
30
|
+
if (normalized)
|
|
31
|
+
return normalized;
|
|
32
|
+
for (const root of [trimOrEmpty(workspacePath), process.cwd()].filter(Boolean)) {
|
|
33
|
+
if (await pathExists(join(root, "pnpm-lock.yaml")))
|
|
34
|
+
return "pnpm";
|
|
35
|
+
if (await pathExists(join(root, "yarn.lock")))
|
|
36
|
+
return "yarn";
|
|
37
|
+
if (await pathExists(join(root, "bun.lockb")))
|
|
38
|
+
return "bun";
|
|
39
|
+
}
|
|
40
|
+
const agent = trimOrEmpty(process.env.npm_config_user_agent);
|
|
41
|
+
if (agent.startsWith("pnpm/"))
|
|
42
|
+
return "pnpm";
|
|
43
|
+
if (agent.startsWith("yarn/"))
|
|
44
|
+
return "yarn";
|
|
45
|
+
if (agent.startsWith("bun/"))
|
|
46
|
+
return "bun";
|
|
47
|
+
return "npm";
|
|
48
|
+
}
|
|
49
|
+
async function pathExists(pathname) {
|
|
50
|
+
try {
|
|
51
|
+
await access(pathname);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function ensureWritableTargetDirectory(targetDir, force = false) {
|
|
59
|
+
await mkdir(targetDir, { recursive: true });
|
|
60
|
+
const entries = await readdir(targetDir);
|
|
61
|
+
if (entries.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
if (!force) {
|
|
64
|
+
throw new Error(`Target directory is not empty: ${targetDir}`);
|
|
65
|
+
}
|
|
66
|
+
await rm(targetDir, { recursive: true, force: true });
|
|
67
|
+
await mkdir(targetDir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
async function readDomainPackageVersion() {
|
|
70
|
+
const packageJsonPath = new URL("../../package.json", import.meta.url);
|
|
71
|
+
const raw = await readFile(packageJsonPath, "utf8");
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
return trimOrEmpty(parsed.version) || "latest";
|
|
74
|
+
}
|
|
75
|
+
export function normalizeCreateAppTemplate(value) {
|
|
76
|
+
const normalized = trimOrEmpty(value);
|
|
77
|
+
if (!normalized)
|
|
78
|
+
return null;
|
|
79
|
+
if (CREATE_DOMAIN_APP_TEMPLATES.includes(normalized)) {
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Unsupported template: ${normalized}. Supported templates: ${CREATE_DOMAIN_APP_TEMPLATES.join(", ")}.`);
|
|
83
|
+
}
|
|
84
|
+
function resolveCreateAppTemplate(params) {
|
|
85
|
+
if (params.template) {
|
|
86
|
+
if (params.demo && params.template !== "supply-chain") {
|
|
87
|
+
throw new Error("--demo only supports --template=supply-chain.");
|
|
88
|
+
}
|
|
89
|
+
return params.template;
|
|
90
|
+
}
|
|
91
|
+
return params.demo ? "supply-chain" : "empty";
|
|
92
|
+
}
|
|
93
|
+
function createScaffoldSchema(template) {
|
|
94
|
+
if (template === "supply-chain")
|
|
95
|
+
return createSupplyChainScaffoldSchema();
|
|
96
|
+
if (template === "agent")
|
|
97
|
+
return createAgentScaffoldSchema();
|
|
98
|
+
return createEmptyScaffoldSchema();
|
|
99
|
+
}
|
|
100
|
+
function createEmptyScaffoldSchema() {
|
|
101
|
+
const scaffoldDomain = domain("app").withSchema({
|
|
102
|
+
entities: {},
|
|
103
|
+
links: {},
|
|
104
|
+
rooms: {},
|
|
105
|
+
});
|
|
106
|
+
return scaffoldDomain.toInstantSchema();
|
|
107
|
+
}
|
|
108
|
+
function createSupplyChainScaffoldSchema() {
|
|
109
|
+
const supplierNetworkDomain = domain("supplierNetwork").withSchema({
|
|
110
|
+
entities: {
|
|
111
|
+
supplierNetwork_supplier: i.entity({
|
|
112
|
+
name: i.string().indexed(),
|
|
113
|
+
region: i.string().indexed(),
|
|
114
|
+
risk: i.string().indexed(),
|
|
115
|
+
score: i.number().indexed(),
|
|
116
|
+
createdAt: i.number().indexed(),
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
links: {},
|
|
120
|
+
rooms: {},
|
|
121
|
+
});
|
|
122
|
+
const procurementDomain = domain("procurement")
|
|
123
|
+
.includes(supplierNetworkDomain)
|
|
124
|
+
.withSchema({
|
|
125
|
+
entities: {
|
|
126
|
+
procurement_order: i.entity({
|
|
127
|
+
reference: i.string().indexed(),
|
|
128
|
+
status: i.string().indexed(),
|
|
129
|
+
spend: i.number().indexed(),
|
|
130
|
+
createdAt: i.number().indexed(),
|
|
131
|
+
}),
|
|
132
|
+
},
|
|
133
|
+
links: {
|
|
134
|
+
procurement_orderSupplier: {
|
|
135
|
+
forward: { on: "procurement_order", has: "one", label: "supplier" },
|
|
136
|
+
reverse: { on: "supplierNetwork_supplier", has: "many", label: "orders" },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
rooms: {},
|
|
140
|
+
});
|
|
141
|
+
const inventoryDomain = domain("inventory")
|
|
142
|
+
.includes(procurementDomain)
|
|
143
|
+
.withSchema({
|
|
144
|
+
entities: {
|
|
145
|
+
inventory_stockItem: i.entity({
|
|
146
|
+
sku: i.string().indexed(),
|
|
147
|
+
warehouse: i.string().indexed(),
|
|
148
|
+
available: i.number().indexed(),
|
|
149
|
+
safetyStock: i.number().indexed(),
|
|
150
|
+
createdAt: i.number().indexed(),
|
|
151
|
+
}),
|
|
152
|
+
},
|
|
153
|
+
links: {
|
|
154
|
+
inventory_stockItemOrder: {
|
|
155
|
+
forward: { on: "inventory_stockItem", has: "one", label: "order" },
|
|
156
|
+
reverse: { on: "procurement_order", has: "many", label: "stockItems" },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
rooms: {},
|
|
160
|
+
});
|
|
161
|
+
const transportationDomain = domain("transportation")
|
|
162
|
+
.includes(procurementDomain)
|
|
163
|
+
.withSchema({
|
|
164
|
+
entities: {
|
|
165
|
+
transportation_shipment: i.entity({
|
|
166
|
+
carrier: i.string().indexed(),
|
|
167
|
+
lane: i.string().indexed(),
|
|
168
|
+
status: i.string().indexed(),
|
|
169
|
+
etaHours: i.number().indexed(),
|
|
170
|
+
createdAt: i.number().indexed(),
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
links: {
|
|
174
|
+
transportation_shipmentOrder: {
|
|
175
|
+
forward: { on: "transportation_shipment", has: "one", label: "order" },
|
|
176
|
+
reverse: { on: "procurement_order", has: "many", label: "shipments" },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
rooms: {},
|
|
180
|
+
});
|
|
181
|
+
const qualityControlDomain = domain("qualityControl")
|
|
182
|
+
.includes(transportationDomain)
|
|
183
|
+
.withSchema({
|
|
184
|
+
entities: {
|
|
185
|
+
qualityControl_inspection: i.entity({
|
|
186
|
+
result: i.string().indexed(),
|
|
187
|
+
severity: i.string().indexed(),
|
|
188
|
+
note: i.string(),
|
|
189
|
+
createdAt: i.number().indexed(),
|
|
190
|
+
}),
|
|
191
|
+
},
|
|
192
|
+
links: {
|
|
193
|
+
qualityControl_inspectionShipment: {
|
|
194
|
+
forward: { on: "qualityControl_inspection", has: "one", label: "shipment" },
|
|
195
|
+
reverse: { on: "transportation_shipment", has: "many", label: "inspections" },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
rooms: {},
|
|
199
|
+
});
|
|
200
|
+
const scaffoldDomain = domain("supplyChain")
|
|
201
|
+
.includes(inventoryDomain)
|
|
202
|
+
.includes(qualityControlDomain)
|
|
203
|
+
.withSchema({ entities: {}, links: {}, rooms: {} });
|
|
204
|
+
return scaffoldDomain.toInstantSchema();
|
|
205
|
+
}
|
|
206
|
+
function createAgentScaffoldSchema() {
|
|
207
|
+
return domain("events")
|
|
208
|
+
.withSchema({
|
|
209
|
+
entities: {
|
|
210
|
+
event_contexts: i.entity({
|
|
211
|
+
createdAt: i.date(),
|
|
212
|
+
updatedAt: i.date().optional(),
|
|
213
|
+
key: i.string().optional().indexed().unique(),
|
|
214
|
+
name: i.string().optional(),
|
|
215
|
+
status: i.string().optional().indexed(),
|
|
216
|
+
content: i.any().optional(),
|
|
217
|
+
reactor: i.json().optional(),
|
|
218
|
+
}),
|
|
219
|
+
event_items: i.entity({
|
|
220
|
+
channel: i.string().indexed(),
|
|
221
|
+
createdAt: i.date().indexed(),
|
|
222
|
+
type: i.string().optional().indexed(),
|
|
223
|
+
content: i.any().optional(),
|
|
224
|
+
status: i.string().optional().indexed(),
|
|
225
|
+
}),
|
|
226
|
+
event_executions: i.entity({
|
|
227
|
+
createdAt: i.date(),
|
|
228
|
+
updatedAt: i.date().optional(),
|
|
229
|
+
status: i.string().optional().indexed(),
|
|
230
|
+
workflowRunId: i.string().optional().indexed(),
|
|
231
|
+
activeStreamId: i.string().optional().indexed(),
|
|
232
|
+
activeStreamClientId: i.string().optional().indexed(),
|
|
233
|
+
lastStreamId: i.string().optional().indexed(),
|
|
234
|
+
lastStreamClientId: i.string().optional().indexed(),
|
|
235
|
+
}),
|
|
236
|
+
event_steps: i.entity({
|
|
237
|
+
createdAt: i.date().indexed(),
|
|
238
|
+
updatedAt: i.date().optional(),
|
|
239
|
+
status: i.string().optional().indexed(),
|
|
240
|
+
iteration: i.number().indexed(),
|
|
241
|
+
errorText: i.string().optional(),
|
|
242
|
+
streamId: i.string().optional().indexed(),
|
|
243
|
+
streamClientId: i.string().optional().indexed(),
|
|
244
|
+
streamStartedAt: i.date().optional().indexed(),
|
|
245
|
+
streamFinishedAt: i.date().optional().indexed(),
|
|
246
|
+
streamAbortReason: i.string().optional(),
|
|
247
|
+
}),
|
|
248
|
+
event_parts: i.entity({
|
|
249
|
+
key: i.string().unique().indexed(),
|
|
250
|
+
stepId: i.string().indexed(),
|
|
251
|
+
idx: i.number().indexed(),
|
|
252
|
+
type: i.string().optional().indexed(),
|
|
253
|
+
part: i.json().optional(),
|
|
254
|
+
metadata: i.json().optional(),
|
|
255
|
+
updatedAt: i.date().optional(),
|
|
256
|
+
}),
|
|
257
|
+
event_trace_events: i.entity({
|
|
258
|
+
key: i.string().unique().indexed(),
|
|
259
|
+
workflowRunId: i.string().indexed(),
|
|
260
|
+
seq: i.number().indexed(),
|
|
261
|
+
eventId: i.string().indexed(),
|
|
262
|
+
eventKind: i.string().indexed(),
|
|
263
|
+
eventAt: i.date().optional(),
|
|
264
|
+
ingestedAt: i.date().optional(),
|
|
265
|
+
orgId: i.string().optional().indexed(),
|
|
266
|
+
projectId: i.string().optional().indexed(),
|
|
267
|
+
contextKey: i.string().optional().indexed(),
|
|
268
|
+
contextId: i.string().optional().indexed(),
|
|
269
|
+
executionId: i.string().optional().indexed(),
|
|
270
|
+
stepId: i.string().optional().indexed(),
|
|
271
|
+
contextEventId: i.string().optional().indexed(),
|
|
272
|
+
toolCallId: i.string().optional().indexed(),
|
|
273
|
+
partKey: i.string().optional().indexed(),
|
|
274
|
+
partIdx: i.number().optional().indexed(),
|
|
275
|
+
spanId: i.string().optional().indexed(),
|
|
276
|
+
parentSpanId: i.string().optional().indexed(),
|
|
277
|
+
isDeleted: i.boolean().optional(),
|
|
278
|
+
aiProvider: i.string().optional().indexed(),
|
|
279
|
+
aiModel: i.string().optional().indexed(),
|
|
280
|
+
promptTokens: i.number().optional(),
|
|
281
|
+
promptTokensCached: i.number().optional(),
|
|
282
|
+
promptTokensUncached: i.number().optional(),
|
|
283
|
+
completionTokens: i.number().optional(),
|
|
284
|
+
totalTokens: i.number().optional(),
|
|
285
|
+
latencyMs: i.number().optional(),
|
|
286
|
+
cacheCostUsd: i.number().optional(),
|
|
287
|
+
computeCostUsd: i.number().optional(),
|
|
288
|
+
costUsd: i.number().optional(),
|
|
289
|
+
payload: i.any().optional(),
|
|
290
|
+
}),
|
|
291
|
+
event_trace_runs: i.entity({
|
|
292
|
+
workflowRunId: i.string().unique().indexed(),
|
|
293
|
+
orgId: i.string().optional().indexed(),
|
|
294
|
+
projectId: i.string().optional().indexed(),
|
|
295
|
+
firstEventAt: i.date().optional().indexed(),
|
|
296
|
+
lastEventAt: i.date().optional().indexed(),
|
|
297
|
+
lastIngestedAt: i.date().optional().indexed(),
|
|
298
|
+
eventsCount: i.number().optional(),
|
|
299
|
+
status: i.string().optional().indexed(),
|
|
300
|
+
payload: i.any().optional(),
|
|
301
|
+
}),
|
|
302
|
+
event_trace_spans: i.entity({
|
|
303
|
+
spanId: i.string().unique().indexed(),
|
|
304
|
+
parentSpanId: i.string().optional().indexed(),
|
|
305
|
+
workflowRunId: i.string().indexed(),
|
|
306
|
+
executionId: i.string().optional().indexed(),
|
|
307
|
+
stepId: i.string().optional().indexed(),
|
|
308
|
+
kind: i.string().optional().indexed(),
|
|
309
|
+
name: i.string().optional().indexed(),
|
|
310
|
+
status: i.string().optional().indexed(),
|
|
311
|
+
startedAt: i.date().optional().indexed(),
|
|
312
|
+
endedAt: i.date().optional().indexed(),
|
|
313
|
+
durationMs: i.number().optional(),
|
|
314
|
+
payload: i.any().optional(),
|
|
315
|
+
}),
|
|
316
|
+
document_documents: i.entity({
|
|
317
|
+
name: i.string().optional().indexed(),
|
|
318
|
+
mimeType: i.string().optional(),
|
|
319
|
+
size: i.number().optional(),
|
|
320
|
+
ownerId: i.string().optional().indexed(),
|
|
321
|
+
orgId: i.string().optional().indexed(),
|
|
322
|
+
createdAt: i.date().optional().indexed(),
|
|
323
|
+
processedAt: i.date().optional().indexed(),
|
|
324
|
+
updatedAt: i.date().optional(),
|
|
325
|
+
lastJobId: i.string().optional(),
|
|
326
|
+
content: i.json().optional(),
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
329
|
+
links: {
|
|
330
|
+
contextItemsContext: {
|
|
331
|
+
forward: { on: "event_items", has: "one", label: "context" },
|
|
332
|
+
reverse: { on: "event_contexts", has: "many", label: "items" },
|
|
333
|
+
},
|
|
334
|
+
contextExecutionsContext: {
|
|
335
|
+
forward: { on: "event_executions", has: "one", label: "context" },
|
|
336
|
+
reverse: { on: "event_contexts", has: "many", label: "executions" },
|
|
337
|
+
},
|
|
338
|
+
contextCurrentExecution: {
|
|
339
|
+
forward: { on: "event_contexts", has: "one", label: "currentExecution" },
|
|
340
|
+
reverse: { on: "event_executions", has: "one", label: "currentOf" },
|
|
341
|
+
},
|
|
342
|
+
contextExecutionsTrigger: {
|
|
343
|
+
forward: { on: "event_executions", has: "one", label: "trigger" },
|
|
344
|
+
reverse: { on: "event_items", has: "many", label: "executionsAsTrigger" },
|
|
345
|
+
},
|
|
346
|
+
contextExecutionsReaction: {
|
|
347
|
+
forward: { on: "event_executions", has: "one", label: "reaction" },
|
|
348
|
+
reverse: { on: "event_items", has: "many", label: "executionsAsReaction" },
|
|
349
|
+
},
|
|
350
|
+
contextStepsExecution: {
|
|
351
|
+
forward: { on: "event_steps", has: "one", label: "execution" },
|
|
352
|
+
reverse: { on: "event_executions", has: "many", label: "steps" },
|
|
353
|
+
},
|
|
354
|
+
contextExecutionItems: {
|
|
355
|
+
forward: { on: "event_items", has: "one", label: "execution" },
|
|
356
|
+
reverse: { on: "event_executions", has: "many", label: "items" },
|
|
357
|
+
},
|
|
358
|
+
contextPartsStep: {
|
|
359
|
+
forward: { on: "event_parts", has: "one", label: "step" },
|
|
360
|
+
reverse: { on: "event_steps", has: "many", label: "parts" },
|
|
361
|
+
},
|
|
362
|
+
contextStepStream: {
|
|
363
|
+
forward: { on: "event_steps", has: "one", label: "stream" },
|
|
364
|
+
reverse: { on: "$streams", has: "many", label: "step" },
|
|
365
|
+
},
|
|
366
|
+
contextExecutionActiveStream: {
|
|
367
|
+
forward: { on: "event_executions", has: "one", label: "activeStream" },
|
|
368
|
+
reverse: { on: "$streams", has: "many", label: "activeOf" },
|
|
369
|
+
},
|
|
370
|
+
contextExecutionLastStream: {
|
|
371
|
+
forward: { on: "event_executions", has: "one", label: "lastStream" },
|
|
372
|
+
reverse: { on: "$streams", has: "many", label: "lastOf" },
|
|
373
|
+
},
|
|
374
|
+
documentFile: {
|
|
375
|
+
forward: { on: "document_documents", has: "one", label: "file" },
|
|
376
|
+
reverse: { on: "$files", has: "one", label: "document" },
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
rooms: {},
|
|
380
|
+
})
|
|
381
|
+
.toInstantSchema();
|
|
382
|
+
}
|
|
383
|
+
function createScaffoldPerms(template) {
|
|
384
|
+
if (template === "supply-chain")
|
|
385
|
+
return createSupplyChainScaffoldPerms();
|
|
386
|
+
if (template === "agent")
|
|
387
|
+
return createAgentScaffoldPerms();
|
|
388
|
+
return createEmptyScaffoldPerms();
|
|
389
|
+
}
|
|
390
|
+
function createEmptyScaffoldPerms() {
|
|
391
|
+
return {
|
|
392
|
+
attrs: {
|
|
393
|
+
allow: { create: "true" },
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function createSupplyChainScaffoldPerms() {
|
|
398
|
+
const entityRules = {
|
|
399
|
+
bind: ["isLoggedIn", "auth.id != null"],
|
|
400
|
+
allow: {
|
|
401
|
+
view: "true",
|
|
402
|
+
create: "isLoggedIn",
|
|
403
|
+
update: "isLoggedIn",
|
|
404
|
+
delete: "false",
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
return {
|
|
408
|
+
attrs: {
|
|
409
|
+
allow: { create: "true" },
|
|
410
|
+
},
|
|
411
|
+
supplierNetwork_supplier: entityRules,
|
|
412
|
+
procurement_order: entityRules,
|
|
413
|
+
inventory_stockItem: entityRules,
|
|
414
|
+
transportation_shipment: entityRules,
|
|
415
|
+
qualityControl_inspection: entityRules,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function createAgentScaffoldPerms() {
|
|
419
|
+
const entityRules = {
|
|
420
|
+
allow: {
|
|
421
|
+
view: "true",
|
|
422
|
+
create: "true",
|
|
423
|
+
update: "true",
|
|
424
|
+
delete: "false",
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
return {
|
|
428
|
+
attrs: {
|
|
429
|
+
allow: { create: "true" },
|
|
430
|
+
},
|
|
431
|
+
event_contexts: entityRules,
|
|
432
|
+
event_items: entityRules,
|
|
433
|
+
event_executions: entityRules,
|
|
434
|
+
event_steps: entityRules,
|
|
435
|
+
event_parts: entityRules,
|
|
436
|
+
event_trace_events: entityRules,
|
|
437
|
+
event_trace_runs: entityRules,
|
|
438
|
+
event_trace_spans: entityRules,
|
|
439
|
+
document_documents: entityRules,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function installCommandFor(packageManager) {
|
|
443
|
+
if (packageManager === "pnpm")
|
|
444
|
+
return "pnpm install";
|
|
445
|
+
if (packageManager === "yarn")
|
|
446
|
+
return "yarn install";
|
|
447
|
+
if (packageManager === "bun")
|
|
448
|
+
return "bun install";
|
|
449
|
+
return "npm install";
|
|
450
|
+
}
|
|
451
|
+
function runScriptCommandFor(packageManager, script) {
|
|
452
|
+
if (packageManager === "pnpm")
|
|
453
|
+
return `pnpm ${script}`;
|
|
454
|
+
if (packageManager === "yarn")
|
|
455
|
+
return `yarn ${script}`;
|
|
456
|
+
if (packageManager === "bun")
|
|
457
|
+
return `bun run ${script}`;
|
|
458
|
+
return `npm run ${script}`;
|
|
459
|
+
}
|
|
460
|
+
function packageBinCommandFor(packageManager, command) {
|
|
461
|
+
void command;
|
|
462
|
+
return "ekairos domain";
|
|
463
|
+
}
|
|
464
|
+
function typecheckCommandFor(packageManager) {
|
|
465
|
+
if (packageManager === "pnpm")
|
|
466
|
+
return { command: "pnpm", args: ["typecheck"] };
|
|
467
|
+
if (packageManager === "yarn")
|
|
468
|
+
return { command: "yarn", args: ["typecheck"] };
|
|
469
|
+
if (packageManager === "bun")
|
|
470
|
+
return { command: "bun", args: ["run", "typecheck"] };
|
|
471
|
+
return { command: "npm", args: ["run", "typecheck"] };
|
|
472
|
+
}
|
|
473
|
+
function nextDevCommandFor(targetDir, port) {
|
|
474
|
+
return {
|
|
475
|
+
command: process.execPath,
|
|
476
|
+
args: [
|
|
477
|
+
join(targetDir, "node_modules", "next", "dist", "bin", "next"),
|
|
478
|
+
"dev",
|
|
479
|
+
"--hostname",
|
|
480
|
+
"127.0.0.1",
|
|
481
|
+
"--port",
|
|
482
|
+
String(port),
|
|
483
|
+
],
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
async function runCommand(params) {
|
|
487
|
+
const timeoutMs = params.timeoutMs ?? 2 * 60 * 1000;
|
|
488
|
+
await new Promise((resolveRun, rejectRun) => {
|
|
489
|
+
const child = spawn(params.command, params.args, {
|
|
490
|
+
cwd: params.targetDir,
|
|
491
|
+
env: process.env,
|
|
492
|
+
shell: process.platform === "win32",
|
|
493
|
+
stdio: "pipe",
|
|
494
|
+
});
|
|
495
|
+
let output = "";
|
|
496
|
+
const timer = setTimeout(() => {
|
|
497
|
+
stopProcess(child.pid);
|
|
498
|
+
rejectRun(new Error(`${params.command} timed out after ${timeoutMs}ms`));
|
|
499
|
+
}, timeoutMs);
|
|
500
|
+
child.stdout.on("data", (chunk) => {
|
|
501
|
+
output += chunk.toString();
|
|
502
|
+
});
|
|
503
|
+
child.stderr.on("data", (chunk) => {
|
|
504
|
+
output += chunk.toString();
|
|
505
|
+
});
|
|
506
|
+
child.on("error", (error) => {
|
|
507
|
+
clearTimeout(timer);
|
|
508
|
+
rejectRun(error);
|
|
509
|
+
});
|
|
510
|
+
child.on("close", (code) => {
|
|
511
|
+
clearTimeout(timer);
|
|
512
|
+
if (code === 0) {
|
|
513
|
+
resolveRun();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
rejectRun(new Error(output.trim() || `${params.command} failed with exit code ${code ?? "unknown"}`));
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
async function reservePort() {
|
|
521
|
+
return await new Promise((resolvePort, rejectPort) => {
|
|
522
|
+
const server = createServer();
|
|
523
|
+
server.once("error", rejectPort);
|
|
524
|
+
server.listen(0, "127.0.0.1", () => {
|
|
525
|
+
const address = server.address();
|
|
526
|
+
if (!address || typeof address === "string") {
|
|
527
|
+
rejectPort(new Error("Failed to reserve smoke port"));
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
const port = address.port;
|
|
531
|
+
server.close((error) => {
|
|
532
|
+
if (error)
|
|
533
|
+
rejectPort(error);
|
|
534
|
+
else
|
|
535
|
+
resolvePort(port);
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
function stopProcess(pid) {
|
|
541
|
+
if (!pid)
|
|
542
|
+
return;
|
|
543
|
+
if (process.platform === "win32") {
|
|
544
|
+
spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
545
|
+
stdio: "ignore",
|
|
546
|
+
});
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
process.kill(-pid, "SIGTERM");
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
try {
|
|
554
|
+
process.kill(pid, "SIGTERM");
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
// Process already exited.
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function quotePowerShellString(value) {
|
|
562
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
563
|
+
}
|
|
564
|
+
function startDetachedWindowsProcess(params) {
|
|
565
|
+
const argumentList = params.args.map(quotePowerShellString).join(", ");
|
|
566
|
+
const script = [
|
|
567
|
+
`$p = Start-Process -FilePath ${quotePowerShellString(params.command)} ` +
|
|
568
|
+
`-ArgumentList @(${argumentList}) ` +
|
|
569
|
+
`-WorkingDirectory ${quotePowerShellString(params.targetDir)} ` +
|
|
570
|
+
"-WindowStyle Hidden -PassThru",
|
|
571
|
+
"Write-Output $p.Id",
|
|
572
|
+
].join("; ");
|
|
573
|
+
const result = spawnSync("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script], {
|
|
574
|
+
encoding: "utf8",
|
|
575
|
+
stdio: "pipe",
|
|
576
|
+
});
|
|
577
|
+
if ((result.status ?? 1) !== 0) {
|
|
578
|
+
throw new Error(result.stderr?.trim() ||
|
|
579
|
+
result.stdout?.trim() ||
|
|
580
|
+
"Failed to start detached Next dev server");
|
|
581
|
+
}
|
|
582
|
+
const pid = Number(String(result.stdout ?? "").trim().split(/\s+/).pop());
|
|
583
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
584
|
+
}
|
|
585
|
+
async function fetchJsonWithTimeout(url, init, timeoutMs = 10000) {
|
|
586
|
+
const controller = new AbortController();
|
|
587
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
588
|
+
try {
|
|
589
|
+
const response = await fetch(url, {
|
|
590
|
+
...init,
|
|
591
|
+
signal: controller.signal,
|
|
592
|
+
});
|
|
593
|
+
const data = await response.json().catch(() => null);
|
|
594
|
+
return { response, data: data };
|
|
595
|
+
}
|
|
596
|
+
finally {
|
|
597
|
+
clearTimeout(timer);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async function waitForDomainEndpoint(params) {
|
|
601
|
+
const endpoint = `${params.baseUrl}/api/ekairos/domain`;
|
|
602
|
+
const deadline = Date.now() + 3 * 60 * 1000;
|
|
603
|
+
let lastError = "";
|
|
604
|
+
while (Date.now() < deadline) {
|
|
605
|
+
if (params.processExited()) {
|
|
606
|
+
throw new Error(`Next dev server exited before smoke endpoint was ready.\n${params.readLogs()}`);
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
const { response, data } = await fetchJsonWithTimeout(endpoint);
|
|
610
|
+
if (response.ok && data?.ok === true)
|
|
611
|
+
return data;
|
|
612
|
+
lastError = `status:${response.status}`;
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
616
|
+
}
|
|
617
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, 1000));
|
|
618
|
+
}
|
|
619
|
+
throw new Error(`Timed out waiting for ${endpoint}: ${lastError}\n${params.readLogs()}`);
|
|
620
|
+
}
|
|
621
|
+
function countCollection(value) {
|
|
622
|
+
if (Array.isArray(value))
|
|
623
|
+
return value.length;
|
|
624
|
+
if (value && typeof value === "object")
|
|
625
|
+
return Object.keys(value).length;
|
|
626
|
+
return 0;
|
|
627
|
+
}
|
|
628
|
+
function asArray(value) {
|
|
629
|
+
return Array.isArray(value) ? value : [];
|
|
630
|
+
}
|
|
631
|
+
async function postSmokeJson(baseUrl, body) {
|
|
632
|
+
const { response, data } = await fetchJsonWithTimeout(`${baseUrl}/api/ekairos/domain`, {
|
|
633
|
+
method: "POST",
|
|
634
|
+
headers: { "content-type": "application/json" },
|
|
635
|
+
body: JSON.stringify(body),
|
|
636
|
+
}, 30000);
|
|
637
|
+
if (!response.ok || data?.ok !== true) {
|
|
638
|
+
throw new Error(data?.error || `Smoke request failed with status ${response.status}`);
|
|
639
|
+
}
|
|
640
|
+
return data;
|
|
641
|
+
}
|
|
642
|
+
async function runSmoke(params) {
|
|
643
|
+
await emitProgress(params.onProgress, {
|
|
644
|
+
stage: "smoke",
|
|
645
|
+
status: "running",
|
|
646
|
+
message: "Running typecheck",
|
|
647
|
+
progress: 97,
|
|
648
|
+
});
|
|
649
|
+
const typecheck = typecheckCommandFor(params.packageManager);
|
|
650
|
+
await runCommand({
|
|
651
|
+
targetDir: params.targetDir,
|
|
652
|
+
command: typecheck.command,
|
|
653
|
+
args: typecheck.args,
|
|
654
|
+
});
|
|
655
|
+
const port = await reservePort();
|
|
656
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
657
|
+
const dev = nextDevCommandFor(params.targetDir, port);
|
|
658
|
+
let logs = "";
|
|
659
|
+
let smokePassed = false;
|
|
660
|
+
let detachedPid = null;
|
|
661
|
+
const child = params.keepServer && process.platform === "win32"
|
|
662
|
+
? null
|
|
663
|
+
: spawn(dev.command, dev.args, {
|
|
664
|
+
cwd: params.targetDir,
|
|
665
|
+
env: process.env,
|
|
666
|
+
detached: params.keepServer,
|
|
667
|
+
shell: false,
|
|
668
|
+
stdio: params.keepServer ? "ignore" : "pipe",
|
|
669
|
+
});
|
|
670
|
+
if (params.keepServer && process.platform === "win32") {
|
|
671
|
+
detachedPid = startDetachedWindowsProcess({
|
|
672
|
+
targetDir: params.targetDir,
|
|
673
|
+
command: dev.command,
|
|
674
|
+
args: dev.args,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
if (child && !params.keepServer) {
|
|
678
|
+
child.stdout?.on("data", (chunk) => {
|
|
679
|
+
logs += chunk.toString();
|
|
680
|
+
});
|
|
681
|
+
child.stderr?.on("data", (chunk) => {
|
|
682
|
+
logs += chunk.toString();
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
try {
|
|
686
|
+
await emitProgress(params.onProgress, {
|
|
687
|
+
stage: "smoke",
|
|
688
|
+
status: "running",
|
|
689
|
+
message: `Waiting for ${baseUrl}`,
|
|
690
|
+
progress: 98,
|
|
691
|
+
});
|
|
692
|
+
const manifest = await waitForDomainEndpoint({
|
|
693
|
+
baseUrl,
|
|
694
|
+
processExited: () => (child ? child.exitCode !== null : false),
|
|
695
|
+
readLogs: () => logs,
|
|
696
|
+
});
|
|
697
|
+
let launchedOrder = false;
|
|
698
|
+
let orders = [];
|
|
699
|
+
let shipments = 0;
|
|
700
|
+
let inspections = 0;
|
|
701
|
+
if (params.demo) {
|
|
702
|
+
const launch = await postSmokeJson(baseUrl, {
|
|
703
|
+
op: "action",
|
|
704
|
+
action: "supplyChain.order.launch",
|
|
705
|
+
input: {
|
|
706
|
+
reference: "PO-SMOKE-7842",
|
|
707
|
+
sku: "DRV-2048",
|
|
708
|
+
supplierName: "Marula Components",
|
|
709
|
+
},
|
|
710
|
+
admin: true,
|
|
711
|
+
});
|
|
712
|
+
if (String(launch.action ?? "") !== "supplyChain.order.launch") {
|
|
713
|
+
throw new Error("Smoke launch order action returned an unexpected action");
|
|
714
|
+
}
|
|
715
|
+
launchedOrder = true;
|
|
716
|
+
const query = await postSmokeJson(baseUrl, {
|
|
717
|
+
op: "query",
|
|
718
|
+
query: {
|
|
719
|
+
procurement_order: {
|
|
720
|
+
supplier: {},
|
|
721
|
+
stockItems: {},
|
|
722
|
+
shipments: {
|
|
723
|
+
inspections: {},
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
admin: true,
|
|
728
|
+
});
|
|
729
|
+
orders = asArray(query?.data?.procurement_order);
|
|
730
|
+
shipments = orders.reduce((total, order) => {
|
|
731
|
+
return total + asArray(order?.shipments).length;
|
|
732
|
+
}, 0);
|
|
733
|
+
inspections = orders.reduce((total, order) => {
|
|
734
|
+
const orderShipments = asArray(order?.shipments);
|
|
735
|
+
return total + orderShipments.reduce((shipmentTotal, shipment) => {
|
|
736
|
+
return shipmentTotal + asArray(shipment?.inspections).length;
|
|
737
|
+
}, 0);
|
|
738
|
+
}, 0);
|
|
739
|
+
}
|
|
740
|
+
smokePassed = true;
|
|
741
|
+
const result = {
|
|
742
|
+
ok: true,
|
|
743
|
+
baseUrl,
|
|
744
|
+
keepServer: params.keepServer,
|
|
745
|
+
pid: detachedPid ?? child?.pid ?? null,
|
|
746
|
+
typecheck: true,
|
|
747
|
+
domainEndpoint: true,
|
|
748
|
+
inspect: {
|
|
749
|
+
entities: countCollection(manifest?.domain?.entities),
|
|
750
|
+
actions: countCollection(manifest?.actions),
|
|
751
|
+
},
|
|
752
|
+
launchedOrder,
|
|
753
|
+
query: {
|
|
754
|
+
inspections,
|
|
755
|
+
orders: orders.length,
|
|
756
|
+
shipments,
|
|
757
|
+
},
|
|
758
|
+
};
|
|
759
|
+
await emitProgress(params.onProgress, {
|
|
760
|
+
stage: "smoke",
|
|
761
|
+
status: "completed",
|
|
762
|
+
message: params.keepServer
|
|
763
|
+
? `Smoke passed and server is running at ${baseUrl}`
|
|
764
|
+
: "Smoke passed",
|
|
765
|
+
progress: 99,
|
|
766
|
+
});
|
|
767
|
+
if (params.keepServer) {
|
|
768
|
+
params.onKeepServer?.({
|
|
769
|
+
unref() {
|
|
770
|
+
child?.unref();
|
|
771
|
+
},
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
return result;
|
|
775
|
+
}
|
|
776
|
+
finally {
|
|
777
|
+
if (!params.keepServer || !smokePassed) {
|
|
778
|
+
stopProcess(detachedPid ?? child?.pid);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async function provisionInstantApp(params) {
|
|
783
|
+
const api = new PlatformApi({
|
|
784
|
+
auth: { token: params.instantToken },
|
|
785
|
+
});
|
|
786
|
+
const created = await api.createApp({
|
|
787
|
+
title: `ekairos-${trimOrEmpty(params.directory.split(/[\\/]/).pop()) || "app"}`,
|
|
788
|
+
orgId: trimOrEmpty(params.orgId) || undefined,
|
|
789
|
+
schema: createScaffoldSchema(params.template),
|
|
790
|
+
perms: createScaffoldPerms(params.template),
|
|
791
|
+
});
|
|
792
|
+
const appId = trimOrEmpty(created?.app?.id);
|
|
793
|
+
const adminToken = trimOrEmpty(created?.app?.adminToken);
|
|
794
|
+
if (!appId || !adminToken) {
|
|
795
|
+
throw new Error("Instant app provisioning did not return appId/adminToken");
|
|
796
|
+
}
|
|
797
|
+
return {
|
|
798
|
+
appId,
|
|
799
|
+
adminToken,
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
async function runInstall(targetDir, packageManager, onProgress) {
|
|
803
|
+
const command = packageManager === "yarn"
|
|
804
|
+
? "yarn"
|
|
805
|
+
: packageManager === "bun"
|
|
806
|
+
? "bun"
|
|
807
|
+
: packageManager === "pnpm"
|
|
808
|
+
? "pnpm"
|
|
809
|
+
: "npm";
|
|
810
|
+
const args = command === "yarn"
|
|
811
|
+
? ["install"]
|
|
812
|
+
: command === "bun"
|
|
813
|
+
? ["install"]
|
|
814
|
+
: command === "pnpm"
|
|
815
|
+
? ["install"]
|
|
816
|
+
: ["install"];
|
|
817
|
+
await new Promise((resolveInstall, rejectInstall) => {
|
|
818
|
+
const emitChunk = (() => {
|
|
819
|
+
let buffer = "";
|
|
820
|
+
return async (chunk) => {
|
|
821
|
+
buffer += chunk;
|
|
822
|
+
const lines = buffer.split(/\r?\n/);
|
|
823
|
+
buffer = lines.pop() ?? "";
|
|
824
|
+
for (const line of lines) {
|
|
825
|
+
const text = line.trim();
|
|
826
|
+
if (!text)
|
|
827
|
+
continue;
|
|
828
|
+
await emitProgress(onProgress, {
|
|
829
|
+
stage: "install",
|
|
830
|
+
status: "log",
|
|
831
|
+
message: text,
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
})();
|
|
836
|
+
const child = spawn(command, args, {
|
|
837
|
+
cwd: targetDir,
|
|
838
|
+
env: process.env,
|
|
839
|
+
shell: process.platform === "win32",
|
|
840
|
+
stdio: "pipe",
|
|
841
|
+
});
|
|
842
|
+
let stderr = "";
|
|
843
|
+
child.stdout.on("data", (chunk) => {
|
|
844
|
+
void emitChunk(chunk.toString());
|
|
845
|
+
});
|
|
846
|
+
child.stderr.on("data", (chunk) => {
|
|
847
|
+
const text = chunk.toString();
|
|
848
|
+
stderr += text;
|
|
849
|
+
void emitChunk(text);
|
|
850
|
+
});
|
|
851
|
+
child.on("error", rejectInstall);
|
|
852
|
+
child.on("close", (code) => {
|
|
853
|
+
if (code === 0) {
|
|
854
|
+
resolveInstall();
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
rejectInstall(new Error(stderr.trim() || `${command} install failed with exit code ${code ?? "unknown"}`));
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
async function writeScaffoldFiles(targetDir, files) {
|
|
862
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
863
|
+
const absolutePath = join(targetDir, relativePath);
|
|
864
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
865
|
+
await writeFile(absolutePath, content, "utf8");
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function resolveWorkspacePackageDependencyVersion(packageName, version, targetDir, workspacePath) {
|
|
869
|
+
const workspaceRoot = trimOrEmpty(workspacePath);
|
|
870
|
+
if (!workspaceRoot) {
|
|
871
|
+
return version;
|
|
872
|
+
}
|
|
873
|
+
const packageRoot = resolve(workspaceRoot, "packages", packageName.split("/").pop() || packageName);
|
|
874
|
+
const relativePath = toPosix(relative(targetDir, packageRoot));
|
|
875
|
+
if (!relativePath)
|
|
876
|
+
return "file:.";
|
|
877
|
+
const prefixed = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
878
|
+
return `file:${prefixed}`;
|
|
879
|
+
}
|
|
880
|
+
function resolveDomainDependencyVersion(version, targetDir, workspacePath) {
|
|
881
|
+
return resolveWorkspacePackageDependencyVersion("@ekairos/domain", version, targetDir, workspacePath);
|
|
882
|
+
}
|
|
883
|
+
function buildNextTemplateFiles(params) {
|
|
884
|
+
const workspaceRoot = trimOrEmpty(params.workspacePath);
|
|
885
|
+
const domainDependency = params.template === "agent" && !workspaceRoot
|
|
886
|
+
? TEMPLATE_EVENTS_VERSION
|
|
887
|
+
: resolveDomainDependencyVersion(params.domainVersion, params.targetDir, params.workspacePath);
|
|
888
|
+
const eventsDependency = resolveWorkspacePackageDependencyVersion("@ekairos/events", TEMPLATE_EVENTS_VERSION, params.targetDir, params.workspacePath);
|
|
889
|
+
const packageJson = {
|
|
890
|
+
name: trimOrEmpty(params.targetDir.split(/[\\/]/).pop()) || "ekairos-app",
|
|
891
|
+
private: true,
|
|
892
|
+
version: "0.1.0",
|
|
893
|
+
type: "module",
|
|
894
|
+
scripts: {
|
|
895
|
+
build: "next build",
|
|
896
|
+
dev: "next dev",
|
|
897
|
+
start: "next start",
|
|
898
|
+
typecheck: "tsc --noEmit",
|
|
899
|
+
},
|
|
900
|
+
dependencies: {
|
|
901
|
+
"@ekairos/domain": domainDependency,
|
|
902
|
+
"@vercel/oidc": TEMPLATE_VERCEL_OIDC_VERSION,
|
|
903
|
+
"@instantdb/admin": TEMPLATE_INSTANT_VERSION,
|
|
904
|
+
"@instantdb/core": TEMPLATE_INSTANT_VERSION,
|
|
905
|
+
"@instantdb/react": TEMPLATE_INSTANT_REACT_VERSION,
|
|
906
|
+
next: TEMPLATE_NEXT_VERSION,
|
|
907
|
+
react: TEMPLATE_REACT_VERSION,
|
|
908
|
+
"react-dom": TEMPLATE_REACT_VERSION,
|
|
909
|
+
workflow: TEMPLATE_WORKFLOW_VERSION,
|
|
910
|
+
zod: "^4.3.6",
|
|
911
|
+
},
|
|
912
|
+
devDependencies: {
|
|
913
|
+
"@types/node": "^24.5.0",
|
|
914
|
+
"@types/react": "^19.2.2",
|
|
915
|
+
"@types/react-dom": "^19.2.2",
|
|
916
|
+
typescript: TEMPLATE_TYPESCRIPT_VERSION,
|
|
917
|
+
},
|
|
918
|
+
packageManager: params.packageManager === "pnpm"
|
|
919
|
+
? "pnpm@10.15.1"
|
|
920
|
+
: params.packageManager === "yarn"
|
|
921
|
+
? "yarn@1"
|
|
922
|
+
: undefined,
|
|
923
|
+
};
|
|
924
|
+
if (params.template === "agent") {
|
|
925
|
+
;
|
|
926
|
+
packageJson.dependencies["@ekairos/events"] = eventsDependency;
|
|
927
|
+
if (params.packageManager === "pnpm") {
|
|
928
|
+
;
|
|
929
|
+
packageJson.pnpm = {
|
|
930
|
+
overrides: {
|
|
931
|
+
"@ekairos/domain": domainDependency,
|
|
932
|
+
"@instantdb/admin": TEMPLATE_INSTANT_VERSION,
|
|
933
|
+
"@instantdb/core": TEMPLATE_INSTANT_VERSION,
|
|
934
|
+
},
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (params.template === "empty") {
|
|
939
|
+
return {
|
|
940
|
+
".gitignore": [".next", "node_modules", ".env.local", ".workflow-data"].join("\n"),
|
|
941
|
+
".env.example": [
|
|
942
|
+
"NEXT_PUBLIC_INSTANT_APP_ID=",
|
|
943
|
+
"INSTANT_ADMIN_TOKEN=",
|
|
944
|
+
"",
|
|
945
|
+
"# Optional: use this only while provisioning new apps with the CLI.",
|
|
946
|
+
"INSTANT_PERSONAL_ACCESS_TOKEN=",
|
|
947
|
+
].join("\n"),
|
|
948
|
+
"DOMAIN.md": [
|
|
949
|
+
"# Ekairos App Domain",
|
|
950
|
+
"",
|
|
951
|
+
"This app starts empty on purpose.",
|
|
952
|
+
"",
|
|
953
|
+
"Add your first domain in `src/domain.ts`, then expose it through `src/runtime.ts` and `/api/ekairos/domain`.",
|
|
954
|
+
"",
|
|
955
|
+
"Suggested first step:",
|
|
956
|
+
"- create one domain with camelCase name",
|
|
957
|
+
"- name entities as `<domainName>_<entityName>`",
|
|
958
|
+
"- add one `defineAction` for the first business write",
|
|
959
|
+
].join("\n"),
|
|
960
|
+
"instant.schema.ts": [
|
|
961
|
+
'import appDomain from "./src/domain";',
|
|
962
|
+
"",
|
|
963
|
+
"const schema = appDomain.toInstantSchema();",
|
|
964
|
+
"",
|
|
965
|
+
"export default schema;",
|
|
966
|
+
].join("\n"),
|
|
967
|
+
"next-env.d.ts": [
|
|
968
|
+
'/// <reference types="next" />',
|
|
969
|
+
'/// <reference types="next/image-types/global" />',
|
|
970
|
+
"",
|
|
971
|
+
"// This file is managed by Next.js.",
|
|
972
|
+
].join("\n"),
|
|
973
|
+
"next.config.ts": [
|
|
974
|
+
'import type { NextConfig } from "next";',
|
|
975
|
+
'import { withWorkflow } from "workflow/next";',
|
|
976
|
+
"",
|
|
977
|
+
"const nextConfig: NextConfig = {",
|
|
978
|
+
" transpilePackages: [\"@ekairos/domain\"],",
|
|
979
|
+
"};",
|
|
980
|
+
"",
|
|
981
|
+
"export default withWorkflow(nextConfig) as NextConfig;",
|
|
982
|
+
].join("\n"),
|
|
983
|
+
"src/app/api/ekairos/domain/route.ts": [
|
|
984
|
+
'import { createRuntimeRouteHandler } from "@ekairos/domain/next";',
|
|
985
|
+
'import { createRuntime } from "@/runtime";',
|
|
986
|
+
"",
|
|
987
|
+
"export const { GET, POST } = createRuntimeRouteHandler({",
|
|
988
|
+
" createRuntime,",
|
|
989
|
+
"});",
|
|
990
|
+
].join("\n"),
|
|
991
|
+
"package.json": `${JSON.stringify(packageJson, null, 2)}\n`,
|
|
992
|
+
"tsconfig.json": [
|
|
993
|
+
"{",
|
|
994
|
+
' "compilerOptions": {',
|
|
995
|
+
' "target": "ES2022",',
|
|
996
|
+
' "lib": ["dom", "dom.iterable", "es2022"],',
|
|
997
|
+
' "allowJs": false,',
|
|
998
|
+
' "skipLibCheck": true,',
|
|
999
|
+
' "strict": true,',
|
|
1000
|
+
' "noEmit": true,',
|
|
1001
|
+
' "esModuleInterop": true,',
|
|
1002
|
+
' "module": "esnext",',
|
|
1003
|
+
' "moduleResolution": "bundler",',
|
|
1004
|
+
' "resolveJsonModule": true,',
|
|
1005
|
+
' "isolatedModules": true,',
|
|
1006
|
+
' "jsx": "react-jsx",',
|
|
1007
|
+
' "incremental": true,',
|
|
1008
|
+
' "paths": {',
|
|
1009
|
+
' "@/*": ["./src/*"]',
|
|
1010
|
+
" },",
|
|
1011
|
+
' "plugins": [',
|
|
1012
|
+
' { "name": "next" }',
|
|
1013
|
+
" ]",
|
|
1014
|
+
" },",
|
|
1015
|
+
' "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],',
|
|
1016
|
+
' "exclude": ["node_modules"]',
|
|
1017
|
+
"}",
|
|
1018
|
+
].join("\n"),
|
|
1019
|
+
"src/app/globals.css": [
|
|
1020
|
+
":root {",
|
|
1021
|
+
" color-scheme: light;",
|
|
1022
|
+
" --background: #f7f8f6;",
|
|
1023
|
+
" --foreground: #181b18;",
|
|
1024
|
+
" --muted: #626a63;",
|
|
1025
|
+
" --border: #d9ded7;",
|
|
1026
|
+
" --surface: #ffffff;",
|
|
1027
|
+
" --accent: #2f6f5d;",
|
|
1028
|
+
"}",
|
|
1029
|
+
"",
|
|
1030
|
+
"* { box-sizing: border-box; }",
|
|
1031
|
+
"html, body { margin: 0; min-height: 100%; }",
|
|
1032
|
+
"body { min-height: 100dvh; background: var(--background); color: var(--foreground); font-family: \"Segoe UI\", sans-serif; }",
|
|
1033
|
+
"button, input { font: inherit; }",
|
|
1034
|
+
"main { width: min(980px, calc(100% - 40px)); margin: 0 auto; padding: 48px 0; }",
|
|
1035
|
+
".shell { display: grid; gap: 24px; }",
|
|
1036
|
+
".eyebrow { color: var(--accent); font-size: 12px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; }",
|
|
1037
|
+
"h1 { max-width: 720px; margin: 0; font-size: clamp(2.4rem, 6vw, 4.8rem); letter-spacing: -0.05em; line-height: 0.96; }",
|
|
1038
|
+
"p { max-width: 64ch; margin: 0; color: var(--muted); line-height: 1.65; }",
|
|
1039
|
+
".workspace { display: grid; gap: 14px; border: 1px solid var(--border); background: var(--surface); padding: 22px; }",
|
|
1040
|
+
".status-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border: 1px solid var(--border); }",
|
|
1041
|
+
".status-grid div { display: grid; gap: 6px; padding: 14px; border-right: 1px solid var(--border); }",
|
|
1042
|
+
".status-grid div:last-child { border-right: 0; }",
|
|
1043
|
+
".status-grid span { color: var(--muted); font-size: 11px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }",
|
|
1044
|
+
".status-grid strong { font-size: 1.4rem; }",
|
|
1045
|
+
".next-steps { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }",
|
|
1046
|
+
".next-steps li { border-top: 1px solid var(--border); padding-top: 10px; color: var(--muted); }",
|
|
1047
|
+
"code { font-family: \"Cascadia Code\", monospace; }",
|
|
1048
|
+
"@media (max-width: 720px) { main { width: min(100% - 28px, 980px); } .status-grid { grid-template-columns: 1fr; } .status-grid div { border-right: 0; border-bottom: 1px solid var(--border); } }",
|
|
1049
|
+
].join("\n"),
|
|
1050
|
+
"src/app/layout.tsx": [
|
|
1051
|
+
'import "./globals.css";',
|
|
1052
|
+
'import type { ReactNode } from "react";',
|
|
1053
|
+
"",
|
|
1054
|
+
"export const metadata = {",
|
|
1055
|
+
' title: "Ekairos App",',
|
|
1056
|
+
' description: "Scaffolded Ekairos app",',
|
|
1057
|
+
"};",
|
|
1058
|
+
"",
|
|
1059
|
+
"export default function RootLayout({ children }: { children: ReactNode }) {",
|
|
1060
|
+
" return (",
|
|
1061
|
+
' <html lang="en">',
|
|
1062
|
+
" <body>{children}</body>",
|
|
1063
|
+
" </html>",
|
|
1064
|
+
" );",
|
|
1065
|
+
"}",
|
|
1066
|
+
].join("\n"),
|
|
1067
|
+
"src/app/page.tsx": [
|
|
1068
|
+
'import DomainWorkbench from "./domain-workbench";',
|
|
1069
|
+
"",
|
|
1070
|
+
'export const dynamic = "force-dynamic";',
|
|
1071
|
+
"",
|
|
1072
|
+
"export default function HomePage() {",
|
|
1073
|
+
" return (",
|
|
1074
|
+
" <main>",
|
|
1075
|
+
' <section className="shell">',
|
|
1076
|
+
' <div className="eyebrow">Ekairos App</div>',
|
|
1077
|
+
" <h1>Empty app. Runtime ready.</h1>",
|
|
1078
|
+
" <p>",
|
|
1079
|
+
" Start here when you want a clean app with the Ekairos runtime,",
|
|
1080
|
+
" domain endpoint, Instant configuration, and a place to add your first domain.",
|
|
1081
|
+
" </p>",
|
|
1082
|
+
" <DomainWorkbench />",
|
|
1083
|
+
" </section>",
|
|
1084
|
+
" </main>",
|
|
1085
|
+
" );",
|
|
1086
|
+
"}",
|
|
1087
|
+
].join("\n"),
|
|
1088
|
+
"src/app/domain-workbench.tsx": [
|
|
1089
|
+
"export default function DomainWorkbench() {",
|
|
1090
|
+
" return (",
|
|
1091
|
+
' <section className="workspace">',
|
|
1092
|
+
' <div className="status-grid">',
|
|
1093
|
+
" <div><span>Runtime</span><strong>Ready</strong></div>",
|
|
1094
|
+
" <div><span>Endpoint</span><strong>/api</strong></div>",
|
|
1095
|
+
" <div><span>Domains</span><strong>0</strong></div>",
|
|
1096
|
+
" </div>",
|
|
1097
|
+
" <p>Add your first domain in <code>src/domain.ts</code>. Keep writes behind typed domain actions.</p>",
|
|
1098
|
+
' <ul className="next-steps">',
|
|
1099
|
+
" <li>Name the domain in camelCase.</li>",
|
|
1100
|
+
" <li>Name entities as <code>{\"<domainName>_<entityName>\"}</code>.</li>",
|
|
1101
|
+
" <li>Expose the first write as a <code>defineAction</code>.</li>",
|
|
1102
|
+
" </ul>",
|
|
1103
|
+
" </section>",
|
|
1104
|
+
" );",
|
|
1105
|
+
"}",
|
|
1106
|
+
].join("\n"),
|
|
1107
|
+
"src/domain.ts": [
|
|
1108
|
+
'import { domain } from "@ekairos/domain";',
|
|
1109
|
+
"",
|
|
1110
|
+
"const baseDomain = domain(\"app\").withSchema({",
|
|
1111
|
+
" entities: {},",
|
|
1112
|
+
" links: {},",
|
|
1113
|
+
" rooms: {},",
|
|
1114
|
+
"});",
|
|
1115
|
+
"",
|
|
1116
|
+
"export const appDomain = baseDomain.withActions({});",
|
|
1117
|
+
"",
|
|
1118
|
+
"export default appDomain;",
|
|
1119
|
+
].join("\n"),
|
|
1120
|
+
"src/runtime.ts": [
|
|
1121
|
+
'import { init } from "@instantdb/admin";',
|
|
1122
|
+
'import { EkairosRuntime } from "@ekairos/domain/runtime-handle";',
|
|
1123
|
+
'import { configureRuntime } from "@ekairos/domain/runtime";',
|
|
1124
|
+
'import appDomain from "./domain";',
|
|
1125
|
+
"",
|
|
1126
|
+
"export type AppRuntimeEnv = {",
|
|
1127
|
+
" actorEmail?: string | null;",
|
|
1128
|
+
" actorId?: string;",
|
|
1129
|
+
" adminToken?: string;",
|
|
1130
|
+
" appId?: string;",
|
|
1131
|
+
"};",
|
|
1132
|
+
"",
|
|
1133
|
+
"function resolveRuntimeEnv(env: AppRuntimeEnv = {}): Required<Pick<AppRuntimeEnv, \"appId\" | \"adminToken\">> & AppRuntimeEnv {",
|
|
1134
|
+
' const appId = String(env.appId ?? process.env.NEXT_PUBLIC_INSTANT_APP_ID ?? "").trim();',
|
|
1135
|
+
' const adminToken = String(env.adminToken ?? process.env.INSTANT_ADMIN_TOKEN ?? "").trim();',
|
|
1136
|
+
" if (!appId || !adminToken) {",
|
|
1137
|
+
' throw new Error("Missing NEXT_PUBLIC_INSTANT_APP_ID or INSTANT_ADMIN_TOKEN. Copy .env.example to .env.local and fill both values.");',
|
|
1138
|
+
" }",
|
|
1139
|
+
" return { ...env, appId, adminToken };",
|
|
1140
|
+
"}",
|
|
1141
|
+
"",
|
|
1142
|
+
"export class AppRuntime extends EkairosRuntime<AppRuntimeEnv, typeof appDomain, any> {",
|
|
1143
|
+
" protected getDomain() { return appDomain; }",
|
|
1144
|
+
" protected async resolveDb(env: AppRuntimeEnv) {",
|
|
1145
|
+
" const resolved = resolveRuntimeEnv(env);",
|
|
1146
|
+
" return init({",
|
|
1147
|
+
" appId: resolved.appId,",
|
|
1148
|
+
" adminToken: resolved.adminToken,",
|
|
1149
|
+
" schema: appDomain.toInstantSchema(),",
|
|
1150
|
+
" useDateObjects: true,",
|
|
1151
|
+
" } as any) as any;",
|
|
1152
|
+
" }",
|
|
1153
|
+
"}",
|
|
1154
|
+
"",
|
|
1155
|
+
"export function createRuntime(env: AppRuntimeEnv = {}) {",
|
|
1156
|
+
" return new AppRuntime(resolveRuntimeEnv(env));",
|
|
1157
|
+
"}",
|
|
1158
|
+
"",
|
|
1159
|
+
"export const runtimeConfig = configureRuntime<AppRuntimeEnv>({",
|
|
1160
|
+
" runtime: async (env) => {",
|
|
1161
|
+
" const runtime = createRuntime(env);",
|
|
1162
|
+
" return { db: await runtime.db() };",
|
|
1163
|
+
" },",
|
|
1164
|
+
" domain: { domain: appDomain },",
|
|
1165
|
+
"});",
|
|
1166
|
+
].join("\n"),
|
|
1167
|
+
"src/workflows/demo.workflow.ts": [
|
|
1168
|
+
"export type DemoWorkflowInput = Record<string, never>;",
|
|
1169
|
+
"",
|
|
1170
|
+
"export async function runDemoWorkflow(_input: DemoWorkflowInput) {",
|
|
1171
|
+
' "use workflow";',
|
|
1172
|
+
" return { ok: true };",
|
|
1173
|
+
"}",
|
|
1174
|
+
].join("\n"),
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
if (params.template === "agent") {
|
|
1178
|
+
return {
|
|
1179
|
+
".gitignore": [".next", "node_modules", ".env.local", ".workflow-data"].join("\n"),
|
|
1180
|
+
".env.example": [
|
|
1181
|
+
"NEXT_PUBLIC_INSTANT_APP_ID=",
|
|
1182
|
+
"INSTANT_ADMIN_TOKEN=",
|
|
1183
|
+
"",
|
|
1184
|
+
"# Optional: use this only while provisioning new apps with the CLI.",
|
|
1185
|
+
"INSTANT_PERSONAL_ACCESS_TOKEN=",
|
|
1186
|
+
].join("\n"),
|
|
1187
|
+
"DOMAIN.md": [
|
|
1188
|
+
"# Ekairos Agent Template",
|
|
1189
|
+
"",
|
|
1190
|
+
"This app is the minimal loop for Ekairos event contexts:",
|
|
1191
|
+
"",
|
|
1192
|
+
"1. create the app",
|
|
1193
|
+
"2. start an agent context through `/api/agent/react`",
|
|
1194
|
+
"3. open the Instant app in Workbench v2",
|
|
1195
|
+
"4. iterate on `src/agent.ts`",
|
|
1196
|
+
"",
|
|
1197
|
+
"The scaffold uses the canonical `@ekairos/events` schema so Workbench v2 can inspect `event_contexts`.",
|
|
1198
|
+
"It includes an Ekairos registry alias so domain UI components can be installed with shadcn and use `@ekairos/events` directly.",
|
|
1199
|
+
].join("\n"),
|
|
1200
|
+
"components.json": [
|
|
1201
|
+
"{",
|
|
1202
|
+
' "$schema": "https://ui.shadcn.com/schema.json",',
|
|
1203
|
+
' "style": "default",',
|
|
1204
|
+
' "rsc": false,',
|
|
1205
|
+
' "tsx": true,',
|
|
1206
|
+
' "tailwind": {',
|
|
1207
|
+
' "config": "",',
|
|
1208
|
+
' "css": "src/app/globals.css",',
|
|
1209
|
+
' "baseColor": "zinc",',
|
|
1210
|
+
' "cssVariables": true,',
|
|
1211
|
+
' "prefix": ""',
|
|
1212
|
+
" },",
|
|
1213
|
+
' "iconLibrary": "lucide",',
|
|
1214
|
+
' "aliases": {',
|
|
1215
|
+
' "components": "@/components",',
|
|
1216
|
+
' "utils": "@/lib/utils",',
|
|
1217
|
+
' "ui": "@/components/ui",',
|
|
1218
|
+
' "lib": "@/lib",',
|
|
1219
|
+
' "hooks": "@/hooks"',
|
|
1220
|
+
" },",
|
|
1221
|
+
' "registries": {',
|
|
1222
|
+
' "@ekairos": "https://registry.ekairos.dev/r/{name}.json"',
|
|
1223
|
+
" }",
|
|
1224
|
+
"}",
|
|
1225
|
+
].join("\n"),
|
|
1226
|
+
"instant.schema.ts": [
|
|
1227
|
+
'import { eventsDomain } from "@ekairos/events/schema";',
|
|
1228
|
+
"",
|
|
1229
|
+
"const schema = eventsDomain.toInstantSchema();",
|
|
1230
|
+
"",
|
|
1231
|
+
"export default schema;",
|
|
1232
|
+
].join("\n"),
|
|
1233
|
+
"next-env.d.ts": [
|
|
1234
|
+
'/// <reference types="next" />',
|
|
1235
|
+
'/// <reference types="next/image-types/global" />',
|
|
1236
|
+
"",
|
|
1237
|
+
"// This file is managed by Next.js.",
|
|
1238
|
+
].join("\n"),
|
|
1239
|
+
"next.config.ts": [
|
|
1240
|
+
'import type { NextConfig } from "next";',
|
|
1241
|
+
'import { withWorkflow } from "workflow/next";',
|
|
1242
|
+
"",
|
|
1243
|
+
"const nextConfig: NextConfig = {",
|
|
1244
|
+
" transpilePackages: [\"@ekairos/domain\", \"@ekairos/events\"],",
|
|
1245
|
+
"};",
|
|
1246
|
+
"",
|
|
1247
|
+
"export default withWorkflow(nextConfig) as NextConfig;",
|
|
1248
|
+
].join("\n"),
|
|
1249
|
+
"package.json": `${JSON.stringify(packageJson, null, 2)}\n`,
|
|
1250
|
+
"tsconfig.json": [
|
|
1251
|
+
"{",
|
|
1252
|
+
' "compilerOptions": {',
|
|
1253
|
+
' "target": "ES2022",',
|
|
1254
|
+
' "lib": ["dom", "dom.iterable", "es2022"],',
|
|
1255
|
+
' "allowJs": false,',
|
|
1256
|
+
' "skipLibCheck": true,',
|
|
1257
|
+
' "strict": true,',
|
|
1258
|
+
' "noEmit": true,',
|
|
1259
|
+
' "esModuleInterop": true,',
|
|
1260
|
+
' "module": "esnext",',
|
|
1261
|
+
' "moduleResolution": "bundler",',
|
|
1262
|
+
' "resolveJsonModule": true,',
|
|
1263
|
+
' "isolatedModules": true,',
|
|
1264
|
+
' "jsx": "react-jsx",',
|
|
1265
|
+
' "incremental": true,',
|
|
1266
|
+
' "paths": {',
|
|
1267
|
+
' "@/*": ["./src/*"]',
|
|
1268
|
+
" },",
|
|
1269
|
+
' "plugins": [',
|
|
1270
|
+
' { "name": "next" }',
|
|
1271
|
+
" ]",
|
|
1272
|
+
" },",
|
|
1273
|
+
' "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],',
|
|
1274
|
+
' "exclude": ["node_modules"]',
|
|
1275
|
+
"}",
|
|
1276
|
+
].join("\n"),
|
|
1277
|
+
"src/app/globals.css": [
|
|
1278
|
+
":root {",
|
|
1279
|
+
" color-scheme: light;",
|
|
1280
|
+
" --background: #f7f8f6;",
|
|
1281
|
+
" --foreground: #171a18;",
|
|
1282
|
+
" --muted: #626a63;",
|
|
1283
|
+
" --border: #d9ded7;",
|
|
1284
|
+
" --surface: #ffffff;",
|
|
1285
|
+
" --accent: #26715f;",
|
|
1286
|
+
"}",
|
|
1287
|
+
"* { box-sizing: border-box; }",
|
|
1288
|
+
"html, body { margin: 0; min-height: 100%; }",
|
|
1289
|
+
"body { min-height: 100dvh; background: var(--background); color: var(--foreground); font-family: \"Segoe UI\", sans-serif; }",
|
|
1290
|
+
"button, textarea, input { font: inherit; }",
|
|
1291
|
+
"main { width: min(980px, calc(100% - 32px)); margin: 0 auto; padding: 44px 0; }",
|
|
1292
|
+
".shell { display: grid; gap: 22px; }",
|
|
1293
|
+
".eyebrow { color: var(--accent); font-size: 12px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; }",
|
|
1294
|
+
"h1 { max-width: 760px; margin: 0; font-size: clamp(2.1rem, 5vw, 4.4rem); line-height: 1; }",
|
|
1295
|
+
"p { max-width: 68ch; margin: 0; color: var(--muted); line-height: 1.65; }",
|
|
1296
|
+
".panel { display: grid; gap: 16px; border: 1px solid var(--border); background: var(--surface); padding: 20px; }",
|
|
1297
|
+
".grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border: 1px solid var(--border); }",
|
|
1298
|
+
".grid div { display: grid; gap: 6px; padding: 14px; border-right: 1px solid var(--border); }",
|
|
1299
|
+
".grid div:last-child { border-right: 0; }",
|
|
1300
|
+
".grid span { color: var(--muted); font-size: 11px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }",
|
|
1301
|
+
".grid strong { font-size: 1rem; overflow-wrap: anywhere; }",
|
|
1302
|
+
"textarea { width: 100%; min-height: 120px; resize: vertical; border: 1px solid var(--border); padding: 12px; }",
|
|
1303
|
+
"button { width: fit-content; border: 0; background: var(--accent); color: white; padding: 10px 14px; font-weight: 700; cursor: pointer; }",
|
|
1304
|
+
"button:disabled { cursor: wait; opacity: 0.65; }",
|
|
1305
|
+
"pre { overflow: auto; margin: 0; border: 1px solid var(--border); background: #f2f4f1; padding: 14px; }",
|
|
1306
|
+
"code { font-family: \"Cascadia Code\", monospace; }",
|
|
1307
|
+
"@media (max-width: 720px) { .grid { grid-template-columns: 1fr; } .grid div { border-right: 0; border-bottom: 1px solid var(--border); } }",
|
|
1308
|
+
].join("\n"),
|
|
1309
|
+
"src/app/layout.tsx": [
|
|
1310
|
+
'import "./globals.css";',
|
|
1311
|
+
'import type { ReactNode } from "react";',
|
|
1312
|
+
"",
|
|
1313
|
+
"export const metadata = {",
|
|
1314
|
+
' title: "Ekairos Agent",',
|
|
1315
|
+
' description: "Scaffolded Ekairos agent context app",',
|
|
1316
|
+
"};",
|
|
1317
|
+
"",
|
|
1318
|
+
"export default function RootLayout({ children }: { children: ReactNode }) {",
|
|
1319
|
+
" return (",
|
|
1320
|
+
' <html lang="en">',
|
|
1321
|
+
" <body>{children}</body>",
|
|
1322
|
+
" </html>",
|
|
1323
|
+
" );",
|
|
1324
|
+
"}",
|
|
1325
|
+
].join("\n"),
|
|
1326
|
+
"src/app/page.tsx": [
|
|
1327
|
+
'import AgentWorkbench from "./agent-workbench";',
|
|
1328
|
+
"",
|
|
1329
|
+
'export const dynamic = "force-dynamic";',
|
|
1330
|
+
"",
|
|
1331
|
+
"export default function HomePage() {",
|
|
1332
|
+
" return (",
|
|
1333
|
+
" <main>",
|
|
1334
|
+
' <section className="shell">',
|
|
1335
|
+
' <div className="eyebrow">Ekairos Agent Template</div>',
|
|
1336
|
+
" <h1>Start a context, inspect it in Workbench v2, iterate.</h1>",
|
|
1337
|
+
" <p>",
|
|
1338
|
+
" This template writes canonical @ekairos/events rows and returns the context id immediately.",
|
|
1339
|
+
" </p>",
|
|
1340
|
+
" <AgentWorkbench />",
|
|
1341
|
+
" </section>",
|
|
1342
|
+
" </main>",
|
|
1343
|
+
" );",
|
|
1344
|
+
"}",
|
|
1345
|
+
].join("\n"),
|
|
1346
|
+
"src/app/agent-workbench.tsx": [
|
|
1347
|
+
'"use client";',
|
|
1348
|
+
"",
|
|
1349
|
+
"import { useMemo, useState } from \"react\";",
|
|
1350
|
+
"",
|
|
1351
|
+
"type AgentResult = {",
|
|
1352
|
+
" ok: boolean;",
|
|
1353
|
+
" appId?: string;",
|
|
1354
|
+
" contextId?: string;",
|
|
1355
|
+
" contextKey?: string | null;",
|
|
1356
|
+
" triggerEventId?: string;",
|
|
1357
|
+
" error?: string;",
|
|
1358
|
+
"};",
|
|
1359
|
+
"",
|
|
1360
|
+
"export default function AgentWorkbench() {",
|
|
1361
|
+
" const [prompt, setPrompt] = useState(\"Create a deterministic template context.\");",
|
|
1362
|
+
" const [result, setResult] = useState<AgentResult | null>(null);",
|
|
1363
|
+
" const [pending, setPending] = useState(false);",
|
|
1364
|
+
"",
|
|
1365
|
+
" const workbenchPath = useMemo(() => {",
|
|
1366
|
+
" if (!result?.appId || !result?.contextId) return \"\";",
|
|
1367
|
+
" return `/app/${result.appId}/contexts/${result.contextId}`;",
|
|
1368
|
+
" }, [result]);",
|
|
1369
|
+
"",
|
|
1370
|
+
" async function startContext() {",
|
|
1371
|
+
" setPending(true);",
|
|
1372
|
+
" setResult(null);",
|
|
1373
|
+
" try {",
|
|
1374
|
+
" const response = await fetch(\"/api/agent/react\", {",
|
|
1375
|
+
" method: \"POST\",",
|
|
1376
|
+
" headers: { \"content-type\": \"application/json\" },",
|
|
1377
|
+
" body: JSON.stringify({ prompt }),",
|
|
1378
|
+
" });",
|
|
1379
|
+
" const data = (await response.json().catch(() => ({}))) as AgentResult;",
|
|
1380
|
+
" setResult(response.ok ? data : { ok: false, error: data.error || response.statusText });",
|
|
1381
|
+
" } catch (error) {",
|
|
1382
|
+
" setResult({ ok: false, error: error instanceof Error ? error.message : String(error) });",
|
|
1383
|
+
" } finally {",
|
|
1384
|
+
" setPending(false);",
|
|
1385
|
+
" }",
|
|
1386
|
+
" }",
|
|
1387
|
+
"",
|
|
1388
|
+
" return (",
|
|
1389
|
+
' <section className="panel">',
|
|
1390
|
+
' <div className="grid">',
|
|
1391
|
+
" <div><span>Schema</span><strong>@ekairos/events</strong></div>",
|
|
1392
|
+
" <div><span>Route</span><strong>/api/agent/react</strong></div>",
|
|
1393
|
+
" <div><span>Mode</span><strong>scripted reactor</strong></div>",
|
|
1394
|
+
" </div>",
|
|
1395
|
+
" <textarea value={prompt} onChange={(event) => setPrompt(event.target.value)} />",
|
|
1396
|
+
" <button type=\"button\" disabled={pending} onClick={startContext}>",
|
|
1397
|
+
" {pending ? \"Starting context...\" : \"Start context\"}",
|
|
1398
|
+
" </button>",
|
|
1399
|
+
" {workbenchPath ? <p>Workbench v2 path: <code>{workbenchPath}</code></p> : null}",
|
|
1400
|
+
" {result ? <pre>{JSON.stringify(result, null, 2)}</pre> : null}",
|
|
1401
|
+
" </section>",
|
|
1402
|
+
" );",
|
|
1403
|
+
"}",
|
|
1404
|
+
].join("\n"),
|
|
1405
|
+
"src/app/api/agent/react/route.ts": [
|
|
1406
|
+
'import { NextRequest, NextResponse } from "next/server";',
|
|
1407
|
+
'import { agentContext, createTriggerEvent } from "@/agent";',
|
|
1408
|
+
'import { createRuntime } from "@/runtime";',
|
|
1409
|
+
"",
|
|
1410
|
+
"export const maxDuration = 60;",
|
|
1411
|
+
"",
|
|
1412
|
+
"function readTrimmed(value: unknown) {",
|
|
1413
|
+
" return typeof value === \"string\" ? value.trim() : \"\";",
|
|
1414
|
+
"}",
|
|
1415
|
+
"",
|
|
1416
|
+
"export async function POST(req: NextRequest) {",
|
|
1417
|
+
" try {",
|
|
1418
|
+
" const body = await req.json().catch(() => ({}));",
|
|
1419
|
+
" const prompt = readTrimmed((body as any).prompt) || \"Create a deterministic template context.\";",
|
|
1420
|
+
" const contextKey = readTrimmed((body as any).contextKey) || `agent-template:${Date.now()}`;",
|
|
1421
|
+
" const triggerEvent = createTriggerEvent(prompt);",
|
|
1422
|
+
" const reaction = await agentContext.react(triggerEvent, {",
|
|
1423
|
+
" runtime: createRuntime({ actorId: \"template-user\" }),",
|
|
1424
|
+
" context: { key: contextKey },",
|
|
1425
|
+
" durable: false,",
|
|
1426
|
+
" });",
|
|
1427
|
+
" if (reaction.run) await reaction.run;",
|
|
1428
|
+
" return NextResponse.json({",
|
|
1429
|
+
" ok: true,",
|
|
1430
|
+
" appId: process.env.NEXT_PUBLIC_INSTANT_APP_ID || null,",
|
|
1431
|
+
" contextId: String(reaction.context.id || \"\"),",
|
|
1432
|
+
" contextKey: reaction.context.key ?? contextKey,",
|
|
1433
|
+
" triggerEventId: triggerEvent.id,",
|
|
1434
|
+
" });",
|
|
1435
|
+
" } catch (error) {",
|
|
1436
|
+
" return NextResponse.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, { status: 500 });",
|
|
1437
|
+
" }",
|
|
1438
|
+
"}",
|
|
1439
|
+
].join("\n"),
|
|
1440
|
+
"src/agent.ts": [
|
|
1441
|
+
"import {",
|
|
1442
|
+
" createContext,",
|
|
1443
|
+
" createScriptedReactor,",
|
|
1444
|
+
" defineAction,",
|
|
1445
|
+
" eventsDomain,",
|
|
1446
|
+
" type ContextItem,",
|
|
1447
|
+
"} from \"@ekairos/events\";",
|
|
1448
|
+
"import { z } from \"zod\";",
|
|
1449
|
+
"",
|
|
1450
|
+
"export type AgentEnv = {",
|
|
1451
|
+
" actorId?: string;",
|
|
1452
|
+
"};",
|
|
1453
|
+
"",
|
|
1454
|
+
"const createMessageInput = z.object({",
|
|
1455
|
+
" message: z.string(),",
|
|
1456
|
+
" marker: z.string().optional(),",
|
|
1457
|
+
"});",
|
|
1458
|
+
"",
|
|
1459
|
+
"const createMessageOutput = z.object({",
|
|
1460
|
+
" message: z.string(),",
|
|
1461
|
+
" marker: z.string(),",
|
|
1462
|
+
"});",
|
|
1463
|
+
"",
|
|
1464
|
+
"async function createMessage(input: z.infer<typeof createMessageInput>) {",
|
|
1465
|
+
" \"use step\";",
|
|
1466
|
+
" return {",
|
|
1467
|
+
" message: input.message,",
|
|
1468
|
+
" marker: input.marker ?? \"AGENT_TEMPLATE_OK\",",
|
|
1469
|
+
" };",
|
|
1470
|
+
"}",
|
|
1471
|
+
"",
|
|
1472
|
+
"function randomId() {",
|
|
1473
|
+
" return globalThis.crypto?.randomUUID?.() ?? `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;",
|
|
1474
|
+
"}",
|
|
1475
|
+
"",
|
|
1476
|
+
"export function createTriggerEvent(text: string): ContextItem {",
|
|
1477
|
+
" return {",
|
|
1478
|
+
" id: randomId(),",
|
|
1479
|
+
" type: \"user_message_created\",",
|
|
1480
|
+
" channel: \"web\",",
|
|
1481
|
+
" createdAt: new Date().toISOString(),",
|
|
1482
|
+
" content: { parts: [{ type: \"text\", text }] },",
|
|
1483
|
+
" } as any;",
|
|
1484
|
+
"}",
|
|
1485
|
+
"",
|
|
1486
|
+
"export const agentContext = createContext<AgentEnv>(\"agent.template\")",
|
|
1487
|
+
" .context((stored, env) => ({ ...(stored.content ?? {}), actorId: env.actorId ?? null }))",
|
|
1488
|
+
" .narrative(() => \"You are the minimal Ekairos agent template. Create one deterministic message.\")",
|
|
1489
|
+
" .actions(() => ({",
|
|
1490
|
+
" createMessage: defineAction<any, any, Record<string, never>, AgentEnv, typeof eventsDomain>({",
|
|
1491
|
+
" description: \"Create the deterministic template message.\",",
|
|
1492
|
+
" input: createMessageInput as any,",
|
|
1493
|
+
" output: createMessageOutput as any,",
|
|
1494
|
+
" execute: async ({ input }) => await createMessage(input),",
|
|
1495
|
+
" }),",
|
|
1496
|
+
" }))",
|
|
1497
|
+
" .reactor(",
|
|
1498
|
+
" createScriptedReactor({",
|
|
1499
|
+
" steps: [",
|
|
1500
|
+
" {",
|
|
1501
|
+
" assistantEvent: {",
|
|
1502
|
+
" content: {",
|
|
1503
|
+
" parts: [",
|
|
1504
|
+
" { type: \"text\", text: \"AGENT_TEMPLATE_OK context ready.\" },",
|
|
1505
|
+
" {",
|
|
1506
|
+
" type: \"tool-createMessage\",",
|
|
1507
|
+
" toolCallId: \"agent-template-create-message\",",
|
|
1508
|
+
" input: { message: \"AGENT_TEMPLATE_OK context ready.\", marker: \"AGENT_TEMPLATE_OK\" },",
|
|
1509
|
+
" },",
|
|
1510
|
+
" ],",
|
|
1511
|
+
" },",
|
|
1512
|
+
" },",
|
|
1513
|
+
" actionRequests: [",
|
|
1514
|
+
" {",
|
|
1515
|
+
" actionRef: \"agent-template-create-message\",",
|
|
1516
|
+
" actionName: \"createMessage\",",
|
|
1517
|
+
" input: { message: \"AGENT_TEMPLATE_OK context ready.\", marker: \"AGENT_TEMPLATE_OK\" },",
|
|
1518
|
+
" },",
|
|
1519
|
+
" ],",
|
|
1520
|
+
" messagesForModel: [],",
|
|
1521
|
+
" llm: { provider: \"scripted\", model: \"agent-template\" },",
|
|
1522
|
+
" },",
|
|
1523
|
+
" ],",
|
|
1524
|
+
" repeatLast: true,",
|
|
1525
|
+
" }),",
|
|
1526
|
+
" )",
|
|
1527
|
+
" .shouldContinue(() => false)",
|
|
1528
|
+
" .build();",
|
|
1529
|
+
].join("\n"),
|
|
1530
|
+
"src/runtime.ts": [
|
|
1531
|
+
'import { init } from "@instantdb/admin";',
|
|
1532
|
+
'import { EkairosRuntime } from "@ekairos/domain/runtime-handle";',
|
|
1533
|
+
'import { eventsDomain } from "@ekairos/events/schema";',
|
|
1534
|
+
"",
|
|
1535
|
+
"export type AgentRuntimeEnv = {",
|
|
1536
|
+
" actorId?: string | null;",
|
|
1537
|
+
" adminToken?: string;",
|
|
1538
|
+
" appId?: string;",
|
|
1539
|
+
"};",
|
|
1540
|
+
"",
|
|
1541
|
+
"function resolveRuntimeEnv(env: AgentRuntimeEnv = {}): Required<Pick<AgentRuntimeEnv, \"appId\" | \"adminToken\">> & AgentRuntimeEnv {",
|
|
1542
|
+
" const appId = String(env.appId ?? process.env.NEXT_PUBLIC_INSTANT_APP_ID ?? \"\").trim();",
|
|
1543
|
+
" const adminToken = String(env.adminToken ?? process.env.INSTANT_ADMIN_TOKEN ?? \"\").trim();",
|
|
1544
|
+
" if (!appId || !adminToken) {",
|
|
1545
|
+
" throw new Error(\"Missing NEXT_PUBLIC_INSTANT_APP_ID or INSTANT_ADMIN_TOKEN. Copy .env.example to .env.local and fill both values.\");",
|
|
1546
|
+
" }",
|
|
1547
|
+
" return { ...env, appId, adminToken };",
|
|
1548
|
+
"}",
|
|
1549
|
+
"",
|
|
1550
|
+
"export class AgentRuntime extends EkairosRuntime<AgentRuntimeEnv, typeof eventsDomain, any> {",
|
|
1551
|
+
" protected getDomain() { return eventsDomain; }",
|
|
1552
|
+
" protected async resolveDb(env: AgentRuntimeEnv) {",
|
|
1553
|
+
" const resolved = resolveRuntimeEnv(env);",
|
|
1554
|
+
" return init({",
|
|
1555
|
+
" appId: resolved.appId,",
|
|
1556
|
+
" adminToken: resolved.adminToken,",
|
|
1557
|
+
" schema: eventsDomain.toInstantSchema(),",
|
|
1558
|
+
" useDateObjects: true,",
|
|
1559
|
+
" } as any) as any;",
|
|
1560
|
+
" }",
|
|
1561
|
+
"}",
|
|
1562
|
+
"",
|
|
1563
|
+
"export function createRuntime(env: AgentRuntimeEnv = {}) {",
|
|
1564
|
+
" return new AgentRuntime(resolveRuntimeEnv(env));",
|
|
1565
|
+
"}",
|
|
1566
|
+
].join("\n"),
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
return {
|
|
1570
|
+
".gitignore": [".next", "node_modules", ".env.local", ".workflow-data"].join("\n"),
|
|
1571
|
+
".env.example": [
|
|
1572
|
+
"NEXT_PUBLIC_INSTANT_APP_ID=",
|
|
1573
|
+
"INSTANT_ADMIN_TOKEN=",
|
|
1574
|
+
"",
|
|
1575
|
+
"# Optional: use this only while provisioning new apps with the CLI.",
|
|
1576
|
+
"INSTANT_PERSONAL_ACCESS_TOKEN=",
|
|
1577
|
+
].join("\n"),
|
|
1578
|
+
"DOMAIN.md": [
|
|
1579
|
+
"# Ekairos Supply Chain Domain",
|
|
1580
|
+
"",
|
|
1581
|
+
"This scaffold ships a supply-chain control tower backed by separate domains:",
|
|
1582
|
+
"- `supplierNetwork` owns supplier risk and score",
|
|
1583
|
+
"- `procurement` owns purchase orders",
|
|
1584
|
+
"- `inventory` owns stock position",
|
|
1585
|
+
"- `transportation` owns shipments and ETA",
|
|
1586
|
+
"- `qualityControl` owns arrival inspection",
|
|
1587
|
+
"",
|
|
1588
|
+
"Actions:",
|
|
1589
|
+
"- `launchOrder` -> creates and links supplier, order, stock, shipment, and inspection",
|
|
1590
|
+
"- `expediteShipment` -> updates shipment status and ETA",
|
|
1591
|
+
].join("\n"),
|
|
1592
|
+
"instant.schema.ts": [
|
|
1593
|
+
'import appDomain from "./src/domain";',
|
|
1594
|
+
"",
|
|
1595
|
+
"const schema = appDomain.toInstantSchema();",
|
|
1596
|
+
"",
|
|
1597
|
+
"export default schema;",
|
|
1598
|
+
].join("\n"),
|
|
1599
|
+
"next-env.d.ts": [
|
|
1600
|
+
'/// <reference types="next" />',
|
|
1601
|
+
'/// <reference types="next/image-types/global" />',
|
|
1602
|
+
"",
|
|
1603
|
+
"// This file is managed by Next.js.",
|
|
1604
|
+
].join("\n"),
|
|
1605
|
+
"next.config.ts": [
|
|
1606
|
+
'import type { NextConfig } from "next";',
|
|
1607
|
+
'import { withWorkflow } from "workflow/next";',
|
|
1608
|
+
"",
|
|
1609
|
+
"const nextConfig: NextConfig = {",
|
|
1610
|
+
" transpilePackages: [\"@ekairos/domain\"],",
|
|
1611
|
+
"};",
|
|
1612
|
+
"",
|
|
1613
|
+
"export default withWorkflow(nextConfig) as NextConfig;",
|
|
1614
|
+
].join("\n"),
|
|
1615
|
+
"src/app/api/ekairos/domain/route.ts": [
|
|
1616
|
+
'import { createRuntimeRouteHandler } from "@ekairos/domain/next";',
|
|
1617
|
+
'import { createRuntime } from "@/runtime";',
|
|
1618
|
+
"",
|
|
1619
|
+
"export const { GET, POST } = createRuntimeRouteHandler({",
|
|
1620
|
+
" createRuntime,",
|
|
1621
|
+
"});",
|
|
1622
|
+
].join("\n"),
|
|
1623
|
+
"package.json": `${JSON.stringify(packageJson, null, 2)}\n`,
|
|
1624
|
+
"tsconfig.json": [
|
|
1625
|
+
"{",
|
|
1626
|
+
' "compilerOptions": {',
|
|
1627
|
+
' "target": "ES2022",',
|
|
1628
|
+
' "lib": ["dom", "dom.iterable", "es2022"],',
|
|
1629
|
+
' "allowJs": false,',
|
|
1630
|
+
' "skipLibCheck": true,',
|
|
1631
|
+
' "strict": true,',
|
|
1632
|
+
' "noEmit": true,',
|
|
1633
|
+
' "esModuleInterop": true,',
|
|
1634
|
+
' "module": "esnext",',
|
|
1635
|
+
' "moduleResolution": "bundler",',
|
|
1636
|
+
' "resolveJsonModule": true,',
|
|
1637
|
+
' "isolatedModules": true,',
|
|
1638
|
+
' "jsx": "react-jsx",',
|
|
1639
|
+
' "incremental": true,',
|
|
1640
|
+
' "paths": {',
|
|
1641
|
+
' "@/*": ["./src/*"]',
|
|
1642
|
+
" },",
|
|
1643
|
+
' "plugins": [',
|
|
1644
|
+
' { "name": "next" }',
|
|
1645
|
+
" ]",
|
|
1646
|
+
" },",
|
|
1647
|
+
' "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],',
|
|
1648
|
+
' "exclude": ["node_modules"]',
|
|
1649
|
+
"}",
|
|
1650
|
+
].join("\n"),
|
|
1651
|
+
"src/app/globals.css": [
|
|
1652
|
+
":root {",
|
|
1653
|
+
" color-scheme: light;",
|
|
1654
|
+
" --bg: #f7f8f6;",
|
|
1655
|
+
" --surface: #ffffff;",
|
|
1656
|
+
" --surface-2: #f0f2ef;",
|
|
1657
|
+
" --ink: #171a18;",
|
|
1658
|
+
" --muted: #646b66;",
|
|
1659
|
+
" --accent: #26715f;",
|
|
1660
|
+
" --accent-soft: #dfece7;",
|
|
1661
|
+
" --border: #d9ded9;",
|
|
1662
|
+
" --line: #b9c1ba;",
|
|
1663
|
+
" --danger: #9f2f28;",
|
|
1664
|
+
"}",
|
|
1665
|
+
"",
|
|
1666
|
+
"* {",
|
|
1667
|
+
" box-sizing: border-box;",
|
|
1668
|
+
"}",
|
|
1669
|
+
"",
|
|
1670
|
+
"html,",
|
|
1671
|
+
"body {",
|
|
1672
|
+
" margin: 0;",
|
|
1673
|
+
" min-height: 100%;",
|
|
1674
|
+
" background: var(--bg);",
|
|
1675
|
+
" color: var(--ink);",
|
|
1676
|
+
" font-family: \"Geist\", \"Aptos\", \"Segoe UI\", sans-serif;",
|
|
1677
|
+
"}",
|
|
1678
|
+
"",
|
|
1679
|
+
"body {",
|
|
1680
|
+
" min-height: 100dvh;",
|
|
1681
|
+
"}",
|
|
1682
|
+
"",
|
|
1683
|
+
"button,",
|
|
1684
|
+
"input {",
|
|
1685
|
+
" font: inherit;",
|
|
1686
|
+
"}",
|
|
1687
|
+
"",
|
|
1688
|
+
"main {",
|
|
1689
|
+
" width: min(1240px, calc(100% - 40px));",
|
|
1690
|
+
" margin: 0 auto;",
|
|
1691
|
+
" padding: 44px 0 64px;",
|
|
1692
|
+
"}",
|
|
1693
|
+
"",
|
|
1694
|
+
".hero {",
|
|
1695
|
+
" display: grid;",
|
|
1696
|
+
" gap: 14px;",
|
|
1697
|
+
" max-width: 720px;",
|
|
1698
|
+
" margin-bottom: 28px;",
|
|
1699
|
+
"}",
|
|
1700
|
+
"",
|
|
1701
|
+
".eyebrow {",
|
|
1702
|
+
" color: var(--accent);",
|
|
1703
|
+
" font-size: 11px;",
|
|
1704
|
+
" font-weight: 800;",
|
|
1705
|
+
" letter-spacing: 0.16em;",
|
|
1706
|
+
" text-transform: uppercase;",
|
|
1707
|
+
"}",
|
|
1708
|
+
"",
|
|
1709
|
+
"h1,",
|
|
1710
|
+
"h2 {",
|
|
1711
|
+
" margin: 0;",
|
|
1712
|
+
" letter-spacing: -0.04em;",
|
|
1713
|
+
"}",
|
|
1714
|
+
"",
|
|
1715
|
+
"h1 {",
|
|
1716
|
+
" max-width: 640px;",
|
|
1717
|
+
" font-size: clamp(2.4rem, 5.4vw, 4.8rem);",
|
|
1718
|
+
" line-height: 0.96;",
|
|
1719
|
+
"}",
|
|
1720
|
+
"",
|
|
1721
|
+
"h2 {",
|
|
1722
|
+
" font-size: 1.28rem;",
|
|
1723
|
+
" line-height: 1.05;",
|
|
1724
|
+
"}",
|
|
1725
|
+
"",
|
|
1726
|
+
"p {",
|
|
1727
|
+
" margin: 0;",
|
|
1728
|
+
" color: var(--muted);",
|
|
1729
|
+
" line-height: 1.6;",
|
|
1730
|
+
"}",
|
|
1731
|
+
"",
|
|
1732
|
+
".shell {",
|
|
1733
|
+
" display: grid;",
|
|
1734
|
+
" gap: 14px;",
|
|
1735
|
+
"}",
|
|
1736
|
+
"",
|
|
1737
|
+
".metrics-strip {",
|
|
1738
|
+
" display: grid;",
|
|
1739
|
+
" grid-template-columns: repeat(4, minmax(0, 1fr));",
|
|
1740
|
+
" border: 1px solid var(--border);",
|
|
1741
|
+
" background: var(--surface);",
|
|
1742
|
+
"}",
|
|
1743
|
+
"",
|
|
1744
|
+
".metrics-strip div {",
|
|
1745
|
+
" display: grid;",
|
|
1746
|
+
" gap: 6px;",
|
|
1747
|
+
" padding: 16px;",
|
|
1748
|
+
" border-right: 1px solid var(--border);",
|
|
1749
|
+
"}",
|
|
1750
|
+
"",
|
|
1751
|
+
".metrics-strip div:last-child {",
|
|
1752
|
+
" border-right: 0;",
|
|
1753
|
+
"}",
|
|
1754
|
+
"",
|
|
1755
|
+
".metrics-strip span,",
|
|
1756
|
+
".manifest-label {",
|
|
1757
|
+
" color: var(--muted);",
|
|
1758
|
+
" font-size: 11px;",
|
|
1759
|
+
" font-weight: 800;",
|
|
1760
|
+
" letter-spacing: 0.12em;",
|
|
1761
|
+
" text-transform: uppercase;",
|
|
1762
|
+
"}",
|
|
1763
|
+
"",
|
|
1764
|
+
".metrics-strip strong {",
|
|
1765
|
+
" font-size: 1.85rem;",
|
|
1766
|
+
" letter-spacing: -0.04em;",
|
|
1767
|
+
" line-height: 1;",
|
|
1768
|
+
"}",
|
|
1769
|
+
"",
|
|
1770
|
+
".workbench {",
|
|
1771
|
+
" display: grid;",
|
|
1772
|
+
" grid-template-columns: 0.8fr 1fr;",
|
|
1773
|
+
" gap: 14px;",
|
|
1774
|
+
" align-items: start;",
|
|
1775
|
+
"}",
|
|
1776
|
+
"",
|
|
1777
|
+
".context-rail,",
|
|
1778
|
+
".command-panel,",
|
|
1779
|
+
".graph-panel {",
|
|
1780
|
+
" border: 1px solid var(--border);",
|
|
1781
|
+
" background: var(--surface);",
|
|
1782
|
+
"}",
|
|
1783
|
+
"",
|
|
1784
|
+
".context-rail,",
|
|
1785
|
+
".command-panel {",
|
|
1786
|
+
" padding: 18px;",
|
|
1787
|
+
"}",
|
|
1788
|
+
"",
|
|
1789
|
+
".graph-panel {",
|
|
1790
|
+
" grid-column: 1 / -1;",
|
|
1791
|
+
" padding: 18px;",
|
|
1792
|
+
"}",
|
|
1793
|
+
"",
|
|
1794
|
+
".panel-head {",
|
|
1795
|
+
" display: flex;",
|
|
1796
|
+
" justify-content: space-between;",
|
|
1797
|
+
" gap: 16px;",
|
|
1798
|
+
" align-items: start;",
|
|
1799
|
+
" margin-bottom: 16px;",
|
|
1800
|
+
"}",
|
|
1801
|
+
"",
|
|
1802
|
+
".context-list,",
|
|
1803
|
+
".project-list,",
|
|
1804
|
+
".task-lines,",
|
|
1805
|
+
".lane-grid,",
|
|
1806
|
+
".calls-feed,",
|
|
1807
|
+
".button-row {",
|
|
1808
|
+
" display: grid;",
|
|
1809
|
+
" gap: 10px;",
|
|
1810
|
+
"}",
|
|
1811
|
+
"",
|
|
1812
|
+
".context-row {",
|
|
1813
|
+
" display: grid;",
|
|
1814
|
+
" grid-template-columns: 0.7fr 1fr;",
|
|
1815
|
+
" gap: 14px;",
|
|
1816
|
+
" padding: 12px 0;",
|
|
1817
|
+
" border-top: 1px solid var(--border);",
|
|
1818
|
+
" opacity: 0;",
|
|
1819
|
+
" transform: translateY(8px);",
|
|
1820
|
+
" animation: lift-in 420ms cubic-bezier(0.16, 1, 0.3, 1) forwards;",
|
|
1821
|
+
" animation-delay: calc(var(--index) * 70ms);",
|
|
1822
|
+
"}",
|
|
1823
|
+
"",
|
|
1824
|
+
".context-row span {",
|
|
1825
|
+
" font-weight: 800;",
|
|
1826
|
+
"}",
|
|
1827
|
+
"",
|
|
1828
|
+
".context-row strong {",
|
|
1829
|
+
" color: var(--muted);",
|
|
1830
|
+
" font-size: 0.92rem;",
|
|
1831
|
+
" font-weight: 500;",
|
|
1832
|
+
"}",
|
|
1833
|
+
"",
|
|
1834
|
+
".field {",
|
|
1835
|
+
" display: grid;",
|
|
1836
|
+
" gap: 7px;",
|
|
1837
|
+
" margin: 14px 0;",
|
|
1838
|
+
"}",
|
|
1839
|
+
"",
|
|
1840
|
+
".field span {",
|
|
1841
|
+
" color: var(--muted);",
|
|
1842
|
+
" font-size: 0.85rem;",
|
|
1843
|
+
" font-weight: 700;",
|
|
1844
|
+
"}",
|
|
1845
|
+
"",
|
|
1846
|
+
".input {",
|
|
1847
|
+
" width: 100%;",
|
|
1848
|
+
" border: 1px solid var(--border);",
|
|
1849
|
+
" border-radius: 6px;",
|
|
1850
|
+
" padding: 12px 13px;",
|
|
1851
|
+
" background: var(--surface-2);",
|
|
1852
|
+
" color: var(--ink);",
|
|
1853
|
+
" outline: none;",
|
|
1854
|
+
"}",
|
|
1855
|
+
"",
|
|
1856
|
+
".input:focus {",
|
|
1857
|
+
" border-color: var(--accent);",
|
|
1858
|
+
" background: var(--surface);",
|
|
1859
|
+
"}",
|
|
1860
|
+
"",
|
|
1861
|
+
".button-row {",
|
|
1862
|
+
" grid-template-columns: repeat(2, minmax(0, 1fr));",
|
|
1863
|
+
" margin-top: 16px;",
|
|
1864
|
+
"}",
|
|
1865
|
+
"",
|
|
1866
|
+
".button {",
|
|
1867
|
+
" appearance: none;",
|
|
1868
|
+
" border: 1px solid var(--ink);",
|
|
1869
|
+
" border-radius: 6px;",
|
|
1870
|
+
" padding: 12px 14px;",
|
|
1871
|
+
" background: var(--ink);",
|
|
1872
|
+
" color: #ffffff;",
|
|
1873
|
+
" cursor: pointer;",
|
|
1874
|
+
" font-weight: 800;",
|
|
1875
|
+
" transition: transform 160ms cubic-bezier(0.16, 1, 0.3, 1), opacity 160ms ease;",
|
|
1876
|
+
"}",
|
|
1877
|
+
"",
|
|
1878
|
+
".button:hover:not(:disabled) {",
|
|
1879
|
+
" transform: translateY(-1px);",
|
|
1880
|
+
"}",
|
|
1881
|
+
"",
|
|
1882
|
+
".button:active:not(:disabled) {",
|
|
1883
|
+
" transform: translateY(1px) scale(0.99);",
|
|
1884
|
+
"}",
|
|
1885
|
+
"",
|
|
1886
|
+
".button:disabled {",
|
|
1887
|
+
" cursor: wait;",
|
|
1888
|
+
" opacity: 0.62;",
|
|
1889
|
+
"}",
|
|
1890
|
+
"",
|
|
1891
|
+
".button.ghost {",
|
|
1892
|
+
" border-color: var(--border);",
|
|
1893
|
+
" background: var(--surface);",
|
|
1894
|
+
" color: var(--ink);",
|
|
1895
|
+
"}",
|
|
1896
|
+
"",
|
|
1897
|
+
".status-pill {",
|
|
1898
|
+
" display: inline-flex;",
|
|
1899
|
+
" align-items: center;",
|
|
1900
|
+
" justify-content: center;",
|
|
1901
|
+
" min-width: 86px;",
|
|
1902
|
+
" padding: 7px 10px;",
|
|
1903
|
+
" border-radius: 6px;",
|
|
1904
|
+
" background: var(--accent-soft);",
|
|
1905
|
+
" color: var(--accent);",
|
|
1906
|
+
" font-size: 11px;",
|
|
1907
|
+
" font-weight: 800;",
|
|
1908
|
+
" letter-spacing: 0.08em;",
|
|
1909
|
+
" text-transform: uppercase;",
|
|
1910
|
+
"}",
|
|
1911
|
+
"",
|
|
1912
|
+
".project-row {",
|
|
1913
|
+
" display: grid;",
|
|
1914
|
+
" grid-template-columns: 1fr auto;",
|
|
1915
|
+
" gap: 14px;",
|
|
1916
|
+
" padding: 16px 0;",
|
|
1917
|
+
" border-top: 1px solid var(--border);",
|
|
1918
|
+
"}",
|
|
1919
|
+
"",
|
|
1920
|
+
".project-row:first-child {",
|
|
1921
|
+
" border-top: 0;",
|
|
1922
|
+
"}",
|
|
1923
|
+
"",
|
|
1924
|
+
".project-row strong {",
|
|
1925
|
+
" display: block;",
|
|
1926
|
+
" font-size: 1.05rem;",
|
|
1927
|
+
"}",
|
|
1928
|
+
"",
|
|
1929
|
+
".project-row span {",
|
|
1930
|
+
" color: var(--muted);",
|
|
1931
|
+
"}",
|
|
1932
|
+
"",
|
|
1933
|
+
".project-meta {",
|
|
1934
|
+
" display: flex;",
|
|
1935
|
+
" flex-wrap: wrap;",
|
|
1936
|
+
" gap: 8px;",
|
|
1937
|
+
" justify-content: flex-end;",
|
|
1938
|
+
"}",
|
|
1939
|
+
"",
|
|
1940
|
+
".project-meta span {",
|
|
1941
|
+
" border: 1px solid var(--border);",
|
|
1942
|
+
" border-radius: 6px;",
|
|
1943
|
+
" padding: 5px 8px;",
|
|
1944
|
+
" color: var(--ink);",
|
|
1945
|
+
" font-size: 0.82rem;",
|
|
1946
|
+
" font-weight: 700;",
|
|
1947
|
+
"}",
|
|
1948
|
+
"",
|
|
1949
|
+
".task-lines {",
|
|
1950
|
+
" grid-column: 1 / -1;",
|
|
1951
|
+
" padding-top: 2px;",
|
|
1952
|
+
"}",
|
|
1953
|
+
"",
|
|
1954
|
+
".task-lines p {",
|
|
1955
|
+
" display: grid;",
|
|
1956
|
+
" grid-template-columns: minmax(180px, 0.8fr) minmax(180px, 1fr);",
|
|
1957
|
+
" gap: 12px;",
|
|
1958
|
+
" padding-left: 14px;",
|
|
1959
|
+
" border-left: 2px solid var(--line);",
|
|
1960
|
+
"}",
|
|
1961
|
+
"",
|
|
1962
|
+
".task-lines b {",
|
|
1963
|
+
" color: var(--ink);",
|
|
1964
|
+
"}",
|
|
1965
|
+
"",
|
|
1966
|
+
".task-lines em {",
|
|
1967
|
+
" grid-column: 1 / -1;",
|
|
1968
|
+
" color: var(--muted);",
|
|
1969
|
+
" font-style: normal;",
|
|
1970
|
+
"}",
|
|
1971
|
+
"",
|
|
1972
|
+
".raid-summary {",
|
|
1973
|
+
" display: grid;",
|
|
1974
|
+
" grid-template-columns: repeat(3, minmax(0, 1fr));",
|
|
1975
|
+
" border: 1px solid var(--border);",
|
|
1976
|
+
" margin-bottom: 14px;",
|
|
1977
|
+
"}",
|
|
1978
|
+
"",
|
|
1979
|
+
".raid-summary div {",
|
|
1980
|
+
" display: grid;",
|
|
1981
|
+
" gap: 6px;",
|
|
1982
|
+
" padding: 13px;",
|
|
1983
|
+
" border-right: 1px solid var(--border);",
|
|
1984
|
+
"}",
|
|
1985
|
+
"",
|
|
1986
|
+
".raid-summary div:last-child {",
|
|
1987
|
+
" border-right: 0;",
|
|
1988
|
+
"}",
|
|
1989
|
+
"",
|
|
1990
|
+
".raid-summary span {",
|
|
1991
|
+
" color: var(--muted);",
|
|
1992
|
+
" font-size: 11px;",
|
|
1993
|
+
" font-weight: 800;",
|
|
1994
|
+
" letter-spacing: 0.12em;",
|
|
1995
|
+
" text-transform: uppercase;",
|
|
1996
|
+
"}",
|
|
1997
|
+
"",
|
|
1998
|
+
".raid-summary strong {",
|
|
1999
|
+
" font-size: 1rem;",
|
|
2000
|
+
"}",
|
|
2001
|
+
"",
|
|
2002
|
+
".lane-grid {",
|
|
2003
|
+
" grid-template-columns: repeat(3, minmax(0, 1fr));",
|
|
2004
|
+
"}",
|
|
2005
|
+
"",
|
|
2006
|
+
".lane {",
|
|
2007
|
+
" min-height: 180px;",
|
|
2008
|
+
" border: 1px solid var(--border);",
|
|
2009
|
+
" padding: 12px;",
|
|
2010
|
+
" background: var(--surface-2);",
|
|
2011
|
+
"}",
|
|
2012
|
+
"",
|
|
2013
|
+
".objective {",
|
|
2014
|
+
" display: grid;",
|
|
2015
|
+
" gap: 5px;",
|
|
2016
|
+
" margin-top: 10px;",
|
|
2017
|
+
" padding: 10px;",
|
|
2018
|
+
" border: 1px solid var(--border);",
|
|
2019
|
+
" background: var(--surface);",
|
|
2020
|
+
"}",
|
|
2021
|
+
"",
|
|
2022
|
+
".objective span,",
|
|
2023
|
+
".objective em {",
|
|
2024
|
+
" color: var(--muted);",
|
|
2025
|
+
" font-size: 0.88rem;",
|
|
2026
|
+
" font-style: normal;",
|
|
2027
|
+
"}",
|
|
2028
|
+
"",
|
|
2029
|
+
".calls-feed {",
|
|
2030
|
+
" margin-top: 14px;",
|
|
2031
|
+
" padding-top: 14px;",
|
|
2032
|
+
" border-top: 1px solid var(--border);",
|
|
2033
|
+
"}",
|
|
2034
|
+
"",
|
|
2035
|
+
".calls-feed p {",
|
|
2036
|
+
" display: flex;",
|
|
2037
|
+
" justify-content: space-between;",
|
|
2038
|
+
" gap: 16px;",
|
|
2039
|
+
"}",
|
|
2040
|
+
"",
|
|
2041
|
+
".calls-feed b {",
|
|
2042
|
+
" color: var(--ink);",
|
|
2043
|
+
"}",
|
|
2044
|
+
"",
|
|
2045
|
+
".muted {",
|
|
2046
|
+
" color: var(--muted);",
|
|
2047
|
+
"}",
|
|
2048
|
+
"",
|
|
2049
|
+
".empty-state,",
|
|
2050
|
+
".error-banner {",
|
|
2051
|
+
" border: 1px solid var(--border);",
|
|
2052
|
+
" border-radius: 6px;",
|
|
2053
|
+
" padding: 14px;",
|
|
2054
|
+
"}",
|
|
2055
|
+
"",
|
|
2056
|
+
".empty-state {",
|
|
2057
|
+
" background: var(--surface-2);",
|
|
2058
|
+
" color: var(--muted);",
|
|
2059
|
+
"}",
|
|
2060
|
+
"",
|
|
2061
|
+
".error-banner {",
|
|
2062
|
+
" background: #fff4f2;",
|
|
2063
|
+
" color: var(--danger);",
|
|
2064
|
+
" border-color: #efc4bd;",
|
|
2065
|
+
"}",
|
|
2066
|
+
"",
|
|
2067
|
+
".skeleton-stack {",
|
|
2068
|
+
" display: grid;",
|
|
2069
|
+
" gap: 10px;",
|
|
2070
|
+
"}",
|
|
2071
|
+
"",
|
|
2072
|
+
".skeleton-stack span {",
|
|
2073
|
+
" display: block;",
|
|
2074
|
+
" height: 54px;",
|
|
2075
|
+
" border-radius: 6px;",
|
|
2076
|
+
" background: linear-gradient(90deg, var(--surface-2), #ffffff, var(--surface-2));",
|
|
2077
|
+
" background-size: 220% 100%;",
|
|
2078
|
+
" animation: shimmer 1.2s ease-in-out infinite;",
|
|
2079
|
+
"}",
|
|
2080
|
+
"",
|
|
2081
|
+
"pre {",
|
|
2082
|
+
" overflow: auto;",
|
|
2083
|
+
" margin-top: 14px;",
|
|
2084
|
+
" border-radius: 6px;",
|
|
2085
|
+
" padding: 12px;",
|
|
2086
|
+
" background: #1d211f;",
|
|
2087
|
+
" color: #eef5f1;",
|
|
2088
|
+
" font-size: 12px;",
|
|
2089
|
+
" line-height: 1.45;",
|
|
2090
|
+
"}",
|
|
2091
|
+
"",
|
|
2092
|
+
"code {",
|
|
2093
|
+
" font-family: \"Geist Mono\", \"Cascadia Code\", monospace;",
|
|
2094
|
+
"}",
|
|
2095
|
+
"",
|
|
2096
|
+
"@keyframes lift-in {",
|
|
2097
|
+
" to {",
|
|
2098
|
+
" opacity: 1;",
|
|
2099
|
+
" transform: translateY(0);",
|
|
2100
|
+
" }",
|
|
2101
|
+
"}",
|
|
2102
|
+
"",
|
|
2103
|
+
"@keyframes shimmer {",
|
|
2104
|
+
" to {",
|
|
2105
|
+
" background-position: -220% 0;",
|
|
2106
|
+
" }",
|
|
2107
|
+
"}",
|
|
2108
|
+
"",
|
|
2109
|
+
"@media (max-width: 820px) {",
|
|
2110
|
+
" main {",
|
|
2111
|
+
" width: min(100% - 28px, 1240px);",
|
|
2112
|
+
" padding: 32px 0 52px;",
|
|
2113
|
+
" }",
|
|
2114
|
+
"",
|
|
2115
|
+
" .metrics-strip,",
|
|
2116
|
+
" .workbench,",
|
|
2117
|
+
" .button-row,",
|
|
2118
|
+
" .raid-summary,",
|
|
2119
|
+
" .lane-grid,",
|
|
2120
|
+
" .project-row,",
|
|
2121
|
+
" .task-lines p {",
|
|
2122
|
+
" grid-template-columns: 1fr;",
|
|
2123
|
+
" }",
|
|
2124
|
+
"",
|
|
2125
|
+
" .metrics-strip div {",
|
|
2126
|
+
" border-right: 0;",
|
|
2127
|
+
" border-bottom: 1px solid var(--border);",
|
|
2128
|
+
" }",
|
|
2129
|
+
"",
|
|
2130
|
+
" .metrics-strip div:last-child {",
|
|
2131
|
+
" border-bottom: 0;",
|
|
2132
|
+
" }",
|
|
2133
|
+
"",
|
|
2134
|
+
" .project-meta {",
|
|
2135
|
+
" justify-content: flex-start;",
|
|
2136
|
+
" }",
|
|
2137
|
+
"",
|
|
2138
|
+
" .raid-summary div {",
|
|
2139
|
+
" border-right: 0;",
|
|
2140
|
+
" border-bottom: 1px solid var(--border);",
|
|
2141
|
+
" }",
|
|
2142
|
+
"",
|
|
2143
|
+
" .raid-summary div:last-child {",
|
|
2144
|
+
" border-bottom: 0;",
|
|
2145
|
+
" }",
|
|
2146
|
+
"}",
|
|
2147
|
+
].join("\n"),
|
|
2148
|
+
"src/app/layout.tsx": [
|
|
2149
|
+
'import "./globals.css";',
|
|
2150
|
+
'import type { ReactNode } from "react";',
|
|
2151
|
+
"",
|
|
2152
|
+
"export const metadata = {",
|
|
2153
|
+
' title: "Ekairos App",',
|
|
2154
|
+
' description: "Scaffolded Ekairos domain app",',
|
|
2155
|
+
"};",
|
|
2156
|
+
"",
|
|
2157
|
+
"export default function RootLayout({ children }: { children: ReactNode }) {",
|
|
2158
|
+
" return (",
|
|
2159
|
+
' <html lang="en">',
|
|
2160
|
+
" <body>{children}</body>",
|
|
2161
|
+
" </html>",
|
|
2162
|
+
" );",
|
|
2163
|
+
"}",
|
|
2164
|
+
].join("\n"),
|
|
2165
|
+
"src/app/page.tsx": [
|
|
2166
|
+
"import DomainShowcase from \"./domain-showcase\";",
|
|
2167
|
+
"",
|
|
2168
|
+
"export const dynamic = \"force-dynamic\";",
|
|
2169
|
+
"",
|
|
2170
|
+
"export default function HomePage() {",
|
|
2171
|
+
" return (",
|
|
2172
|
+
" <main>",
|
|
2173
|
+
" <section className=\"hero\">",
|
|
2174
|
+
" <div className=\"eyebrow\">Ekairos Domain Scaffold</div>",
|
|
2175
|
+
" <h1>Supply chain control tower.</h1>",
|
|
2176
|
+
" <p>",
|
|
2177
|
+
" Open an order, track stock, shipment, supplier risk, and quality status in one live view.",
|
|
2178
|
+
" </p>",
|
|
2179
|
+
" </section>",
|
|
2180
|
+
"",
|
|
2181
|
+
" <DomainShowcase />",
|
|
2182
|
+
" </main>",
|
|
2183
|
+
" );",
|
|
2184
|
+
"}",
|
|
2185
|
+
].join("\n"),
|
|
2186
|
+
"src/app/domain-showcase.tsx": [
|
|
2187
|
+
"\"use client\";",
|
|
2188
|
+
"",
|
|
2189
|
+
"import { useMemo, useState } from \"react\";",
|
|
2190
|
+
"import { init } from \"@instantdb/react\";",
|
|
2191
|
+
"",
|
|
2192
|
+
"type SupplierRow = {",
|
|
2193
|
+
" id?: string;",
|
|
2194
|
+
" name?: string;",
|
|
2195
|
+
" region?: string;",
|
|
2196
|
+
" risk?: string;",
|
|
2197
|
+
" score?: number;",
|
|
2198
|
+
"};",
|
|
2199
|
+
"",
|
|
2200
|
+
"type StockItemRow = {",
|
|
2201
|
+
" id?: string;",
|
|
2202
|
+
" sku?: string;",
|
|
2203
|
+
" warehouse?: string;",
|
|
2204
|
+
" available?: number;",
|
|
2205
|
+
" safetyStock?: number;",
|
|
2206
|
+
"};",
|
|
2207
|
+
"",
|
|
2208
|
+
"type InspectionRow = {",
|
|
2209
|
+
" id?: string;",
|
|
2210
|
+
" result?: string;",
|
|
2211
|
+
" severity?: string;",
|
|
2212
|
+
" note?: string;",
|
|
2213
|
+
"};",
|
|
2214
|
+
"",
|
|
2215
|
+
"type ShipmentRow = {",
|
|
2216
|
+
" id?: string;",
|
|
2217
|
+
" carrier?: string;",
|
|
2218
|
+
" lane?: string;",
|
|
2219
|
+
" status?: string;",
|
|
2220
|
+
" etaHours?: number;",
|
|
2221
|
+
" inspections?: InspectionRow[] | InspectionRow;",
|
|
2222
|
+
"};",
|
|
2223
|
+
"",
|
|
2224
|
+
"type OrderRow = {",
|
|
2225
|
+
" id?: string;",
|
|
2226
|
+
" reference?: string;",
|
|
2227
|
+
" status?: string;",
|
|
2228
|
+
" spend?: number;",
|
|
2229
|
+
" supplier?: SupplierRow | SupplierRow[];",
|
|
2230
|
+
" stockItems?: StockItemRow[] | StockItemRow;",
|
|
2231
|
+
" shipments?: ShipmentRow[] | ShipmentRow;",
|
|
2232
|
+
"};",
|
|
2233
|
+
"",
|
|
2234
|
+
"const db = init({",
|
|
2235
|
+
" appId: process.env.NEXT_PUBLIC_INSTANT_APP_ID || \"\",",
|
|
2236
|
+
"});",
|
|
2237
|
+
"",
|
|
2238
|
+
"function asArray<T>(value: T | T[] | null | undefined): T[] {",
|
|
2239
|
+
" if (!value) return [];",
|
|
2240
|
+
" return Array.isArray(value) ? value : [value];",
|
|
2241
|
+
"}",
|
|
2242
|
+
"",
|
|
2243
|
+
"function first<T>(value: T | T[] | null | undefined): T | null {",
|
|
2244
|
+
" return asArray(value)[0] ?? null;",
|
|
2245
|
+
"}",
|
|
2246
|
+
"",
|
|
2247
|
+
"function money(value?: number) {",
|
|
2248
|
+
" return new Intl.NumberFormat(undefined, {",
|
|
2249
|
+
" currency: \"USD\",",
|
|
2250
|
+
" maximumFractionDigits: 0,",
|
|
2251
|
+
" style: \"currency\",",
|
|
2252
|
+
" }).format(value ?? 0);",
|
|
2253
|
+
"}",
|
|
2254
|
+
"",
|
|
2255
|
+
"async function runAction(action: string, input: Record<string, unknown>) {",
|
|
2256
|
+
" const response = await fetch(\"/api/ekairos/domain\", {",
|
|
2257
|
+
" method: \"POST\",",
|
|
2258
|
+
" headers: { \"content-type\": \"application/json\" },",
|
|
2259
|
+
" body: JSON.stringify({ op: \"action\", action, input }),",
|
|
2260
|
+
" });",
|
|
2261
|
+
" const text = await response.text();",
|
|
2262
|
+
" if (!response.ok) throw new Error(text || `request_failed:${response.status}`);",
|
|
2263
|
+
" return text ? JSON.parse(text) : null;",
|
|
2264
|
+
"}",
|
|
2265
|
+
"",
|
|
2266
|
+
"export default function DomainShowcase() {",
|
|
2267
|
+
" const [reference, setReference] = useState(\"PO-7842\");",
|
|
2268
|
+
" const [supplierName, setSupplierName] = useState(\"Marula Components\");",
|
|
2269
|
+
" const [sku, setSku] = useState(\"DRV-2048\");",
|
|
2270
|
+
" const [loadingAction, setLoadingAction] = useState<string | null>(null);",
|
|
2271
|
+
" const [actionError, setActionError] = useState<string | null>(null);",
|
|
2272
|
+
"",
|
|
2273
|
+
" const query = db.useQuery({",
|
|
2274
|
+
" procurement_order: {",
|
|
2275
|
+
" $: { order: { createdAt: \"desc\" }, limit: 8 },",
|
|
2276
|
+
" supplier: {},",
|
|
2277
|
+
" stockItems: {},",
|
|
2278
|
+
" shipments: {",
|
|
2279
|
+
" inspections: {},",
|
|
2280
|
+
" },",
|
|
2281
|
+
" },",
|
|
2282
|
+
" }) as {",
|
|
2283
|
+
" data?: { procurement_order?: OrderRow[] };",
|
|
2284
|
+
" error?: unknown;",
|
|
2285
|
+
" isLoading: boolean;",
|
|
2286
|
+
" };",
|
|
2287
|
+
"",
|
|
2288
|
+
" const orders = query.data?.procurement_order ?? [];",
|
|
2289
|
+
" const activeOrder = orders[0] ?? null;",
|
|
2290
|
+
" const supplier = first(activeOrder?.supplier);",
|
|
2291
|
+
" const stockItems = asArray(activeOrder?.stockItems);",
|
|
2292
|
+
" const shipments = asArray(activeOrder?.shipments);",
|
|
2293
|
+
" const shipment = shipments[0] ?? null;",
|
|
2294
|
+
" const inspections = shipments.flatMap((entry) => asArray(entry.inspections));",
|
|
2295
|
+
" const inspection = inspections[0] ?? null;",
|
|
2296
|
+
"",
|
|
2297
|
+
" const metrics = useMemo(() => ({",
|
|
2298
|
+
" orders: orders.length,",
|
|
2299
|
+
" stock: stockItems.reduce((total, item) => total + (item.available ?? 0), 0),",
|
|
2300
|
+
" eta: shipment?.etaHours ?? 0,",
|
|
2301
|
+
" risk: supplier?.risk ?? \"none\",",
|
|
2302
|
+
" }), [orders.length, shipment?.etaHours, stockItems, supplier?.risk]);",
|
|
2303
|
+
"",
|
|
2304
|
+
" async function submitAction(action: string, input: Record<string, unknown>) {",
|
|
2305
|
+
" setLoadingAction(action);",
|
|
2306
|
+
" setActionError(null);",
|
|
2307
|
+
" try {",
|
|
2308
|
+
" await runAction(action, input);",
|
|
2309
|
+
" } catch (error) {",
|
|
2310
|
+
" setActionError(error instanceof Error ? error.message : String(error));",
|
|
2311
|
+
" } finally {",
|
|
2312
|
+
" setLoadingAction(null);",
|
|
2313
|
+
" }",
|
|
2314
|
+
" }",
|
|
2315
|
+
"",
|
|
2316
|
+
" return (",
|
|
2317
|
+
" <section className=\"shell\">",
|
|
2318
|
+
" <div className=\"metrics-strip\">",
|
|
2319
|
+
" <div><span>open orders</span><strong>{metrics.orders}</strong></div>",
|
|
2320
|
+
" <div><span>available stock</span><strong>{metrics.stock}</strong></div>",
|
|
2321
|
+
" <div><span>shipment eta</span><strong>{metrics.eta}h</strong></div>",
|
|
2322
|
+
" <div><span>supplier risk</span><strong>{metrics.risk}</strong></div>",
|
|
2323
|
+
" </div>",
|
|
2324
|
+
"",
|
|
2325
|
+
" <div className=\"workbench\">",
|
|
2326
|
+
" <section className=\"command-panel\">",
|
|
2327
|
+
" <span className=\"eyebrow\">Release</span>",
|
|
2328
|
+
" <h2>Open a purchase order</h2>",
|
|
2329
|
+
" <label className=\"field\">",
|
|
2330
|
+
" <span>PO reference</span>",
|
|
2331
|
+
" <input className=\"input\" value={reference} onChange={(event) => setReference(event.target.value)} />",
|
|
2332
|
+
" </label>",
|
|
2333
|
+
" <label className=\"field\">",
|
|
2334
|
+
" <span>Supplier</span>",
|
|
2335
|
+
" <input className=\"input\" value={supplierName} onChange={(event) => setSupplierName(event.target.value)} />",
|
|
2336
|
+
" </label>",
|
|
2337
|
+
" <label className=\"field\">",
|
|
2338
|
+
" <span>SKU</span>",
|
|
2339
|
+
" <input className=\"input\" value={sku} onChange={(event) => setSku(event.target.value)} />",
|
|
2340
|
+
" </label>",
|
|
2341
|
+
" <div className=\"button-row\">",
|
|
2342
|
+
" <button",
|
|
2343
|
+
" className=\"button\"",
|
|
2344
|
+
" disabled={loadingAction !== null}",
|
|
2345
|
+
" onClick={() => void submitAction(\"supplyChain.order.launch\", { reference, sku, supplierName })}",
|
|
2346
|
+
" >",
|
|
2347
|
+
" {loadingAction === \"supplyChain.order.launch\" ? \"Opening\" : \"Open order\"}",
|
|
2348
|
+
" </button>",
|
|
2349
|
+
" <button",
|
|
2350
|
+
" className=\"button ghost\"",
|
|
2351
|
+
" disabled={loadingAction !== null || !shipment?.id}",
|
|
2352
|
+
" onClick={() => void submitAction(\"supplyChain.shipment.expedite\", { shipmentId: shipment?.id })}",
|
|
2353
|
+
" >",
|
|
2354
|
+
" Expedite shipment",
|
|
2355
|
+
" </button>",
|
|
2356
|
+
" </div>",
|
|
2357
|
+
" </section>",
|
|
2358
|
+
"",
|
|
2359
|
+
" <section className=\"context-rail\" aria-label=\"Operational path\">",
|
|
2360
|
+
" <div className=\"panel-head\">",
|
|
2361
|
+
" <div>",
|
|
2362
|
+
" <span className=\"eyebrow\">Path</span>",
|
|
2363
|
+
" <h2>What gets linked</h2>",
|
|
2364
|
+
" </div>",
|
|
2365
|
+
" </div>",
|
|
2366
|
+
" <div className=\"context-list\">",
|
|
2367
|
+
" <div className=\"context-row\"><span>Supplier</span><strong>Risk and commercial owner</strong></div>",
|
|
2368
|
+
" <div className=\"context-row\"><span>Order</span><strong>Spend and release state</strong></div>",
|
|
2369
|
+
" <div className=\"context-row\"><span>Inventory</span><strong>SKU and stock position</strong></div>",
|
|
2370
|
+
" <div className=\"context-row\"><span>Transport</span><strong>Carrier lane and ETA</strong></div>",
|
|
2371
|
+
" <div className=\"context-row\"><span>Quality</span><strong>Arrival inspection</strong></div>",
|
|
2372
|
+
" </div>",
|
|
2373
|
+
" </section>",
|
|
2374
|
+
"",
|
|
2375
|
+
" <section className=\"graph-panel\">",
|
|
2376
|
+
" <div className=\"panel-head\">",
|
|
2377
|
+
" <div>",
|
|
2378
|
+
" <span className=\"eyebrow\">Live order</span>",
|
|
2379
|
+
" <h2>{activeOrder?.reference ?? \"No order active\"}</h2>",
|
|
2380
|
+
" </div>",
|
|
2381
|
+
" <span className=\"status-pill\">{query.isLoading ? \"loading\" : activeOrder?.status ?? \"idle\"}</span>",
|
|
2382
|
+
" </div>",
|
|
2383
|
+
"",
|
|
2384
|
+
" {query.isLoading ? (",
|
|
2385
|
+
" <div className=\"skeleton-stack\" aria-label=\"Loading order\">",
|
|
2386
|
+
" <span />",
|
|
2387
|
+
" <span />",
|
|
2388
|
+
" <span />",
|
|
2389
|
+
" </div>",
|
|
2390
|
+
" ) : query.error ? (",
|
|
2391
|
+
" <div className=\"error-banner\">{String(query.error)}</div>",
|
|
2392
|
+
" ) : !activeOrder ? (",
|
|
2393
|
+
" <div className=\"empty-state\">Open an order to create the first control tower view.</div>",
|
|
2394
|
+
" ) : (",
|
|
2395
|
+
" <>",
|
|
2396
|
+
" <div className=\"raid-summary\">",
|
|
2397
|
+
" <div>",
|
|
2398
|
+
" <span>supplier</span>",
|
|
2399
|
+
" <strong>{supplier?.name ?? \"unassigned\"}</strong>",
|
|
2400
|
+
" </div>",
|
|
2401
|
+
" <div>",
|
|
2402
|
+
" <span>region</span>",
|
|
2403
|
+
" <strong>{supplier?.region ?? \"unknown\"}</strong>",
|
|
2404
|
+
" </div>",
|
|
2405
|
+
" <div>",
|
|
2406
|
+
" <span>spend</span>",
|
|
2407
|
+
" <strong>{money(activeOrder.spend)}</strong>",
|
|
2408
|
+
" </div>",
|
|
2409
|
+
" </div>",
|
|
2410
|
+
"",
|
|
2411
|
+
" <div className=\"lane-grid\">",
|
|
2412
|
+
" <div className=\"lane\">",
|
|
2413
|
+
" <span className=\"manifest-label\">Inventory</span>",
|
|
2414
|
+
" {stockItems.map((item) => (",
|
|
2415
|
+
" <article className=\"objective\" key={item.id}>",
|
|
2416
|
+
" <strong>{item.sku}</strong>",
|
|
2417
|
+
" <span>{item.warehouse}</span>",
|
|
2418
|
+
" <em>{item.available} units / {item.safetyStock} safety</em>",
|
|
2419
|
+
" </article>",
|
|
2420
|
+
" ))}",
|
|
2421
|
+
" </div>",
|
|
2422
|
+
" <div className=\"lane\">",
|
|
2423
|
+
" <span className=\"manifest-label\">Transport</span>",
|
|
2424
|
+
" {shipments.map((entry) => (",
|
|
2425
|
+
" <article className=\"objective\" key={entry.id}>",
|
|
2426
|
+
" <strong>{entry.carrier}</strong>",
|
|
2427
|
+
" <span>{entry.lane}</span>",
|
|
2428
|
+
" <em>{entry.status} / {entry.etaHours}h ETA</em>",
|
|
2429
|
+
" </article>",
|
|
2430
|
+
" ))}",
|
|
2431
|
+
" </div>",
|
|
2432
|
+
" <div className=\"lane\">",
|
|
2433
|
+
" <span className=\"manifest-label\">Quality</span>",
|
|
2434
|
+
" {inspections.map((entry) => (",
|
|
2435
|
+
" <article className=\"objective\" key={entry.id}>",
|
|
2436
|
+
" <strong>{entry.result}</strong>",
|
|
2437
|
+
" <span>{entry.severity}</span>",
|
|
2438
|
+
" <em>{entry.note}</em>",
|
|
2439
|
+
" </article>",
|
|
2440
|
+
" ))}",
|
|
2441
|
+
" </div>",
|
|
2442
|
+
" </div>",
|
|
2443
|
+
" </>",
|
|
2444
|
+
" )}",
|
|
2445
|
+
" </section>",
|
|
2446
|
+
" </div>",
|
|
2447
|
+
"",
|
|
2448
|
+
" {actionError ? <div className=\"error-banner\">{actionError}</div> : null}",
|
|
2449
|
+
" </section>",
|
|
2450
|
+
" );",
|
|
2451
|
+
"}",
|
|
2452
|
+
].join("\n"),
|
|
2453
|
+
"src/domain.ts": [
|
|
2454
|
+
"import { defineAction, domain } from \"@ekairos/domain\";",
|
|
2455
|
+
"import { i } from \"@instantdb/core\";",
|
|
2456
|
+
"import { z } from \"zod\";",
|
|
2457
|
+
"",
|
|
2458
|
+
"export const supplierNetworkDomain = domain(\"supplierNetwork\").withSchema({",
|
|
2459
|
+
" entities: {",
|
|
2460
|
+
" supplierNetwork_supplier: i.entity({",
|
|
2461
|
+
" name: i.string().indexed(),",
|
|
2462
|
+
" region: i.string().indexed(),",
|
|
2463
|
+
" risk: i.string().indexed(),",
|
|
2464
|
+
" score: i.number().indexed(),",
|
|
2465
|
+
" createdAt: i.number().indexed(),",
|
|
2466
|
+
" }),",
|
|
2467
|
+
" },",
|
|
2468
|
+
" links: {},",
|
|
2469
|
+
" rooms: {},",
|
|
2470
|
+
"});",
|
|
2471
|
+
"",
|
|
2472
|
+
"export const procurementDomain = domain(\"procurement\")",
|
|
2473
|
+
" .includes(supplierNetworkDomain)",
|
|
2474
|
+
" .withSchema({",
|
|
2475
|
+
" entities: {",
|
|
2476
|
+
" procurement_order: i.entity({",
|
|
2477
|
+
" reference: i.string().indexed(),",
|
|
2478
|
+
" status: i.string().indexed(),",
|
|
2479
|
+
" spend: i.number().indexed(),",
|
|
2480
|
+
" createdAt: i.number().indexed(),",
|
|
2481
|
+
" }),",
|
|
2482
|
+
" },",
|
|
2483
|
+
" links: {",
|
|
2484
|
+
" procurement_orderSupplier: {",
|
|
2485
|
+
" forward: { on: \"procurement_order\", has: \"one\", label: \"supplier\" },",
|
|
2486
|
+
" reverse: { on: \"supplierNetwork_supplier\", has: \"many\", label: \"orders\" },",
|
|
2487
|
+
" },",
|
|
2488
|
+
" },",
|
|
2489
|
+
" rooms: {},",
|
|
2490
|
+
" });",
|
|
2491
|
+
"",
|
|
2492
|
+
"export const inventoryDomain = domain(\"inventory\")",
|
|
2493
|
+
" .includes(procurementDomain)",
|
|
2494
|
+
" .withSchema({",
|
|
2495
|
+
" entities: {",
|
|
2496
|
+
" inventory_stockItem: i.entity({",
|
|
2497
|
+
" sku: i.string().indexed(),",
|
|
2498
|
+
" warehouse: i.string().indexed(),",
|
|
2499
|
+
" available: i.number().indexed(),",
|
|
2500
|
+
" safetyStock: i.number().indexed(),",
|
|
2501
|
+
" createdAt: i.number().indexed(),",
|
|
2502
|
+
" }),",
|
|
2503
|
+
" },",
|
|
2504
|
+
" links: {",
|
|
2505
|
+
" inventory_stockItemOrder: {",
|
|
2506
|
+
" forward: { on: \"inventory_stockItem\", has: \"one\", label: \"order\" },",
|
|
2507
|
+
" reverse: { on: \"procurement_order\", has: \"many\", label: \"stockItems\" },",
|
|
2508
|
+
" },",
|
|
2509
|
+
" },",
|
|
2510
|
+
" rooms: {},",
|
|
2511
|
+
" });",
|
|
2512
|
+
"",
|
|
2513
|
+
"export const transportationDomain = domain(\"transportation\")",
|
|
2514
|
+
" .includes(procurementDomain)",
|
|
2515
|
+
" .withSchema({",
|
|
2516
|
+
" entities: {",
|
|
2517
|
+
" transportation_shipment: i.entity({",
|
|
2518
|
+
" carrier: i.string().indexed(),",
|
|
2519
|
+
" lane: i.string().indexed(),",
|
|
2520
|
+
" status: i.string().indexed(),",
|
|
2521
|
+
" etaHours: i.number().indexed(),",
|
|
2522
|
+
" createdAt: i.number().indexed(),",
|
|
2523
|
+
" }),",
|
|
2524
|
+
" },",
|
|
2525
|
+
" links: {",
|
|
2526
|
+
" transportation_shipmentOrder: {",
|
|
2527
|
+
" forward: { on: \"transportation_shipment\", has: \"one\", label: \"order\" },",
|
|
2528
|
+
" reverse: { on: \"procurement_order\", has: \"many\", label: \"shipments\" },",
|
|
2529
|
+
" },",
|
|
2530
|
+
" },",
|
|
2531
|
+
" rooms: {},",
|
|
2532
|
+
" });",
|
|
2533
|
+
"",
|
|
2534
|
+
"export const qualityControlDomain = domain(\"qualityControl\")",
|
|
2535
|
+
" .includes(transportationDomain)",
|
|
2536
|
+
" .withSchema({",
|
|
2537
|
+
" entities: {",
|
|
2538
|
+
" qualityControl_inspection: i.entity({",
|
|
2539
|
+
" result: i.string().indexed(),",
|
|
2540
|
+
" severity: i.string().indexed(),",
|
|
2541
|
+
" note: i.string(),",
|
|
2542
|
+
" createdAt: i.number().indexed(),",
|
|
2543
|
+
" }),",
|
|
2544
|
+
" },",
|
|
2545
|
+
" links: {",
|
|
2546
|
+
" qualityControl_inspectionShipment: {",
|
|
2547
|
+
" forward: { on: \"qualityControl_inspection\", has: \"one\", label: \"shipment\" },",
|
|
2548
|
+
" reverse: { on: \"transportation_shipment\", has: \"many\", label: \"inspections\" },",
|
|
2549
|
+
" },",
|
|
2550
|
+
" },",
|
|
2551
|
+
" rooms: {},",
|
|
2552
|
+
" });",
|
|
2553
|
+
"",
|
|
2554
|
+
"const baseDomain = domain(\"supplyChain\")",
|
|
2555
|
+
" .includes(inventoryDomain)",
|
|
2556
|
+
" .includes(qualityControlDomain)",
|
|
2557
|
+
" .withSchema({ entities: {}, links: {}, rooms: {} });",
|
|
2558
|
+
"",
|
|
2559
|
+
"export const launchOrderAction = defineAction({",
|
|
2560
|
+
" name: \"supplyChain.order.launch\",",
|
|
2561
|
+
" input: z.object({",
|
|
2562
|
+
" reference: z.string().optional(),",
|
|
2563
|
+
" supplierName: z.string().optional(),",
|
|
2564
|
+
" sku: z.string().optional(),",
|
|
2565
|
+
" }),",
|
|
2566
|
+
" output: z.object({",
|
|
2567
|
+
" supplierId: z.string(),",
|
|
2568
|
+
" orderId: z.string(),",
|
|
2569
|
+
" stockItemId: z.string(),",
|
|
2570
|
+
" shipmentId: z.string(),",
|
|
2571
|
+
" inspectionId: z.string(),",
|
|
2572
|
+
" }),",
|
|
2573
|
+
" async execute({ runtime, input }): Promise<{",
|
|
2574
|
+
" supplierId: string;",
|
|
2575
|
+
" orderId: string;",
|
|
2576
|
+
" stockItemId: string;",
|
|
2577
|
+
" shipmentId: string;",
|
|
2578
|
+
" inspectionId: string;",
|
|
2579
|
+
" }> {",
|
|
2580
|
+
" const now = Date.now();",
|
|
2581
|
+
" const supplierId = globalThis.crypto.randomUUID();",
|
|
2582
|
+
" const orderId = globalThis.crypto.randomUUID();",
|
|
2583
|
+
" const stockItemId = globalThis.crypto.randomUUID();",
|
|
2584
|
+
" const shipmentId = globalThis.crypto.randomUUID();",
|
|
2585
|
+
" const inspectionId = globalThis.crypto.randomUUID();",
|
|
2586
|
+
"",
|
|
2587
|
+
" await runtime.db.transact([",
|
|
2588
|
+
" runtime.db.tx.supplierNetwork_supplier[supplierId].update({",
|
|
2589
|
+
" name: String(input?.supplierName ?? \"\").trim() || \"Marula Components\",",
|
|
2590
|
+
" region: \"Pacific North\",",
|
|
2591
|
+
" risk: \"watch\",",
|
|
2592
|
+
" score: 82,",
|
|
2593
|
+
" createdAt: now,",
|
|
2594
|
+
" }),",
|
|
2595
|
+
" runtime.db.tx.procurement_order[orderId].update({",
|
|
2596
|
+
" reference: String(input?.reference ?? \"\").trim() || \"PO-7842\",",
|
|
2597
|
+
" status: \"released\",",
|
|
2598
|
+
" spend: 184700,",
|
|
2599
|
+
" createdAt: now + 1,",
|
|
2600
|
+
" }),",
|
|
2601
|
+
" runtime.db.tx.procurement_order[orderId].link({ supplier: supplierId }),",
|
|
2602
|
+
" runtime.db.tx.inventory_stockItem[stockItemId].update({",
|
|
2603
|
+
" sku: String(input?.sku ?? \"\").trim() || \"DRV-2048\",",
|
|
2604
|
+
" warehouse: \"Reno DC\",",
|
|
2605
|
+
" available: 320,",
|
|
2606
|
+
" safetyStock: 140,",
|
|
2607
|
+
" createdAt: now + 2,",
|
|
2608
|
+
" }),",
|
|
2609
|
+
" runtime.db.tx.inventory_stockItem[stockItemId].link({ order: orderId }),",
|
|
2610
|
+
" runtime.db.tx.transportation_shipment[shipmentId].update({",
|
|
2611
|
+
" carrier: \"Northstar Freight\",",
|
|
2612
|
+
" lane: \"Reno -> Austin\",",
|
|
2613
|
+
" status: \"in-transit\",",
|
|
2614
|
+
" etaHours: 38,",
|
|
2615
|
+
" createdAt: now + 3,",
|
|
2616
|
+
" }),",
|
|
2617
|
+
" runtime.db.tx.transportation_shipment[shipmentId].link({ order: orderId }),",
|
|
2618
|
+
" runtime.db.tx.qualityControl_inspection[inspectionId].update({",
|
|
2619
|
+
" result: \"pending\",",
|
|
2620
|
+
" severity: \"medium\",",
|
|
2621
|
+
" note: \"Inspect seal integrity on arrival.\",",
|
|
2622
|
+
" createdAt: now + 4,",
|
|
2623
|
+
" }),",
|
|
2624
|
+
" runtime.db.tx.qualityControl_inspection[inspectionId].link({ shipment: shipmentId }),",
|
|
2625
|
+
" ]);",
|
|
2626
|
+
"",
|
|
2627
|
+
" return { supplierId, orderId, stockItemId, shipmentId, inspectionId };",
|
|
2628
|
+
" },",
|
|
2629
|
+
"});",
|
|
2630
|
+
"",
|
|
2631
|
+
"export const expediteShipmentAction = defineAction({",
|
|
2632
|
+
" name: \"supplyChain.shipment.expedite\",",
|
|
2633
|
+
" input: z.object({ shipmentId: z.string().optional() }),",
|
|
2634
|
+
" output: z.object({ shipmentId: z.string() }),",
|
|
2635
|
+
" async execute({ runtime, input }): Promise<{ shipmentId: string }> {",
|
|
2636
|
+
" const shipmentId = String(input?.shipmentId ?? \"\").trim();",
|
|
2637
|
+
" if (!shipmentId) throw new Error(\"shipmentId is required\");",
|
|
2638
|
+
"",
|
|
2639
|
+
" await runtime.db.transact([",
|
|
2640
|
+
" runtime.db.tx.transportation_shipment[shipmentId].update({",
|
|
2641
|
+
" status: \"expedited\",",
|
|
2642
|
+
" etaHours: 16,",
|
|
2643
|
+
" }),",
|
|
2644
|
+
" ]);",
|
|
2645
|
+
"",
|
|
2646
|
+
" return { shipmentId };",
|
|
2647
|
+
" },",
|
|
2648
|
+
"});",
|
|
2649
|
+
"",
|
|
2650
|
+
"export const appDomain = baseDomain.withActions({",
|
|
2651
|
+
" expediteShipment: expediteShipmentAction,",
|
|
2652
|
+
" launchOrder: launchOrderAction,",
|
|
2653
|
+
"});",
|
|
2654
|
+
"",
|
|
2655
|
+
"export default appDomain;",
|
|
2656
|
+
].join("\n"),
|
|
2657
|
+
"src/runtime.ts": [
|
|
2658
|
+
'import { init } from "@instantdb/admin";',
|
|
2659
|
+
'import { EkairosRuntime } from "@ekairos/domain/runtime-handle";',
|
|
2660
|
+
'import { configureRuntime } from "@ekairos/domain/runtime";',
|
|
2661
|
+
'import appDomain from "./domain";',
|
|
2662
|
+
"",
|
|
2663
|
+
"export type AppRuntimeEnv = {",
|
|
2664
|
+
" actorEmail?: string | null;",
|
|
2665
|
+
" actorId?: string;",
|
|
2666
|
+
" adminToken?: string;",
|
|
2667
|
+
" appId?: string;",
|
|
2668
|
+
"};",
|
|
2669
|
+
"",
|
|
2670
|
+
"function resolveRuntimeEnv(env: AppRuntimeEnv = {}): Required<Pick<AppRuntimeEnv, \"appId\" | \"adminToken\">> & AppRuntimeEnv {",
|
|
2671
|
+
' const appId = String(env.appId ?? process.env.NEXT_PUBLIC_INSTANT_APP_ID ?? "").trim();',
|
|
2672
|
+
' const adminToken = String(env.adminToken ?? process.env.INSTANT_ADMIN_TOKEN ?? "").trim();',
|
|
2673
|
+
" if (!appId || !adminToken) {",
|
|
2674
|
+
' throw new Error("Missing NEXT_PUBLIC_INSTANT_APP_ID or INSTANT_ADMIN_TOKEN. Copy .env.example to .env.local and fill both values.");',
|
|
2675
|
+
" }",
|
|
2676
|
+
" return {",
|
|
2677
|
+
" ...env,",
|
|
2678
|
+
" appId,",
|
|
2679
|
+
" adminToken,",
|
|
2680
|
+
" };",
|
|
2681
|
+
"}",
|
|
2682
|
+
"",
|
|
2683
|
+
"export class AppRuntime extends EkairosRuntime<AppRuntimeEnv, typeof appDomain, any> {",
|
|
2684
|
+
" protected getDomain() {",
|
|
2685
|
+
" return appDomain;",
|
|
2686
|
+
" }",
|
|
2687
|
+
"",
|
|
2688
|
+
" protected async resolveDb(env: AppRuntimeEnv) {",
|
|
2689
|
+
" const resolved = resolveRuntimeEnv(env);",
|
|
2690
|
+
" return init({",
|
|
2691
|
+
" appId: resolved.appId,",
|
|
2692
|
+
" adminToken: resolved.adminToken,",
|
|
2693
|
+
" schema: appDomain.toInstantSchema(),",
|
|
2694
|
+
" useDateObjects: true,",
|
|
2695
|
+
" } as any) as any;",
|
|
2696
|
+
" }",
|
|
2697
|
+
"}",
|
|
2698
|
+
"",
|
|
2699
|
+
"export function createRuntime(env: AppRuntimeEnv = {}) {",
|
|
2700
|
+
" return new AppRuntime(resolveRuntimeEnv(env));",
|
|
2701
|
+
"}",
|
|
2702
|
+
"",
|
|
2703
|
+
"export const runtimeConfig = configureRuntime<AppRuntimeEnv>({",
|
|
2704
|
+
" runtime: async (env) => {",
|
|
2705
|
+
" const runtime = createRuntime(env);",
|
|
2706
|
+
" return { db: await runtime.db() };",
|
|
2707
|
+
" },",
|
|
2708
|
+
" domain: {",
|
|
2709
|
+
" domain: appDomain,",
|
|
2710
|
+
" },",
|
|
2711
|
+
"});",
|
|
2712
|
+
].join("\n"),
|
|
2713
|
+
"src/workflows/demo.workflow.ts": [
|
|
2714
|
+
"import type { ActiveDomain } from \"@ekairos/domain\";",
|
|
2715
|
+
"import appDomain from \"../domain\";",
|
|
2716
|
+
"import { createRuntime } from \"../runtime\";",
|
|
2717
|
+
"",
|
|
2718
|
+
"export type DemoWorkflowInput = {",
|
|
2719
|
+
" expedite?: boolean;",
|
|
2720
|
+
" reference?: string;",
|
|
2721
|
+
" sku?: string;",
|
|
2722
|
+
" supplierName?: string;",
|
|
2723
|
+
"};",
|
|
2724
|
+
"",
|
|
2725
|
+
"export async function runDemoWorkflow(input: DemoWorkflowInput) {",
|
|
2726
|
+
" \"use workflow\";",
|
|
2727
|
+
" const runtime = createRuntime();",
|
|
2728
|
+
" const scoped = (await runtime.use(appDomain)) as ActiveDomain<typeof appDomain>;",
|
|
2729
|
+
" const created = await scoped.actions.launchOrder({",
|
|
2730
|
+
" reference: input.reference,",
|
|
2731
|
+
" sku: input.sku,",
|
|
2732
|
+
" supplierName: input.supplierName,",
|
|
2733
|
+
" });",
|
|
2734
|
+
"",
|
|
2735
|
+
" if (input.expedite) {",
|
|
2736
|
+
" await scoped.actions.expediteShipment({",
|
|
2737
|
+
" shipmentId: created.shipmentId,",
|
|
2738
|
+
" });",
|
|
2739
|
+
" }",
|
|
2740
|
+
"",
|
|
2741
|
+
" return created;",
|
|
2742
|
+
"}",
|
|
2743
|
+
].join("\n"),
|
|
2744
|
+
};
|
|
2745
|
+
}
|
|
2746
|
+
export async function createDomainApp(params) {
|
|
2747
|
+
if (params.framework !== "next") {
|
|
2748
|
+
throw new Error("Only --next is supported right now.");
|
|
2749
|
+
}
|
|
2750
|
+
const template = resolveCreateAppTemplate(params);
|
|
2751
|
+
if (params.smoke && !params.install) {
|
|
2752
|
+
throw new Error("--smoke requires dependencies. Remove --no-install or run smoke after installing.");
|
|
2753
|
+
}
|
|
2754
|
+
if (params.demo && !params.smoke) {
|
|
2755
|
+
throw new Error("--demo runs the full app cycle and requires smoke validation.");
|
|
2756
|
+
}
|
|
2757
|
+
const targetDir = resolve(params.directory || ".");
|
|
2758
|
+
await emitProgress(params.onProgress, {
|
|
2759
|
+
stage: "prepare-target",
|
|
2760
|
+
status: "running",
|
|
2761
|
+
message: `Preparing ${targetDir}`,
|
|
2762
|
+
progress: 5,
|
|
2763
|
+
});
|
|
2764
|
+
await ensureWritableTargetDirectory(targetDir, params.force);
|
|
2765
|
+
await emitProgress(params.onProgress, {
|
|
2766
|
+
stage: "prepare-target",
|
|
2767
|
+
status: "completed",
|
|
2768
|
+
message: "Target ready",
|
|
2769
|
+
progress: 12,
|
|
2770
|
+
});
|
|
2771
|
+
await emitProgress(params.onProgress, {
|
|
2772
|
+
stage: "detect-package-manager",
|
|
2773
|
+
status: "running",
|
|
2774
|
+
message: "Detecting package manager",
|
|
2775
|
+
progress: 16,
|
|
2776
|
+
});
|
|
2777
|
+
const packageManager = await detectPackageManager(params.packageManager, params.workspacePath);
|
|
2778
|
+
await emitProgress(params.onProgress, {
|
|
2779
|
+
stage: "detect-package-manager",
|
|
2780
|
+
status: "completed",
|
|
2781
|
+
message: `Using ${packageManager}`,
|
|
2782
|
+
progress: 22,
|
|
2783
|
+
});
|
|
2784
|
+
await emitProgress(params.onProgress, {
|
|
2785
|
+
stage: "resolve-version",
|
|
2786
|
+
status: "running",
|
|
2787
|
+
message: "Resolving @ekairos/domain version",
|
|
2788
|
+
progress: 26,
|
|
2789
|
+
});
|
|
2790
|
+
const domainVersion = await readDomainPackageVersion();
|
|
2791
|
+
await emitProgress(params.onProgress, {
|
|
2792
|
+
stage: "resolve-version",
|
|
2793
|
+
status: "completed",
|
|
2794
|
+
message: `Version ${domainVersion}`,
|
|
2795
|
+
progress: 30,
|
|
2796
|
+
});
|
|
2797
|
+
const explicitAppId = trimOrEmpty(params.appId);
|
|
2798
|
+
const explicitAdminToken = trimOrEmpty(params.adminToken);
|
|
2799
|
+
const shouldProvision = Boolean(trimOrEmpty(params.instantToken)) &&
|
|
2800
|
+
(!explicitAppId || !explicitAdminToken);
|
|
2801
|
+
let provisioned = null;
|
|
2802
|
+
if (shouldProvision) {
|
|
2803
|
+
await emitProgress(params.onProgress, {
|
|
2804
|
+
stage: "provision-instant",
|
|
2805
|
+
status: "running",
|
|
2806
|
+
message: "Provisioning Instant app",
|
|
2807
|
+
progress: 38,
|
|
2808
|
+
});
|
|
2809
|
+
provisioned = await provisionInstantApp({
|
|
2810
|
+
directory: targetDir,
|
|
2811
|
+
template,
|
|
2812
|
+
instantToken: trimOrEmpty(params.instantToken),
|
|
2813
|
+
orgId: params.orgId,
|
|
2814
|
+
});
|
|
2815
|
+
await emitProgress(params.onProgress, {
|
|
2816
|
+
stage: "provision-instant",
|
|
2817
|
+
status: "completed",
|
|
2818
|
+
message: `Provisioned ${provisioned.appId}`,
|
|
2819
|
+
progress: 50,
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
const appId = explicitAppId || provisioned?.appId || null;
|
|
2823
|
+
const adminToken = explicitAdminToken || provisioned?.adminToken || null;
|
|
2824
|
+
if (params.smoke && (!appId || !adminToken)) {
|
|
2825
|
+
throw new Error(params.demo
|
|
2826
|
+
? "--demo requires Instant provisioning. Set INSTANT_PERSONAL_ACCESS_TOKEN or pass --instantToken."
|
|
2827
|
+
: "--smoke requires a configured Instant app. Pass --instantToken or --appId with --adminToken.");
|
|
2828
|
+
}
|
|
2829
|
+
await emitProgress(params.onProgress, {
|
|
2830
|
+
stage: "write-files",
|
|
2831
|
+
status: "running",
|
|
2832
|
+
message: "Writing scaffold files",
|
|
2833
|
+
progress: 58,
|
|
2834
|
+
});
|
|
2835
|
+
const files = buildNextTemplateFiles({
|
|
2836
|
+
targetDir,
|
|
2837
|
+
template,
|
|
2838
|
+
domainVersion,
|
|
2839
|
+
packageManager,
|
|
2840
|
+
workspacePath: params.workspacePath,
|
|
2841
|
+
});
|
|
2842
|
+
await writeScaffoldFiles(targetDir, files);
|
|
2843
|
+
await emitProgress(params.onProgress, {
|
|
2844
|
+
stage: "write-files",
|
|
2845
|
+
status: "completed",
|
|
2846
|
+
message: "Scaffold files written",
|
|
2847
|
+
progress: 72,
|
|
2848
|
+
});
|
|
2849
|
+
const envFile = appId && adminToken ? join(targetDir, ".env.local") : null;
|
|
2850
|
+
if (appId && adminToken && envFile) {
|
|
2851
|
+
await emitProgress(params.onProgress, {
|
|
2852
|
+
stage: "write-env",
|
|
2853
|
+
status: "running",
|
|
2854
|
+
message: "Writing .env.local",
|
|
2855
|
+
progress: 78,
|
|
2856
|
+
});
|
|
2857
|
+
await writeFile(envFile, [
|
|
2858
|
+
`NEXT_PUBLIC_INSTANT_APP_ID=${appId}`,
|
|
2859
|
+
`INSTANT_ADMIN_TOKEN=${adminToken}`,
|
|
2860
|
+
"",
|
|
2861
|
+
].join("\n"), "utf8");
|
|
2862
|
+
await emitProgress(params.onProgress, {
|
|
2863
|
+
stage: "write-env",
|
|
2864
|
+
status: "completed",
|
|
2865
|
+
message: ".env.local ready",
|
|
2866
|
+
progress: 84,
|
|
2867
|
+
});
|
|
2868
|
+
}
|
|
2869
|
+
if (params.install) {
|
|
2870
|
+
await emitProgress(params.onProgress, {
|
|
2871
|
+
stage: "install",
|
|
2872
|
+
status: "running",
|
|
2873
|
+
message: `Installing dependencies with ${packageManager}`,
|
|
2874
|
+
progress: 88,
|
|
2875
|
+
});
|
|
2876
|
+
await runInstall(targetDir, packageManager, params.onProgress);
|
|
2877
|
+
await emitProgress(params.onProgress, {
|
|
2878
|
+
stage: "install",
|
|
2879
|
+
status: "completed",
|
|
2880
|
+
message: "Dependencies installed",
|
|
2881
|
+
progress: 96,
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
const smoke = params.smoke
|
|
2885
|
+
? await runSmoke({
|
|
2886
|
+
demo: template === "supply-chain",
|
|
2887
|
+
targetDir,
|
|
2888
|
+
packageManager,
|
|
2889
|
+
keepServer: Boolean(params.keepServer),
|
|
2890
|
+
onKeepServer: params.onKeepServer,
|
|
2891
|
+
onProgress: params.onProgress,
|
|
2892
|
+
})
|
|
2893
|
+
: null;
|
|
2894
|
+
const cliCommand = packageBinCommandFor(packageManager, "domain");
|
|
2895
|
+
const reviewUrl = smoke?.baseUrl ?? "http://localhost:3000";
|
|
2896
|
+
const startStep = smoke?.keepServer
|
|
2897
|
+
? `Open ${reviewUrl} for review`
|
|
2898
|
+
: params.install
|
|
2899
|
+
? runScriptCommandFor(packageManager, "dev")
|
|
2900
|
+
: `${installCommandFor(packageManager)} && ${runScriptCommandFor(packageManager, "dev")}`;
|
|
2901
|
+
const nextSteps = template === "supply-chain"
|
|
2902
|
+
? [
|
|
2903
|
+
`cd ${targetDir}`,
|
|
2904
|
+
startStep,
|
|
2905
|
+
`Open ${reviewUrl} and launch a purchase order from the control tower UI`,
|
|
2906
|
+
`${cliCommand} inspect --baseUrl=${reviewUrl} --admin --pretty`,
|
|
2907
|
+
`${cliCommand} "supplyChain.order.launch" "{ reference: 'PO-7842', supplierName: 'Marula Components', sku: 'DRV-2048' }" --baseUrl=${reviewUrl} --admin --pretty`,
|
|
2908
|
+
`${cliCommand} query "{ procurement_order: { supplier: {}, stockItems: {}, shipments: { inspections: {} } } }" --baseUrl=${reviewUrl} --admin --pretty`,
|
|
2909
|
+
]
|
|
2910
|
+
: template === "agent"
|
|
2911
|
+
? [
|
|
2912
|
+
`cd ${targetDir}`,
|
|
2913
|
+
startStep,
|
|
2914
|
+
`Open ${reviewUrl} and start a context through /api/agent/react`,
|
|
2915
|
+
`Inspect the returned context id in Workbench v2 for app ${appId || "<instant-app-id>"}`,
|
|
2916
|
+
`Iterate on src/agent.ts`,
|
|
2917
|
+
]
|
|
2918
|
+
: [
|
|
2919
|
+
`cd ${targetDir}`,
|
|
2920
|
+
startStep,
|
|
2921
|
+
`Open ${reviewUrl} and add your first domain in src/domain.ts`,
|
|
2922
|
+
`${cliCommand} inspect --baseUrl=${reviewUrl} --admin --pretty`,
|
|
2923
|
+
];
|
|
2924
|
+
const result = {
|
|
2925
|
+
ok: true,
|
|
2926
|
+
directory: targetDir,
|
|
2927
|
+
framework: params.framework,
|
|
2928
|
+
template,
|
|
2929
|
+
installed: params.install,
|
|
2930
|
+
packageManager,
|
|
2931
|
+
provisioned: Boolean(provisioned),
|
|
2932
|
+
appId,
|
|
2933
|
+
adminToken,
|
|
2934
|
+
adminTokenWritten: Boolean(envFile),
|
|
2935
|
+
envFile,
|
|
2936
|
+
smoke,
|
|
2937
|
+
demo: Boolean(params.demo),
|
|
2938
|
+
nextSteps,
|
|
2939
|
+
};
|
|
2940
|
+
await emitProgress(params.onProgress, {
|
|
2941
|
+
stage: "complete",
|
|
2942
|
+
status: "completed",
|
|
2943
|
+
message: "App scaffolded successfully",
|
|
2944
|
+
progress: 100,
|
|
2945
|
+
});
|
|
2946
|
+
return result;
|
|
2947
|
+
}
|
|
2948
|
+
//# sourceMappingURL=create-app.js.map
|