flowsprite 0.1.3 → 0.1.4

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/dist/sfCli.js CHANGED
@@ -1,7 +1,22 @@
1
1
  /**
2
2
  * The pure half of `flowsprite connect salesforce --from-sf-cli <alias>`
3
- * (FLO-515): the alias rule, the `sf org display --verbose --json` envelope
4
- * parser, the remedies, and the allow-list projection of the door's reply.
3
+ * (FLO-515): the alias rule, the `sf org auth show-sfdx-auth-url --json`
4
+ * envelope parser, the remedies, and the allow-list projection of the door's
5
+ * reply.
6
+ *
7
+ * The command changed on 2026-09-20 (card 263cefa5). FLO-515 read the login
8
+ * from `sf org display --verbose --json`; sf 2.147.0 (published 2026-07-31,
9
+ * `npm view @salesforce/cli time`) bundles @salesforce/plugin-org 6.0.x, which
10
+ * writes a `[REDACTED] Use 'sf org auth show-sfdx-auth-url' to view`
11
+ * placeholder into that command's `sfdxAuthUrl` unless a documented-temporary
12
+ * env var is set, so on a current sf the old parser answered `no_url` for a
13
+ * perfectly good web login (measured on sf 2.150.6, 2026-09-20). The stable
14
+ * command this module now parses ships in the same plugin version and needs no
15
+ * env var. Its `--json` stdout is `{status, result:{sfdxAuthUrl}, warnings}` —
16
+ * no accessToken, no password, no clientId; the command's own warning text
17
+ * lands both in that `warnings` array and on stderr (sf-plugins-core
18
+ * `toSuccessJson`/`warn`, measured against 12.2.6), and this CLI prints
19
+ * neither.
5
20
  *
6
21
  * NOTHING HERE PRINTS. The `sfdxAuthUrl` this module extracts is a reusable
7
22
  * Salesforce credential that the CLI's own redactor cannot see (`redact.ts`
@@ -31,24 +46,40 @@ export const FROM_SF_CLI_EXCLUSIVE = "--from-sf-cli takes the org from the sf CL
31
46
  export function scrubForceUrls(text) {
32
47
  return text.replace(/force:\/\/\S*/g, "[sf auth url removed]");
33
48
  }
34
- const SF_CLIENT_ID = "PlatformCLI";
35
49
  const SF_URL_PREFIX = "force://PlatformCLI::";
36
50
  const ERROR_NAME_RE = /^[A-Za-z0-9_]{1,64}$/;
37
51
  /**
38
- * Read the `sf org display --verbose --json` envelope: `{status, result:{…}}`,
39
- * parsed from the first `{` because a CLI notice can precede the JSON on
40
- * stdout (measured: the update notice goes to stderr on sf 2.127 / 2.150, but
41
- * the setup skill body already parses from the first `{` for the other
42
- * configurations).
52
+ * The sentence for an `sf` that predates the command (card 263cefa5 DM4). It
53
+ * names no alias: a missing subcommand is a property of the INSTALL, not of
54
+ * the org, so the login line and the logout-then-login remedy are both wrong
55
+ * here — logging in again does not add a subcommand. It also stays true if sf
56
+ * ever removes the command, which is why it ends at the browser road.
57
+ */
58
+ export const SF_TOO_OLD_REMEDY = "Your Salesforce CLI does not have `sf org auth show-sfdx-auth-url` (added in sf 2.147.0, July 2026), " +
59
+ "which this command reads the login from. Update it — sf update, or npm install -g @salesforce/cli — " +
60
+ "then run this again; if it persists on a current sf, connect this org from the browser: flowsprite connect salesforce.";
61
+ /**
62
+ * Read the `sf org auth show-sfdx-auth-url --target-org <alias> --json`
63
+ * envelope: `{status, result:{sfdxAuthUrl}, warnings}`, parsed from the first
64
+ * `{` because a CLI notice can precede the JSON (the setup skill body already
65
+ * parses from the first `{` for the other configurations). Only
66
+ * `result.sfdxAuthUrl` is read: `warnings` carries the command's own "this
67
+ * exposes an SFDX Auth URL" text, which also goes to stderr, and neither is
68
+ * returned or printed.
43
69
  *
44
70
  * A non-zero envelope `status` reports the envelope's `name` — an error-class
45
- * identifier such as `NoAuthInfoFound` — and NEVER its free-text `message`:
46
- * that text is third-party and can quote the URL (spec-gate codex C2), so it
47
- * is not even returned. An `sf org login jwt` org has no `sfdxAuthUrl`, and an
48
- * auth file written before the CLI stored its client id yields
49
- * `force://:<token>@host` (empty client id); both are `no_url`.
71
+ * identifier such as `NamedOrgNotFoundError` (a logged-out or mistyped alias)
72
+ * or `NoRefreshTokenError` (a JWT / access-token login) — and NEVER its
73
+ * free-text `message`: that text is third-party and can quote the URL
74
+ * (spec-gate codex C2), so it is not even returned.
75
+ *
76
+ * There is no `clientId` in this envelope and no `client` reason: the url's own
77
+ * `force://PlatformCLI::` prefix IS the client check here, and the server door
78
+ * re-checks it. An auth file written before the CLI stored its client id
79
+ * yields `force://:<token>@host` (empty client id), which fails that prefix and
80
+ * is `no_url` like an absent or non-string url.
50
81
  */
