specli 0.0.1 → 0.0.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 +76 -86
- package/cli.ts +4 -10
- package/package.json +8 -2
- package/src/cli/compile.ts +5 -28
- package/src/cli/derive-name.ts +2 -2
- package/src/cli/exec.ts +1 -1
- package/src/cli/main.ts +12 -27
- package/src/cli/runtime/auth/resolve.ts +10 -2
- package/src/cli/runtime/body-flags.ts +176 -0
- package/src/cli/runtime/execute.ts +41 -91
- package/src/cli/runtime/generated.ts +28 -93
- package/src/cli/runtime/profile/secrets.ts +1 -1
- package/src/cli/runtime/profile/store.ts +1 -1
- package/src/cli/runtime/request.ts +42 -152
- package/src/cli/stable-json.ts +2 -2
- package/src/compiled.ts +13 -15
- package/src/macros/env.ts +0 -4
- package/CLAUDE.md +0 -111
- package/PLAN.md +0 -274
- package/biome.jsonc +0 -1
- package/bun.lock +0 -98
- package/fixtures/openapi-array-items.json +0 -22
- package/fixtures/openapi-auth.json +0 -34
- package/fixtures/openapi-body.json +0 -41
- package/fixtures/openapi-collision.json +0 -21
- package/fixtures/openapi-oauth.json +0 -54
- package/fixtures/openapi-servers.json +0 -35
- package/fixtures/openapi.json +0 -87
- package/scripts/smoke-specs.ts +0 -64
- package/src/cli/auth-requirements.test.ts +0 -27
- package/src/cli/auth-schemes.test.ts +0 -66
- package/src/cli/capabilities.test.ts +0 -94
- package/src/cli/command-id.test.ts +0 -32
- package/src/cli/command-model.test.ts +0 -44
- package/src/cli/naming.test.ts +0 -86
- package/src/cli/operations.test.ts +0 -57
- package/src/cli/params.test.ts +0 -70
- package/src/cli/positional.test.ts +0 -65
- package/src/cli/request-body.test.ts +0 -35
- package/src/cli/runtime/request.test.ts +0 -153
- package/src/cli/server.test.ts +0 -35
- package/tsconfig.json +0 -29
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Body flag generation and parsing utilities.
|
|
3
|
+
*
|
|
4
|
+
* Generates CLI flags from JSON schema properties and parses
|
|
5
|
+
* dot-notation flags back into nested objects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type JsonSchema = {
|
|
9
|
+
type?: string;
|
|
10
|
+
properties?: Record<string, JsonSchema>;
|
|
11
|
+
items?: JsonSchema;
|
|
12
|
+
required?: string[];
|
|
13
|
+
description?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type BodyFlagDef = {
|
|
17
|
+
flag: string; // e.g. "--name" or "--address.street"
|
|
18
|
+
path: string[]; // e.g. ["name"] or ["address", "street"]
|
|
19
|
+
type: "string" | "number" | "integer" | "boolean";
|
|
20
|
+
description: string;
|
|
21
|
+
required: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate flag definitions from a JSON schema.
|
|
26
|
+
* Recursively handles nested objects using dot notation.
|
|
27
|
+
*/
|
|
28
|
+
export function generateBodyFlags(
|
|
29
|
+
schema: JsonSchema | undefined,
|
|
30
|
+
reservedFlags: Set<string>,
|
|
31
|
+
): BodyFlagDef[] {
|
|
32
|
+
if (!schema || schema.type !== "object" || !schema.properties) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const flags: BodyFlagDef[] = [];
|
|
37
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
38
|
+
|
|
39
|
+
collectFlags(schema.properties, [], requiredSet, flags, reservedFlags);
|
|
40
|
+
|
|
41
|
+
return flags;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function collectFlags(
|
|
45
|
+
properties: Record<string, JsonSchema>,
|
|
46
|
+
pathPrefix: string[],
|
|
47
|
+
requiredAtRoot: Set<string>,
|
|
48
|
+
out: BodyFlagDef[],
|
|
49
|
+
reservedFlags: Set<string>,
|
|
50
|
+
): void {
|
|
51
|
+
for (const [name, propSchema] of Object.entries(properties)) {
|
|
52
|
+
if (!name || typeof name !== "string") continue;
|
|
53
|
+
if (!propSchema || typeof propSchema !== "object") continue;
|
|
54
|
+
|
|
55
|
+
const path = [...pathPrefix, name];
|
|
56
|
+
const flagName = `--${path.join(".")}`;
|
|
57
|
+
|
|
58
|
+
// Skip if this flag would conflict with an operation parameter
|
|
59
|
+
if (reservedFlags.has(flagName)) continue;
|
|
60
|
+
|
|
61
|
+
const t = propSchema.type;
|
|
62
|
+
|
|
63
|
+
if (t === "object" && propSchema.properties) {
|
|
64
|
+
// Recurse into nested object
|
|
65
|
+
const nestedRequired = new Set(propSchema.required ?? []);
|
|
66
|
+
collectFlags(
|
|
67
|
+
propSchema.properties,
|
|
68
|
+
path,
|
|
69
|
+
nestedRequired,
|
|
70
|
+
out,
|
|
71
|
+
reservedFlags,
|
|
72
|
+
);
|
|
73
|
+
} else if (
|
|
74
|
+
t === "string" ||
|
|
75
|
+
t === "number" ||
|
|
76
|
+
t === "integer" ||
|
|
77
|
+
t === "boolean"
|
|
78
|
+
) {
|
|
79
|
+
// Leaf property - generate a flag
|
|
80
|
+
const isRequired =
|
|
81
|
+
pathPrefix.length === 0 ? requiredAtRoot.has(name) : false;
|
|
82
|
+
|
|
83
|
+
out.push({
|
|
84
|
+
flag: flagName,
|
|
85
|
+
path,
|
|
86
|
+
type: t,
|
|
87
|
+
description: propSchema.description ?? `Body field '${path.join(".")}'`,
|
|
88
|
+
required: isRequired,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// Skip arrays and other complex types for now
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Parse flag values with dot notation into a nested object.
|
|
97
|
+
*
|
|
98
|
+
* Example:
|
|
99
|
+
* { "address.street": "123 Main", "address.city": "NYC", "name": "Ada" }
|
|
100
|
+
* Becomes:
|
|
101
|
+
* { address: { street: "123 Main", city: "NYC" }, name: "Ada" }
|
|
102
|
+
*/
|
|
103
|
+
export function parseDotNotationFlags(
|
|
104
|
+
flagValues: Record<string, unknown>,
|
|
105
|
+
flagDefs: BodyFlagDef[],
|
|
106
|
+
): Record<string, unknown> {
|
|
107
|
+
const result: Record<string, unknown> = {};
|
|
108
|
+
|
|
109
|
+
for (const def of flagDefs) {
|
|
110
|
+
// Commander keeps dots in option names: --address.street -> "address.street"
|
|
111
|
+
const dotKey = def.path.join(".");
|
|
112
|
+
const value = flagValues[dotKey];
|
|
113
|
+
|
|
114
|
+
if (value === undefined) continue;
|
|
115
|
+
|
|
116
|
+
setNestedValue(result, def.path, value, def.type);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Set a value at a nested path, creating intermediate objects as needed.
|
|
124
|
+
*/
|
|
125
|
+
function setNestedValue(
|
|
126
|
+
obj: Record<string, unknown>,
|
|
127
|
+
path: string[],
|
|
128
|
+
value: unknown,
|
|
129
|
+
type: string,
|
|
130
|
+
): void {
|
|
131
|
+
let current = obj;
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
134
|
+
const key = path[i] as string;
|
|
135
|
+
if (!(key in current) || typeof current[key] !== "object") {
|
|
136
|
+
current[key] = {};
|
|
137
|
+
}
|
|
138
|
+
current = current[key] as Record<string, unknown>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const finalKey = path[path.length - 1] as string;
|
|
142
|
+
|
|
143
|
+
// Coerce value based on type
|
|
144
|
+
if (type === "boolean") {
|
|
145
|
+
current[finalKey] = true;
|
|
146
|
+
} else if (type === "integer") {
|
|
147
|
+
current[finalKey] = Number.parseInt(String(value), 10);
|
|
148
|
+
} else if (type === "number") {
|
|
149
|
+
current[finalKey] = Number(String(value));
|
|
150
|
+
} else {
|
|
151
|
+
current[finalKey] = String(value);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if all required fields are present.
|
|
157
|
+
* Returns list of missing field paths.
|
|
158
|
+
*/
|
|
159
|
+
export function findMissingRequired(
|
|
160
|
+
flagValues: Record<string, unknown>,
|
|
161
|
+
flagDefs: BodyFlagDef[],
|
|
162
|
+
): string[] {
|
|
163
|
+
const missing: string[] = [];
|
|
164
|
+
|
|
165
|
+
for (const def of flagDefs) {
|
|
166
|
+
if (!def.required) continue;
|
|
167
|
+
|
|
168
|
+
// Commander keeps dots in option names: --address.street -> "address.street"
|
|
169
|
+
const dotKey = def.path.join(".");
|
|
170
|
+
if (flagValues[dotKey] === undefined) {
|
|
171
|
+
missing.push(dotKey);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return missing;
|
|
176
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CommandAction } from "../command-model.ts";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { BodyFlagDef } from "./body-flags.ts";
|
|
4
|
+
import { buildRequest, type EmbeddedDefaults } from "./request.ts";
|
|
4
5
|
|
|
5
6
|
export type ExecuteInput = {
|
|
6
7
|
action: CommandAction;
|
|
@@ -10,6 +11,8 @@ export type ExecuteInput = {
|
|
|
10
11
|
servers: import("../server.ts").ServerInfo[];
|
|
11
12
|
authSchemes: import("../auth-schemes.ts").AuthScheme[];
|
|
12
13
|
specId: string;
|
|
14
|
+
embeddedDefaults?: EmbeddedDefaults;
|
|
15
|
+
bodyFlagDefs?: BodyFlagDef[];
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
export async function executeAction(input: ExecuteInput): Promise<void> {
|
|
@@ -22,109 +25,56 @@ export async function executeAction(input: ExecuteInput): Promise<void> {
|
|
|
22
25
|
globals: input.globals,
|
|
23
26
|
servers: input.servers,
|
|
24
27
|
authSchemes: input.authSchemes,
|
|
28
|
+
embeddedDefaults: input.embeddedDefaults,
|
|
29
|
+
bodyFlagDefs: input.bodyFlagDefs,
|
|
25
30
|
});
|
|
26
31
|
|
|
27
|
-
if (input.globals.curl
|
|
32
|
+
if (input.globals.curl) {
|
|
28
33
|
process.stdout.write(`${curl}\n`);
|
|
29
34
|
return;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
process.stdout.write(`${k}: ${v}\n`);
|
|
36
|
-
}
|
|
37
|
-
if (request.body) {
|
|
38
|
-
const text = await request.clone().text();
|
|
39
|
-
if (text) process.stdout.write(`\n${text}\n`);
|
|
40
|
-
}
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const timeoutMs = input.globals.timeout
|
|
45
|
-
? Number(input.globals.timeout)
|
|
46
|
-
: input.globals.ocTimeout
|
|
47
|
-
? Number(input.globals.ocTimeout)
|
|
48
|
-
: undefined;
|
|
49
|
-
let timeout: Timer | undefined;
|
|
50
|
-
let controller: AbortController | undefined;
|
|
51
|
-
if (timeoutMs && Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
|
52
|
-
controller = new AbortController();
|
|
53
|
-
timeout = setTimeout(() => controller?.abort(), timeoutMs);
|
|
54
|
-
}
|
|
37
|
+
const res = await fetch(request);
|
|
38
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
39
|
+
const status = res.status;
|
|
55
40
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const status = res.status;
|
|
41
|
+
const text = await res.text();
|
|
42
|
+
let body: unknown = text;
|
|
43
|
+
let parsedJson: unknown | undefined;
|
|
60
44
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
parsedJson = text ? JSON.parse(text) : null;
|
|
68
|
-
body = parsedJson;
|
|
69
|
-
} catch {
|
|
70
|
-
// keep as text
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!res.ok) {
|
|
75
|
-
if (input.globals.json) {
|
|
76
|
-
process.stdout.write(
|
|
77
|
-
`${JSON.stringify({
|
|
78
|
-
status,
|
|
79
|
-
body,
|
|
80
|
-
headers:
|
|
81
|
-
input.globals.headers || input.globals.ocHeaders
|
|
82
|
-
? Object.fromEntries(res.headers.entries())
|
|
83
|
-
: undefined,
|
|
84
|
-
})}\n`,
|
|
85
|
-
);
|
|
86
|
-
} else {
|
|
87
|
-
process.stderr.write(`HTTP ${status}\n`);
|
|
88
|
-
process.stderr.write(
|
|
89
|
-
`${typeof body === "string" ? body : JSON.stringify(body, null, 2)}\n`,
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
process.exitCode = 1;
|
|
93
|
-
return;
|
|
45
|
+
if (contentType.includes("json")) {
|
|
46
|
+
try {
|
|
47
|
+
parsedJson = text ? JSON.parse(text) : null;
|
|
48
|
+
body = parsedJson;
|
|
49
|
+
} catch {
|
|
50
|
+
// keep as text
|
|
94
51
|
}
|
|
52
|
+
}
|
|
95
53
|
|
|
54
|
+
if (!res.ok) {
|
|
96
55
|
if (input.globals.json) {
|
|
97
|
-
|
|
98
|
-
input.globals.status ||
|
|
99
|
-
input.globals.headers ||
|
|
100
|
-
input.globals.ocStatus ||
|
|
101
|
-
input.globals.ocHeaders
|
|
102
|
-
? {
|
|
103
|
-
status:
|
|
104
|
-
input.globals.status || input.globals.ocStatus
|
|
105
|
-
? status
|
|
106
|
-
: undefined,
|
|
107
|
-
headers:
|
|
108
|
-
input.globals.headers || input.globals.ocHeaders
|
|
109
|
-
? Object.fromEntries(res.headers.entries())
|
|
110
|
-
: undefined,
|
|
111
|
-
body,
|
|
112
|
-
}
|
|
113
|
-
: body;
|
|
114
|
-
|
|
115
|
-
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// default (human + agent readable)
|
|
120
|
-
if (typeof parsedJson !== "undefined") {
|
|
121
|
-
process.stdout.write(`${JSON.stringify(parsedJson, null, 2)}\n`);
|
|
56
|
+
process.stdout.write(`${JSON.stringify({ status, body })}\n`);
|
|
122
57
|
} else {
|
|
123
|
-
process.
|
|
124
|
-
|
|
58
|
+
process.stderr.write(`HTTP ${status}\n`);
|
|
59
|
+
process.stderr.write(
|
|
60
|
+
`${typeof body === "string" ? body : JSON.stringify(body, null, 2)}\n`,
|
|
61
|
+
);
|
|
125
62
|
}
|
|
126
|
-
|
|
127
|
-
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (input.globals.json) {
|
|
68
|
+
process.stdout.write(`${JSON.stringify(body)}\n`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// default (human + agent readable)
|
|
73
|
+
if (typeof parsedJson !== "undefined") {
|
|
74
|
+
process.stdout.write(`${JSON.stringify(parsedJson, null, 2)}\n`);
|
|
75
|
+
} else {
|
|
76
|
+
process.stdout.write(text);
|
|
77
|
+
if (!text.endsWith("\n")) process.stdout.write("\n");
|
|
128
78
|
}
|
|
129
79
|
} catch (err) {
|
|
130
80
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4,8 +4,9 @@ import type { AuthScheme } from "../auth-schemes.ts";
|
|
|
4
4
|
import type { CommandModel } from "../command-model.ts";
|
|
5
5
|
import type { ServerInfo } from "../server.ts";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { type BodyFlagDef, generateBodyFlags } from "./body-flags.ts";
|
|
8
8
|
import { executeAction } from "./execute.ts";
|
|
9
|
+
import type { EmbeddedDefaults } from "./request.ts";
|
|
9
10
|
import { coerceArrayInput, coerceValue } from "./validate/index.ts";
|
|
10
11
|
|
|
11
12
|
export type GeneratedCliContext = {
|
|
@@ -13,6 +14,7 @@ export type GeneratedCliContext = {
|
|
|
13
14
|
authSchemes: AuthScheme[];
|
|
14
15
|
commands: CommandModel;
|
|
15
16
|
specId: string;
|
|
17
|
+
embeddedDefaults?: EmbeddedDefaults;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
export function addGeneratedCommands(
|
|
@@ -47,7 +49,8 @@ export function addGeneratedCommands(
|
|
|
47
49
|
|
|
48
50
|
const isArray = flag.type === "array";
|
|
49
51
|
const itemType = flag.itemType ?? "string";
|
|
50
|
-
const
|
|
52
|
+
const flagType = isArray ? itemType : flag.type;
|
|
53
|
+
const parser = (raw: string) => coerceValue(raw, flagType);
|
|
51
54
|
|
|
52
55
|
if (isArray) {
|
|
53
56
|
const key = `${opt} <value>`;
|
|
@@ -74,95 +77,31 @@ export function addGeneratedCommands(
|
|
|
74
77
|
else cmd.option(key, desc, parser);
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
cmd
|
|
84
|
-
.option(
|
|
85
|
-
"--oc-header <header>",
|
|
86
|
-
"Extra header (repeatable)",
|
|
87
|
-
collectRepeatable,
|
|
88
|
-
)
|
|
89
|
-
.option("--oc-accept <type>", "Override Accept header")
|
|
90
|
-
.option("--oc-status", "Include status in --json output")
|
|
91
|
-
.option("--oc-headers", "Include headers in --json output")
|
|
92
|
-
.option("--oc-dry-run", "Print request without sending")
|
|
93
|
-
.option("--oc-curl", "Print curl command without sending")
|
|
94
|
-
.option("--oc-timeout <ms>", "Request timeout in milliseconds");
|
|
95
|
-
|
|
96
|
-
if (!reservedFlags.has("--header")) {
|
|
97
|
-
cmd.option(
|
|
98
|
-
"--header <header>",
|
|
99
|
-
"Extra header (repeatable)",
|
|
100
|
-
collectRepeatable,
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
if (!reservedFlags.has("--accept")) {
|
|
104
|
-
cmd.option("--accept <type>", "Override Accept header");
|
|
105
|
-
}
|
|
106
|
-
if (!reservedFlags.has("--status")) {
|
|
107
|
-
cmd.option("--status", "Include status in --json output");
|
|
108
|
-
}
|
|
109
|
-
if (!reservedFlags.has("--headers")) {
|
|
110
|
-
cmd.option("--headers", "Include headers in --json output");
|
|
111
|
-
}
|
|
112
|
-
if (!reservedFlags.has("--dry-run")) {
|
|
113
|
-
cmd.option("--dry-run", "Print request without sending");
|
|
114
|
-
}
|
|
115
|
-
if (!reservedFlags.has("--curl")) {
|
|
80
|
+
// Collect reserved flags: operation params + --curl
|
|
81
|
+
const operationFlags = new Set(action.flags.map((f) => f.flag));
|
|
82
|
+
const reservedFlags = new Set([...operationFlags, "--curl"]);
|
|
83
|
+
|
|
84
|
+
// Only --curl is a built-in flag (for debugging)
|
|
85
|
+
if (!operationFlags.has("--curl")) {
|
|
116
86
|
cmd.option("--curl", "Print curl command without sending");
|
|
117
87
|
}
|
|
118
|
-
if (!reservedFlags.has("--timeout")) {
|
|
119
|
-
cmd.option("--timeout <ms>", "Request timeout in milliseconds");
|
|
120
|
-
}
|
|
121
88
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.option("--oc-data <data>", "Inline request body")
|
|
125
|
-
.option("--oc-file <path>", "Request body from file")
|
|
126
|
-
.option(
|
|
127
|
-
"--oc-content-type <type>",
|
|
128
|
-
"Override Content-Type (defaults from OpenAPI)",
|
|
129
|
-
);
|
|
89
|
+
// Track body flag definitions for this action
|
|
90
|
+
let bodyFlagDefs: BodyFlagDef[] = [];
|
|
130
91
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
cmd.option(
|
|
139
|
-
"--content-type <type>",
|
|
140
|
-
"Override Content-Type (defaults from OpenAPI)",
|
|
141
|
-
);
|
|
142
|
-
}
|
|
92
|
+
if (action.requestBody) {
|
|
93
|
+
// Generate body flags from schema (recursive with dot notation)
|
|
94
|
+
// Pass reserved flags to avoid conflicts with operation params and --curl
|
|
95
|
+
bodyFlagDefs = generateBodyFlags(
|
|
96
|
+
action.requestBodySchema,
|
|
97
|
+
reservedFlags,
|
|
98
|
+
);
|
|
143
99
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (!propSchema || typeof propSchema !== "object") continue;
|
|
150
|
-
const t = (propSchema as { type?: unknown }).type;
|
|
151
|
-
if (
|
|
152
|
-
t !== "string" &&
|
|
153
|
-
t !== "number" &&
|
|
154
|
-
t !== "integer" &&
|
|
155
|
-
t !== "boolean"
|
|
156
|
-
) {
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const flagName = `--body-${name}`;
|
|
161
|
-
if (t === "boolean") {
|
|
162
|
-
cmd.option(flagName, `Body field '${name}'`);
|
|
163
|
-
} else {
|
|
164
|
-
cmd.option(`${flagName} <value>`, `Body field '${name}'`);
|
|
165
|
-
}
|
|
100
|
+
for (const def of bodyFlagDefs) {
|
|
101
|
+
if (def.type === "boolean") {
|
|
102
|
+
cmd.option(def.flag, def.description);
|
|
103
|
+
} else {
|
|
104
|
+
cmd.option(`${def.flag} <value>`, def.description);
|
|
166
105
|
}
|
|
167
106
|
}
|
|
168
107
|
}
|
|
@@ -179,20 +118,16 @@ export function addGeneratedCommands(
|
|
|
179
118
|
const globals = command.optsWithGlobals();
|
|
180
119
|
const local = command.opts();
|
|
181
120
|
|
|
182
|
-
const bodyFlags: Record<string, unknown> = {};
|
|
183
|
-
for (const key of Object.keys(local)) {
|
|
184
|
-
if (!key.startsWith("body")) continue;
|
|
185
|
-
bodyFlags[key] = local[key];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
121
|
await executeAction({
|
|
189
122
|
action,
|
|
190
123
|
positionalValues,
|
|
191
|
-
flagValues:
|
|
124
|
+
flagValues: local,
|
|
192
125
|
globals,
|
|
193
126
|
servers: context.servers,
|
|
194
127
|
authSchemes: context.authSchemes,
|
|
195
128
|
specId: context.specId,
|
|
129
|
+
embeddedDefaults: context.embeddedDefaults,
|
|
130
|
+
bodyFlagDefs,
|
|
196
131
|
});
|
|
197
132
|
});
|
|
198
133
|
}
|
|
@@ -17,7 +17,7 @@ function configDir(): string {
|
|
|
17
17
|
// Keep it simple (v1). We can move to env-paths later.
|
|
18
18
|
const home = process.env.HOME;
|
|
19
19
|
if (!home) throw new Error("Missing HOME env var");
|
|
20
|
-
return `${home}/.config/
|
|
20
|
+
return `${home}/.config/specli`;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function configPathJson(): string {
|