@ekanos/cli 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/README.md +103 -8
  2. package/dist/auth/ekanos-api.d.ts +23 -0
  3. package/dist/auth/ekanos-api.js +33 -6
  4. package/dist/auth/ekanos-api.js.map +1 -1
  5. package/dist/commands/add.d.ts +2 -0
  6. package/dist/commands/add.js +118 -0
  7. package/dist/commands/add.js.map +1 -0
  8. package/dist/commands/agents.js +3 -2
  9. package/dist/commands/agents.js.map +1 -1
  10. package/dist/commands/apps.d.ts +11 -3
  11. package/dist/commands/apps.js +86 -28
  12. package/dist/commands/apps.js.map +1 -1
  13. package/dist/commands/audiences.d.ts +31 -0
  14. package/dist/commands/audiences.js +239 -0
  15. package/dist/commands/audiences.js.map +1 -0
  16. package/dist/commands/build.d.ts +2 -0
  17. package/dist/commands/build.js +41 -0
  18. package/dist/commands/build.js.map +1 -0
  19. package/dist/commands/catalog.d.ts +4 -2
  20. package/dist/commands/catalog.js +166 -35
  21. package/dist/commands/catalog.js.map +1 -1
  22. package/dist/commands/check.d.ts +2 -0
  23. package/dist/commands/check.js +43 -0
  24. package/dist/commands/check.js.map +1 -0
  25. package/dist/commands/codegen.d.ts +2 -0
  26. package/dist/commands/codegen.js +49 -0
  27. package/dist/commands/codegen.js.map +1 -0
  28. package/dist/commands/contract.d.ts +52 -0
  29. package/dist/commands/contract.js +300 -0
  30. package/dist/commands/contract.js.map +1 -0
  31. package/dist/commands/dev.d.ts +7 -0
  32. package/dist/commands/dev.js +186 -12
  33. package/dist/commands/dev.js.map +1 -1
  34. package/dist/commands/index-experience.d.ts +31 -0
  35. package/dist/commands/index-experience.js +21 -0
  36. package/dist/commands/index-experience.js.map +1 -0
  37. package/dist/commands/init.d.ts +7 -0
  38. package/dist/commands/init.js +12 -0
  39. package/dist/commands/init.js.map +1 -1
  40. package/dist/commands/login.d.ts +2 -0
  41. package/dist/commands/login.js +17 -0
  42. package/dist/commands/login.js.map +1 -1
  43. package/dist/commands/new.d.ts +2 -0
  44. package/dist/commands/new.js +39 -0
  45. package/dist/commands/new.js.map +1 -0
  46. package/dist/commands/publish.d.ts +59 -0
  47. package/dist/commands/publish.js +118 -65
  48. package/dist/commands/publish.js.map +1 -1
  49. package/dist/commands/runs.d.ts +7 -3
  50. package/dist/commands/runs.js +21 -5
  51. package/dist/commands/runs.js.map +1 -1
  52. package/dist/commands/scope-notice.d.ts +13 -0
  53. package/dist/commands/scope-notice.js +12 -4
  54. package/dist/commands/scope-notice.js.map +1 -1
  55. package/dist/commands/sources-roles.d.ts +5 -0
  56. package/dist/commands/sources-roles.js +235 -0
  57. package/dist/commands/sources-roles.js.map +1 -0
  58. package/dist/commands/sources.d.ts +5 -0
  59. package/dist/commands/sources.js +8 -1
  60. package/dist/commands/sources.js.map +1 -1
  61. package/dist/commands/sync.d.ts +2 -0
  62. package/dist/commands/sync.js +150 -0
  63. package/dist/commands/sync.js.map +1 -0
  64. package/dist/commands/team-agent-readiness.d.ts +18 -2
  65. package/dist/commands/team-agent-readiness.js +39 -39
  66. package/dist/commands/team-agent-readiness.js.map +1 -1
  67. package/dist/commands/team-agents-agents.d.ts +5 -2
  68. package/dist/commands/team-agents-agents.js +152 -15
  69. package/dist/commands/team-agents-agents.js.map +1 -1
  70. package/dist/commands/team-agents-define.d.ts +31 -0
  71. package/dist/commands/team-agents-define.js +176 -0
  72. package/dist/commands/team-agents-define.js.map +1 -0
  73. package/dist/commands/team-agents-mcp.js +112 -10
  74. package/dist/commands/team-agents-mcp.js.map +1 -1
  75. package/dist/commands/team-agents-triggers.d.ts +1 -1
  76. package/dist/commands/team-agents-triggers.js +137 -5
  77. package/dist/commands/team-agents-triggers.js.map +1 -1
  78. package/dist/commands/team-agents.d.ts +4 -1
  79. package/dist/commands/team-agents.js +17 -4
  80. package/dist/commands/team-agents.js.map +1 -1
  81. package/dist/commands/team-llm-key.d.ts +14 -0
  82. package/dist/commands/team-llm-key.js +122 -0
  83. package/dist/commands/team-llm-key.js.map +1 -0
  84. package/dist/commands/teams-audience.d.ts +12 -0
  85. package/dist/commands/teams-audience.js +144 -0
  86. package/dist/commands/teams-audience.js.map +1 -0
  87. package/dist/commands/teams-connect.js +123 -31
  88. package/dist/commands/teams-connect.js.map +1 -1
  89. package/dist/commands/teams-lifecycle.d.ts +90 -0
  90. package/dist/commands/teams-lifecycle.js +457 -0
  91. package/dist/commands/teams-lifecycle.js.map +1 -0
  92. package/dist/commands/teams-onboard.d.ts +14 -0
  93. package/dist/commands/teams-onboard.js +62 -13
  94. package/dist/commands/teams-onboard.js.map +1 -1
  95. package/dist/commands/teams-overview.js +6 -2
  96. package/dist/commands/teams-overview.js.map +1 -1
  97. package/dist/commands/teams-scope.js +4 -0
  98. package/dist/commands/teams-scope.js.map +1 -1
  99. package/dist/commands/teams.d.ts +25 -5
  100. package/dist/commands/teams.js +106 -39
  101. package/dist/commands/teams.js.map +1 -1
  102. package/dist/commands/workflows.d.ts +37 -0
  103. package/dist/commands/workflows.js +589 -0
  104. package/dist/commands/workflows.js.map +1 -0
  105. package/dist/compatibility.d.ts +10 -1
  106. package/dist/compatibility.js +10 -1
  107. package/dist/compatibility.js.map +1 -1
  108. package/dist/connect-fields.d.ts +22 -0
  109. package/dist/connect-fields.js +115 -0
  110. package/dist/connect-fields.js.map +1 -0
  111. package/dist/context.d.ts +12 -0
  112. package/dist/context.js +32 -9
  113. package/dist/context.js.map +1 -1
  114. package/dist/dev-experience.d.ts +125 -0
  115. package/dist/dev-experience.js +128 -0
  116. package/dist/dev-experience.js.map +1 -0
  117. package/dist/experience/boundary.d.ts +12 -0
  118. package/dist/experience/boundary.js +260 -0
  119. package/dist/experience/boundary.js.map +1 -0
  120. package/dist/experience/build.d.ts +16 -0
  121. package/dist/experience/build.js +90 -0
  122. package/dist/experience/build.js.map +1 -0
  123. package/dist/experience/checks.d.ts +29 -0
  124. package/dist/experience/checks.js +133 -0
  125. package/dist/experience/checks.js.map +1 -0
  126. package/dist/experience/contract-sources.d.ts +36 -0
  127. package/dist/experience/contract-sources.js +111 -0
  128. package/dist/experience/contract-sources.js.map +1 -0
  129. package/dist/experience/definition-checks.d.ts +26 -0
  130. package/dist/experience/definition-checks.js +201 -0
  131. package/dist/experience/definition-checks.js.map +1 -0
  132. package/dist/experience/definition-shape.d.ts +60 -0
  133. package/dist/experience/definition-shape.js +2 -0
  134. package/dist/experience/definition-shape.js.map +1 -0
  135. package/dist/experience/init.d.ts +10 -0
  136. package/dist/experience/init.js +64 -0
  137. package/dist/experience/init.js.map +1 -0
  138. package/dist/experience/integrations-codegen.d.ts +17 -0
  139. package/dist/experience/integrations-codegen.js +51 -0
  140. package/dist/experience/integrations-codegen.js.map +1 -0
  141. package/dist/experience/json-schema-to-ts.d.ts +1 -0
  142. package/dist/experience/json-schema-to-ts.js +90 -0
  143. package/dist/experience/json-schema-to-ts.js.map +1 -0
  144. package/dist/experience/load-experience.d.ts +23 -0
  145. package/dist/experience/load-experience.js +115 -0
  146. package/dist/experience/load-experience.js.map +1 -0
  147. package/dist/experience/overrides.d.ts +8 -0
  148. package/dist/experience/overrides.js +21 -0
  149. package/dist/experience/overrides.js.map +1 -0
  150. package/dist/experience/pin-integration.d.ts +8 -0
  151. package/dist/experience/pin-integration.js +17 -0
  152. package/dist/experience/pin-integration.js.map +1 -0
  153. package/dist/experience/pin-ops.d.ts +12 -0
  154. package/dist/experience/pin-ops.js +36 -0
  155. package/dist/experience/pin-ops.js.map +1 -0
  156. package/dist/experience/pins.d.ts +35 -0
  157. package/dist/experience/pins.js +91 -0
  158. package/dist/experience/pins.js.map +1 -0
  159. package/dist/experience/project.d.ts +52 -0
  160. package/dist/experience/project.js +149 -0
  161. package/dist/experience/project.js.map +1 -0
  162. package/dist/experience/publish.d.ts +51 -0
  163. package/dist/experience/publish.js +144 -0
  164. package/dist/experience/publish.js.map +1 -0
  165. package/dist/experience/regenerate.d.ts +10 -0
  166. package/dist/experience/regenerate.js +33 -0
  167. package/dist/experience/regenerate.js.map +1 -0
  168. package/dist/experience/report.d.ts +9 -0
  169. package/dist/experience/report.js +23 -0
  170. package/dist/experience/report.js.map +1 -0
  171. package/dist/harness-scaffold.d.ts +52 -18
  172. package/dist/harness-scaffold.js +189 -9
  173. package/dist/harness-scaffold.js.map +1 -1
  174. package/dist/index.d.ts +13 -0
  175. package/dist/index.js +494 -127
  176. package/dist/index.js.map +1 -1
  177. package/dist/invocation.d.ts +31 -0
  178. package/dist/invocation.js +58 -0
  179. package/dist/invocation.js.map +1 -0
  180. package/dist/project-checks.d.ts +12 -0
  181. package/dist/project-checks.js +46 -0
  182. package/dist/project-checks.js.map +1 -1
  183. package/dist/project.d.ts +14 -0
  184. package/dist/project.js +10 -0
  185. package/dist/project.js.map +1 -1
  186. package/dist/repo/publish.d.ts +42 -0
  187. package/dist/repo/publish.js +225 -0
  188. package/dist/repo/publish.js.map +1 -0
  189. package/dist/repo/scaffold.d.ts +30 -0
  190. package/dist/repo/scaffold.js +165 -0
  191. package/dist/repo/scaffold.js.map +1 -0
  192. package/dist/runs-api.js +8 -3
  193. package/dist/runs-api.js.map +1 -1
  194. package/dist/safe-fs.d.ts +18 -0
  195. package/dist/safe-fs.js +57 -0
  196. package/dist/safe-fs.js.map +1 -1
  197. package/dist/secret-form.d.ts +48 -0
  198. package/dist/secret-form.js +212 -0
  199. package/dist/secret-form.js.map +1 -0
  200. package/dist/secret-input.d.ts +96 -0
  201. package/dist/secret-input.js +299 -0
  202. package/dist/secret-input.js.map +1 -0
  203. package/dist/team-connect-api.d.ts +78 -3
  204. package/dist/team-connect-api.js +17 -2
  205. package/dist/team-connect-api.js.map +1 -1
  206. package/dist/team-lifecycle-api.d.ts +435 -0
  207. package/dist/team-lifecycle-api.js +301 -0
  208. package/dist/team-lifecycle-api.js.map +1 -0
  209. package/dist/teams-api.d.ts +49 -1
  210. package/dist/teams-api.js +149 -27
  211. package/dist/teams-api.js.map +1 -1
  212. package/dist/unknown-command.d.ts +18 -0
  213. package/dist/unknown-command.js +50 -0
  214. package/dist/unknown-command.js.map +1 -0
  215. package/dist/validate-findings.js +2 -1
  216. package/dist/validate-findings.js.map +1 -1
  217. package/dist/workflows-api.d.ts +69 -0
  218. package/dist/workflows-api.js +182 -0
  219. package/dist/workflows-api.js.map +1 -0
  220. package/package.json +3 -3
  221. package/templates/AGENTS.md.tmpl +6 -4
  222. package/templates/claude-skill.md.tmpl +4 -1
  223. package/templates/ekanos.json.tmpl +1 -0
  224. package/templates/experience/AGENTS.md.tmpl +22 -0
  225. package/templates/experience/ekanos.json.tmpl +7 -0
  226. package/templates/experience/ekanos.lock.json.tmpl +4 -0
  227. package/templates/experience/eslint.config.mjs.tmpl +25 -0
  228. package/templates/experience/gitignore.tmpl +17 -0
  229. package/templates/experience/package.json.tmpl +41 -0
  230. package/templates/experience/src/.ekanos/integrations.d.ts.tmpl +3 -0
  231. package/templates/experience/src/experience.ts.tmpl +30 -0
  232. package/templates/experience/src/widgets/hello-widget.tsx.tmpl +40 -0
  233. package/templates/experience/tsconfig.json.tmpl +20 -0
  234. package/templates/harness/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +9 -0
  235. package/templates/harness/app/[slug]/activation/page.tsx.tmpl +1 -1
  236. package/templates/harness/app/[slug]/page.tsx.tmpl +1 -1
  237. package/templates/harness/app/[slug]/tile/page.tsx.tmpl +1 -1
  238. package/templates/harness/app/[slug]/triggers/page.tsx.tmpl +1 -1
  239. package/templates/harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl +1 -1
  240. package/templates/harness/app/[slug]/widgets/page.tsx.tmpl +1 -1
  241. package/templates/harness/app/experience/layout.tsx.tmpl +17 -0
  242. package/templates/harness/app/experience/page.tsx.tmpl +1 -0
  243. package/templates/harness/app/experience/pages/[id]/page.tsx.tmpl +1 -0
  244. package/templates/harness/app/experience/review-queue/page.tsx.tmpl +1 -0
  245. package/templates/harness/app/experience/runs/page.tsx.tmpl +1 -0
  246. package/templates/harness/app/experience/widgets/[id]/page.tsx.tmpl +1 -0
  247. package/templates/harness/app/experience/widgets/page.tsx.tmpl +1 -0
  248. package/templates/harness/app/experience/workflows/[id]/page.tsx.tmpl +1 -0
  249. package/templates/harness/app/experience/workflows/page.tsx.tmpl +1 -0
  250. package/templates/harness/app/integrations/[slug]/connect/page.tsx.tmpl +1 -0
  251. package/templates/harness/app/integrations/[slug]/events/page.tsx.tmpl +1 -0
  252. package/templates/harness/app/integrations/[slug]/health/page.tsx.tmpl +1 -0
  253. package/templates/harness/app/integrations/[slug]/operations/[kind]/[name]/page.tsx.tmpl +1 -0
  254. package/templates/harness/app/integrations/[slug]/operations/page.tsx.tmpl +1 -0
  255. package/templates/harness/app/integrations/[slug]/page.tsx.tmpl +1 -0
  256. package/templates/harness/app/integrations/[slug]/preview/activation/page.tsx.tmpl +1 -0
  257. package/templates/harness/app/integrations/[slug]/preview/layout.tsx.tmpl +1 -0
  258. package/templates/harness/app/integrations/[slug]/preview/page.tsx.tmpl +1 -0
  259. package/templates/harness/app/integrations/[slug]/preview/tile/page.tsx.tmpl +1 -0
  260. package/templates/harness/app/integrations/[slug]/preview/triggers/page.tsx.tmpl +1 -0
  261. package/templates/harness/app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx.tmpl +1 -0
  262. package/templates/harness/app/integrations/[slug]/preview/widgets/page.tsx.tmpl +1 -0
  263. package/templates/harness/app/integrations/[slug]/runs/page.tsx.tmpl +1 -0
  264. package/templates/harness/app/integrations/layout.tsx.tmpl +1 -0
  265. package/templates/harness/app/integrations/page.tsx.tmpl +1 -0
  266. package/templates/harness/app/page.experience.tsx.tmpl +5 -0
  267. package/templates/harness/repo/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +11 -0
  268. package/templates/harness/repo/app/(repo)/connections/page.tsx.tmpl +1 -0
  269. package/templates/harness/repo/app/(repo)/layout.tsx.tmpl +1 -0
  270. package/templates/harness/repo/app/(repo)/page.tsx.tmpl +1 -0
  271. package/templates/harness/repo/app/experiences/[name]/connect/page.tsx.tmpl +1 -0
  272. package/templates/harness/repo/app/experiences/[name]/layout.tsx.tmpl +9 -0
  273. package/templates/harness/repo/app/experiences/page.tsx.tmpl +5 -0
  274. package/templates/harness/repo/app/harness-shell.tsx.tmpl +9 -0
  275. package/templates/harness/repo/app/integrations/[slug]/layout.tsx.tmpl +1 -0
  276. package/templates/harness/repo/app/integrations/page.tsx.tmpl +5 -0
  277. package/templates/harness/repo/app/repo-experiences.ts.tmpl +5 -0
  278. package/templates/harness/styles/globals.css.tmpl +15 -1
  279. package/templates/package.json.tmpl +4 -4
  280. package/templates/skills/build-agent/SKILL.md +43 -10
  281. package/templates/skills/build-agent/references/agent-json.md +4 -1
  282. package/templates/skills/build-agent/references/app-setup.md +14 -7
  283. package/templates/skills/build-agent/references/rollout.md +26 -2
  284. package/templates/skills/build-agent/references/set-up-an-integration.md +46 -29
  285. package/templates/skills/build-integration/references/widget-design.md +1 -1
  286. package/templates/skills/ekanos-platform/SKILL.md +66 -14
  287. package/templates/skills/polish-widgets/references/patterns.md +3 -3
