@cowliss/cli 0.13.0 → 0.14.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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "checkout_started": {
3
+ "cartId": "cart_10482",
4
+ "checkoutUrl": "https://store.example.com/checkout/10482"
5
+ },
6
+ "purchase_completed": {
7
+ "orderId": "ord_10482"
8
+ }
9
+ }
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,28 @@
1
+ {
2
+ "checkout.started": {
3
+ "cartId": "cart_10482",
4
+ "checkoutUrl": "https://store.example.com/checkout/10482",
5
+ "total": 110.0
6
+ },
7
+ "order.delivered": {
8
+ "orderId": "ord_10482"
9
+ },
10
+ "order.placed": {
11
+ "orderId": "ord_10482",
12
+ "total": 110.0,
13
+ "items": [
14
+ {
15
+ "name": "Linen shirt",
16
+ "price": 78.0
17
+ },
18
+ {
19
+ "name": "Canvas tote",
20
+ "price": 32.0
21
+ }
22
+ ]
23
+ },
24
+ "product.viewed": {
25
+ "productId": "prod_linen_shirt",
26
+ "category": "apparel"
27
+ }
28
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "level.completed": {
3
+ "level": 4,
4
+ "score": 1250
5
+ },
6
+ "level.failed": {
7
+ "level": 4,
8
+ "attempts": 3
9
+ },
10
+ "purchase.completed": {
11
+ "packId": "starter_bundle",
12
+ "coins": 500,
13
+ "price": 4.99
14
+ },
15
+ "session.started": {
16
+ "platform": "ios",
17
+ "version": "1.2.0"
18
+ },
19
+ "tutorial.completed": {
20
+ "step": "boss_fight"
21
+ }
22
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "invite.sent": {
3
+ "recipientEmail": "colleague@example.com",
4
+ "role": "editor"
5
+ },
6
+ "project.created": {
7
+ "projectId": "proj_123",
8
+ "template": "starter"
9
+ },
10
+ "subscription.created": {
11
+ "plan": "pro",
12
+ "seats": 5
13
+ },
14
+ "trial.started": {
15
+ "plan": "trial",
16
+ "days": 14
17
+ }
18
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "purchase_completed": {
3
+ "orderId": "ord_10482",
4
+ "amount": 49.99
5
+ }
6
+ }
package/dist/index.js CHANGED
@@ -23,6 +23,40 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
23
23
  //#region \0rolldown/runtime.js
24
24
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
25
25
 
26
+ //#endregion
27
+ //#region ../../packages/shared/src/agent-prompts/index.ts
28
+ function buildEventsPrompt(args) {
29
+ if (!args.sdkPackage?.trim()) throw new Error("sdkPackage is required");
30
+ if (!args.trackMethod?.trim()) throw new Error("trackMethod is required");
31
+ if (!args.referenceUrl?.trim()) throw new Error("referenceUrl is required");
32
+ if (!Array.isArray(args.events) || args.events.length === 0) throw new Error("events list must not be empty");
33
+ const lines = [
34
+ `Send these events from your application using ${args.sdkPackage}:`,
35
+ "",
36
+ `Reference documentation: ${args.referenceUrl}`,
37
+ ""
38
+ ];
39
+ for (const event of args.events) {
40
+ if (!event.name?.trim()) throw new Error("event name is required");
41
+ lines.push(`### Event: \`${event.name}\``);
42
+ if (event.description) lines.push(`${event.description}`);
43
+ lines.push("");
44
+ if (event.payload) {
45
+ lines.push("Example payload:");
46
+ lines.push("```json");
47
+ lines.push(JSON.stringify(event.payload, null, 2));
48
+ lines.push("```");
49
+ lines.push("");
50
+ }
51
+ lines.push("Code example:");
52
+ lines.push("```ts");
53
+ lines.push(`await ${args.trackMethod}({`, " identifiers: { /* the same identifiers you pass to identify */ },", ` event: ${JSON.stringify(event.name)},`, ` properties: ${JSON.stringify(event.payload ?? {})},`, "});");
54
+ lines.push("```");
55
+ lines.push("");
56
+ }
57
+ return lines.join("\n");
58
+ }
59
+
26
60
  //#endregion
27
61
  //#region ../../packages/shared/src/constants.ts
28
62
  /** The public docs site, for the mail and the pages that point people at it. */
@@ -99,6 +133,12 @@ const IDENTIFIER_KINDS = [
99
133
  "email"
100
134
  ];
101
135
  /**
136
+ * The monthly free allowance, in micro-dollars. Every org gets it, no plan
137
+ * required: it resets at each UTC calendar month and does not roll over.
138
+ * Spend past it draws the prepaid wallet balance.
139
+ */
140
+ const FREE_ALLOWANCE_MICROS = 4e6;
141
+ /**
102
142
  * The fixed-amount one-time top-ups, in micro-dollars ($10 / $50 / $200,
103
143
  * provisional). A topup is a single Stripe Checkout payment; the wallet
104
144
  * credit carries over month to month. Provisional numbers live here so a
@@ -109,6 +149,8 @@ const TOPUP_PRESETS_MICROS = [
109
149
  5e7,
110
150
  2e8
111
151
  ];
152
+ /** Micro-dollars in one dollar; the only place a display conversion starts. */
153
+ const MICRO_DOLLARS_PER_DOLLAR = 1e6;
112
154
  /**
113
155
  * The marketing purpose by name, since it is the one every gate, the
114
156
  * unsubscribe route, and the developer's own toggle all reach for.
@@ -5687,6 +5729,10 @@ const walletTopupSchema = z.object({
5687
5729
  completedAt: z.iso.datetime().nullable()
5688
5730
  });
5689
5731
  const listWalletTopupsQuerySchema = paginationQuerySchema;
5732
+ /** Micro-dollars as a plain dollar string, for the whole-dollar constants. */
5733
+ function dollars(micros) {
5734
+ return `$${micros / MICRO_DOLLARS_PER_DOLLAR}`;
5735
+ }
5690
5736
 
5691
5737
  //#endregion
5692
5738
  //#region ../../packages/shared/src/governance.ts
