@ekanos/cli 0.17.1 → 0.18.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.
- package/README.md +1 -0
- package/dist/commands/catalog.js +11 -7
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/names.d.ts +19 -0
- package/dist/commands/names.js +122 -0
- package/dist/commands/names.js.map +1 -0
- package/dist/commands/publish.d.ts +53 -3
- package/dist/commands/publish.js +58 -16
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/sources.js +15 -5
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +12 -12
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/teams-connect.js +1 -1
- package/dist/commands/teams-connect.js.map +1 -1
- package/dist/experience/boundary.d.ts +2 -2
- package/dist/experience/boundary.js +2 -11
- package/dist/experience/boundary.js.map +1 -1
- package/dist/experience/build.d.ts +3 -1
- package/dist/experience/build.js +15 -26
- package/dist/experience/build.js.map +1 -1
- package/dist/experience/clean-room.d.ts +40 -0
- package/dist/experience/clean-room.js +150 -0
- package/dist/experience/clean-room.js.map +1 -0
- package/dist/experience/definition-checks.d.ts +12 -1
- package/dist/experience/definition-checks.js +34 -11
- package/dist/experience/definition-checks.js.map +1 -1
- package/dist/experience/definition-shape.d.ts +20 -2
- package/dist/experience/definition-shape.js.map +1 -1
- package/dist/experience/publish.d.ts +42 -5
- package/dist/experience/publish.js +195 -11
- package/dist/experience/publish.js.map +1 -1
- package/dist/harness-scaffold.js +3 -1
- package/dist/harness-scaffold.js.map +1 -1
- package/dist/index.js +25 -2
- package/dist/index.js.map +1 -1
- package/dist/names-api.d.ts +90 -0
- package/dist/names-api.js +123 -0
- package/dist/names-api.js.map +1 -0
- package/dist/product-kinds.d.ts +13 -0
- package/dist/product-kinds.js +17 -0
- package/dist/product-kinds.js.map +1 -0
- package/dist/publish-api.d.ts +38 -5
- package/dist/publish-api.js +59 -16
- package/dist/publish-api.js.map +1 -1
- package/dist/repo/publish.d.ts +6 -3
- package/dist/repo/publish.js +48 -14
- package/dist/repo/publish.js.map +1 -1
- package/dist/source-products-api.d.ts +3 -2
- package/dist/source-products-api.js +1 -1
- package/dist/source-products-api.js.map +1 -1
- package/dist/team-connect-api.d.ts +4 -0
- package/dist/team-connect-api.js +1 -1
- package/dist/team-connect-api.js.map +1 -1
- package/dist/toolchain-api.d.ts +15 -0
- package/dist/toolchain-api.js +12 -1
- package/dist/toolchain-api.js.map +1 -1
- package/package.json +2 -2
- package/templates/experience/package.json.tmpl +7 -4
- package/templates/harness/app/experience/layout/page.tsx.tmpl +1 -0
- package/templates/harness/app/experience/layout.tsx.tmpl +3 -0
- package/templates/harness/app/experience/pages/[id]/[...path]/page.tsx.tmpl +1 -0
- package/templates/package.json.tmpl +4 -4
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ npx @ekanos/cli dev
|
|
|
30
30
|
| `catalog` | `ekanos catalog [<product-slug>]` — what a source can already use (`GET /api/partner/catalog`): every product, Experiences then Integrations as `sources products` splits them (the two list the same products), enabled, switched off or available to enable, with its tools and their input schemas, whose connection an agent run uses, agent triggers and generated setup steps. Check it before building something the source may already have. |
|
|
31
31
|
| `agents` | `ekanos agents init | list | show | pull | apply | publish | rollback | archive | unarchive` — the source's agents, held by Ekanos (`/api/partner/agents`). `init` sets up a folder for designing agents with Claude (the `build-agent` and `ekanos-platform` skills, the docs MCP server, a CLAUDE.md) without scaffolding an integration. `apply` stores a new version as a draft, `publish` makes a version the one every team runs, `rollback` publishes an earlier one, `archive` retires one no team has on. Either seat applies; only the source admin publishes or archives.
|
|
32
32
|
| `teams` | A Source admin's teams (`/api/partner/teams`): members, connections, and each team's agents. `ekanos teams agents create <team> --from <dir> [--personal]` builds the team's own agent — or, with `--personal`, your own, which acts for you and only you change — from the same `agent.json` (+ `AGENT.md`, + `skills/<slug>/`) that `agents apply` reads; `teams agents apply <team> <dir> [--base-version <n>]` creates or changes it from that file. You become its creator, so you must be an active member of the team. `teams triggers add <team> <key> schedule\|webhook …` starts it; a webhook token goes to a `0600` file under `~/.ekanos/secrets`, never the output. Someone else's personal agent is refused for every write (`PERSONAL_AGENT_CREATOR_ONLY`). |
|
|
33
|
+
| `names` | `ekanos names claim <slug> [--reason <text>] \| status <slug>` — request an approved global package name and see where the request stands (`/api/partner/names/claims`). A package that is not global is named under your Source's namespace (`acme/google-drive`); `ekanos.json` keeps the bare slug. |
|
|
33
34
|
| `reviewers` | `ekanos reviewers list \| add <email> --reach source\|team [--team <slug>] \| remove <email> [--team <slug>]` — a Source admin names who decides agent reply drafts held under an `approve` policy (`/api/partner/reviewers`). A `source`-reach reviewer decides every team's drafts; a `team`-reach reviewer one team's. The reviewer must be a member of one of the Source's teams. |
|
|
34
35
|
| `workflows` | `ekanos workflows list \| show \| pull \| apply \| enable \| disable \| run \| runs list\|show\|cancel \| approvals list\|approve\|reject --team <slug>` — a team's workflows, for its Source admin (`/api/partner/teams/<team>/workflows`). A workflow is the JSON the visual editor saves; `apply` creates a draft (or replaces one with `--update`) through the editor's own checks, including the Custom Code security review and approval gates before writes; `enable` re-checks it and asks you to approve what Custom Code reaches (`--confirm-access` in a script); `run --dry-run` rehearses it with writes recorded, not sent. Needs the Workflows Experience on for the Source; a member's personal workflow is never reachable. |
|
|
35
36
|
| `use` | `ekanos use <source-slug>` — set this project's publish target to a source you hold a seat on. Refuses (`forbidden`, with your actual seats in the hint) for a slug you don't hold a seat on, so a typo can't silently repoint a project. |
|
package/dist/commands/catalog.js
CHANGED
|
@@ -2,6 +2,7 @@ import { loginHint } from '../auth/host-trust.js';
|
|
|
2
2
|
import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
|
|
3
3
|
import { fetchSourceCatalog, } from '../catalog-api.js';
|
|
4
4
|
import { authRequiredError } from '../errors.js';
|
|
5
|
+
import { hostPredatesFeatures, productGroup } from '../product-kinds.js';
|
|
5
6
|
import { resolveSourceTarget } from '../source-target.js';
|
|
6
7
|
import { pluralize } from '../ui.js';
|
|
7
8
|
import { withDocsHint } from './agents-init.js';
|
|
@@ -64,13 +65,19 @@ function actorLabel(actor, product) {
|
|
|
64
65
|
: ACTOR_LABEL[actor];
|
|
65
66
|
}
|
|
66
67
|
const KIND_TITLE = {
|
|
68
|
+
feature: 'Features',
|
|
67
69
|
experience: 'Experiences',
|
|
68
70
|
integration: 'Integrations',
|
|
69
71
|
};
|
|
70
72
|
function catalogSummary(ui, catalog) {
|
|
71
|
-
// The same split as `ekanos sources products`:
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
// The same split as `ekanos sources products`: Features, Experiences (only
|
|
74
|
+
// when there are any), then Integrations. A host from before the Features
|
|
75
|
+
// rename calls a Feature an `experience`.
|
|
76
|
+
const legacyHost = hostPredatesFeatures(catalog.products);
|
|
77
|
+
const kinds = ['feature', 'experience', 'integration'].filter((kind) => kind !== 'experience' ||
|
|
78
|
+
catalog.products.some((product) => productGroup(product, legacyHost) === 'experience'));
|
|
79
|
+
const blocks = kinds.map((kind) => {
|
|
80
|
+
const products = catalog.products.filter((product) => productGroup(product, legacyHost) === kind);
|
|
74
81
|
return ui.result({
|
|
75
82
|
title: `${KIND_TITLE[kind]} on ${catalog.source.slug} (${products.length})`,
|
|
76
83
|
tone: 'ok',
|
|
@@ -101,10 +108,7 @@ function productDetail(ui, product) {
|
|
|
101
108
|
var _a;
|
|
102
109
|
const rows = [
|
|
103
110
|
['What', (_a = product.summary) !== null && _a !== void 0 ? _a : '—'],
|
|
104
|
-
[
|
|
105
|
-
'Kind',
|
|
106
|
-
product.kind === 'experience' ? 'Ekanos Experience' : 'Integration',
|
|
107
|
-
],
|
|
111
|
+
['Kind', product.kind === 'integration' ? 'Integration' : 'Ekanos Feature'],
|
|
108
112
|
[
|
|
109
113
|
'Status',
|
|
110
114
|
[STATUS_LABEL[product.availability], enableNeeds(product)]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/commands/catalog.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CatalogConnection,\n CatalogProduct,\n CatalogSetupStep,\n CatalogTool,\n SourceCatalog,\n} from '@ekanos/integration-schema';\n\nimport { loginHint } from '../auth/host-trust';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport {\n type FetchSourceCatalogResult,\n fetchSourceCatalog,\n} from '../catalog-api';\nimport type { CliContext } from '../context';\nimport { authRequiredError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { resolveSourceTarget } from '../source-target';\nimport { type Ui, pluralize } from '../ui';\nimport { withDocsHint } from './agents-init';\n\nexport interface CatalogArgs {\n host?: string;\n source?: string;\n /** One product's detail, when given. */\n product?: string;\n dir: string;\n env: Record<string, string | undefined>;\n}\n\n/**\n * `catalog` — what the Source can already do (plan 239.0). Lists every\n * product the Source can use, Experiences then Integrations as `ekanos\n * sources products` splits them, with its tools, agent events and the steps\n * left to set it up; `ekanos catalog <product>` shows one in full, with each\n * tool's inputs and whose connection an agent run uses.\n *\n * Read before building: an agent migration asks it first, so a capability\n * the Source already has is set up rather than rebuilt. Works outside a\n * project — the Source comes from `--source`, else `ekanos.json`, else the\n * caller's only seat.\n */\nexport async function runCatalog(\n ctx: CliContext,\n args: CatalogArgs,\n): Promise<ExitCode> {\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n let session = requireSession(env);\n\n const resolved = await resolveSourceTarget(env, session, args);\n session = resolved.session;\n\n const query = { source: resolved.source, product: args.product };\n let result = await fetchSourceCatalog(env.host, session.accessToken, query);\n if (result.status === 'unauthorized') {\n session = await refreshStoredSession(env, session);\n result = await fetchSourceCatalog(env.host, session.accessToken, query);\n }\n const catalog = requireCatalog(result, env);\n\n // The agent-design tip belongs on the overview only, not every product.\n return ctx.succeed(\n { host: env.host, ...catalog },\n args.product\n ? productDetail(ctx.ui, catalog.products[0]!)\n : withDocsHint(ctx.ui, args.dir, catalogSummary(ctx.ui, catalog)),\n );\n}\n\nfunction requireCatalog(\n result: FetchSourceCatalogResult,\n env: AuthEnvironment,\n): SourceCatalog {\n if (result.status === 'ok') return result.catalog;\n throw authRequiredError(\n `The session for ${env.host} could not authorize the catalog read.`,\n loginHint(env),\n );\n}\n\nconst STATUS_LABEL = {\n enabled: 'Enabled on this Source',\n 'turned-off': 'Not enabled — granted, but a Flag or plan switches it off',\n available: 'Not enabled',\n} as const;\n\nconst SHORT_STATUS = {\n enabled: '✓ enabled',\n 'turned-off': '✗ switched off',\n available: '✗ not enabled',\n} as const;\n\n/** Who does a setup step, in words. The wire value `ekanos` is Vastly. */\nconst ACTOR_LABEL = {\n ekanos: 'Vastly',\n source_admin: 'Source admin',\n team: 'team',\n} as const;\n\n/** A `team` step on a per-person product is each person's to do. */\nfunction actorLabel(\n actor: CatalogSetupStep['actor'],\n product: CatalogProduct,\n): string {\n return actor === 'team' && product.scope === 'user'\n ? 'each person'\n : ACTOR_LABEL[actor];\n}\n\nconst KIND_TITLE = {\n experience: 'Experiences',\n integration: 'Integrations',\n} as const;\n\nfunction catalogSummary(ui: Ui, catalog: SourceCatalog): string {\n // The same split as `ekanos sources products`: Experiences, then Integrations.\n const blocks = (['experience', 'integration'] as const).map((kind) => {\n const products = catalog.products.filter(\n (product) => product.kind === kind,\n );\n return ui.result({\n title: `${KIND_TITLE[kind]} on ${catalog.source.slug} (${products.length})`,\n tone: 'ok',\n rows: products.map((product): [string, string] => [\n product.slug,\n [\n SHORT_STATUS[product.availability],\n product.name,\n describeShape(product),\n nextStep(product),\n ]\n .filter(Boolean)\n .join(' · '),\n ]),\n });\n });\n\n const unregistered = catalog.products.filter(\n (product) => product.registered === false,\n ).length;\n return [\n ...blocks,\n ui.hint(\n `${pluralize(catalog.products.length, 'product')} on ${catalog.source.slug}` +\n (unregistered > 0\n ? `, ${unregistered} with no package on this host (listed so this matches \"ekanos sources products\"; nothing here can use them)`\n : '') +\n `. \"ekanos catalog <product>\" shows tools with their inputs, whose connection agent runs use, agent events and setup steps.`,\n ),\n ].join('\\n\\n');\n}\n\nfunction productDetail(ui: Ui, product: CatalogProduct): string {\n const rows: [string, string | string[]][] = [\n ['What', product.summary ?? '—'],\n [\n 'Kind',\n product.kind === 'experience' ? 'Ekanos Experience' : 'Integration',\n ],\n [\n 'Status',\n [STATUS_LABEL[product.availability], enableNeeds(product)]\n .filter(Boolean)\n .join(' — '),\n ],\n ];\n if (product.registered === false) {\n rows.push([\n 'Package',\n 'None on this host — nothing here can use it until a build that ships it is deployed.',\n ]);\n }\n if (product.connection) {\n rows.push(['Connections', describeConnection(product.connection)]);\n }\n if (product.connectedTeams !== null) {\n rows.push(['Teams connected', String(product.connectedTeams)]);\n }\n if (\n product.connectedPeople !== null &&\n product.connectedPeople !== undefined\n ) {\n rows.push(['People connected', String(product.connectedPeople)]);\n }\n if (product.tools.length > 0) {\n rows.push(['Tools', product.tools.flatMap(describeTool)]);\n }\n if (product.agentEvents.length > 0) {\n rows.push([\n 'Agent triggers',\n product.agentEvents.map((event) => `${event.id} — ${event.label}`),\n ]);\n }\n if (product.agentReplyTool) {\n rows.push(['Replies with', product.agentReplyTool]);\n }\n if (product.setupSteps.length > 0) {\n rows.push([\n 'Setup',\n product.setupSteps.flatMap((step, index) => [\n `${index + 1}. [${step.state}] ${step.title} (${actorLabel(step.actor, product)}) — ${step.detail}` +\n (step.guideUrl ? ` ${step.guideUrl}` : ''),\n ...(step.consoleUrl ? [` Open ${step.consoleUrl}`] : []),\n ...(step.instructions ?? []).map(\n (line, lineIndex) =>\n ` ${String.fromCharCode(97 + lineIndex)}. ${line}`,\n ),\n ]),\n ]);\n }\n\n return ui.result({ title: `${product.name} (${product.slug})`, rows });\n}\n\nfunction describeShape(product: CatalogProduct): string {\n if (product.registered === false) return 'no package on this host';\n const parts = [\n product.tools.length > 0 ? pluralize(product.tools.length, 'tool') : null,\n product.agentEvents.length > 0\n ? pluralize(product.agentEvents.length, 'agent trigger')\n : null,\n ].filter(Boolean);\n return parts.join(', ');\n}\n\n/**\n * What stands between the Source and using it. A product only Vastly can\n * enable says so instead of counting steps nobody on the Source can take.\n */\nfunction nextStep(product: CatalogProduct): string {\n if (product.registered === false) return '';\n const needs = enableNeeds(product);\n if (needs) return needs;\n\n const open = product.setupSteps.filter((step) => step.state === 'todo');\n const ours = open.filter((step) => step.actor !== 'ekanos');\n return [\n ours.length > 0 ? `${pluralize(ours.length, 'step')} left` : null,\n ours.length < open.length ? 'waiting on Vastly' : null,\n ]\n .filter(Boolean)\n .join(', ');\n}\n\n/** Who has to enable a product that is not enabled, or null when it is. */\nfunction enableNeeds(product: CatalogProduct): string | null {\n if (product.availability === 'enabled') return null;\n if (product.availability === 'turned-off') {\n return 'needs Vastly to switch it on';\n }\n return product.owned\n ? `built by this Source — its Source admin enables it (\"ekanos sources products enable ${product.slug}\")`\n : 'needs a Vastly grant';\n}\n\n/**\n * Whose connection a product's calls use: the team's, each person's own (on a\n * team or provided agent, the creator's, lent to whoever the run acts for),\n * or the Source's, when a Source admin connected it once for every team.\n */\nfunction describeConnection(connection: CatalogConnection): string[] {\n const lines: string[] = [];\n switch (connection.connectedBy) {\n case 'nobody':\n return ['Nothing to connect — it works as shipped.'];\n case 'team':\n lines.push(\n \"Each team connects its own. An agent run uses its team's connection.\",\n );\n break;\n case 'person':\n lines.push(\n \"Each person connects their own. An agent run uses its creator's own connection — on a personal agent that is the person it runs for, including its schedule and webhook runs; on a team or provided agent it is lent to whoever the run acts for.\",\n );\n break;\n }\n if (connection.sourceConnection !== 'unsupported') {\n lines.push(\n `A Source admin can connect it once for every team (\"ekanos apps connect\"): a provided agent whose template marks it Source-owned then uses the Source's connection, limited to the team's routed records. ` +\n (connection.sourceConnection === 'connected'\n ? 'This Source has.'\n : 'This Source has not yet.'),\n );\n }\n return lines;\n}\n\n/**\n * A tool, then its inputs one per line from its JSON schema — what an agent\n * designer needs to see which other tool supplies an id.\n */\nfunction describeTool(tool: CatalogTool): string[] {\n const head =\n `${tool.name} (${tool.effect}${tool.sensitivity === 'undeclared' ? '' : `, ${tool.sensitivity}`})` +\n (tool.description ? ` — ${tool.description}` : '');\n if (!tool.inputSchema) return [head];\n\n const properties = asRecord(tool.inputSchema.properties);\n const required = new Set(\n Array.isArray(tool.inputSchema.required)\n ? tool.inputSchema.required.filter(\n (key): key is string => typeof key === 'string',\n )\n : [],\n );\n const inputs = Object.entries(properties).map(([key, value]) => {\n const property = asRecord(value);\n const type = typeof property.type === 'string' ? property.type : null;\n const description =\n typeof property.description === 'string' ? property.description : null;\n return (\n ` ${key}` +\n [type, required.has(key) ? 'required' : null]\n .filter(Boolean)\n .map((part, index) => (index === 0 ? `: ${part}` : `, ${part}`))\n .join('') +\n (description ? ` — ${description}` : '')\n );\n });\n return [head, ...(inputs.length > 0 ? inputs : [' no inputs'])];\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/commands/catalog.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CatalogConnection,\n CatalogProduct,\n CatalogSetupStep,\n CatalogTool,\n SourceCatalog,\n} from '@ekanos/integration-schema';\n\nimport { loginHint } from '../auth/host-trust';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport {\n type FetchSourceCatalogResult,\n fetchSourceCatalog,\n} from '../catalog-api';\nimport type { CliContext } from '../context';\nimport { authRequiredError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { hostPredatesFeatures, productGroup } from '../product-kinds';\nimport { resolveSourceTarget } from '../source-target';\nimport { type Ui, pluralize } from '../ui';\nimport { withDocsHint } from './agents-init';\n\nexport interface CatalogArgs {\n host?: string;\n source?: string;\n /** One product's detail, when given. */\n product?: string;\n dir: string;\n env: Record<string, string | undefined>;\n}\n\n/**\n * `catalog` — what the Source can already do (plan 239.0). Lists every\n * product the Source can use, Experiences then Integrations as `ekanos\n * sources products` splits them, with its tools, agent events and the steps\n * left to set it up; `ekanos catalog <product>` shows one in full, with each\n * tool's inputs and whose connection an agent run uses.\n *\n * Read before building: an agent migration asks it first, so a capability\n * the Source already has is set up rather than rebuilt. Works outside a\n * project — the Source comes from `--source`, else `ekanos.json`, else the\n * caller's only seat.\n */\nexport async function runCatalog(\n ctx: CliContext,\n args: CatalogArgs,\n): Promise<ExitCode> {\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n let session = requireSession(env);\n\n const resolved = await resolveSourceTarget(env, session, args);\n session = resolved.session;\n\n const query = { source: resolved.source, product: args.product };\n let result = await fetchSourceCatalog(env.host, session.accessToken, query);\n if (result.status === 'unauthorized') {\n session = await refreshStoredSession(env, session);\n result = await fetchSourceCatalog(env.host, session.accessToken, query);\n }\n const catalog = requireCatalog(result, env);\n\n // The agent-design tip belongs on the overview only, not every product.\n return ctx.succeed(\n { host: env.host, ...catalog },\n args.product\n ? productDetail(ctx.ui, catalog.products[0]!)\n : withDocsHint(ctx.ui, args.dir, catalogSummary(ctx.ui, catalog)),\n );\n}\n\nfunction requireCatalog(\n result: FetchSourceCatalogResult,\n env: AuthEnvironment,\n): SourceCatalog {\n if (result.status === 'ok') return result.catalog;\n throw authRequiredError(\n `The session for ${env.host} could not authorize the catalog read.`,\n loginHint(env),\n );\n}\n\nconst STATUS_LABEL = {\n enabled: 'Enabled on this Source',\n 'turned-off': 'Not enabled — granted, but a Flag or plan switches it off',\n available: 'Not enabled',\n} as const;\n\nconst SHORT_STATUS = {\n enabled: '✓ enabled',\n 'turned-off': '✗ switched off',\n available: '✗ not enabled',\n} as const;\n\n/** Who does a setup step, in words. The wire value `ekanos` is Vastly. */\nconst ACTOR_LABEL = {\n ekanos: 'Vastly',\n source_admin: 'Source admin',\n team: 'team',\n} as const;\n\n/** A `team` step on a per-person product is each person's to do. */\nfunction actorLabel(\n actor: CatalogSetupStep['actor'],\n product: CatalogProduct,\n): string {\n return actor === 'team' && product.scope === 'user'\n ? 'each person'\n : ACTOR_LABEL[actor];\n}\n\nconst KIND_TITLE = {\n feature: 'Features',\n experience: 'Experiences',\n integration: 'Integrations',\n} as const;\n\nfunction catalogSummary(ui: Ui, catalog: SourceCatalog): string {\n // The same split as `ekanos sources products`: Features, Experiences (only\n // when there are any), then Integrations. A host from before the Features\n // rename calls a Feature an `experience`.\n const legacyHost = hostPredatesFeatures(catalog.products);\n const kinds = (['feature', 'experience', 'integration'] as const).filter(\n (kind) =>\n kind !== 'experience' ||\n catalog.products.some(\n (product) => productGroup(product, legacyHost) === 'experience',\n ),\n );\n const blocks = kinds.map((kind) => {\n const products = catalog.products.filter(\n (product) => productGroup(product, legacyHost) === kind,\n );\n return ui.result({\n title: `${KIND_TITLE[kind]} on ${catalog.source.slug} (${products.length})`,\n tone: 'ok',\n rows: products.map((product): [string, string] => [\n product.slug,\n [\n SHORT_STATUS[product.availability],\n product.name,\n describeShape(product),\n nextStep(product),\n ]\n .filter(Boolean)\n .join(' · '),\n ]),\n });\n });\n\n const unregistered = catalog.products.filter(\n (product) => product.registered === false,\n ).length;\n return [\n ...blocks,\n ui.hint(\n `${pluralize(catalog.products.length, 'product')} on ${catalog.source.slug}` +\n (unregistered > 0\n ? `, ${unregistered} with no package on this host (listed so this matches \"ekanos sources products\"; nothing here can use them)`\n : '') +\n `. \"ekanos catalog <product>\" shows tools with their inputs, whose connection agent runs use, agent events and setup steps.`,\n ),\n ].join('\\n\\n');\n}\n\nfunction productDetail(ui: Ui, product: CatalogProduct): string {\n const rows: [string, string | string[]][] = [\n ['What', product.summary ?? '—'],\n ['Kind', product.kind === 'integration' ? 'Integration' : 'Ekanos Feature'],\n [\n 'Status',\n [STATUS_LABEL[product.availability], enableNeeds(product)]\n .filter(Boolean)\n .join(' — '),\n ],\n ];\n if (product.registered === false) {\n rows.push([\n 'Package',\n 'None on this host — nothing here can use it until a build that ships it is deployed.',\n ]);\n }\n if (product.connection) {\n rows.push(['Connections', describeConnection(product.connection)]);\n }\n if (product.connectedTeams !== null) {\n rows.push(['Teams connected', String(product.connectedTeams)]);\n }\n if (\n product.connectedPeople !== null &&\n product.connectedPeople !== undefined\n ) {\n rows.push(['People connected', String(product.connectedPeople)]);\n }\n if (product.tools.length > 0) {\n rows.push(['Tools', product.tools.flatMap(describeTool)]);\n }\n if (product.agentEvents.length > 0) {\n rows.push([\n 'Agent triggers',\n product.agentEvents.map((event) => `${event.id} — ${event.label}`),\n ]);\n }\n if (product.agentReplyTool) {\n rows.push(['Replies with', product.agentReplyTool]);\n }\n if (product.setupSteps.length > 0) {\n rows.push([\n 'Setup',\n product.setupSteps.flatMap((step, index) => [\n `${index + 1}. [${step.state}] ${step.title} (${actorLabel(step.actor, product)}) — ${step.detail}` +\n (step.guideUrl ? ` ${step.guideUrl}` : ''),\n ...(step.consoleUrl ? [` Open ${step.consoleUrl}`] : []),\n ...(step.instructions ?? []).map(\n (line, lineIndex) =>\n ` ${String.fromCharCode(97 + lineIndex)}. ${line}`,\n ),\n ]),\n ]);\n }\n\n return ui.result({ title: `${product.name} (${product.slug})`, rows });\n}\n\nfunction describeShape(product: CatalogProduct): string {\n if (product.registered === false) return 'no package on this host';\n const parts = [\n product.tools.length > 0 ? pluralize(product.tools.length, 'tool') : null,\n product.agentEvents.length > 0\n ? pluralize(product.agentEvents.length, 'agent trigger')\n : null,\n ].filter(Boolean);\n return parts.join(', ');\n}\n\n/**\n * What stands between the Source and using it. A product only Vastly can\n * enable says so instead of counting steps nobody on the Source can take.\n */\nfunction nextStep(product: CatalogProduct): string {\n if (product.registered === false) return '';\n const needs = enableNeeds(product);\n if (needs) return needs;\n\n const open = product.setupSteps.filter((step) => step.state === 'todo');\n const ours = open.filter((step) => step.actor !== 'ekanos');\n return [\n ours.length > 0 ? `${pluralize(ours.length, 'step')} left` : null,\n ours.length < open.length ? 'waiting on Vastly' : null,\n ]\n .filter(Boolean)\n .join(', ');\n}\n\n/** Who has to enable a product that is not enabled, or null when it is. */\nfunction enableNeeds(product: CatalogProduct): string | null {\n if (product.availability === 'enabled') return null;\n if (product.availability === 'turned-off') {\n return 'needs Vastly to switch it on';\n }\n return product.owned\n ? `built by this Source — its Source admin enables it (\"ekanos sources products enable ${product.slug}\")`\n : 'needs a Vastly grant';\n}\n\n/**\n * Whose connection a product's calls use: the team's, each person's own (on a\n * team or provided agent, the creator's, lent to whoever the run acts for),\n * or the Source's, when a Source admin connected it once for every team.\n */\nfunction describeConnection(connection: CatalogConnection): string[] {\n const lines: string[] = [];\n switch (connection.connectedBy) {\n case 'nobody':\n return ['Nothing to connect — it works as shipped.'];\n case 'team':\n lines.push(\n \"Each team connects its own. An agent run uses its team's connection.\",\n );\n break;\n case 'person':\n lines.push(\n \"Each person connects their own. An agent run uses its creator's own connection — on a personal agent that is the person it runs for, including its schedule and webhook runs; on a team or provided agent it is lent to whoever the run acts for.\",\n );\n break;\n }\n if (connection.sourceConnection !== 'unsupported') {\n lines.push(\n `A Source admin can connect it once for every team (\"ekanos apps connect\"): a provided agent whose template marks it Source-owned then uses the Source's connection, limited to the team's routed records. ` +\n (connection.sourceConnection === 'connected'\n ? 'This Source has.'\n : 'This Source has not yet.'),\n );\n }\n return lines;\n}\n\n/**\n * A tool, then its inputs one per line from its JSON schema — what an agent\n * designer needs to see which other tool supplies an id.\n */\nfunction describeTool(tool: CatalogTool): string[] {\n const head =\n `${tool.name} (${tool.effect}${tool.sensitivity === 'undeclared' ? '' : `, ${tool.sensitivity}`})` +\n (tool.description ? ` — ${tool.description}` : '');\n if (!tool.inputSchema) return [head];\n\n const properties = asRecord(tool.inputSchema.properties);\n const required = new Set(\n Array.isArray(tool.inputSchema.required)\n ? tool.inputSchema.required.filter(\n (key): key is string => typeof key === 'string',\n )\n : [],\n );\n const inputs = Object.entries(properties).map(([key, value]) => {\n const property = asRecord(value);\n const type = typeof property.type === 'string' ? property.type : null;\n const description =\n typeof property.description === 'string' ? property.description : null;\n return (\n ` ${key}` +\n [type, required.has(key) ? 'required' : null]\n .filter(Boolean)\n .map((part, index) => (index === 0 ? `: ${part}` : `, ${part}`))\n .join('') +\n (description ? ` — ${description}` : '')\n );\n });\n return [head, ...(inputs.length > 0 ? inputs : [' no inputs'])];\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CliContext } from '../context.js';
|
|
2
|
+
import type { ExitCode } from '../exit-codes.js';
|
|
3
|
+
export declare const NAMES_SUBCOMMANDS: readonly ['claim', 'status'];
|
|
4
|
+
export interface NamesArgs {
|
|
5
|
+
host?: string;
|
|
6
|
+
source?: string;
|
|
7
|
+
/** `claim <slug>`, `status <slug>`. */
|
|
8
|
+
positionals: readonly string[];
|
|
9
|
+
reason?: string;
|
|
10
|
+
dir: string;
|
|
11
|
+
env: Record<string, string | undefined>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `names` - a global package name is approved by a super admin. `claim`
|
|
15
|
+
* requests one for a slug; `status` says where the request stands. A package
|
|
16
|
+
* that is not global needs no claim: it is named under the Source's own
|
|
17
|
+
* namespace.
|
|
18
|
+
*/
|
|
19
|
+
export declare function runNames(ctx: CliContext, args: NamesArgs): Promise<ExitCode>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { SLUG_PATTERN } from '@ekanos/integration-schema';
|
|
2
|
+
import { loginHint } from '../auth/host-trust.js';
|
|
3
|
+
import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
|
|
4
|
+
import { authRequiredError, usageError } from '../errors.js';
|
|
5
|
+
import { getNameClaim, requestNameClaim, } from '../names-api.js';
|
|
6
|
+
import { resolveSourceTarget } from '../source-target.js';
|
|
7
|
+
export const NAMES_SUBCOMMANDS = ['claim', 'status'];
|
|
8
|
+
const USAGE = 'Run one of: ekanos names claim <slug> [--reason <text>] [--source <slug>], ekanos names status <slug> [--source <slug>].';
|
|
9
|
+
/**
|
|
10
|
+
* `names` - a global package name is approved by a super admin. `claim`
|
|
11
|
+
* requests one for a slug; `status` says where the request stands. A package
|
|
12
|
+
* that is not global needs no claim: it is named under the Source's own
|
|
13
|
+
* namespace.
|
|
14
|
+
*/
|
|
15
|
+
export async function runNames(ctx, args) {
|
|
16
|
+
var _a;
|
|
17
|
+
const [rawSubcommand, rawSlug, ...extra] = args.positionals;
|
|
18
|
+
const subcommand = parseSubcommand(rawSubcommand);
|
|
19
|
+
if (extra.length > 0) {
|
|
20
|
+
throw usageError(`Unexpected argument "${extra[0]}".`, USAGE);
|
|
21
|
+
}
|
|
22
|
+
if (subcommand === 'status' && args.reason !== undefined) {
|
|
23
|
+
throw usageError('names status takes no --reason.', USAGE);
|
|
24
|
+
}
|
|
25
|
+
const slug = requireSlug(rawSlug, subcommand);
|
|
26
|
+
const reason = ((_a = args.reason) === null || _a === void 0 ? void 0 : _a.trim()) || undefined;
|
|
27
|
+
const env = resolveAuthEnvironment(ctx, args.host, args.env, {
|
|
28
|
+
projectDir: args.dir,
|
|
29
|
+
});
|
|
30
|
+
const resolved = await resolveSourceTarget(env, requireSession(env), args);
|
|
31
|
+
const call = withOneRefresh(env, resolved.session);
|
|
32
|
+
const source = resolved.source;
|
|
33
|
+
if (subcommand === 'claim') {
|
|
34
|
+
const claim = await call((token) => requestNameClaim(env.host, token, Object.assign({ source,
|
|
35
|
+
slug }, (reason ? { reason } : {}))));
|
|
36
|
+
return ctx.succeed(Object.assign({ host: env.host, source }, claim), ctx.ui.result({
|
|
37
|
+
title: `Requested the global name ${claim.slug}`,
|
|
38
|
+
tone: 'ok',
|
|
39
|
+
rows: [
|
|
40
|
+
['Name', claim.slug],
|
|
41
|
+
['Status', claim.status],
|
|
42
|
+
['Source', source],
|
|
43
|
+
],
|
|
44
|
+
notes: [
|
|
45
|
+
'A super admin decides it. Check with "ekanos names status ' +
|
|
46
|
+
`${claim.slug}".`,
|
|
47
|
+
'Until it is approved, publish restricted: the package is named under your Source namespace.',
|
|
48
|
+
],
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
const status = await call((token) => getNameClaim(env.host, token, source, slug));
|
|
52
|
+
return ctx.succeed(Object.assign({ host: env.host, source }, status), ctx.ui.result({
|
|
53
|
+
title: statusTitle(status),
|
|
54
|
+
tone: status.status === 'approved' ? 'ok' : 'warn',
|
|
55
|
+
rows: [
|
|
56
|
+
['Name', status.slug],
|
|
57
|
+
['Status', status.status],
|
|
58
|
+
...(status.requestedAt
|
|
59
|
+
? [['Requested', status.requestedAt]]
|
|
60
|
+
: []),
|
|
61
|
+
...(status.decidedAt ? [['Decided', status.decidedAt]] : []),
|
|
62
|
+
...(status.reason ? [['Reason', status.reason]] : []),
|
|
63
|
+
],
|
|
64
|
+
notes: statusNotes(status),
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
function statusTitle(status) {
|
|
68
|
+
switch (status.status) {
|
|
69
|
+
case 'none':
|
|
70
|
+
return `No claim for ${status.slug}`;
|
|
71
|
+
case 'requested':
|
|
72
|
+
return `The claim for ${status.slug} is waiting for a super admin`;
|
|
73
|
+
case 'approved':
|
|
74
|
+
return `${status.slug} is an approved global name`;
|
|
75
|
+
case 'rejected':
|
|
76
|
+
return `The claim for ${status.slug} was rejected`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function statusNotes(status) {
|
|
80
|
+
switch (status.status) {
|
|
81
|
+
case 'none':
|
|
82
|
+
return [`Request it: "ekanos names claim ${status.slug}".`];
|
|
83
|
+
case 'requested':
|
|
84
|
+
return ['Publish restricted in the meantime.'];
|
|
85
|
+
case 'approved':
|
|
86
|
+
return ['Publish with availability.mode "global" in ekanos.json.'];
|
|
87
|
+
case 'rejected':
|
|
88
|
+
return [
|
|
89
|
+
'Publish restricted under your Source namespace, or claim a different name.',
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function withOneRefresh(env, initial) {
|
|
94
|
+
let session = initial;
|
|
95
|
+
return async (send) => {
|
|
96
|
+
let result = await send(session.accessToken);
|
|
97
|
+
if (result.status === 'unauthorized') {
|
|
98
|
+
session = await refreshStoredSession(env, session);
|
|
99
|
+
result = await send(session.accessToken);
|
|
100
|
+
}
|
|
101
|
+
if (result.status === 'ok')
|
|
102
|
+
return result.data;
|
|
103
|
+
throw authRequiredError(`The session for ${env.host} could not authorize the names call.`, loginHint(env));
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function parseSubcommand(value) {
|
|
107
|
+
const match = NAMES_SUBCOMMANDS.find((name) => name === value);
|
|
108
|
+
if (match)
|
|
109
|
+
return match;
|
|
110
|
+
throw usageError(value
|
|
111
|
+
? `Unknown names subcommand "${value}".`
|
|
112
|
+
: 'names needs a subcommand.', USAGE);
|
|
113
|
+
}
|
|
114
|
+
function requireSlug(value, subcommand) {
|
|
115
|
+
const slug = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
|
|
116
|
+
if (slug && SLUG_PATTERN.test(slug))
|
|
117
|
+
return slug;
|
|
118
|
+
throw usageError(value
|
|
119
|
+
? `"${value}" is not a package slug (kebab-case, no namespace).`
|
|
120
|
+
: 'Name the package slug.', `e.g. "ekanos names ${subcommand} google-drive". ekanos.json keeps the bare slug.`);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.js","sourceRoot":"","sources":["../../src/commands/names.ts"],"names":[],"mappings":"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","sourcesContent":["import { SLUG_PATTERN } from '@ekanos/integration-schema';\n\nimport type { StoredSession } from '../auth/credential-store';\nimport { loginHint } from '../auth/host-trust';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport type { CliContext } from '../context';\nimport { authRequiredError, usageError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n type NameClaimStatus,\n type NamesApiResult,\n getNameClaim,\n requestNameClaim,\n} from '../names-api';\nimport { resolveSourceTarget } from '../source-target';\n\nexport const NAMES_SUBCOMMANDS = ['claim', 'status'] as const;\ntype NamesSubcommand = (typeof NAMES_SUBCOMMANDS)[number];\n\nconst USAGE =\n 'Run one of: ekanos names claim <slug> [--reason <text>] [--source <slug>], ekanos names status <slug> [--source <slug>].';\n\nexport interface NamesArgs {\n host?: string;\n source?: string;\n /** `claim <slug>`, `status <slug>`. */\n positionals: readonly string[];\n reason?: string;\n dir: string;\n env: Record<string, string | undefined>;\n}\n\n/**\n * `names` - a global package name is approved by a super admin. `claim`\n * requests one for a slug; `status` says where the request stands. A package\n * that is not global needs no claim: it is named under the Source's own\n * namespace.\n */\nexport async function runNames(\n ctx: CliContext,\n args: NamesArgs,\n): Promise<ExitCode> {\n const [rawSubcommand, rawSlug, ...extra] = args.positionals;\n const subcommand = parseSubcommand(rawSubcommand);\n if (extra.length > 0) {\n throw usageError(`Unexpected argument \"${extra[0]}\".`, USAGE);\n }\n if (subcommand === 'status' && args.reason !== undefined) {\n throw usageError('names status takes no --reason.', USAGE);\n }\n const slug = requireSlug(rawSlug, subcommand);\n const reason = args.reason?.trim() || undefined;\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(env, requireSession(env), args);\n const call = withOneRefresh(env, resolved.session);\n const source = resolved.source;\n\n if (subcommand === 'claim') {\n const claim = await call((token) =>\n requestNameClaim(env.host, token, {\n source,\n slug,\n ...(reason ? { reason } : {}),\n }),\n );\n\n return ctx.succeed(\n { host: env.host, source, ...claim },\n ctx.ui.result({\n title: `Requested the global name ${claim.slug}`,\n tone: 'ok',\n rows: [\n ['Name', claim.slug],\n ['Status', claim.status],\n ['Source', source],\n ],\n notes: [\n 'A super admin decides it. Check with \"ekanos names status ' +\n `${claim.slug}\".`,\n 'Until it is approved, publish restricted: the package is named under your Source namespace.',\n ],\n }),\n );\n }\n\n const status = await call((token) =>\n getNameClaim(env.host, token, source, slug),\n );\n\n return ctx.succeed(\n { host: env.host, source, ...status },\n ctx.ui.result({\n title: statusTitle(status),\n tone: status.status === 'approved' ? 'ok' : 'warn',\n rows: [\n ['Name', status.slug],\n ['Status', status.status],\n ...(status.requestedAt\n ? [['Requested', status.requestedAt] as const]\n : []),\n ...(status.decidedAt ? [['Decided', status.decidedAt] as const] : []),\n ...(status.reason ? [['Reason', status.reason] as const] : []),\n ],\n notes: statusNotes(status),\n }),\n );\n}\n\nfunction statusTitle(status: NameClaimStatus): string {\n switch (status.status) {\n case 'none':\n return `No claim for ${status.slug}`;\n case 'requested':\n return `The claim for ${status.slug} is waiting for a super admin`;\n case 'approved':\n return `${status.slug} is an approved global name`;\n case 'rejected':\n return `The claim for ${status.slug} was rejected`;\n }\n}\n\nfunction statusNotes(status: NameClaimStatus): string[] {\n switch (status.status) {\n case 'none':\n return [`Request it: \"ekanos names claim ${status.slug}\".`];\n case 'requested':\n return ['Publish restricted in the meantime.'];\n case 'approved':\n return ['Publish with availability.mode \"global\" in ekanos.json.'];\n case 'rejected':\n return [\n 'Publish restricted under your Source namespace, or claim a different name.',\n ];\n }\n}\n\nfunction withOneRefresh(env: AuthEnvironment, initial: StoredSession) {\n let session = initial;\n\n return async <T>(\n send: (accessToken: string) => Promise<NamesApiResult<T>>,\n ): Promise<T> => {\n let result = await send(session.accessToken);\n if (result.status === 'unauthorized') {\n session = await refreshStoredSession(env, session);\n result = await send(session.accessToken);\n }\n if (result.status === 'ok') return result.data;\n throw authRequiredError(\n `The session for ${env.host} could not authorize the names call.`,\n loginHint(env),\n );\n };\n}\n\nfunction parseSubcommand(value: string | undefined): NamesSubcommand {\n const match = NAMES_SUBCOMMANDS.find((name) => name === value);\n if (match) return match;\n throw usageError(\n value\n ? `Unknown names subcommand \"${value}\".`\n : 'names needs a subcommand.',\n USAGE,\n );\n}\n\nfunction requireSlug(\n value: string | undefined,\n subcommand: NamesSubcommand,\n): string {\n const slug = value?.trim().toLowerCase();\n if (slug && SLUG_PATTERN.test(slug)) return slug;\n throw usageError(\n value\n ? `\"${value}\" is not a package slug (kebab-case, no namespace).`\n : 'Name the package slug.',\n `e.g. \"ekanos names ${subcommand} google-drive\". ekanos.json keeps the bare slug.`,\n );\n}\n"]}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Finding } from '@ekanos/integration-schema';
|
|
2
2
|
import type { StoredSession } from '../auth/credential-store.js';
|
|
3
|
-
import { type AuthEnvironment } from '../auth/session.js';
|
|
3
|
+
import { type AuthEnvironment, resolveAuthEnvironment } from '../auth/session.js';
|
|
4
4
|
import type { CliContext } from '../context.js';
|
|
5
5
|
import type { ExitCode } from '../exit-codes.js';
|
|
6
6
|
import { type LoadedProject } from '../project.js';
|
|
7
|
+
import { submitArchive } from '../publish-api.js';
|
|
7
8
|
import { assertSchemaNotSkewed } from '../schema-skew.js';
|
|
9
|
+
import { type SourceSeat } from '../sources-api.js';
|
|
8
10
|
import { checkForUpdate } from '../update-notice.js';
|
|
9
11
|
/**
|
|
10
12
|
* Escalate the checks in `PUBLISH_ERROR_ESCALATIONS` from `warn` to `error`
|
|
@@ -48,7 +50,7 @@ export interface PublishArgs {
|
|
|
48
50
|
all?: boolean;
|
|
49
51
|
}
|
|
50
52
|
/** Where the resolved source slug came from — governs the confirmation rules below. */
|
|
51
|
-
interface SourceResolution {
|
|
53
|
+
export interface SourceResolution {
|
|
52
54
|
source: string;
|
|
53
55
|
/** True iff `--source` was passed THIS invocation. */
|
|
54
56
|
explicit: boolean;
|
|
@@ -95,6 +97,37 @@ export interface PreparedSubmission {
|
|
|
95
97
|
* and the repo verb (which gates every package before submitting any).
|
|
96
98
|
*/
|
|
97
99
|
export declare function prepareIntegrationSubmission(ctx: CliContext, args: PublishArgs): Promise<PreparedSubmission>;
|
|
100
|
+
/**
|
|
101
|
+
* The publish-target verification gate.
|
|
102
|
+
*
|
|
103
|
+
* FORBIDDEN (exit 5) whenever the resolved source is not among the caller's
|
|
104
|
+
* seats — regardless of whether it came from `--source` or ekanos.json.
|
|
105
|
+
* Beyond that, the confirmation rules branch on how the source was resolved:
|
|
106
|
+
*
|
|
107
|
+
* - `--source` THIS invocation: explicit is consent. Proceed.
|
|
108
|
+
* - ekanos.json, exactly one seat held (which must be the match, or the
|
|
109
|
+
* FORBIDDEN check above would have already fired): proceed, but say so —
|
|
110
|
+
* there is nothing to disambiguate, but the target should never be silent.
|
|
111
|
+
* - ekanos.json, multiple seats held: the ambiguous case this mechanism
|
|
112
|
+
* exists for. Only when BOTH human (non-JSON) mode AND stdin is a real
|
|
113
|
+
* TTY does it print the target and require an explicit y/N confirm
|
|
114
|
+
* (`--yes` skips it) — see `canPrompt` below for why stdin matters, not
|
|
115
|
+
* just stdout. Every other combination (JSON mode, or human mode with a
|
|
116
|
+
* closed/non-interactive stdin) NEVER prompts — it proceeds only with
|
|
117
|
+
* `--yes`, otherwise VALIDATION (exit 3) with the seat list, naming
|
|
118
|
+
* `--source`, `--yes` and `ekanos use`.
|
|
119
|
+
*/
|
|
120
|
+
export declare function verifyPublishTarget(ctx: CliContext, env: AuthEnvironment, session: StoredSession, args: SourceResolution & {
|
|
121
|
+
slug: string;
|
|
122
|
+
version: string;
|
|
123
|
+
yes: boolean;
|
|
124
|
+
/** Real interactivity signal for stdin — see `canPrompt` below. */
|
|
125
|
+
stdinIsTTY: boolean;
|
|
126
|
+
confirm: (question: string) => Promise<boolean>;
|
|
127
|
+
}): Promise<{
|
|
128
|
+
sourceSeat: SourceSeat;
|
|
129
|
+
session: StoredSession;
|
|
130
|
+
}>;
|
|
98
131
|
/**
|
|
99
132
|
* The real, non-test y/N prompt. Reads a line from stdin and writes the
|
|
100
133
|
* question to stderr, so it never touches stdout — the one stream the JSON
|
|
@@ -108,6 +141,24 @@ export declare function prepareIntegrationSubmission(ctx: CliContext, args: Publ
|
|
|
108
141
|
* is only ever invoked behind.
|
|
109
142
|
*/
|
|
110
143
|
export declare function promptYesNo(question: string): Promise<boolean>;
|
|
144
|
+
export declare function submitWithOneRefresh(env: ReturnType<typeof resolveAuthEnvironment>, session: StoredSession, payload: Parameters<typeof submitArchive>[2]): Promise<import("../publish-api.js").SubmissionReceipt>;
|
|
145
|
+
/**
|
|
146
|
+
* Resolve the source slug (flag → ekanos.json) and whether it was given
|
|
147
|
+
* EXPLICITLY this invocation — the fact `verifyPublishTarget` uses to decide
|
|
148
|
+
* whether a confirmation is even in play. `flag !== undefined` (rather than
|
|
149
|
+
* "non-empty after trim") is deliberate: an explicit `--source` that is
|
|
150
|
+
* effectively empty is still a caller who typed the flag, not one who
|
|
151
|
+
* fell through to ekanos.json.
|
|
152
|
+
*/
|
|
153
|
+
export declare function resolveSourceSlug(env: AuthEnvironment, session: StoredSession, flag: string | undefined,
|
|
154
|
+
/** Anything that carries the project's saved `source`: an integration project or an experience's ekanos.json. */
|
|
155
|
+
loaded: {
|
|
156
|
+
project: {
|
|
157
|
+
source?: string;
|
|
158
|
+
};
|
|
159
|
+
}): Promise<SourceResolution & {
|
|
160
|
+
session: StoredSession;
|
|
161
|
+
}>;
|
|
111
162
|
/**
|
|
112
163
|
* Persist the chosen source AND host into ekanos.json after a SUCCESSFUL
|
|
113
164
|
* publish, so the next publish — and every other host-taking verb — needs no
|
|
@@ -145,4 +196,3 @@ export declare function buildManifest(loaded: LoadedProject, files: string[]): s
|
|
|
145
196
|
* source of truth, the same field npm would publish.
|
|
146
197
|
*/
|
|
147
198
|
export declare function readProjectVersion(projectDir: string): string;
|
|
148
|
-
export {};
|
package/dist/commands/publish.js
CHANGED
|
@@ -2,13 +2,14 @@ import { findRepoRoot } from '@ekanos/integration-schema/repo';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
|
|
5
|
-
import { forbiddenError, gateFailedError, preconditionError, usageError, validationError, } from '../errors.js';
|
|
5
|
+
import { CliError, forbiddenError, gateFailedError, preconditionError, usageError, validationError, } from '../errors.js';
|
|
6
6
|
import { readProjectKind } from '../experience/project.js';
|
|
7
7
|
import { runExperiencePublish } from '../experience/publish.js';
|
|
8
8
|
import { renderFindings } from '../findings-renderer.js';
|
|
9
|
+
import { lookupSlug } from '../names-api.js';
|
|
9
10
|
import { packProject } from '../pack.js';
|
|
10
11
|
import { loadProject, serializeProject } from '../project.js';
|
|
11
|
-
import { MAX_MANIFEST_BYTES, stillUnauthorized, submitArchive, } from '../publish-api.js';
|
|
12
|
+
import { MAX_MANIFEST_BYTES, globalNameUnapprovedError, stillUnauthorized, submitArchive, } from '../publish-api.js';
|
|
12
13
|
import { runRepoPublish } from '../repo/publish.js';
|
|
13
14
|
import { assertSchemaNotSkewed } from '../schema-skew.js';
|
|
14
15
|
import { fetchSeatsWithOneRefresh } from '../seats.js';
|
|
@@ -79,6 +80,11 @@ export async function runPublish(ctx, args) {
|
|
|
79
80
|
dir: args.dir,
|
|
80
81
|
dryRun: args.dryRun === true,
|
|
81
82
|
env: args.env,
|
|
83
|
+
host: args.host,
|
|
84
|
+
source: args.source,
|
|
85
|
+
yes: args.yes,
|
|
86
|
+
stdinIsTTY: args.stdinIsTTY,
|
|
87
|
+
confirm: args.confirm,
|
|
82
88
|
});
|
|
83
89
|
}
|
|
84
90
|
const prepared = await prepareIntegrationSubmission(ctx, args);
|
|
@@ -120,7 +126,8 @@ export async function runPublish(ctx, args) {
|
|
|
120
126
|
* the host.
|
|
121
127
|
*/
|
|
122
128
|
export async function submitPrepared(ctx, args, prepared) {
|
|
123
|
-
var _a
|
|
129
|
+
var _a;
|
|
130
|
+
var _b, _c, _d, _e, _f, _g;
|
|
124
131
|
const { loaded, skew, notice, env, version, slug, resolution } = prepared;
|
|
125
132
|
// The 100%-certainty mechanism: confirm the resolved source is one the
|
|
126
133
|
// caller actually holds a seat on — and, when it was resolved IMPLICITLY
|
|
@@ -133,10 +140,14 @@ export async function submitPrepared(ctx, args, prepared) {
|
|
|
133
140
|
slug,
|
|
134
141
|
version,
|
|
135
142
|
yes: args.yes,
|
|
136
|
-
stdinIsTTY: (
|
|
137
|
-
confirm: (
|
|
143
|
+
stdinIsTTY: (_b = args.stdinIsTTY) !== null && _b !== void 0 ? _b : process.stdin.isTTY === true,
|
|
144
|
+
confirm: (_c = args.confirm) !== null && _c !== void 0 ? _c : promptYesNo,
|
|
138
145
|
});
|
|
139
146
|
const source = verified.sourceSeat.slug;
|
|
147
|
+
const availability = (_a = loaded.project.availability) === null || _a === void 0 ? void 0 : _a.mode;
|
|
148
|
+
// Say the full name the host will use, and refuse an unapproved global
|
|
149
|
+
// name, before anything is packed.
|
|
150
|
+
const lookup = await preflightName(ctx, env, verified.session, source, slug, availability);
|
|
140
151
|
ctx.log(`Packing ${slug}@${version}…`);
|
|
141
152
|
const packed = packProject(loaded.projectDir);
|
|
142
153
|
const manifest = buildManifest(loaded, packed.files);
|
|
@@ -144,15 +155,13 @@ export async function submitPrepared(ctx, args, prepared) {
|
|
|
144
155
|
ctx.log(ctx.ui.ok(`${ctx.ui.glyphs.ok} packed ${slug}@${version} (${packed.files.length} files).`));
|
|
145
156
|
}
|
|
146
157
|
ctx.log(`Submitting to ${env.host} (source "${source}")…`);
|
|
147
|
-
const receipt = await submitWithOneRefresh(env, verified.session, {
|
|
148
|
-
source,
|
|
158
|
+
const receipt = await submitWithOneRefresh(env, verified.session, Object.assign({ source,
|
|
149
159
|
slug,
|
|
150
160
|
version,
|
|
151
|
-
manifest,
|
|
152
|
-
|
|
153
|
-
});
|
|
161
|
+
manifest, archive: packed.archive }, (availability ? { availability } : {})));
|
|
162
|
+
const displayName = (_e = (_d = receipt.displayName) !== null && _d !== void 0 ? _d : lookup === null || lookup === void 0 ? void 0 : lookup.displayName) !== null && _e !== void 0 ? _e : receipt.slug;
|
|
154
163
|
if (!ctx.jsonMode) {
|
|
155
|
-
ctx.log(ctx.ui.ok(`${ctx.ui.glyphs.ok} submitted ${
|
|
164
|
+
ctx.log(ctx.ui.ok(`${ctx.ui.glyphs.ok} submitted ${displayName}@${receipt.version} ` +
|
|
156
165
|
`(state "${receipt.state}").`));
|
|
157
166
|
}
|
|
158
167
|
const persisted = persistProjectFields(loaded, source, env.host);
|
|
@@ -165,7 +174,7 @@ export async function submitPrepared(ctx, args, prepared) {
|
|
|
165
174
|
id: verified.sourceSeat.id,
|
|
166
175
|
slug: verified.sourceSeat.slug,
|
|
167
176
|
name: verified.sourceSeat.name,
|
|
168
|
-
}, slug: receipt.slug, version: receipt.version, submissionId: receipt.submissionId, state: receipt.state, archiveSha256: receipt.archiveSha256, files: packed.files, sourcePersisted: persisted.source, hostPersisted: persisted.host, hostOverride: persisted.hostOverride }, (skew.status === 'project-older'
|
|
177
|
+
}, slug: receipt.slug, namespace: (_g = (_f = receipt.namespace) !== null && _f !== void 0 ? _f : lookup === null || lookup === void 0 ? void 0 : lookup.namespace) !== null && _g !== void 0 ? _g : null, displayName, version: receipt.version, submissionId: receipt.submissionId, state: receipt.state, archiveSha256: receipt.archiveSha256, files: packed.files, sourcePersisted: persisted.source, hostPersisted: persisted.host, hostOverride: persisted.hostOverride }, (skew.status === 'project-older'
|
|
169
178
|
? {
|
|
170
179
|
schemaSkew: {
|
|
171
180
|
status: skew.status,
|
|
@@ -177,7 +186,7 @@ export async function submitPrepared(ctx, args, prepared) {
|
|
|
177
186
|
human: ctx.ui.result({
|
|
178
187
|
title: 'Submitted',
|
|
179
188
|
rows: [
|
|
180
|
-
['Integration', `${
|
|
189
|
+
['Integration', `${displayName}@${receipt.version}`],
|
|
181
190
|
['State', receipt.state],
|
|
182
191
|
['Source', source],
|
|
183
192
|
['Host', shortHost(env.host)],
|
|
@@ -200,6 +209,37 @@ export async function submitPrepared(ctx, args, prepared) {
|
|
|
200
209
|
}),
|
|
201
210
|
};
|
|
202
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Ask the host what it will call this slug for the Source, before packing.
|
|
214
|
+
* An unapproved global name stops the publish here. Any other answer - an
|
|
215
|
+
* older host without the route, a server error, a shape this CLI does not
|
|
216
|
+
* read - is not a reason to stop: the submission itself is authoritative, and
|
|
217
|
+
* the bare slug stands in for the name.
|
|
218
|
+
*/
|
|
219
|
+
async function preflightName(ctx, env, session, source, slug, availability) {
|
|
220
|
+
let lookup = null;
|
|
221
|
+
try {
|
|
222
|
+
let result = await lookupSlug(env.host, session.accessToken, source, slug, availability);
|
|
223
|
+
if (result.status === 'unauthorized') {
|
|
224
|
+
const refreshed = await refreshStoredSession(env, session);
|
|
225
|
+
result = await lookupSlug(env.host, refreshed.accessToken, source, slug, availability);
|
|
226
|
+
}
|
|
227
|
+
if (result.status === 'ok')
|
|
228
|
+
lookup = result.lookup;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (!(error instanceof CliError))
|
|
232
|
+
throw error;
|
|
233
|
+
ctx.log(`Could not pre-check the name of ${slug} (${error.message}); continuing.`);
|
|
234
|
+
}
|
|
235
|
+
if ((lookup === null || lookup === void 0 ? void 0 : lookup.status) === 'global_unapproved') {
|
|
236
|
+
throw globalNameUnapprovedError(slug);
|
|
237
|
+
}
|
|
238
|
+
if ((lookup === null || lookup === void 0 ? void 0 : lookup.displayName) && !ctx.jsonMode) {
|
|
239
|
+
ctx.log(`Publishing as ${lookup.displayName}.`);
|
|
240
|
+
}
|
|
241
|
+
return lookup;
|
|
242
|
+
}
|
|
203
243
|
/**
|
|
204
244
|
* Everything `publish` does before it gates a project: sign-in, load, schema
|
|
205
245
|
* skew, toolchain check, source resolution. Shared by the single-project verb
|
|
@@ -256,7 +296,7 @@ export async function prepareIntegrationSubmission(ctx, args) {
|
|
|
256
296
|
* `--yes`, otherwise VALIDATION (exit 3) with the seat list, naming
|
|
257
297
|
* `--source`, `--yes` and `ekanos use`.
|
|
258
298
|
*/
|
|
259
|
-
async function verifyPublishTarget(ctx, env, session, args) {
|
|
299
|
+
export async function verifyPublishTarget(ctx, env, session, args) {
|
|
260
300
|
var _a, _b;
|
|
261
301
|
const result = await fetchSeatsWithOneRefresh(env, session);
|
|
262
302
|
if (result.status === 'unauthorized') {
|
|
@@ -348,7 +388,7 @@ export async function promptYesNo(question) {
|
|
|
348
388
|
rl.close();
|
|
349
389
|
}
|
|
350
390
|
}
|
|
351
|
-
async function submitWithOneRefresh(env, session, payload) {
|
|
391
|
+
export async function submitWithOneRefresh(env, session, payload) {
|
|
352
392
|
const first = await submitArchive(env.host, session.accessToken, payload);
|
|
353
393
|
if (first.status === 'ok')
|
|
354
394
|
return first.receipt;
|
|
@@ -368,7 +408,9 @@ async function submitWithOneRefresh(env, session, payload) {
|
|
|
368
408
|
* effectively empty is still a caller who typed the flag, not one who
|
|
369
409
|
* fell through to ekanos.json.
|
|
370
410
|
*/
|
|
371
|
-
async function resolveSourceSlug(env, session, flag,
|
|
411
|
+
export async function resolveSourceSlug(env, session, flag,
|
|
412
|
+
/** Anything that carries the project's saved `source`: an integration project or an experience's ekanos.json. */
|
|
413
|
+
loaded) {
|
|
372
414
|
const explicit = flag !== undefined;
|
|
373
415
|
const raw = flag !== null && flag !== void 0 ? flag : loaded.project.source;
|
|
374
416
|
if (!raw || raw.trim().length === 0) {
|