@ekanos/cli 0.20.0 → 0.22.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 (46) hide show
  1. package/dist/apps-api.js +1 -1
  2. package/dist/apps-api.js.map +1 -1
  3. package/dist/commands/names.js +2 -2
  4. package/dist/commands/names.js.map +1 -1
  5. package/dist/commands/publish.js +1 -1
  6. package/dist/commands/publish.js.map +1 -1
  7. package/dist/commands/sources.js +3 -3
  8. package/dist/commands/sources.js.map +1 -1
  9. package/dist/commands/team-agent-readiness.js +1 -1
  10. package/dist/commands/team-agent-readiness.js.map +1 -1
  11. package/dist/commands/teams-channel-install.js +1 -1
  12. package/dist/commands/teams-channel-install.js.map +1 -1
  13. package/dist/commands/teams-connect.js +2 -2
  14. package/dist/commands/teams-connect.js.map +1 -1
  15. package/dist/commands/teams.js +2 -2
  16. package/dist/commands/teams.js.map +1 -1
  17. package/dist/commands/use.js +1 -1
  18. package/dist/commands/use.js.map +1 -1
  19. package/dist/connections-api.js +1 -1
  20. package/dist/connections-api.js.map +1 -1
  21. package/dist/experience/definition-checks.js +1 -1
  22. package/dist/experience/definition-checks.js.map +1 -1
  23. package/dist/experience/definition-shape.d.ts +4 -1
  24. package/dist/experience/definition-shape.js.map +1 -1
  25. package/dist/index.js +2 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/publish-api.js +1 -2
  28. package/dist/publish-api.js.map +1 -1
  29. package/dist/runs-api.js +1 -1
  30. package/dist/runs-api.js.map +1 -1
  31. package/dist/secret-form.js +1 -1
  32. package/dist/secret-form.js.map +1 -1
  33. package/dist/source-products-api.js +1 -1
  34. package/dist/source-products-api.js.map +1 -1
  35. package/dist/teams-api.js +2 -2
  36. package/dist/teams-api.js.map +1 -1
  37. package/dist/workflows-api.js +1 -1
  38. package/dist/workflows-api.js.map +1 -1
  39. package/package.json +6 -6
  40. package/templates/AGENTS.md.tmpl +26 -9
  41. package/templates/experience/AGENTS.md.tmpl +3 -1
  42. package/templates/experience/package.json.tmpl +4 -4
  43. package/templates/experience/src/widgets/hello-widget.tsx.tmpl +3 -1
  44. package/templates/package.json.tmpl +4 -4
  45. package/templates/skills/build-integration/SKILL.md +2 -1
  46. package/templates/skills/build-integration/references/api-wiring.md +4 -2
@@ -124,8 +124,7 @@ export async function submitArchive(host, accessToken, payload) {
124
124
  throw globalNameUnapprovedError(payload.slug);
125
125
  }
126
126
  if (response.status === 409 && body.code === 'NAMESPACE_REQUIRED') {
127
- throw invalidStateError(serverMessage !== null && serverMessage !== void 0 ? serverMessage : `Source "${payload.source}" has no namespace to name "${payload.slug}" under.`, (_a = str(body.hint)) !== null && _a !== void 0 ? _a : `Ask an Ekanos super-admin to give "${payload.source}" a namespace, ` +
128
- `then retry.`);
127
+ throw invalidStateError(serverMessage !== null && serverMessage !== void 0 ? serverMessage : `Source "${payload.source}" has no namespace to name "${payload.slug}" under.`, (_a = str(body.hint)) !== null && _a !== void 0 ? _a : `Ask Ekanos to give "${payload.source}" a namespace, then retry.`);
129
128
  }
