@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,225 @@
1
+ import { RepoManifestError, describeAvailability, discoverRepo, packageContaining, planRepoOrder, } from '@ekanos/integration-schema/repo';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { buildManifest, escalateForPublish, prepareIntegrationSubmission, readProjectVersion, submitPrepared, } from '../commands/publish.js';
5
+ import { CliError, gateFailedError, preconditionError, usageError, validationError, } from '../errors.js';
6
+ import { EXPERIENCE_NOT_SUPPORTED, gateExperience, packExperience, } from '../experience/publish.js';
7
+ import { packProject } from '../pack.js';
8
+ import { loadProject } from '../project.js';
9
+ import { shortHost } from '../ui.js';
10
+ import { collectAllFindings } from '../validate-findings.js';
11
+ export const PUBLISH_ARTIFACT_DIR = path.join('.ekanos', 'publish');
12
+ const AVAILABILITY_NOTE = 'availability is set by a super admin today; the host does not take it at submission';
13
+ function loadRepo(root) {
14
+ try {
15
+ return discoverRepo(root);
16
+ }
17
+ catch (error) {
18
+ if (error instanceof RepoManifestError) {
19
+ throw validationError(error.message, 'Fix the root ekanos.json: { "kind": "repo", "packages": ["integrations/*", "experiences/*"] }.');
20
+ }
21
+ throw error;
22
+ }
23
+ }
24
+ function select(repo, args) {
25
+ var _a;
26
+ var _b;
27
+ if (args.all && args.names.length > 0) {
28
+ throw usageError('Pass package names or --all, not both.', 'e.g. "ekanos publish acme-crm" or "ekanos publish --all".');
29
+ }
30
+ if (args.names.length > 0)
31
+ return [...args.names];
32
+ if (args.all)
33
+ return [];
34
+ const here = packageContaining(repo, args.cwd);
35
+ if (here)
36
+ return [here.name];
37
+ throw usageError('This is a repo: say what to publish.', `Name packages ("ekanos publish ${(_b = (_a = repo.packages[0]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '<package>'}"), pass --all, or run it from inside a package. Packages: ${repo.packages.map((p) => p.name).join(', ') || '(none yet; "ekanos new integration <slug>")'}.`);
38
+ }
39
+ function artifactName(pkg) {
40
+ return `${pkg.name}-${pkg.version}`;
41
+ }
42
+ function renderTable(ctx, rows) {
43
+ return ctx.ui.table(['package', 'kind', 'version', 'availability', 'artifact', 'status'], rows.map((row) => { var _a; return [
44
+ row.name,
45
+ row.kind,
46
+ row.version,
47
+ row.availability,
48
+ (_a = row.artifact) !== null && _a !== void 0 ? _a : '-',
49
+ row.detail ? `${row.status}: ${row.detail}` : row.status,
50
+ ]; }));
51
+ }
52
+ /** Gate one integration and, when asked, build its artifact. No network. */
53
+ async function checkIntegration(pkg, repoRoot, build) {
54
+ const row = {
55
+ name: pkg.name,
56
+ kind: 'integration',
57
+ version: pkg.version,
58
+ availability: describeAvailability(pkg.availability),
59
+ artifact: null,
60
+ status: 'ready',
61
+ };
62
+ try {
63
+ readProjectVersion(pkg.dir);
64
+ const loaded = loadProject(pkg.dir);
65
+ const findings = escalateForPublish(await collectAllFindings(loaded));
66
+ const errors = findings.filter((f) => f.severity === 'error');
67
+ if (errors.length > 0) {
68
+ return Object.assign(Object.assign({}, row), { status: 'gate failed', detail: `${errors.length} error${errors.length === 1 ? '' : 's'}; run "ekanos validate" in ${pkg.relDir}`, findings });
69
+ }
70
+ if (!build)
71
+ return row;
72
+ const packed = packProject(loaded.projectDir);
73
+ const dir = path.join(repoRoot, PUBLISH_ARTIFACT_DIR, artifactName(pkg));
74
+ fs.rmSync(dir, { recursive: true, force: true });
75
+ fs.mkdirSync(dir, { recursive: true });
76
+ fs.writeFileSync(path.join(dir, `${artifactName(pkg)}.tgz`), packed.archive);
77
+ fs.writeFileSync(path.join(dir, 'manifest.json'), `${buildManifest(loaded, packed.files)}\n`);
78
+ const contract = path.join(pkg.dir, 'contract.json');
79
+ if (fs.existsSync(contract)) {
80
+ fs.copyFileSync(contract, path.join(dir, 'contract.json'));
81
+ }
82
+ return Object.assign(Object.assign(Object.assign({}, row), { status: 'built', artifact: path.relative(repoRoot, dir).split(path.sep).join('/') }), (findings.length > 0 ? { findings } : {}));
83
+ }
84
+ catch (error) {
85
+ if (error instanceof CliError) {
86
+ return Object.assign(Object.assign({}, row), { status: 'refused', detail: error.message });
87
+ }
88
+ throw error;
89
+ }
90
+ }
91
+ /** Gate one experience and, when asked, build and pack it. No network. */
92
+ async function checkExperience(pkg, repoRoot, build, env) {
93
+ const row = {
94
+ name: pkg.name,
95
+ kind: 'experience',
96
+ version: pkg.version,
97
+ availability: describeAvailability(pkg.availability),
98
+ artifact: null,
99
+ status: 'ready',
100
+ };
101
+ try {
102
+ const gate = await gateExperience({ dir: pkg.dir, dryRun: true, env });
103
+ const errors = gate.result.findings.filter((f) => f.severity === 'error');
104
+ if (errors.length > 0) {
105
+ return Object.assign(Object.assign({}, row), { status: 'gate failed', detail: `${errors.length} error${errors.length === 1 ? '' : 's'}; run "ekanos check" in ${pkg.relDir}`, findings: gate.result.findings });
106
+ }
107
+ if (!build)
108
+ return row;
109
+ const dir = path.join(repoRoot, PUBLISH_ARTIFACT_DIR, artifactName(pkg));
110
+ fs.rmSync(dir, { recursive: true, force: true });
111
+ const packed = await packExperience(gate, dir);
112
+ const manifest = path.join(pkg.dir, 'manifest.json');
113
+ if (fs.existsSync(manifest)) {
114
+ fs.copyFileSync(manifest, path.join(dir, 'manifest.json'));
115
+ }
116
+ return Object.assign(Object.assign({}, row), { version: packed.version, status: 'built', artifact: path.relative(repoRoot, dir).split(path.sep).join('/') });
117
+ }
118
+ catch (error) {
119
+ if (error instanceof CliError) {
120
+ return Object.assign(Object.assign({}, row), { status: 'refused', detail: error.message });
121
+ }
122
+ throw error;
123
+ }
124
+ }
125
+ /**
126
+ * `ekanos publish [name...] | --all [--dry-run]` in a repo.
127
+ *
128
+ * The plan is the selected packages, integrations before the experiences that
129
+ * use them (a same-repo integration is not an override, so it is ordered
130
+ * first, never refused). Every package passes its own gate before anything is
131
+ * sent. `--dry-run` also builds each artifact under `.ekanos/publish/` and
132
+ * touches no network. A real run submits the integrations in order through the
133
+ * existing path; the host cannot admit an experience yet, so selected
134
+ * experiences end the run with that refusal.
135
+ */
136
+ export async function runRepoPublish(ctx, args) {
137
+ const repo = loadRepo(args.repoRoot);
138
+ const names = select(repo, args);
139
+ const plan = planRepoOrder(repo, names);
140
+ if (plan.unknown.length > 0) {
141
+ throw usageError(`No package named ${plan.unknown.map((n) => `"${n}"`).join(', ')} in this repo.`, `Packages: ${repo.packages.map((p) => p.name).join(', ') || '(none)'}.`);
142
+ }
143
+ if (args.all && repo.problems.length > 0) {
144
+ throw validationError(`${repo.problems.length} package${repo.problems.length === 1 ? ' is' : 's are'} unreadable: ${repo.problems.map((p) => `${p.where} (${p.message})`).join('; ')}`, 'Fix or remove them, then re-run "ekanos publish --all".');
145
+ }
146
+ if (plan.order.length === 0) {
147
+ throw preconditionError('This repo has no packages yet.', 'Add one with "ekanos new integration <slug>" or "ekanos new experience <slug>".');
148
+ }
149
+ const dryRun = args.publish.dryRun === true;
150
+ const warnings = plan.unselectedDependencies.map((dep) => `${dep.experience} uses ${dep.integration}, which is in this repo but not in this publish: publish ${dep.integration} first, or make sure it is already published at a compatible version.`);
151
+ const notes = plan.order.flatMap((pkg) => pkg.kind === 'integration' && pkg.availability
152
+ ? [
153
+ `${pkg.name}: ${describeAvailability(pkg.availability)} - ${AVAILABILITY_NOTE}.`,
154
+ ]
155
+ : []);
156
+ if (!ctx.jsonMode) {
157
+ ctx.log(`${dryRun ? 'Dry run: ' : ''}publishing ${plan.order.length} package${plan.order.length === 1 ? '' : 's'} in this order:`);
158
+ plan.order.forEach((pkg, index) => ctx.log(` ${index + 1}. ${pkg.name} (${pkg.kind} ${pkg.version}, ${describeAvailability(pkg.availability)})`));
159
+ for (const warning of warnings)
160
+ ctx.warn(` warning: ${warning}`);
161
+ }
162
+ // Gate (and, on a dry run, build) every package before anything is sent.
163
+ const rows = [];
164
+ for (const pkg of plan.order) {
165
+ rows.push(pkg.kind === 'integration'
166
+ ? await checkIntegration(pkg, repo.root, dryRun)
167
+ : await checkExperience(pkg, repo.root, dryRun, args.publish.env));
168
+ }
169
+ const data = (extra = {}) => (Object.assign({ repo: repo.root, dryRun, order: plan.order.map((p) => p.name), warnings,
170
+ notes, packages: rows }, extra));
171
+ const summary = (title) => [title, '', renderTable(ctx, rows), ...notes.map((n) => `note: ${n}`)].join('\n');
172
+ const failed = rows.filter((row) => row.status === 'gate failed' || row.status === 'refused');
173
+ if (failed.length > 0) {
174
+ if (!ctx.jsonMode)
175
+ ctx.log(summary('Nothing was published.'));
176
+ return ctx.fail(gateFailedError(`${failed.length} of ${rows.length} package${rows.length === 1 ? '' : 's'} did not pass: ${failed.map((r) => r.name).join(', ')}.`, 'Fix the packages named above (the status column says what), then re-run "ekanos publish". Nothing was submitted.'), data());
177
+ }
178
+ if (dryRun) {
179
+ return ctx.succeed(data({ published: false }), summary(`Dry run: ${rows.length} package${rows.length === 1 ? '' : 's'} built under ${PUBLISH_ARTIFACT_DIR}. Nothing was published.`));
180
+ }
181
+ // Real publish: integrations in plan order through the existing path.
182
+ let stop = null;
183
+ let host = null;
184
+ for (const [index, pkg] of plan.order.entries()) {
185
+ const row = rows[index];
186
+ if (pkg.kind === 'experience') {
187
+ row.status = 'not supported by the host';
188
+ continue;
189
+ }
190
+ if (stop) {
191
+ row.status = 'not attempted';
192
+ continue;
193
+ }
194
+ try {
195
+ const single = Object.assign(Object.assign({}, args.publish), { dir: pkg.dir });
196
+ const prepared = await prepareIntegrationSubmission(ctx, single);
197
+ host = prepared.env.host;
198
+ const submitted = await submitPrepared(ctx, single, prepared);
199
+ row.status = 'submitted';
200
+ row.receipt = submitted.data;
201
+ if (!ctx.jsonMode)
202
+ ctx.log(submitted.human);
203
+ }
204
+ catch (error) {
205
+ if (!(error instanceof CliError))
206
+ throw error;
207
+ row.status = 'failed';
208
+ row.detail = error.message;
209
+ stop = error;
210
+ }
211
+ }
212
+ const hostNote = host ? ` to ${shortHost(host)}` : '';
213
+ if (stop) {
214
+ if (!ctx.jsonMode)
215
+ ctx.log(summary('Publish stopped.'));
216
+ return ctx.fail(stop, data());
217
+ }
218
+ if (rows.some((row) => row.status === 'not supported by the host')) {
219
+ if (!ctx.jsonMode)
220
+ ctx.log(summary(`Submitted${hostNote}.`));
221
+ return ctx.fail(gateFailedError(EXPERIENCE_NOT_SUPPORTED, 'The integrations in this publish were submitted; run "ekanos publish --dry-run" to build and pack the experiences. The host has no admission path for experiences until it ships one.'), data({ published: true }));
222
+ }
223
+ return ctx.succeed(data({ published: true }), summary(`Submitted ${rows.length} package${rows.length === 1 ? '' : 's'}${hostNote}.`));
224
+ }
225
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sourceRoot":"","sources":["../../src/repo/publish.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport {\n type RepoDiscovery,\n type RepoPackage,\n RepoManifestError,\n describeAvailability,\n discoverRepo,\n packageContaining,\n planRepoOrder,\n} from '@ekanos/integration-schema/repo';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport {\n type PublishArgs,\n buildManifest,\n escalateForPublish,\n prepareIntegrationSubmission,\n readProjectVersion,\n submitPrepared,\n} from '../commands/publish';\nimport type { CliContext } from '../context';\nimport {\n CliError,\n gateFailedError,\n preconditionError,\n usageError,\n validationError,\n} from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n EXPERIENCE_NOT_SUPPORTED,\n gateExperience,\n packExperience,\n} from '../experience/publish';\nimport { packProject } from '../pack';\nimport { loadProject } from '../project';\nimport { shortHost } from '../ui';\nimport { collectAllFindings } from '../validate-findings';\n\nexport const PUBLISH_ARTIFACT_DIR = path.join('.ekanos', 'publish');\n\nexport interface RepoPublishArgs {\n repoRoot: string;\n /** Where the command was run: inside a package it narrows the selection. */\n cwd: string;\n /** Package names given on the command line. */\n names: readonly string[];\n all: boolean;\n /** The single-project publish arguments, reused for every integration. */\n publish: PublishArgs;\n}\n\nexport type PackageStatus =\n | 'ready'\n | 'built'\n | 'gate failed'\n | 'refused'\n | 'submitted'\n | 'failed'\n | 'not attempted'\n | 'not supported by the host';\n\nexport interface PackageRow {\n name: string;\n kind: 'integration' | 'experience';\n version: string;\n availability: string;\n /** Artifact directory relative to the repo root, when one was built. */\n artifact: string | null;\n status: PackageStatus;\n /** Why the status is not a success, one line. */\n detail?: string;\n findings?: Finding[];\n /** Integration submissions: what the host answered. */\n receipt?: Record<string, unknown>;\n}\n\nconst AVAILABILITY_NOTE =\n 'availability is set by a super admin today; the host does not take it at submission';\n\nfunction loadRepo(root: string): RepoDiscovery {\n try {\n return discoverRepo(root);\n } catch (error) {\n if (error instanceof RepoManifestError) {\n throw validationError(\n error.message,\n 'Fix the root ekanos.json: { \"kind\": \"repo\", \"packages\": [\"integrations/*\", \"experiences/*\"] }.',\n );\n }\n throw error;\n }\n}\n\nfunction select(repo: RepoDiscovery, args: RepoPublishArgs): string[] {\n if (args.all && args.names.length > 0) {\n throw usageError(\n 'Pass package names or --all, not both.',\n 'e.g. \"ekanos publish acme-crm\" or \"ekanos publish --all\".',\n );\n }\n if (args.names.length > 0) return [...args.names];\n if (args.all) return [];\n const here = packageContaining(repo, args.cwd);\n if (here) return [here.name];\n throw usageError(\n 'This is a repo: say what to publish.',\n `Name packages (\"ekanos publish ${repo.packages[0]?.name ?? '<package>'}\"), pass --all, or run it from inside a package. Packages: ${repo.packages.map((p) => p.name).join(', ') || '(none yet; \"ekanos new integration <slug>\")'}.`,\n );\n}\n\nfunction artifactName(pkg: RepoPackage): string {\n return `${pkg.name}-${pkg.version}`;\n}\n\nfunction renderTable(ctx: CliContext, rows: readonly PackageRow[]): string {\n return ctx.ui.table(\n ['package', 'kind', 'version', 'availability', 'artifact', 'status'],\n rows.map((row) => [\n row.name,\n row.kind,\n row.version,\n row.availability,\n row.artifact ?? '-',\n row.detail ? `${row.status}: ${row.detail}` : row.status,\n ]),\n );\n}\n\n/** Gate one integration and, when asked, build its artifact. No network. */\nasync function checkIntegration(\n pkg: RepoPackage,\n repoRoot: string,\n build: boolean,\n): Promise<PackageRow> {\n const row: PackageRow = {\n name: pkg.name,\n kind: 'integration',\n version: pkg.version,\n availability: describeAvailability(pkg.availability),\n artifact: null,\n status: 'ready',\n };\n try {\n readProjectVersion(pkg.dir);\n const loaded = loadProject(pkg.dir);\n const findings = escalateForPublish(await collectAllFindings(loaded));\n const errors = findings.filter((f) => f.severity === 'error');\n if (errors.length > 0) {\n return {\n ...row,\n status: 'gate failed',\n detail: `${errors.length} error${errors.length === 1 ? '' : 's'}; run \"ekanos validate\" in ${pkg.relDir}`,\n findings,\n };\n }\n if (!build) return row;\n\n const packed = packProject(loaded.projectDir);\n const dir = path.join(repoRoot, PUBLISH_ARTIFACT_DIR, artifactName(pkg));\n fs.rmSync(dir, { recursive: true, force: true });\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(\n path.join(dir, `${artifactName(pkg)}.tgz`),\n packed.archive,\n );\n fs.writeFileSync(\n path.join(dir, 'manifest.json'),\n `${buildManifest(loaded, packed.files)}\\n`,\n );\n const contract = path.join(pkg.dir, 'contract.json');\n if (fs.existsSync(contract)) {\n fs.copyFileSync(contract, path.join(dir, 'contract.json'));\n }\n\n return {\n ...row,\n status: 'built',\n artifact: path.relative(repoRoot, dir).split(path.sep).join('/'),\n ...(findings.length > 0 ? { findings } : {}),\n };\n } catch (error) {\n if (error instanceof CliError) {\n return { ...row, status: 'refused', detail: error.message };\n }\n throw error;\n }\n}\n\n/** Gate one experience and, when asked, build and pack it. No network. */\nasync function checkExperience(\n pkg: RepoPackage,\n repoRoot: string,\n build: boolean,\n env: Record<string, string | undefined>,\n): Promise<PackageRow> {\n const row: PackageRow = {\n name: pkg.name,\n kind: 'experience',\n version: pkg.version,\n availability: describeAvailability(pkg.availability),\n artifact: null,\n status: 'ready',\n };\n try {\n const gate = await gateExperience({ dir: pkg.dir, dryRun: true, env });\n const errors = gate.result.findings.filter((f) => f.severity === 'error');\n if (errors.length > 0) {\n return {\n ...row,\n status: 'gate failed',\n detail: `${errors.length} error${errors.length === 1 ? '' : 's'}; run \"ekanos check\" in ${pkg.relDir}`,\n findings: gate.result.findings,\n };\n }\n if (!build) return row;\n\n const dir = path.join(repoRoot, PUBLISH_ARTIFACT_DIR, artifactName(pkg));\n fs.rmSync(dir, { recursive: true, force: true });\n const packed = await packExperience(gate, dir);\n const manifest = path.join(pkg.dir, 'manifest.json');\n if (fs.existsSync(manifest)) {\n fs.copyFileSync(manifest, path.join(dir, 'manifest.json'));\n }\n\n return {\n ...row,\n version: packed.version,\n status: 'built',\n artifact: path.relative(repoRoot, dir).split(path.sep).join('/'),\n };\n } catch (error) {\n if (error instanceof CliError) {\n return { ...row, status: 'refused', detail: error.message };\n }\n throw error;\n }\n}\n\n/**\n * `ekanos publish [name...] | --all [--dry-run]` in a repo.\n *\n * The plan is the selected packages, integrations before the experiences that\n * use them (a same-repo integration is not an override, so it is ordered\n * first, never refused). Every package passes its own gate before anything is\n * sent. `--dry-run` also builds each artifact under `.ekanos/publish/` and\n * touches no network. A real run submits the integrations in order through the\n * existing path; the host cannot admit an experience yet, so selected\n * experiences end the run with that refusal.\n */\nexport async function runRepoPublish(\n ctx: CliContext,\n args: RepoPublishArgs,\n): Promise<ExitCode> {\n const repo = loadRepo(args.repoRoot);\n const names = select(repo, args);\n const plan = planRepoOrder(repo, names);\n\n if (plan.unknown.length > 0) {\n throw usageError(\n `No package named ${plan.unknown.map((n) => `\"${n}\"`).join(', ')} in this repo.`,\n `Packages: ${repo.packages.map((p) => p.name).join(', ') || '(none)'}.`,\n );\n }\n if (args.all && repo.problems.length > 0) {\n throw validationError(\n `${repo.problems.length} package${repo.problems.length === 1 ? ' is' : 's are'} unreadable: ${repo.problems.map((p) => `${p.where} (${p.message})`).join('; ')}`,\n 'Fix or remove them, then re-run \"ekanos publish --all\".',\n );\n }\n if (plan.order.length === 0) {\n throw preconditionError(\n 'This repo has no packages yet.',\n 'Add one with \"ekanos new integration <slug>\" or \"ekanos new experience <slug>\".',\n );\n }\n\n const dryRun = args.publish.dryRun === true;\n const warnings = plan.unselectedDependencies.map(\n (dep) =>\n `${dep.experience} uses ${dep.integration}, which is in this repo but not in this publish: publish ${dep.integration} first, or make sure it is already published at a compatible version.`,\n );\n const notes = plan.order.flatMap((pkg) =>\n pkg.kind === 'integration' && pkg.availability\n ? [\n `${pkg.name}: ${describeAvailability(pkg.availability)} - ${AVAILABILITY_NOTE}.`,\n ]\n : [],\n );\n\n if (!ctx.jsonMode) {\n ctx.log(\n `${dryRun ? 'Dry run: ' : ''}publishing ${plan.order.length} package${plan.order.length === 1 ? '' : 's'} in this order:`,\n );\n plan.order.forEach((pkg, index) =>\n ctx.log(\n ` ${index + 1}. ${pkg.name} (${pkg.kind} ${pkg.version}, ${describeAvailability(pkg.availability)})`,\n ),\n );\n for (const warning of warnings) ctx.warn(` warning: ${warning}`);\n }\n\n // Gate (and, on a dry run, build) every package before anything is sent.\n const rows: PackageRow[] = [];\n for (const pkg of plan.order) {\n rows.push(\n pkg.kind === 'integration'\n ? await checkIntegration(pkg, repo.root, dryRun)\n : await checkExperience(pkg, repo.root, dryRun, args.publish.env),\n );\n }\n\n const data = (extra: Record<string, unknown> = {}) => ({\n repo: repo.root,\n dryRun,\n order: plan.order.map((p) => p.name),\n warnings,\n notes,\n packages: rows,\n ...extra,\n });\n const summary = (title: string) =>\n [title, '', renderTable(ctx, rows), ...notes.map((n) => `note: ${n}`)].join(\n '\\n',\n );\n\n const failed = rows.filter(\n (row) => row.status === 'gate failed' || row.status === 'refused',\n );\n if (failed.length > 0) {\n if (!ctx.jsonMode) ctx.log(summary('Nothing was published.'));\n return ctx.fail(\n gateFailedError(\n `${failed.length} of ${rows.length} package${rows.length === 1 ? '' : 's'} did not pass: ${failed.map((r) => r.name).join(', ')}.`,\n 'Fix the packages named above (the status column says what), then re-run \"ekanos publish\". Nothing was submitted.',\n ),\n data(),\n );\n }\n\n if (dryRun) {\n return ctx.succeed(\n data({ published: false }),\n summary(\n `Dry run: ${rows.length} package${rows.length === 1 ? '' : 's'} built under ${PUBLISH_ARTIFACT_DIR}. Nothing was published.`,\n ),\n );\n }\n\n // Real publish: integrations in plan order through the existing path.\n let stop: CliError | null = null;\n let host: string | null = null;\n for (const [index, pkg] of plan.order.entries()) {\n const row = rows[index] as PackageRow;\n if (pkg.kind === 'experience') {\n row.status = 'not supported by the host';\n continue;\n }\n if (stop) {\n row.status = 'not attempted';\n continue;\n }\n try {\n const single: PublishArgs = { ...args.publish, dir: pkg.dir };\n const prepared = await prepareIntegrationSubmission(ctx, single);\n host = prepared.env.host;\n const submitted = await submitPrepared(ctx, single, prepared);\n row.status = 'submitted';\n row.receipt = submitted.data;\n if (!ctx.jsonMode) ctx.log(submitted.human);\n } catch (error) {\n if (!(error instanceof CliError)) throw error;\n row.status = 'failed';\n row.detail = error.message;\n stop = error;\n }\n }\n\n const hostNote = host ? ` to ${shortHost(host)}` : '';\n if (stop) {\n if (!ctx.jsonMode) ctx.log(summary('Publish stopped.'));\n return ctx.fail(stop, data());\n }\n if (rows.some((row) => row.status === 'not supported by the host')) {\n if (!ctx.jsonMode) ctx.log(summary(`Submitted${hostNote}.`));\n return ctx.fail(\n gateFailedError(\n EXPERIENCE_NOT_SUPPORTED,\n 'The integrations in this publish were submitted; run \"ekanos publish --dry-run\" to build and pack the experiences. The host has no admission path for experiences until it ships one.',\n ),\n data({ published: true }),\n );\n }\n\n return ctx.succeed(\n data({ published: true }),\n summary(\n `Submitted ${rows.length} package${rows.length === 1 ? '' : 's'}${hostNote}.`,\n ),\n );\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import type { CliContext } from '../context.js';
2
+ import type { ExitCode } from '../exit-codes.js';
3
+ export declare const REPO_PACKAGE_GLOBS: readonly ['integrations/*', 'experiences/*'];
4
+ /** Where `ekanos new <kind>` puts a package, relative to the repo root. */
5
+ export declare const REPO_PACKAGE_DIR: {
6
+ readonly integration: 'integrations';
7
+ readonly experience: 'experiences';
8
+ };
9
+ export interface InitRepoArgs {
10
+ slug?: string;
11
+ dir?: string;
12
+ cwd: string;
13
+ force: boolean;
14
+ }
15
+ /** `ekanos init --kind repo`: a root that holds many integrations and experiences. */
16
+ export declare function runInitRepo(ctx: CliContext, args: InitRepoArgs): ExitCode;
17
+ export interface NewPackageArgs {
18
+ kind: string | undefined;
19
+ slug: string | undefined;
20
+ cwd: string;
21
+ force: boolean;
22
+ /** Runs the per-kind scaffold; injected by the command to avoid an import cycle with init. */
23
+ scaffold: (args: {
24
+ kind: 'integration' | 'experience';
25
+ slug: string;
26
+ dir: string;
27
+ }) => Promise<ExitCode>;
28
+ }
29
+ /** `ekanos new integration|experience <slug>`: scaffold a package into the right folder of the repo. */
30
+ export declare function runNewPackage(ctx: CliContext, args: NewPackageArgs): Promise<ExitCode>;
@@ -0,0 +1,165 @@
1
+ import { discoverRepo, findRepoRoot, matchesPackageGlob, } from '@ekanos/integration-schema/repo';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { invalidStateError, preconditionError, usageError, validationError, } from '../errors.js';
5
+ import { classifyPath, writeFileNoFollow } from '../safe-fs.js';
6
+ import { readTemplateSource } from '../templates.js';
7
+ const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
8
+ export const REPO_PACKAGE_GLOBS = ['integrations/*', 'experiences/*'];
9
+ /** Where `ekanos new <kind>` puts a package, relative to the repo root. */
10
+ export const REPO_PACKAGE_DIR = {
11
+ integration: 'integrations',
12
+ experience: 'experiences',
13
+ };
14
+ function formatJson(value) {
15
+ return `${JSON.stringify(value, null, 2)}\n`;
16
+ }
17
+ /**
18
+ * What the repo-root harness shell resolves upward into the root's
19
+ * node_modules: Next, React and the @ekanos packages. The pins come from the
20
+ * integration template, so there is one source of truth.
21
+ */
22
+ const HARNESS_DEV_DEPS = [
23
+ '@ekanos/cli',
24
+ '@ekanos/harness',
25
+ '@tailwindcss/postcss',
26
+ '@tanstack/react-query',
27
+ '@types/react',
28
+ '@types/react-dom',
29
+ 'next',
30
+ 'next-intl',
31
+ 'react',
32
+ 'react-dom',
33
+ 'tailwindcss',
34
+ 'typescript',
35
+ ];
36
+ const HARNESS_DEPS = ['@ekanos/sdk', '@ekanos/ui'];
37
+ /** Next type-checks its own config and offers to install this when it is missing. */
38
+ const NODE_TYPES_PIN = '^22.10.0';
39
+ function templatePins(section, names) {
40
+ const template = JSON.parse(readTemplateSource('package.json.tmpl'));
41
+ return Object.fromEntries(names.flatMap((name) => {
42
+ const range = template[section][name];
43
+ return range ? [[name, range]] : [];
44
+ }));
45
+ }
46
+ function harnessDevDependencies() {
47
+ return Object.fromEntries(Object.entries(Object.assign(Object.assign({}, templatePins('devDependencies', HARNESS_DEV_DEPS)), { '@types/node': NODE_TYPES_PIN })).sort(([left], [right]) => left.localeCompare(right)));
48
+ }
49
+ const REPO_GITIGNORE = `# Dependencies
50
+ node_modules/
51
+
52
+ # Build output
53
+ dist/
54
+ *.tsbuildinfo
55
+
56
+ # Ekanos dev harness and publish artifacts (generated)
57
+ .ekanos/
58
+
59
+ # Per-developer integration overrides and credentials
60
+ ekanos.local.json
61
+
62
+ # Logs
63
+ *.log
64
+ `;
65
+ function repoPnpmWorkspace() {
66
+ return `${readTemplateSource('pnpm-workspace.yaml.tmpl').trimEnd()}
67
+
68
+ packages:
69
+ ${REPO_PACKAGE_GLOBS.map((glob) => ` - '${glob}'`).join('\n')}
70
+ `;
71
+ }
72
+ /** `ekanos init --kind repo`: a root that holds many integrations and experiences. */
73
+ export function runInitRepo(ctx, args) {
74
+ var _a;
75
+ const slug = args.slug;
76
+ if (slug !== undefined && !SLUG_PATTERN.test(slug)) {
77
+ throw usageError(`Invalid slug "${slug}".`, 'Use kebab-case, e.g. "ekanos init --kind repo --slug acme-platform".');
78
+ }
79
+ if (!slug && args.dir === undefined) {
80
+ throw usageError('init --kind repo needs a name or a directory.', 'Pass "--slug <name>" (creates ./<name>) or "--dir ." to scaffold in place.');
81
+ }
82
+ const targetDir = path.resolve(args.cwd, (_a = args.dir) !== null && _a !== void 0 ? _a : slug);
83
+ if (fs.existsSync(targetDir) && fs.lstatSync(targetDir).isSymbolicLink()) {
84
+ throw invalidStateError(`The target directory ${targetDir} is a symlink.`, 'Scaffold into the real directory.');
85
+ }
86
+ if (findRepoRoot(targetDir) !== null && !args.force) {
87
+ throw invalidStateError(`${targetDir} is already inside an Ekanos repo.`, 'Add packages with "ekanos new integration <slug>" or "ekanos new experience <slug>".');
88
+ }
89
+ fs.mkdirSync(targetDir, { recursive: true });
90
+ const name = slug !== null && slug !== void 0 ? slug : path.basename(targetDir);
91
+ const files = [
92
+ {
93
+ dest: 'ekanos.json',
94
+ content: formatJson({
95
+ kind: 'repo',
96
+ packages: [...REPO_PACKAGE_GLOBS],
97
+ }),
98
+ },
99
+ {
100
+ dest: 'package.json',
101
+ content: formatJson({
102
+ name: SLUG_PATTERN.test(name) ? name : 'ekanos-repo',
103
+ version: '0.0.0',
104
+ private: true,
105
+ type: 'module',
106
+ description: 'Ekanos integrations and experiences in one repo.',
107
+ scripts: { dev: 'ekanos dev --start' },
108
+ dependencies: templatePins('dependencies', HARNESS_DEPS),
109
+ devDependencies: harnessDevDependencies(),
110
+ }),
111
+ },
112
+ { dest: 'pnpm-workspace.yaml', content: repoPnpmWorkspace() },
113
+ { dest: '.gitignore', content: REPO_GITIGNORE },
114
+ { dest: 'integrations/.gitkeep', content: '' },
115
+ { dest: 'experiences/.gitkeep', content: '' },
116
+ ];
117
+ const created = [];
118
+ const skipped = [];
119
+ for (const { dest, content } of files) {
120
+ const destPath = path.join(targetDir, dest);
121
+ const kind = classifyPath(destPath);
122
+ if (kind !== 'missing' && kind !== 'symlink' && !args.force) {
123
+ skipped.push(dest);
124
+ continue;
125
+ }
126
+ writeFileNoFollow(targetDir, destPath, content);
127
+ created.push(dest);
128
+ }
129
+ for (const file of created)
130
+ ctx.log(` created ${file}`);
131
+ for (const file of skipped) {
132
+ ctx.log(` skipped ${file} (already exists; --force overwrites)`);
133
+ }
134
+ const rel = path.relative(args.cwd, targetDir) || '.';
135
+ return ctx.succeed({ kind: 'repo', name, dir: targetDir, created, skipped }, `Scaffolded repo "${name}". Next: ${rel === '.' ? '' : `cd ${rel} && `}ekanos new integration <slug> and ekanos new experience <slug>.`);
136
+ }
137
+ /** `ekanos new integration|experience <slug>`: scaffold a package into the right folder of the repo. */
138
+ export async function runNewPackage(ctx, args) {
139
+ const kind = args.kind;
140
+ if (kind !== 'integration' && kind !== 'experience') {
141
+ throw usageError(kind ? `Unknown package kind "${kind}".` : 'new needs a package kind.', 'Use "ekanos new integration <slug>" or "ekanos new experience <slug>".');
142
+ }
143
+ const slug = args.slug;
144
+ if (!slug || !SLUG_PATTERN.test(slug)) {
145
+ throw usageError(slug ? `Invalid slug "${slug}".` : 'new needs a slug.', `Pass kebab-case, e.g. "ekanos new ${kind} acme-${kind === 'integration' ? 'crm' : 'queue'}".`);
146
+ }
147
+ const root = findRepoRoot(args.cwd);
148
+ if (!root) {
149
+ throw preconditionError('This directory is not inside an Ekanos repo.', 'Run "ekanos init --kind repo --slug <name>" first, or run this from inside one.');
150
+ }
151
+ const repo = discoverRepo(root);
152
+ const clash = repo.packages.find((pkg) => pkg.name === slug);
153
+ if (clash) {
154
+ throw validationError(`"${slug}" is already a ${clash.kind} in this repo (${clash.relDir}).`, 'Pick a different slug; a package is addressed by its slug.');
155
+ }
156
+ const dir = path.join(root, REPO_PACKAGE_DIR[kind], slug);
157
+ const globs = repo.manifest.packages;
158
+ const rel = path.relative(root, dir).split(path.sep).join('/');
159
+ const covered = globs.some((glob) => matchesPackageGlob(rel, glob));
160
+ if (!covered) {
161
+ throw validationError(`${rel} is not matched by the repo's "packages" globs (${globs.join(', ')}).`, 'Add a glob that covers it to the root ekanos.json, then re-run.');
162
+ }
163
+ return args.scaffold({ kind, slug, dir });
164
+ }
165
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/repo/scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n discoverRepo,\n findRepoRoot,\n matchesPackageGlob,\n} from '@ekanos/integration-schema/repo';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type { CliContext } from '../context';\nimport {\n invalidStateError,\n preconditionError,\n usageError,\n validationError,\n} from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { classifyPath, writeFileNoFollow } from '../safe-fs';\nimport { readTemplateSource } from '../templates';\n\nconst SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport const REPO_PACKAGE_GLOBS = ['integrations/*', 'experiences/*'] as const;\n\n/** Where `ekanos new <kind>` puts a package, relative to the repo root. */\nexport const REPO_PACKAGE_DIR = {\n integration: 'integrations',\n experience: 'experiences',\n} as const;\n\nfunction formatJson(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\n/**\n * What the repo-root harness shell resolves upward into the root's\n * node_modules: Next, React and the @ekanos packages. The pins come from the\n * integration template, so there is one source of truth.\n */\nconst HARNESS_DEV_DEPS = [\n '@ekanos/cli',\n '@ekanos/harness',\n '@tailwindcss/postcss',\n '@tanstack/react-query',\n '@types/react',\n '@types/react-dom',\n 'next',\n 'next-intl',\n 'react',\n 'react-dom',\n 'tailwindcss',\n 'typescript',\n] as const;\n\nconst HARNESS_DEPS = ['@ekanos/sdk', '@ekanos/ui'] as const;\n\n/** Next type-checks its own config and offers to install this when it is missing. */\nconst NODE_TYPES_PIN = '^22.10.0';\n\nfunction templatePins(\n section: 'dependencies' | 'devDependencies',\n names: readonly string[],\n): Record<string, string> {\n const template = JSON.parse(\n readTemplateSource('package.json.tmpl'),\n ) as Record<'dependencies' | 'devDependencies', Record<string, string>>;\n\n return Object.fromEntries(\n names.flatMap((name) => {\n const range = template[section][name];\n return range ? [[name, range] as const] : [];\n }),\n );\n}\n\nfunction harnessDevDependencies(): Record<string, string> {\n return Object.fromEntries(\n Object.entries({\n ...templatePins('devDependencies', HARNESS_DEV_DEPS),\n '@types/node': NODE_TYPES_PIN,\n }).sort(([left], [right]) => left.localeCompare(right)),\n );\n}\n\nconst REPO_GITIGNORE = `# Dependencies\nnode_modules/\n\n# Build output\ndist/\n*.tsbuildinfo\n\n# Ekanos dev harness and publish artifacts (generated)\n.ekanos/\n\n# Per-developer integration overrides and credentials\nekanos.local.json\n\n# Logs\n*.log\n`;\n\nfunction repoPnpmWorkspace(): string {\n return `${readTemplateSource('pnpm-workspace.yaml.tmpl').trimEnd()}\n\npackages:\n${REPO_PACKAGE_GLOBS.map((glob) => ` - '${glob}'`).join('\\n')}\n`;\n}\n\nexport interface InitRepoArgs {\n slug?: string;\n dir?: string;\n cwd: string;\n force: boolean;\n}\n\n/** `ekanos init --kind repo`: a root that holds many integrations and experiences. */\nexport function runInitRepo(ctx: CliContext, args: InitRepoArgs): ExitCode {\n const slug = args.slug;\n if (slug !== undefined && !SLUG_PATTERN.test(slug)) {\n throw usageError(\n `Invalid slug \"${slug}\".`,\n 'Use kebab-case, e.g. \"ekanos init --kind repo --slug acme-platform\".',\n );\n }\n if (!slug && args.dir === undefined) {\n throw usageError(\n 'init --kind repo needs a name or a directory.',\n 'Pass \"--slug <name>\" (creates ./<name>) or \"--dir .\" to scaffold in place.',\n );\n }\n\n const targetDir = path.resolve(args.cwd, args.dir ?? (slug as string));\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 if (findRepoRoot(targetDir) !== null && !args.force) {\n throw invalidStateError(\n `${targetDir} is already inside an Ekanos repo.`,\n 'Add packages with \"ekanos new integration <slug>\" or \"ekanos new experience <slug>\".',\n );\n }\n fs.mkdirSync(targetDir, { recursive: true });\n\n const name = slug ?? path.basename(targetDir);\n const files: Array<{ dest: string; content: string }> = [\n {\n dest: 'ekanos.json',\n content: formatJson({\n kind: 'repo',\n packages: [...REPO_PACKAGE_GLOBS],\n }),\n },\n {\n dest: 'package.json',\n content: formatJson({\n name: SLUG_PATTERN.test(name) ? name : 'ekanos-repo',\n version: '0.0.0',\n private: true,\n type: 'module',\n description: 'Ekanos integrations and experiences in one repo.',\n scripts: { dev: 'ekanos dev --start' },\n dependencies: templatePins('dependencies', HARNESS_DEPS),\n devDependencies: harnessDevDependencies(),\n }),\n },\n { dest: 'pnpm-workspace.yaml', content: repoPnpmWorkspace() },\n { dest: '.gitignore', content: REPO_GITIGNORE },\n { dest: 'integrations/.gitkeep', content: '' },\n { dest: 'experiences/.gitkeep', content: '' },\n ];\n\n const created: string[] = [];\n const skipped: string[] = [];\n for (const { dest, content } 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(targetDir, destPath, content);\n created.push(dest);\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\n const rel = path.relative(args.cwd, targetDir) || '.';\n\n return ctx.succeed(\n { kind: 'repo', name, dir: targetDir, created, skipped },\n `Scaffolded repo \"${name}\". Next: ${rel === '.' ? '' : `cd ${rel} && `}ekanos new integration <slug> and ekanos new experience <slug>.`,\n );\n}\n\nexport interface NewPackageArgs {\n kind: string | undefined;\n slug: string | undefined;\n cwd: string;\n force: boolean;\n /** Runs the per-kind scaffold; injected by the command to avoid an import cycle with init. */\n scaffold: (args: {\n kind: 'integration' | 'experience';\n slug: string;\n dir: string;\n }) => Promise<ExitCode>;\n}\n\n/** `ekanos new integration|experience <slug>`: scaffold a package into the right folder of the repo. */\nexport async function runNewPackage(\n ctx: CliContext,\n args: NewPackageArgs,\n): Promise<ExitCode> {\n const kind = args.kind;\n if (kind !== 'integration' && kind !== 'experience') {\n throw usageError(\n kind ? `Unknown package kind \"${kind}\".` : 'new needs a package kind.',\n 'Use \"ekanos new integration <slug>\" or \"ekanos new experience <slug>\".',\n );\n }\n const slug = args.slug;\n if (!slug || !SLUG_PATTERN.test(slug)) {\n throw usageError(\n slug ? `Invalid slug \"${slug}\".` : 'new needs a slug.',\n `Pass kebab-case, e.g. \"ekanos new ${kind} acme-${kind === 'integration' ? 'crm' : 'queue'}\".`,\n );\n }\n\n const root = findRepoRoot(args.cwd);\n if (!root) {\n throw preconditionError(\n 'This directory is not inside an Ekanos repo.',\n 'Run \"ekanos init --kind repo --slug <name>\" first, or run this from inside one.',\n );\n }\n\n const repo = discoverRepo(root);\n const clash = repo.packages.find((pkg) => pkg.name === slug);\n if (clash) {\n throw validationError(\n `\"${slug}\" is already a ${clash.kind} in this repo (${clash.relDir}).`,\n 'Pick a different slug; a package is addressed by its slug.',\n );\n }\n\n const dir = path.join(root, REPO_PACKAGE_DIR[kind], slug);\n const globs = repo.manifest.packages;\n const rel = path.relative(root, dir).split(path.sep).join('/');\n const covered = globs.some((glob) => matchesPackageGlob(rel, glob));\n if (!covered) {\n throw validationError(\n `${rel} is not matched by the repo's \"packages\" globs (${globs.join(', ')}).`,\n 'Add a glob that covers it to the root ekanos.json, then re-run.',\n );\n }\n\n return args.scaffold({ kind, slug, dir });\n}\n"]}
package/dist/runs-api.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { AgentApprovalDecidedSchema, AgentApprovalListSchema, AgentRunCancelledSchema, AgentRunDetailSchema, AgentRunListSchema, AgentRunStartedSchema, } from '@ekanos/integration-schema';
2
2
  import { request } from './auth/ekanos-api.js';
3
3
  import { authRequiredError, forbiddenError, invalidStateError, networkError, notFoundError, usageError, } from './errors.js';
4
+ import { cli } from './invocation.js';
4
5
  export function listRuns(host, accessToken, target) {
5
6
  return call(host, accessToken, AgentRunListSchema, {
6
7
  method: 'GET',
@@ -68,10 +69,14 @@ async function call(host, accessToken, schema, init) {
68
69
  }
69
70
  if (response.status === 403) {
70
71
  throw forbiddenError(message !== null && message !== void 0 ? message : 'Not permitted on this Source.', code === 'SOURCE_ADMIN_REQUIRED'
71
- ? "Only a Source admin runs its teams' agents and decides their approvals."
72
+ ? `Only a Source admin runs its teams' agents. Approvals are also decided by a seat holder with that authority on the team: "${cli()} approvals list --team <slug>" shows yours.`
72
73
  : code === 'NOT_A_TEAM_APPROVER'
73
- ? "A team's own agent's approvals are the team's: a member who manages its agents decides them, in the app."
74
- : 'Run "ekanos sources" to list the sources you hold a seat on.');
74
+ ? `Someone with that authority on the team decides it: a member who manages its agents (or reviews them, for a reply draft), or a reviewer the Source admin named ("${cli()} reviewers list").`
75
+ : code === 'PERSONAL_AGENT_CREATOR_ONLY'
76
+ ? `A personal agent acts with its creator's own connections: only they start or cancel its runs and decide its approvals, signed in as themselves ("${cli()} login").`
77
+ : code === 'NOT_A_TEAM_MEMBER'
78
+ ? 'Add yourself to the team first ("ekanos teams members add <team> --self"), then run this again.'
79
+ : 'Run "ekanos sources" to list the sources you hold a seat on.');
75
80
  }
76
81
  if (response.status === 404) {
77
82
  throw notFoundError(message !== null && message !== void 0 ? message : 'Not found.', code === 'TEAM_NOT_FOUND'
@@ -1 +1 @@
1
- {"version":3,"file":"runs-api.js","sourceRoot":"","sources":["../src/runs-api.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AgentApprovalDecideRequest,\n type AgentApprovalDecided,\n AgentApprovalDecidedSchema,\n type AgentApprovalList,\n AgentApprovalListSchema,\n type AgentRunCancelled,\n AgentRunCancelledSchema,\n type AgentRunDetail,\n AgentRunDetailSchema,\n type AgentRunList,\n AgentRunListSchema,\n type AgentRunStartRequest,\n type AgentRunStarted,\n AgentRunStartedSchema,\n} from '@ekanos/integration-schema';\nimport type { z } from 'zod';\n\nimport { request } from './auth/ekanos-api';\nimport {\n authRequiredError,\n forbiddenError,\n invalidStateError,\n networkError,\n notFoundError,\n usageError,\n} from './errors';\n\n/**\n * The CLI's client for a team's agent runs and approvals — `ekanos runs`,\n * `ekanos agents test` and `ekanos approvals`.\n *\n * GET /api/partner/teams/<team>/runs?source=<slug>[&agent=<key>] → AgentRunList\n * POST /api/partner/teams/<team>/runs?source=<slug> → AgentRunStarted\n * GET /api/partner/teams/<team>/runs/<id>?source=<slug>[&after=] → AgentRunDetail\n * POST /api/partner/teams/<team>/runs/<id>/cancel?source=<slug> → AgentRunCancelled\n * GET /api/partner/teams/<team>/approvals?source=<slug> → AgentApprovalList\n * POST /api/partner/teams/<team>/approvals/<id>?source=<slug> → AgentApprovalDecided\n */\n\nexport type RunsApiResult<T> =\n | { status: 'ok'; data: T }\n | { status: 'unauthorized' };\n\ninterface TeamTarget {\n source: string;\n team: string;\n}\n\nexport function listRuns(\n host: string,\n accessToken: string,\n target: TeamTarget & { agent?: string; status?: string; since?: string },\n): Promise<RunsApiResult<AgentRunList>> {\n return call(host, accessToken, AgentRunListSchema, {\n method: 'GET',\n path: `${teamPath(target)}/runs`,\n query: {\n source: target.source,\n ...(target.agent ? { agent: target.agent } : {}),\n ...(target.status ? { status: target.status } : {}),\n ...(target.since ? { since: target.since } : {}),\n },\n });\n}\n\nexport function startRun(\n host: string,\n accessToken: string,\n target: TeamTarget,\n body: AgentRunStartRequest,\n): Promise<RunsApiResult<AgentRunStarted>> {\n return call(host, accessToken, AgentRunStartedSchema, {\n method: 'POST',\n path: `${teamPath(target)}/runs`,\n query: { source: target.source },\n body,\n });\n}\n\nexport function showRun(\n host: string,\n accessToken: string,\n target: TeamTarget & { runId: string; after?: number },\n): Promise<RunsApiResult<AgentRunDetail>> {\n return call(host, accessToken, AgentRunDetailSchema, {\n method: 'GET',\n path: `${teamPath(target)}/runs/${encodeURIComponent(target.runId)}`,\n query: {\n source: target.source,\n ...(target.after ? { after: String(target.after) } : {}),\n },\n });\n}\n\nexport function cancelRun(\n host: string,\n accessToken: string,\n target: TeamTarget & { runId: string },\n): Promise<RunsApiResult<AgentRunCancelled>> {\n return call(host, accessToken, AgentRunCancelledSchema, {\n method: 'POST',\n path: `${teamPath(target)}/runs/${encodeURIComponent(target.runId)}/cancel`,\n query: { source: target.source },\n });\n}\n\nexport function listApprovals(\n host: string,\n accessToken: string,\n target: TeamTarget,\n): Promise<RunsApiResult<AgentApprovalList>> {\n return call(host, accessToken, AgentApprovalListSchema, {\n method: 'GET',\n path: `${teamPath(target)}/approvals`,\n query: { source: target.source },\n });\n}\n\nexport function decideApproval(\n host: string,\n accessToken: string,\n target: TeamTarget & { approvalId: string },\n body: AgentApprovalDecideRequest,\n): Promise<RunsApiResult<AgentApprovalDecided>> {\n return call(host, accessToken, AgentApprovalDecidedSchema, {\n method: 'POST',\n path: `${teamPath(target)}/approvals/${encodeURIComponent(target.approvalId)}`,\n query: { source: target.source },\n body,\n });\n}\n\nfunction teamPath(target: TeamTarget): string {\n return `/api/partner/teams/${encodeURIComponent(target.team)}`;\n}\n\nasync function call<T>(\n host: string,\n accessToken: string,\n schema: z.ZodType<T>,\n init: {\n method: 'GET' | 'POST';\n path: string;\n query: Record<string, string>;\n body?: unknown;\n },\n): Promise<RunsApiResult<T>> {\n const response = await request(\n `${host}${init.path}?${new URLSearchParams(init.query)}`,\n {\n method: init.method,\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n ...(init.body === undefined\n ? {}\n : { 'content-type': 'application/json' }),\n },\n body: init.body === undefined ? undefined : JSON.stringify(init.body),\n },\n );\n\n if (\n response.status === 401 ||\n (response.status >= 300 && response.status < 400)\n ) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const code = typeof body.code === 'string' ? body.code : null;\n const message =\n typeof body.error === 'string' && body.error ? body.error : null;\n\n if (response.status === 403 && code === 'MFA_REQUIRED') {\n throw authRequiredError(\n message ?? `${host} requires multi-factor authentication.`,\n 'Complete MFA in the browser, then run \"ekanos login\" again.',\n );\n }\n if (response.status === 403) {\n throw forbiddenError(\n message ?? 'Not permitted on this Source.',\n code === 'SOURCE_ADMIN_REQUIRED'\n ? \"Only a Source admin runs its teams' agents and decides their approvals.\"\n : code === 'NOT_A_TEAM_APPROVER'\n ? \"A team's own agent's approvals are the team's: a member who manages its agents decides them, in the app.\"\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 404) {\n throw notFoundError(\n message ?? 'Not found.',\n code === 'TEAM_NOT_FOUND'\n ? 'Run \"ekanos teams list\" to see the teams on this Source.'\n : code === 'AGENT_NOT_FOUND' || code === 'TEMPLATE_NOT_FOUND'\n ? 'Run \"ekanos agents list\" for the Source\\'s agents; a team runs one once it is turned on there.'\n : code === 'RUN_NOT_FOUND'\n ? 'Run \"ekanos runs list --team <slug>\" for the team\\'s runs.'\n : code === 'NOT_FOUND'\n ? 'Run \"ekanos approvals list --team <slug>\" for what is waiting.'\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 409 || response.status === 502) {\n throw invalidStateError(\n message ?? 'The host refused the request in its current state.',\n code === 'ALREADY_DECIDED'\n ? 'Run \"ekanos approvals list --team <slug>\" for what is still waiting.'\n : code === 'RUN_FINISHED'\n ? 'Nothing to stop. \"ekanos runs show <run-id> --team <slug>\" shows how it ended.'\n : code === 'CANCEL_FAILED'\n ? 'Nothing changed. Retry \"ekanos runs cancel\" shortly.'\n : code === 'RESUME_FAILED'\n ? 'The decision is saved; the run picks it up shortly. Check with \"ekanos runs show <run-id>\".'\n : code === 'AGENTS_EXPERIENCE_OFF'\n ? 'Ask Ekanos to turn on the Agents Experience for this Source: only a super-admin can (\"ekanos sources products\" shows it as off).'\n : 'Run \"ekanos teams agents list <team>\": the agent may be paused or still need a connection.',\n );\n }\n if (response.status === 400) {\n throw usageError(\n message ?? 'The host refused the request.',\n code === 'NOT_EDITABLE'\n ? 'Only a reply draft can be edited; approve or reject it instead.'\n : 'See \"ekanos runs --help\" / \"ekanos approvals --help\".',\n );\n }\n if (!response.ok) {\n throw networkError(\n `${host}${init.path} responded ${response.status}.`,\n response.status >= 500\n ? 'The Ekanos deployment returned a server error. Retry shortly.'\n : 'Confirm the host runs a build that includes the runs routes.',\n );\n }\n\n const parsed = schema.safeParse(body.data);\n if (!parsed.success) {\n throw networkError(\n `${host}${init.path} answered in a shape this CLI does not read.`,\n 'Run \"ekanos upgrade\" — the CLI and the host disagree on the runs contract.',\n );\n }\n return { status: 'ok', data: parsed.data };\n}\n\nasync function readOptionalJson(\n response: Response,\n): Promise<Record<string, unknown>> {\n try {\n const parsed: unknown = await response.json();\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n"]}
1
+ {"version":3,"file":"runs-api.js","sourceRoot":"","sources":["../src/runs-api.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AgentApprovalDecideRequest,\n type AgentApprovalDecided,\n AgentApprovalDecidedSchema,\n type AgentApprovalList,\n AgentApprovalListSchema,\n type AgentRunCancelled,\n AgentRunCancelledSchema,\n type AgentRunDetail,\n AgentRunDetailSchema,\n type AgentRunList,\n AgentRunListSchema,\n type AgentRunStartRequest,\n type AgentRunStarted,\n AgentRunStartedSchema,\n} from '@ekanos/integration-schema';\nimport type { z } from 'zod';\n\nimport { request } from './auth/ekanos-api';\nimport {\n authRequiredError,\n forbiddenError,\n invalidStateError,\n networkError,\n notFoundError,\n usageError,\n} from './errors';\nimport { cli } from './invocation';\n\n/**\n * The CLI's client for a team's agent runs and approvals — `ekanos runs`,\n * `ekanos agents test` and `ekanos approvals`.\n *\n * GET /api/partner/teams/<team>/runs?source=<slug>[&agent=<key>] → AgentRunList\n * POST /api/partner/teams/<team>/runs?source=<slug> → AgentRunStarted\n * GET /api/partner/teams/<team>/runs/<id>?source=<slug>[&after=] → AgentRunDetail\n * POST /api/partner/teams/<team>/runs/<id>/cancel?source=<slug> → AgentRunCancelled\n * GET /api/partner/teams/<team>/approvals?source=<slug> → AgentApprovalList\n * POST /api/partner/teams/<team>/approvals/<id>?source=<slug> → AgentApprovalDecided\n */\n\nexport type RunsApiResult<T> =\n | { status: 'ok'; data: T }\n | { status: 'unauthorized' };\n\ninterface TeamTarget {\n source: string;\n team: string;\n}\n\nexport function listRuns(\n host: string,\n accessToken: string,\n target: TeamTarget & { agent?: string; status?: string; since?: string },\n): Promise<RunsApiResult<AgentRunList>> {\n return call(host, accessToken, AgentRunListSchema, {\n method: 'GET',\n path: `${teamPath(target)}/runs`,\n query: {\n source: target.source,\n ...(target.agent ? { agent: target.agent } : {}),\n ...(target.status ? { status: target.status } : {}),\n ...(target.since ? { since: target.since } : {}),\n },\n });\n}\n\nexport function startRun(\n host: string,\n accessToken: string,\n target: TeamTarget,\n body: AgentRunStartRequest,\n): Promise<RunsApiResult<AgentRunStarted>> {\n return call(host, accessToken, AgentRunStartedSchema, {\n method: 'POST',\n path: `${teamPath(target)}/runs`,\n query: { source: target.source },\n body,\n });\n}\n\nexport function showRun(\n host: string,\n accessToken: string,\n target: TeamTarget & { runId: string; after?: number },\n): Promise<RunsApiResult<AgentRunDetail>> {\n return call(host, accessToken, AgentRunDetailSchema, {\n method: 'GET',\n path: `${teamPath(target)}/runs/${encodeURIComponent(target.runId)}`,\n query: {\n source: target.source,\n ...(target.after ? { after: String(target.after) } : {}),\n },\n });\n}\n\nexport function cancelRun(\n host: string,\n accessToken: string,\n target: TeamTarget & { runId: string },\n): Promise<RunsApiResult<AgentRunCancelled>> {\n return call(host, accessToken, AgentRunCancelledSchema, {\n method: 'POST',\n path: `${teamPath(target)}/runs/${encodeURIComponent(target.runId)}/cancel`,\n query: { source: target.source },\n });\n}\n\nexport function listApprovals(\n host: string,\n accessToken: string,\n target: TeamTarget,\n): Promise<RunsApiResult<AgentApprovalList>> {\n return call(host, accessToken, AgentApprovalListSchema, {\n method: 'GET',\n path: `${teamPath(target)}/approvals`,\n query: { source: target.source },\n });\n}\n\nexport function decideApproval(\n host: string,\n accessToken: string,\n target: TeamTarget & { approvalId: string },\n body: AgentApprovalDecideRequest,\n): Promise<RunsApiResult<AgentApprovalDecided>> {\n return call(host, accessToken, AgentApprovalDecidedSchema, {\n method: 'POST',\n path: `${teamPath(target)}/approvals/${encodeURIComponent(target.approvalId)}`,\n query: { source: target.source },\n body,\n });\n}\n\nfunction teamPath(target: TeamTarget): string {\n return `/api/partner/teams/${encodeURIComponent(target.team)}`;\n}\n\nasync function call<T>(\n host: string,\n accessToken: string,\n schema: z.ZodType<T>,\n init: {\n method: 'GET' | 'POST';\n path: string;\n query: Record<string, string>;\n body?: unknown;\n },\n): Promise<RunsApiResult<T>> {\n const response = await request(\n `${host}${init.path}?${new URLSearchParams(init.query)}`,\n {\n method: init.method,\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n ...(init.body === undefined\n ? {}\n : { 'content-type': 'application/json' }),\n },\n body: init.body === undefined ? undefined : JSON.stringify(init.body),\n },\n );\n\n if (\n response.status === 401 ||\n (response.status >= 300 && response.status < 400)\n ) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const code = typeof body.code === 'string' ? body.code : null;\n const message =\n typeof body.error === 'string' && body.error ? body.error : null;\n\n if (response.status === 403 && code === 'MFA_REQUIRED') {\n throw authRequiredError(\n message ?? `${host} requires multi-factor authentication.`,\n 'Complete MFA in the browser, then run \"ekanos login\" again.',\n );\n }\n if (response.status === 403) {\n throw forbiddenError(\n message ?? 'Not permitted on this Source.',\n code === 'SOURCE_ADMIN_REQUIRED'\n ? `Only a Source admin runs its teams' agents. Approvals are also decided by a seat holder with that authority on the team: \"${cli()} approvals list --team <slug>\" shows yours.`\n : code === 'NOT_A_TEAM_APPROVER'\n ? `Someone with that authority on the team decides it: a member who manages its agents (or reviews them, for a reply draft), or a reviewer the Source admin named (\"${cli()} reviewers list\").`\n : code === 'PERSONAL_AGENT_CREATOR_ONLY'\n ? `A personal agent acts with its creator's own connections: only they start or cancel its runs and decide its approvals, signed in as themselves (\"${cli()} login\").`\n : code === 'NOT_A_TEAM_MEMBER'\n ? 'Add yourself to the team first (\"ekanos teams members add <team> --self\"), then run this again.'\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 404) {\n throw notFoundError(\n message ?? 'Not found.',\n code === 'TEAM_NOT_FOUND'\n ? 'Run \"ekanos teams list\" to see the teams on this Source.'\n : code === 'AGENT_NOT_FOUND' || code === 'TEMPLATE_NOT_FOUND'\n ? 'Run \"ekanos agents list\" for the Source\\'s agents; a team runs one once it is turned on there.'\n : code === 'RUN_NOT_FOUND'\n ? 'Run \"ekanos runs list --team <slug>\" for the team\\'s runs.'\n : code === 'NOT_FOUND'\n ? 'Run \"ekanos approvals list --team <slug>\" for what is waiting.'\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 409 || response.status === 502) {\n throw invalidStateError(\n message ?? 'The host refused the request in its current state.',\n code === 'ALREADY_DECIDED'\n ? 'Run \"ekanos approvals list --team <slug>\" for what is still waiting.'\n : code === 'RUN_FINISHED'\n ? 'Nothing to stop. \"ekanos runs show <run-id> --team <slug>\" shows how it ended.'\n : code === 'CANCEL_FAILED'\n ? 'Nothing changed. Retry \"ekanos runs cancel\" shortly.'\n : code === 'RESUME_FAILED'\n ? 'The decision is saved; the run picks it up shortly. Check with \"ekanos runs show <run-id>\".'\n : code === 'AGENTS_EXPERIENCE_OFF'\n ? 'Ask Ekanos to turn on the Agents Experience for this Source: only a super-admin can (\"ekanos sources products\" shows it as off).'\n : 'Run \"ekanos teams agents list <team>\": the agent may be paused or still need a connection.',\n );\n }\n if (response.status === 400) {\n throw usageError(\n message ?? 'The host refused the request.',\n code === 'NOT_EDITABLE'\n ? 'Only a reply draft can be edited; approve or reject it instead.'\n : 'See \"ekanos runs --help\" / \"ekanos approvals --help\".',\n );\n }\n if (!response.ok) {\n throw networkError(\n `${host}${init.path} responded ${response.status}.`,\n response.status >= 500\n ? 'The Ekanos deployment returned a server error. Retry shortly.'\n : 'Confirm the host runs a build that includes the runs routes.',\n );\n }\n\n const parsed = schema.safeParse(body.data);\n if (!parsed.success) {\n throw networkError(\n `${host}${init.path} answered in a shape this CLI does not read.`,\n 'Run \"ekanos upgrade\" — the CLI and the host disagree on the runs contract.',\n );\n }\n return { status: 'ok', data: parsed.data };\n}\n\nasync function readOptionalJson(\n response: Response,\n): Promise<Record<string, unknown>> {\n try {\n const parsed: unknown = await response.json();\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n"]}