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/cli.js +260 -62
- package/dist/credentials.js +108 -0
- package/dist/doctor.js +17 -3
- package/dist/ghLogin.js +13 -6
- package/dist/login.js +148 -26
- package/dist/mcpClient.js +36 -1
- package/dist/messages.js +64 -2
- package/dist/setup.js +107 -7
- package/dist/sfCli.js +45 -17
- package/dist/usage.js +57 -32
- package/dist/version.js +1 -1
- package/dist/wiring.js +29 -10
- package/package.json +1 -1
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
|
|
4
|
-
* parser, the remedies, and the allow-list projection of the door's
|
|
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
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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 `
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
7
|
-
setup
|
|
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
|
-
|
|
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
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
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
|
63
|
-
// History
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
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 ? {
|
|
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
|
+
"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",
|