@ekanos/cli 0.16.0 → 0.17.1

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 (192) hide show
  1. package/README.md +62 -4
  2. package/dist/commands/add.d.ts +2 -0
  3. package/dist/commands/add.js +118 -0
  4. package/dist/commands/add.js.map +1 -0
  5. package/dist/commands/audiences.d.ts +31 -0
  6. package/dist/commands/audiences.js +239 -0
  7. package/dist/commands/audiences.js.map +1 -0
  8. package/dist/commands/build.d.ts +2 -0
  9. package/dist/commands/build.js +41 -0
  10. package/dist/commands/build.js.map +1 -0
  11. package/dist/commands/check.d.ts +2 -0
  12. package/dist/commands/check.js +43 -0
  13. package/dist/commands/check.js.map +1 -0
  14. package/dist/commands/codegen.d.ts +2 -0
  15. package/dist/commands/codegen.js +49 -0
  16. package/dist/commands/codegen.js.map +1 -0
  17. package/dist/commands/contract.d.ts +52 -0
  18. package/dist/commands/contract.js +300 -0
  19. package/dist/commands/contract.js.map +1 -0
  20. package/dist/commands/dev.d.ts +7 -0
  21. package/dist/commands/dev.js +186 -12
  22. package/dist/commands/dev.js.map +1 -1
  23. package/dist/commands/index-experience.d.ts +31 -0
  24. package/dist/commands/index-experience.js +21 -0
  25. package/dist/commands/index-experience.js.map +1 -0
  26. package/dist/commands/init.d.ts +7 -0
  27. package/dist/commands/init.js +12 -0
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/new.d.ts +2 -0
  30. package/dist/commands/new.js +39 -0
  31. package/dist/commands/new.js.map +1 -0
  32. package/dist/commands/publish.d.ts +59 -0
  33. package/dist/commands/publish.js +118 -65
  34. package/dist/commands/publish.js.map +1 -1
  35. package/dist/commands/scope-notice.d.ts +6 -0
  36. package/dist/commands/scope-notice.js +15 -0
  37. package/dist/commands/scope-notice.js.map +1 -1
  38. package/dist/commands/sources.js +2 -0
  39. package/dist/commands/sources.js.map +1 -1
  40. package/dist/commands/sync.d.ts +2 -0
  41. package/dist/commands/sync.js +150 -0
  42. package/dist/commands/sync.js.map +1 -0
  43. package/dist/commands/teams-audience.d.ts +12 -0
  44. package/dist/commands/teams-audience.js +144 -0
  45. package/dist/commands/teams-audience.js.map +1 -0
  46. package/dist/commands/teams-scope.js +4 -0
  47. package/dist/commands/teams-scope.js.map +1 -1
  48. package/dist/commands/teams.d.ts +1 -1
  49. package/dist/commands/teams.js +6 -1
  50. package/dist/commands/teams.js.map +1 -1
  51. package/dist/compatibility.d.ts +10 -1
  52. package/dist/compatibility.js +10 -1
  53. package/dist/compatibility.js.map +1 -1
  54. package/dist/dev-experience.d.ts +125 -0
  55. package/dist/dev-experience.js +128 -0
  56. package/dist/dev-experience.js.map +1 -0
  57. package/dist/experience/boundary.d.ts +12 -0
  58. package/dist/experience/boundary.js +260 -0
  59. package/dist/experience/boundary.js.map +1 -0
  60. package/dist/experience/build.d.ts +16 -0
  61. package/dist/experience/build.js +90 -0
  62. package/dist/experience/build.js.map +1 -0
  63. package/dist/experience/checks.d.ts +29 -0
  64. package/dist/experience/checks.js +133 -0
  65. package/dist/experience/checks.js.map +1 -0
  66. package/dist/experience/contract-sources.d.ts +36 -0
  67. package/dist/experience/contract-sources.js +111 -0
  68. package/dist/experience/contract-sources.js.map +1 -0
  69. package/dist/experience/definition-checks.d.ts +26 -0
  70. package/dist/experience/definition-checks.js +201 -0
  71. package/dist/experience/definition-checks.js.map +1 -0
  72. package/dist/experience/definition-shape.d.ts +60 -0
  73. package/dist/experience/definition-shape.js +2 -0
  74. package/dist/experience/definition-shape.js.map +1 -0
  75. package/dist/experience/init.d.ts +10 -0
  76. package/dist/experience/init.js +64 -0
  77. package/dist/experience/init.js.map +1 -0
  78. package/dist/experience/integrations-codegen.d.ts +17 -0
  79. package/dist/experience/integrations-codegen.js +51 -0
  80. package/dist/experience/integrations-codegen.js.map +1 -0
  81. package/dist/experience/json-schema-to-ts.d.ts +1 -0
  82. package/dist/experience/json-schema-to-ts.js +90 -0
  83. package/dist/experience/json-schema-to-ts.js.map +1 -0
  84. package/dist/experience/load-experience.d.ts +23 -0
  85. package/dist/experience/load-experience.js +115 -0
  86. package/dist/experience/load-experience.js.map +1 -0
  87. package/dist/experience/overrides.d.ts +8 -0
  88. package/dist/experience/overrides.js +21 -0
  89. package/dist/experience/overrides.js.map +1 -0
  90. package/dist/experience/pin-integration.d.ts +8 -0
  91. package/dist/experience/pin-integration.js +17 -0
  92. package/dist/experience/pin-integration.js.map +1 -0
  93. package/dist/experience/pin-ops.d.ts +12 -0
  94. package/dist/experience/pin-ops.js +36 -0
  95. package/dist/experience/pin-ops.js.map +1 -0
  96. package/dist/experience/pins.d.ts +35 -0
  97. package/dist/experience/pins.js +91 -0
  98. package/dist/experience/pins.js.map +1 -0
  99. package/dist/experience/project.d.ts +52 -0
  100. package/dist/experience/project.js +149 -0
  101. package/dist/experience/project.js.map +1 -0
  102. package/dist/experience/publish.d.ts +51 -0
  103. package/dist/experience/publish.js +144 -0
  104. package/dist/experience/publish.js.map +1 -0
  105. package/dist/experience/regenerate.d.ts +10 -0
  106. package/dist/experience/regenerate.js +33 -0
  107. package/dist/experience/regenerate.js.map +1 -0
  108. package/dist/experience/report.d.ts +9 -0
  109. package/dist/experience/report.js +23 -0
  110. package/dist/experience/report.js.map +1 -0
  111. package/dist/harness-scaffold.d.ts +52 -18
  112. package/dist/harness-scaffold.js +189 -9
  113. package/dist/harness-scaffold.js.map +1 -1
  114. package/dist/index.js +139 -7
  115. package/dist/index.js.map +1 -1
  116. package/dist/pack.js +5 -0
  117. package/dist/pack.js.map +1 -1
  118. package/dist/project-checks.d.ts +19 -0
  119. package/dist/project-checks.js +93 -0
  120. package/dist/project-checks.js.map +1 -1
  121. package/dist/project.d.ts +14 -0
  122. package/dist/project.js +10 -0
  123. package/dist/project.js.map +1 -1
  124. package/dist/repo/publish.d.ts +42 -0
  125. package/dist/repo/publish.js +225 -0
  126. package/dist/repo/publish.js.map +1 -0
  127. package/dist/repo/scaffold.d.ts +30 -0
  128. package/dist/repo/scaffold.js +165 -0
  129. package/dist/repo/scaffold.js.map +1 -0
  130. package/dist/teams-api.d.ts +37 -1
  131. package/dist/teams-api.js +62 -1
  132. package/dist/teams-api.js.map +1 -1
  133. package/dist/validate-findings.js +3 -1
  134. package/dist/validate-findings.js.map +1 -1
  135. package/package.json +2 -2
  136. package/templates/ekanos.json.tmpl +1 -0
  137. package/templates/experience/AGENTS.md.tmpl +22 -0
  138. package/templates/experience/ekanos.json.tmpl +7 -0
  139. package/templates/experience/ekanos.lock.json.tmpl +4 -0
  140. package/templates/experience/eslint.config.mjs.tmpl +25 -0
  141. package/templates/experience/gitignore.tmpl +17 -0
  142. package/templates/experience/package.json.tmpl +41 -0
  143. package/templates/experience/src/.ekanos/integrations.d.ts.tmpl +3 -0
  144. package/templates/experience/src/experience.ts.tmpl +30 -0
  145. package/templates/experience/src/widgets/hello-widget.tsx.tmpl +40 -0
  146. package/templates/experience/tsconfig.json.tmpl +20 -0
  147. package/templates/harness/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +9 -0
  148. package/templates/harness/app/[slug]/activation/page.tsx.tmpl +1 -1
  149. package/templates/harness/app/[slug]/page.tsx.tmpl +1 -1
  150. package/templates/harness/app/[slug]/tile/page.tsx.tmpl +1 -1
  151. package/templates/harness/app/[slug]/triggers/page.tsx.tmpl +1 -1
  152. package/templates/harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl +1 -1
  153. package/templates/harness/app/[slug]/widgets/page.tsx.tmpl +1 -1
  154. package/templates/harness/app/experience/layout.tsx.tmpl +17 -0
  155. package/templates/harness/app/experience/page.tsx.tmpl +1 -0
  156. package/templates/harness/app/experience/pages/[id]/page.tsx.tmpl +1 -0
  157. package/templates/harness/app/experience/review-queue/page.tsx.tmpl +1 -0
  158. package/templates/harness/app/experience/runs/page.tsx.tmpl +1 -0
  159. package/templates/harness/app/experience/widgets/[id]/page.tsx.tmpl +1 -0
  160. package/templates/harness/app/experience/widgets/page.tsx.tmpl +1 -0
  161. package/templates/harness/app/experience/workflows/[id]/page.tsx.tmpl +1 -0
  162. package/templates/harness/app/experience/workflows/page.tsx.tmpl +1 -0
  163. package/templates/harness/app/integrations/[slug]/connect/page.tsx.tmpl +1 -0
  164. package/templates/harness/app/integrations/[slug]/events/page.tsx.tmpl +1 -0
  165. package/templates/harness/app/integrations/[slug]/health/page.tsx.tmpl +1 -0
  166. package/templates/harness/app/integrations/[slug]/operations/[kind]/[name]/page.tsx.tmpl +1 -0
  167. package/templates/harness/app/integrations/[slug]/operations/page.tsx.tmpl +1 -0
  168. package/templates/harness/app/integrations/[slug]/page.tsx.tmpl +1 -0
  169. package/templates/harness/app/integrations/[slug]/preview/activation/page.tsx.tmpl +1 -0
  170. package/templates/harness/app/integrations/[slug]/preview/layout.tsx.tmpl +1 -0
  171. package/templates/harness/app/integrations/[slug]/preview/page.tsx.tmpl +1 -0
  172. package/templates/harness/app/integrations/[slug]/preview/tile/page.tsx.tmpl +1 -0
  173. package/templates/harness/app/integrations/[slug]/preview/triggers/page.tsx.tmpl +1 -0
  174. package/templates/harness/app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx.tmpl +1 -0
  175. package/templates/harness/app/integrations/[slug]/preview/widgets/page.tsx.tmpl +1 -0
  176. package/templates/harness/app/integrations/[slug]/runs/page.tsx.tmpl +1 -0
  177. package/templates/harness/app/integrations/layout.tsx.tmpl +1 -0
  178. package/templates/harness/app/integrations/page.tsx.tmpl +1 -0
  179. package/templates/harness/app/page.experience.tsx.tmpl +5 -0
  180. package/templates/harness/repo/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +11 -0
  181. package/templates/harness/repo/app/(repo)/connections/page.tsx.tmpl +1 -0
  182. package/templates/harness/repo/app/(repo)/layout.tsx.tmpl +1 -0
  183. package/templates/harness/repo/app/(repo)/page.tsx.tmpl +1 -0
  184. package/templates/harness/repo/app/experiences/[name]/connect/page.tsx.tmpl +1 -0
  185. package/templates/harness/repo/app/experiences/[name]/layout.tsx.tmpl +9 -0
  186. package/templates/harness/repo/app/experiences/page.tsx.tmpl +5 -0
  187. package/templates/harness/repo/app/harness-shell.tsx.tmpl +9 -0
  188. package/templates/harness/repo/app/integrations/[slug]/layout.tsx.tmpl +1 -0
  189. package/templates/harness/repo/app/integrations/page.tsx.tmpl +5 -0
  190. package/templates/harness/repo/app/repo-experiences.ts.tmpl +5 -0
  191. package/templates/harness/styles/globals.css.tmpl +15 -1
  192. package/templates/package.json.tmpl +5 -4
