flostep 0.1.1 → 0.1.2
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 +7 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/cli.js +71 -18
- package/src/commands/create.js +1 -1
- package/src/commands/folder.js +1 -1
- package/src/commands/init.js +1 -1
- package/src/commands/list.js +2 -2
- package/src/commands/login.js +5 -3
- package/src/commands/node.js +1 -1
- package/src/commands/open.js +10 -3
- package/src/commands/share.js +61 -11
- package/src/commands/step.js +3 -3
- package/src/commands/syntax.js +3 -2
- package/src/commands/update.js +1 -1
- package/src/commands/whoami.js +1 -1
- package/src/config.js +2 -1
- package/src/errors.js +2 -1
- package/src/output.js +18 -0
- package/src/stdin.js +1 -1
- package/src/target.js +6 -3
package/AGENTS.md
CHANGED
|
@@ -48,6 +48,12 @@ npx flostep share 42 # prints the public link
|
|
|
48
48
|
npx flostep share 42 --embed # iframe URL, for a docs page
|
|
49
49
|
```
|
|
50
50
|
|
|
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
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx flostep share 42 --markdown # an image that follows the diagram as it changes
|
|
55
|
+
```
|
|
56
|
+
|
|
51
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:
|
|
52
58
|
|
|
53
59
|
```bash
|
|
@@ -100,6 +106,7 @@ npx flostep whoami --json # which workspace you're writing to
|
|
|
100
106
|
- **Don't leave files behind.** No command writes to disk. Redirect `show` yourself if the user asks for the steps in a file.
|
|
101
107
|
- **Never invent a diagram id.** Get it from `list`, `create`, or the user.
|
|
102
108
|
- **Exit codes**: `0` success, `1` error, `2` usage. Errors go to stderr with the reason.
|
|
109
|
+
- **Every command takes `--json`**, and `flostep help <command>` lists its flags — look them up rather than guess.
|
|
103
110
|
- **There is no `node add` and no `--type`.** The format cannot express an unconnected component or an explicit type — types are inferred from the name. Add a component by naming it in a step.
|
|
104
111
|
- **Notes, positions and hand-drawn curves are not in the text format** and are dropped by any write. Say so if the user has them.
|
|
105
112
|
- Ask before `delete`. It needs `--yes` when there's no terminal, and it cannot be undone.
|
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ Run `flostep syntax` for the authoritative version, fetched from the server.
|
|
|
59
59
|
| `flostep show <id>` | print a diagram as steps (pipeable) |
|
|
60
60
|
| `flostep create` | create from stdin, writing no files (`--share` for a link) |
|
|
61
61
|
| `flostep update <id>` | replace a diagram's steps from stdin (`--if-version` to refuse if it changed) |
|
|
62
|
-
| `flostep share <id>` | public link on/off (`--embed` for an iframe) |
|
|
62
|
+
| `flostep share <id>` | public link on/off (`--embed` for an iframe, `--markdown` for a README image) |
|
|
63
63
|
| `flostep open <id>` | open the editor in a browser |
|
|
64
64
|
| `flostep delete <id>` | delete a diagram |
|
|
65
65
|
| `flostep folder list\|create\|rename\|delete` | manage the workspace's folders |
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -39,12 +39,22 @@ const GLOBAL_OPTIONS = {
|
|
|
39
39
|
version: { type: "boolean", short: "v", default: false }
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
const GLOBAL_OPTION_HELP = [
|
|
43
|
+
["--json", "machine-readable output"],
|
|
44
|
+
["-h, --help", "show this help"]
|
|
45
|
+
];
|
|
46
|
+
|
|
42
47
|
|
|
43
48
|
async function load(name) {
|
|
44
49
|
const mod = await COMMANDS[name]();
|
|
45
50
|
return mod.default;
|
|
46
51
|
}
|
|
47
52
|
|
|
53
|
+
// Continuation lines line up under the first, after "Usage: ".
|
|
54
|
+
function usageText(command) {
|
|
55
|
+
return [].concat(command.usage).join("\n ");
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
// The tool's own description of its surface, built from the very objects
|
|
49
59
|
// `main` dispatches — so it cannot disagree with what the CLI actually accepts.
|
|
50
60
|
//
|
|
@@ -95,10 +105,18 @@ function topLevelHelp() {
|
|
|
95
105
|
out(" -v, --version print the version");
|
|
96
106
|
out();
|
|
97
107
|
out(bold("GETTING STARTED"));
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
// Each line has to run exactly as printed — `create` with nothing piped in
|
|
109
|
+
// only errors, so its example carries a step.
|
|
110
|
+
const starters = [
|
|
111
|
+
["flostep login", "sign in from a browser"],
|
|
112
|
+
['echo "A -> B: hello" | flostep create --share', "steps on stdin, returns a link"],
|
|
113
|
+
["flostep show 42 | flostep update 42", "read it, change it, write it back"],
|
|
114
|
+
["flostep init", "teach this repo's coding agent to use flostep"]
|
|
115
|
+
];
|
|
116
|
+
const starterWidth = Math.max(...starters.map(([command]) => command.length));
|
|
117
|
+
for (const [command, what] of starters) {
|
|
118
|
+
out(` ${cyan(command.padEnd(starterWidth))} ${what}`);
|
|
119
|
+
}
|
|
102
120
|
out();
|
|
103
121
|
out(dim("Steps are written one per line: `From -> To: what happens`."));
|
|
104
122
|
out(dim("Run `flostep syntax` for the full grammar, straight from the server."));
|
|
@@ -139,12 +157,13 @@ function commandHelp(command) {
|
|
|
139
157
|
for (const line of [].concat(command.details)) out(` ${line}`);
|
|
140
158
|
}
|
|
141
159
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
// The global flags are listed on every command, because every command
|
|
161
|
+
// honours them — a usage line that mentions --json on some commands and not
|
|
162
|
+
// others reads as though the rest don't take it.
|
|
163
|
+
out();
|
|
164
|
+
out(bold("OPTIONS"));
|
|
165
|
+
for (const [flag, description] of [ ...(command.optionHelp ?? []), ...GLOBAL_OPTION_HELP ]) {
|
|
166
|
+
out(` ${flag.padEnd(22)} ${description}`);
|
|
148
167
|
}
|
|
149
168
|
|
|
150
169
|
if (command.examples) {
|
|
@@ -162,6 +181,14 @@ export async function main(argv) {
|
|
|
162
181
|
|
|
163
182
|
const wantsJson = argv.includes("--json");
|
|
164
183
|
|
|
184
|
+
// `flostep help create` is the form people try first, so it means the same
|
|
185
|
+
// as `flostep create --help`.
|
|
186
|
+
if (name === "help" && rest[0] && !rest[0].startsWith("-")) {
|
|
187
|
+
if (!Object.hasOwn(COMMANDS, rest[0])) throw unknownCommand(rest[0]);
|
|
188
|
+
commandHelp(await load(rest[0]));
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
165
192
|
if (!name || name === "help") {
|
|
166
193
|
// --help is not an error; asking for it should exit 0 so a wrapper script
|
|
167
194
|
// can call it without tripping `set -e`.
|
|
@@ -191,11 +218,7 @@ export async function main(argv) {
|
|
|
191
218
|
return 0;
|
|
192
219
|
}
|
|
193
220
|
|
|
194
|
-
if (!Object.hasOwn(COMMANDS, name))
|
|
195
|
-
throw new UsageError(`Unknown command "${name}".`, {
|
|
196
|
-
usage: "Run `flostep --help` to see the available commands."
|
|
197
|
-
});
|
|
198
|
-
}
|
|
221
|
+
if (!Object.hasOwn(COMMANDS, name)) throw unknownCommand(name);
|
|
199
222
|
|
|
200
223
|
const command = await load(name);
|
|
201
224
|
|
|
@@ -211,7 +234,7 @@ export async function main(argv) {
|
|
|
211
234
|
strict: true
|
|
212
235
|
});
|
|
213
236
|
} catch (cause) {
|
|
214
|
-
throw new UsageError(cause
|
|
237
|
+
throw new UsageError(parseErrorMessage(cause), { usage: usageText(command) });
|
|
215
238
|
}
|
|
216
239
|
|
|
217
240
|
if (parsed.values.help) {
|
|
@@ -219,6 +242,12 @@ export async function main(argv) {
|
|
|
219
242
|
return 0;
|
|
220
243
|
}
|
|
221
244
|
|
|
245
|
+
// Honoured after a command too, as --help is, rather than parsed and ignored.
|
|
246
|
+
if (parsed.values.version) {
|
|
247
|
+
out(VERSION);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
222
251
|
const { token, source: tokenSource } = resolveToken();
|
|
223
252
|
const ctx = {
|
|
224
253
|
json: parsed.values.json,
|
|
@@ -230,7 +259,31 @@ export async function main(argv) {
|
|
|
230
259
|
}
|
|
231
260
|
};
|
|
232
261
|
|
|
233
|
-
|
|
262
|
+
try {
|
|
263
|
+
return (await command.run({ positionals: parsed.positionals, values: parsed.values, ctx })) ?? 0;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
// Shared helpers like resolveTarget don't know which command called them;
|
|
266
|
+
// this does, so their usage errors still end with the right usage line.
|
|
267
|
+
if (error instanceof UsageError && !error.usage) error.usage = usageText(command);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function unknownCommand(name) {
|
|
273
|
+
return new UsageError(`Unknown command "${name}".`, {
|
|
274
|
+
hint: "Run `flostep --help` to see the available commands."
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Node's own wording for a mistyped flag goes on to explain how to pass a
|
|
279
|
+
// positional that starts with "-", which is never what someone typing --tittle
|
|
280
|
+
// meant. Everything else it says is clear enough to keep.
|
|
281
|
+
function parseErrorMessage(cause) {
|
|
282
|
+
if (cause.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
|
|
283
|
+
const flag = cause.message.match(/'([^']+)'/)?.[1];
|
|
284
|
+
if (flag) return `Unknown option ${flag}.`;
|
|
285
|
+
}
|
|
286
|
+
return cause.message;
|
|
234
287
|
}
|
|
235
288
|
|
|
236
289
|
export async function run(argv) {
|
|
@@ -238,7 +291,7 @@ export async function run(argv) {
|
|
|
238
291
|
return await main(argv);
|
|
239
292
|
} catch (error) {
|
|
240
293
|
if (error instanceof UsageError) {
|
|
241
|
-
fail(error.message);
|
|
294
|
+
fail(error.message, error.hint);
|
|
242
295
|
if (error.usage) {
|
|
243
296
|
note();
|
|
244
297
|
note(`Usage: ${error.usage}`);
|
package/src/commands/create.js
CHANGED
|
@@ -11,7 +11,7 @@ import { diagramFromStdin, describe } from "../source.js";
|
|
|
11
11
|
export default {
|
|
12
12
|
name: "create",
|
|
13
13
|
usage: [
|
|
14
|
-
'flostep create [--title <title>] [--share]',
|
|
14
|
+
'flostep create [--title <title>] [--share] < steps.txt',
|
|
15
15
|
'flostep show 42 | flostep create --title "Copy"'
|
|
16
16
|
],
|
|
17
17
|
details: [
|
package/src/commands/folder.js
CHANGED
|
@@ -15,7 +15,7 @@ const SUBCOMMANDS = ["list", "create", "rename", "delete"];
|
|
|
15
15
|
export default {
|
|
16
16
|
name: "folder",
|
|
17
17
|
usage: [
|
|
18
|
-
"flostep folder list
|
|
18
|
+
"flostep folder list",
|
|
19
19
|
'flostep folder create "<name>"',
|
|
20
20
|
'flostep folder rename "<old>" "<new>"',
|
|
21
21
|
'flostep folder delete "<name>" [--yes]'
|
package/src/commands/init.js
CHANGED
|
@@ -46,7 +46,7 @@ export default {
|
|
|
46
46
|
["-y, --yes", "skip the confirmation prompt"]
|
|
47
47
|
],
|
|
48
48
|
examples: [
|
|
49
|
-
["
|
|
49
|
+
["flostep init", "in the root of the repository"],
|
|
50
50
|
["flostep init --file docs/agents.md"],
|
|
51
51
|
["flostep init --print >> system-prompt.md", "for a prompt that isn't a file in a repo"]
|
|
52
52
|
],
|
package/src/commands/list.js
CHANGED
|
@@ -2,7 +2,7 @@ import { out, table, json, relativeTime, dim } from "../output.js";
|
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
4
|
name: "list",
|
|
5
|
-
usage: "flostep list [--folder <name>]
|
|
5
|
+
usage: "flostep list [--folder <name>]",
|
|
6
6
|
details: [
|
|
7
7
|
"Every diagram in the workspace the key belongs to, most recently updated first.",
|
|
8
8
|
"--folder narrows it to one folder; `--folder uncategorized` to diagrams in none."
|
|
@@ -39,7 +39,7 @@ export default {
|
|
|
39
39
|
|
|
40
40
|
if (diagrams.length === 0) {
|
|
41
41
|
out(dim(folder === undefined
|
|
42
|
-
? "No diagrams yet. Create one with `flostep create --title X --share`."
|
|
42
|
+
? "No diagrams yet. Create one with `echo 'A -> B: hello' | flostep create --title X --share`."
|
|
43
43
|
: "No diagrams in that folder."));
|
|
44
44
|
return 0;
|
|
45
45
|
}
|
package/src/commands/login.js
CHANGED
|
@@ -13,7 +13,7 @@ import { hostname } from "node:os";
|
|
|
13
13
|
|
|
14
14
|
import { CliError } from "../errors.js";
|
|
15
15
|
import { readConfig, writeConfig, resolveHost } from "../config.js";
|
|
16
|
-
import { note, json, box, fields, startSpinner, dim, bold, cyan, green } from "../output.js";
|
|
16
|
+
import { note, json, box, banner, fields, startSpinner, dim, bold, cyan, green } from "../output.js";
|
|
17
17
|
import { Api } from "../api.js";
|
|
18
18
|
import { openBrowser } from "../browser.js";
|
|
19
19
|
|
|
@@ -46,6 +46,8 @@ export default {
|
|
|
46
46
|
async run({ values, ctx }) {
|
|
47
47
|
const host = resolveHost();
|
|
48
48
|
|
|
49
|
+
if (!ctx.json) banner();
|
|
50
|
+
|
|
49
51
|
if (values.token) return storeToken({ host, token: values.token.trim(), ctx });
|
|
50
52
|
|
|
51
53
|
const api = ctx.api();
|
|
@@ -151,8 +153,8 @@ async function storeToken({ host, token, expiresAt, ctx }) {
|
|
|
151
153
|
...(expiresAt ? [ [ "Expires", formatExpiry(expiresAt) ] ] : [])
|
|
152
154
|
]);
|
|
153
155
|
note();
|
|
154
|
-
note(dim(` Next: ${cyan('flostep create --title "My flow" --share')}`));
|
|
155
|
-
note(dim(" steps on stdin,
|
|
156
|
+
note(dim(` Next: ${cyan('echo "A -> B: hello" | flostep create --title "My flow" --share')}`));
|
|
157
|
+
note(dim(" steps go in on stdin, one per line — `flostep --help` for the rest."));
|
|
156
158
|
return 0;
|
|
157
159
|
}
|
|
158
160
|
|
package/src/commands/node.js
CHANGED
package/src/commands/open.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { out, ok, note, dim } from "../output.js";
|
|
1
|
+
import { out, ok, note, json, dim } from "../output.js";
|
|
2
2
|
import { openBrowser } from "../browser.js";
|
|
3
3
|
import { resolveTarget } from "../target.js";
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
details: ["Opens the diagram in the editor in your default browser."],
|
|
9
9
|
examples: [
|
|
10
10
|
["flostep open 15"],
|
|
11
|
-
["flostep open
|
|
11
|
+
["flostep open 15 --json", "the url, and whether a browser started"]
|
|
12
12
|
],
|
|
13
13
|
|
|
14
14
|
async run({ positionals, ctx }) {
|
|
@@ -17,7 +17,14 @@ export default {
|
|
|
17
17
|
|
|
18
18
|
// Awaited: whether a browser actually started is only known a tick later,
|
|
19
19
|
// and headless is a normal place to run this — the URL is the useful half.
|
|
20
|
-
|
|
20
|
+
const opened = await openBrowser(diagram.url);
|
|
21
|
+
|
|
22
|
+
if (ctx.json) {
|
|
23
|
+
json({ id: diagram.id, title: diagram.title, url: diagram.url, opened });
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (opened) {
|
|
21
28
|
ok(`Opening ${diagram.title}`);
|
|
22
29
|
out(diagram.url);
|
|
23
30
|
} else {
|
package/src/commands/share.js
CHANGED
|
@@ -1,24 +1,56 @@
|
|
|
1
1
|
import { out, ok, json, dim } from "../output.js";
|
|
2
2
|
import { resolveTarget } from "../target.js";
|
|
3
|
+
import { CliError, UsageError } from "../errors.js";
|
|
4
|
+
|
|
5
|
+
// --off on a line of its own, because it takes neither of the others.
|
|
6
|
+
const USAGE = [
|
|
7
|
+
"flostep share <id> [--embed | --markdown]",
|
|
8
|
+
"flostep share <id> --off"
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
// Square brackets would end the alt text early, so the title gives them up.
|
|
12
|
+
function markdown(title, imageUrl, linkUrl) {
|
|
13
|
+
const alt = String(title ?? "").replace(/[\[\]\s]+/g, " ").trim() || "Diagram";
|
|
14
|
+
return `[](${linkUrl})`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// The server decides what a plan includes and says so with a null; the CLI
|
|
18
|
+
// never restates the plan rules. A missing key is a different thing — a server
|
|
19
|
+
// from before the feature — and saying "upgrade" there would be wrong.
|
|
20
|
+
function need(value, what, shareUrl) {
|
|
21
|
+
if (value === undefined) {
|
|
22
|
+
throw new CliError(`This Flostep server doesn't offer ${what} yet.`);
|
|
23
|
+
}
|
|
24
|
+
if (value === null) {
|
|
25
|
+
throw new CliError(`${what[0].toUpperCase()}${what.slice(1)} need a paid plan.`, {
|
|
26
|
+
hint: `The diagram is shared, and the link works on every plan: ${shareUrl}`
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
3
31
|
|
|
4
32
|
export default {
|
|
5
33
|
name: "share",
|
|
6
|
-
usage:
|
|
34
|
+
usage: USAGE,
|
|
7
35
|
details: [
|
|
8
36
|
"Turns on the public link and prints it — the natural last step after `create` or `update`.",
|
|
9
|
-
"The link is read-only and needs no account; viewers can step through the flow."
|
|
37
|
+
"The link is read-only and needs no account; viewers can step through the flow.",
|
|
38
|
+
"GitHub strips iframes, so for a README, an ADR or a PR use --markdown: an image of the diagram that links to the walkthrough."
|
|
10
39
|
],
|
|
11
40
|
options: {
|
|
12
41
|
off: { type: "boolean", default: false },
|
|
13
|
-
embed: { type: "boolean", default: false }
|
|
42
|
+
embed: { type: "boolean", default: false },
|
|
43
|
+
markdown: { type: "boolean", default: false }
|
|
14
44
|
},
|
|
15
45
|
optionHelp: [
|
|
16
46
|
["--off", "stop sharing (the URL is kept, so re-sharing restores it)"],
|
|
17
|
-
["--embed", "print the iframe URL instead of the page URL"]
|
|
47
|
+
["--embed", "print the iframe URL instead of the page URL"],
|
|
48
|
+
["--markdown", "print a markdown image that links to the diagram (paid plans)"]
|
|
18
49
|
],
|
|
19
50
|
examples: [
|
|
20
51
|
["flostep share 15"],
|
|
21
|
-
["flostep share 42 --embed", "for a
|
|
52
|
+
["flostep share 42 --embed", "for a docs page or Confluence"],
|
|
53
|
+
["flostep share 42 --markdown", "for a GitHub README; follows the diagram"],
|
|
22
54
|
["flostep share 15 --off"]
|
|
23
55
|
],
|
|
24
56
|
|
|
@@ -26,22 +58,40 @@ export default {
|
|
|
26
58
|
const { id } = resolveTarget(positionals[0]);
|
|
27
59
|
const api = ctx.api();
|
|
28
60
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (ctx.json) {
|
|
32
|
-
json(result);
|
|
33
|
-
return 0;
|
|
61
|
+
if (values.off && (values.embed || values.markdown)) {
|
|
62
|
+
throw new UsageError("--off takes no other flag.", { usage: USAGE.join("\n ") });
|
|
34
63
|
}
|
|
64
|
+
if (values.embed && values.markdown) {
|
|
65
|
+
throw new UsageError("Use --embed or --markdown, not both.", { usage: USAGE.join("\n ") });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result = values.off ? await api.unshareDiagram(id) : await api.shareDiagram(id);
|
|
35
69
|
|
|
36
70
|
if (values.off) {
|
|
71
|
+
if (ctx.json) {
|
|
72
|
+
json(result);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
37
75
|
ok(`Sharing off for #${result.id} ${dim(result.title)}`);
|
|
38
76
|
// Worth saying: people expect "unshare" to burn the URL, and it doesn't.
|
|
39
77
|
out(dim("The link is kept — sharing again restores the same URL."));
|
|
40
78
|
return 0;
|
|
41
79
|
}
|
|
42
80
|
|
|
81
|
+
// Checked before anything is printed, --json included: what was asked for
|
|
82
|
+
// either exists or the command fails, in both registers.
|
|
83
|
+
if (values.markdown) need(result.image_url, "image embeds", result.share_url);
|
|
84
|
+
|
|
85
|
+
const snippet = values.markdown ? markdown(result.title, result.image_url, result.share_url) : null;
|
|
86
|
+
|
|
87
|
+
if (ctx.json) {
|
|
88
|
+
json(snippet ? { ...result, markdown: snippet } : result);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
43
92
|
ok(`Sharing #${result.id} ${dim(result.title)}`);
|
|
44
|
-
out(
|
|
93
|
+
if (snippet) out(snippet);
|
|
94
|
+
else out(values.embed ? result.embed_url : result.share_url);
|
|
45
95
|
return 0;
|
|
46
96
|
}
|
|
47
97
|
};
|
package/src/commands/step.js
CHANGED
|
@@ -19,7 +19,7 @@ export default {
|
|
|
19
19
|
usage: [
|
|
20
20
|
'flostep step add <id> "From -> To: what happens" [--at <n>]',
|
|
21
21
|
"flostep step rm <id> <n>",
|
|
22
|
-
"flostep step list <id>
|
|
22
|
+
"flostep step list <id>"
|
|
23
23
|
],
|
|
24
24
|
details: [
|
|
25
25
|
"Steps are numbered from 1, matching the badges on the canvas.",
|
|
@@ -77,7 +77,7 @@ export default {
|
|
|
77
77
|
function applyAdd({ code, text, at }) {
|
|
78
78
|
if (!text.trim()) {
|
|
79
79
|
throw new UsageError('What step? Pass it in quotes: "From -> To: what happens".', {
|
|
80
|
-
usage: 'flostep step add <id> "From -> To: what happens"'
|
|
80
|
+
usage: 'flostep step add <id> "From -> To: what happens" [--at <n>]'
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -85,7 +85,7 @@ function applyAdd({ code, text, at }) {
|
|
|
85
85
|
if (at !== undefined) {
|
|
86
86
|
position = Number(at);
|
|
87
87
|
if (!Number.isInteger(position)) {
|
|
88
|
-
throw new UsageError("--at takes a step number.", { usage:
|
|
88
|
+
throw new UsageError("--at takes a step number.", { usage: 'flostep step add <id> "From -> To: what happens" --at <n>' });
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
package/src/commands/syntax.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { out } from "../output.js";
|
|
1
|
+
import { out, json } from "../output.js";
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
4
|
name: "syntax",
|
|
@@ -11,7 +11,8 @@ export default {
|
|
|
11
11
|
|
|
12
12
|
async run({ ctx }) {
|
|
13
13
|
const { syntax } = await ctx.api().syntax();
|
|
14
|
-
|
|
14
|
+
if (ctx.json) json({ syntax });
|
|
15
|
+
else out(syntax);
|
|
15
16
|
return 0;
|
|
16
17
|
}
|
|
17
18
|
};
|
package/src/commands/update.js
CHANGED
|
@@ -13,7 +13,7 @@ import { diagramFromStdin, describe } from "../source.js";
|
|
|
13
13
|
export default {
|
|
14
14
|
name: "update",
|
|
15
15
|
usage: [
|
|
16
|
-
"flostep update <id> [--title <title>] [--if-version <n>]",
|
|
16
|
+
"flostep update <id> [--title <title>] [--if-version <n>] < steps.txt",
|
|
17
17
|
"flostep show 42 | flostep update 42"
|
|
18
18
|
],
|
|
19
19
|
details: [
|
package/src/commands/whoami.js
CHANGED
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
usage: "flostep whoami",
|
|
8
8
|
details: [
|
|
9
9
|
"A key belongs to you but acts on the whole workspace — anything it creates",
|
|
10
|
-
"belongs to the team. This is how you check which
|
|
10
|
+
"belongs to the team. This is how you check which workspace you're writing to."
|
|
11
11
|
],
|
|
12
12
|
examples: [
|
|
13
13
|
["flostep whoami"],
|
package/src/config.js
CHANGED
|
@@ -9,7 +9,8 @@ import { homedir } from "node:os";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { mkdirSync, readFileSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
|
11
11
|
|
|
12
|
-
export const DEFAULT_HOST = "https://flostep.dev";
|
|
12
|
+
// export const DEFAULT_HOST = "https://flostep.dev";
|
|
13
|
+
export const DEFAULT_HOST = "http://localhost:3000";
|
|
13
14
|
|
|
14
15
|
// XDG first, so anyone who has moved their config directory is respected;
|
|
15
16
|
// ~/.config is the fallback rather than ~/.flostep because a dotfile per tool
|
package/src/errors.js
CHANGED
|
@@ -17,10 +17,11 @@ export class CliError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export class UsageError extends Error {
|
|
20
|
-
constructor(message, { usage } = {}) {
|
|
20
|
+
constructor(message, { usage, hint } = {}) {
|
|
21
21
|
super(message);
|
|
22
22
|
this.name = "UsageError";
|
|
23
23
|
this.exitCode = 2;
|
|
24
24
|
this.usage = usage;
|
|
25
|
+
this.hint = hint;
|
|
25
26
|
}
|
|
26
27
|
}
|
package/src/output.js
CHANGED
|
@@ -118,6 +118,24 @@ export function box(lines, { indent = " " } = {}) {
|
|
|
118
118
|
note(`${indent}${dim("╰" + bar + "╯")}`);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// The wordmark, shown once at the top of a login. Skipped when stderr is not a
|
|
122
|
+
// TTY — in a CI log it is noise — and when the terminal is too
|
|
123
|
+
// narrow, where it would wrap into an unreadable mess.
|
|
124
|
+
const WORDMARK = [
|
|
125
|
+
"█▀▀ █ █▀█ █▀ ▀█▀ █▀▀ █▀█",
|
|
126
|
+
"█▀ █▄▄ █▄█ ▄█ █ ██▄ █▀▀"
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
export function banner({ indent = " " } = {}) {
|
|
130
|
+
if (!process.stderr.isTTY) return;
|
|
131
|
+
|
|
132
|
+
const width = indent.length + Math.max(...WORDMARK.map((line) => line.length));
|
|
133
|
+
if ((process.stderr.columns ?? 80) < width) return;
|
|
134
|
+
|
|
135
|
+
note();
|
|
136
|
+
for (const line of WORDMARK) note(`${indent}${cyan(line)}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
121
139
|
// Label/value rows, aligned. Same idea as `table` but for a single record.
|
|
122
140
|
export function fields(rows, { indent = " " } = {}) {
|
|
123
141
|
const width = Math.max(...rows.map(([label]) => label.length));
|
package/src/stdin.js
CHANGED
|
@@ -12,7 +12,7 @@ export async function readStdin({ what }) {
|
|
|
12
12
|
// staring at a cursor with no idea the process is waiting on them.
|
|
13
13
|
if (process.stdin.isTTY) {
|
|
14
14
|
throw new CliError(`No steps on stdin.`, {
|
|
15
|
-
hint: `Pipe them in
|
|
15
|
+
hint: `Pipe them in, or redirect a file: ${what}.`
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
package/src/target.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// a pipe and write nothing to disk, so there is no local file to name and
|
|
6
6
|
// nothing to keep in sync.
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { UsageError } from "./errors.js";
|
|
9
9
|
|
|
10
10
|
// A bare positive integer. Ids come from `flostep list`, from `create`, or
|
|
11
11
|
// from the user — never from a guess.
|
|
@@ -13,15 +13,18 @@ export function looksLikeId(ref) {
|
|
|
13
13
|
return /^[1-9][0-9]*$/.test(String(ref).trim());
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// Usage errors, not CliErrors: a missing or malformed id is the caller getting
|
|
17
|
+
// the command wrong, and exits 2 like any other. cli.js fills in the usage line
|
|
18
|
+
// of whichever command was running.
|
|
16
19
|
export function resolveTarget(ref) {
|
|
17
20
|
if (!ref) {
|
|
18
|
-
throw new
|
|
21
|
+
throw new UsageError("Which diagram? Pass an id.", {
|
|
19
22
|
hint: "Run `flostep list` to see ids."
|
|
20
23
|
});
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
if (!looksLikeId(ref)) {
|
|
24
|
-
throw new
|
|
27
|
+
throw new UsageError(`"${ref}" is not a diagram id.`, {
|
|
25
28
|
hint: "Ids are numbers — run `flostep list` to see them."
|
|
26
29
|
});
|
|
27
30
|
}
|