51
- export function parseSfOrgDisplay(stdout) {
82
+ export function parseSfAuthUrl(stdout) {
52
83
  const start = stdout.indexOf("{");
53
84
  if (start < 0)
54
85
  return { ok: false, reason: "not_json" };
@@ -72,9 +103,6 @@ export function parseSfOrgDisplay(stdout) {
72
103
  const url = result.sfdxAuthUrl;
73
104
  if (typeof url !== "string" || !url.startsWith(SF_URL_PREFIX))
74
105
  return { ok: false, reason: "no_url" };
75
- const clientId = typeof result.clientId === "string" ? result.clientId : "";
76
- if (clientId !== SF_CLIENT_ID)
77
- return { ok: false, reason: "client", clientId };
78
106
  return { ok: true, sfdxAuthUrl: url };
79
107
  }
80
108
  /** The `sf org login web` line for this alias — sandbox users need the host. */
package/dist/usage.js CHANGED
@@ -1,59 +1,84 @@
1
- /** The usage text, as data, so `--help` and every exit-2 refusal print the same thing. */
1
+ /**
2
+ * The usage text, as data, so `--help` and every exit-2 refusal print the same thing.
3
+ *
4
+ * Card eecc9d6f N5/N6/N13: what Flowsprite is first, then examples, then each
5
+ * command WITH ITS OWN OPTIONS under it. Every flag `cli.ts` parses appears here
6
+ * (pinned in usage.test.ts against the parser's own option table). `--sandbox`
7
+ * names the generic hosts the server picks (`lib/salesforceOAuthExchange.ts`
8
+ * GENERIC_OAUTH_HOST: sandbox -> test.salesforce.com, prod -> login.salesforce.com).
9
+ */
2
10
  export const USAGE = `flowsprite — set up and drive a Flowsprite workspace from a terminal.
3
11
 
12
+ Flowsprite mirrors a Salesforce org's metadata into a PRIVATE GitHub repository.
13
+ \`flowsprite setup\` takes three steps: log this terminal in, connect Salesforce,
14
+ then connect GitHub and create the private repository.
15
+
4
16
  Usage: flowsprite [command] [options]
5
17
 
6
- Commands:
7
- setup Run the whole setup ladder (the default).
18
+ Examples:
19
+ flowsprite setup Log in and set the workspace up, step by step.
20
+ flowsprite setup --sandbox The same, connecting a Salesforce sandbox org.
21
+ flowsprite mcp install --client claude-code
22
+ Point Claude Code at this workspace.
23
+
24
+ Commands, each with its own options:
25
+ setup Run the whole setup (the default command). Steps
26
+ that are already done are skipped.
27
+ --sandbox Connect a Salesforce sandbox org (sign-in at
28
+ test.salesforce.com). Without it the sign-in is at
29
+ login.salesforce.com, the production login host.
30
+ --my-domain <host> Sign in to Salesforce at this My Domain host instead.
31
+ --no-open Do not try to open a browser for the login step.
8
32
  login [--workspace <name>] [--no-gh]
9
33
  Log this terminal in. Uses the GitHub CLI's login
10
34
  (gh) when it has one, else a device code in the
11
35
  browser.
36
+ --workspace <name> The name for the workspace the GitHub CLI login
37
+ creates. Omitted, login asks at a terminal; through
38
+ a pipe it uses the browser instead.
39
+ --no-gh Skip the GitHub CLI and use the browser.
40
+ --no-open Do not try to open a browser.
41
+ --token <token> Use this token instead of logging in.
42
+ --save With --token: also store it for this endpoint.
12
43
  logout Forget the stored credential for this endpoint.
13
44
  status Show the setup checklist, connections and installations.
14
45
  connect salesforce Get a one-time link to connect a Salesforce org.
15
- connect github Get a one-time link to install the GitHub app.
46
+ Takes --sandbox and --my-domain, as setup does.
16
47
  connect salesforce --from-sf-cli <alias>
17
48
  Connect an org the Salesforce CLI is already logged
18
- in to (no browser).
49
+ in to (no browser). Needs sf on PATH and a web
50
+ login (not JWT).
51
+ connect github Get a one-time link to install the GitHub app.
19
52
  bind Give a Salesforce connection a GitHub repo. With no
20
53
  --repo/--create it offers to create a new private
21
54
  one with a suggested name, or pick an existing one.
55
+ --connection <id> The Salesforce connection id (also for sync).
56
+ --installation <number> The GitHub installation id.
57
+ --repo <owner/name> Use a repository that already exists.
58
+ --create <name> Create a new repository with this name, without
59
+ asking. Always PRIVATE — Flowsprite only mirrors
60
+ into private repos.
22
61
  sync Start a metadata sync and show the recent sync log.
62
+ Takes --connection, as bind does.
23
63
  doctor Check node, the endpoint, the credential, and sf/gh.
64
+ Exits 1 when a check FAILs.
24
65
  mcp install Point an AI client at this workspace's MCP endpoint.
66
+ --client <name> claude-code, codex, claude-desktop or cursor.
67
+ Omitted, mcp install offers the ones it detects and
68
+ mcp print shows every one.
69
+ --print Show what it would do, change nothing.
70
+ --force Replace an existing flowsprite entry.
71
+ --token-env <NAME> codex reads the bearer from this variable;
72
+ claude-code uses its CURRENT value.
25
73
  mcp print Show the config snippet for a client, token hidden.
74
+ Takes --client, as mcp install does.
75
+ --reveal Print the real token instead of <YOUR_TOKEN>. Only
76
+ at a terminal, never through a pipe.
26
77
 
27
- Options:
78
+ Options for every command:
28
79
  --endpoint <origin> Flowsprite origin (default https://app.flowsprite.com).
29
80
  --json Machine-readable output where a command has one.
30
81
  --token <token> Use this token instead of the stored credential.
31
- --save With --token: also store it for this endpoint.
32
- --no-open Do not try to open a browser during login.
33
- --workspace <name> login: the name for the workspace the GitHub CLI
34
- login creates. Omitted, login asks at a terminal;
35
- through a pipe it uses the browser instead.
36
- --no-gh login: skip the GitHub CLI and use the browser.
37
- --sandbox setup / connect salesforce: use the sandbox login host.
38
- --my-domain <host> setup / connect salesforce: use this My Domain login host.
39
- --from-sf-cli <alias> connect salesforce: hand over the sf CLI's login for
40
- that alias instead of a browser link. Needs sf on
41
- PATH and a web login (not JWT).
42
- --connection <id> bind / sync: the Salesforce connection id.
43
- --installation <number> bind: the GitHub installation id.
44
- --repo <owner/name> bind: use a repository that already exists.
45
- --create <name> bind: create a new repository with this name, without
46
- asking. Always PRIVATE — Flowsprite only mirrors
47
- into private repos.
48
- --client <name> mcp: claude-code, codex, claude-desktop or cursor.
49
- Omitted, mcp install offers the ones it detects and
50
- mcp print shows every one.
51
- --print mcp install: show what it would do, change nothing.
52
- --force mcp install: replace an existing flowsprite entry.
53
- --reveal mcp print: print the real token instead of
54
- <YOUR_TOKEN>. Only at a terminal, never through a pipe.
55
- --token-env <NAME> mcp install: codex reads the bearer from this
56
- variable; claude-code uses its CURRENT value.
57
82
  -h, --help Show this help and exit 0.
58
83
  -v, --version Show the version and exit 0.
59
84
 
package/dist/version.js CHANGED
@@ -7,4 +7,4 @@
7
7
  * manifest are pinned EQUAL by cli/src/packaging.test.ts, which reads both, so
8
8
  * they cannot disagree without a red test.
9
9
  */
10
- export const VERSION = "0.1.3";
10
+ export const VERSION = "0.1.4";
package/dist/wiring.js CHANGED
@@ -14,9 +14,10 @@ import { printable, redact } from "./redact.js";
14
14
  * through `log` (a separate function a caller could point anywhere). Its only
15
15
  * reader is `mcpPrint` in `cli.ts`.
16
16
  *
17
- * The browser opener and the prompt need BOTH stdout and stdin to be terminals
18
- * (`interactive`); the secret needs only stdout: stdin may be a here-doc while
19
- * the person is still watching the terminal the secret lands in.
17
+ * The prompt needs BOTH stdout and stdin to be terminals (`interactive`); the
18
+ * secret needs only stdout: stdin may be a here-doc while the person is still
19
+ * watching the terminal the secret lands in. The browser opener needs NEITHER —
20
+ * it is decided by `shouldOpenBrowser` (card eecc9d6f C1).
20
21
  */
21
22
  export function wireDeps(io) {
22
23
  const stdoutIsTTY = io.stdout.isTTY === true;
@@ -59,19 +60,37 @@ export function wireDeps(io) {
59
60
  },
60
61
  }
61
62
  : {}),
62
- // The browser opener and the prompt are BOTH gated on there being a terminal.
63
- // History (measured 2026-09-15 at bin.ts, before this wiring existed,
64
- // FLO-500): a non-interactive `login` (stdout piped, as in a CI job or a
65
- // probe script) still popped a browser tab on this machine, because the
66
- // opener was wired unconditionally. A process nobody is watching must not
67
- // take over the screen of whoever happens to be at the machine.
63
+ // The browser opener is NOT gated on a terminal any more (card eecc9d6f C1).
64
+ // History: FLO-500 gated it on stdin+stdout TTYs after a piped probe popped
65
+ // a tab; that also starved every agent shell (Claude Code runs commands with
66
+ // no TTY on a person's own desktop), which is exactly where `login` is now
67
+ // run most. The rule is `shouldOpenBrowser`: `--no-open` (cli.ts), a CI
68
+ // environment, or a Linux box with no display keeps the browser shut.
69
+ ...(shouldOpenBrowser(io.env, io.platform) ? { openUrl: io.openUrl } : {}),
70
+ // The prompt stays gated on there being a terminal on BOTH ends.
68
71
  // The question is redacted AND scrubbed HERE because readline writes it to
69
72
  // stdout raw, around the writer — the prompt text is CLI-built (a path, a
70
73
  // display name), but the guarantee should not depend on every future caller
71
74
  // remembering. `printable`, not the writer's per-line scrub: a question is
72
75
  // ONE line by construction, so a newline in a spliced display name is a
73
76
  // forged line, not structure.
74
- ...(interactive ? { openUrl: io.openUrl, ask: (question) => io.ask(printable(redact(question))) } : {}),
77
+ ...(interactive ? { ask: (question) => io.ask(printable(redact(question))) } : {}),
75
78
  };
76
79
  return { deps, statusLine };
77
80
  }
81
+ /**
82
+ * C1 — may `login` open a browser on this machine? Yes, unless a CI job (`CI`
83
+ * set to anything non-empty — the convention GitHub Actions, GitLab and most
84
+ * runners follow) or Linux with neither `DISPLAY` nor `WAYLAND_DISPLAY` (an SSH
85
+ * session or a container: `xdg-open` there has no screen, or picks a text
86
+ * browser that takes over the terminal). `--no-open` is the person's override,
87
+ * applied in `cli.ts`. PURE, so the whole cross product is pinned in wiring.test.ts.
88
+ */
89
+ export function shouldOpenBrowser(env, platform) {
90
+ const set = (name) => typeof env[name] === "string" && env[name].length > 0;
91
+ if (set("CI"))
92
+ return false;
93
+ if (platform === "linux" && !set("DISPLAY") && !set("WAYLAND_DISPLAY"))
94
+ return false;
95
+ return true;
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowsprite",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "The Flowsprite CLI: log a terminal in to Flowsprite and drive workspace setup over the MCP endpoint. Zero runtime dependencies, node builtins only.",
6
6
  "license": "UNLICENSED",