flostep 0.1.3 → 0.1.5
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/AGENTS.md +19 -19
- package/README.md +6 -6
- package/package.json +1 -1
- package/src/api.js +10 -2
- package/src/cli.js +1 -1
- package/src/commands/create.js +1 -1
- package/src/commands/delete.js +2 -2
- package/src/commands/list.js +7 -4
- package/src/commands/login.js +51 -7
- package/src/commands/move.js +2 -2
- package/src/commands/node.js +4 -4
- package/src/commands/open.js +2 -2
- package/src/commands/share.js +4 -4
- package/src/commands/show.js +4 -4
- package/src/commands/step.js +4 -4
- package/src/commands/update.js +6 -6
- package/src/commands/whoami.js +4 -1
- package/src/target.js +9 -6
package/AGENTS.md
CHANGED
|
@@ -35,7 +35,7 @@ Customer -> API: POST /checkout
|
|
|
35
35
|
API -> Payments: charge card
|
|
36
36
|
API -> Customer: order confirmed
|
|
37
37
|
EOF
|
|
38
|
-
# ✓ Created #
|
|
38
|
+
# ✓ Created #Xk3p9QvA2wE Checkout
|
|
39
39
|
# https://flostep.dev/s/rEFdW8GSDwQ <- give the user this
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -44,36 +44,36 @@ EOF
|
|
|
44
44
|
For a diagram that already exists, share it on its own:
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
npx flostep share
|
|
48
|
-
npx flostep share
|
|
47
|
+
npx flostep share Xk3p9QvA2wE # prints the public link
|
|
48
|
+
npx flostep share Xk3p9QvA2wE --embed # iframe URL, for a docs page
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
GitHub strips iframes, so for a README, an ADR or a PR description print a markdown image that links to the diagram instead (paid plans; on a free one the command fails and gives you the plain link):
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
npx flostep share
|
|
54
|
+
npx flostep share Xk3p9QvA2wE --markdown # an image that follows the diagram as it changes
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
If the user keeps the steps in a file in their repo, pipe the file in and let them keep the file — the CLI tracks nothing on disk:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
npx flostep update
|
|
60
|
+
npx flostep update Xk3p9QvA2wE < docs/checkout.flostep
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
To rewrite an existing diagram, read it, transform it, pipe it back:
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
npx flostep show
|
|
66
|
+
npx flostep show Xk3p9QvA2wE | sed 's/Redis/Session Cache/' | npx flostep update Xk3p9QvA2wE
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
Or change one step at a time when you have no local file:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
npx flostep show
|
|
73
|
-
npx flostep step add
|
|
74
|
-
npx flostep step add
|
|
75
|
-
npx flostep step rm
|
|
76
|
-
npx flostep node rename
|
|
72
|
+
npx flostep show Xk3p9QvA2wE # read it first
|
|
73
|
+
npx flostep step add Xk3p9QvA2wE "API -> Cache: read session"
|
|
74
|
+
npx flostep step add Xk3p9QvA2wE "Client -> API: retry" --at 3
|
|
75
|
+
npx flostep step rm Xk3p9QvA2wE 5
|
|
76
|
+
npx flostep node rename Xk3p9QvA2wE "Redis" "Session Cache"
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
### Folders
|
|
@@ -81,9 +81,9 @@ npx flostep node rename 42 "Redis" "Session Cache"
|
|
|
81
81
|
Folders are shared by a team and addressed by name. File a diagram only when the user asks you to organise it:
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
npx flostep folder list --json
|
|
85
|
-
npx flostep folder create "Payments"
|
|
86
|
-
npx flostep move
|
|
84
|
+
npx flostep folder list --json # names and diagram counts
|
|
85
|
+
npx flostep folder create "Payments" # returns the folder if it already exists
|
|
86
|
+
npx flostep move Xk3p9QvA2wE "Payments" # file it; --none takes it out
|
|
87
87
|
npx flostep list --folder "Payments" --json
|
|
88
88
|
```
|
|
89
89
|
|
|
@@ -92,11 +92,11 @@ npx flostep list --folder "Payments" --json
|
|
|
92
92
|
### Reading
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
|
-
npx flostep list --json
|
|
96
|
-
npx flostep show
|
|
97
|
-
npx flostep step list
|
|
98
|
-
npx flostep node list
|
|
99
|
-
npx flostep whoami --json
|
|
95
|
+
npx flostep list --json # ids, titles, urls
|
|
96
|
+
npx flostep show Xk3p9QvA2wE # the steps, plain text
|
|
97
|
+
npx flostep step list Xk3p9QvA2wE --json # numbered steps
|
|
98
|
+
npx flostep node list Xk3p9QvA2wE --json # components, first-appearance order
|
|
99
|
+
npx flostep whoami --json # which workspace you're writing to
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
### Rules
|
package/README.md
CHANGED
|
@@ -20,14 +20,14 @@ Customer -> API: POST /checkout
|
|
|
20
20
|
API -> Payments: charge card
|
|
21
21
|
API -> Customer: order confirmed
|
|
22
22
|
EOF
|
|
23
|
-
# ✓ Created #
|
|
23
|
+
# ✓ Created #Xk3p9QvA2wE Checkout
|
|
24
24
|
# https://flostep.dev/s/rEFdW8GSDwQ
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
That wrote nothing to disk, and the id it printed is what every other command takes:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
npx flostep show
|
|
30
|
+
npx flostep show Xk3p9QvA2wE | sed 's/Payments/Stripe/' | npx flostep update Xk3p9QvA2wE
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## The format
|
|
@@ -51,9 +51,9 @@ Run `flostep syntax` for the authoritative version, fetched from the server.
|
|
|
51
51
|
|
|
52
52
|
| Command | |
|
|
53
53
|
| --- | --- |
|
|
54
|
-
| `flostep login` | sign in from a browser (`--token` to paste a key instead) |
|
|
54
|
+
| `flostep login` | sign in from a browser (`--read-only` for a key that can't change anything, `--token` to paste a key instead) |
|
|
55
55
|
| `flostep logout` | forget the stored credentials |
|
|
56
|
-
| `flostep whoami` | which account and workspace a key writes to |
|
|
56
|
+
| `flostep whoami` | which account and workspace a key writes to, and whether it can write |
|
|
57
57
|
| `flostep init` | add the agent instructions to AGENTS.md / CLAUDE.md / `.cursor/rules` |
|
|
58
58
|
| `flostep list` | the diagrams in the workspace (`--folder` to narrow) |
|
|
59
59
|
| `flostep show <id>` | print a diagram as steps (pipeable) |
|
|
@@ -79,7 +79,7 @@ Ids come from `flostep list` or from `create`. Nothing is resolved from a file p
|
|
|
79
79
|
There is no browser in CI, so the device flow doesn't apply. Create a key at [flostep.dev/api_keys](https://flostep.dev/api_keys) — leave its expiry as **Never**, since a key that lapses takes the pipeline down on a date nobody chose — and set it as `FLOSTEP_TOKEN`.
|
|
80
80
|
|
|
81
81
|
```yaml
|
|
82
|
-
- run: npx flostep update
|
|
82
|
+
- run: npx flostep update Xk3p9QvA2wE < docs/checkout.flostep
|
|
83
83
|
env:
|
|
84
84
|
FLOSTEP_TOKEN: ${{ secrets.FLOSTEP_TOKEN }}
|
|
85
85
|
```
|
|
@@ -90,7 +90,7 @@ Keeping the steps in a file and piping them in is up to you — the CLI tracks n
|
|
|
90
90
|
|
|
91
91
|
| | |
|
|
92
92
|
| --- | --- |
|
|
93
|
-
| `FLOSTEP_TOKEN` | API key. Takes precedence over a stored login. |
|
|
93
|
+
| `FLOSTEP_TOKEN` | API key. Takes precedence over a stored login. For a job that only reads, create a **Read only** key on the API keys page. |
|
|
94
94
|
| `NO_COLOR` | Disable colour. Colour is off automatically when not a TTY. |
|
|
95
95
|
|
|
96
96
|
Credentials are stored at `~/.config/flostep/config.json`, mode `0600`.
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -21,6 +21,7 @@ export class Api {
|
|
|
21
21
|
this.host = host;
|
|
22
22
|
this.token = token;
|
|
23
23
|
this.tokenSource = tokenSource;
|
|
24
|
+
this.keyAccess = null;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
async request(method, path, body) {
|
|
@@ -50,6 +51,11 @@ export class Api {
|
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
// What the server says this key may do ("read" or "write"), from every
|
|
55
|
+
// authenticated response. Null against a server from before access levels,
|
|
56
|
+
// where every key could write — callers treat null as "don't know".
|
|
57
|
+
if (response.headers.has("x-key-access")) this.keyAccess = response.headers.get("x-key-access");
|
|
58
|
+
|
|
53
59
|
// 204 has no body, and neither does anything that failed to serialise.
|
|
54
60
|
const text = await response.text();
|
|
55
61
|
let payload = null;
|
|
@@ -186,8 +192,10 @@ export class Api {
|
|
|
186
192
|
|
|
187
193
|
// --- device flow ------------------------------------------------------
|
|
188
194
|
|
|
189
|
-
|
|
190
|
-
|
|
195
|
+
// `access: "read"` asks for a read-only key. Omitted otherwise, rather than
|
|
196
|
+
// sent as "write", so the request is byte-for-byte what older CLIs sent.
|
|
197
|
+
requestDeviceCode(clientName, { access } = {}) {
|
|
198
|
+
return this.post("/api/v1/device/code", { client_name: clientName, ...(access ? { access } : {}) });
|
|
191
199
|
}
|
|
192
200
|
|
|
193
201
|
// Polling is expected to fail most of the time — `authorization_pending` is
|
package/src/cli.js
CHANGED
|
@@ -110,7 +110,7 @@ function topLevelHelp() {
|
|
|
110
110
|
const starters = [
|
|
111
111
|
["flostep login", "sign in from a browser"],
|
|
112
112
|
['echo "A -> B: hello" | flostep create --share', "steps on stdin, returns a link"],
|
|
113
|
-
["flostep show
|
|
113
|
+
["flostep show Xk3p9QvA2wE | flostep update Xk3p9QvA2wE", "read it, change it, write it back"],
|
|
114
114
|
["flostep init", "teach this repo's coding agent to use flostep"]
|
|
115
115
|
];
|
|
116
116
|
const starterWidth = Math.max(...starters.map(([command]) => command.length));
|
package/src/commands/create.js
CHANGED
|
@@ -12,7 +12,7 @@ export default {
|
|
|
12
12
|
name: "create",
|
|
13
13
|
usage: [
|
|
14
14
|
'flostep create [--title <title>] [--share] < steps.txt',
|
|
15
|
-
'flostep show
|
|
15
|
+
'flostep show Xk3p9QvA2wE | flostep create --title "Copy"'
|
|
16
16
|
],
|
|
17
17
|
details: [
|
|
18
18
|
"Reads steps from stdin and creates a diagram. Writes no files.",
|
package/src/commands/delete.js
CHANGED
|
@@ -13,8 +13,8 @@ export default {
|
|
|
13
13
|
options: { yes: { type: "boolean", short: "y", default: false } },
|
|
14
14
|
optionHelp: [["-y, --yes", "skip the confirmation prompt"]],
|
|
15
15
|
examples: [
|
|
16
|
-
["flostep delete
|
|
17
|
-
["flostep delete
|
|
16
|
+
["flostep delete Xk3p9QvA2wE"],
|
|
17
|
+
["flostep delete Xk3p9QvA2wE --yes", "for scripts and agents"]
|
|
18
18
|
],
|
|
19
19
|
|
|
20
20
|
async run({ positionals, values, ctx }) {
|
package/src/commands/list.js
CHANGED
|
@@ -38,14 +38,17 @@ export default {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
if (diagrams.length === 0) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// The create hint is the obvious next step, except for a read-only key,
|
|
42
|
+
// which the server would refuse — so that key is told why instead.
|
|
43
|
+
const emptyLibrary = api.keyAccess === "read"
|
|
44
|
+
? "No diagrams yet. This key is read-only, so it can't create one — use a read & write key."
|
|
45
|
+
: "No diagrams yet. Create one with `echo 'A -> B: hello' | flostep create --title X --share`.";
|
|
46
|
+
out(dim(folder === undefined ? emptyLibrary : "No diagrams in that folder."));
|
|
44
47
|
return 0;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
table(diagrams, [
|
|
48
|
-
["ID", (d) => d.id
|
|
51
|
+
["ID", (d) => d.id],
|
|
49
52
|
["TITLE", (d) => d.title],
|
|
50
53
|
["FOLDER", (d) => d.folder ?? ""],
|
|
51
54
|
["UPDATED", (d) => relativeTime(d.updated_at)],
|
package/src/commands/login.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { hostname } from "node:os";
|
|
13
13
|
|
|
14
|
-
import { CliError } from "../errors.js";
|
|
14
|
+
import { CliError, UsageError } from "../errors.js";
|
|
15
15
|
import { readConfig, writeConfig, resolveHost } from "../config.js";
|
|
16
16
|
import { note, json, box, banner, fields, startSpinner, dim, bold, cyan, green } from "../output.js";
|
|
17
17
|
import { Api } from "../api.js";
|
|
@@ -23,6 +23,7 @@ export default {
|
|
|
23
23
|
name: "login",
|
|
24
24
|
usage: [
|
|
25
25
|
"flostep login",
|
|
26
|
+
"flostep login --read-only",
|
|
26
27
|
"flostep login --token <fls_...>"
|
|
27
28
|
],
|
|
28
29
|
details: [
|
|
@@ -31,15 +32,18 @@ export default {
|
|
|
31
32
|
],
|
|
32
33
|
options: {
|
|
33
34
|
token: { type: "string" },
|
|
34
|
-
"no-browser": { type: "boolean", default: false }
|
|
35
|
+
"no-browser": { type: "boolean", default: false },
|
|
36
|
+
"read-only": { type: "boolean", default: false }
|
|
35
37
|
},
|
|
36
38
|
optionHelp: [
|
|
39
|
+
["--read-only", "ask for a key that can read diagrams but not change, delete or share them"],
|
|
37
40
|
["--token <fls_...>", "store a key you already have instead of opening a browser"],
|
|
38
41
|
["--no-browser", "print the URL rather than opening it"]
|
|
39
42
|
],
|
|
40
43
|
examples: [
|
|
41
44
|
["flostep login", "approve in a browser on any device"],
|
|
42
45
|
["flostep login --no-browser", "for SSH sessions where nothing can open"],
|
|
46
|
+
["flostep login --read-only", "for a machine that should only look"],
|
|
43
47
|
["FLOSTEP_TOKEN=fls_… flostep list", "CI needs no login at all — set the env var"]
|
|
44
48
|
],
|
|
45
49
|
|
|
@@ -48,10 +52,20 @@ export default {
|
|
|
48
52
|
|
|
49
53
|
if (!ctx.json) banner();
|
|
50
54
|
|
|
55
|
+
// A pasted key already has whatever access it was created with; there is
|
|
56
|
+
// nothing this flag could change about it, and silently ignoring it would
|
|
57
|
+
// leave someone believing a write key was read-only.
|
|
58
|
+
if (values.token && values["read-only"]) {
|
|
59
|
+
throw new UsageError("--read-only applies to the browser login, not to a pasted key.", {
|
|
60
|
+
hint: "A key keeps the access it was created with — create a read-only one on the API keys page."
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
if (values.token) return storeToken({ host, token: values.token.trim(), ctx });
|
|
52
65
|
|
|
66
|
+
const readOnly = values["read-only"];
|
|
53
67
|
const api = ctx.api();
|
|
54
|
-
const grant = await api.requestDeviceCode(hostname());
|
|
68
|
+
const grant = await api.requestDeviceCode(hostname(), readOnly ? { access: "read" } : {});
|
|
55
69
|
|
|
56
70
|
note();
|
|
57
71
|
box([ `Your code: ${bold(cyan(grant.user_code))}` ]);
|
|
@@ -74,6 +88,16 @@ export default {
|
|
|
74
88
|
stopSpinner();
|
|
75
89
|
}
|
|
76
90
|
|
|
91
|
+
// A server from before access levels ignores the request and mints a write
|
|
92
|
+
// key. Storing it would leave a credential that can change everything on a
|
|
93
|
+
// machine its owner asked to keep read-only, so it is refused instead — the
|
|
94
|
+
// key exists, though, and only the website can revoke it.
|
|
95
|
+
if (readOnly && token.access !== "read") {
|
|
96
|
+
throw new CliError("This Flostep server doesn't support read-only keys, so the key it created can write.", {
|
|
97
|
+
hint: `It was not saved. Revoke it on your API keys page (${host}/api-keys).`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
77
101
|
return storeToken({ host, token: token.token, expiresAt: token.expires_at, ctx });
|
|
78
102
|
}
|
|
79
103
|
};
|
|
@@ -139,7 +163,7 @@ async function storeToken({ host, token, expiresAt, ctx }) {
|
|
|
139
163
|
const me = await new Api({ host, token }).me();
|
|
140
164
|
|
|
141
165
|
if (ctx.json) {
|
|
142
|
-
json({ email: me.email, workspace: me.workspace, host, expires_at: expiresAt ?? null });
|
|
166
|
+
json({ email: me.email, workspace: me.workspace, access: me.access ?? null, host, expires_at: expiresAt ?? null });
|
|
143
167
|
return 0;
|
|
144
168
|
}
|
|
145
169
|
|
|
@@ -149,15 +173,35 @@ async function storeToken({ host, token, expiresAt, ctx }) {
|
|
|
149
173
|
fields([
|
|
150
174
|
[ "Account", me.email ],
|
|
151
175
|
[ "Workspace", `${me.workspace.name}${me.workspace.team ? dim(" (team)") : ""}` ],
|
|
152
|
-
[ "Plan", me.workspace
|
|
176
|
+
[ "Plan", formatPlan(me.workspace) ],
|
|
177
|
+
...(me.access ? [ [ "Access", formatAccess(me.access) ] ] : []),
|
|
153
178
|
...(expiresAt ? [ [ "Expires", formatExpiry(expiresAt) ] ] : [])
|
|
154
179
|
]);
|
|
155
180
|
note();
|
|
156
|
-
|
|
157
|
-
|
|
181
|
+
// The usual next step creates a diagram, which a read-only key would refuse.
|
|
182
|
+
if (me.access === "read") {
|
|
183
|
+
note(dim(` Next: ${cyan("flostep list")}`));
|
|
184
|
+
note(dim(" this key can read diagrams but not change them — `flostep --help` for the rest."));
|
|
185
|
+
} else {
|
|
186
|
+
note(dim(` Next: ${cyan('echo "A -> B: hello" | flostep create --title "My flow" --share')}`));
|
|
187
|
+
note(dim(" steps go in on stdin, one per line — `flostep --help` for the rest."));
|
|
188
|
+
}
|
|
158
189
|
return 0;
|
|
159
190
|
}
|
|
160
191
|
|
|
192
|
+
// "team", or "team (complimentary until 10 Oct 2027)" while a comp is what's
|
|
193
|
+
// paying for it — the billing page calls it Complimentary and states the end,
|
|
194
|
+
// and a terminal saying only "team" would make that end a surprise.
|
|
195
|
+
export function formatPlan(workspace) {
|
|
196
|
+
return workspace.comped_until
|
|
197
|
+
? `${workspace.plan} (complimentary until ${formatExpiry(workspace.comped_until)})`
|
|
198
|
+
: workspace.plan;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function formatAccess(access) {
|
|
202
|
+
return access === "read" ? "Read only" : "Read & write";
|
|
203
|
+
}
|
|
204
|
+
|
|
161
205
|
// A named month rather than toLocaleDateString()'s default, which renders
|
|
162
206
|
// "28/11/2026" — unreadable without knowing whether the locale puts the day or
|
|
163
207
|
// the month first, and this line is the one thing a person reads about how long
|
package/src/commands/move.js
CHANGED
|
@@ -17,8 +17,8 @@ export default {
|
|
|
17
17
|
options: { none: { type: "boolean", default: false } },
|
|
18
18
|
optionHelp: [["--none", "take the diagram out of its folder (uncategorized)"]],
|
|
19
19
|
examples: [
|
|
20
|
-
['flostep move
|
|
21
|
-
["flostep move
|
|
20
|
+
['flostep move Xk3p9QvA2wE "Payments"'],
|
|
21
|
+
["flostep move Xk3p9QvA2wE --none"]
|
|
22
22
|
],
|
|
23
23
|
|
|
24
24
|
async run({ positionals, values, ctx }) {
|
package/src/commands/node.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// way to express a type — the server infers that from the name. A `--type cache`
|
|
8
8
|
// flag would be discarded on the next write, which is worse than not offering it.
|
|
9
9
|
//
|
|
10
|
-
// To add a component, name it in a step: `flostep step add
|
|
10
|
+
// To add a component, name it in a step: `flostep step add Xk3p9QvA2wE "API -> Redis: read"`.
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
import { UsageError } from "../errors.js";
|
|
@@ -29,9 +29,9 @@ export default {
|
|
|
29
29
|
"Types (service, database, person…) are inferred from the name by the server."
|
|
30
30
|
],
|
|
31
31
|
examples: [
|
|
32
|
-
["flostep node list
|
|
33
|
-
['flostep node rename
|
|
34
|
-
["flostep node list
|
|
32
|
+
["flostep node list Xk3p9QvA2wE"],
|
|
33
|
+
['flostep node rename Xk3p9QvA2wE "Redis" "Session Cache"'],
|
|
34
|
+
["flostep node list Xk3p9QvA2wE --json", "components in first-appearance order"]
|
|
35
35
|
],
|
|
36
36
|
|
|
37
37
|
async run({ positionals, ctx }) {
|
package/src/commands/open.js
CHANGED
|
@@ -7,8 +7,8 @@ export default {
|
|
|
7
7
|
usage: "flostep open <id>",
|
|
8
8
|
details: ["Opens the diagram in the editor in your default browser."],
|
|
9
9
|
examples: [
|
|
10
|
-
["flostep open
|
|
11
|
-
["flostep open
|
|
10
|
+
["flostep open Xk3p9QvA2wE"],
|
|
11
|
+
["flostep open Xk3p9QvA2wE --json", "the url, and whether a browser started"]
|
|
12
12
|
],
|
|
13
13
|
|
|
14
14
|
async run({ positionals, ctx }) {
|
package/src/commands/share.js
CHANGED
|
@@ -48,10 +48,10 @@ export default {
|
|
|
48
48
|
["--markdown", "print a markdown image that links to the diagram (paid plans)"]
|
|
49
49
|
],
|
|
50
50
|
examples: [
|
|
51
|
-
["flostep share
|
|
52
|
-
["flostep share
|
|
53
|
-
["flostep share
|
|
54
|
-
["flostep share
|
|
51
|
+
["flostep share Xk3p9QvA2wE"],
|
|
52
|
+
["flostep share Xk3p9QvA2wE --embed", "for a docs page or Confluence"],
|
|
53
|
+
["flostep share Xk3p9QvA2wE --markdown", "for a GitHub README; follows the diagram"],
|
|
54
|
+
["flostep share Xk3p9QvA2wE --off"]
|
|
55
55
|
],
|
|
56
56
|
|
|
57
57
|
async run({ positionals, values, ctx }) {
|
package/src/commands/show.js
CHANGED
|
@@ -6,12 +6,12 @@ export default {
|
|
|
6
6
|
usage: "flostep show <id>",
|
|
7
7
|
details: [
|
|
8
8
|
"Prints the diagram as steps, and nothing else, so it pipes cleanly:",
|
|
9
|
-
" flostep show
|
|
9
|
+
" flostep show Xk3p9QvA2wE > docs/checkout.flostep"
|
|
10
10
|
],
|
|
11
11
|
examples: [
|
|
12
|
-
["flostep show
|
|
13
|
-
["flostep show
|
|
14
|
-
["flostep show
|
|
12
|
+
["flostep show Xk3p9QvA2wE"],
|
|
13
|
+
["flostep show Xk3p9QvA2wE > docs/checkout.flostep", "redirect it yourself if you want a file"],
|
|
14
|
+
["flostep show Xk3p9QvA2wE --json", "with the title and url alongside the code"]
|
|
15
15
|
],
|
|
16
16
|
|
|
17
17
|
async run({ positionals, ctx }) {
|
package/src/commands/step.js
CHANGED
|
@@ -28,10 +28,10 @@ export default {
|
|
|
28
28
|
options: { at: { type: "string" } },
|
|
29
29
|
optionHelp: [["--at <n>", "insert before step n instead of appending"]],
|
|
30
30
|
examples: [
|
|
31
|
-
['flostep step add
|
|
32
|
-
['flostep step add
|
|
33
|
-
["flostep step list
|
|
34
|
-
["flostep step rm
|
|
31
|
+
['flostep step add Xk3p9QvA2wE "API -> Cache: read session"'],
|
|
32
|
+
['flostep step add Xk3p9QvA2wE "Client -> API: retry" --at 3'],
|
|
33
|
+
["flostep step list Xk3p9QvA2wE --json"],
|
|
34
|
+
["flostep step rm Xk3p9QvA2wE 4"]
|
|
35
35
|
],
|
|
36
36
|
|
|
37
37
|
async run({ positionals, values, ctx }) {
|
package/src/commands/update.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `flostep update` — replace a diagram's steps from a pipe.
|
|
2
2
|
//
|
|
3
3
|
// The counterpart to `create`, and the composable half of the tool:
|
|
4
|
-
// flostep show
|
|
4
|
+
// flostep show Xk3p9QvA2wE | sed 's/Redis/Session Cache/' | flostep update Xk3p9QvA2wE
|
|
5
5
|
|
|
6
6
|
import { readStdin } from "../stdin.js";
|
|
7
7
|
import { UsageError } from "../errors.js";
|
|
@@ -14,7 +14,7 @@ export default {
|
|
|
14
14
|
name: "update",
|
|
15
15
|
usage: [
|
|
16
16
|
"flostep update <id> [--title <title>] [--if-version <n>] < steps.txt",
|
|
17
|
-
"flostep show
|
|
17
|
+
"flostep show Xk3p9QvA2wE | flostep update Xk3p9QvA2wE"
|
|
18
18
|
],
|
|
19
19
|
details: [
|
|
20
20
|
"Reads steps from stdin and replaces every step in the diagram.",
|
|
@@ -31,15 +31,15 @@ export default {
|
|
|
31
31
|
["--if-version <n>", "only write if the diagram is still at this version"]
|
|
32
32
|
],
|
|
33
33
|
examples: [
|
|
34
|
-
["flostep show
|
|
35
|
-
["flostep update
|
|
36
|
-
["flostep update
|
|
34
|
+
["flostep show Xk3p9QvA2wE | sed 's/Redis/Cache/' | flostep update Xk3p9QvA2wE"],
|
|
35
|
+
["flostep update Xk3p9QvA2wE < revised.txt"],
|
|
36
|
+
["flostep update Xk3p9QvA2wE --if-version 7 < revised.txt", "refused if it changed since version 7"]
|
|
37
37
|
],
|
|
38
38
|
|
|
39
39
|
async run({ positionals, values, ctx }) {
|
|
40
40
|
const { id } = resolveTarget(positionals[0]);
|
|
41
41
|
const version = parseVersion(values["if-version"]);
|
|
42
|
-
const input = await readStdin({ what: "`flostep update
|
|
42
|
+
const input = await readStdin({ what: "`flostep update Xk3p9QvA2wE < flow.txt`" });
|
|
43
43
|
const { code, summary } = diagramFromStdin(input);
|
|
44
44
|
|
|
45
45
|
const body = { code };
|
package/src/commands/whoami.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveToken } from "../config.js";
|
|
2
2
|
import { CliError } from "../errors.js";
|
|
3
3
|
import { out, json, dim, bold } from "../output.js";
|
|
4
|
+
import { formatAccess, formatPlan } from "./login.js";
|
|
4
5
|
|
|
5
6
|
export default {
|
|
6
7
|
name: "whoami",
|
|
@@ -27,7 +28,9 @@ export default {
|
|
|
27
28
|
|
|
28
29
|
out(`${bold(me.email)}${me.name && me.name !== me.email ? dim(` (${me.name})`) : ""}`);
|
|
29
30
|
out(`${dim("Workspace")} ${me.workspace.name}${me.workspace.team ? dim(" (team)") : ""}`);
|
|
30
|
-
out(`${dim("Plan")} ${me.workspace
|
|
31
|
+
out(`${dim("Plan")} ${formatPlan(me.workspace)}`);
|
|
32
|
+
// Absent from servers before access levels, where every key could write.
|
|
33
|
+
if (me.access) out(`${dim("Access")} ${formatAccess(me.access)}`);
|
|
31
34
|
out(
|
|
32
35
|
`${dim("Diagrams")} ${
|
|
33
36
|
me.diagrams_remaining === null ? "unlimited" : `${me.diagrams_remaining} remaining on this plan`
|
package/src/target.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Turning "
|
|
1
|
+
// Turning "Xk3_p9QvA2w" into a diagram id.
|
|
2
2
|
//
|
|
3
3
|
// Every command that acts on one diagram takes an id, and only an id. Diagrams
|
|
4
4
|
// live in the account, not in a checkout: `create` and `update` read steps from
|
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import { UsageError } from "./errors.js";
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// Any URL-safe token. Ids are random strings now; the integers servers used to
|
|
11
|
+
// hand out still resolve, so they pass too. This deliberately doesn't pin the
|
|
12
|
+
// server's exact format — when ids were checked as digits-only, changing the
|
|
13
|
+
// format would have broken every installed CLI. A wrong id is the server's 404
|
|
14
|
+
// to report; what this catches is a title passed where the id goes.
|
|
12
15
|
export function looksLikeId(ref) {
|
|
13
|
-
return /^[
|
|
16
|
+
return /^[A-Za-z0-9_-]+$/.test(String(ref).trim());
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
// Usage errors, not CliErrors: a missing or malformed id is the caller getting
|
|
@@ -25,9 +28,9 @@ export function resolveTarget(ref) {
|
|
|
25
28
|
|
|
26
29
|
if (!looksLikeId(ref)) {
|
|
27
30
|
throw new UsageError(`"${ref}" is not a diagram id.`, {
|
|
28
|
-
hint: "
|
|
31
|
+
hint: "Pass the ID column from `flostep list`, not the title."
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
return { id:
|
|
35
|
+
return { id: String(ref).trim() };
|
|
33
36
|
}
|