saasfunnels 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,6 +114,8 @@ CLI 0.5.0 requires Setup contract 4 and stops before creating a run against an o
114
114
 
115
115
  Discovery follows Git ignore rules (including custom build folders) while keeping tracked application sources and new, non-ignored source files. Standalone folders without Git use the standard source filters.
116
116
 
117
+ The coding-agent installation handoff can also create `.saasfunnels/shared-installation.json` with the shared host's source binding, supported entry families, and `entryBindings` naming each installed application event, API invocation, or lifecycle entry by its exact `kind` and `key`. Setup submits that binding once using the existing workspace, integration, environment, and installation revision. It records progress after each accepted stage. If the file is absent, existing workspace installation evidence is preserved; creating another Funnel never requires this file or another catalog upload. The application must still verify the installed host and hooks at runtime before Setup marks them connected.
118
+
117
119
  The command does not implement arbitrary payment code, approve feature access, or verify transactions. Complete **Review and verify** in Setup after deploying the runtime and payment integration. Requires the server's guided setup API release.
118
120
 
119
121
  ### Discovery accuracy and review
package/dist/library.js CHANGED
@@ -235858,7 +235858,7 @@ function validateFeatureCatalogManifest(value) {
235858
235858
 
235859
235859
  // src/identity.ts
235860
235860
  var SAASFUNNELS_CLI_NAME = "saasfunnels";
235861
- var SAASFUNNELS_CLI_VERSION = "0.5.0";
235861
+ var SAASFUNNELS_CLI_VERSION = "0.5.1";
235862
235862
  var SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
235863
235863
  var SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
235864
235864
  var SAASFUNNELS_ENV = Object.freeze({
@@ -218902,6 +218902,10 @@ var require_dist = __commonJS({
218902
218902
  // src/saasfunnels.ts
218903
218903
  import { createInterface as createInterface2 } from "node:readline/promises";
218904
218904
 
218905
+ // src/cli.ts
218906
+ import { execFile as execFile5 } from "node:child_process";
218907
+ import { promisify as promisify5 } from "node:util";
218908
+
218905
218909
  // src/setup-access.ts
218906
218910
  import { execFile } from "node:child_process";
218907
218911
  import { readFile } from "node:fs/promises";
@@ -234044,7 +234048,7 @@ function extractSetupPatch(source, catalog, baseline) {
234044
234048
 
234045
234049
  // src/identity.ts
234046
234050
  var SAASFUNNELS_CLI_NAME = "saasfunnels";
234047
- var SAASFUNNELS_CLI_VERSION = "0.5.0";
234051
+ var SAASFUNNELS_CLI_VERSION = "0.5.1";
234048
234052
  var SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
234049
234053
  var SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
234050
234054
  var SAASFUNNELS_ENV = Object.freeze({
@@ -238214,6 +238218,7 @@ var contextSchema = external_exports.object({
238214
238218
  installationId: external_exports.string().nullable(),
238215
238219
  integrationId: external_exports.string().nullable(),
238216
238220
  environment: external_exports.enum(["test", "production"]),
238221
+ sharedInstallation: external_exports.object({ registryRevision: external_exports.number().int().nonnegative().nullable(), fingerprint: external_exports.string().nullable(), verified: external_exports.boolean() }).optional(),
238217
238222
  catalogReady: external_exports.boolean(),
238218
238223
  planNames: external_exports.array(external_exports.string()),
238219
238224
  catalogPrices: external_exports.array(
@@ -238386,7 +238391,7 @@ async function runGuidedSetup(options) {
238386
238391
  })).run;
238387
238392
  if (!run) throw new Error("Setup did not return a run.");
238388
238393
  await mkdir3(join6(options.cwd, ".saasfunnels"), { recursive: true });
238389
- await writeFile3(
238394
+ const saveRun = () => writeFile3(
238390
238395
  statePath,
238391
238396
  JSON.stringify(
238392
238397
  {
@@ -238394,13 +238399,32 @@ async function runGuidedSetup(options) {
238394
238399
  generation: context.generation,
238395
238400
  repositoryKey,
238396
238401
  revision,
238397
- runId: run.id
238402
+ runId: run.id,
238403
+ receipts: run.receipts
238398
238404
  },
238399
238405
  null,
238400
238406
  2
238401
238407
  ) + "\n",
238402
238408
  { mode: 384 }
238403
238409
  );
238410
+ await saveRun();
238411
+ try {
238412
+ const shared = JSON.parse(await readFile6(join6(options.cwd, ".saasfunnels", "shared-installation.json"), "utf8"));
238413
+ if (context.sharedInstallation?.registryRevision == null)
238414
+ throw new Error("The app connection has not issued a verified identity yet. Complete its shared installation first.");
238415
+ await request("/api/developer-tools/setup/installation", {
238416
+ ...shared,
238417
+ schemaVersion: 1,
238418
+ installationId: context.installationId,
238419
+ installationRevision: context.sharedInstallation.registryRevision,
238420
+ integrationId: context.integrationId,
238421
+ environment: context.environment,
238422
+ repositoryKey,
238423
+ repositoryRevision: head
238424
+ });
238425
+ } catch (error51) {
238426
+ if (error51.code !== "ENOENT") throw error51;
238427
+ }
238404
238428
  const update = (state, failureCode = null) => {
238405
238429
  progressState = state;
238406
238430
  progressFailure = failureCode;
@@ -238446,6 +238470,7 @@ async function runGuidedSetup(options) {
238446
238470
  if (typeof result3.sequence === "number")
238447
238471
  run.sequence = result3.sequence;
238448
238472
  run.receipts[currentStage] = result3.receipt ?? { complete: true };
238473
+ await saveRun();
238449
238474
  if (currentStage === "plans")
238450
238475
  run.planNames = result3.planNames ?? evidence.plans.flatMap((plan) => [plan.key, plan.name]);
238451
238476
  });
@@ -240767,7 +240792,14 @@ async function commandFeatures(args, flags, options) {
240767
240792
  }
240768
240793
  if (args[0] === "handoff") {
240769
240794
  const path2 = flagString(flags, "file") ?? ".saasfunnels/catalog.yaml";
240770
- const repositoryRevision = flagString(flags, "repository-revision");
240795
+ const gitValue = async (...args2) => {
240796
+ try {
240797
+ return (await promisify5(execFile5)("git", args2, { cwd: cwd(options), timeout: 5e3, maxBuffer: 8192 })).stdout.trim();
240798
+ } catch {
240799
+ return "";
240800
+ }
240801
+ };
240802
+ const repositoryRevision = flagString(flags, "repository-revision") ?? await gitValue("rev-parse", "HEAD");
240771
240803
  if (!repositoryRevision) {
240772
240804
  return result(
240773
240805
  2,
@@ -240775,7 +240807,16 @@ async function commandFeatures(args, flags, options) {
240775
240807
  "Missing --repository-revision for the structured Feature handoff.\n"
240776
240808
  );
240777
240809
  }
240778
- const repositoryKey = flagString(flags, "repository-key");
240810
+ let repositoryKey = flagString(flags, "repository-key");
240811
+ if (!repositoryKey) {
240812
+ const remote = await gitValue("remote", "get-url", "origin");
240813
+ if (remote) {
240814
+ try {
240815
+ repositoryKey = safeRepositoryKey(remote);
240816
+ } catch {
240817
+ }
240818
+ }
240819
+ }
240779
240820
  if (!repositoryKey) {
240780
240821
  return result(
240781
240822
  2,
@@ -240796,10 +240837,32 @@ async function commandFeatures(args, flags, options) {
240796
240837
  );
240797
240838
  }
240798
240839
  const discoveryRoots = (flagString(flags, "discovery-roots") ?? "").split(",").map((root) => root.trim()).filter(Boolean);
240799
- const source = await filesystem(options).readFile(
240800
- resolve5(cwd(options), path2),
240801
- "utf8"
240802
- );
240840
+ let source;
240841
+ try {
240842
+ source = await filesystem(options).readFile(resolve5(cwd(options), path2), "utf8");
240843
+ } catch (error51) {
240844
+ if (error51.code !== "ENOENT") throw error51;
240845
+ const key4 = envValue2(options, SAASFUNNELS_ENV.apiKey);
240846
+ if (key4 && !flagString(flags, "file")) {
240847
+ const query = new URLSearchParams({ repositoryKey, repositoryRevision, environment: flagString(flags, "environment") ?? "production", ...flagString(flags, "discovery-roots") ? { discoveryRoots: JSON.stringify([...discoveryRoots].sort()) } : {} });
240848
+ try {
240849
+ const response = await (options.fetch ?? fetch)(`${apiBaseUrl2(options, flags)}/api/developer-tools/features/instrumentation?${query}`, { headers: { authorization: `Bearer ${key4}` }, signal: AbortSignal.timeout(15e3) });
240850
+ if (response.ok) {
240851
+ const evidence = await response.json();
240852
+ if (evidence.reused === true) return jsonMode(flags) ? jsonResult(0, { reused: true, currentRevision: evidence.currentRevision === true, manifestFingerprint: evidence.manifestFingerprint, validatedAt: evidence.validatedAt }) : result(
240853
+ 0,
240854
+ `Reused the existing workspace Feature evidence. No catalog was uploaded.${evidence.currentRevision ? "" : " This is the previous scan, not verification of this checkout; Setup shows any remaining review."} Open Setup to continue.
240855
+ `
240856
+ );
240857
+ } else if (response.status !== 404) {
240858
+ return result(2, "", "Workspace Feature evidence could not be checked. Open Setup to check the connection and resume; your existing catalog has not changed.\n");
240859
+ }
240860
+ } catch {
240861
+ return result(2, "", "Workspace Feature evidence could not be checked. Open Setup to check the connection and resume; your existing catalog has not changed.\n");
240862
+ }
240863
+ }
240864
+ return result(2, "", "No local discovery file was found. Open Setup to reuse your workspace catalog and finish any missing connection steps. Do not recreate the catalog.\n");
240865
+ }
240803
240866
  const validation = parseFeatureManifestSource(source);
240804
240867
  if (!validation.ok || !validation.manifest) {
240805
240868
  return jsonMode(flags) ? jsonResult(1, validation) : result(
@@ -1,5 +1,5 @@
1
1
  export declare const SAASFUNNELS_CLI_NAME = "saasfunnels";
2
- export declare const SAASFUNNELS_CLI_VERSION = "0.5.0";
2
+ export declare const SAASFUNNELS_CLI_VERSION = "0.5.1";
3
3
  export declare const SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
4
4
  export declare const SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
5
5
  export declare const SAASFUNNELS_ENV: Readonly<{
@@ -77,7 +77,7 @@ export type SaaSFunnelsMcpOptions = {
77
77
  output?: Pick<NodeJS.WritableStream, "write">;
78
78
  };
79
79
  declare const mcpProtocolVersion = "2025-06-18";
80
- declare const mcpServerVersion = "0.5.0";
80
+ declare const mcpServerVersion = "0.5.1";
81
81
  export declare function saasFunnelsMcpResources(): McpResourceDefinition[];
82
82
  export declare function readSaaSFunnelsMcpResource(uri: string, options?: SaaSFunnelsMcpOptions): string;
83
83
  export declare function saasFunnelsMcpToolDefinitions({ allowFunnelTests, allowFunnelWrites, allowTestWrites, includeHostedAccountTools, }?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasfunnels",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "private": false,
5
5
  "description": "SaaSFunnels CLI and local stdio MCP server",
6
6
  "author": "SaaSFunnels",