@flowmind/cli 0.3.6 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +51 -15
  2. package/package.json +8 -1
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ var ERROR_CODES = {
23
23
  INVALID_API_RESPONSE: "INVALID_API_RESPONSE",
24
24
  MAX_ACTIVE_JOBS_REACHED: "MAX_ACTIVE_JOBS_REACHED",
25
25
  NOT_FOUND: "NOT_FOUND",
26
+ PLAN_LIMIT_REACHED: "PLAN_LIMIT_REACHED",
26
27
  QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
27
28
  RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
28
29
  SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
@@ -455,10 +456,10 @@ async function writeIgnore(start, reason) {
455
456
  }
456
457
 
457
458
  // ../../packages/contracts/src/generated/revision.ts
458
- var contractRevision = "601655633e35e0cc9e849f50cb39df507ee6112f56a9bfff4ce2c8db526b8d45";
459
+ var contractRevision = "bbd616246f8533ef25c4f4889cea85dde03967c231740071c89597c025853d6b";
459
460
 
460
461
  // ../../packages/contracts/src/generated/validators/apiError.js
461
- var m = { additionalProperties: false, properties: { data: { enum: [null], nullable: true, type: "object" }, details: {}, error: { enum: ["BAD_REQUEST", "PUBLICATION_REVISION_CONFLICT", "PUBLICATION_STALE_ENTRIES", "SHARE_STALE_ENTRIES", "SLUG_TAKEN", "CONFLICT", "EXPIRED", "FORBIDDEN", "HUMAN_SESSION_REQUIRED", "INTERNAL_ERROR", "INVALID_API_RESPONSE", "MAX_ACTIVE_JOBS_REACHED", "NOT_FOUND", "QUOTA_EXCEEDED", "RATE_LIMIT_EXCEEDED", "SERVICE_UNAVAILABLE", "UNDECLARED_API_OPERATION", "UNAUTHENTICATED", "VALIDATION_FAILED"], type: "string" }, message: { type: "string" }, requestId: { minLength: 1, type: "string" }, status: { maximum: 599, minimum: 400, type: "integer" }, success: { enum: [false], type: "boolean" } }, required: ["success", "status", "message", "data", "requestId", "error", "details"], type: "object" };
462
+ var m = { additionalProperties: false, properties: { data: { enum: [null], nullable: true, type: "object" }, details: {}, error: { enum: ["BAD_REQUEST", "PUBLICATION_REVISION_CONFLICT", "PUBLICATION_STALE_ENTRIES", "SHARE_STALE_ENTRIES", "SLUG_TAKEN", "CONFLICT", "EXPIRED", "FORBIDDEN", "HUMAN_SESSION_REQUIRED", "INTERNAL_ERROR", "INVALID_API_RESPONSE", "MAX_ACTIVE_JOBS_REACHED", "NOT_FOUND", "PLAN_LIMIT_REACHED", "QUOTA_EXCEEDED", "RATE_LIMIT_EXCEEDED", "SERVICE_UNAVAILABLE", "UNDECLARED_API_OPERATION", "UNAUTHENTICATED", "VALIDATION_FAILED"], type: "string" }, message: { type: "string" }, requestId: { minLength: 1, type: "string" }, status: { maximum: 599, minimum: 400, type: "integer" }, success: { enum: [false], type: "boolean" } }, required: ["success", "status", "message", "data", "requestId", "error", "details"], type: "object" };
462
463
  function t2(r2, { instancePath: i4 = "", parentData: E, parentDataProperty: d5, rootData: f2 = r2 } = {}) {
463
464
  let p7 = null, s6 = 0;
464
465
  if (s6 === 0) if (r2 && typeof r2 == "object" && !Array.isArray(r2)) {
@@ -481,7 +482,7 @@ function t2(r2, { instancePath: i4 = "", parentData: E, parentDataProperty: d5,
481
482
  if (r2.error !== void 0) {
482
483
  let e4 = r2.error, o10 = s6;
483
484
  if (typeof e4 != "string") return t2.errors = [{ instancePath: i4 + "/error", schemaPath: "#/properties/error/type", keyword: "type", params: { type: "string" }, message: "must be string" }], false;
484
- if (!(e4 === "BAD_REQUEST" || e4 === "PUBLICATION_REVISION_CONFLICT" || e4 === "PUBLICATION_STALE_ENTRIES" || e4 === "SHARE_STALE_ENTRIES" || e4 === "SLUG_TAKEN" || e4 === "CONFLICT" || e4 === "EXPIRED" || e4 === "FORBIDDEN" || e4 === "HUMAN_SESSION_REQUIRED" || e4 === "INTERNAL_ERROR" || e4 === "INVALID_API_RESPONSE" || e4 === "MAX_ACTIVE_JOBS_REACHED" || e4 === "NOT_FOUND" || e4 === "QUOTA_EXCEEDED" || e4 === "RATE_LIMIT_EXCEEDED" || e4 === "SERVICE_UNAVAILABLE" || e4 === "UNDECLARED_API_OPERATION" || e4 === "UNAUTHENTICATED" || e4 === "VALIDATION_FAILED")) return t2.errors = [{ instancePath: i4 + "/error", schemaPath: "#/properties/error/enum", keyword: "enum", params: { allowedValues: m.properties.error.enum }, message: "must be equal to one of the allowed values" }], false;
485
+ if (!(e4 === "BAD_REQUEST" || e4 === "PUBLICATION_REVISION_CONFLICT" || e4 === "PUBLICATION_STALE_ENTRIES" || e4 === "SHARE_STALE_ENTRIES" || e4 === "SLUG_TAKEN" || e4 === "CONFLICT" || e4 === "EXPIRED" || e4 === "FORBIDDEN" || e4 === "HUMAN_SESSION_REQUIRED" || e4 === "INTERNAL_ERROR" || e4 === "INVALID_API_RESPONSE" || e4 === "MAX_ACTIVE_JOBS_REACHED" || e4 === "NOT_FOUND" || e4 === "PLAN_LIMIT_REACHED" || e4 === "QUOTA_EXCEEDED" || e4 === "RATE_LIMIT_EXCEEDED" || e4 === "SERVICE_UNAVAILABLE" || e4 === "UNDECLARED_API_OPERATION" || e4 === "UNAUTHENTICATED" || e4 === "VALIDATION_FAILED")) return t2.errors = [{ instancePath: i4 + "/error", schemaPath: "#/properties/error/enum", keyword: "enum", params: { allowedValues: m.properties.error.enum }, message: "must be equal to one of the allowed values" }], false;
485
486
  var a4 = o10 === s6;
486
487
  } else var a4 = true;
487
488
  if (a4) {
@@ -621,7 +622,7 @@ var U = c((r2) => {
621
622
  // ../../packages/contracts/src/generated/validators/shared/774NGUAG.js
622
623
  var t3 = U().fullFormats.date;
623
624
 
624
- // ../../packages/contracts/src/generated/validators/shared/2JZBIP7E.js
625
+ // ../../packages/contracts/src/generated/validators/shared/53EW46KW.js
625
626
  function s(e4, { instancePath: a4 = "", parentData: n7, parentDataProperty: u4, rootData: l4 = e4 } = {}) {
626
627
  let m5 = null, r2 = 0;
627
628
  if (r2 === 0) if (e4 && typeof e4 == "object" && !Array.isArray(e4)) {
@@ -2794,9 +2795,23 @@ var ApiRejectionError = class extends BridgeError {
2794
2795
  return this.#rejectionMessage;
2795
2796
  }
2796
2797
  };
2798
+ var PlanLimitError = class extends BridgeError {
2799
+ constructor(error, details) {
2800
+ super(error.code, error.context);
2801
+ this.details = details;
2802
+ }
2803
+ };
2804
+ var readPlanLimitDetails = (details) => {
2805
+ const value = details;
2806
+ if (typeof value !== "object" || value === null || typeof value.limitKey !== "string" || !Number.isInteger(value.limit) || !Number.isInteger(value.used) || !(value.resetAt === null || typeof value.resetAt === "string")) {
2807
+ throw new Error("Flowmind sent a plan limit error without its details");
2808
+ }
2809
+ return { limitKey: value.limitKey, limit: value.limit, used: value.used, resetAt: value.resetAt };
2810
+ };
2797
2811
  async function apiRequest(endpoint, args, options = {}) {
2798
2812
  const apiKey = options.authOptional ? void 0 : requireApiKey();
2799
2813
  let rejectionResponse;
2814
+ let planLimitResponse;
2800
2815
  try {
2801
2816
  return await requestEndpoint(toleratingAdditions(endpoint), API_URL, args, {
2802
2817
  headers: {
@@ -2804,17 +2819,21 @@ async function apiRequest(endpoint, args, options = {}) {
2804
2819
  ...options.headers
2805
2820
  },
2806
2821
  ...options.signal ? { signal: options.signal } : {},
2807
- ...options.preserveRejectionMessage ? {
2808
- fetch: async (input2, init) => {
2809
- const response = await fetch(input2, init);
2810
- if (response.status >= 400 && response.status < 500 && response.status !== 408) {
2811
- rejectionResponse = response.clone();
2812
- }
2813
- return response;
2822
+ fetch: async (input2, init) => {
2823
+ const response = await fetch(input2, init);
2824
+ if (response.status === 402) planLimitResponse = response.clone();
2825
+ if (options.preserveRejectionMessage && response.status >= 400 && response.status < 500 && response.status !== 408) {
2826
+ rejectionResponse = response.clone();
2814
2827
  }
2815
- } : {}
2828
+ return response;
2829
+ }
2816
2830
  });
2817
2831
  } catch (error) {
2832
+ if (error instanceof BridgeError && error.context.kind === "api" && error.code === "PLAN_LIMIT_REACHED" && planLimitResponse) {
2833
+ const envelope = await planLimitResponse.json();
2834
+ assertApiEnvelope(envelope);
2835
+ throw new PlanLimitError(error, readPlanLimitDetails(envelope.details));
2836
+ }
2818
2837
  if (error instanceof BridgeError && error.context.kind === "api" && rejectionResponse) {
2819
2838
  const envelope = await rejectionResponse.json();
2820
2839
  assertApiEnvelope(envelope);
@@ -3498,8 +3517,25 @@ async function resolveSession(explicit, cwd, now = /* @__PURE__ */ new Date()) {
3498
3517
  }
3499
3518
 
3500
3519
  // src/command-error.ts
3520
+ var LIMIT_WORDS = {
3521
+ "projects.active": (limit) => `${limit} active projects`,
3522
+ "logs.personal.month": (limit) => `${limit} personal logs this month`,
3523
+ "logs.agent.month": (limit) => `${limit} agent logs this month`
3524
+ };
3525
+ var describePlanLimit = ({ limit, limitKey, resetAt }) => {
3526
+ const what = (LIMIT_WORDS[limitKey] ?? ((count) => `${count} of ${limitKey}`))(limit);
3527
+ const frees = resetAt === null ? "Archiving a finished project frees a slot (archiving is final), or upgrade to Pro." : `It resets on ${new Date(resetAt).toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric", timeZone: "UTC" })}, or upgrade to Pro.`;
3528
+ return `Your plan allows ${what}. ${frees} Everything you already logged stays. Upgrade in the Flowmind web app under Settings > Plan.`;
3529
+ };
3501
3530
  var printCommandError = (action, error, json = false, extra) => {
3502
3531
  let diagnostic;
3532
+ if (error instanceof PlanLimitError) {
3533
+ const message = describePlanLimit(error.details);
3534
+ console.error(json ? JSON.stringify({ error: message, code: error.code, ...error.details, ...extra }) : `${message}${extra ? `
3535
+ ${extra.message}` : ""}`);
3536
+ process.exitCode = 1;
3537
+ return;
3538
+ }
3503
3539
  if (error instanceof BridgeError) {
3504
3540
  const context2 = error.context;
3505
3541
  const schema = `${context2.schemaId ?? "unavailable"}${context2.schemaPath ?? ""}`;
@@ -5053,7 +5089,7 @@ ${box}
5053
5089
  }
5054
5090
  });
5055
5091
 
5056
- // ../../packages/contracts/src/generated/validators/shared/FSQ24WG2.js
5092
+ // ../../packages/contracts/src/generated/validators/shared/2AOOQGDJ.js
5057
5093
  var b2 = { description: "A project", properties: { archivedAt: { format: "date-time", nullable: true, type: "string" }, category: { type: "string" }, color: { type: "string" }, completedAt: { format: "date-time", nullable: true, type: "string" }, createdAt: { format: "date-time", type: "string" }, description: { nullable: true, type: "string" }, details: { description: "Private details for lastOutcome, or an empty array when no outcome exists.", items: { type: "string" }, type: "array" }, estimatedHours: { nullable: true, type: "integer" }, icon: { type: "string" }, id: { format: "uuid", type: "string" }, lastActiveDay: { format: "date", nullable: true, type: "string" }, lastOutcome: { nullable: true, type: "string" }, launchDescription: { nullable: true, type: "string" }, launchUrl: { nullable: true, type: "string" }, launchedAt: { format: "date-time", nullable: true, type: "string" }, name: { type: "string" }, nextStep: { anyOf: [{ $ref: "urn:flowmind:schema:ProjectNextStep" }, { enum: [null], nullable: true, type: "object" }] }, updatedAt: { format: "date-time", type: "string" } }, required: ["id", "name", "description", "color", "icon", "category", "estimatedHours", "launchedAt", "completedAt", "launchDescription", "launchUrl", "createdAt", "updatedAt", "archivedAt", "nextStep", "lastOutcome", "details", "lastActiveDay"], type: "object" };
5058
5094
  function n6(r2, { instancePath: i4 = "", parentData: k3, parentDataProperty: _, rootData: w3 = r2 } = {}) {
5059
5095
  let l4 = null, e4 = 0;
@@ -6224,7 +6260,7 @@ import { Command as Command10 } from "commander";
6224
6260
  // ../../packages/contracts/src/generated/validators/shared/VVGMHJLN.js
6225
6261
  var e3 = new RegExp("^[a-f0-9]{64}$", "u");
6226
6262
 
6227
- // ../../packages/contracts/src/generated/validators/shared/32ECWY24.js
6263
+ // ../../packages/contracts/src/generated/validators/shared/LVEL2FHK.js
6228
6264
  var K2 = new RegExp("^[a-z0-9]+(-[a-z0-9]+)*$", "u");
6229
6265
  var w2 = { additionalProperties: false, properties: { candidates: { items: { additionalProperties: false, properties: { agentLabel: { nullable: true, type: "string" }, artifactUrl: { nullable: true, type: "string" }, captureSource: { type: "string" }, details: { items: { type: "string" }, type: "array" }, digest: { pattern: "^[a-f0-9]{64}$", type: "string" }, durationSeconds: { minimum: 0, type: "integer" }, entryId: { format: "uuid", type: "string" }, fieldVersion: { minimum: 0, type: "integer" }, localDay: { pattern: "^\\d{4}-\\d{2}-\\d{2}$", type: "string" }, loggedLate: { type: "boolean" }, outcome: { type: "string" }, provenance: { enum: ["personal", "agent"], type: "string" }, selection: { additionalProperties: false, nullable: true, properties: { artifactKind: { enum: ["code", "design", "document", "demo", "other", null], nullable: true, type: "string" }, selectedAt: { format: "date-time", type: "string" } }, required: ["artifactKind", "selectedAt"], type: "object" }, state: { enum: ["public", "changed", "new", "private"], type: "string" }, suggestedArtifactKind: { enum: ["code", "design", "document", "demo", "other", null], nullable: true, type: "string" } }, required: ["entryId", "localDay", "outcome", "details", "artifactUrl", "durationSeconds", "provenance", "captureSource", "loggedLate", "digest", "fieldVersion", "agentLabel", "suggestedArtifactKind", "state", "selection"], type: "object" }, type: "array" }, candidatesTruncated: { type: "boolean" }, counts: { additionalProperties: false, properties: { changed: { minimum: 0, type: "integer" }, eligible: { minimum: 0, type: "integer" }, new: { minimum: 0, type: "integer" }, public: { minimum: 0, type: "integer" } }, required: ["public", "changed", "new", "eligible"], type: "object" }, project: { additionalProperties: false, properties: { archived: { type: "boolean" }, color: { type: "string" }, id: { format: "uuid", type: "string" }, name: { type: "string" } }, required: ["id", "name", "color", "archived"], type: "object" }, publication: { additionalProperties: false, nullable: true, properties: { hiddenByModeration: { type: "boolean" }, id: { format: "uuid", type: "string" }, links: { additionalProperties: false, properties: { badgeMarkdown: { type: "string" }, card: { type: "string" }, feed: { type: "string" }, page: { type: "string" } }, required: ["page", "card", "feed", "badgeMarkdown"], type: "object" }, publicSummary: { nullable: true, type: "string" }, publicTitle: { type: "string" }, publishedAt: { format: "date-time", nullable: true, type: "string" }, reviewedAt: { format: "date-time", type: "string" }, slug: { maxLength: 60, minLength: 3, pattern: "^[a-z0-9]+(-[a-z0-9]+)*$", type: "string" }, status: { enum: ["published", "unpublished"], type: "string" }, updatedAt: { format: "date-time", type: "string" } }, required: ["id", "slug", "publicTitle", "publicSummary", "status", "hiddenByModeration", "publishedAt", "reviewedAt", "updatedAt", "links"], type: "object" }, revision: { minimum: 0, type: "integer" }, suggestedSlug: { maxLength: 60, minLength: 3, pattern: "^[a-z0-9]+(-[a-z0-9]+)*$", type: "string" }, suggestedTitle: { type: "string" }, username: { type: "string" } }, required: ["project", "username", "revision", "suggestedSlug", "suggestedTitle", "publication", "counts", "candidates", "candidatesTruncated"], type: "object" };
6230
6266
  function s5(l4, { instancePath: i4 = "", parentData: x4, parentDataProperty: I2, rootData: B = l4 } = {}) {
@@ -6793,7 +6829,7 @@ var shipLogCommand = new Command10("ship-log").description("Read a project ship
6793
6829
 
6794
6830
  // src/index.ts
6795
6831
  var program = new Command11();
6796
- program.name("flowmind").description("Flowmind CLI (also installed as fm)").version("0.3.6").enablePositionalOptions();
6832
+ program.name("flowmind").description("Flowmind CLI (also installed as fm)").version("0.3.7").enablePositionalOptions();
6797
6833
  program.addCommand(agentCommand);
6798
6834
  program.addCommand(authCommand);
6799
6835
  program.addCommand(logCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowmind/cli",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Flowmind CLI: log what you and your coding agents ship, with the outcome and the link that proves it. Works with Codex and Claude Code.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -37,8 +37,15 @@
37
37
  "flowmind": "dist/index.js"
38
38
  },
39
39
  "type": "module",
40
+ "imports": {
41
+ "#contracts": {
42
+ "types": "./src/contracts.ts",
43
+ "default": "./dist/contracts.js"
44
+ }
45
+ },
40
46
  "scripts": {
41
47
  "build": "tsc --noEmit && node build.mjs",
48
+ "typecheck:tests": "tsc -p tsconfig.tests.json",
42
49
  "prepack": "npm run build",
43
50
  "test": "pnpm run build && node --test test/*.test.mjs && tsx --test src/*.test.ts && node test/pack-install.mjs",
44
51
  "dev": "npm run build && node dist/index.js"