@@ -0,0 +1,201 @@
1
+ import { WorkflowTemplateSchema, canonicalJson, sha256Hex, validateAgainstSchema, workflowTemplateProblems, } from '@ekanos/integration-schema';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { declaredOperationKeys } from './integrations-codegen.js';
5
+ import { effectLookup } from './pins.js';
6
+ export const DEFINITION_CHECK = 'checks/experience-definition';
7
+ const err = (message, hint, file) => (Object.assign({ check: DEFINITION_CHECK, severity: 'error', message,
8
+ hint }, (file ? { file } : {})));
9
+ const warn = (message, hint, file) => (Object.assign({ check: DEFINITION_CHECK, severity: 'warn', message,
10
+ hint }, (file ? { file } : {})));
11
+ /**
12
+ * Cross-check the loaded definition against `ekanos.json` and the pinned
13
+ * contracts: every integration a widget, page, review section or workflow step names
14
+ * must be declared, every operation must be in that integration's allowlist, the
15
+ * review queue reads only, and each template serializes to the engine's DSL.
16
+ */
17
+ export function analyzeDefinition(project, loaded, pinned) {
18
+ var _a, _b, _c, _d, _e;
19
+ var _f, _g;
20
+ const { definition } = loaded;
21
+ const findings = [];
22
+ const workflows = [];
23
+ const declaredIntegrations = new Map(project.integrations.map((u) => [u.name, u]));
24
+ const declared = new Map(project.integrations.map((u) => [
25
+ u.name,
26
+ new Set(declaredOperationKeys(u)),
27
+ ]));
28
+ const contractOps = new Map(pinned.map(({ use, contract }) => [
29
+ use.name,
30
+ new Map(contract.operations.map((op) => [`${op.kind}/${op.name}`, op])),
31
+ ]));
32
+ if (definition.slug !== project.slug) {
33
+ findings.push(err(`the definition's slug "${definition.slug}" differs from ekanos.json "${project.slug}".`, 'Make them equal.', project.entry));
34
+ }
35
+ const checkIntegration = (integration, owner) => {
36
+ if (!declaredIntegrations.has(integration)) {
37
+ findings.push(err(`${owner} uses integration "${integration}", which ekanos.json "integrations" does not declare.`, `Add it with "ekanos add ${integration}@<version>".`, project.entry));
38
+ return false;
39
+ }
40
+ return true;
41
+ };
42
+ for (const widget of definition.widgets) {
43
+ for (const integration of widget.integrations)
44
+ checkIntegration(integration, `widget "${widget.id}"`);
45
+ }
46
+ for (const page of definition.pages) {
47
+ for (const integration of page.integrations)
48
+ checkIntegration(integration, `page "${page.id}"`);
49
+ }
50
+ for (const section of (_f = (_a = definition.reviewQueue) === null || _a === void 0 ? void 0 : _a.sections) !== null && _f !== void 0 ? _f : []) {
51
+ if (!checkIntegration(section.integration, `review section "${section.id}"`))
52
+ continue;
53
+ if (!((_b = declared.get(section.integration)) === null || _b === void 0 ? void 0 : _b.has(section.read))) {
54
+ findings.push(err(`review section "${section.id}" reads ${section.read}, which integration "${section.integration}" does not declare.`, 'Add the operation to the integration in ekanos.json, or read another one.', project.entry));
55
+ }
56
+ else if (((_d = (_c = contractOps.get(section.integration)) === null || _c === void 0 ? void 0 : _c.get(section.read)) === null || _d === void 0 ? void 0 : _d.effect) !== 'read') {
57
+ findings.push(err(`review section "${section.id}" reads ${section.read}, which is not a read operation.`, 'The review queue is reads-only; writes belong behind a workflow approval.', project.entry));
58
+ }
59
+ }
60
+ const effectOf = effectLookup(pinned);
61
+ for (const template of definition.workflows) {
62
+ const where = `workflow "${template.id}"`;
63
+ let steps;
64
+ try {
65
+ steps = template.build();
66
+ }
67
+ catch (error) {
68
+ findings.push(err(`${where}: build() threw: ${error instanceof Error ? error.message : String(error)}`, 'Fix the template.', project.entry));
69
+ continue;
70
+ }
71
+ let stepsOk = true;
72
+ for (const step of steps) {
73
+ if (step.type !== 'tool' || !step.integration || !step.op)
74
+ continue;
75
+ if (!checkIntegration(step.integration, `${where} step "${step.id}"`)) {
76
+ stepsOk = false;
77
+ }
78
+ else if (!((_e = declared.get(step.integration)) === null || _e === void 0 ? void 0 : _e.has(step.op))) {
79
+ stepsOk = false;
80
+ findings.push(err(`${where} step "${step.id}" calls ${step.integration} ${step.op}, which the integration does not declare.`, 'Add the operation to the integration\'s "operations" in ekanos.json (ekanos add --ops).', project.entry));
81
+ }
82
+ }
83
+ if (!stepsOk)
84
+ continue;
85
+ let dsl;
86
+ try {
87
+ dsl = loaded.serializeWorkflowTemplate(template, { effectOf });
88
+ }
89
+ catch (error) {
90
+ findings.push(err(`${where}: ${error instanceof Error ? error.message : String(error)}`, 'Fix the template.', project.entry));
91
+ continue;
92
+ }
93
+ const parsed = WorkflowTemplateSchema.safeParse(dsl);
94
+ if (!parsed.success) {
95
+ findings.push(err(`${where} does not serialize to a valid workflow: ${parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`, 'Fix the step definitions.', project.entry));
96
+ continue;
97
+ }
98
+ for (const problem of workflowTemplateProblems(parsed.data)) {
99
+ findings.push(err(`${where}: ${problem}.`, 'Fix the template.', project.entry));
100
+ }
101
+ const gated = new Set();
102
+ const preds = new Map();
103
+ for (const e of parsed.data.edges)
104
+ preds.set(e.to, [...((_g = preds.get(e.to)) !== null && _g !== void 0 ? _g : []), e.from]);
105
+ const hasGate = (id, seen = new Set()) => {
106
+ var _a;
107
+ if (seen.has(id))
108
+ return false;
109
+ seen.add(id);
110
+ return ((_a = preds.get(id)) !== null && _a !== void 0 ? _a : []).some((p) => { var _a; return ((_a = parsed.data.steps.find((s) => s.id === p)) === null || _a === void 0 ? void 0 : _a.type) === 'approval_gate' ||
111
+ hasGate(p, seen); });
112
+ };
113
+ for (const s of parsed.data.steps) {
114
+ if (s.type === 'fusion_tool' && s.effect === 'write' && !hasGate(s.id)) {
115
+ gated.add(s.id);
116
+ }
117
+ }
118
+ for (const id of gated) {
119
+ findings.push(warn(`${where}: write step "${id}" has no approval gate upstream.`, 'Put step.approval(...) before a write so a person confirms it.', project.entry));
120
+ }
121
+ workflows.push(parsed.data);
122
+ }
123
+ return { findings, workflows };
124
+ }
125
+ // ─── fixtures ───────────────────────────────────────────────────────────────
126
+ const FIXTURE_NAME = /^(tool|action)-(.+)\.(default|empty|error)\.json$/;
127
+ /** `fixtures/<integration>/<kind>-<name>.<variant>.json` must satisfy the pinned output schema. */
128
+ export function checkFixtures(project, pinned) {
129
+ const findings = [];
130
+ const root = path.join(project.dir, 'fixtures');
131
+ if (!fs.existsSync(root))
132
+ return findings;
133
+ for (const { use, contract } of pinned) {
134
+ const dir = path.join(root, use.name);
135
+ if (!fs.existsSync(dir))
136
+ continue;
137
+ for (const name of fs.readdirSync(dir)) {
138
+ const rel = `fixtures/${use.name}/${name}`;
139
+ const match = FIXTURE_NAME.exec(name);
140
+ if (!match) {
141
+ findings.push(warn(`${rel} is not <kind>-<name>.<default|empty|error>.json.`, 'Rename it so the harness can find it.', rel));
142
+ continue;
143
+ }
144
+ const [, kind, op, variant] = match;
145
+ if (variant === 'error')
146
+ continue;
147
+ const operation = contract.operations.find((o) => o.kind === kind && o.name === op);
148
+ if (!operation) {
149
+ findings.push(err(`${rel}: ${use.name}@${use.version} has no ${kind}/${op}.`, 'Delete the fixture or fix its name.', rel));
150
+ continue;
151
+ }
152
+ if (operation.opaque.output)
153
+ continue;
154
+ let value;
155
+ try {
156
+ value = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf8'));
157
+ }
158
+ catch (_a) {
159
+ findings.push(err(`${rel} is not valid JSON.`, 'Fix the syntax.', rel));
160
+ continue;
161
+ }
162
+ const problems = validateAgainstSchema(operation.output, value);
163
+ if (problems.length > 0) {
164
+ findings.push(err(`${rel} violates the pinned output schema: ${problems[0]}`, 'A fixture must satisfy the contract, or the harness would show data the integration can never return.', rel));
165
+ }
166
+ }
167
+ }
168
+ return findings;
169
+ }
170
+ // ─── manifest ───────────────────────────────────────────────────────────────
171
+ export function buildManifest(input) {
172
+ const { project, definition, pinned } = input;
173
+ const body = Object.assign(Object.assign(Object.assign({ schemaVersion: 1, slug: project.slug, form: 'composed', version: input.version, sdk: input.sdk, integrations: project.integrations, contractRefs: pinned.map(({ use, contract }) => ({
174
+ integration: use.name,
175
+ version: use.version,
176
+ contractVersion: contract.contractVersion,
177
+ digest: contract.digest,
178
+ })) }, (definition.sidebar ? { sidebar: definition.sidebar } : {})), { widgets: definition.widgets.map((w) => (Object.assign(Object.assign(Object.assign({ id: w.id, title: w.title, integrations: [...w.integrations] }, (w.width ? { width: w.width } : {})), (w.isCollapsible === undefined
179
+ ? {}
180
+ : { isCollapsible: w.isCollapsible })), (w.aiFooterEnabled === undefined
181
+ ? {}
182
+ : { aiFooterEnabled: w.aiFooterEnabled })))), pages: definition.pages.map((p) => ({
183
+ id: p.id,
184
+ path: p.path,
185
+ title: p.title,
186
+ integrations: [...p.integrations],
187
+ })), workflowTemplates: [...input.workflows] }), (definition.reviewQueue
188
+ ? {
189
+ reviewQueue: {
190
+ readsOnly: true,
191
+ sections: definition.reviewQueue.sections.map((s) => ({
192
+ id: s.id,
193
+ integration: s.integration,
194
+ read: s.read,
195
+ })),
196
+ },
197
+ }
198
+ : {}));
199
+ return Object.assign(Object.assign({}, body), { digest: `sha256:${sha256Hex(canonicalJson(body))}` });
200
+ }
201
+ //# sourceMappingURL=definition-checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition-checks.js","sourceRoot":"","sources":["../../src/experience/definition-checks.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type ExperienceManifest,\n type Finding,\n type WorkflowTemplate,\n WorkflowTemplateSchema,\n canonicalJson,\n sha256Hex,\n validateAgainstSchema,\n workflowTemplateProblems,\n} from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type {\n LoadedExperienceDefinition,\n LoadedExperienceModule,\n} from './definition-shape';\nimport { declaredOperationKeys } from './integrations-codegen';\nimport { type PinnedIntegration, effectLookup } from './pins';\nimport type { ExperienceProject } from './project';\n\nexport const DEFINITION_CHECK = 'checks/experience-definition';\n\nconst err = (message: string, hint: string, file?: string): Finding => ({\n check: DEFINITION_CHECK,\n severity: 'error',\n message,\n hint,\n ...(file ? { file } : {}),\n});\n\nconst warn = (message: string, hint: string, file?: string): Finding => ({\n check: DEFINITION_CHECK,\n severity: 'warn',\n message,\n hint,\n ...(file ? { file } : {}),\n});\n\nexport interface DefinitionAnalysis {\n findings: Finding[];\n workflows: WorkflowTemplate[];\n}\n\n/**\n * Cross-check the loaded definition against `ekanos.json` and the pinned\n * contracts: every integration a widget, page, review section or workflow step names\n * must be declared, every operation must be in that integration's allowlist, the\n * review queue reads only, and each template serializes to the engine's DSL.\n */\nexport function analyzeDefinition(\n project: ExperienceProject,\n loaded: LoadedExperienceModule,\n pinned: readonly PinnedIntegration[],\n): DefinitionAnalysis {\n const { definition } = loaded;\n const findings: Finding[] = [];\n const workflows: WorkflowTemplate[] = [];\n const declaredIntegrations = new Map(\n project.integrations.map((u) => [u.name, u]),\n );\n const declared = new Map(\n project.integrations.map((u) => [\n u.name,\n new Set(declaredOperationKeys(u)),\n ]),\n );\n const contractOps = new Map(\n pinned.map(({ use, contract }) => [\n use.name,\n new Map(contract.operations.map((op) => [`${op.kind}/${op.name}`, op])),\n ]),\n );\n\n if (definition.slug !== project.slug) {\n findings.push(\n err(\n `the definition's slug \"${definition.slug}\" differs from ekanos.json \"${project.slug}\".`,\n 'Make them equal.',\n project.entry,\n ),\n );\n }\n\n const checkIntegration = (integration: string, owner: string) => {\n if (!declaredIntegrations.has(integration)) {\n findings.push(\n err(\n `${owner} uses integration \"${integration}\", which ekanos.json \"integrations\" does not declare.`,\n `Add it with \"ekanos add ${integration}@<version>\".`,\n project.entry,\n ),\n );\n\n return false;\n }\n\n return true;\n };\n\n for (const widget of definition.widgets) {\n for (const integration of widget.integrations)\n checkIntegration(integration, `widget \"${widget.id}\"`);\n }\n for (const page of definition.pages) {\n for (const integration of page.integrations)\n checkIntegration(integration, `page \"${page.id}\"`);\n }\n\n for (const section of definition.reviewQueue?.sections ?? []) {\n if (\n !checkIntegration(section.integration, `review section \"${section.id}\"`)\n )\n continue;\n if (!declared.get(section.integration)?.has(section.read)) {\n findings.push(\n err(\n `review section \"${section.id}\" reads ${section.read}, which integration \"${section.integration}\" does not declare.`,\n 'Add the operation to the integration in ekanos.json, or read another one.',\n project.entry,\n ),\n );\n } else if (\n contractOps.get(section.integration)?.get(section.read)?.effect !== 'read'\n ) {\n findings.push(\n err(\n `review section \"${section.id}\" reads ${section.read}, which is not a read operation.`,\n 'The review queue is reads-only; writes belong behind a workflow approval.',\n project.entry,\n ),\n );\n }\n }\n\n const effectOf = effectLookup(pinned);\n for (const template of definition.workflows) {\n const where = `workflow \"${template.id}\"`;\n let steps: ReturnType<typeof template.build>;\n try {\n steps = template.build();\n } catch (error) {\n findings.push(\n err(\n `${where}: build() threw: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix the template.',\n project.entry,\n ),\n );\n continue;\n }\n\n let stepsOk = true;\n for (const step of steps) {\n if (step.type !== 'tool' || !step.integration || !step.op) continue;\n if (!checkIntegration(step.integration, `${where} step \"${step.id}\"`)) {\n stepsOk = false;\n } else if (!declared.get(step.integration)?.has(step.op)) {\n stepsOk = false;\n findings.push(\n err(\n `${where} step \"${step.id}\" calls ${step.integration} ${step.op}, which the integration does not declare.`,\n 'Add the operation to the integration\\'s \"operations\" in ekanos.json (ekanos add --ops).',\n project.entry,\n ),\n );\n }\n }\n if (!stepsOk) continue;\n\n let dsl: unknown;\n try {\n dsl = loaded.serializeWorkflowTemplate(template, { effectOf });\n } catch (error) {\n findings.push(\n err(\n `${where}: ${error instanceof Error ? error.message : String(error)}`,\n 'Fix the template.',\n project.entry,\n ),\n );\n continue;\n }\n const parsed = WorkflowTemplateSchema.safeParse(dsl);\n if (!parsed.success) {\n findings.push(\n err(\n `${where} does not serialize to a valid workflow: ${parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`,\n 'Fix the step definitions.',\n project.entry,\n ),\n );\n continue;\n }\n for (const problem of workflowTemplateProblems(parsed.data)) {\n findings.push(\n err(`${where}: ${problem}.`, 'Fix the template.', project.entry),\n );\n }\n\n const gated = new Set<string>();\n const preds = new Map<string, string[]>();\n for (const e of parsed.data.edges)\n preds.set(e.to, [...(preds.get(e.to) ?? []), e.from]);\n const hasGate = (id: string, seen = new Set<string>()): boolean => {\n if (seen.has(id)) return false;\n seen.add(id);\n\n return (preds.get(id) ?? []).some(\n (p) =>\n parsed.data.steps.find((s) => s.id === p)?.type === 'approval_gate' ||\n hasGate(p, seen),\n );\n };\n for (const s of parsed.data.steps) {\n if (s.type === 'fusion_tool' && s.effect === 'write' && !hasGate(s.id)) {\n gated.add(s.id);\n }\n }\n for (const id of gated) {\n findings.push(\n warn(\n `${where}: write step \"${id}\" has no approval gate upstream.`,\n 'Put step.approval(...) before a write so a person confirms it.',\n project.entry,\n ),\n );\n }\n\n workflows.push(parsed.data);\n }\n\n return { findings, workflows };\n}\n\n// ─── fixtures ───────────────────────────────────────────────────────────────\n\nconst FIXTURE_NAME = /^(tool|action)-(.+)\\.(default|empty|error)\\.json$/;\n\n/** `fixtures/<integration>/<kind>-<name>.<variant>.json` must satisfy the pinned output schema. */\nexport function checkFixtures(\n project: ExperienceProject,\n pinned: readonly PinnedIntegration[],\n): Finding[] {\n const findings: Finding[] = [];\n const root = path.join(project.dir, 'fixtures');\n if (!fs.existsSync(root)) return findings;\n\n for (const { use, contract } of pinned) {\n const dir = path.join(root, use.name);\n if (!fs.existsSync(dir)) continue;\n for (const name of fs.readdirSync(dir)) {\n const rel = `fixtures/${use.name}/${name}`;\n const match = FIXTURE_NAME.exec(name);\n if (!match) {\n findings.push(\n warn(\n `${rel} is not <kind>-<name>.<default|empty|error>.json.`,\n 'Rename it so the harness can find it.',\n rel,\n ),\n );\n continue;\n }\n const [, kind, op, variant] = match;\n if (variant === 'error') continue;\n const operation = contract.operations.find(\n (o) => o.kind === kind && o.name === op,\n );\n if (!operation) {\n findings.push(\n err(\n `${rel}: ${use.name}@${use.version} has no ${kind}/${op}.`,\n 'Delete the fixture or fix its name.',\n rel,\n ),\n );\n continue;\n }\n if (operation.opaque.output) continue;\n let value: unknown;\n try {\n value = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf8'));\n } catch {\n findings.push(err(`${rel} is not valid JSON.`, 'Fix the syntax.', rel));\n continue;\n }\n const problems = validateAgainstSchema(operation.output, value);\n if (problems.length > 0) {\n findings.push(\n err(\n `${rel} violates the pinned output schema: ${problems[0]}`,\n 'A fixture must satisfy the contract, or the harness would show data the integration can never return.',\n rel,\n ),\n );\n }\n }\n }\n\n return findings;\n}\n\n// ─── manifest ───────────────────────────────────────────────────────────────\n\nexport function buildManifest(input: {\n project: ExperienceProject;\n definition: LoadedExperienceDefinition;\n pinned: readonly PinnedIntegration[];\n workflows: readonly WorkflowTemplate[];\n version: string;\n sdk: string;\n}): ExperienceManifest {\n const { project, definition, pinned } = input;\n const body = {\n schemaVersion: 1 as const,\n slug: project.slug,\n form: 'composed' as const,\n version: input.version,\n sdk: input.sdk,\n integrations: project.integrations,\n contractRefs: pinned.map(({ use, contract }) => ({\n integration: use.name,\n version: use.version,\n contractVersion: contract.contractVersion,\n digest: contract.digest,\n })),\n ...(definition.sidebar ? { sidebar: definition.sidebar } : {}),\n widgets: definition.widgets.map((w) => ({\n id: w.id,\n title: w.title,\n integrations: [...w.integrations],\n ...(w.width ? { width: w.width } : {}),\n ...(w.isCollapsible === undefined\n ? {}\n : { isCollapsible: w.isCollapsible }),\n ...(w.aiFooterEnabled === undefined\n ? {}\n : { aiFooterEnabled: w.aiFooterEnabled }),\n })),\n pages: definition.pages.map((p) => ({\n id: p.id,\n path: p.path,\n title: p.title,\n integrations: [...p.integrations],\n })),\n workflowTemplates: [...input.workflows],\n ...(definition.reviewQueue\n ? {\n reviewQueue: {\n readsOnly: true as const,\n sections: definition.reviewQueue.sections.map((s) => ({\n id: s.id,\n integration: s.integration,\n read: s.read,\n })),\n },\n }\n : {}),\n };\n\n return {\n ...body,\n digest: `sha256:${sha256Hex(canonicalJson(body))}`,\n };\n}\n"]}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The structural slice of an `ExperienceDefinition` the CLI reads. The CLI does
3
+ * not depend on `@ekanos/sdk`: the definition is produced by whichever SDK the
4
+ * project installed, and anything the CLI needs computed (workflow
5
+ * serialization) is obtained from that same SDK at load time.
6
+ */
7
+ export interface LoadedWorkflowTemplate {
8
+ id: string;
9
+ name: string;
10
+ build(): ReadonlyArray<{
11
+ type: string;
12
+ id: string;
13
+ integration?: string;
14
+ op?: string;
15
+ }>;
16
+ }
17
+ export interface LoadedExperienceDefinition {
18
+ kind: 'experience';
19
+ slug: string;
20
+ name: string;
21
+ description?: string;
22
+ sidebar?: {
23
+ section: string;
24
+ icon: string;
25
+ label: string;
26
+ };
27
+ widgets: ReadonlyArray<{
28
+ id: string;
29
+ title: string;
30
+ integrations: readonly string[];
31
+ width?: string;
32
+ isCollapsible?: boolean;
33
+ isPinnable?: boolean;
34
+ aiFooterEnabled?: boolean;
35
+ }>;
36
+ pages: ReadonlyArray<{
37
+ id: string;
38
+ path: string;
39
+ title: string;
40
+ integrations: readonly string[];
41
+ }>;
42
+ workflows: readonly LoadedWorkflowTemplate[];
43
+ reviewQueue?: {
44
+ readsOnly: true;
45
+ sections: ReadonlyArray<{
46
+ id: string;
47
+ integration: string;
48
+ read: string;
49
+ }>;
50
+ };
51
+ }
52
+ export interface SerializedWorkflowOptions {
53
+ effectOf: (integration: string, op: string) => 'read' | 'write' | undefined;
54
+ }
55
+ export interface LoadedExperienceModule {
56
+ definition: LoadedExperienceDefinition;
57
+ /** The project's own SDK serializer, so the output matches the SDK that authored it. */
58
+ serializeWorkflowTemplate(template: LoadedWorkflowTemplate, options: SerializedWorkflowOptions): unknown;
59
+ slotsUsedBy(definition: LoadedExperienceDefinition): string[];
60
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=definition-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition-shape.js","sourceRoot":"","sources":["../../src/experience/definition-shape.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The structural slice of an `ExperienceDefinition` the CLI reads. The CLI does\n * not depend on `@ekanos/sdk`: the definition is produced by whichever SDK the\n * project installed, and anything the CLI needs computed (workflow\n * serialization) is obtained from that same SDK at load time.\n */\nexport interface LoadedWorkflowTemplate {\n id: string;\n name: string;\n build(): ReadonlyArray<{\n type: string;\n id: string;\n integration?: string;\n op?: string;\n }>;\n}\n\nexport interface LoadedExperienceDefinition {\n kind: 'experience';\n slug: string;\n name: string;\n description?: string;\n sidebar?: { section: string; icon: string; label: string };\n widgets: ReadonlyArray<{\n id: string;\n title: string;\n integrations: readonly string[];\n width?: string;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n aiFooterEnabled?: boolean;\n }>;\n pages: ReadonlyArray<{\n id: string;\n path: string;\n title: string;\n integrations: readonly string[];\n }>;\n workflows: readonly LoadedWorkflowTemplate[];\n reviewQueue?: {\n readsOnly: true;\n sections: ReadonlyArray<{ id: string; integration: string; read: string }>;\n };\n}\n\nexport interface SerializedWorkflowOptions {\n effectOf: (integration: string, op: string) => 'read' | 'write' | undefined;\n}\n\nexport interface LoadedExperienceModule {\n definition: LoadedExperienceDefinition;\n /** The project's own SDK serializer, so the output matches the SDK that authored it. */\n serializeWorkflowTemplate(\n template: LoadedWorkflowTemplate,\n options: SerializedWorkflowOptions,\n ): unknown;\n slotsUsedBy(definition: LoadedExperienceDefinition): string[];\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { CliContext } from '../context.js';
2
+ import type { ExitCode } from '../exit-codes.js';
3
+ export interface InitExperienceArgs {
4
+ slug?: string;
5
+ dir?: string;
6
+ cwd: string;
7
+ force: boolean;
8
+ }
9
+ /** `ekanos init --kind experience`: scaffold a project with no integrations yet. */
10
+ export declare function runInitExperience(ctx: CliContext, args: InitExperienceArgs): ExitCode;
@@ -0,0 +1,64 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { invalidStateError, usageError } from '../errors.js';
4
+ import { classifyPath, writeFileNoFollow } from '../safe-fs.js';
5
+ import { readTemplate, toDisplayName } from '../templates.js';
6
+ const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
+ /** template (under templates/experience/) -> destination. */
8
+ const FILES = [
9
+ { template: 'ekanos.json.tmpl', dest: 'ekanos.json' },
10
+ { template: 'package.json.tmpl', dest: 'package.json' },
11
+ { template: 'tsconfig.json.tmpl', dest: 'tsconfig.json' },
12
+ { template: 'gitignore.tmpl', dest: '.gitignore' },
13
+ { template: 'eslint.config.mjs.tmpl', dest: 'eslint.config.mjs' },
14
+ { template: 'AGENTS.md.tmpl', dest: 'AGENTS.md' },
15
+ { template: 'ekanos.lock.json.tmpl', dest: 'ekanos.lock.json' },
16
+ { template: 'src/experience.ts.tmpl', dest: 'src/experience.ts' },
17
+ {
18
+ template: 'src/widgets/hello-widget.tsx.tmpl',
19
+ dest: 'src/widgets/hello-widget.tsx',
20
+ },
21
+ {
22
+ template: 'src/.ekanos/integrations.d.ts.tmpl',
23
+ dest: 'src/.ekanos/integrations.d.ts',
24
+ },
25
+ ];
26
+ /** `ekanos init --kind experience`: scaffold a project with no integrations yet. */
27
+ export function runInitExperience(ctx, args) {
28
+ var _a;
29
+ const slug = args.slug;
30
+ if (!slug || !SLUG_PATTERN.test(slug)) {
31
+ throw usageError(slug ? `Invalid slug "${slug}".` : 'init requires a slug.', 'Pass kebab-case: "ekanos init --kind experience --slug content-engine".');
32
+ }
33
+ const targetDir = path.resolve(args.cwd, (_a = args.dir) !== null && _a !== void 0 ? _a : slug);
34
+ if (fs.existsSync(targetDir) && fs.lstatSync(targetDir).isSymbolicLink()) {
35
+ throw invalidStateError(`The target directory ${targetDir} is a symlink.`, 'Scaffold into the real directory.');
36
+ }
37
+ fs.mkdirSync(targetDir, { recursive: true });
38
+ const vars = { SLUG: slug, DISPLAY_NAME: toDisplayName(slug) };
39
+ const created = [];
40
+ const skipped = [];
41
+ for (const { template, dest } of FILES) {
42
+ const destPath = path.join(targetDir, dest);
43
+ const kind = classifyPath(destPath);
44
+ if (kind !== 'missing' && kind !== 'symlink' && !args.force) {
45
+ skipped.push(dest);
46
+ continue;
47
+ }
48
+ writeFileNoFollow(targetDir, destPath, readTemplate(`experience/${template}`, vars));
49
+ created.push(dest);
50
+ }
51
+ for (const keep of ['contracts/.gitkeep', 'fixtures/.gitkeep']) {
52
+ const destPath = path.join(targetDir, keep);
53
+ if (classifyPath(destPath) === 'missing') {
54
+ writeFileNoFollow(targetDir, destPath, '');
55
+ created.push(keep);
56
+ }
57
+ }
58
+ for (const file of created)
59
+ ctx.log(` created ${file}`);
60
+ for (const file of skipped)
61
+ ctx.log(` skipped ${file} (already exists; --force overwrites)`);
62
+ return ctx.succeed({ kind: 'experience', slug, dir: targetDir, created, skipped }, `Scaffolded experience "${slug}". Next: cd ${path.relative(args.cwd, targetDir) || '.'} && install, then "ekanos add <integration>@<version> --from-dir <contracts folder>".`);
63
+ }
64
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/experience/init.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type { CliContext } from '../context';\nimport { invalidStateError, usageError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { classifyPath, writeFileNoFollow } from '../safe-fs';\nimport { readTemplate, toDisplayName } from '../templates';\n\nconst SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/** template (under templates/experience/) -> destination. */\nconst FILES: ReadonlyArray<{ template: string; dest: string }> = [\n { template: 'ekanos.json.tmpl', dest: 'ekanos.json' },\n { template: 'package.json.tmpl', dest: 'package.json' },\n { template: 'tsconfig.json.tmpl', dest: 'tsconfig.json' },\n { template: 'gitignore.tmpl', dest: '.gitignore' },\n { template: 'eslint.config.mjs.tmpl', dest: 'eslint.config.mjs' },\n { template: 'AGENTS.md.tmpl', dest: 'AGENTS.md' },\n { template: 'ekanos.lock.json.tmpl', dest: 'ekanos.lock.json' },\n { template: 'src/experience.ts.tmpl', dest: 'src/experience.ts' },\n {\n template: 'src/widgets/hello-widget.tsx.tmpl',\n dest: 'src/widgets/hello-widget.tsx',\n },\n {\n template: 'src/.ekanos/integrations.d.ts.tmpl',\n dest: 'src/.ekanos/integrations.d.ts',\n },\n];\n\nexport interface InitExperienceArgs {\n slug?: string;\n dir?: string;\n cwd: string;\n force: boolean;\n}\n\n/** `ekanos init --kind experience`: scaffold a project with no integrations yet. */\nexport function runInitExperience(\n ctx: CliContext,\n args: InitExperienceArgs,\n): ExitCode {\n const slug = args.slug;\n if (!slug || !SLUG_PATTERN.test(slug)) {\n throw usageError(\n slug ? `Invalid slug \"${slug}\".` : 'init requires a slug.',\n 'Pass kebab-case: \"ekanos init --kind experience --slug content-engine\".',\n );\n }\n const targetDir = path.resolve(args.cwd, args.dir ?? slug);\n if (fs.existsSync(targetDir) && fs.lstatSync(targetDir).isSymbolicLink()) {\n throw invalidStateError(\n `The target directory ${targetDir} is a symlink.`,\n 'Scaffold into the real directory.',\n );\n }\n fs.mkdirSync(targetDir, { recursive: true });\n\n const vars = { SLUG: slug, DISPLAY_NAME: toDisplayName(slug) };\n const created: string[] = [];\n const skipped: string[] = [];\n for (const { template, dest } of FILES) {\n const destPath = path.join(targetDir, dest);\n const kind = classifyPath(destPath);\n if (kind !== 'missing' && kind !== 'symlink' && !args.force) {\n skipped.push(dest);\n continue;\n }\n writeFileNoFollow(\n targetDir,\n destPath,\n readTemplate(`experience/${template}`, vars),\n );\n created.push(dest);\n }\n for (const keep of ['contracts/.gitkeep', 'fixtures/.gitkeep']) {\n const destPath = path.join(targetDir, keep);\n if (classifyPath(destPath) === 'missing') {\n writeFileNoFollow(targetDir, destPath, '');\n created.push(keep);\n }\n }\n\n for (const file of created) ctx.log(` created ${file}`);\n for (const file of skipped)\n ctx.log(` skipped ${file} (already exists; --force overwrites)`);\n\n return ctx.succeed(\n { kind: 'experience', slug, dir: targetDir, created, skipped },\n `Scaffolded experience \"${slug}\". Next: cd ${path.relative(args.cwd, targetDir) || '.'} && install, then \"ekanos add <integration>@<version> --from-dir <contracts folder>\".`,\n );\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { type Contract } from '@ekanos/integration-schema';
2
+ import type { ExperienceProject } from './project.js';
3
+ export declare const GENERATED_BANNER: string;
4
+ export interface IntegrationContract {
5
+ integration: string;
6
+ contract: Contract;
7
+ /** The `kind/name` keys the experience declared for this integration in `integrations[].operations`. */
8
+ operations: readonly string[];
9
+ }
10
+ /**
11
+ * The `declare module` file that gives `useOperation` / `operation()` /
12
+ * `step.tool` their types. Only operations the experience declared appear, so
13
+ * calling any other is a compile error as well as a runner refusal.
14
+ */
15
+ export declare function generateIntegrationsDts(integrations: readonly IntegrationContract[]): string;
16
+ /** `kind/name` keys of an integration's declared operations (canonical ids stripped). */
17
+ export declare function declaredOperationKeys(integration: ExperienceProject['integrations'][number]): string[];
@@ -0,0 +1,51 @@
1
+ import { operationKey } from '@ekanos/integration-schema';
2
+ import { toTs } from './json-schema-to-ts.js';
3
+ export const GENERATED_BANNER = '// GENERATED by `ekanos codegen` from the pinned contracts. Do not edit; commit it.\n' +
4
+ '// Run `ekanos add <integration>@<version>` or `ekanos sync` to change the integrations.\n';
5
+ /**
6
+ * The `declare module` file that gives `useOperation` / `operation()` /
7
+ * `step.tool` their types. Only operations the experience declared appear, so
8
+ * calling any other is a compile error as well as a runner refusal.
9
+ */
10
+ export function generateIntegrationsDts(integrations) {
11
+ const sorted = [...integrations].sort((a, b) => a.integration < b.integration ? -1 : 1);
12
+ const blocks = sorted.map(({ integration, contract, operations }) => {
13
+ const wanted = new Set(operations);
14
+ const ops = contract.operations
15
+ .filter((op) => wanted.has(`${op.kind}/${op.name}`))
16
+ .sort((a, b) => (`${a.kind}/${a.name}` < `${b.kind}/${b.name}` ? -1 : 1))
17
+ .map((op) => {
18
+ const input = toTs(op.input, ' ');
19
+ const output = op.opaque.output
20
+ ? 'unknown'
21
+ : toTs(op.output, ' ');
22
+ return (` ${JSON.stringify(`${op.kind}/${op.name}`)}: {\n` +
23
+ ` effect: ${JSON.stringify(op.effect)};\n` +
24
+ ` input: ${input};\n` +
25
+ ` output: ${output};\n` +
26
+ ' };');
27
+ });
28
+ return (` ${JSON.stringify(integration).replace(/^"|"$/g, (q) => q)}: {\n` +
29
+ ` version: ${JSON.stringify(contract.compatLine)};\n` +
30
+ ' ops: {\n' +
31
+ `${ops.join('\n')}\n` +
32
+ ' };\n' +
33
+ ' };');
34
+ });
35
+ if (blocks.length === 0) {
36
+ return `${GENERATED_BANNER}export {};\n`;
37
+ }
38
+ return (`${GENERATED_BANNER}export {};\n\n` +
39
+ "declare module '@ekanos/sdk/experience' {\n" +
40
+ ' interface IntegrationRegistry {\n' +
41
+ `${blocks.join('\n')}\n` +
42
+ ' }\n' +
43
+ '}\n');
44
+ }
45
+ /** `kind/name` keys of an integration's declared operations (canonical ids stripped). */
46
+ export function declaredOperationKeys(integration) {
47
+ return integration.operations
48
+ .map((id) => operationKey(id))
49
+ .filter((key) => key !== null);
50
+ }
51
+ //# sourceMappingURL=integrations-codegen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrations-codegen.js","sourceRoot":"","sources":["../../src/experience/integrations-codegen.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Contract, operationKey } from '@ekanos/integration-schema';\n\nimport { toTs } from './json-schema-to-ts';\nimport type { ExperienceProject } from './project';\n\nexport const GENERATED_BANNER =\n '// GENERATED by `ekanos codegen` from the pinned contracts. Do not edit; commit it.\\n' +\n '// Run `ekanos add <integration>@<version>` or `ekanos sync` to change the integrations.\\n';\n\nexport interface IntegrationContract {\n integration: string;\n contract: Contract;\n /** The `kind/name` keys the experience declared for this integration in `integrations[].operations`. */\n operations: readonly string[];\n}\n\n/**\n * The `declare module` file that gives `useOperation` / `operation()` /\n * `step.tool` their types. Only operations the experience declared appear, so\n * calling any other is a compile error as well as a runner refusal.\n */\nexport function generateIntegrationsDts(\n integrations: readonly IntegrationContract[],\n): string {\n const sorted = [...integrations].sort((a, b) =>\n a.integration < b.integration ? -1 : 1,\n );\n const blocks = sorted.map(({ integration, contract, operations }) => {\n const wanted = new Set(operations);\n const ops = contract.operations\n .filter((op) => wanted.has(`${op.kind}/${op.name}`))\n .sort((a, b) => (`${a.kind}/${a.name}` < `${b.kind}/${b.name}` ? -1 : 1))\n .map((op) => {\n const input = toTs(op.input, ' ');\n const output = op.opaque.output\n ? 'unknown'\n : toTs(op.output, ' ');\n\n return (\n ` ${JSON.stringify(`${op.kind}/${op.name}`)}: {\\n` +\n ` effect: ${JSON.stringify(op.effect)};\\n` +\n ` input: ${input};\\n` +\n ` output: ${output};\\n` +\n ' };'\n );\n });\n\n return (\n ` ${JSON.stringify(integration).replace(/^\"|\"$/g, (q) => q)}: {\\n` +\n ` version: ${JSON.stringify(contract.compatLine)};\\n` +\n ' ops: {\\n' +\n `${ops.join('\\n')}\\n` +\n ' };\\n' +\n ' };'\n );\n });\n\n if (blocks.length === 0) {\n return `${GENERATED_BANNER}export {};\\n`;\n }\n\n return (\n `${GENERATED_BANNER}export {};\\n\\n` +\n \"declare module '@ekanos/sdk/experience' {\\n\" +\n ' interface IntegrationRegistry {\\n' +\n `${blocks.join('\\n')}\\n` +\n ' }\\n' +\n '}\\n'\n );\n}\n\n/** `kind/name` keys of an integration's declared operations (canonical ids stripped). */\nexport function declaredOperationKeys(\n integration: ExperienceProject['integrations'][number],\n): string[] {\n return integration.operations\n .map((id) => operationKey(id))\n .filter((key): key is NonNullable<typeof key> => key !== null);\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function toTs(schema: unknown, indent?: string): string;
@@ -0,0 +1,90 @@
1
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
+ const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
3
+ function key(name) {
4
+ return IDENT.test(name) ? name : JSON.stringify(name);
5
+ }
6
+ function literal(value) {
7
+ var _a;
8
+ return (_a = JSON.stringify(value)) !== null && _a !== void 0 ? _a : 'unknown';
9
+ }
10
+ function forType(type, schema, indent) {
11
+ switch (type) {
12
+ case 'string':
13
+ return 'string';
14
+ case 'number':
15
+ case 'integer':
16
+ return 'number';
17
+ case 'boolean':
18
+ return 'boolean';
19
+ case 'null':
20
+ return 'null';
21
+ case 'array': {
22
+ const items = isRecord(schema.items)
23
+ ? toTs(schema.items, indent)
24
+ : 'unknown';
25
+ return items.includes(' | ') || items.includes('&')
26
+ ? `Array<${items}>`
27
+ : `${items}[]`;
28
+ }
29
+ case 'object':
30
+ return objectType(schema, indent);
31
+ default:
32
+ return 'unknown';
33
+ }
34
+ }
35
+ function objectType(schema, indent) {
36
+ const properties = isRecord(schema.properties) ? schema.properties : {};
37
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
38
+ const names = Object.keys(properties);
39
+ const extra = schema.additionalProperties;
40
+ if (names.length === 0) {
41
+ if (isRecord(extra))
42
+ return `Record<string, ${toTs(extra, indent)}>`;
43
+ if (extra === true)
44
+ return 'Record<string, unknown>';
45
+ return 'Record<string, never>';
46
+ }
47
+ const inner = `${indent} `;
48
+ const lines = names.map((name) => {
49
+ const value = properties[name];
50
+ const type = isRecord(value) ? toTs(value, inner) : 'unknown';
51
+ return `${inner}${key(name)}${required.has(name) ? '' : '?'}: ${type};`;
52
+ });
53
+ if (isRecord(extra)) {
54
+ lines.push(`${inner}[key: string]: ${toTs(extra, inner)};`);
55
+ }
56
+ else if (extra === true) {
57
+ lines.push(`${inner}[key: string]: unknown;`);
58
+ }
59
+ return `{\n${lines.join('\n')}\n${indent}}`;
60
+ }
61
+ export function toTs(schema, indent = '') {
62
+ var _a;
63
+ if (schema === true || schema === undefined)
64
+ return 'unknown';
65
+ if (schema === false)
66
+ return 'never';
67
+ if (!isRecord(schema))
68
+ return 'unknown';
69
+ if ('const' in schema)
70
+ return literal(schema.const);
71
+ if (Array.isArray(schema.enum)) {
72
+ return schema.enum.map(literal).join(' | ') || 'never';
73
+ }
74
+ const alternatives = (_a = schema.anyOf) !== null && _a !== void 0 ? _a : schema.oneOf;
75
+ if (Array.isArray(alternatives)) {
76
+ const parts = alternatives.map((alt) => toTs(alt, indent));
77
+ return [...new Set(parts)].join(' | ') || 'never';
78
+ }
79
+ if (Array.isArray(schema.type)) {
80
+ const parts = schema.type.map((type) => forType(type, schema, indent));
81
+ return [...new Set(parts)].join(' | ');
82
+ }
83
+ if (typeof schema.type === 'string') {
84
+ return forType(schema.type, schema, indent);
85
+ }
86
+ if (isRecord(schema.properties))
87
+ return objectType(schema, indent);
88
+ return 'unknown';
89
+ }
90
+ //# sourceMappingURL=json-schema-to-ts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-schema-to-ts.js","sourceRoot":"","sources":["../../src/experience/json-schema-to-ts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * JSON Schema (the contract dialect) to a TypeScript type expression.\n *\n * Deliberately small: contracts only carry the subset `validateAgainstSchema`\n * understands (type, properties/required/additionalProperties, items, enum,\n * const, anyOf/oneOf, nullable type arrays). Anything else degrades to\n * `unknown`, never to a wrong type. Output is deterministic (property order is\n * the schema's own), so regenerating is a byte-for-byte no-op.\n */\ntype Schema = Record<string, unknown>;\n\nconst isRecord = (value: unknown): value is Schema =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\nfunction key(name: string): string {\n return IDENT.test(name) ? name : JSON.stringify(name);\n}\n\nfunction literal(value: unknown): string {\n return JSON.stringify(value) ?? 'unknown';\n}\n\nfunction forType(type: string, schema: Schema, indent: string): string {\n switch (type) {\n case 'string':\n return 'string';\n case 'number':\n case 'integer':\n return 'number';\n case 'boolean':\n return 'boolean';\n case 'null':\n return 'null';\n case 'array': {\n const items = isRecord(schema.items)\n ? toTs(schema.items, indent)\n : 'unknown';\n\n return items.includes(' | ') || items.includes('&')\n ? `Array<${items}>`\n : `${items}[]`;\n }\n case 'object':\n return objectType(schema, indent);\n default:\n return 'unknown';\n }\n}\n\nfunction objectType(schema: Schema, indent: string): string {\n const properties = isRecord(schema.properties) ? schema.properties : {};\n const required = new Set(\n Array.isArray(schema.required) ? (schema.required as string[]) : [],\n );\n const names = Object.keys(properties);\n const extra = schema.additionalProperties;\n\n if (names.length === 0) {\n if (isRecord(extra)) return `Record<string, ${toTs(extra, indent)}>`;\n if (extra === true) return 'Record<string, unknown>';\n\n return 'Record<string, never>';\n }\n\n const inner = `${indent} `;\n const lines = names.map((name) => {\n const value = properties[name];\n const type = isRecord(value) ? toTs(value, inner) : 'unknown';\n\n return `${inner}${key(name)}${required.has(name) ? '' : '?'}: ${type};`;\n });\n if (isRecord(extra)) {\n lines.push(`${inner}[key: string]: ${toTs(extra, inner)};`);\n } else if (extra === true) {\n lines.push(`${inner}[key: string]: unknown;`);\n }\n\n return `{\\n${lines.join('\\n')}\\n${indent}}`;\n}\n\nexport function toTs(schema: unknown, indent = ''): string {\n if (schema === true || schema === undefined) return 'unknown';\n if (schema === false) return 'never';\n if (!isRecord(schema)) return 'unknown';\n\n if ('const' in schema) return literal(schema.const);\n\n if (Array.isArray(schema.enum)) {\n return schema.enum.map(literal).join(' | ') || 'never';\n }\n\n const alternatives = schema.anyOf ?? schema.oneOf;\n if (Array.isArray(alternatives)) {\n const parts = alternatives.map((alt) => toTs(alt, indent));\n\n return [...new Set(parts)].join(' | ') || 'never';\n }\n\n if (Array.isArray(schema.type)) {\n const parts = (schema.type as string[]).map((type) =>\n forType(type, schema, indent),\n );\n\n return [...new Set(parts)].join(' | ');\n }\n\n if (typeof schema.type === 'string') {\n return forType(schema.type, schema, indent);\n }\n\n if (isRecord(schema.properties)) return objectType(schema, indent);\n\n return 'unknown';\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type { LoadedExperienceModule } from './definition-shape.js';
2
+ import type { ExperienceProject } from './project.js';
3
+ export interface LoadExperienceOptions {
4
+ /**
5
+ * Root of an `@ekanos/sdk` checkout to bundle instead of importing
6
+ * `@ekanos/sdk` from node_modules. Needed only when the SDK's exports point
7
+ * at TypeScript source (the monorepo's own tests); a published SDK ships
8
+ * built output. Also settable as EKANOS_INLINE_SDK.
9
+ */
10
+ inlineSdk?: string;
11
+ }
12
+ export type LoadedExperience = ({
13
+ ok: true;
14
+ } & LoadedExperienceModule) | {
15
+ ok: false;
16
+ message: string;
17
+ };
18
+ /**
19
+ * Evaluate the experience entry in Node to read its definition, including the
20
+ * workflow templates' `build()` output. Components are loaded but never
21
+ * rendered, so a widget needs no browser to be inspected.
22
+ */
23
+ export declare function loadExperience(project: ExperienceProject, options?: LoadExperienceOptions): Promise<LoadedExperience>;