@flow-engineer/flow 0.2.1 → 0.2.3
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 +167 -10
- package/coding-agents/auth-skill.md +137 -0
- package/coding-agents/context-skill.md +94 -0
- package/coding-agents/skill.md +219 -17
- package/coding-agents/test-skill.md +3 -1
- package/coding-agents/tools-skill.md +78 -1
- package/dist/cli/src/auth.d.ts +11 -0
- package/dist/cli/src/auth.js +147 -0
- package/dist/cli/src/commands.d.ts +119 -0
- package/dist/cli/src/commands.js +212 -0
- package/dist/cli/src/index.d.ts +2 -0
- package/dist/cli/src/index.js +5 -0
- package/dist/cli/src/run.d.ts +28 -0
- package/dist/cli/src/run.js +523 -0
- package/dist/cli/src/terminal.d.ts +3 -0
- package/dist/cli/src/terminal.js +135 -0
- package/dist/flow-messaging/src/client.d.ts +2 -2
- package/dist/flow-messaging/src/client.js +3 -3
- package/dist/flow-messaging/src/index.js +17 -9
- package/dist/mcp/src/client.d.ts +54 -0
- package/dist/mcp/src/client.js +175 -0
- package/dist/mcp/src/describe.d.ts +41 -0
- package/dist/mcp/src/describe.js +110 -0
- package/dist/mcp/src/index.d.ts +4 -0
- package/dist/mcp/src/index.js +7 -0
- package/dist/mcp/src/server.d.ts +70 -0
- package/dist/mcp/src/server.js +252 -0
- package/dist/mcp/src/terminal.d.ts +24 -0
- package/dist/mcp/src/terminal.js +54 -0
- package/dist/simulators/src/index.d.ts +1 -1
- package/dist/simulators/src/page.html +267 -49
- package/dist/simulators/src/server.d.ts +27 -0
- package/dist/simulators/src/server.js +264 -40
- package/dist/src/agent.d.ts +69 -3
- package/dist/src/agent.js +162 -8
- package/dist/src/channels.d.ts +6 -4
- package/dist/src/channels.js +4 -3
- package/dist/src/conversation.d.ts +17 -1
- package/dist/src/conversation.js +170 -16
- package/dist/src/events.d.ts +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +1 -1
- package/dist/src/model.d.ts +27 -0
- package/dist/src/model.js +85 -13
- package/dist/src/skills.d.ts +4 -0
- package/dist/src/skills.js +23 -0
- package/dist/src/store.d.ts +2 -0
- package/dist/src/store.js +18 -0
- package/dist/src/tools.d.ts +69 -0
- package/dist/src/tools.js +17 -1
- package/dist/src/traces.js +32 -4
- package/dist/src/types.d.ts +5 -0
- package/dist/testing/src/account.d.ts +1 -1
- package/dist/testing/src/account.js +368 -57
- package/dist/testing/src/chat.js +24 -16
- package/dist/testing/src/check.d.ts +4 -0
- package/dist/testing/src/check.js +90 -0
- package/dist/testing/src/cli.js +77 -19
- package/dist/testing/src/flow-login.d.ts +33 -0
- package/dist/testing/src/flow-login.js +30 -5
- package/dist/testing/src/new.d.ts +14 -5
- package/dist/testing/src/new.js +54 -20
- package/dist/testing/src/plugin.d.ts +4 -0
- package/dist/testing/src/plugin.js +116 -0
- package/docs/agents.md +87 -12
- package/examples/cafe/src/cli.ts +24 -0
- package/examples/cafe/src/mcp.ts +22 -0
- package/examples/cafe/src/tools.ts +13 -0
- package/flow-messaging/README.md +3 -1
- package/package.json +28 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Command as StricliCommand, CommandContext } from "@stricli/core";
|
|
2
|
+
export interface ToolCommand {
|
|
3
|
+
/** The tool this command calls. */
|
|
4
|
+
tool: string;
|
|
5
|
+
/** Input fields taken from positional arguments, in order (`order view 1042` → orderId). */
|
|
6
|
+
args?: string[];
|
|
7
|
+
/** For a list: the columns of the table people see. */
|
|
8
|
+
columns?: string[];
|
|
9
|
+
/** For one record: the fields people see, in order. */
|
|
10
|
+
view?: string[];
|
|
11
|
+
/** Another name for the command. */
|
|
12
|
+
alias?: string;
|
|
13
|
+
/** One line under the command in help (default: the tool's title). */
|
|
14
|
+
summary?: string;
|
|
15
|
+
examples?: string[];
|
|
16
|
+
}
|
|
17
|
+
/** What a command of the team's own gets as `this` (Stricli's context): the server's tools and the CLI's output. */
|
|
18
|
+
export interface CliContext extends CommandContext {
|
|
19
|
+
readonly cli: {
|
|
20
|
+
/** Call one of the server's tools; resolves with its result, throws its error. */
|
|
21
|
+
call(tool: string, input: Record<string, unknown>): Promise<unknown>;
|
|
22
|
+
/** Print a value as the CLI prints tool results (a table, fields, or JSON with --json). */
|
|
23
|
+
print(value: unknown, options?: {
|
|
24
|
+
json?: boolean;
|
|
25
|
+
fields?: string[];
|
|
26
|
+
columns?: string[];
|
|
27
|
+
}): void;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** A command of the team's own: any Stricli command (buildCommand from @stricli/core), one level down. */
|
|
31
|
+
export interface CustomCommand {
|
|
32
|
+
command: StricliCommand<CliContext>;
|
|
33
|
+
}
|
|
34
|
+
export type CommandConfig = ToolCommand | CustomCommand;
|
|
35
|
+
export interface CliConfig {
|
|
36
|
+
/** The command's name ("acme"): its help, its config folder (~/.config/<name>) and token variable. */
|
|
37
|
+
name: string;
|
|
38
|
+
/** The CLI client version sent to the server (default "1"). */
|
|
39
|
+
version?: string;
|
|
40
|
+
/** The organization's MCP endpoint. Else `--server` or <NAME>_SERVER. */
|
|
41
|
+
server?: string;
|
|
42
|
+
/** The agent terminal URL; default the server origin plus /agent/terminal. */
|
|
43
|
+
agent?: string;
|
|
44
|
+
/** The variable a token is read from for CI and agents (default <NAME>_TOKEN). */
|
|
45
|
+
tokenEnv?: string;
|
|
46
|
+
/** Override the local credential/conversation directory, e.g. for an isolated embedded session. */
|
|
47
|
+
directory?: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
/** Commands by their words ("order place"): the first words are groups. Tools no command names keep their own name. */
|
|
50
|
+
commands?: Record<string, CommandConfig>;
|
|
51
|
+
/** One line about each group, shown in help ("order": "Place and track orders"). Default: its subcommands. */
|
|
52
|
+
groups?: Record<string, string>;
|
|
53
|
+
/** A Client ID Metadata Document URL for sign-in; without it the CLI registers itself (DCR). */
|
|
54
|
+
clientMetadataUrl?: string;
|
|
55
|
+
/** A registered public OAuth client, for providers without dynamic registration. No client secret in a distributed CLI. */
|
|
56
|
+
clientId?: string;
|
|
57
|
+
/** A provider requiring an exact registered loopback port can fix it here; default chooses a free port. */
|
|
58
|
+
redirectPort?: number;
|
|
59
|
+
/** How a sign-in link is opened (default: printed for the person to open). */
|
|
60
|
+
openUrl?: (url: string) => void | Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
/** What the CLI knows of a tool, from the server's tools/list. */
|
|
63
|
+
export interface ToolInfo {
|
|
64
|
+
name: string;
|
|
65
|
+
title?: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
inputSchema: {
|
|
68
|
+
properties?: Record<string, JsonSchema>;
|
|
69
|
+
required?: string[];
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
};
|
|
72
|
+
outputSchema?: JsonSchema;
|
|
73
|
+
annotations?: {
|
|
74
|
+
readOnlyHint?: boolean;
|
|
75
|
+
destructiveHint?: boolean;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export interface JsonSchema {
|
|
79
|
+
type?: string | string[];
|
|
80
|
+
enum?: unknown[];
|
|
81
|
+
items?: JsonSchema;
|
|
82
|
+
properties?: Record<string, JsonSchema>;
|
|
83
|
+
description?: string;
|
|
84
|
+
[key: string]: unknown;
|
|
85
|
+
}
|
|
86
|
+
export interface Command {
|
|
87
|
+
words: string[];
|
|
88
|
+
summary: string;
|
|
89
|
+
tool?: ToolInfo;
|
|
90
|
+
config?: CommandConfig;
|
|
91
|
+
}
|
|
92
|
+
export declare function defineCli(config: CliConfig): CliConfig;
|
|
93
|
+
export declare function isCustom(config: CommandConfig): config is CustomCommand;
|
|
94
|
+
/** Words the CLI keeps for itself. */
|
|
95
|
+
export declare const BUILT_IN: Set<string>;
|
|
96
|
+
/** "place_order" → "place-order". */
|
|
97
|
+
export declare function derivedName(tool: string): string;
|
|
98
|
+
/** Every command: the CLI file's, then each tool it does not name, under the tool's own name. */
|
|
99
|
+
export declare function commandTable(config: CliConfig, tools: ToolInfo[]): Command[];
|
|
100
|
+
/** The command the words start with, the longest match first, and the words left after it. */
|
|
101
|
+
export declare function findCommand(commands: Command[], words: string[]): {
|
|
102
|
+
command: Command;
|
|
103
|
+
rest: string[];
|
|
104
|
+
} | null;
|
|
105
|
+
/** What is wrong in a CLI file against the server's tools, one sentence each; warnings apart. */
|
|
106
|
+
export declare function cliProblems(config: CliConfig, tools: ToolInfo[]): {
|
|
107
|
+
errors: string[];
|
|
108
|
+
warnings: string[];
|
|
109
|
+
};
|
|
110
|
+
/** The CLI's SKILL.md for coding agents, made from the tools and the CLI file, never written by hand. */
|
|
111
|
+
export declare function skillFile(config: CliConfig, tools: ToolInfo[]): string;
|
|
112
|
+
export declare function tokenEnvOf(config: CliConfig): string;
|
|
113
|
+
export declare function flagName(field: string): string;
|
|
114
|
+
/** One line per flag: its name, type and what it is for. */
|
|
115
|
+
export declare function flagList(tool: ToolInfo, args?: string[]): string[];
|
|
116
|
+
/** A short picture of a JSON value's shape: "list of {item, size, milk?, quantity?}". */
|
|
117
|
+
export declare function typeHint(s: JsonSchema): string;
|
|
118
|
+
export type FlagKind = "string" | "number" | "integer" | "boolean" | "list" | "json";
|
|
119
|
+
export declare function flagKind(s: JsonSchema): FlagKind;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// An organization's CLI commands: every tool of its MCP server is a command under its own name
|
|
2
|
+
// (place_order → `place-order`), and the CLI file the team's coding agent writes (defineCli) adds
|
|
3
|
+
// what needs judgement: command groups and verbs (`order place`, where `order` is a group whose
|
|
4
|
+
// help lists its subcommands), the positional arguments, a table's columns, examples, aliases, and
|
|
5
|
+
// commands of its own. cliProblems is the rule-based check that keeps that file true to the tools;
|
|
6
|
+
// it never judges taste.
|
|
7
|
+
export function defineCli(config) {
|
|
8
|
+
return config;
|
|
9
|
+
}
|
|
10
|
+
export function isCustom(config) {
|
|
11
|
+
return config.command !== undefined;
|
|
12
|
+
}
|
|
13
|
+
/** The MCP server's own way to read its skills: plumbing for hosts, not a command, unless the CLI file names it. */
|
|
14
|
+
const HOST_ONLY = new Set(["read_skill"]);
|
|
15
|
+
/** Words the CLI keeps for itself. */
|
|
16
|
+
export const BUILT_IN = new Set(["auth", "tools", "skill"]);
|
|
17
|
+
/** "place_order" → "place-order". */
|
|
18
|
+
export function derivedName(tool) {
|
|
19
|
+
return tool.replace(/_/g, "-");
|
|
20
|
+
}
|
|
21
|
+
/** Every command: the CLI file's, then each tool it does not name, under the tool's own name. */
|
|
22
|
+
export function commandTable(config, tools) {
|
|
23
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
24
|
+
const commands = [];
|
|
25
|
+
const named = new Set();
|
|
26
|
+
for (const [words, c] of Object.entries(config.commands ?? {})) {
|
|
27
|
+
const split = words.trim().split(/\s+/);
|
|
28
|
+
if (isCustom(c)) {
|
|
29
|
+
commands.push({ words: split, summary: "", config: c });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const tool = byName.get(c.tool);
|
|
33
|
+
if (!tool)
|
|
34
|
+
continue;
|
|
35
|
+
named.add(c.tool);
|
|
36
|
+
commands.push({ words: split, summary: c.summary ?? tool.title ?? tool.name, tool, config: c });
|
|
37
|
+
if (c.alias)
|
|
38
|
+
commands.push({ words: c.alias.trim().split(/\s+/), summary: `Same as ${words}`, tool, config: c });
|
|
39
|
+
}
|
|
40
|
+
for (const tool of tools) {
|
|
41
|
+
if (!named.has(tool.name) && !HOST_ONLY.has(tool.name))
|
|
42
|
+
commands.push({ words: [derivedName(tool.name)], summary: tool.title ?? tool.name, tool });
|
|
43
|
+
}
|
|
44
|
+
return commands;
|
|
45
|
+
}
|
|
46
|
+
/** The command the words start with, the longest match first, and the words left after it. */
|
|
47
|
+
export function findCommand(commands, words) {
|
|
48
|
+
let best = null;
|
|
49
|
+
for (const c of commands) {
|
|
50
|
+
if (c.words.length <= words.length && c.words.every((w, i) => w === words[i]) && (!best || c.words.length > best.words.length))
|
|
51
|
+
best = c;
|
|
52
|
+
}
|
|
53
|
+
return best ? { command: best, rest: words.slice(best.words.length) } : null;
|
|
54
|
+
}
|
|
55
|
+
/** Fields a list's rows or a record carry: the output schema's, or for an array, its items'. */
|
|
56
|
+
function outputFields(tool) {
|
|
57
|
+
const schema = tool.outputSchema;
|
|
58
|
+
if (!schema)
|
|
59
|
+
return null;
|
|
60
|
+
const props = schema.properties ?? schema.items?.properties;
|
|
61
|
+
if (!props)
|
|
62
|
+
return null;
|
|
63
|
+
const fields = new Set(Object.keys(props));
|
|
64
|
+
// A list wrapped in an object ({ orders: [...] }) shows its rows' fields too.
|
|
65
|
+
for (const p of Object.values(props))
|
|
66
|
+
for (const k of Object.keys(p.items?.properties ?? {}))
|
|
67
|
+
fields.add(k);
|
|
68
|
+
return fields;
|
|
69
|
+
}
|
|
70
|
+
/** What is wrong in a CLI file against the server's tools, one sentence each; warnings apart. */
|
|
71
|
+
export function cliProblems(config, tools) {
|
|
72
|
+
const errors = [];
|
|
73
|
+
const warnings = [];
|
|
74
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
75
|
+
const words = new Map();
|
|
76
|
+
const claim = (w, owner) => {
|
|
77
|
+
const key = w.trim().split(/\s+/).join(" ");
|
|
78
|
+
if (words.has(key))
|
|
79
|
+
errors.push(`command "${key}": both ${words.get(key)} and ${owner} use it`);
|
|
80
|
+
else
|
|
81
|
+
words.set(key, owner);
|
|
82
|
+
};
|
|
83
|
+
const named = new Set();
|
|
84
|
+
for (const [w, c] of Object.entries(config.commands ?? {})) {
|
|
85
|
+
claim(w, `"${w}"`);
|
|
86
|
+
if (isCustom(c))
|
|
87
|
+
continue;
|
|
88
|
+
if (c.alias)
|
|
89
|
+
claim(c.alias, `the alias of "${w}"`);
|
|
90
|
+
const tool = byName.get(c.tool);
|
|
91
|
+
if (!tool) {
|
|
92
|
+
errors.push(`command "${w}": no tool named ${c.tool} on the server`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
named.add(c.tool);
|
|
96
|
+
const input = new Set(Object.keys(tool.inputSchema.properties ?? {}));
|
|
97
|
+
for (const a of c.args ?? [])
|
|
98
|
+
if (!input.has(a))
|
|
99
|
+
errors.push(`command "${w}": args names ${a}, which ${c.tool}'s input does not have`);
|
|
100
|
+
const output = outputFields(tool);
|
|
101
|
+
for (const key of ["columns", "view"]) {
|
|
102
|
+
for (const f of c[key] ?? []) {
|
|
103
|
+
if (!output)
|
|
104
|
+
warnings.push(`command "${w}": ${key} names ${f}, but ${c.tool} declares no output to check it against`);
|
|
105
|
+
else if (!output.has(f.split(".")[0]))
|
|
106
|
+
errors.push(`command "${w}": ${key} names ${f}, which ${c.tool}'s output does not have`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const t of tools) {
|
|
111
|
+
if (named.has(t.name) || HOST_ONLY.has(t.name))
|
|
112
|
+
continue;
|
|
113
|
+
claim(derivedName(t.name), `tool ${t.name}`);
|
|
114
|
+
if (config.commands)
|
|
115
|
+
warnings.push(`tool ${t.name} has no command in the CLI file; it is reachable as "${derivedName(t.name)}"`);
|
|
116
|
+
}
|
|
117
|
+
for (const [key, owner] of words) {
|
|
118
|
+
const first = key.split(" ")[0];
|
|
119
|
+
if (BUILT_IN.has(first))
|
|
120
|
+
errors.push(`command "${key}": ${first} is the CLI's own (${[...BUILT_IN].join(", ")})`);
|
|
121
|
+
for (const other of words.keys()) {
|
|
122
|
+
if (other !== key && other.startsWith(`${key} `))
|
|
123
|
+
errors.push(`command "${key}" (${owner}): it is also the group of "${other}"; a word is a command or a group, not both`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { errors, warnings };
|
|
127
|
+
}
|
|
128
|
+
/** The CLI's SKILL.md for coding agents, made from the tools and the CLI file, never written by hand. */
|
|
129
|
+
export function skillFile(config, tools) {
|
|
130
|
+
const name = config.name;
|
|
131
|
+
const lines = [
|
|
132
|
+
"---",
|
|
133
|
+
`name: ${name}-cli`,
|
|
134
|
+
`description: Use the ${name} command line${config.description ? ` (${config.description})` : ""}. Load before running ${name} commands or when asked to do something in ${name} from a terminal.`,
|
|
135
|
+
"---",
|
|
136
|
+
"",
|
|
137
|
+
`# The ${name} command line`,
|
|
138
|
+
"",
|
|
139
|
+
`Run \`${name} <command> --json\` and read the JSON; never parse the human output. \`--fields a,b\` keeps only those fields.`,
|
|
140
|
+
"Commands that change things accept `--dry-run` (shows the call, sends nothing) and, when they may delete or overwrite, need `--yes` without a terminal.",
|
|
141
|
+
`Sign in once with \`${name} auth login\`, or set ${tokenEnvOf(config)} in CI.`,
|
|
142
|
+
"",
|
|
143
|
+
"Exit codes: 0 done, 1 the tool failed (its message on stderr), 2 wrong command or arguments, 3 sign-in needed, 4 needs --yes, 5 the server could not be reached.",
|
|
144
|
+
"",
|
|
145
|
+
"## Talking to the agent",
|
|
146
|
+
"",
|
|
147
|
+
`\`${name} chat "<message>" --json\` sends one message to the agent; \`${name} chat\` opens an interactive loop (Ctrl-D or /exit to leave). Both need sign-in.`,
|
|
148
|
+
"The CLI keeps its conversation reference in chat.json in its config folder; --new forgets that reference and preserves shared person history. chat without a message needs a terminal (otherwise exits 2).",
|
|
149
|
+
"",
|
|
150
|
+
"## Commands",
|
|
151
|
+
];
|
|
152
|
+
for (const c of commandTable(config, tools)) {
|
|
153
|
+
if (!c.tool)
|
|
154
|
+
continue;
|
|
155
|
+
lines.push("", `### ${name} ${c.words.join(" ")}`, "", c.summary);
|
|
156
|
+
if (c.tool) {
|
|
157
|
+
if (c.tool.description && c.tool.description !== c.summary)
|
|
158
|
+
lines.push("", c.tool.description);
|
|
159
|
+
const args = c.config && !isCustom(c.config) ? (c.config.args ?? []) : [];
|
|
160
|
+
const flags = flagList(c.tool, args);
|
|
161
|
+
if (args.length || flags.length)
|
|
162
|
+
lines.push("", `Usage: \`${name} ${c.words.join(" ")}${args.map((a) => ` <${a}>`).join("")}${flags.length ? " [flags]" : ""}\``);
|
|
163
|
+
for (const f of flags)
|
|
164
|
+
lines.push(`- ${f}`);
|
|
165
|
+
if (c.tool.annotations?.readOnlyHint !== true)
|
|
166
|
+
lines.push(`- Changes things${c.tool.annotations?.destructiveHint === false ? "" : " and may delete or overwrite: needs --yes"}.`);
|
|
167
|
+
if (c.tool.outputSchema)
|
|
168
|
+
lines.push("", "JSON output schema:", "", "```json", JSON.stringify(c.tool.outputSchema), "```");
|
|
169
|
+
}
|
|
170
|
+
for (const e of c.config && !isCustom(c.config) ? (c.config.examples ?? []) : [])
|
|
171
|
+
lines.push("", "```bash", e, "```");
|
|
172
|
+
}
|
|
173
|
+
return lines.join("\n") + "\n";
|
|
174
|
+
}
|
|
175
|
+
export function tokenEnvOf(config) {
|
|
176
|
+
return config.tokenEnv ?? `${config.name.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_TOKEN`;
|
|
177
|
+
}
|
|
178
|
+
export function flagName(field) {
|
|
179
|
+
return field.replace(/_/g, "-").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
180
|
+
}
|
|
181
|
+
/** One line per flag: its name, type and what it is for. */
|
|
182
|
+
export function flagList(tool, args = []) {
|
|
183
|
+
const required = new Set(tool.inputSchema.required ?? []);
|
|
184
|
+
return Object.entries(tool.inputSchema.properties ?? {})
|
|
185
|
+
.filter(([field]) => !args.includes(field))
|
|
186
|
+
.map(([field, s]) => {
|
|
187
|
+
const kind = s.enum ? s.enum.join("|") : flagKind(s) === "json" ? `JSON: ${typeHint(s)} (or @file.json)` : flagKind(s);
|
|
188
|
+
return `\`--${flagName(field)}\` ${kind}${required.has(field) ? ", required" : ""}${s.description ? `: ${s.description}` : ""}`;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/** A short picture of a JSON value's shape: "list of {item, size, milk?, quantity?}". */
|
|
192
|
+
export function typeHint(s) {
|
|
193
|
+
const type = Array.isArray(s.type) ? s.type.find((t) => t !== "null") : s.type;
|
|
194
|
+
if (type === "array")
|
|
195
|
+
return `list of ${s.items ? typeHint(s.items) : "values"}`;
|
|
196
|
+
if (type === "object" || s.properties) {
|
|
197
|
+
const required = new Set(s.required ?? []);
|
|
198
|
+
const keys = Object.keys(s.properties ?? {}).map((k) => (required.has(k) ? k : `${k}?`));
|
|
199
|
+
return keys.length ? `{${keys.join(", ")}}` : "object";
|
|
200
|
+
}
|
|
201
|
+
return s.enum ? s.enum.join("|") : (type ?? "value");
|
|
202
|
+
}
|
|
203
|
+
export function flagKind(s) {
|
|
204
|
+
const type = Array.isArray(s.type) ? s.type.find((t) => t !== "null") : s.type;
|
|
205
|
+
if (type === "array") {
|
|
206
|
+
const item = Array.isArray(s.items?.type) ? s.items?.type[0] : s.items?.type;
|
|
207
|
+
return item === "string" || item === "number" || item === "integer" ? "list" : "json";
|
|
208
|
+
}
|
|
209
|
+
if (type === "number" || type === "integer" || type === "boolean" || type === "string")
|
|
210
|
+
return type;
|
|
211
|
+
return s.enum ? "string" : "json";
|
|
212
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @flow-engineer/flow/cli: an organization's command line over its MCP server (the same tools, sign-in and
|
|
2
|
+
// gating as Claude or ChatGPT get). `defineCli` is the team's CLI file; `runCli` runs it. Needs
|
|
3
|
+
// @modelcontextprotocol/client and @stricli/core, optional peers of Flow Framework.
|
|
4
|
+
export { cliProblems, commandTable, defineCli, derivedName, skillFile } from "./commands.js";
|
|
5
|
+
export { EXIT, processIO, runCli } from "./run.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type CliConfig } from "./commands.ts";
|
|
2
|
+
export declare const EXIT: {
|
|
3
|
+
readonly ok: 0;
|
|
4
|
+
readonly failed: 1;
|
|
5
|
+
readonly usage: 2;
|
|
6
|
+
readonly signIn: 3;
|
|
7
|
+
readonly approval: 4;
|
|
8
|
+
readonly unreachable: 5;
|
|
9
|
+
};
|
|
10
|
+
export interface CliIO {
|
|
11
|
+
/** An embedded client's environment; omitted uses the local process environment. */
|
|
12
|
+
env?: Record<string, string | undefined>;
|
|
13
|
+
/** Cancels an embedded sign-in or terminal request when its session ends. */
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
/** Write to stdout as is. */
|
|
16
|
+
write(text: string): void;
|
|
17
|
+
/** Write to stderr as is. */
|
|
18
|
+
writeError(text: string): void;
|
|
19
|
+
/** Whether a person is at a terminal to answer a question. */
|
|
20
|
+
interactive: boolean;
|
|
21
|
+
/** Lines from an interactive terminal, with an optional input prompt; injectable for embedded CLIs and tests. */
|
|
22
|
+
lines?: (prompt?: string) => AsyncIterable<string>;
|
|
23
|
+
/** Ask a yes-or-no question (only when interactive). */
|
|
24
|
+
confirm(question: string): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
export declare function processIO(): CliIO;
|
|
27
|
+
/** Run the CLI once; resolves with its exit code. */
|
|
28
|
+
export declare function runCli(config: CliConfig, argv: string[], io?: CliIO): Promise<number>;
|