130
129
  if (response.status === 409) {
131
130
  throw invalidStateError(serverMessage !== null && serverMessage !== void 0 ? serverMessage : `Version ${payload.version} of "${payload.slug}" is already submitted.`, `Submitted versions are immutable. Bump the "version" field in ` +
@@ -1 +1 @@
1
- {"version":3,"file":"publish-api.js","sourceRoot":"","sources":["../src/publish-api.ts"],"names":[],"mappings":"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","sourcesContent":["import { request } from './auth/ekanos-api';\nimport { loginHint } from './auth/host-trust';\nimport type { AuthEnvironment } from './auth/session';\nimport {\n authRequiredError,\n forbiddenError,\n invalidStateError,\n networkError,\n notFoundError,\n validationError,\n} from './errors';\n\n/**\n * The CLI's client for the Ekanos submission intake route.\n *\n * ---------------------------------------------------------------------------\n * WIRE CONTRACT — the seam between this package and the Ekanos web app.\n *\n * The server side is the host's `/api/partner/submissions` route. As with\n * the auth routes (see `auth/ekanos-api.ts`), the two halves are coupled\n * only by HTTP; tests stub `fetch`, never the route.\n *\n * POST /api/partner/submissions [Authorization: Bearer <access_token>]\n * ← multipart/form-data:\n * source — Ekanos source slug\n * slug — integration (or experience) slug (kebab-case; bare, never namespaced)\n * version — semver\n * kind — \"experience\" for a composed experience; omitted for an\n * integration (an older host never sees the field). Sent\n * only to a host that advertises\n * `capabilities.experienceSubmissions` on the toolchain\n * route.\n * manifest — JSON string (the parsed ekanos.json + declaration\n * summary; for an experience, its built manifest.json)\n * archive — gzipped tarball, ≤ 4 MiB\n * availability — \"global\" | \"restricted\"; sent only when ekanos.json\n * declares it\n * → 201 { ok: true, data: { submissionId, slug, version, state,\n * archiveSha256,\n * // newer hosts only:\n * namespace?: string|null, displayName? } }\n * → 400 { error, code: \"INVALID_REQUEST\" | \"INVALID_ARCHIVE\" }\n * → 401 { error, code: \"AUTH_REQUIRED\" } token absent or expired —\n * refresh once and retry\n * → 403 { error, code: \"FORBIDDEN\" } no developer seat on the source\n * → 404 { error, code: \"SOURCE_NOT_FOUND\" }\n * → 409 { error, code: \"VERSION_EXISTS\" } this (slug, version) is\n * already submitted\n * → 409 { error, code: \"KIND_MISMATCH\" } the slug is registered as\n * the other kind\n * → 409 { error, code: \"GLOBAL_NAME_UNAPPROVED\", hint? }\n * availability \"global\" without\n * an approved global name\n * → 409 { error, code: \"NAMESPACE_REQUIRED\" } the source has no namespace\n * → 413 { error, code: \"ARCHIVE_TOO_LARGE\" }\n * → 3xx treated as 401 (an\n * auth-gated Ekanos page\n * answers 307)\n *\n * GET /api/partner/submissions?source=<slug|id>[&detail=findings]\n * [Authorization: Bearer]\n * → 200 { ok: true, data: { source, integrations: [{ id, slug, name,\n * createdAt, namespace?: string|null, displayName?, versions: [{ id, version, state, archiveSha256,\n * submittedAt, gatedAt,\n * // present once a gate has run (gate_report is non-null):\n * errorCount?, warnCount?,\n * // present ONLY with ?detail=findings, alongside the above:\n * findings?, stages?\n * }] }] } }\n * → 400 { error, code: \"INVALID_REQUEST\" }\n * → 401 { error, code: \"AUTH_REQUIRED\" } refresh once and retry\n * → 403 { error, code: \"MFA_REQUIRED\" }\n * → 404 { error, code: \"SOURCE_NOT_FOUND\" }\n * → 3xx treated as 401\n *\n * `state` walks submitted -> gated|rejected -> dev -> prod -> revoked (a\n * `dev` version is marked `superseded` once a newer version reaches dev),\n * enforced by a database trigger on the host's side (the full transition\n * matrix lives there). `gated`/`dev`/`rejected` are the\n * terminal-for-waiting states `ekanos status --watch` polls for.\n * ---------------------------------------------------------------------------\n */\n\n/**\n * The server's manifest cap — 64 KiB of UTF-8, enforced server-side against\n * `Buffer.byteLength`. Mirrored here so `publish` can refuse an oversized\n * manifest with a clear message instead of the server's generic 400.\n */\nexport const MAX_MANIFEST_BYTES = 64 * 1024;\n\nexport interface SubmissionPayload {\n source: string;\n slug: string;\n /** Omitted for an integration. */\n kind?: 'experience';\n version: string;\n /** JSON string — already serialized by the caller. */\n manifest: string;\n archive: Uint8Array;\n /** Sent only when ekanos.json declares an availability mode. */\n availability?: 'global' | 'restricted';\n}\n\nexport interface SubmissionReceipt {\n submissionId: string;\n slug: string;\n version: string;\n state: string;\n archiveSha256: string;\n /** The Source namespace the package is named under; absent on an older host. */\n namespace?: string | null;\n /** The full name the host uses (`acme/google-drive`); absent on an older host. */\n displayName?: string;\n}\n\nexport type SubmitResult =\n | { status: 'ok'; receipt: SubmissionReceipt }\n /** The bearer token did not authenticate — refresh and retry, once. */\n | { status: 'unauthorized' };\n\nexport async function submitArchive(\n host: string,\n accessToken: string,\n payload: SubmissionPayload,\n): Promise<SubmitResult> {\n const form = new FormData();\n form.append('source', payload.source);\n form.append('slug', payload.slug);\n form.append('version', payload.version);\n if (payload.kind) form.append('kind', payload.kind);\n form.append('manifest', payload.manifest);\n if (payload.availability) form.append('availability', payload.availability);\n form.append(\n 'archive',\n // Copy into a fresh Uint8Array: Buffer is Uint8Array<ArrayBufferLike>,\n // which BlobPart refuses (a SharedArrayBuffer view is not a BlobPart).\n new Blob([new Uint8Array(payload.archive)], { type: 'application/gzip' }),\n 'archive.tgz',\n );\n\n const response = await request(`${host}/api/partner/submissions`, {\n method: 'POST',\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n body: form,\n });\n\n // 401, or the 307 an auth-gated Ekanos route answers with: the token did\n // not authenticate. The caller owns the refresh-and-retry decision.\n if (response.status === 401 || isRedirect(response.status)) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const serverMessage = str(body.error);\n\n if (response.status === 403) {\n throw forbiddenError(\n serverMessage ??\n `${host} refused the submission: this account does not hold the ` +\n `developer seat on source \"${payload.source}\".`,\n `Ask the source operator to grant your account the developer seat for ` +\n `\"${payload.source}\", then retry.`,\n );\n }\n\n if (response.status === 404) {\n throw notFoundError(\n serverMessage ?? `${host} has no source with slug \"${payload.source}\".`,\n `Check the --source value (or the \"source\" field in ekanos.json) ` +\n `against the slug your Ekanos operator gave you.`,\n );\n }\n\n if (response.status === 409 && str(body.code) === 'KIND_MISMATCH') {\n throw invalidStateError(\n serverMessage ??\n `\"${payload.slug}\" is registered as the other kind of package.`,\n `A slug names one kind for its whole life. Pick a different slug, or ` +\n `submit it as the kind it was first registered as.`,\n );\n }\n\n if (response.status === 409 && body.code === 'GLOBAL_NAME_UNAPPROVED') {\n throw globalNameUnapprovedError(payload.slug);\n }\n\n if (response.status === 409 && body.code === 'NAMESPACE_REQUIRED') {\n throw invalidStateError(\n serverMessage ??\n `Source \"${payload.source}\" has no namespace to name \"${payload.slug}\" under.`,\n str(body.hint) ??\n `Ask an Ekanos super-admin to give \"${payload.source}\" a namespace, ` +\n `then retry.`,\n );\n }\n\n if (response.status === 409) {\n throw invalidStateError(\n serverMessage ??\n `Version ${payload.version} of \"${payload.slug}\" is already submitted.`,\n `Submitted versions are immutable. Bump the \"version\" field in ` +\n `package.json and publish again.`,\n );\n }\n\n if (response.status === 400 || response.status === 413) {\n throw validationError(\n serverMessage ?? `${host} rejected the submission as invalid.`,\n `Fix the reported problem and re-run \"ekanos publish\". If the message ` +\n `names the archive, re-check the project layout (\"ekanos validate\").`,\n );\n }\n\n if (!response.ok) {\n throw networkError(\n `${host}/api/partner/submissions responded ${response.status}.`,\n response.status >= 500\n ? `The Ekanos deployment returned a server error. Retry shortly; if ` +\n `it persists, report it with the status code.`\n : `Confirm the host is an Ekanos deployment running a build that ` +\n `includes the partner submissions route.`,\n );\n }\n\n const data = (body.data ?? {}) as Record<string, unknown>;\n const receipt = {\n submissionId: str(data.submissionId),\n slug: str(data.slug),\n version: str(data.version),\n state: str(data.state),\n archiveSha256: str(data.archiveSha256),\n };\n const displayName = str(data.displayName);\n\n if (Object.values(receipt).some((value) => value === null)) {\n throw networkError(\n `${host} accepted the submission but returned an incomplete receipt.`,\n `Confirm the deployment is running an Ekanos build that includes the ` +\n `partner submissions route, then check the submission's state with ` +\n `your operator before re-publishing.`,\n );\n }\n\n return {\n status: 'ok',\n receipt: {\n ...(receipt as SubmissionReceipt),\n ...(data.namespace === null || typeof data.namespace === 'string'\n ? { namespace: data.namespace }\n : {}),\n ...(displayName ? { displayName } : {}),\n },\n };\n}\n\n/**\n * The refusal for a `global` package whose name no super-admin has approved.\n * Shared by the pre-flight (`publish.ts`) and the 409 the host answers with.\n */\nexport function globalNameUnapprovedError(slug: string) {\n return invalidStateError(\n `The global name \"${slug}\" is not approved.`,\n `Run \"ekanos names claim ${slug}\" to request it, or publish restricted ` +\n `(remove availability.mode \"global\" from ekanos.json, or set it to ` +\n `\"restricted\").`,\n );\n}\n\n/**\n * One gate finding, mirroring `@ekanos/integration-schema`'s `Finding` shape\n * (the same one `validate`/`publish` carry in `data.findings`). Declared\n * locally rather than imported: the gate's own report type lives in a\n * server-side toolchain package private to the platform monorepo, which this\n * partner-facing CLI has no business depending on — the wire shape is the\n * only contract.\n *\n * `origin`/`id`/`createdAt`/`resolvedAt` are the additive fields the host's\n * findings-merge endpoint puts on the wire once host-filed findings — CI\n * failures, host observations, a revocation reason — are merged in alongside\n * the gate's own verdict. A gate-only finding still omits them entirely\n * (this CLI's own `validate`/`publish` findings never set `origin`, which\n * the renderer reads as \"gate\"); they only appear on findings read back from\n * `GET /api/partner/submissions?detail=findings`.\n */\nexport interface GateFinding {\n check: string;\n severity: 'error' | 'warn' | 'info';\n file?: string;\n line?: number;\n message: string;\n hint: string;\n /** `'gate' | 'ci' | 'host' | 'revocation'` on the wire; kept as `string` — see the doc comment on `HostFindingRow` in `merge-findings.ts` for why the host stays defensive rather than a narrower literal union here. */\n origin?: string;\n id?: string;\n createdAt?: string;\n resolvedAt?: string | null;\n}\n\nexport interface GateStageResult {\n id: string;\n status: 'pass' | 'fail' | 'skipped';\n durationMs: number;\n}\n\nexport interface SubmissionVersion {\n id: string;\n version: string;\n state: string;\n archiveSha256: string | null;\n submittedAt: string | null;\n gatedAt: string | null;\n /** Present once a gate has run. */\n errorCount: number | null;\n warnCount: number | null;\n /** Present only when the request passed `?detail=findings`. */\n findings?: GateFinding[];\n stages?: GateStageResult[];\n}\n\nexport interface SubmissionListing {\n id: string;\n slug: string;\n name: string | null;\n createdAt: string | null;\n /** Absent on an older host. */\n namespace?: string | null;\n displayName?: string;\n versions: SubmissionVersion[];\n}\n\nexport type ListResult =\n | { status: 'ok'; integrations: SubmissionListing[] }\n /** The bearer token did not authenticate — refresh and retry, once. */\n | { status: 'unauthorized' };\n\n/**\n * List the caller's visible submissions for a source. Same authorization model\n * as `submitArchive`: the seat SELECT policies filter server-side, so a caller\n * with no seat sees an empty list rather than an error.\n *\n * `detail: 'findings'` requests the full per-version findings/stages arrays\n * (used by `status --findings` and `status --watch`); omitted, the server\n * answers with counts only — see the wire contract above.\n */\nexport async function listSubmissions(\n host: string,\n accessToken: string,\n source: string,\n options: { detail?: 'findings' } = {},\n): Promise<ListResult> {\n const query = new URLSearchParams({ source });\n if (options.detail) query.set('detail', options.detail);\n\n const response = await request(\n `${host}/api/partner/submissions?${query.toString()}`,\n {\n method: 'GET',\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n },\n );\n\n if (response.status === 401 || isRedirect(response.status)) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const serverMessage = str(body.error);\n\n if (response.status === 403) {\n throw forbiddenError(\n serverMessage ?? `${host} refused to list submissions for \"${source}\".`,\n `Complete MFA in the browser if the message names it, then run ` +\n `\"ekanos login\" again.`,\n );\n }\n\n if (response.status === 404) {\n throw notFoundError(\n serverMessage ?? `${host} has no source with slug \"${source}\".`,\n `Check the \"source\" field in ekanos.json (or --source) against the ` +\n `slug your Ekanos operator gave you.`,\n );\n }\n\n if (response.status === 400) {\n throw validationError(\n serverMessage ?? `${host} rejected the submissions query.`,\n `Fix the reported problem and re-run \"ekanos status\".`,\n );\n }\n\n if (!response.ok) {\n throw networkError(\n `${host}/api/partner/submissions responded ${response.status}.`,\n response.status >= 500\n ? `The Ekanos deployment returned a server error. Retry shortly; if ` +\n `it persists, report it with the status code.`\n : `Confirm the host is an Ekanos deployment running a build that ` +\n `includes the partner submissions route.`,\n );\n }\n\n const data = (body.data ?? {}) as Record<string, unknown>;\n const rows = Array.isArray(data.integrations) ? data.integrations : [];\n\n const integrations = rows\n .filter(\n (row): row is Record<string, unknown> =>\n typeof row === 'object' && row !== null,\n )\n .map((row) => ({\n id: str(row.id) ?? '',\n slug: str(row.slug) ?? '',\n name: str(row.name),\n createdAt: str(row.createdAt),\n ...(row.namespace === null || typeof row.namespace === 'string'\n ? { namespace: row.namespace }\n : {}),\n ...(str(row.displayName) ? { displayName: str(row.displayName)! } : {}),\n versions: (Array.isArray(row.versions) ? row.versions : [])\n .filter(\n (version): version is Record<string, unknown> =>\n typeof version === 'object' && version !== null,\n )\n .map((version) => ({\n id: str(version.id) ?? '',\n version: str(version.version) ?? '',\n state: str(version.state) ?? '',\n archiveSha256: str(version.archiveSha256),\n submittedAt: str(version.submittedAt),\n gatedAt: str(version.gatedAt),\n errorCount: num(version.errorCount),\n warnCount: num(version.warnCount),\n ...(Array.isArray(version.findings)\n ? { findings: version.findings as GateFinding[] }\n : {}),\n ...(Array.isArray(version.stages)\n ? { stages: version.stages as GateStageResult[] }\n : {}),\n })),\n }));\n\n return { status: 'ok', integrations };\n}\n\n/** Guard for the \"still unauthorized after one refresh\" terminal case. */\nexport function stillUnauthorized(env: AuthEnvironment): never {\n throw authRequiredError(\n `The session for ${env.host} could not authorize the submission.`,\n loginHint(env),\n );\n}\n\nasync function readOptionalJson(\n response: Response,\n): Promise<Record<string, unknown>> {\n try {\n const parsed: unknown = await response.json();\n\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n\nfunction isRedirect(status: number): boolean {\n return status >= 300 && status < 400;\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nfunction num(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null;\n}\n"]}
1
+ {"version":3,"file":"publish-api.js","sourceRoot":"","sources":["../src/publish-api.ts"],"names":[],"mappings":"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","sourcesContent":["import { request } from './auth/ekanos-api';\nimport { loginHint } from './auth/host-trust';\nimport type { AuthEnvironment } from './auth/session';\nimport {\n authRequiredError,\n forbiddenError,\n invalidStateError,\n networkError,\n notFoundError,\n validationError,\n} from './errors';\n\n/**\n * The CLI's client for the Ekanos submission intake route.\n *\n * ---------------------------------------------------------------------------\n * WIRE CONTRACT — the seam between this package and the Ekanos web app.\n *\n * The server side is the host's `/api/partner/submissions` route. As with\n * the auth routes (see `auth/ekanos-api.ts`), the two halves are coupled\n * only by HTTP; tests stub `fetch`, never the route.\n *\n * POST /api/partner/submissions [Authorization: Bearer <access_token>]\n * ← multipart/form-data:\n * source — Ekanos source slug\n * slug — integration (or experience) slug (kebab-case; bare, never namespaced)\n * version — semver\n * kind — \"experience\" for a composed experience; omitted for an\n * integration (an older host never sees the field). Sent\n * only to a host that advertises\n * `capabilities.experienceSubmissions` on the toolchain\n * route.\n * manifest — JSON string (the parsed ekanos.json + declaration\n * summary; for an experience, its built manifest.json)\n * archive — gzipped tarball, ≤ 4 MiB\n * availability — \"global\" | \"restricted\"; sent only when ekanos.json\n * declares it\n * → 201 { ok: true, data: { submissionId, slug, version, state,\n * archiveSha256,\n * // newer hosts only:\n * namespace?: string|null, displayName? } }\n * → 400 { error, code: \"INVALID_REQUEST\" | \"INVALID_ARCHIVE\" }\n * → 401 { error, code: \"AUTH_REQUIRED\" } token absent or expired —\n * refresh once and retry\n * → 403 { error, code: \"FORBIDDEN\" } no developer seat on the source\n * → 404 { error, code: \"SOURCE_NOT_FOUND\" }\n * → 409 { error, code: \"VERSION_EXISTS\" } this (slug, version) is\n * already submitted\n * → 409 { error, code: \"KIND_MISMATCH\" } the slug is registered as\n * the other kind\n * → 409 { error, code: \"GLOBAL_NAME_UNAPPROVED\", hint? }\n * availability \"global\" without\n * an approved global name\n * → 409 { error, code: \"NAMESPACE_REQUIRED\" } the source has no namespace\n * → 413 { error, code: \"ARCHIVE_TOO_LARGE\" }\n * → 3xx treated as 401 (an\n * auth-gated Ekanos page\n * answers 307)\n *\n * GET /api/partner/submissions?source=<slug|id>[&detail=findings]\n * [Authorization: Bearer]\n * → 200 { ok: true, data: { source, integrations: [{ id, slug, name,\n * createdAt, namespace?: string|null, displayName?, versions: [{ id, version, state, archiveSha256,\n * submittedAt, gatedAt,\n * // present once a gate has run (gate_report is non-null):\n * errorCount?, warnCount?,\n * // present ONLY with ?detail=findings, alongside the above:\n * findings?, stages?\n * }] }] } }\n * → 400 { error, code: \"INVALID_REQUEST\" }\n * → 401 { error, code: \"AUTH_REQUIRED\" } refresh once and retry\n * → 403 { error, code: \"MFA_REQUIRED\" }\n * → 404 { error, code: \"SOURCE_NOT_FOUND\" }\n * → 3xx treated as 401\n *\n * `state` walks submitted -> gated|rejected -> dev -> prod -> revoked (a\n * `dev` version is marked `superseded` once a newer version reaches dev),\n * enforced by a database trigger on the host's side (the full transition\n * matrix lives there). `gated`/`dev`/`rejected` are the\n * terminal-for-waiting states `ekanos status --watch` polls for.\n * ---------------------------------------------------------------------------\n */\n\n/**\n * The server's manifest cap — 64 KiB of UTF-8, enforced server-side against\n * `Buffer.byteLength`. Mirrored here so `publish` can refuse an oversized\n * manifest with a clear message instead of the server's generic 400.\n */\nexport const MAX_MANIFEST_BYTES = 64 * 1024;\n\nexport interface SubmissionPayload {\n source: string;\n slug: string;\n /** Omitted for an integration. */\n kind?: 'experience';\n version: string;\n /** JSON string — already serialized by the caller. */\n manifest: string;\n archive: Uint8Array;\n /** Sent only when ekanos.json declares an availability mode. */\n availability?: 'global' | 'restricted';\n}\n\nexport interface SubmissionReceipt {\n submissionId: string;\n slug: string;\n version: string;\n state: string;\n archiveSha256: string;\n /** The Source namespace the package is named under; absent on an older host. */\n namespace?: string | null;\n /** The full name the host uses (`acme/google-drive`); absent on an older host. */\n displayName?: string;\n}\n\nexport type SubmitResult =\n | { status: 'ok'; receipt: SubmissionReceipt }\n /** The bearer token did not authenticate — refresh and retry, once. */\n | { status: 'unauthorized' };\n\nexport async function submitArchive(\n host: string,\n accessToken: string,\n payload: SubmissionPayload,\n): Promise<SubmitResult> {\n const form = new FormData();\n form.append('source', payload.source);\n form.append('slug', payload.slug);\n form.append('version', payload.version);\n if (payload.kind) form.append('kind', payload.kind);\n form.append('manifest', payload.manifest);\n if (payload.availability) form.append('availability', payload.availability);\n form.append(\n 'archive',\n // Copy into a fresh Uint8Array: Buffer is Uint8Array<ArrayBufferLike>,\n // which BlobPart refuses (a SharedArrayBuffer view is not a BlobPart).\n new Blob([new Uint8Array(payload.archive)], { type: 'application/gzip' }),\n 'archive.tgz',\n );\n\n const response = await request(`${host}/api/partner/submissions`, {\n method: 'POST',\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n body: form,\n });\n\n // 401, or the 307 an auth-gated Ekanos route answers with: the token did\n // not authenticate. The caller owns the refresh-and-retry decision.\n if (response.status === 401 || isRedirect(response.status)) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const serverMessage = str(body.error);\n\n if (response.status === 403) {\n throw forbiddenError(\n serverMessage ??\n `${host} refused the submission: this account does not hold the ` +\n `developer seat on source \"${payload.source}\".`,\n `Ask the source operator to grant your account the developer seat for ` +\n `\"${payload.source}\", then retry.`,\n );\n }\n\n if (response.status === 404) {\n throw notFoundError(\n serverMessage ?? `${host} has no source with slug \"${payload.source}\".`,\n `Check the --source value (or the \"source\" field in ekanos.json) ` +\n `against the slug your Ekanos operator gave you.`,\n );\n }\n\n if (response.status === 409 && str(body.code) === 'KIND_MISMATCH') {\n throw invalidStateError(\n serverMessage ??\n `\"${payload.slug}\" is registered as the other kind of package.`,\n `A slug names one kind for its whole life. Pick a different slug, or ` +\n `submit it as the kind it was first registered as.`,\n );\n }\n\n if (response.status === 409 && body.code === 'GLOBAL_NAME_UNAPPROVED') {\n throw globalNameUnapprovedError(payload.slug);\n }\n\n if (response.status === 409 && body.code === 'NAMESPACE_REQUIRED') {\n throw invalidStateError(\n serverMessage ??\n `Source \"${payload.source}\" has no namespace to name \"${payload.slug}\" under.`,\n str(body.hint) ??\n `Ask Ekanos to give \"${payload.source}\" a namespace, then retry.`,\n );\n }\n\n if (response.status === 409) {\n throw invalidStateError(\n serverMessage ??\n `Version ${payload.version} of \"${payload.slug}\" is already submitted.`,\n `Submitted versions are immutable. Bump the \"version\" field in ` +\n `package.json and publish again.`,\n );\n }\n\n if (response.status === 400 || response.status === 413) {\n throw validationError(\n serverMessage ?? `${host} rejected the submission as invalid.`,\n `Fix the reported problem and re-run \"ekanos publish\". If the message ` +\n `names the archive, re-check the project layout (\"ekanos validate\").`,\n );\n }\n\n if (!response.ok) {\n throw networkError(\n `${host}/api/partner/submissions responded ${response.status}.`,\n response.status >= 500\n ? `The Ekanos deployment returned a server error. Retry shortly; if ` +\n `it persists, report it with the status code.`\n : `Confirm the host is an Ekanos deployment running a build that ` +\n `includes the partner submissions route.`,\n );\n }\n\n const data = (body.data ?? {}) as Record<string, unknown>;\n const receipt = {\n submissionId: str(data.submissionId),\n slug: str(data.slug),\n version: str(data.version),\n state: str(data.state),\n archiveSha256: str(data.archiveSha256),\n };\n const displayName = str(data.displayName);\n\n if (Object.values(receipt).some((value) => value === null)) {\n throw networkError(\n `${host} accepted the submission but returned an incomplete receipt.`,\n `Confirm the deployment is running an Ekanos build that includes the ` +\n `partner submissions route, then check the submission's state with ` +\n `your operator before re-publishing.`,\n );\n }\n\n return {\n status: 'ok',\n receipt: {\n ...(receipt as SubmissionReceipt),\n ...(data.namespace === null || typeof data.namespace === 'string'\n ? { namespace: data.namespace }\n : {}),\n ...(displayName ? { displayName } : {}),\n },\n };\n}\n\n/**\n * The refusal for a `global` package whose name no super-admin has approved.\n * Shared by the pre-flight (`publish.ts`) and the 409 the host answers with.\n */\nexport function globalNameUnapprovedError(slug: string) {\n return invalidStateError(\n `The global name \"${slug}\" is not approved.`,\n `Run \"ekanos names claim ${slug}\" to request it, or publish restricted ` +\n `(remove availability.mode \"global\" from ekanos.json, or set it to ` +\n `\"restricted\").`,\n );\n}\n\n/**\n * One gate finding, mirroring `@ekanos/integration-schema`'s `Finding` shape\n * (the same one `validate`/`publish` carry in `data.findings`). Declared\n * locally rather than imported: the gate's own report type lives in a\n * server-side toolchain package private to the platform monorepo, which this\n * partner-facing CLI has no business depending on — the wire shape is the\n * only contract.\n *\n * `origin`/`id`/`createdAt`/`resolvedAt` are the additive fields the host's\n * findings-merge endpoint puts on the wire once host-filed findings — CI\n * failures, host observations, a revocation reason — are merged in alongside\n * the gate's own verdict. A gate-only finding still omits them entirely\n * (this CLI's own `validate`/`publish` findings never set `origin`, which\n * the renderer reads as \"gate\"); they only appear on findings read back from\n * `GET /api/partner/submissions?detail=findings`.\n */\nexport interface GateFinding {\n check: string;\n severity: 'error' | 'warn' | 'info';\n file?: string;\n line?: number;\n message: string;\n hint: string;\n /** `'gate' | 'ci' | 'host' | 'revocation'` on the wire; kept as `string` — see the doc comment on `HostFindingRow` in `merge-findings.ts` for why the host stays defensive rather than a narrower literal union here. */\n origin?: string;\n id?: string;\n createdAt?: string;\n resolvedAt?: string | null;\n}\n\nexport interface GateStageResult {\n id: string;\n status: 'pass' | 'fail' | 'skipped';\n durationMs: number;\n}\n\nexport interface SubmissionVersion {\n id: string;\n version: string;\n state: string;\n archiveSha256: string | null;\n submittedAt: string | null;\n gatedAt: string | null;\n /** Present once a gate has run. */\n errorCount: number | null;\n warnCount: number | null;\n /** Present only when the request passed `?detail=findings`. */\n findings?: GateFinding[];\n stages?: GateStageResult[];\n}\n\nexport interface SubmissionListing {\n id: string;\n slug: string;\n name: string | null;\n createdAt: string | null;\n /** Absent on an older host. */\n namespace?: string | null;\n displayName?: string;\n versions: SubmissionVersion[];\n}\n\nexport type ListResult =\n | { status: 'ok'; integrations: SubmissionListing[] }\n /** The bearer token did not authenticate — refresh and retry, once. */\n | { status: 'unauthorized' };\n\n/**\n * List the caller's visible submissions for a source. Same authorization model\n * as `submitArchive`: the seat SELECT policies filter server-side, so a caller\n * with no seat sees an empty list rather than an error.\n *\n * `detail: 'findings'` requests the full per-version findings/stages arrays\n * (used by `status --findings` and `status --watch`); omitted, the server\n * answers with counts only — see the wire contract above.\n */\nexport async function listSubmissions(\n host: string,\n accessToken: string,\n source: string,\n options: { detail?: 'findings' } = {},\n): Promise<ListResult> {\n const query = new URLSearchParams({ source });\n if (options.detail) query.set('detail', options.detail);\n\n const response = await request(\n `${host}/api/partner/submissions?${query.toString()}`,\n {\n method: 'GET',\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n },\n );\n\n if (response.status === 401 || isRedirect(response.status)) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const serverMessage = str(body.error);\n\n if (response.status === 403) {\n throw forbiddenError(\n serverMessage ?? `${host} refused to list submissions for \"${source}\".`,\n `Complete MFA in the browser if the message names it, then run ` +\n `\"ekanos login\" again.`,\n );\n }\n\n if (response.status === 404) {\n throw notFoundError(\n serverMessage ?? `${host} has no source with slug \"${source}\".`,\n `Check the \"source\" field in ekanos.json (or --source) against the ` +\n `slug your Ekanos operator gave you.`,\n );\n }\n\n if (response.status === 400) {\n throw validationError(\n serverMessage ?? `${host} rejected the submissions query.`,\n `Fix the reported problem and re-run \"ekanos status\".`,\n );\n }\n\n if (!response.ok) {\n throw networkError(\n `${host}/api/partner/submissions responded ${response.status}.`,\n response.status >= 500\n ? `The Ekanos deployment returned a server error. Retry shortly; if ` +\n `it persists, report it with the status code.`\n : `Confirm the host is an Ekanos deployment running a build that ` +\n `includes the partner submissions route.`,\n );\n }\n\n const data = (body.data ?? {}) as Record<string, unknown>;\n const rows = Array.isArray(data.integrations) ? data.integrations : [];\n\n const integrations = rows\n .filter(\n (row): row is Record<string, unknown> =>\n typeof row === 'object' && row !== null,\n )\n .map((row) => ({\n id: str(row.id) ?? '',\n slug: str(row.slug) ?? '',\n name: str(row.name),\n createdAt: str(row.createdAt),\n ...(row.namespace === null || typeof row.namespace === 'string'\n ? { namespace: row.namespace }\n : {}),\n ...(str(row.displayName) ? { displayName: str(row.displayName)! } : {}),\n versions: (Array.isArray(row.versions) ? row.versions : [])\n .filter(\n (version): version is Record<string, unknown> =>\n typeof version === 'object' && version !== null,\n )\n .map((version) => ({\n id: str(version.id) ?? '',\n version: str(version.version) ?? '',\n state: str(version.state) ?? '',\n archiveSha256: str(version.archiveSha256),\n submittedAt: str(version.submittedAt),\n gatedAt: str(version.gatedAt),\n errorCount: num(version.errorCount),\n warnCount: num(version.warnCount),\n ...(Array.isArray(version.findings)\n ? { findings: version.findings as GateFinding[] }\n : {}),\n ...(Array.isArray(version.stages)\n ? { stages: version.stages as GateStageResult[] }\n : {}),\n })),\n }));\n\n return { status: 'ok', integrations };\n}\n\n/** Guard for the \"still unauthorized after one refresh\" terminal case. */\nexport function stillUnauthorized(env: AuthEnvironment): never {\n throw authRequiredError(\n `The session for ${env.host} could not authorize the submission.`,\n loginHint(env),\n );\n}\n\nasync function readOptionalJson(\n response: Response,\n): Promise<Record<string, unknown>> {\n try {\n const parsed: unknown = await response.json();\n\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n\nfunction isRedirect(status: number): boolean {\n return status >= 300 && status < 400;\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nfunction num(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null;\n}\n"]}
package/dist/runs-api.js CHANGED
@@ -99,7 +99,7 @@ async function call(host, accessToken, schema, init) {
99
99
  : code === 'RESUME_FAILED'
100
100
  ? 'The decision is saved; the run picks it up shortly. Check with "ekanos runs show <run-id>".'
101
101
  : code === 'AGENTS_EXPERIENCE_OFF'
102
- ? 'Ask Ekanos to turn on the Agents Experience for this Source: only a super-admin can ("ekanos sources products" shows it as off).'
102
+ ? 'Ask Ekanos to turn on the Agents Experience for this Source: only Ekanos can ("ekanos sources products" shows it as off).'
103
103
  : 'Run "ekanos teams agents list <team>": the agent may be paused or still need a connection.');
104
104
  }
105
105
  if (response.status === 400) {
@@ -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';\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"]}
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 Ekanos 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"]}
@@ -206,7 +206,7 @@ const STYLE = [
206
206
  'label{display:block;font-weight:600;font-size:.9rem;margin:0 0 4px}',
207
207
  '.optional{font-weight:400;opacity:.6}',
208
208
  '.help{margin:0 0 6px;font-size:.85rem;opacity:.7}',
209
- 'input{box-sizing:border-box;width:100%;padding:8px 10px;font:inherit;border:1px solid #8888;border-radius:6px;background:transparent;color:inherit}',
209
+ 'input{box-sizing:border-box;width:100%;padding:8px 10px;font:inherit;border:1px solid oklch(0.6268 0 0 / 0.53);border-radius:6px;background:transparent;color:inherit}',
210
210
  'button{margin-top:8px;padding:8px 16px;font:inherit;font-weight:600;border:1px solid currentColor;border-radius:6px;background:transparent;color:inherit;cursor:pointer}',
211
211
  ].join('');
212
212
  //# sourceMappingURL=secret-form.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"secret-form.js","sourceRoot":"","sources":["../src/secret-form.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes, timingSafeEqual } from 'node:crypto';\nimport * as http from 'node:http';\nimport type { AddressInfo } from 'node:net';\n\nimport { validationError } from './errors';\n\n/**\n * The \"little GUI\" for a secret: a one-shot form served by the CLI itself on\n * this computer, for a run with no terminal to type into (a coding agent, a\n * script) but a person at the desktop. The person types the secret into\n * their own browser; it goes from there to this process and on to the host,\n * and never to stdout, stderr, a log or the `--json` envelope — so an agent\n * driving the CLI starts the form and never sees what was entered.\n *\n * What keeps it to that one person and that one submission:\n *\n * - **Loopback only.** It listens on 127.0.0.1, on a port the OS picks.\n * - **An unguessable path.** 256 random bits; any other path is a 404.\n * - **Host and Origin.** A request whose `Host` is not exactly\n * `127.0.0.1:<port>` is refused (a DNS-rebinding page names its own\n * host), and a submission must carry this form's `Origin` — so another\n * page in the same browser cannot post to it.\n * - **One use, short life.** The first good submission closes the server;\n * so does the timeout, which fails the command rather than waiting on.\n * - **A page that loads nothing.** No script, no external asset: a CSP of\n * `default-src 'none'` with one nonce'd stylesheet, never framed, never\n * cached, no referrer.\n */\n\nexport interface SecretFormField {\n name: string;\n label: string;\n sensitive: boolean;\n description?: string;\n required?: boolean;\n /** A value is saved already: leaving it empty keeps it. */\n set?: boolean;\n /** A non-secret field's saved value, shown as the placeholder. Never a secret. */\n value?: string | null;\n}\n\nexport interface SecretFormOptions {\n title: string;\n fields: readonly SecretFormField[];\n /** Opens the form's URL in the person's browser. */\n open: (url: string) => void;\n /** How long to wait for the submission (default five minutes). */\n timeoutMs?: number;\n}\n\nexport const SECRET_FORM_TIMEOUT_MS = 5 * 60 * 1000;\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Serve the form, open it, and resolve with what was submitted: each field's\n * value by name, empty ones left out. Rejects on the timeout.\n */\nexport function collectWithSecretForm(\n options: SecretFormOptions,\n): Promise<Record<string, string>> {\n const token = randomBytes(32).toString('base64url');\n const nonce = randomBytes(16).toString('base64');\n const timeoutMs = options.timeoutMs ?? SECRET_FORM_TIMEOUT_MS;\n\n return new Promise((resolve, reject) => {\n let settled = false;\n let origin = '';\n let hostHeader = '';\n\n const server = http.createServer((request, response) => {\n handle(request, response).catch(() => {\n if (!response.headersSent)\n send(\n response,\n 500,\n page('Something went wrong', 'Return to your terminal.'),\n );\n });\n });\n\n const finish = (outcome: () => void) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n server.close();\n server.closeAllConnections();\n outcome();\n };\n\n const timer = setTimeout(() => {\n finish(() =>\n reject(\n validationError(\n `The secure form was not submitted within ${duration(timeoutMs)}; nothing was saved.`,\n 'Run the command again for a fresh form, or pass the secret with --secret-env, --secret-file or --secret-stdin.',\n ),\n ),\n );\n }, timeoutMs);\n\n const send = (\n response: http.ServerResponse,\n status: number,\n body: string,\n ) => {\n response.writeHead(status, {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n 'content-security-policy': `default-src 'none'; style-src 'nonce-${nonce}'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'`,\n 'x-frame-options': 'DENY',\n 'x-content-type-options': 'nosniff',\n 'referrer-policy': 'no-referrer',\n 'cross-origin-resource-policy': 'same-origin',\n });\n response.end(body);\n };\n\n const page = (heading: string, text: string, inner = '') =>\n [\n '<!doctype html>',\n '<html lang=\"en\"><head><meta charset=\"utf-8\">',\n '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">',\n '<meta name=\"referrer\" content=\"no-referrer\">',\n `<title>${escapeHtml(heading)} · ekanos</title>`,\n `<style nonce=\"${nonce}\">${STYLE}</style>`,\n '</head><body><main>',\n `<h1>${escapeHtml(heading)}</h1>`,\n `<p class=\"lede\">${escapeHtml(text)}</p>`,\n inner,\n '</main></body></html>',\n ].join('');\n\n const isOurPath = (pathname: string): boolean => {\n const expected = Buffer.from(`/${token}`);\n const given = Buffer.from(pathname);\n return (\n given.length === expected.length && timingSafeEqual(given, expected)\n );\n };\n\n async function handle(\n request: http.IncomingMessage,\n response: http.ServerResponse,\n ): Promise<void> {\n if (settled) {\n send(\n response,\n 410,\n page('This form is closed', 'Return to your terminal.'),\n );\n return;\n }\n if (request.headers.host !== hostHeader) {\n send(\n response,\n 421,\n page('Wrong address', 'Open the link the ekanos CLI opened.'),\n );\n return;\n }\n const pathname = (request.url ?? '').split('?')[0] ?? '';\n if (!isOurPath(pathname)) {\n send(\n response,\n 404,\n page('Not found', 'Open the link the ekanos CLI opened.'),\n );\n return;\n }\n\n if (request.method === 'GET') {\n send(response, 200, formPage());\n return;\n }\n if (request.method !== 'POST') {\n response.setHeader('allow', 'GET, POST');\n send(response, 405, page('Not allowed', 'Return to your terminal.'));\n return;\n }\n\n const fetchSite = request.headers['sec-fetch-site'];\n if (\n request.headers.origin !== origin ||\n (fetchSite !== undefined && fetchSite !== 'same-origin')\n ) {\n send(\n response,\n 403,\n page('Refused', 'This form only accepts what is typed into it.'),\n );\n return;\n }\n if (\n !(request.headers['content-type'] ?? '')\n .toLowerCase()\n .startsWith('application/x-www-form-urlencoded')\n ) {\n send(response, 415, page('Refused', 'Submit the form itself.'));\n return;\n }\n\n const body = await readBody(request);\n if (body === null) {\n send(response, 413, page('Too large', 'Return to your terminal.'));\n return;\n }\n const params = new URLSearchParams(body);\n const values: Record<string, string> = {};\n options.fields.forEach((field, index) => {\n const value = (params.get(`f${index}`) ?? '').trim();\n if (value) values[field.name] = value;\n });\n\n send(\n response,\n 200,\n page(\n 'Received',\n 'The ekanos CLI has what you entered and is saving it. You can close this tab and return to your terminal.',\n ),\n );\n finish(() => resolve(values));\n }\n\n function formPage(): string {\n const inputs = options.fields\n .map((field, index) => {\n const id = `f${index}`;\n const placeholder = field.sensitive\n ? field.set\n ? 'Saved — leave empty to keep it'\n : ''\n : field.set\n ? field.value\n ? `Current: ${field.value} — leave empty to keep it`\n : 'Saved — leave empty to keep it'\n : '';\n const required = field.required && !field.set;\n return [\n '<div class=\"field\">',\n `<label for=\"${id}\">${escapeHtml(field.label)}${required ? '' : ' <span class=\"optional\">(optional)</span>'}</label>`,\n field.description\n ? `<p class=\"help\" id=\"${id}-help\">${escapeHtml(field.description)}</p>`\n : '',\n `<input id=\"${id}\" name=\"${id}\" type=\"${field.sensitive ? 'password' : 'text'}\"`,\n ' autocomplete=\"off\" autocapitalize=\"off\" spellcheck=\"false\"',\n placeholder ? ` placeholder=\"${escapeHtml(placeholder)}\"` : '',\n field.description ? ` aria-describedby=\"${id}-help\"` : '',\n required ? ' required' : '',\n index === 0 ? ' autofocus' : '',\n '>',\n '</div>',\n ].join('');\n })\n .join('');\n return page(\n options.title,\n 'This page is served by the ekanos CLI on your own computer. What you enter goes only to it, then to Ekanos over HTTPS — never to your terminal, a log or the agent that ran the command. It works once and closes.',\n `<form method=\"post\" action=\"/${token}\">${inputs}<button type=\"submit\">Save</button></form>`,\n );\n }\n\n server.on('error', (error) => finish(() => reject(error)));\n server.listen(0, '127.0.0.1', () => {\n const { port } = server.address() as AddressInfo;\n origin = `http://127.0.0.1:${port}`;\n hostHeader = `127.0.0.1:${port}`;\n try {\n options.open(`${origin}/${token}`);\n } catch {\n // The timeout still ends the wait.\n }\n });\n });\n}\n\nfunction readBody(request: http.IncomingMessage): Promise<string | null> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let tooLarge = false;\n request.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n tooLarge = true;\n return;\n }\n chunks.push(chunk);\n });\n request.on('end', () =>\n resolve(tooLarge ? null : Buffer.concat(chunks).toString('utf8')),\n );\n request.on('error', reject);\n });\n}\n\nfunction duration(ms: number): string {\n if (ms < 60_000) {\n const seconds = Math.max(1, Math.ceil(ms / 1000));\n return `${seconds} second${seconds === 1 ? '' : 's'}`;\n }\n const minutes = Math.round(ms / 60_000);\n return `${minutes} minute${minutes === 1 ? '' : 's'}`;\n}\n\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\nconst STYLE = [\n ':root{color-scheme:light dark;font-family:ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",sans-serif;line-height:1.5}',\n 'body{margin:0;display:flex;justify-content:center;padding:48px 16px}',\n 'main{width:100%;max-width:440px}',\n 'h1{font-size:1.25rem;margin:0 0 8px}',\n '.lede{margin:0 0 24px;opacity:.75;font-size:.9rem}',\n '.field{margin:0 0 16px}',\n 'label{display:block;font-weight:600;font-size:.9rem;margin:0 0 4px}',\n '.optional{font-weight:400;opacity:.6}',\n '.help{margin:0 0 6px;font-size:.85rem;opacity:.7}',\n 'input{box-sizing:border-box;width:100%;padding:8px 10px;font:inherit;border:1px solid #8888;border-radius:6px;background:transparent;color:inherit}',\n 'button{margin-top:8px;padding:8px 16px;font:inherit;font-weight:600;border:1px solid currentColor;border-radius:6px;background:transparent;color:inherit;cursor:pointer}',\n].join('');\n"]}
1
+ {"version":3,"file":"secret-form.js","sourceRoot":"","sources":["../src/secret-form.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes, timingSafeEqual } from 'node:crypto';\nimport * as http from 'node:http';\nimport type { AddressInfo } from 'node:net';\n\nimport { validationError } from './errors';\n\n/**\n * The \"little GUI\" for a secret: a one-shot form served by the CLI itself on\n * this computer, for a run with no terminal to type into (a coding agent, a\n * script) but a person at the desktop. The person types the secret into\n * their own browser; it goes from there to this process and on to the host,\n * and never to stdout, stderr, a log or the `--json` envelope — so an agent\n * driving the CLI starts the form and never sees what was entered.\n *\n * What keeps it to that one person and that one submission:\n *\n * - **Loopback only.** It listens on 127.0.0.1, on a port the OS picks.\n * - **An unguessable path.** 256 random bits; any other path is a 404.\n * - **Host and Origin.** A request whose `Host` is not exactly\n * `127.0.0.1:<port>` is refused (a DNS-rebinding page names its own\n * host), and a submission must carry this form's `Origin` — so another\n * page in the same browser cannot post to it.\n * - **One use, short life.** The first good submission closes the server;\n * so does the timeout, which fails the command rather than waiting on.\n * - **A page that loads nothing.** No script, no external asset: a CSP of\n * `default-src 'none'` with one nonce'd stylesheet, never framed, never\n * cached, no referrer.\n */\n\nexport interface SecretFormField {\n name: string;\n label: string;\n sensitive: boolean;\n description?: string;\n required?: boolean;\n /** A value is saved already: leaving it empty keeps it. */\n set?: boolean;\n /** A non-secret field's saved value, shown as the placeholder. Never a secret. */\n value?: string | null;\n}\n\nexport interface SecretFormOptions {\n title: string;\n fields: readonly SecretFormField[];\n /** Opens the form's URL in the person's browser. */\n open: (url: string) => void;\n /** How long to wait for the submission (default five minutes). */\n timeoutMs?: number;\n}\n\nexport const SECRET_FORM_TIMEOUT_MS = 5 * 60 * 1000;\nconst MAX_BODY_BYTES = 64 * 1024;\n\n/**\n * Serve the form, open it, and resolve with what was submitted: each field's\n * value by name, empty ones left out. Rejects on the timeout.\n */\nexport function collectWithSecretForm(\n options: SecretFormOptions,\n): Promise<Record<string, string>> {\n const token = randomBytes(32).toString('base64url');\n const nonce = randomBytes(16).toString('base64');\n const timeoutMs = options.timeoutMs ?? SECRET_FORM_TIMEOUT_MS;\n\n return new Promise((resolve, reject) => {\n let settled = false;\n let origin = '';\n let hostHeader = '';\n\n const server = http.createServer((request, response) => {\n handle(request, response).catch(() => {\n if (!response.headersSent)\n send(\n response,\n 500,\n page('Something went wrong', 'Return to your terminal.'),\n );\n });\n });\n\n const finish = (outcome: () => void) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n server.close();\n server.closeAllConnections();\n outcome();\n };\n\n const timer = setTimeout(() => {\n finish(() =>\n reject(\n validationError(\n `The secure form was not submitted within ${duration(timeoutMs)}; nothing was saved.`,\n 'Run the command again for a fresh form, or pass the secret with --secret-env, --secret-file or --secret-stdin.',\n ),\n ),\n );\n }, timeoutMs);\n\n const send = (\n response: http.ServerResponse,\n status: number,\n body: string,\n ) => {\n response.writeHead(status, {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n 'content-security-policy': `default-src 'none'; style-src 'nonce-${nonce}'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'`,\n 'x-frame-options': 'DENY',\n 'x-content-type-options': 'nosniff',\n 'referrer-policy': 'no-referrer',\n 'cross-origin-resource-policy': 'same-origin',\n });\n response.end(body);\n };\n\n const page = (heading: string, text: string, inner = '') =>\n [\n '<!doctype html>',\n '<html lang=\"en\"><head><meta charset=\"utf-8\">',\n '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">',\n '<meta name=\"referrer\" content=\"no-referrer\">',\n `<title>${escapeHtml(heading)} · ekanos</title>`,\n `<style nonce=\"${nonce}\">${STYLE}</style>`,\n '</head><body><main>',\n `<h1>${escapeHtml(heading)}</h1>`,\n `<p class=\"lede\">${escapeHtml(text)}</p>`,\n inner,\n '</main></body></html>',\n ].join('');\n\n const isOurPath = (pathname: string): boolean => {\n const expected = Buffer.from(`/${token}`);\n const given = Buffer.from(pathname);\n return (\n given.length === expected.length && timingSafeEqual(given, expected)\n );\n };\n\n async function handle(\n request: http.IncomingMessage,\n response: http.ServerResponse,\n ): Promise<void> {\n if (settled) {\n send(\n response,\n 410,\n page('This form is closed', 'Return to your terminal.'),\n );\n return;\n }\n if (request.headers.host !== hostHeader) {\n send(\n response,\n 421,\n page('Wrong address', 'Open the link the ekanos CLI opened.'),\n );\n return;\n }\n const pathname = (request.url ?? '').split('?')[0] ?? '';\n if (!isOurPath(pathname)) {\n send(\n response,\n 404,\n page('Not found', 'Open the link the ekanos CLI opened.'),\n );\n return;\n }\n\n if (request.method === 'GET') {\n send(response, 200, formPage());\n return;\n }\n if (request.method !== 'POST') {\n response.setHeader('allow', 'GET, POST');\n send(response, 405, page('Not allowed', 'Return to your terminal.'));\n return;\n }\n\n const fetchSite = request.headers['sec-fetch-site'];\n if (\n request.headers.origin !== origin ||\n (fetchSite !== undefined && fetchSite !== 'same-origin')\n ) {\n send(\n response,\n 403,\n page('Refused', 'This form only accepts what is typed into it.'),\n );\n return;\n }\n if (\n !(request.headers['content-type'] ?? '')\n .toLowerCase()\n .startsWith('application/x-www-form-urlencoded')\n ) {\n send(response, 415, page('Refused', 'Submit the form itself.'));\n return;\n }\n\n const body = await readBody(request);\n if (body === null) {\n send(response, 413, page('Too large', 'Return to your terminal.'));\n return;\n }\n const params = new URLSearchParams(body);\n const values: Record<string, string> = {};\n options.fields.forEach((field, index) => {\n const value = (params.get(`f${index}`) ?? '').trim();\n if (value) values[field.name] = value;\n });\n\n send(\n response,\n 200,\n page(\n 'Received',\n 'The ekanos CLI has what you entered and is saving it. You can close this tab and return to your terminal.',\n ),\n );\n finish(() => resolve(values));\n }\n\n function formPage(): string {\n const inputs = options.fields\n .map((field, index) => {\n const id = `f${index}`;\n const placeholder = field.sensitive\n ? field.set\n ? 'Saved — leave empty to keep it'\n : ''\n : field.set\n ? field.value\n ? `Current: ${field.value} — leave empty to keep it`\n : 'Saved — leave empty to keep it'\n : '';\n const required = field.required && !field.set;\n return [\n '<div class=\"field\">',\n `<label for=\"${id}\">${escapeHtml(field.label)}${required ? '' : ' <span class=\"optional\">(optional)</span>'}</label>`,\n field.description\n ? `<p class=\"help\" id=\"${id}-help\">${escapeHtml(field.description)}</p>`\n : '',\n `<input id=\"${id}\" name=\"${id}\" type=\"${field.sensitive ? 'password' : 'text'}\"`,\n ' autocomplete=\"off\" autocapitalize=\"off\" spellcheck=\"false\"',\n placeholder ? ` placeholder=\"${escapeHtml(placeholder)}\"` : '',\n field.description ? ` aria-describedby=\"${id}-help\"` : '',\n required ? ' required' : '',\n index === 0 ? ' autofocus' : '',\n '>',\n '</div>',\n ].join('');\n })\n .join('');\n return page(\n options.title,\n 'This page is served by the ekanos CLI on your own computer. What you enter goes only to it, then to Ekanos over HTTPS — never to your terminal, a log or the agent that ran the command. It works once and closes.',\n `<form method=\"post\" action=\"/${token}\">${inputs}<button type=\"submit\">Save</button></form>`,\n );\n }\n\n server.on('error', (error) => finish(() => reject(error)));\n server.listen(0, '127.0.0.1', () => {\n const { port } = server.address() as AddressInfo;\n origin = `http://127.0.0.1:${port}`;\n hostHeader = `127.0.0.1:${port}`;\n try {\n options.open(`${origin}/${token}`);\n } catch {\n // The timeout still ends the wait.\n }\n });\n });\n}\n\nfunction readBody(request: http.IncomingMessage): Promise<string | null> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let tooLarge = false;\n request.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n tooLarge = true;\n return;\n }\n chunks.push(chunk);\n });\n request.on('end', () =>\n resolve(tooLarge ? null : Buffer.concat(chunks).toString('utf8')),\n );\n request.on('error', reject);\n });\n}\n\nfunction duration(ms: number): string {\n if (ms < 60_000) {\n const seconds = Math.max(1, Math.ceil(ms / 1000));\n return `${seconds} second${seconds === 1 ? '' : 's'}`;\n }\n const minutes = Math.round(ms / 60_000);\n return `${minutes} minute${minutes === 1 ? '' : 's'}`;\n}\n\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\nconst STYLE = [\n ':root{color-scheme:light dark;font-family:ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",sans-serif;line-height:1.5}',\n 'body{margin:0;display:flex;justify-content:center;padding:48px 16px}',\n 'main{width:100%;max-width:440px}',\n 'h1{font-size:1.25rem;margin:0 0 8px}',\n '.lede{margin:0 0 24px;opacity:.75;font-size:.9rem}',\n '.field{margin:0 0 16px}',\n 'label{display:block;font-weight:600;font-size:.9rem;margin:0 0 4px}',\n '.optional{font-weight:400;opacity:.6}',\n '.help{margin:0 0 6px;font-size:.85rem;opacity:.7}',\n 'input{box-sizing:border-box;width:100%;padding:8px 10px;font:inherit;border:1px solid oklch(0.6268 0 0 / 0.53);border-radius:6px;background:transparent;color:inherit}',\n 'button{margin-top:8px;padding:8px 16px;font:inherit;font-weight:600;border:1px solid currentColor;border-radius:6px;background:transparent;color:inherit;cursor:pointer}',\n].join('');\n"]}
@@ -79,7 +79,7 @@ async function call(host, accessToken, schema, init) {
79
79
  throw authRequiredError(message !== null && message !== void 0 ? message : `${host} requires multi-factor authentication.`, 'Complete MFA in the browser, then run "ekanos login" again.');
80
80
  }
81
81
  if (response.status === 403 && code === 'SUPER_ADMIN_REQUIRED') {
82
- throw forbiddenError(message !== null && message !== void 0 ? message : 'Only a super-admin enables this product for a Source.', 'A Source admin switches only the integrations their Source built. Anything else a super-admin does in the browser: /admin/sources → the Source → Products (Experiences are switched on the same page).');
82
+ throw forbiddenError(message !== null && message !== void 0 ? message : 'Only Ekanos enables this product for a Source.', 'A Source admin switches only the integrations their Source built. Ask Ekanos to switch anything else.');
83
83
  }
84
84
  if (response.status === 403 && code === 'SOURCE_ADMIN_REQUIRED') {
85
85
  throw forbiddenError(message !== null && message !== void 0 ? message : 'Switching a product needs a Source admin seat on the Source.', 'Run "ekanos sources" to see your seats; a Source admin grants the seat.');
@@ -1 +1 @@
1
- {"version":3,"file":"source-products-api.js","sourceRoot":"","sources":["../src/source-products-api.ts"],"names":[],"mappings":"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","sourcesContent":["import { 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 Source's products (`ekanos sources products`).\n *\n * GET /api/partner/sources/products?source=<slug|id>\n * → { source, products: [{ slug, name, kind, enabled, lever, registered, owned }],\n * canEnable, canEnableOwned }\n * PUT /api/partner/sources/products/<product>?source=<slug|id> enable\n * DELETE /api/partner/sources/products/<product>?source=<slug|id> disable\n * → { source, product, changed }\n *\n * → 401 / 3xx refresh once and retry\n * → 403 SOURCE_ADMIN_REQUIRED no Source admin seat on the Source\n * → 403 SUPER_ADMIN_REQUIRED not an integration the Source built: a\n * super-admin grant, which an `ekanos login`\n * session never carries\n * → 409 REFUSED a dependency or demo-only guard refused\n *\n * The shapes are read here rather than from `@ekanos/integration-schema`, so\n * this CLI works against a host without a schema release.\n */\n\nconst SourceRefSchema = z.object({\n id: z.string(),\n slug: z.string(),\n name: z.string().nullable(),\n});\n\nexport const SourceProductSchema = z.object({\n slug: z.string(),\n name: z.string(),\n kind: z.enum(['feature', 'experience', 'integration']),\n enabled: z.boolean(),\n lever: z.enum(['flag', 'plan', 'entitlement']),\n registered: z.boolean(),\n /** The Source built it, so its Source admin may switch it. Absent on an\n * older host, which allowed no Source admin to. */\n owned: z.boolean().optional().default(false),\n});\nexport type SourceProduct = z.infer<typeof SourceProductSchema>;\n\nconst ListSchema = z.object({\n source: SourceRefSchema,\n products: z.array(SourceProductSchema),\n canEnable: z.boolean(),\n canEnableOwned: z.boolean().optional().default(false),\n});\nconst ChangeSchema = z.object({\n source: SourceRefSchema,\n product: SourceProductSchema,\n changed: z.boolean(),\n});\n\nexport type SourceProductsApiResult<T> =\n | { status: 'ok'; data: T }\n | { status: 'unauthorized' };\n\nexport function listSourceProducts(\n host: string,\n token: string,\n source: string,\n) {\n return call(host, token, ListSchema, {\n method: 'GET',\n path: `/api/partner/sources/products?${new URLSearchParams({ source })}`,\n });\n}\n\nexport function setSourceProduct(\n host: string,\n token: string,\n input: { source: string; product: string; enabled: boolean },\n) {\n return call(host, token, ChangeSchema, {\n method: input.enabled ? 'PUT' : 'DELETE',\n path: `/api/partner/sources/products/${encodeURIComponent(input.product)}?${new URLSearchParams({ source: input.source })}`,\n });\n}\n\nasync function call<T>(\n host: string,\n accessToken: string,\n schema: z.ZodType<T>,\n init: { method: 'GET' | 'PUT' | 'DELETE'; path: string },\n): Promise<SourceProductsApiResult<T>> {\n const response = await request(`${host}${init.path}`, {\n method: init.method,\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\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 && code === 'SUPER_ADMIN_REQUIRED') {\n throw forbiddenError(\n message ?? 'Only a super-admin enables this product for a Source.',\n 'A Source admin switches only the integrations their Source built. Anything else a super-admin does in the browser: /admin/sources → the Source → Products (Experiences are switched on the same page).',\n );\n }\n if (response.status === 403 && code === 'SOURCE_ADMIN_REQUIRED') {\n throw forbiddenError(\n message ?? 'Switching a product needs a Source admin seat on the Source.',\n 'Run \"ekanos sources\" to see your seats; a Source admin grants the seat.',\n );\n }\n if (response.status === 403) {\n throw forbiddenError(\n message ?? 'Not permitted on this Source.',\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 === 'PRODUCT_NOT_FOUND'\n ? '\"ekanos sources products\" lists the catalog.'\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 409) {\n throw invalidStateError(\n message ?? 'The host refused the change.',\n '\"ekanos sources products\" shows what this Source has.',\n );\n }\n if (response.status === 400) {\n throw usageError(\n message ?? 'The host refused the request.',\n 'See \"ekanos sources --help\".',\n );\n }\n if (!response.ok) {\n throw networkError(\n `${host}${init.path.split('?')[0]} 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 source products routes.',\n );\n }\n\n const parsed = schema.safeParse(body.data);\n if (!parsed.success) {\n throw networkError(\n `${host}/api/partner/sources/products answered in a shape this CLI does not read.`,\n 'Run \"ekanos upgrade\" — the CLI and the host disagree on the source products 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":"source-products-api.js","sourceRoot":"","sources":["../src/source-products-api.ts"],"names":[],"mappings":"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","sourcesContent":["import { 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 Source's products (`ekanos sources products`).\n *\n * GET /api/partner/sources/products?source=<slug|id>\n * → { source, products: [{ slug, name, kind, enabled, lever, registered, owned }],\n * canEnable, canEnableOwned }\n * PUT /api/partner/sources/products/<product>?source=<slug|id> enable\n * DELETE /api/partner/sources/products/<product>?source=<slug|id> disable\n * → { source, product, changed }\n *\n * → 401 / 3xx refresh once and retry\n * → 403 SOURCE_ADMIN_REQUIRED no Source admin seat on the Source\n * → 403 SUPER_ADMIN_REQUIRED not an integration the Source built: a\n * super-admin grant, which an `ekanos login`\n * session never carries\n * → 409 REFUSED a dependency or demo-only guard refused\n *\n * The shapes are read here rather than from `@ekanos/integration-schema`, so\n * this CLI works against a host without a schema release.\n */\n\nconst SourceRefSchema = z.object({\n id: z.string(),\n slug: z.string(),\n name: z.string().nullable(),\n});\n\nexport const SourceProductSchema = z.object({\n slug: z.string(),\n name: z.string(),\n kind: z.enum(['feature', 'experience', 'integration']),\n enabled: z.boolean(),\n lever: z.enum(['flag', 'plan', 'entitlement']),\n registered: z.boolean(),\n /** The Source built it, so its Source admin may switch it. Absent on an\n * older host, which allowed no Source admin to. */\n owned: z.boolean().optional().default(false),\n});\nexport type SourceProduct = z.infer<typeof SourceProductSchema>;\n\nconst ListSchema = z.object({\n source: SourceRefSchema,\n products: z.array(SourceProductSchema),\n canEnable: z.boolean(),\n canEnableOwned: z.boolean().optional().default(false),\n});\nconst ChangeSchema = z.object({\n source: SourceRefSchema,\n product: SourceProductSchema,\n changed: z.boolean(),\n});\n\nexport type SourceProductsApiResult<T> =\n | { status: 'ok'; data: T }\n | { status: 'unauthorized' };\n\nexport function listSourceProducts(\n host: string,\n token: string,\n source: string,\n) {\n return call(host, token, ListSchema, {\n method: 'GET',\n path: `/api/partner/sources/products?${new URLSearchParams({ source })}`,\n });\n}\n\nexport function setSourceProduct(\n host: string,\n token: string,\n input: { source: string; product: string; enabled: boolean },\n) {\n return call(host, token, ChangeSchema, {\n method: input.enabled ? 'PUT' : 'DELETE',\n path: `/api/partner/sources/products/${encodeURIComponent(input.product)}?${new URLSearchParams({ source: input.source })}`,\n });\n}\n\nasync function call<T>(\n host: string,\n accessToken: string,\n schema: z.ZodType<T>,\n init: { method: 'GET' | 'PUT' | 'DELETE'; path: string },\n): Promise<SourceProductsApiResult<T>> {\n const response = await request(`${host}${init.path}`, {\n method: init.method,\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\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 && code === 'SUPER_ADMIN_REQUIRED') {\n throw forbiddenError(\n message ?? 'Only Ekanos enables this product for a Source.',\n 'A Source admin switches only the integrations their Source built. Ask Ekanos to switch anything else.',\n );\n }\n if (response.status === 403 && code === 'SOURCE_ADMIN_REQUIRED') {\n throw forbiddenError(\n message ?? 'Switching a product needs a Source admin seat on the Source.',\n 'Run \"ekanos sources\" to see your seats; a Source admin grants the seat.',\n );\n }\n if (response.status === 403) {\n throw forbiddenError(\n message ?? 'Not permitted on this Source.',\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 === 'PRODUCT_NOT_FOUND'\n ? '\"ekanos sources products\" lists the catalog.'\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 409) {\n throw invalidStateError(\n message ?? 'The host refused the change.',\n '\"ekanos sources products\" shows what this Source has.',\n );\n }\n if (response.status === 400) {\n throw usageError(\n message ?? 'The host refused the request.',\n 'See \"ekanos sources --help\".',\n );\n }\n if (!response.ok) {\n throw networkError(\n `${host}${init.path.split('?')[0]} 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 source products routes.',\n );\n }\n\n const parsed = schema.safeParse(body.data);\n if (!parsed.success) {\n throw networkError(\n `${host}/api/partner/sources/products answered in a shape this CLI does not read.`,\n 'Run \"ekanos upgrade\" — the CLI and the host disagree on the source products 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"]}
package/dist/teams-api.js CHANGED
@@ -251,7 +251,7 @@ export async function call(host, accessToken, schema, init) {
251
251
  throw notFoundError(message !== null && message !== void 0 ? message : 'Not found.', (_a = (code ? LIFECYCLE_HINTS[code] : undefined)) !== null && _a !== void 0 ? _a : (code === 'TEAM_NOT_FOUND'
252
252
  ? 'Run "ekanos teams list" to see the teams on this Source.'
253
253
  : code === 'PRODUCT_NOT_ENTITLED'
254
- ? '"ekanos sources products" shows what this Source has; only a super-admin enables a product for a Source (in /admin).'
254
+ ? '"ekanos sources products" shows what this Source has; only Ekanos enables a product for a Source.'
255
255
  : code === 'SCOPE_NOT_FOUND' || code === 'PRODUCT_NOT_IN_SCOPE'
256
256
  ? 'Run "ekanos sources scopes" to see the Source\'s product scopes and their products.'
257
257
  : code === 'WEBHOOK_NOT_FOUND' || code === 'CONNECTION_NOT_FOUND'
@@ -288,7 +288,7 @@ export async function call(host, accessToken, schema, init) {
288
288
  : code === 'PRIMARY_OWNER'
289
289
  ? "The primary owner's ownership is transferred in the app (team Settings → Members)."
290
290
  : code === 'TEAM_ACCOUNTS_DISABLED'
291
- ? 'Ekanos switches team accounts on for a Source; ask a super-admin.'
291
+ ? 'Ekanos switches team accounts on for a Source; ask Ekanos.'
292
292
  : code === 'PERSONAL_CONNECTION'
293
293
  ? 'Each member disconnects their own: "ekanos teams disconnect <team> <product> --mine".'
294
294
  : code === 'MCP_SERVER_FROM_TEMPLATE'