package/README.md CHANGED
@@ -178,7 +178,7 @@ imply an isolation that does not exist.
178
178
  app/harness-shell.tsx 'use client' — injects your registry
179
179
  app/page.tsx server component — the index
180
180
  app/[slug]/layout.tsx ─┐
181
- app/[slug]/page.tsx │ (redirects /<slug> to /<slug>/widgets)
181
+ app/[slug]/page.tsx │ (redirect the old /<slug> addresses to Preview)
182
182
  app/[slug]/widgets/page.tsx │
183
183
  app/[slug]/widgets/[widgetId]/page.tsx
184
184
  app/[slug]/tile/page.tsx ├─ one-line re-exports of @ekanos/harness/routes
@@ -187,6 +187,24 @@ imply an isolation that does not exist.
187
187
  app/api/integrations/[slug]/[...path]/route.ts
188
188
  server — injects your registry into @ekanos/harness/api
189
189
  (live-mode OAuth loop, storage bridge, trigger route)
190
+ app/%5F%5Fekanos/api/[...path]/route.ts
191
+ server — /__ekanos/api/*: integrations, operations, approvals, run log
192
+ app/integrations/layout.tsx the Integration console (mounted from @ekanos/harness/routes)
193
+ app/integrations/page.tsx
194
+ app/integrations/[slug]/page.tsx
195
+ app/integrations/[slug]/connect/page.tsx
196
+ app/integrations/[slug]/operations/page.tsx
197
+ app/integrations/[slug]/operations/[kind]/[name]/page.tsx
198
+ app/integrations/[slug]/events/page.tsx
199
+ app/integrations/[slug]/health/page.tsx
200
+ app/integrations/[slug]/runs/page.tsx
201
+ app/integrations/[slug]/preview/layout.tsx
202
+ app/integrations/[slug]/preview/page.tsx
203
+ app/integrations/[slug]/preview/widgets/page.tsx
204
+ app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx
205
+ app/integrations/[slug]/preview/tile/page.tsx
206
+ app/integrations/[slug]/preview/activation/page.tsx
207
+ app/integrations/[slug]/preview/triggers/page.tsx
190
208
  next.config.mjs transpiles @ekanos/harness, aliases the workspace mock
191
209
  postcss.config.mjs @tailwindcss/postcss
192
210
  tsconfig.json no baseUrl, no paths
@@ -195,6 +213,27 @@ imply an isolation that does not exist.
195
213
  package.json scripts only — no deps, everything resolves upward
196
214
  ```
197
215
 
216
+ A `kind: "experience"` project also gets `app/experience/**` (the Experience view,
217
+ whose layout imports your entry) and a home page that redirects there.
218
+
219
+ An experience declares the integrations it depends on in `ekanos.json`:
220
+
221
+ ```json
222
+ { "kind": "experience", "slug": "my-exp", "entry": "src/experience.ts",
223
+ "integrations": [{ "name": "sanity", "version": "0.2",
224
+ "operations": ["tool/list_posts", "action/create-draft"] }] }
225
+ ```
226
+
227
+ `name` is the integration's slug and `version` its compat line. `ekanos add
228
+ sanity@0.2` adds an entry (reads by default; writes via `--ops`), pins its
229
+ contract in `contracts/` and `ekanos.lock.json`, and regenerates
230
+ `src/.ekanos/integrations.d.ts`, after which `useOperation('sanity',
231
+ 'tool/list_posts')` is typed. A per-developer `ekanos.local.json`
232
+ (`{ "integrations": { "sanity": { "path": "../sanity" } } }`) points an
233
+ integration at a local checkout; `ekanos publish` refuses while one is active.
234
+ The earlier `uses` / `slots` shapes are still read for one version with a
235
+ warning; `ekanos sync` rewrites `ekanos.json` and the lock.
236
+
198
237
  `next dev`/`next build` add `next-env.d.ts`, `.next/`, and (Next 16)
199
238
  `AGENTS.md`/`CLAUDE.md` alongside them. None of that is ours and none of it is
200
239
  tracked — `.ekanos/` is gitignored in full.
@@ -360,9 +399,28 @@ edit.
360
399
  ### For agents
361
400
 
362
401
  `dev` in JSON mode (`--json`, a pipe, or `CLAUDECODE=1`) **scaffolds and
363
- returns** rather than blocking on a dev server that never exits — the envelope's
364
- `data.server` carries `{ started: false, command, url, port }` so the caller can
365
- start it itself. Pass `--start` to override.
402
+ returns** rather than blocking on a dev server that never exits — it prints
403
+ "Scaffolded only: the server was NOT started", and the envelope's `data.server`
404
+ carries `{ started: false, reason, note, command, url, port }` so the caller can
405
+ start it itself. **Pass `--start` to actually run the server** under an agent
406
+ or a pipe (`ekanos dev --start --json`).
407
+
408
+ ## ESLint and the publish gate
409
+
410
+ The hosted gate's `checks/capability-lint` runs the **project's own** ESLint (its
411
+ pinned `node_modules/.bin/eslint` and an `eslint.config.*` spreading
412
+ `@ekanos/sdk/eslint`) and refuses a submission without them. It does not bring an
413
+ ESLint of its own: a control the submission can leave out is not a control.
414
+ `ekanos init` scaffolds `eslint`, `@typescript-eslint/parser` and
415
+ `eslint.config.mjs`. A repo created before that (or by hand) adds them once:
416
+
417
+ ```sh
418
+ pnpm add -D eslint @typescript-eslint/parser # or npm install -D / yarn add -D
419
+ # then add eslint.config.mjs spreading @ekanos/sdk/eslint
420
+ ```
421
+
422
+ `ekanos validate` warns (`project.capability-lint-tooling`) when either is missing
423
+ and prints the config to paste.
366
424
 
367
425
  ## Keeping the toolchain current
368
426
 
@@ -0,0 +1,2 @@
1
+ import type { ExperienceCommand } from './index-experience.js';
2
+ export declare const addCommand: ExperienceCommand;
@@ -0,0 +1,118 @@
1
+ import { harnessContractSources } from '../dev-experience.js';
2
+ import { invalidStateError, usageError } from '../errors.js';
3
+ import { defaultSources, resolveContract, } from '../experience/contract-sources.js';
4
+ import { pinIntegration } from '../experience/pin-integration.js';
5
+ import { inContractOrder, readOperationIds, resolveOpTokens, } from '../experience/pin-ops.js';
6
+ import { DEPRECATED_USES } from '../experience/pins.js';
7
+ import { readExperienceProject, writeIntegrations, } from '../experience/project.js';
8
+ import { loadPins, regenerateIntegrations } from '../experience/regenerate.js';
9
+ const SPEC = /^([a-z][a-z0-9-]*)@(\d+(?:\.\d+)?)$/;
10
+ export const addCommand = {
11
+ name: 'add',
12
+ usage: [
13
+ ' add Add an integration to an experience: pin its contract,',
14
+ ' record it in ekanos.json + ekanos.lock.json, regenerate types',
15
+ ' (add <integration>@<version> [--ops a,b] [--from-dir <dir>])',
16
+ ],
17
+ positionals: ['spec'],
18
+ specs: {
19
+ dir: {
20
+ type: 'string',
21
+ description: 'Project directory (default: the current one)',
22
+ },
23
+ ops: {
24
+ type: 'string',
25
+ description: 'Operations to allow, comma-separated (tool/name, action/name or a unique bare name). Default on a new integration: every read operation. Writes are opt-in.',
26
+ },
27
+ as: {
28
+ type: 'string',
29
+ description: 'Removed: an experience addresses an integration by its own name',
30
+ },
31
+ 'from-dir': {
32
+ type: 'string',
33
+ description: 'Directory of <integration>.contract.json files (the contract source)',
34
+ },
35
+ 'from-path': {
36
+ type: 'string',
37
+ description: 'An integration project directory whose contract.json to pin',
38
+ },
39
+ },
40
+ async run(ctx, args) {
41
+ var _a, _b, _c;
42
+ const spec = args.positionals[0];
43
+ const match = spec ? SPEC.exec(spec) : null;
44
+ if (!match) {
45
+ throw usageError(spec
46
+ ? `"${spec}" is not <integration>@<version>.`
47
+ : 'add needs <integration>@<version>.', 'e.g. "ekanos add sanity@0.2 --from-dir ../contracts".');
48
+ }
49
+ const [, integration, version] = match;
50
+ if (args.flags.as !== undefined) {
51
+ throw usageError('--as is gone: an experience addresses an integration by its own name.', `Use "${integration}" in useOperation('${integration}', ...).`);
52
+ }
53
+ const project = readExperienceProject(args.dir);
54
+ if (project.deprecatedUses)
55
+ ctx.warn(DEPRECATED_USES);
56
+ const sources = defaultSources(project, {
57
+ fromDir: args.flags['from-dir'],
58
+ fromPath: args.flags['from-path'],
59
+ env: args.env,
60
+ }, await harnessContractSources(project.dir, args.env));
61
+ const { contract, origin } = await resolveContract(sources, integration, version);
62
+ const existing = project.integrations.find((u) => u.name === integration);
63
+ if (existing && existing.version !== version) {
64
+ throw invalidStateError(`Integration "${integration}" is already pinned at ${existing.version}.`, `Move it to ${version} by editing its "version" in ekanos.json, then run "ekanos sync".`);
65
+ }
66
+ const requested = ((_a = args.flags.ops) !== null && _a !== void 0 ? _a : '')
67
+ .split(',')
68
+ .map((t) => t.trim())
69
+ .filter(Boolean);
70
+ const explicit = resolveOpTokens(contract, requested);
71
+ const operations = inContractOrder(contract, [
72
+ ...(existing ? existing.operations : readOperationIds(contract)),
73
+ ...explicit,
74
+ ]);
75
+ if (operations.length === 0) {
76
+ throw usageError(`${integration}@${version} has no read operations to allow by default.`, 'Pass --ops <tool/name,...> to choose operations.');
77
+ }
78
+ const entry = {
79
+ name: integration,
80
+ version,
81
+ operations,
82
+ required: (_b = existing === null || existing === void 0 ? void 0 : existing.required) !== null && _b !== void 0 ? _b : 'required',
83
+ modes: (_c = existing === null || existing === void 0 ? void 0 : existing.modes) !== null && _c !== void 0 ? _c : ['team'],
84
+ };
85
+ const integrations = existing
86
+ ? project.integrations.map((u) => (u.name === integration ? entry : u))
87
+ : [...project.integrations, entry];
88
+ const integrationsChanged = writeIntegrations(project, integrations);
89
+ const updated = Object.assign(Object.assign({}, project), { integrations });
90
+ const pins = pinIntegration(updated, entry, contract);
91
+ const { pinned } = loadPins(updated);
92
+ const typesChanged = regenerateIntegrations(updated, pinned);
93
+ const changed = integrationsChanged ||
94
+ pins.snapshotChanged ||
95
+ pins.lockChanged ||
96
+ typesChanged;
97
+ const writes = operations.filter((id) => contract.operations.find((op) => op.id === id && op.effect === 'write'));
98
+ return ctx.succeed({
99
+ integration,
100
+ version,
101
+ contractVersion: contract.contractVersion,
102
+ digest: contract.digest,
103
+ operations,
104
+ writes,
105
+ origin,
106
+ changed,
107
+ files: {
108
+ integrationsChanged,
109
+ snapshotChanged: pins.snapshotChanged,
110
+ lockChanged: pins.lockChanged,
111
+ typesChanged,
112
+ },
113
+ }, changed
114
+ ? `Added integration "${integration}" (${integration}@${version}, contract ${contract.contractVersion}) with ${operations.length} operation${operations.length === 1 ? '' : 's'}${writes.length ? `, ${writes.length} write${writes.length === 1 ? '' : 's'}` : ''}.`
115
+ : `Integration "${integration}" is already up to date (no files changed).`);
116
+ },
117
+ };
118
+ //# sourceMappingURL=add.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IntegrationEntry } from '@ekanos/integration-schema';\n\nimport { harnessContractSources } from '../dev-experience';\nimport { invalidStateError, usageError } from '../errors';\nimport {\n defaultSources,\n resolveContract,\n} from '../experience/contract-sources';\nimport { pinIntegration } from '../experience/pin-integration';\nimport {\n inContractOrder,\n readOperationIds,\n resolveOpTokens,\n} from '../experience/pin-ops';\nimport { DEPRECATED_USES } from '../experience/pins';\nimport {\n readExperienceProject,\n writeIntegrations,\n} from '../experience/project';\nimport { loadPins, regenerateIntegrations } from '../experience/regenerate';\nimport type { ExperienceCommand } from './index-experience';\n\nconst SPEC = /^([a-z][a-z0-9-]*)@(\\d+(?:\\.\\d+)?)$/;\n\nexport const addCommand: ExperienceCommand = {\n name: 'add',\n usage: [\n ' add Add an integration to an experience: pin its contract,',\n ' record it in ekanos.json + ekanos.lock.json, regenerate types',\n ' (add <integration>@<version> [--ops a,b] [--from-dir <dir>])',\n ],\n positionals: ['spec'],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n ops: {\n type: 'string',\n description:\n 'Operations to allow, comma-separated (tool/name, action/name or a unique bare name). Default on a new integration: every read operation. Writes are opt-in.',\n },\n as: {\n type: 'string',\n description:\n 'Removed: an experience addresses an integration by its own name',\n },\n 'from-dir': {\n type: 'string',\n description:\n 'Directory of <integration>.contract.json files (the contract source)',\n },\n 'from-path': {\n type: 'string',\n description:\n 'An integration project directory whose contract.json to pin',\n },\n },\n async run(ctx, args) {\n const spec = args.positionals[0];\n const match = spec ? SPEC.exec(spec) : null;\n if (!match) {\n throw usageError(\n spec\n ? `\"${spec}\" is not <integration>@<version>.`\n : 'add needs <integration>@<version>.',\n 'e.g. \"ekanos add sanity@0.2 --from-dir ../contracts\".',\n );\n }\n const [, integration, version] = match as unknown as [\n string,\n string,\n string,\n ];\n if (args.flags.as !== undefined) {\n throw usageError(\n '--as is gone: an experience addresses an integration by its own name.',\n `Use \"${integration}\" in useOperation('${integration}', ...).`,\n );\n }\n\n const project = readExperienceProject(args.dir);\n if (project.deprecatedUses) ctx.warn(DEPRECATED_USES);\n const sources = defaultSources(\n project,\n {\n fromDir: args.flags['from-dir'] as string | undefined,\n fromPath: args.flags['from-path'] as string | undefined,\n env: args.env,\n },\n await harnessContractSources(project.dir, args.env),\n );\n const { contract, origin } = await resolveContract(\n sources,\n integration,\n version,\n );\n\n const existing = project.integrations.find((u) => u.name === integration);\n if (existing && existing.version !== version) {\n throw invalidStateError(\n `Integration \"${integration}\" is already pinned at ${existing.version}.`,\n `Move it to ${version} by editing its \"version\" in ekanos.json, then run \"ekanos sync\".`,\n );\n }\n\n const requested = ((args.flags.ops as string | undefined) ?? '')\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const explicit = resolveOpTokens(contract, requested);\n const operations = inContractOrder(contract, [\n ...(existing ? existing.operations : readOperationIds(contract)),\n ...explicit,\n ]);\n if (operations.length === 0) {\n throw usageError(\n `${integration}@${version} has no read operations to allow by default.`,\n 'Pass --ops <tool/name,...> to choose operations.',\n );\n }\n\n const entry: IntegrationEntry = {\n name: integration,\n version,\n operations,\n required: existing?.required ?? 'required',\n modes: existing?.modes ?? ['team'],\n };\n const integrations = existing\n ? project.integrations.map((u) => (u.name === integration ? entry : u))\n : [...project.integrations, entry];\n const integrationsChanged = writeIntegrations(project, integrations);\n\n const updated = { ...project, integrations };\n const pins = pinIntegration(updated, entry, contract);\n const { pinned } = loadPins(updated);\n const typesChanged = regenerateIntegrations(updated, pinned);\n\n const changed =\n integrationsChanged ||\n pins.snapshotChanged ||\n pins.lockChanged ||\n typesChanged;\n const writes = operations.filter((id) =>\n contract.operations.find((op) => op.id === id && op.effect === 'write'),\n );\n\n return ctx.succeed(\n {\n integration,\n version,\n contractVersion: contract.contractVersion,\n digest: contract.digest,\n operations,\n writes,\n origin,\n changed,\n files: {\n integrationsChanged,\n snapshotChanged: pins.snapshotChanged,\n lockChanged: pins.lockChanged,\n typesChanged,\n },\n },\n changed\n ? `Added integration \"${integration}\" (${integration}@${version}, contract ${contract.contractVersion}) with ${operations.length} operation${operations.length === 1 ? '' : 's'}${writes.length ? `, ${writes.length} write${writes.length === 1 ? '' : 's'}` : ''}.`\n : `Integration \"${integration}\" is already up to date (no files changed).`,\n );\n },\n};\n"]}
@@ -0,0 +1,31 @@
1
+ import type { CliContext } from '../context.js';
2
+ import type { ExitCode } from '../exit-codes.js';
3
+ export interface AudiencesArgs {
4
+ host?: string;
5
+ source?: string;
6
+ /** `list`, `show <audience>`, `create <name>`, `set <audience>`. */
7
+ positionals: readonly string[];
8
+ yes: boolean;
9
+ /** `--products`, `--widgets`, `--tools`, `--name`, `--description` … */
10
+ flags?: Record<string, string | boolean | string[]>;
11
+ dir: string;
12
+ env: Record<string, string | undefined>;
13
+ }
14
+ /**
15
+ * `audiences` — a Source's audiences, for its Source admin. An audience is a
16
+ * named set of the Source's products (with optional per-product widget and
17
+ * tool rules) that people see in the app: each team member holds one, their
18
+ * own else their team's default (`ekanos teams audience`). It replaces a
19
+ * product scope; `sources scopes` and `teams scope` keep working until 0.20.
20
+ *
21
+ * list every audience, its products and rules
22
+ * show <audience> one, in full
23
+ * create <name> … --products a,b | --all-entitled, --description,
24
+ * --default, --widgets <product>=<id>,…,
25
+ * --tools <product>=<name>,…
26
+ * set <audience> … the same flags, plus --name; --products
27
+ * replaces the product set (rules of kept
28
+ * products stay unless re-stated). Dropping a
29
+ * product needs --yes.
30
+ */
31
+ export declare function runAudiences(ctx: CliContext, args: AudiencesArgs): Promise<ExitCode>;
@@ -0,0 +1,239 @@
1
+ import { requireSession, resolveAuthEnvironment } from '../auth/session.js';
2
+ import { invalidStateError, usageError } from '../errors.js';
3
+ import { resolveSourceTarget } from '../source-target.js';
4
+ import { createAudience, listAudiences, showAudience, updateAudience, } from '../teams-api.js';
5
+ import { pluralize } from '../ui.js';
6
+ import { TeamsClient } from './teams.js';
7
+ const USAGE = 'Run "ekanos audiences list", "ekanos audiences show <audience>", "ekanos audiences create <name> [--products a,b | --all-entitled]" or "ekanos audiences set <audience> [--products a,b] [--widgets <product>=<id>,…] [--tools <product>=<name>,…]".';
8
+ /**
9
+ * `audiences` — a Source's audiences, for its Source admin. An audience is a
10
+ * named set of the Source's products (with optional per-product widget and
11
+ * tool rules) that people see in the app: each team member holds one, their
12
+ * own else their team's default (`ekanos teams audience`). It replaces a
13
+ * product scope; `sources scopes` and `teams scope` keep working until 0.20.
14
+ *
15
+ * list every audience, its products and rules
16
+ * show <audience> one, in full
17
+ * create <name> … --products a,b | --all-entitled, --description,
18
+ * --default, --widgets <product>=<id>,…,
19
+ * --tools <product>=<name>,…
20
+ * set <audience> … the same flags, plus --name; --products
21
+ * replaces the product set (rules of kept
22
+ * products stay unless re-stated). Dropping a
23
+ * product needs --yes.
24
+ */
25
+ export async function runAudiences(ctx, args) {
26
+ var _a, _b;
27
+ const [verb = 'list', rawName, ...extra] = args.positionals;
28
+ if (!['list', 'show', 'create', 'set'].includes(verb)) {
29
+ throw usageError(`Unknown audiences subcommand "${verb}".`, USAGE);
30
+ }
31
+ if (extra.length > 0) {
32
+ throw usageError(`Unexpected argument "${extra[0]}".`, USAGE);
33
+ }
34
+ if (verb === 'list' && rawName) {
35
+ throw usageError(`Unexpected argument "${rawName}".`, USAGE);
36
+ }
37
+ const name = rawName === null || rawName === void 0 ? void 0 : rawName.trim();
38
+ if (verb !== 'list' && !name) {
39
+ throw usageError(verb === 'create'
40
+ ? 'audiences create needs a name.'
41
+ : `audiences ${verb} needs an audience (its name or id).`, USAGE);
42
+ }
43
+ const flags = readFlags((_a = args.flags) !== null && _a !== void 0 ? _a : {});
44
+ const env = resolveAuthEnvironment(ctx, args.host, args.env, {
45
+ projectDir: args.dir,
46
+ });
47
+ const resolved = await resolveSourceTarget(env, requireSession(env), args);
48
+ const client = new TeamsClient(env, resolved.session);
49
+ const source = resolved.source;
50
+ if (verb === 'list') {
51
+ const list = await client.call((token) => listAudiences(env.host, token, source));
52
+ if (list.audiences.length === 0) {
53
+ return ctx.succeed(Object.assign({ host: env.host }, list), ctx.ui.result({
54
+ title: `No audiences on ${list.source.slug}`,
55
+ notes: [`"ekanos audiences create <name> --products a,b" makes one.`],
56
+ }));
57
+ }
58
+ return ctx.succeed(Object.assign({ host: env.host }, list), ctx.ui.result({
59
+ title: `Audiences on ${list.source.slug}`,
60
+ rows: list.audiences.map((audience) => [
61
+ audience.name,
62
+ describeLines(audience),
63
+ ]),
64
+ notes: [
65
+ 'A person sees the products of their own audience, else their team’s ("ekanos teams audience <team>").',
66
+ ],
67
+ }));
68
+ }
69
+ if (verb === 'show') {
70
+ const shown = await client.call((token) => showAudience(env.host, token, { source, audience: name }));
71
+ return ctx.succeed(Object.assign({ host: env.host }, shown), renderAudience(ctx.ui, shown.audience, `${shown.audience.name}`));
72
+ }
73
+ if (verb === 'create') {
74
+ const body = Object.assign(Object.assign(Object.assign(Object.assign({ name: name }, (flags.description !== undefined
75
+ ? { description: flags.description }
76
+ : {})), (flags.isDefault ? { isDefault: true } : {})), (flags.allEntitled !== undefined
77
+ ? { includesAllEntitled: flags.allEntitled }
78
+ : {})), (flags.products || flags.widgets.size > 0 || flags.tools.size > 0
79
+ ? { products: applyRules([], flags) }
80
+ : {}));
81
+ const created = await client.call((token) => createAudience(env.host, token, source, body));
82
+ return ctx.succeed(Object.assign({ host: env.host }, created), renderAudience(ctx.ui, created.audience, `Created ${created.audience.name}`));
83
+ }
84
+ const current = await client.call((token) => showAudience(env.host, token, { source, audience: name }));
85
+ const before = current.audience;
86
+ const body = Object.assign(Object.assign(Object.assign(Object.assign({}, (flags.name !== undefined ? { name: flags.name } : {})), (flags.description !== undefined
87
+ ? { description: flags.description }
88
+ : {})), (flags.isDefault ? { isDefault: true } : {})), (flags.allEntitled !== undefined
89
+ ? { includesAllEntitled: flags.allEntitled }
90
+ : {}));
91
+ if (flags.products || flags.widgets.size > 0 || flags.tools.size > 0) {
92
+ body.products = applyRules(before.products.map((product) => (Object.assign(Object.assign({ slug: product.slug }, (product.widgetIds ? { widgetIds: product.widgetIds } : {})), (product.toolNames ? { toolNames: product.toolNames } : {})))), flags);
93
+ }
94
+ if (Object.keys(body).length === 0) {
95
+ throw usageError('Nothing to change.', 'Pass --products, --widgets, --tools, --name, --description, --default or --all-entitled.');
96
+ }
97
+ const kept = new Set(((_b = body.products) !== null && _b !== void 0 ? _b : []).map((product) => product.slug));
98
+ const dropped = body.products
99
+ ? before.products.filter((product) => !kept.has(product.slug))
100
+ : [];
101
+ if (dropped.length > 0 && !args.yes) {
102
+ throw invalidStateError(`${before.name} would no longer show ${dropped.map((product) => product.slug).join(', ')}: everyone on it stops seeing ${dropped.length === 1 ? 'it' : 'them'} in the app.`, `Re-run with --yes: ekanos audiences set ${quote(before.name)} … --source ${source} --yes`);
103
+ }
104
+ const updated = await client.call((token) => updateAudience(env.host, token, { source, audience: name }, body));
105
+ return ctx.succeed(Object.assign({ host: env.host }, updated), renderAudience(ctx.ui, updated.audience, `Updated ${updated.audience.name}`, {
106
+ tone: dropped.length > 0 ? 'warn' : 'ok',
107
+ }));
108
+ }
109
+ function readFlags(flags) {
110
+ const text = (key) => typeof flags[key] === 'string' ? flags[key] : undefined;
111
+ const list = (key) => {
112
+ const value = flags[key];
113
+ return Array.isArray(value)
114
+ ? value
115
+ : typeof value === 'string'
116
+ ? [value]
117
+ : [];
118
+ };
119
+ const products = text('products');
120
+ return {
121
+ name: text('name'),
122
+ description: text('description'),
123
+ isDefault: flags.default === true,
124
+ allEntitled: typeof flags['all-entitled'] === 'boolean'
125
+ ? flags['all-entitled']
126
+ : undefined,
127
+ products: products === undefined
128
+ ? undefined
129
+ : products === 'none'
130
+ ? []
131
+ : products
132
+ .split(',')
133
+ .map((slug) => slug.trim().toLowerCase())
134
+ .filter(Boolean),
135
+ widgets: ruleMap('widgets', list('widgets')),
136
+ tools: ruleMap('tools', list('tools')),
137
+ };
138
+ }
139
+ /** `linear=w1,w2` per product; `linear=` clears the rule (all of them). */
140
+ function ruleMap(flag, values) {
141
+ const rules = new Map();
142
+ for (const value of values) {
143
+ const at = value.indexOf('=');
144
+ const slug = (at < 0 ? value : value.slice(0, at)).trim().toLowerCase();
145
+ if (!slug || at < 0) {
146
+ throw usageError(`--${flag} takes <product>=<name>,<name>… (got "${value}").`, `e.g. --${flag} linear=${flag === 'widgets' ? 'linear-issues' : 'create_issue'}; "<product>=" lifts the rule.`);
147
+ }
148
+ rules.set(slug, value
149
+ .slice(at + 1)
150
+ .split(',')
151
+ .map((item) => item.trim())
152
+ .filter(Boolean));
153
+ }
154
+ return rules;
155
+ }
156
+ /** The product set after `--products` and the widget and tool rules. */
157
+ function applyRules(base, flags) {
158
+ var _a;
159
+ const known = new Map(base.map((product) => [product.slug, product]));
160
+ const slugs = (_a = flags.products) !== null && _a !== void 0 ? _a : base.map((product) => product.slug);
161
+ const products = slugs.map((slug) => {
162
+ const existing = known.get(slug);
163
+ return Object.assign(Object.assign({ slug }, ((existing === null || existing === void 0 ? void 0 : existing.widgetIds) ? { widgetIds: existing.widgetIds } : {})), ((existing === null || existing === void 0 ? void 0 : existing.toolNames) ? { toolNames: existing.toolNames } : {}));
164
+ });
165
+ for (const [kind, rules] of [
166
+ ['widgetIds', flags.widgets],
167
+ ['toolNames', flags.tools],
168
+ ]) {
169
+ for (const [slug, names] of rules) {
170
+ const product = products.find((entry) => entry.slug === slug);
171
+ if (!product) {
172
+ throw usageError(`--${kind === 'widgetIds' ? 'widgets' : 'tools'} names ${slug}, which the audience would not show.`, 'Add it with --products, or name a product that is in the audience.');
173
+ }
174
+ if (names.length > 0)
175
+ product[kind] = names;
176
+ else
177
+ delete product[kind];
178
+ }
179
+ }
180
+ return products;
181
+ }
182
+ function describeLines(audience) {
183
+ const lines = [
184
+ [
185
+ audience.includesAllEntitled
186
+ ? 'every product the Source has'
187
+ : pluralize(audience.products.length, 'product'),
188
+ audience.isDefault ? 'given to every new team' : null,
189
+ ]
190
+ .filter(Boolean)
191
+ .join(' · '),
192
+ ];
193
+ if (!audience.includesAllEntitled) {
194
+ lines.push(audience.products.length > 0
195
+ ? audience.products.map(productWords).join(', ')
196
+ : 'no products');
197
+ }
198
+ return lines;
199
+ }
200
+ function productWords(product) {
201
+ const rules = [
202
+ product.widgetIds
203
+ ? `${pluralize(product.widgetIds.length, 'widget')}`
204
+ : null,
205
+ product.toolNames ? `${pluralize(product.toolNames.length, 'tool')}` : null,
206
+ ].filter(Boolean);
207
+ return rules.length > 0
208
+ ? `${product.slug} (only ${rules.join(', ')})`
209
+ : product.slug;
210
+ }
211
+ function renderAudience(ui, audience, title, options = {}) {
212
+ var _a;
213
+ return ui.result({
214
+ title,
215
+ tone: (_a = options.tone) !== null && _a !== void 0 ? _a : 'ok',
216
+ rows: [
217
+ ['Audience', audience.name],
218
+ ...(audience.description
219
+ ? [['About', audience.description]]
220
+ : []),
221
+ ['Default', audience.isDefault ? 'yes, new teams start on it' : 'no'],
222
+ [
223
+ 'Products',
224
+ audience.includesAllEntitled
225
+ ? 'every product the Source has, now and later'
226
+ : audience.products.length > 0
227
+ ? audience.products.map(productWords)
228
+ : 'none',
229
+ ],
230
+ ],
231
+ notes: [
232
+ 'Teams and members are put on an audience with "ekanos teams audience <team> [<audience>]".',
233
+ ],
234
+ });
235
+ }
236
+ function quote(name) {
237
+ return /^[A-Za-z0-9._-]+$/.test(name) ? name : `"${name}"`;
238
+ }
239
+ //# sourceMappingURL=audiences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audiences.js","sourceRoot":"","sources":["../../src/commands/audiences.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Audience,\n AudienceCreateRequest,\n AudienceUpdateRequest,\n} from '@ekanos/integration-schema';\n\nimport { requireSession, resolveAuthEnvironment } from '../auth/session';\nimport type { CliContext } from '../context';\nimport { invalidStateError, usageError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { resolveSourceTarget } from '../source-target';\nimport {\n createAudience,\n listAudiences,\n showAudience,\n updateAudience,\n} from '../teams-api';\nimport { type Ui, pluralize } from '../ui';\nimport { TeamsClient } from './teams';\n\nexport interface AudiencesArgs {\n host?: string;\n source?: string;\n /** `list`, `show <audience>`, `create <name>`, `set <audience>`. */\n positionals: readonly string[];\n yes: boolean;\n /** `--products`, `--widgets`, `--tools`, `--name`, `--description` … */\n flags?: Record<string, string | boolean | string[]>;\n dir: string;\n env: Record<string, string | undefined>;\n}\n\nconst USAGE =\n 'Run \"ekanos audiences list\", \"ekanos audiences show <audience>\", \"ekanos audiences create <name> [--products a,b | --all-entitled]\" or \"ekanos audiences set <audience> [--products a,b] [--widgets <product>=<id>,…] [--tools <product>=<name>,…]\".';\n\ntype ProductRule = { slug: string; widgetIds?: string[]; toolNames?: string[] };\n\n/**\n * `audiences` — a Source's audiences, for its Source admin. An audience is a\n * named set of the Source's products (with optional per-product widget and\n * tool rules) that people see in the app: each team member holds one, their\n * own else their team's default (`ekanos teams audience`). It replaces a\n * product scope; `sources scopes` and `teams scope` keep working until 0.20.\n *\n * list every audience, its products and rules\n * show <audience> one, in full\n * create <name> … --products a,b | --all-entitled, --description,\n * --default, --widgets <product>=<id>,…,\n * --tools <product>=<name>,…\n * set <audience> … the same flags, plus --name; --products\n * replaces the product set (rules of kept\n * products stay unless re-stated). Dropping a\n * product needs --yes.\n */\nexport async function runAudiences(\n ctx: CliContext,\n args: AudiencesArgs,\n): Promise<ExitCode> {\n const [verb = 'list', rawName, ...extra] = args.positionals;\n if (!['list', 'show', 'create', 'set'].includes(verb)) {\n throw usageError(`Unknown audiences subcommand \"${verb}\".`, USAGE);\n }\n if (extra.length > 0) {\n throw usageError(`Unexpected argument \"${extra[0]}\".`, USAGE);\n }\n if (verb === 'list' && rawName) {\n throw usageError(`Unexpected argument \"${rawName}\".`, USAGE);\n }\n const name = rawName?.trim();\n if (verb !== 'list' && !name) {\n throw usageError(\n verb === 'create'\n ? 'audiences create needs a name.'\n : `audiences ${verb} needs an audience (its name or id).`,\n USAGE,\n );\n }\n const flags = readFlags(args.flags ?? {});\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(env, requireSession(env), args);\n const client = new TeamsClient(env, resolved.session);\n const source = resolved.source;\n\n if (verb === 'list') {\n const list = await client.call((token) =>\n listAudiences(env.host, token, source),\n );\n if (list.audiences.length === 0) {\n return ctx.succeed(\n { host: env.host, ...list },\n ctx.ui.result({\n title: `No audiences on ${list.source.slug}`,\n notes: [`\"ekanos audiences create <name> --products a,b\" makes one.`],\n }),\n );\n }\n return ctx.succeed(\n { host: env.host, ...list },\n ctx.ui.result({\n title: `Audiences on ${list.source.slug}`,\n rows: list.audiences.map((audience): [string, string[]] => [\n audience.name,\n describeLines(audience),\n ]),\n notes: [\n 'A person sees the products of their own audience, else their team’s (\"ekanos teams audience <team>\").',\n ],\n }),\n );\n }\n\n if (verb === 'show') {\n const shown = await client.call((token) =>\n showAudience(env.host, token, { source, audience: name! }),\n );\n return ctx.succeed(\n { host: env.host, ...shown },\n renderAudience(ctx.ui, shown.audience, `${shown.audience.name}`),\n );\n }\n\n if (verb === 'create') {\n const body: AudienceCreateRequest = {\n name: name!,\n ...(flags.description !== undefined\n ? { description: flags.description }\n : {}),\n ...(flags.isDefault ? { isDefault: true } : {}),\n ...(flags.allEntitled !== undefined\n ? { includesAllEntitled: flags.allEntitled }\n : {}),\n ...(flags.products || flags.widgets.size > 0 || flags.tools.size > 0\n ? { products: applyRules([], flags) }\n : {}),\n };\n const created = await client.call((token) =>\n createAudience(env.host, token, source, body),\n );\n return ctx.succeed(\n { host: env.host, ...created },\n renderAudience(\n ctx.ui,\n created.audience,\n `Created ${created.audience.name}`,\n ),\n );\n }\n\n const current = await client.call((token) =>\n showAudience(env.host, token, { source, audience: name! }),\n );\n const before = current.audience;\n const body: AudienceUpdateRequest = {\n ...(flags.name !== undefined ? { name: flags.name } : {}),\n ...(flags.description !== undefined\n ? { description: flags.description }\n : {}),\n ...(flags.isDefault ? { isDefault: true } : {}),\n ...(flags.allEntitled !== undefined\n ? { includesAllEntitled: flags.allEntitled }\n : {}),\n };\n if (flags.products || flags.widgets.size > 0 || flags.tools.size > 0) {\n body.products = applyRules(\n before.products.map((product) => ({\n slug: product.slug,\n ...(product.widgetIds ? { widgetIds: product.widgetIds } : {}),\n ...(product.toolNames ? { toolNames: product.toolNames } : {}),\n })),\n flags,\n );\n }\n if (Object.keys(body).length === 0) {\n throw usageError(\n 'Nothing to change.',\n 'Pass --products, --widgets, --tools, --name, --description, --default or --all-entitled.',\n );\n }\n\n const kept = new Set((body.products ?? []).map((product) => product.slug));\n const dropped = body.products\n ? before.products.filter((product) => !kept.has(product.slug))\n : [];\n if (dropped.length > 0 && !args.yes) {\n throw invalidStateError(\n `${before.name} would no longer show ${dropped.map((product) => product.slug).join(', ')}: everyone on it stops seeing ${dropped.length === 1 ? 'it' : 'them'} in the app.`,\n `Re-run with --yes: ekanos audiences set ${quote(before.name)} … --source ${source} --yes`,\n );\n }\n\n const updated = await client.call((token) =>\n updateAudience(env.host, token, { source, audience: name! }, body),\n );\n return ctx.succeed(\n { host: env.host, ...updated },\n renderAudience(\n ctx.ui,\n updated.audience,\n `Updated ${updated.audience.name}`,\n {\n tone: dropped.length > 0 ? 'warn' : 'ok',\n },\n ),\n );\n}\n\ninterface AudienceFlags {\n name?: string;\n description?: string;\n isDefault: boolean;\n allEntitled?: boolean;\n /** Slugs, in the order given; absent when the flag was not passed. */\n products?: string[];\n widgets: Map<string, string[]>;\n tools: Map<string, string[]>;\n}\n\nfunction readFlags(\n flags: Record<string, string | boolean | string[]>,\n): AudienceFlags {\n const text = (key: string) =>\n typeof flags[key] === 'string' ? (flags[key] as string) : undefined;\n const list = (key: string) => {\n const value = flags[key];\n return Array.isArray(value)\n ? value\n : typeof value === 'string'\n ? [value]\n : [];\n };\n\n const products = text('products');\n return {\n name: text('name'),\n description: text('description'),\n isDefault: flags.default === true,\n allEntitled:\n typeof flags['all-entitled'] === 'boolean'\n ? (flags['all-entitled'] as boolean)\n : undefined,\n products:\n products === undefined\n ? undefined\n : products === 'none'\n ? []\n : products\n .split(',')\n .map((slug) => slug.trim().toLowerCase())\n .filter(Boolean),\n widgets: ruleMap('widgets', list('widgets')),\n tools: ruleMap('tools', list('tools')),\n };\n}\n\n/** `linear=w1,w2` per product; `linear=` clears the rule (all of them). */\nfunction ruleMap(flag: string, values: string[]): Map<string, string[]> {\n const rules = new Map<string, string[]>();\n for (const value of values) {\n const at = value.indexOf('=');\n const slug = (at < 0 ? value : value.slice(0, at)).trim().toLowerCase();\n if (!slug || at < 0) {\n throw usageError(\n `--${flag} takes <product>=<name>,<name>… (got \"${value}\").`,\n `e.g. --${flag} linear=${flag === 'widgets' ? 'linear-issues' : 'create_issue'}; \"<product>=\" lifts the rule.`,\n );\n }\n rules.set(\n slug,\n value\n .slice(at + 1)\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean),\n );\n }\n return rules;\n}\n\n/** The product set after `--products` and the widget and tool rules. */\nfunction applyRules(base: ProductRule[], flags: AudienceFlags): ProductRule[] {\n const known = new Map(base.map((product) => [product.slug, product]));\n const slugs = flags.products ?? base.map((product) => product.slug);\n\n const products = slugs.map((slug): ProductRule => {\n const existing = known.get(slug);\n return {\n slug,\n ...(existing?.widgetIds ? { widgetIds: existing.widgetIds } : {}),\n ...(existing?.toolNames ? { toolNames: existing.toolNames } : {}),\n };\n });\n for (const [kind, rules] of [\n ['widgetIds', flags.widgets],\n ['toolNames', flags.tools],\n ] as const) {\n for (const [slug, names] of rules) {\n const product = products.find((entry) => entry.slug === slug);\n if (!product) {\n throw usageError(\n `--${kind === 'widgetIds' ? 'widgets' : 'tools'} names ${slug}, which the audience would not show.`,\n 'Add it with --products, or name a product that is in the audience.',\n );\n }\n if (names.length > 0) product[kind] = names;\n else delete product[kind];\n }\n }\n return products;\n}\n\nfunction describeLines(audience: Audience): string[] {\n const lines = [\n [\n audience.includesAllEntitled\n ? 'every product the Source has'\n : pluralize(audience.products.length, 'product'),\n audience.isDefault ? 'given to every new team' : null,\n ]\n .filter(Boolean)\n .join(' · '),\n ];\n if (!audience.includesAllEntitled) {\n lines.push(\n audience.products.length > 0\n ? audience.products.map(productWords).join(', ')\n : 'no products',\n );\n }\n return lines;\n}\n\nfunction productWords(product: Audience['products'][number]): string {\n const rules = [\n product.widgetIds\n ? `${pluralize(product.widgetIds.length, 'widget')}`\n : null,\n product.toolNames ? `${pluralize(product.toolNames.length, 'tool')}` : null,\n ].filter(Boolean);\n return rules.length > 0\n ? `${product.slug} (only ${rules.join(', ')})`\n : product.slug;\n}\n\nfunction renderAudience(\n ui: Ui,\n audience: Audience,\n title: string,\n options: { tone?: 'ok' | 'warn' } = {},\n): string {\n return ui.result({\n title,\n tone: options.tone ?? 'ok',\n rows: [\n ['Audience', audience.name],\n ...(audience.description\n ? ([['About', audience.description]] as Array<[string, string]>)\n : []),\n ['Default', audience.isDefault ? 'yes, new teams start on it' : 'no'],\n [\n 'Products',\n audience.includesAllEntitled\n ? 'every product the Source has, now and later'\n : audience.products.length > 0\n ? audience.products.map(productWords)\n : 'none',\n ],\n ],\n notes: [\n 'Teams and members are put on an audience with \"ekanos teams audience <team> [<audience>]\".',\n ],\n });\n}\n\nfunction quote(name: string): string {\n return /^[A-Za-z0-9._-]+$/.test(name) ? name : `\"${name}\"`;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { ExperienceCommand } from './index-experience.js';
2
+ export declare const buildCommand: ExperienceCommand;
@@ -0,0 +1,41 @@
1
+ import { emitExperience } from '../experience/build.js';
2
+ import { runExperienceChecks } from '../experience/checks.js';
3
+ import { readExperienceProject } from '../experience/project.js';
4
+ import { failOnErrors } from '../experience/report.js';
5
+ export const buildCommand = {
6
+ name: 'build',
7
+ usage: [
8
+ ' build Build an experience: gate (pins, boundary, definition), then emit',
9
+ ' dist/ and manifest.json',
10
+ ],
11
+ specs: {
12
+ dir: {
13
+ type: 'string',
14
+ description: 'Project directory (default: the current one)',
15
+ },
16
+ },
17
+ async run(ctx, args) {
18
+ const project = readExperienceProject(args.dir);
19
+ const result = await runExperienceChecks(project, {
20
+ typecheck: false,
21
+ loadDefinition: true,
22
+ inlineSdk: args.env.EKANOS_INLINE_SDK,
23
+ });
24
+ const failed = failOnErrors(ctx, 'build', result.findings, { slug: project.slug }, project.dir);
25
+ if (failed !== null)
26
+ return failed;
27
+ const output = await emitExperience(project, result);
28
+ return ctx.succeed({
29
+ slug: project.slug,
30
+ version: output.manifest.version,
31
+ digest: output.manifest.digest,
32
+ files: output.files,
33
+ widgets: output.manifest.widgets.length,
34
+ pages: output.manifest.pages.length,
35
+ workflowTemplates: output.manifest.workflowTemplates.length,
36
+ findings: result.findings,
37
+ changed: output.changed,
38
+ }, `Built ${project.slug}@${output.manifest.version} (${output.manifest.digest.slice(0, 18)}…): ${output.files.join(', ')}.`);
39
+ },
40
+ };
41
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,MAAM,CAAC,MAAM,YAAY,GAAsB;IAC7C,IAAI,EAAE,OAAO;IACb,KAAK,EAAE;QACL,gFAAgF;QAChF,sCAAsC;KACvC;IACD,KAAK,EAAE;QACL,GAAG,EAAE;YACH,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,8CAA8C;SAC5D;KACF;IACD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI;QACjB,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,OAAO,EAAE;YAChD,SAAS,EAAE,KAAK;YAChB,cAAc,EAAE,IAAI;YACpB,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,iBAAiB;SACtC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,YAAY,CACzB,GAAG,EACH,OAAO,EACP,MAAM,CAAC,QAAQ,EACf,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EACtB,OAAO,CAAC,GAAG,CACZ,CAAC;QACF,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC;QAEnC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAErD,OAAO,GAAG,CAAC,OAAO,CAChB;YACE,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO;YAChC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;YAC9B,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM;YACvC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM;YACnC,iBAAiB,EAAE,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,MAAM;YAC3D,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,EACD,SAAS,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC1H,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { emitExperience } from '../experience/build';\nimport { runExperienceChecks } from '../experience/checks';\nimport { readExperienceProject } from '../experience/project';\nimport { failOnErrors } from '../experience/report';\nimport type { ExperienceCommand } from './index-experience';\n\nexport const buildCommand: ExperienceCommand = {\n name: 'build',\n usage: [\n ' build Build an experience: gate (pins, boundary, definition), then emit',\n ' dist/ and manifest.json',\n ],\n specs: {\n dir: {\n type: 'string',\n description: 'Project directory (default: the current one)',\n },\n },\n async run(ctx, args) {\n const project = readExperienceProject(args.dir);\n const result = await runExperienceChecks(project, {\n typecheck: false,\n loadDefinition: true,\n inlineSdk: args.env.EKANOS_INLINE_SDK,\n });\n\n const failed = failOnErrors(\n ctx,\n 'build',\n result.findings,\n { slug: project.slug },\n project.dir,\n );\n if (failed !== null) return failed;\n\n const output = await emitExperience(project, result);\n\n return ctx.succeed(\n {\n slug: project.slug,\n version: output.manifest.version,\n digest: output.manifest.digest,\n files: output.files,\n widgets: output.manifest.widgets.length,\n pages: output.manifest.pages.length,\n workflowTemplates: output.manifest.workflowTemplates.length,\n findings: result.findings,\n changed: output.changed,\n },\n `Built ${project.slug}@${output.manifest.version} (${output.manifest.digest.slice(0, 18)}…): ${output.files.join(', ')}.`,\n );\n },\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import type { ExperienceCommand } from './index-experience.js';
2
+ export declare const checkCommand: ExperienceCommand;