@@ -7673,7 +7719,10 @@ const templateSchema = z.object({
7673
7719
  * manifest entry, and searching inside that is a query the database layer
7674
7720
  * would have to grow a helper for. The badges in each row still show them.
7675
7721
  */
7676
- const listTemplatesQuerySchema = paginationQuerySchema.extend({ q: z.string().trim().max(200, "q must be at most 200 characters").optional().transform((value) => value ? value : void 0) });
7722
+ const listTemplatesQuerySchema = paginationQuerySchema.extend({
7723
+ appId: appIdSchema.optional(),
7724
+ q: z.string().trim().max(200, "q must be at most 200 characters").optional().transform((value) => value ? value : void 0)
7725
+ });
7677
7726
  /** The props a preview or a test send renders the template with. */
7678
7727
  const renderTemplateBodySchema = z.object({ data: z.object({ props: z.record(z.string(), z.unknown()).default({}) }) });
7679
7728
  /** A test send is a delivery like any other, so it takes the same props. */
@@ -9604,8 +9653,11 @@ const createRoute = (routeConfig) => {
9604
9653
  //#region ../../packages/shared/src/contract/define.ts
9605
9654
  /**
9606
9655
  * `createRoute` with the contract's additions: a required `operationId`
9607
- * (`<tag>.<verb>`, kept as a literal type so handler maps can key on it) and
9608
- * the `surfaces` block.
9656
+ * (`<tag>.<verb>`, kept as a literal type so handler maps can key on it),
9657
+ * `tags` drawn from `TAG_DOCS`, and the `surfaces` block. An optional
9658
+ * `description` is the text the summary cannot carry: it renders under the
9659
+ * operation in the docs, after the summary in the MCP tool, and after the
9660
+ * options in `cow <command> --help`.
9609
9661
  */
9610
9662
  function defineRoute(config) {
9611
9663
  return createRoute(config);
@@ -9746,6 +9798,7 @@ const apps = defineModule(defineRoute({
9746
9798
  operationId: "apps.update",
9747
9799
  tags: ["apps"],
9748
9800
  summary: "Rename or archive an app",
9801
+ description: "Admin only. Archiving is permanent: an archived app cannot be unarchived or modified. Renaming changes the display name only; the app id never changes.",
9749
9802
  security: SESSION_AUTH,
9750
9803
  request: {
9751
9804
  params: params$11,
@@ -9779,9 +9832,7 @@ const apps = defineModule(defineRoute({
9779
9832
  //#region ../../packages/shared/src/contract/artifacts.ts
9780
9833
  /**
9781
9834
  * Raw artifact transfer, the one place in the API where bytes travel outside
9782
- * an envelope. `cow push` HEADs every digest it built and PUTs only the ones
9783
- * the org does not already have, so a rebuild that changes one journey
9784
- * uploads one bundle.
9835
+ * an envelope (the story is the `artifacts` intro in `tags.ts`).
9785
9836
  *
9786
9837
  * Both routes are hidden from the CLI and MCP surfaces: they are plumbing
9787
9838
  * for the composite `cow push`, not something a human or an agent invokes
@@ -9857,6 +9908,7 @@ const billing = defineModule(defineRoute({
9857
9908
  operationId: "billing.topups.create",
9858
9909
  tags: ["billing"],
9859
9910
  summary: "Start a wallet top-up",
9911
+ description: "Admin only. Returns a hosted checkout URL to redirect to. `amountMicros` must be one of the fixed presets.",
9860
9912
  security: SESSION_AUTH,
9861
9913
  request: { body: jsonBody(createTopupBodySchema) },
9862
9914
  responses: {
@@ -9947,6 +9999,7 @@ const catalog = defineModule(defineRoute({
9947
9999
  operationId: "catalog.events.setState",
9948
10000
  tags: ["catalog"],
9949
10001
  summary: "Allow or deny an event name",
10002
+ description: "Denying an event name permanently drops any entries held for it in the review queue.",
9950
10003
  security: SESSION_AUTH,
9951
10004
  request: {
9952
10005
  params: params$10,
@@ -10003,6 +10056,7 @@ const catalog = defineModule(defineRoute({
10003
10056
  operationId: "catalog.traits.update",
10004
10057
  tags: ["catalog"],
10005
10058
  summary: "Update a catalog trait",
10059
+ description: "A trait type is frozen when registered and cannot be changed. Requests that specify a different `type` are rejected.",
10006
10060
  security: SESSION_AUTH,
10007
10061
  request: {
10008
10062
  params: params$10,
@@ -10020,6 +10074,7 @@ const catalog = defineModule(defineRoute({
10020
10074
  operationId: "catalog.traits.setState",
10021
10075
  tags: ["catalog"],
10022
10076
  summary: "Allow or deny a trait name",
10077
+ description: "Denying a trait name permanently drops any entries held for it in the review queue.",
10023
10078
  security: SESSION_AUTH,
10024
10079
  request: {
10025
10080
  params: params$10,
@@ -10036,6 +10091,7 @@ const catalog = defineModule(defineRoute({
10036
10091
  operationId: "catalog.quarantine.promote",
10037
10092
  tags: ["catalog"],
10038
10093
  summary: "Promote a quarantined name into the catalog",
10094
+ description: "Allow-lists the name in the catalog and backfills all held entries. Held events are recorded with their original timestamps, and held trait values apply to profiles in occurrence order.",
10039
10095
  security: SESSION_AUTH,
10040
10096
  request: { body: jsonBody(quarantineActionBodySchema) },
10041
10097
  responses: {
@@ -10050,6 +10106,7 @@ const catalog = defineModule(defineRoute({
10050
10106
  operationId: "catalog.quarantine.deny",
10051
10107
  tags: ["catalog"],
10052
10108
  summary: "Deny a quarantined name",
10109
+ description: "Denies the name in the catalog and permanently drops all entries held for it in the review queue.",
10053
10110
  security: SESSION_AUTH,
10054
10111
  request: { body: jsonBody(quarantineActionBodySchema) },
10055
10112
  responses: {
@@ -10128,6 +10185,7 @@ const deliveries = defineModule(defineRoute({
10128
10185
  operationId: "deliveries.sesFeedback",
10129
10186
  tags: ["deliveries"],
10130
10187
  summary: "SES delivery feedback via SNS (public)",
10188
+ description: "Updates delivery statuses, adds bounced and complained addresses to your suppression list, and revokes consent on complaints. Open tracking events are rejected because open tracking is disabled. Subscription confirmation requests from the platform's email provider are confirmed automatically.",
10131
10189
  security: NO_AUTH,
10132
10190
  surfaces: HIDDEN_FROM_TOOLS,
10133
10191
  request: { body: { content: { "text/plain": { schema: z.string() } } } },
@@ -10141,6 +10199,7 @@ const deliveries = defineModule(defineRoute({
10141
10199
  operationId: "deliveries.unsubscribe",
10142
10200
  tags: ["deliveries"],
10143
10201
  summary: "One-click unsubscribe (public)",
10202
+ description: "Revokes consent for the purpose the email was sent under on the recipient's profile. Setting `all` to `1` revokes the master `marketing` purpose instead, stopping all marketing email.",
10144
10203
  security: NO_AUTH,
10145
10204
  surfaces: HIDDEN_FROM_TOOLS,
10146
10205
  request: { query: tokenQuery.extend({
@@ -10248,6 +10307,7 @@ const domains = defineModule(defineRoute({
10248
10307
  operationId: "domains.update",
10249
10308
  tags: ["domains"],
10250
10309
  summary: "Toggle click tracking",
10310
+ description: "Enabling click tracking requires a verified domain, while disabling is always allowed. Tracking covers every email sent from the domain, including transactional email.",
10251
10311
  security: SESSION_AUTH,
10252
10312
  request: {
10253
10313
  params: params$9,
@@ -10264,6 +10324,7 @@ const domains = defineModule(defineRoute({
10264
10324
  operationId: "domains.delete",
10265
10325
  tags: ["domains"],
10266
10326
  summary: "Give up a sending domain",
10327
+ description: "Sending from this domain stops immediately, and the domain becomes available for any organization to claim.",
10267
10328
  security: SESSION_AUTH,
10268
10329
  request: { params: params$9 },
10269
10330
  responses: {
@@ -10318,6 +10379,7 @@ const emails = defineModule(defineRoute({
10318
10379
  operationId: "emails.verify",
10319
10380
  tags: ["emails"],
10320
10381
  summary: "Verify an email address (public)",
10382
+ description: "Marks the address verified. If the profile associated with the link still carries that address, sets `emailVerified` to `true` and records a `system.email_verified` event.",
10321
10383
  security: NO_AUTH,
10322
10384
  surfaces: HIDDEN_FROM_TOOLS,
10323
10385
  request: { query: tokenQuery },
@@ -10375,6 +10437,7 @@ const events = defineModule(defineRoute({
10375
10437
  operationId: "apps.events.seen",
10376
10438
  tags: ["events"],
10377
10439
  summary: "Whether an app has ever received these event names",
10440
+ description: "A name may be a pattern where `*` matches any run of characters against the whole name. A pattern reports the latest timestamp among matching events, or `null` if the app has never received one.",
10378
10441
  security: PIPELINE_AUTH,
10379
10442
  surfaces: { cli: false },
10380
10443
  request: {
@@ -10436,6 +10499,7 @@ const executions = defineModule(defineRoute({
10436
10499
  operationId: "executions.cancel",
10437
10500
  tags: ["executions"],
10438
10501
  summary: "Cancel live executions of a journey, or of one version",
10502
+ description: "Admin only. Each execution stops when it reaches its next step.",
10439
10503
  security: SESSION_AUTH,
10440
10504
  request: { body: jsonBody(cancelExecutionsBodySchema) },
10441
10505
  responses: {
@@ -10546,6 +10610,7 @@ const ingestion = defineModule(defineRoute({
10546
10610
  operationId: "ingestion.batch",
10547
10611
  tags: ["ingestion"],
10548
10612
  summary: "Import one source's calls in one request",
10613
+ description: "Batch writes skip triggers on raw events, but segments still recompute and a journey triggered by segment entry can fire. A failing item is reported in the response and does not fail the rest of the batch.",
10549
10614
  security: API_KEY_AUTH,
10550
10615
  surfaces: HIDDEN_FROM_TOOLS,
10551
10616
  request: { body: jsonBody(batchBodySchema) },
@@ -10626,6 +10691,7 @@ const journeys = defineModule(defineRoute({
10626
10691
  operationId: "journeys.setStatus",
10627
10692
  tags: ["journeys"],
10628
10693
  summary: "Put journeys on, off or on hold",
10694
+ description: "Admin only. Turning a journey off lets executions in flight finish, while putting it on hold pauses them before their next step. Turning a held journey on resumes them.",
10629
10695
  security: PIPELINE_AUTH,
10630
10696
  surfaces: { cli: false },
10631
10697
  request: { body: jsonBody(setJourneysStatusBodySchema) },
@@ -10656,6 +10722,7 @@ const journeys = defineModule(defineRoute({
10656
10722
  operationId: "journeys.delete",
10657
10723
  tags: ["journeys"],
10658
10724
  summary: "Delete a journey",
10725
+ description: "Admin only. Executions already in flight finish, but no new ones start. A push never deletes a journey, so this is the only way to remove one whose file was deleted from your code.",
10659
10726
  security: SESSION_AUTH,
10660
10727
  request: { params: params$7 },
10661
10728
  responses: {
@@ -10682,6 +10749,7 @@ const journeys = defineModule(defineRoute({
10682
10749
  operationId: "journeys.startEnrollment",
10683
10750
  tags: ["journeys"],
10684
10751
  summary: "Enroll the journey's current members",
10752
+ description: "Admin only. Starts an enrollment for recipients currently in the journey's segment. If an enrollment is already running, this returns the existing enrollment rather than starting another.",
10685
10753
  security: SESSION_AUTH,
10686
10754
  surfaces: HIDDEN_FROM_TOOLS,
10687
10755
  request: { params: params$7 },
@@ -10709,6 +10777,7 @@ const journeys = defineModule(defineRoute({
10709
10777
  operationId: "journeys.cancelEnrollment",
10710
10778
  tags: ["journeys"],
10711
10779
  summary: "Stop a running enrollment",
10780
+ description: "Admin only. Recipients already enrolled remain in the journey and their executions continue. Stopping the enrollment only prevents remaining recipients from enrolling.",
10712
10781
  security: SESSION_AUTH,
10713
10782
  request: { params: params$7 },
10714
10783
  responses: {
@@ -10722,6 +10791,7 @@ const journeys = defineModule(defineRoute({
10722
10791
  operationId: "journeys.dryRun",
10723
10792
  tags: ["journeys"],
10724
10793
  summary: "Dry-run a journey against a real user",
10794
+ description: "Admin only. Runs the journey's latest ready version against the specified profile with sends disabled, delivering no emails or webhooks. The execution proceeds even if the journey is turned off.",
10725
10795
  security: SESSION_AUTH,
10726
10796
  request: {
10727
10797
  params: params$7,
@@ -10816,6 +10886,7 @@ const pushes = defineModule(defineRoute({
10816
10886
  operationId: "pushes.create",
10817
10887
  tags: ["pushes"],
10818
10888
  summary: "Push a built app",
10889
+ description: "Every bundle and the source archive must be uploaded before creating the push. The push is refused if any referenced artifact is missing, or if a journey's `from` address uses a domain your organization has not verified.",
10819
10890
  security: PIPELINE_AUTH,
10820
10891
  surfaces: HIDDEN_FROM_TOOLS,
10821
10892
  request: { body: jsonBody(createPushBodySchema) },
@@ -11056,6 +11127,7 @@ const segments = defineModule(defineRoute({
11056
11127
  operationId: "segments.update",
11057
11128
  tags: ["segments"],
11058
11129
  summary: "Update a segment",
11130
+ description: "Updating a segment's `definition` recomputes its membership over existing profile history. A segment defined in code (in a journey trigger or your project's `segments/` directory) cannot be updated here: change the code and push the project again.",
11059
11131
  security: SESSION_AUTH,
11060
11132
  request: {
11061
11133
  params: params$5,
@@ -11073,6 +11145,7 @@ const segments = defineModule(defineRoute({
11073
11145
  operationId: "segments.delete",
11074
11146
  tags: ["segments"],
11075
11147
  summary: "Delete a segment",
11148
+ description: "Deleting a segment permanently removes all of its members. A segment defined in code (in a journey trigger or your project's `segments/` directory) cannot be deleted here: remove it from your project and push again.",
11076
11149
  security: SESSION_AUTH,
11077
11150
  request: { params: params$5 },
11078
11151
  responses: {
@@ -11117,6 +11190,7 @@ const settings = defineModule(defineRoute({
11117
11190
  operationId: "settings.update",
11118
11191
  tags: ["settings"],
11119
11192
  summary: "Update org settings",
11193
+ description: "Admin only. Lowering `eventRetentionDays` permanently deletes stored events older than the new window.",
11120
11194
  security: SESSION_AUTH,
11121
11195
  request: { body: jsonBody(updateOrgSettingsBodySchema) },
11122
11196
  responses: {
@@ -11182,6 +11256,7 @@ const settings = defineModule(defineRoute({
11182
11256
  operationId: "settings.pipelineKeys.revoke",
11183
11257
  tags: ["settings"],
11184
11258
  summary: "Revoke an org pipeline key",
11259
+ description: "Revocation is permanent. Calls authenticating with this key can still be accepted for up to a minute.",
11185
11260
  security: SESSION_AUTH,
11186
11261
  request: {
11187
11262
  params: z.object({ id: z.string() }),
@@ -11198,6 +11273,7 @@ const settings = defineModule(defineRoute({
11198
11273
  operationId: "settings.apiKeys.revoke",
11199
11274
  tags: ["settings"],
11200
11275
  summary: "Revoke an org API key",
11276
+ description: "Revocation is permanent. Calls authenticating with this key stop being accepted immediately.",
11201
11277
  security: SESSION_AUTH,
11202
11278
  request: {
11203
11279
  params: z.object({ id: z.string() }),
@@ -11215,10 +11291,9 @@ const settings = defineModule(defineRoute({
11215
11291
  const appParams = z.object({ appId: z.string() });
11216
11292
  const params$4 = z.object({ id: z.string() });
11217
11293
  /**
11218
- * A source is one inbound pipe into an app. It is created and listed under
11219
- * its parent app (the app is the attribution unit) and addressed by its own
11220
- * id afterwards. The webhook receiver is public: the provider's signature is
11221
- * its authorization.
11294
+ * Sources: created and listed under their parent app (so those two routes
11295
+ * carry the `apps` tag), addressed by their own id afterwards. The customer
11296
+ * story is the `sources` intro in `tags.ts`.
11222
11297
  */
11223
11298
  const sources = defineModule(defineRoute({
11224
11299
  method: "post",
@@ -11287,6 +11362,7 @@ const sources = defineModule(defineRoute({
11287
11362
  operationId: "sources.archive",
11288
11363
  tags: ["sources"],
11289
11364
  summary: "Archive a source",
11365
+ description: "Admin only. Archiving is irreversible and immediately stops this source from accepting calls or webhook deliveries. For an `api` source, this halts ingestion for the parent app until you add a replacement.",
11290
11366
  security: SESSION_AUTH,
11291
11367
  request: { params: params$4 },
11292
11368
  responses: {
@@ -11335,6 +11411,7 @@ const suppressions = defineModule(defineRoute({
11335
11411
  operationId: "suppressions.delete",
11336
11412
  tags: ["suppressions"],
11337
11413
  summary: "Un-suppress an address",
11414
+ description: "Admin only. Clears the address at the email provider so future sends can be attempted. If the provider cannot be reached, the address remains suppressed.",
11338
11415
  security: SESSION_AUTH,
11339
11416
  request: { params: z.object({ id: z.string() }) },
11340
11417
  responses: {
@@ -11345,19 +11422,160 @@ const suppressions = defineModule(defineRoute({
11345
11422
  }));
11346
11423
 
11347
11424
  //#endregion
11348
- //#region ../../packages/shared/src/contract/templates.ts
11425
+ //#region ../../packages/shared/src/contract/tags.ts
11349
11426
  /**
11350
- * Templates: the emails a journey sends, addressed by key like a journey.
11351
- * A template has versions and nothing else (ADR 0011), so there is no
11352
- * create and no update here: templates arrive with a push, and the only
11353
- * writes are rendering one to look at it, sending one to yourself, and
11354
- * deleting the key.
11427
+ * The prose for every contract tag: the API reference page's title,
11428
+ * description and intro, the OpenAPI document's `tags[]`, and the CLI's help
11429
+ * line for a command group named after a tag. Route `tags` are typed against
11430
+ * this record, so a new tag without an entry fails typecheck.
11355
11431
  *
11356
- * `preview` and `testSend` both render the latest version that finished
11357
- * compiling, in the sandbox, with the props the caller passes. `testSend`
11358
- * then sends that message to the caller's own address through the
11359
- * organization's default sender: it reaches no recipient, so it passes no
11360
- * consent gate, counts against nothing, and is logged as a test.
11432
+ * Intro links are docs-site-relative (`/tracking/apps/`): the docs render
11433
+ * them as written, and `buildOpenApiDocument` prefixes them with DOCS_URL.
11434
+ */
11435
+ const TAG_DOCS = {
11436
+ health: {
11437
+ title: "Health",
11438
+ description: "Liveness and readiness probes.",
11439
+ intro: "These routes are orchestration probes. Both sit outside the `/v1` prefix and need no credential."
11440
+ },
11441
+ ingestion: {
11442
+ title: "Ingestion",
11443
+ description: "POST /v1/identify, /v1/track, and /v1/batch: the org API-key-authenticated write path.",
11444
+ intro: "The write path ingests traits and events using an organization API key (`Authorization: Bearer <key>`). All calls sit behind the organization rate limiter and the billing hard stop, and dedupe retries using `Idempotency-Key` or `messageId`. Every call requires the `sourceId` of an `api` source, from which Cowliss derives and stamps the app (see [Apps tracking](/tracking/apps/)). Calls with an unknown, archived, or non-API `sourceId` are rejected. Event names starting with `system.` are rejected because that prefix is reserved for events Cowliss writes itself (see [Event patterns](/journeys/#the-system-namespace))."
11445
+ },
11446
+ users: {
11447
+ title: "Users",
11448
+ description: "Profiles: list, search, the 360 view, consent editing, merged event history, segment membership, GDPR export and erasure.",
11449
+ intro: "A profile holds what an app knows about a recipient: traits, identifiers, the consent map, merged event history, and segment membership. When a profile is merged into another, requests to its `usr_` id redirect to the survivor's URL. GDPR export and erasure can target a single profile by its `usr_` id, taking everything merged into it, or every profile across your apps that carries an identifier. All routes require session-token authentication, and erasure requires `org:admin`."
11450
+ },
11451
+ consent: {
11452
+ title: "Consent purposes",
11453
+ description: "The consent purposes your organization's profiles answer: `marketing` plus the ones your own code declares.",
11454
+ intro: "Every profile's `consent` map answers these purposes, and every switch that offers one is labelled from here. One is fixed for every organization, `marketing`, the master switch every other purpose sits under; the rest are whatever your code declares under `purposes` in [`cow.json`](/guides/repo/). `transactional` is not here: it is why a receipt or an alert goes out, which is not something a recipient chooses, so nothing stores an answer to it. Purposes are code, like journeys and templates, so this resource is read-only: a push is what adds or relabels one, and nothing ever deletes one, because profiles already hold answers against it."
11455
+ },
11456
+ events: {
11457
+ title: "Events",
11458
+ description: "The org-wide event feed, per-app volume stats, and single-event fetch.",
11459
+ intro: "These routes provide read views over your event history. Events are append-only and never rewritten. Ingestion writes are on the [Ingestion](/reference/api/ingestion/) page."
11460
+ },
11461
+ apps: {
11462
+ title: "Apps",
11463
+ description: "The app registry: your apps and products, and the sources that push into them.",
11464
+ intro: "An app is the attribution unit for one product: it carries no kind, configuration, or credential of its own, and each event and profile carries its `appId`. Inbound pipes into an app are sources created under it, and all of an app's sources land on the same profiles. This includes the first-party `api` source created with the app, which the SDK and HTTP path write through. Routes that address a source by its own id (including the public webhook receiver) are on the [Sources](/reference/api/sources/) page. See the [Apps and sources guide](/tracking/apps/)."
11465
+ },
11466
+ sources: {
11467
+ title: "Sources",
11468
+ description: "Sources addressed by id: config rotation, archiving, and the public webhook receiver.",
11469
+ intro: "A source is one inbound pipe into an [app](/reference/api/apps/): each app is created with the first-party `api` source, and a `kind` is singleton per app. Sources are created and listed under their parent app, and these routes address one by its own `id`. All writes are attributed to the source's parent app. The signing secret in `config` is write-only and never present in any response: `configured` reports whether one is stored. The webhook receiver is public: the caller's signature is the authorization, and the route answers with plain text rather than the envelope. See the [Apps and sources guide](/tracking/apps/)."
11470
+ },
11471
+ catalog: {
11472
+ title: "Catalog",
11473
+ description: "The tracking plan: event and trait definitions, governance states (allow/deny), and the strict-mode quarantine review queue.",
11474
+ intro: "The catalog is the tracking plan: event and trait definitions addressed by name, unique per organization, each in a governance state (`allowed`, `denied`, or `quarantined`, where denied always wins). Ingestion accepts by default, rejecting only denied names and canonical traits that fail format validation. With the org's `ingestionPolicy` set to `strict`, unclassified names are held in the quarantine review queue instead of applied. Property and trait types: " + propertyTypeSchema.options.map((type) => `\`${type}\``).join(", ") + ". Observation infers an array's item type when its members agree, and `array<any>` when they do not or the array is empty."
11475
+ },
11476
+ review: {
11477
+ title: "Review queue",
11478
+ description: "One list over both halves of the review queue: names held unapplied under a strict policy, and names accepted but flagged under a permissive one.",
11479
+ intro: "The review queue brings together everything ingestion parked for a person to decide on, replacing the two lists it unions. A held name is a write a strict policy would not apply until its name is classified; a violation is a name a permissive policy accepted and flagged. Both carry a `kind` and `name` alongside a first and a last sighting, so they list together under one cursor, ordered by last seen. This resource is a read and nothing else, as the per-item verbs stay where they are: allow or deny a held name on the [Catalog](/reference/api/catalog/) page, and resolve or dismiss a violation on the [Violations](/reference/api/violations/) page."
11480
+ },
11481
+ violations: {
11482
+ title: "Violations",
11483
+ description: "Tracking-plan violations: unknown names accepted but flagged, with one-click resolve into the catalog or dismissal.",
11484
+ intro: "In permissive mode, an unknown event or trait name is accepted and flagged here, so a typo never silently drops data. Resolving a violation registers the name into [the catalog](/reference/api/catalog/) pre-filled from the observed payload, while dismissing silences noise. Resolving and dismissing are admin only."
11485
+ },
11486
+ segments: {
11487
+ title: "Segments",
11488
+ description: "Computed audiences: predicate definitions, preview, member lists, and the recomputation lifecycle.",
11489
+ intro: "Segments are predicate definitions over traits and event history, recomputed synchronously on each write. Transitions emit `system.segment_entered` and `system.segment_exited` system events that journeys trigger on, except those found by a bulk recompute, which start nobody. See the [Segments guide](/segments/) for predicate semantics. All mutations require organization admin permissions."
11490
+ },
11491
+ journeys: {
11492
+ title: "Journeys",
11493
+ description: "One journey per key: trigger, tags, recorded step spine, the enable switch, and operational stats.",
11494
+ intro: "A journey is addressed by the key its author gave the file, running its latest ready version. Status decides whether the journey fires, and a push never touches it: a key is off until it is turned on. The `spine` is read from the journey's source and is display only: what a journey does during execution is whatever its code decides. A journey belongs to one app and fires on that app's events only. A trigger's `event` is a pattern in which `*` matches any sequence of characters, one or a list of them, and a pattern reaches Cowliss's own `system.` events only when its literal prefix starts with `system.`: see [Event patterns](/journeys/#event-patterns). Executions are on the [Executions](/reference/api/executions/) page."
11495
+ },
11496
+ templates: {
11497
+ title: "Templates",
11498
+ description: "The email templates a journey sends: their props, who sends them, rendering one, and sending yourself a test.",
11499
+ intro: "Templates are the email templates a journey sends, each addressed by the key its author gave the file. Templates arrive with a push, so there is no create and no update here. A template has versions and nothing else, no switch, because a send resolves the latest version that finished compiling at the moment it sends, so fixing copy reaches executions already in flight."
11500
+ },
11501
+ artifacts: {
11502
+ title: "Artifacts",
11503
+ description: "Content-addressed bundle and source storage: HEAD to check, PUT to upload.",
11504
+ intro: "Artifacts are immutable bytes named by their own `sha256:` digest and deduplicated within an organization. They serve as plumbing for `cow push` so changing one journey transfers only one bundle, and remain documented for anyone reimplementing push. This is the one place in the API where bytes travel outside an envelope. The body of a `PUT` must hash to the digest it is filed under, and repeat transfers of the same digest are a no-op. These routes carry their own size limit rather than the global request one."
11505
+ },
11506
+ pushes: {
11507
+ title: "Pushes",
11508
+ description: "One push per cow push: the whole project at one point in time, and the versions it compiles.",
11509
+ intro: "A push stores the app's source archive and creates a new version of every journey and template whose bundle changed; a key that did not change gets none. The push returns as soon as it is stored, and each version compiles on its own, so one file that cannot compile costs its own key and nothing else. A push turns nothing on: journey `status` is untouched."
11510
+ },
11511
+ versions: {
11512
+ title: "Versions",
11513
+ description: "One key's history: every version of a journey or a template, newest first.",
11514
+ intro: "A version is an immutable, content-addressed snapshot of one journey or one template. It is read-only and addressed through its key rather than on its own path. Journeys and templates share a key space, so both `key` and `kind` are required. The latest `ready` version is the code new executions and new sends use, which leaves the previous version running when a compile fails."
11515
+ },
11516
+ executions: {
11517
+ title: "Executions",
11518
+ description: "One run of one journey for one profile, pinned to the version it started on.",
11519
+ intro: "An execution is one journey for one profile, pinned to the version it started on. It can be filtered by journey, version, status, and profile in one place. Its detail carries what the journey logged with `api.log`. Pipeline keys can read executions, but stopping them is explicit: `cancel` is the only write and requires a dashboard session."
11520
+ },
11521
+ webhooks: {
11522
+ title: "Webhooks",
11523
+ description: "The URLs a journey POSTs a Standard-Webhooks-signed payload to, one registry the journey references by name.",
11524
+ intro: "Journeys reference webhooks by name, so journey code never hardcodes a URL. A webhook carries its own signing secret, shown only at creation. Payloads follow the Standard Webhooks convention with the `v: 1` version field and `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers. A webhook with too many consecutive failures auto-disables until re-enabled. All mutations require `org:admin`."
11525
+ },
11526
+ domains: {
11527
+ title: "Sending domains",
11528
+ description: "Org-verified sending domains: claims, the DNS records to install, verification status, and manual re-check.",
11529
+ intro: "An organization cannot send without a verified domain. Claims are global and exact-string: a parent or child of a claimed domain conflicts. The `dnsRecords` array specifies what your DNS must carry (three DKIM CNAMEs, the MX return path and its SPF TXT, plus a recommended DMARC TXT), each with its own status. DNS is re-checked on a schedule and nothing pushes an event when it is, so status refreshes are pull-only."
11530
+ },
11531
+ deliveries: {
11532
+ title: "Deliveries",
11533
+ description: "The global delivery log (every email and webhook attempted, with status progression) plus the public SES feedback and one-click unsubscribe endpoints.",
11534
+ intro: "Each send attempt creates a delivery. A delivery is `sent` then `delivered`, `bounced` or `complained`, or it is `failed`, a typed `skipped_*`, or a dry-run `would_*`. Status precedence ensures that a late `delivered` status never overwrites `bounced`, and a complaint outranks a bounce. For webhooks, a successful response confirms delivery, making `delivered` terminal. Public routes for delivery feedback and RFC 8058 one-click unsubscribe verify signatures instead of sessions, answering per the caller's convention rather than the standard envelope. Address verification is on the [emails](/reference/api/emails/) page."
11535
+ },
11536
+ emails: {
11537
+ title: "Emails",
11538
+ description: "The public email-address verification pages: the signed link in a verification email, answered as HTML.",
11539
+ intro: "These routes handle direct transactional email sending and public email-address verification. The `emails.send` route is the first-party shape of the same send the [Resend facade](/resend/) serves, authenticating with an org API key and answering in the envelope with the delivery id. The public verification routes require no Cowliss credentials and answer with HTML pages rather than the envelope. Verification links authorize through a single-use `?token=` that expires after 48 hours, and an invalid, expired, or already-spent token is rejected. The `GET` request only offers the verification and the `POST` request performs it, so a link scanner that fetches the URL does not verify the address."
11540
+ },
11541
+ suppressions: {
11542
+ title: "Suppressions",
11543
+ description: "Cowliss's mirror of your organization's suppression list: the cheap send-gate check and the dashboard's why.",
11544
+ intro: "Cowliss mirrors the suppression list your organization accumulates from hard bounces and complaints per address so the send gate skips cheaply and the dashboard can say why. Suppression is a separate fact from [consent](/reference/api/consent/): a complaint revokes consent, while a bounce only suppresses the address. The list is your organization's own: an address one organization suppresses is untouched for every other."
11545
+ },
11546
+ settings: {
11547
+ title: "Settings",
11548
+ description: "Org settings: ingestion policy, event retention, the sending-pause state, and self-service org API keys and pipeline keys.",
11549
+ intro: "Organization settings configure data handling: `ingestionPolicy` decides whether unclassified event and trait names are accepted and flagged or quarantined for review, `eventRetentionDays` keeps events for at most 18 months, and `sendingPaused` is a platform pause that the organization can read but only the platform sets. This module is also the self-service home of the organization's two key families: ingestion API keys, which write events and reach nothing else, and pipeline keys, which push an app and reach nothing else. Any member can list either family, but only admins create and revoke them. Neither list shows the other family's keys, and revoking a key from the other family cannot find it. See the [Keys guide](/guides/keys/)."
11550
+ },
11551
+ billing: {
11552
+ title: "Billing",
11553
+ description: "The prepaid wallet: metered spend against the monthly free allowance, fixed-amount Stripe top-ups, and the hard stop at zero credit.",
11554
+ intro: "Spend is one weighted total over events, emails, webhooks and journey runs, counted from what already happened, never billed in arrears. Credit is the monthly included usage (" + dollars(FREE_ALLOWANCE_MICROS) + ", resets each UTC month, no rollover) plus the prepaid wallet (carries over). At zero credit, the platform hard-stops: ingestion is refused and sends are skipped. See the [Billing guide](/guides/billing/)."
11555
+ },
11556
+ openapi: {
11557
+ title: "OpenAPI",
11558
+ description: "The OpenAPI 3.1 document, built from this contract.",
11559
+ intro: "The document is built from the route contract. Requests to `GET /openapi.json` are served unauthenticated."
11560
+ },
11561
+ me: {
11562
+ title: "Me",
11563
+ description: "The signed-in developer's own settings.",
11564
+ intro: "These routes manage the signed-in developer's own settings. Like the rest of the dashboard API, requests use session authentication, but this resource is deliberately not org-scoped: the subject is the caller's own platform-workspace profile, taken from the session, never from the request. They are hidden from the CLI and the MCP server because both act with an organization key on that organization's data without a signed-in human. An operator changing an end user's consent uses `users.updateConsent` instead, which is the same decision made by someone else."
11565
+ },
11566
+ resend: {
11567
+ title: "Resend-compatible send",
11568
+ description: "The Resend-compatible send facade: point `RESEND_BASE_URL` at `/resend/{appOrSourceId}` and keep your code.",
11569
+ intro: "The Resend-compatible send facade lets developers point `RESEND_BASE_URL` at `/resend/{appOrSourceId}` and keep their code. The `appOrSourceId` path segment accepts an app id or the id of its API source, because the Resend SDK carries no other field and accepts no custom header. The bearer is the organization's ingestion key. Every send creates a delivery and requires a from-address on a domain the organization has verified. These routes answer Resend's own shapes rather than the envelope."
11570
+ }
11571
+ };
11572
+
11573
+ //#endregion
11574
+ //#region ../../packages/shared/src/contract/templates.ts
11575
+ /**
11576
+ * Templates, addressed by key like a journey. No create and no update: a
11577
+ * template has versions and nothing else (ADR 0011). What `preview` and
11578
+ * `testSend` do is the `templates` intro in `tags.ts`.
11361
11579
  */
11362
11580
  const params$3 = z.object({ key: z.string() });
11363
11581
  const templates = defineModule(defineRoute({
@@ -11408,6 +11626,7 @@ const templates = defineModule(defineRoute({
11408
11626
  operationId: "templates.testSend",
11409
11627
  tags: ["templates"],
11410
11628
  summary: "Send a rendered template to your own address",
11629
+ description: "Sends through your organization's default sender. The test goes to you regardless of your consent settings, costs nothing, and shows as a test.",
11411
11630
  security: SESSION_AUTH,
11412
11631
  request: {
11413
11632
  params: params$3,
@@ -11424,6 +11643,7 @@ const templates = defineModule(defineRoute({
11424
11643
  operationId: "templates.delete",
11425
11644
  tags: ["templates"],
11426
11645
  summary: "Delete an email template",
11646
+ description: "Admin only. Deleting a key deletes every version of it and is refused while a journey still sends it, naming the journeys.",
11427
11647
  security: SESSION_AUTH,
11428
11648
  request: { params: params$3 },
11429
11649
  responses: {
@@ -11504,6 +11724,7 @@ const users = defineModule(defineRoute({
11504
11724
  operationId: "users.eraseByIdentifier",
11505
11725
  tags: ["users"],
11506
11726
  summary: "Erase every person one identifier names (GDPR)",
11727
+ description: "Admin only. Terminates active journey executions, then permanently deletes every profile across your apps that carries the identifier, along with their merged profiles, and their event and delivery history. This action cannot be undone.",
11507
11728
  security: SESSION_AUTH,
11508
11729
  request: { body: jsonBody(eraseUserByIdentifierBodySchema) },
11509
11730
  responses: {
@@ -11531,6 +11752,7 @@ const users = defineModule(defineRoute({
11531
11752
  operationId: "users.erase",
11532
11753
  tags: ["users"],
11533
11754
  summary: "Erase a user (GDPR)",
11755
+ description: "Admin only. Terminates active journey executions, then permanently deletes the profile, any profiles merged into it, and their event and delivery history. This action cannot be undone.",
11534
11756
  security: SESSION_AUTH,
11535
11757
  request: { params: params$2 },
11536
11758
  responses: {
@@ -11651,6 +11873,7 @@ const violations = defineModule(defineRoute({
11651
11873
  operationId: "violations.resolve",
11652
11874
  tags: ["violations"],
11653
11875
  summary: "Resolve a violation into the catalog",
11876
+ description: "Admin only. Resolving is irreversible and refuses violations that have already been handled or whose name is already in the catalog. You must supply `data.type` when resolving a trait that was only observed as `null`.",
11654
11877
  security: SESSION_AUTH,
11655
11878
  request: {
11656
11879
  params: params$1,
@@ -11667,6 +11890,7 @@ const violations = defineModule(defineRoute({
11667
11890
  operationId: "violations.dismiss",
11668
11891
  tags: ["violations"],
11669
11892
  summary: "Dismiss a violation",
11893
+ description: "Admin only. Dismissing is irreversible and refuses violations that have already been handled. Future sightings of the name continue to be accepted but do not flag new violations.",
11670
11894
  security: SESSION_AUTH,
11671
11895
  request: { params: params$1 },
11672
11896
  responses: {
@@ -11724,6 +11948,7 @@ const webhooks = defineModule(defineRoute({
11724
11948
  operationId: "webhooks.update",
11725
11949
  tags: ["webhooks"],
11726
11950
  summary: "Update a webhook",
11951
+ description: "Setting `enabled` to `true` re-enables an auto-disabled webhook and resets its consecutive failure counter to zero. Setting `enabled` to `false` disables the webhook manually.",
11727
11952
  security: SESSION_AUTH,
11728
11953
  request: {
11729
11954
  params,
@@ -12136,6 +12361,7 @@ function cliCommandFor(route, moduleKey) {
12136
12361
  groups: segments.slice(0, -1),
12137
12362
  verb,
12138
12363
  summary: route.summary ?? route.operationId,
12364
+ description: route.description,
12139
12365
  positionals: pathParams(route),
12140
12366
  queryOptions: queryOptions(route),
12141
12367
  bodyOptions: body,
@@ -12150,7 +12376,8 @@ function shapeKeys(schema) {
12150
12376
  function mcpToolFor(route, moduleKey) {
12151
12377
  assertNoHeaders(route);
12152
12378
  const name = route.surfaces?.mcpName ?? route.operationId.replaceAll(".", "_");
12153
- const description = route.surfaces?.description ?? route.summary ?? route.operationId;
12379
+ const summary = route.summary ?? route.operationId;
12380
+ const description = route.surfaces?.description ?? (route.description ? `${summary}\n\n${route.description}` : summary);
12154
12381
  const data = bodyDataSchema(route);
12155
12382
  const rawBody = data !== void 0 && !(data instanceof z.ZodObject);
12156
12383
  const bodyKeys = data === void 0 ? [] : data instanceof z.ZodObject ? Object.keys(data.shape) : ["data"];
@@ -12304,7 +12531,7 @@ function groupFor(program, groups, segments) {
12304
12531
  const segment = segments[depth - 1];
12305
12532
  let group = groups.get(key);
12306
12533
  if (!group) {
12307
- group = parent.command(segment).description(segment);
12534
+ group = parent.command(segment).description(depth === 1 && Object.hasOwn(TAG_DOCS, segment) ? TAG_DOCS[segment].description : segment);
12308
12535
  groups.set(key, group);
12309
12536
  }
12310
12537
  parent = group;
@@ -12318,6 +12545,7 @@ function registerContractCommands(program, run) {
12318
12545
  const requestOf = (client) => client.request;
12319
12546
  for (const spec of plan.cli) {
12320
12547
  const command = groupFor(program, groups, spec.groups).command([spec.verb, ...spec.positionals.map((name) => `<${name}>`)].join(" ")).description(spec.summary);
12548
+ if (spec.description) command.addHelpText("after", `\n${spec.description}`);
12321
12549
  for (const option of [...spec.queryOptions, ...spec.bodyOptions]) registerOption(command, option);
12322
12550
  command.action(async (...args) => {
12323
12551
  const positionals = args.slice(0, spec.positionals.length);
@@ -12482,6 +12710,42 @@ function registerEnable(program, clientFor, io) {
12482
12710
  register(program, clientFor, io, "pause", "paused");
12483
12711
  }
12484
12712
 
12713
+ //#endregion
12714
+ //#region src/commands/events.ts
12715
+ /** `cow events --help` and the `events_prompt` MCP tool, in one place. */
12716
+ const eventsDescription = {
12717
+ cli: "instructions for a coding agent to send the events this app's journeys need",
12718
+ mcp: "Instructions for a coding agent to send the events this app's journeys need. Reads the local project; calls no API."
12719
+ };
12720
+ async function projectEventsPrompt(dir) {
12721
+ const projectDir = dir ?? resolveProjectDir();
12722
+ const { manifest } = await buildProject(projectDir);
12723
+ const items = manifest.events.map((evt) => ({ name: evt.pattern }));
12724
+ return buildEventsPrompt({
12725
+ events: items,
12726
+ sdkPackage: "@cowliss/sdk",
12727
+ trackMethod: "cow.track",
12728
+ referenceUrl: `${DOCS_URL}/sdk/track/`
12729
+ });
12730
+ }
12731
+ function registerEvents(program, io) {
12732
+ let eventsCmd = program.commands.find((c) => c.name() === "events");
12733
+ if (!eventsCmd) eventsCmd = program.command("events");
12734
+ eventsCmd.description(eventsDescription.cli);
12735
+ eventsCmd.action(async (opts) => {
12736
+ const merged = {
12737
+ ...program.opts(),
12738
+ ...opts
12739
+ };
12740
+ const prompt = await projectEventsPrompt();
12741
+ if (merged.json === true) {
12742
+ emit({ data: { prompt } }, io, true);
12743
+ return;
12744
+ }
12745
+ io.stdout(`${prompt}\n`);
12746
+ });
12747
+ }
12748
+
12485
12749
  //#endregion
12486
12750
  //#region src/commands/init.ts
12487
12751
  const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
@@ -13085,6 +13349,11 @@ function registerPush(program, clientFor, env, io) {
13085
13349
  * `unseenReport`. There is no second computation that could drift from the
13086
13350
  * one the push itself shows.
13087
13351
  */
13352
+ /** `cow plan --help` and the `pushes_plan` MCP tool, in one place. */
13353
+ const planDescription = {
13354
+ cli: "show what a push of this repository would change, and change nothing",
13355
+ mcp: "What a push of the project in the current directory would change. Builds locally; uploads and stores nothing."
13356
+ };
13088
13357
  /** The envelope `cow plan --json` prints, and what the MCP tool answers. */
13089
13358
  async function projectPlan(client) {
13090
13359
  const { plan } = await planProject({
@@ -13094,7 +13363,7 @@ async function projectPlan(client) {
13094
13363
  return { data: plan };
13095
13364
  }
13096
13365
  function registerPlan(program, clientFor, io) {
13097
- program.command("plan").description("show what a push of this repository would change, and change nothing").action(async (opts) => {
13366
+ program.command("plan").description(planDescription.cli).action(async (opts) => {
13098
13367
  const merged = {
13099
13368
  ...program.opts(),
13100
13369
  ...opts
@@ -13195,9 +13464,28 @@ async function createMcpServer({ client }) {
13195
13464
  inputSchema: tool.inputSchema
13196
13465
  }, async (args) => toolResult(() => tool.route.request ? request(tool.route, tool.toInput(args)) : request(tool.route)));
13197
13466
  server.registerTool("pushes_plan", {
13198
- description: "What a push of the project in the current directory would change. Builds locally; uploads and stores nothing.",
13467
+ description: planDescription.mcp,
13199
13468
  inputSchema: {}
13200
13469
  }, async () => toolResult(() => projectPlan(client)));
13470
+ server.registerTool("events_prompt", {
13471
+ description: eventsDescription.mcp,
13472
+ inputSchema: {}
13473
+ }, async () => {
13474
+ try {
13475
+ return { content: [{
13476
+ type: "text",
13477
+ text: await projectEventsPrompt()
13478
+ }] };
13479
+ } catch (error) {
13480
+ return {
13481
+ content: [{
13482
+ type: "text",
13483
+ text: errorMessage$1(error)
13484
+ }],
13485
+ isError: true
13486
+ };
13487
+ }
13488
+ });
13201
13489
  return server;
13202
13490
  }
13203
13491
  function errorMessage$1(error) {
@@ -13615,6 +13903,7 @@ function buildProgram(env, io) {
13615
13903
  emit(await fn(await clientFor(merged)), io, typeof merged.json === "boolean" ? merged.json : void 0);
13616
13904
  };
13617
13905
  registerContractCommands(program, run);
13906
+ registerEvents(program, io);
13618
13907
  registerAuth(program, env, io);
13619
13908
  registerInit(program, clientFor, env, io);
13620
13909
  registerAdd(program, io);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cowliss/cli",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "cow": "./dist/index.js"