@pikku/core 0.12.4 → 0.12.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/CHANGELOG.md +11 -1
- package/dist/dev/hot-reload.d.ts +10 -0
- package/dist/dev/hot-reload.js +158 -0
- package/dist/middleware-runner.d.ts +1 -0
- package/dist/middleware-runner.js +5 -0
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.js +5 -0
- package/dist/services/in-memory-workflow-service.js +7 -11
- package/dist/wirings/ai-agent/ai-agent-prepare.js +16 -1
- package/dist/wirings/channel/channel-middleware-runner.d.ts +1 -0
- package/dist/wirings/channel/channel-middleware-runner.js +5 -0
- package/dist/wirings/workflow/pikku-workflow-service.js +7 -8
- package/package.json +3 -2
- package/src/crypto-utils.test.ts +214 -0
- package/src/crypto-utils.ts +213 -0
- package/src/dev/hot-reload.test.ts +484 -0
- package/src/dev/hot-reload.ts +212 -0
- package/src/errors/error-handler.ts +62 -0
- package/src/errors/error.test.ts +195 -0
- package/src/errors/errors.ts +374 -0
- package/src/errors/index.ts +2 -0
- package/src/factory-functions.test.ts +109 -0
- package/src/function/function-runner.test.ts +536 -0
- package/src/function/function-runner.ts +365 -0
- package/src/function/functions.types.ts +304 -0
- package/src/function/index.ts +5 -0
- package/src/handle-error.test.ts +424 -0
- package/src/handle-error.ts +69 -0
- package/src/index.ts +118 -0
- package/src/internal.ts +6 -0
- package/src/middleware/auth-apikey.test.ts +363 -0
- package/src/middleware/auth-apikey.ts +47 -0
- package/src/middleware/auth-bearer.test.ts +450 -0
- package/src/middleware/auth-bearer.ts +72 -0
- package/src/middleware/auth-cookie.test.ts +528 -0
- package/src/middleware/auth-cookie.ts +80 -0
- package/src/middleware/cors.test.ts +424 -0
- package/src/middleware/cors.ts +104 -0
- package/src/middleware/index.ts +5 -0
- package/src/middleware/remote-auth.test.ts +488 -0
- package/src/middleware/remote-auth.ts +68 -0
- package/src/middleware/timeout.ts +15 -0
- package/src/middleware-runner.test.ts +418 -0
- package/src/middleware-runner.ts +240 -0
- package/src/permissions.test.ts +434 -0
- package/src/permissions.ts +327 -0
- package/src/pikku-request.ts +23 -0
- package/src/pikku-response.ts +5 -0
- package/src/pikku-state.test.ts +224 -0
- package/src/pikku-state.ts +216 -0
- package/src/run-tests-script.test.ts +49 -0
- package/src/schema.test.ts +249 -0
- package/src/schema.ts +151 -0
- package/src/services/ai-agent-runner-service.ts +41 -0
- package/src/services/ai-run-state-service.ts +20 -0
- package/src/services/ai-storage-service.ts +39 -0
- package/src/services/content-service.ts +76 -0
- package/src/services/deployment-service.ts +22 -0
- package/src/services/gateway-service.ts +20 -0
- package/src/services/gopass-secrets.ts +98 -0
- package/src/services/in-memory-ai-run-state-service.ts +73 -0
- package/src/services/in-memory-trigger-service.ts +64 -0
- package/src/services/in-memory-workflow-service.test.ts +351 -0
- package/src/services/in-memory-workflow-service.ts +502 -0
- package/src/services/index.ts +56 -0
- package/src/services/jwt-service.ts +30 -0
- package/src/services/local-content.ts +120 -0
- package/src/services/local-gateway-service.ts +62 -0
- package/src/services/local-secrets.test.ts +80 -0
- package/src/services/local-secrets.ts +94 -0
- package/src/services/local-variables.test.ts +61 -0
- package/src/services/local-variables.ts +39 -0
- package/src/services/logger-console.test.ts +118 -0
- package/src/services/logger-console.ts +98 -0
- package/src/services/logger.ts +57 -0
- package/src/services/scheduler-service.ts +86 -0
- package/src/services/schema-service.ts +33 -0
- package/src/services/scoped-secret-service.test.ts +74 -0
- package/src/services/scoped-secret-service.ts +41 -0
- package/src/services/secret-service.ts +38 -0
- package/src/services/trigger-service.ts +17 -0
- package/src/services/typed-secret-service.test.ts +93 -0
- package/src/services/typed-secret-service.ts +70 -0
- package/src/services/typed-variables-service.test.ts +73 -0
- package/src/services/typed-variables-service.ts +81 -0
- package/src/services/user-session-service.test.ts +113 -0
- package/src/services/user-session-service.ts +86 -0
- package/src/services/variables-service.ts +11 -0
- package/src/services/workflow-service.ts +95 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/service-tests.ts +873 -0
- package/src/time-utils.test.ts +56 -0
- package/src/time-utils.ts +107 -0
- package/src/types/core.types.ts +478 -0
- package/src/types/state.types.ts +188 -0
- package/src/utils/hash.test.ts +68 -0
- package/src/utils/hash.ts +26 -0
- package/src/utils.test.ts +350 -0
- package/src/utils.ts +129 -0
- package/src/version.test.ts +80 -0
- package/src/version.ts +25 -0
- package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
- package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
- package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +310 -0
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
- package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +659 -0
- package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
- package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +319 -0
- package/src/wirings/ai-agent/ai-agent-runner.ts +773 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +859 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +1153 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +382 -0
- package/src/wirings/ai-agent/index.ts +37 -0
- package/src/wirings/channel/channel-common.ts +90 -0
- package/src/wirings/channel/channel-handler.ts +250 -0
- package/src/wirings/channel/channel-middleware-runner.ts +126 -0
- package/src/wirings/channel/channel-runner.ts +166 -0
- package/src/wirings/channel/channel-store.ts +29 -0
- package/src/wirings/channel/channel.types.ts +172 -0
- package/src/wirings/channel/define-channel-routes.ts +25 -0
- package/src/wirings/channel/eventhub-service.ts +34 -0
- package/src/wirings/channel/eventhub-store.ts +13 -0
- package/src/wirings/channel/index.ts +24 -0
- package/src/wirings/channel/local/index.ts +3 -0
- package/src/wirings/channel/local/local-channel-handler.ts +81 -0
- package/src/wirings/channel/local/local-channel-runner.test.ts +215 -0
- package/src/wirings/channel/local/local-channel-runner.ts +210 -0
- package/src/wirings/channel/local/local-eventhub-service.test.ts +95 -0
- package/src/wirings/channel/local/local-eventhub-service.ts +101 -0
- package/src/wirings/channel/log-channels.ts +20 -0
- package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +54 -0
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +45 -0
- package/src/wirings/channel/serverless/index.ts +5 -0
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +289 -0
- package/src/wirings/cli/channel/cli-channel-runner.ts +136 -0
- package/src/wirings/cli/channel/index.ts +1 -0
- package/src/wirings/cli/cli-runner.test.ts +403 -0
- package/src/wirings/cli/cli-runner.ts +529 -0
- package/src/wirings/cli/cli.types.ts +358 -0
- package/src/wirings/cli/command-parser.test.ts +445 -0
- package/src/wirings/cli/command-parser.ts +504 -0
- package/src/wirings/cli/define-cli-commands.ts +24 -0
- package/src/wirings/cli/index.ts +19 -0
- package/src/wirings/gateway/gateway-runner.test.ts +474 -0
- package/src/wirings/gateway/gateway-runner.ts +411 -0
- package/src/wirings/gateway/gateway.types.ts +149 -0
- package/src/wirings/gateway/index.ts +13 -0
- package/src/wirings/http/http-routes.test.ts +322 -0
- package/src/wirings/http/http-routes.ts +197 -0
- package/src/wirings/http/http-runner.test.ts +144 -0
- package/src/wirings/http/http-runner.ts +588 -0
- package/src/wirings/http/http.types.ts +369 -0
- package/src/wirings/http/index.ts +28 -0
- package/src/wirings/http/log-http-routes.ts +22 -0
- package/src/wirings/http/pikku-fetch-http-request.test.ts +237 -0
- package/src/wirings/http/pikku-fetch-http-request.ts +170 -0
- package/src/wirings/http/pikku-fetch-http-response.test.ts +82 -0
- package/src/wirings/http/pikku-fetch-http-response.ts +147 -0
- package/src/wirings/http/routers/http-router.ts +13 -0
- package/src/wirings/http/routers/path-to-regex.test.ts +319 -0
- package/src/wirings/http/routers/path-to-regex.ts +126 -0
- package/src/wirings/http/web-request.test.ts +236 -0
- package/src/wirings/http/web-request.ts +104 -0
- package/src/wirings/mcp/index.ts +27 -0
- package/src/wirings/mcp/mcp-endpoint-registry.test.ts +389 -0
- package/src/wirings/mcp/mcp-endpoint-registry.ts +159 -0
- package/src/wirings/mcp/mcp-runner.ts +323 -0
- package/src/wirings/mcp/mcp.types.ts +216 -0
- package/src/wirings/node/index.ts +2 -0
- package/src/wirings/node/node.types.ts +23 -0
- package/src/wirings/oauth2/index.ts +3 -0
- package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
- package/src/wirings/oauth2/oauth2-client.ts +335 -0
- package/src/wirings/oauth2/oauth2.types.ts +69 -0
- package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
- package/src/wirings/queue/index.ts +31 -0
- package/src/wirings/queue/queue-runner.test.ts +710 -0
- package/src/wirings/queue/queue-runner.ts +192 -0
- package/src/wirings/queue/queue.types.ts +189 -0
- package/src/wirings/queue/register-queue-helper.ts +60 -0
- package/src/wirings/queue/validate-worker-config.test.ts +108 -0
- package/src/wirings/queue/validate-worker-config.ts +116 -0
- package/src/wirings/rpc/index.ts +4 -0
- package/src/wirings/rpc/rpc-runner.ts +460 -0
- package/src/wirings/rpc/rpc-types.ts +56 -0
- package/src/wirings/rpc/wire-addon.ts +21 -0
- package/src/wirings/scheduler/index.ts +11 -0
- package/src/wirings/scheduler/log-schedulers.ts +20 -0
- package/src/wirings/scheduler/scheduler-runner.test.ts +660 -0
- package/src/wirings/scheduler/scheduler-runner.ts +133 -0
- package/src/wirings/scheduler/scheduler.types.ts +53 -0
- package/src/wirings/secret/index.ts +9 -0
- package/src/wirings/secret/secret.types.ts +32 -0
- package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
- package/src/wirings/secret/validate-secret-definitions.ts +82 -0
- package/src/wirings/trigger/index.ts +10 -0
- package/src/wirings/trigger/pikku-trigger-service.ts +112 -0
- package/src/wirings/trigger/trigger-runner.test.ts +79 -0
- package/src/wirings/trigger/trigger-runner.ts +135 -0
- package/src/wirings/trigger/trigger.types.ts +178 -0
- package/src/wirings/variable/index.ts +8 -0
- package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
- package/src/wirings/variable/validate-variable-definitions.ts +69 -0
- package/src/wirings/variable/variable.types.ts +22 -0
- package/src/wirings/workflow/dsl/index.ts +31 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
- package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
- package/src/wirings/workflow/graph/graph-node.ts +222 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +487 -0
- package/src/wirings/workflow/graph/graph-runner.ts +816 -0
- package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
- package/src/wirings/workflow/graph/graph-validation.ts +237 -0
- package/src/wirings/workflow/graph/index.ts +19 -0
- package/src/wirings/workflow/graph/template.test.ts +49 -0
- package/src/wirings/workflow/graph/template.ts +42 -0
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
- package/src/wirings/workflow/index.ts +82 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +200 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +1179 -0
- package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
- package/src/wirings/workflow/workflow-helpers.ts +79 -0
- package/src/wirings/workflow/workflow.types.ts +274 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/lcov.info +0 -18891
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CLIMeta,
|
|
3
|
+
CLIProgramMeta,
|
|
4
|
+
CLICommandMeta,
|
|
5
|
+
CLIPositional,
|
|
6
|
+
CLIOption,
|
|
7
|
+
} from './cli.types.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Result of parsing CLI arguments
|
|
11
|
+
*/
|
|
12
|
+
export interface ParsedCommand {
|
|
13
|
+
program: string
|
|
14
|
+
commandPath: string[]
|
|
15
|
+
positionals: Record<string, any>
|
|
16
|
+
options: Record<string, any>
|
|
17
|
+
errors: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parses raw CLI arguments into structured data for a specific program
|
|
22
|
+
*/
|
|
23
|
+
export function parseCLIArguments(
|
|
24
|
+
args: string[],
|
|
25
|
+
programName: string,
|
|
26
|
+
allMeta: CLIMeta
|
|
27
|
+
): ParsedCommand {
|
|
28
|
+
const result: ParsedCommand = {
|
|
29
|
+
program: programName,
|
|
30
|
+
commandPath: [],
|
|
31
|
+
positionals: {},
|
|
32
|
+
options: {},
|
|
33
|
+
errors: [],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const meta = allMeta.programs[programName]
|
|
37
|
+
if (!meta) {
|
|
38
|
+
result.errors.push(`Program not found: ${programName}`)
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let currentIndex = 0
|
|
43
|
+
let currentMeta = meta
|
|
44
|
+
|
|
45
|
+
// Parse command path (non-flag arguments at the beginning)
|
|
46
|
+
while (currentIndex < args.length && !args[currentIndex].startsWith('-')) {
|
|
47
|
+
const arg = args[currentIndex]
|
|
48
|
+
|
|
49
|
+
// Check if this is a known subcommand
|
|
50
|
+
if (currentMeta.commands && currentMeta.commands[arg]) {
|
|
51
|
+
result.commandPath.push(arg)
|
|
52
|
+
currentMeta = {
|
|
53
|
+
program: currentMeta.program,
|
|
54
|
+
commands: currentMeta.commands[arg].subcommands || {},
|
|
55
|
+
options: {
|
|
56
|
+
...currentMeta.options,
|
|
57
|
+
...currentMeta.commands[arg].options,
|
|
58
|
+
},
|
|
59
|
+
defaultRenderName:
|
|
60
|
+
currentMeta.commands[arg].renderName || currentMeta.defaultRenderName,
|
|
61
|
+
}
|
|
62
|
+
currentIndex++
|
|
63
|
+
} else {
|
|
64
|
+
// Not a subcommand, must be a positional argument
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If no command was parsed, check for a default command
|
|
70
|
+
// Only use default if user didn't provide any non-flag arguments
|
|
71
|
+
const hasNonFlagArgs = args.some(
|
|
72
|
+
(arg, idx) => idx >= currentIndex && !arg.startsWith('-')
|
|
73
|
+
)
|
|
74
|
+
if (result.commandPath.length === 0 && meta.commands && !hasNonFlagArgs) {
|
|
75
|
+
for (const [name, cmd] of Object.entries(meta.commands)) {
|
|
76
|
+
if (cmd.isDefault) {
|
|
77
|
+
result.commandPath.push(name)
|
|
78
|
+
currentMeta = {
|
|
79
|
+
program: currentMeta.program,
|
|
80
|
+
commands: cmd.subcommands || {},
|
|
81
|
+
options: {
|
|
82
|
+
...currentMeta.options,
|
|
83
|
+
...cmd.options,
|
|
84
|
+
},
|
|
85
|
+
defaultRenderName: cmd.renderName || currentMeta.defaultRenderName,
|
|
86
|
+
}
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Get the final command metadata
|
|
93
|
+
const commandMeta = getCommandMeta(meta, result.commandPath)
|
|
94
|
+
if (!commandMeta) {
|
|
95
|
+
// If we have no command path but there are non-flag args, the first arg is likely an unknown command
|
|
96
|
+
if (
|
|
97
|
+
result.commandPath.length === 0 &&
|
|
98
|
+
hasNonFlagArgs &&
|
|
99
|
+
args.length > 0 &&
|
|
100
|
+
!args[0].startsWith('-')
|
|
101
|
+
) {
|
|
102
|
+
result.errors.push(`Unknown command: ${args[0]}`)
|
|
103
|
+
} else {
|
|
104
|
+
result.errors.push(`Unknown command: ${result.commandPath.join(' ')}`)
|
|
105
|
+
}
|
|
106
|
+
return result
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Collect all available options (global + inherited)
|
|
110
|
+
const availableOptions = collectAvailableOptions(meta, result.commandPath)
|
|
111
|
+
|
|
112
|
+
// Parse remaining arguments as positionals and options
|
|
113
|
+
const positionalArgs: string[] = []
|
|
114
|
+
const optionArgs: Record<string, any> = {}
|
|
115
|
+
|
|
116
|
+
while (currentIndex < args.length) {
|
|
117
|
+
const arg = args[currentIndex]
|
|
118
|
+
|
|
119
|
+
if (arg.startsWith('--')) {
|
|
120
|
+
// Long option
|
|
121
|
+
const equalIndex = arg.indexOf('=')
|
|
122
|
+
if (equalIndex > 0) {
|
|
123
|
+
// --option=value format
|
|
124
|
+
const key = arg.slice(2, equalIndex)
|
|
125
|
+
const optionDef = availableOptions[key]
|
|
126
|
+
|
|
127
|
+
// Unknown options are allowed for forward compatibility
|
|
128
|
+
const value = arg.slice(equalIndex + 1)
|
|
129
|
+
optionArgs[key] = parseOptionValue(value, optionDef)
|
|
130
|
+
} else {
|
|
131
|
+
// --option value format
|
|
132
|
+
const key = arg.slice(2)
|
|
133
|
+
const optionDef = availableOptions[key]
|
|
134
|
+
|
|
135
|
+
// Unknown options are allowed for forward compatibility
|
|
136
|
+
if (optionDef && optionDef.array) {
|
|
137
|
+
// Array option - collect all following non-flag values
|
|
138
|
+
currentIndex++
|
|
139
|
+
const values: any[] = []
|
|
140
|
+
while (
|
|
141
|
+
currentIndex < args.length &&
|
|
142
|
+
!args[currentIndex].startsWith('-')
|
|
143
|
+
) {
|
|
144
|
+
values.push(parseOptionValue(args[currentIndex], optionDef))
|
|
145
|
+
currentIndex++
|
|
146
|
+
}
|
|
147
|
+
currentIndex-- // Back up one since we'll increment at loop end
|
|
148
|
+
optionArgs[key] = values
|
|
149
|
+
} else if (
|
|
150
|
+
currentIndex + 1 < args.length &&
|
|
151
|
+
!args[currentIndex + 1].startsWith('-')
|
|
152
|
+
) {
|
|
153
|
+
// Next arg is the value
|
|
154
|
+
currentIndex++
|
|
155
|
+
optionArgs[key] = parseOptionValue(args[currentIndex], optionDef)
|
|
156
|
+
} else {
|
|
157
|
+
// Boolean flag
|
|
158
|
+
optionArgs[key] = true
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} else if (arg.startsWith('-') && arg.length > 1) {
|
|
162
|
+
// Short option(s)
|
|
163
|
+
for (let i = 1; i < arg.length; i++) {
|
|
164
|
+
const shortFlag = arg[i]
|
|
165
|
+
|
|
166
|
+
// Find the corresponding long option
|
|
167
|
+
const longOption = findLongOption(shortFlag, availableOptions)
|
|
168
|
+
if (longOption) {
|
|
169
|
+
// Check if this is the last character and there's a value
|
|
170
|
+
if (
|
|
171
|
+
i === arg.length - 1 &&
|
|
172
|
+
currentIndex + 1 < args.length &&
|
|
173
|
+
!args[currentIndex + 1].startsWith('-')
|
|
174
|
+
) {
|
|
175
|
+
currentIndex++
|
|
176
|
+
optionArgs[longOption] = parseOptionValue(
|
|
177
|
+
args[currentIndex],
|
|
178
|
+
availableOptions[longOption]
|
|
179
|
+
)
|
|
180
|
+
} else {
|
|
181
|
+
// Boolean flag
|
|
182
|
+
optionArgs[longOption] = true
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
result.errors.push(`Unknown option: -${shortFlag}`)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
// Positional argument
|
|
190
|
+
positionalArgs.push(arg)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
currentIndex++
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Map positional arguments to named parameters
|
|
197
|
+
mapPositionalArguments(commandMeta.positionals, positionalArgs, result)
|
|
198
|
+
|
|
199
|
+
// Apply option defaults and validation
|
|
200
|
+
applyOptionDefaults(availableOptions, optionArgs, result)
|
|
201
|
+
|
|
202
|
+
result.options = optionArgs
|
|
203
|
+
|
|
204
|
+
return result
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Gets the command metadata for a given path
|
|
209
|
+
*/
|
|
210
|
+
function getCommandMeta(
|
|
211
|
+
meta: CLIProgramMeta,
|
|
212
|
+
path: string[]
|
|
213
|
+
): CLICommandMeta | null {
|
|
214
|
+
if (path.length === 0) {
|
|
215
|
+
return null
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let current = meta.commands[path[0]]
|
|
219
|
+
if (!current) {
|
|
220
|
+
return null
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (let i = 1; i < path.length; i++) {
|
|
224
|
+
if (!current.subcommands || !current.subcommands[path[i]]) {
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
227
|
+
current = current.subcommands[path[i]]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return current
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Collects all available options through the inheritance chain
|
|
235
|
+
*/
|
|
236
|
+
function collectAvailableOptions(
|
|
237
|
+
meta: CLIProgramMeta,
|
|
238
|
+
path: string[]
|
|
239
|
+
): Record<string, CLIOption> {
|
|
240
|
+
let options: Record<string, CLIOption> = { ...meta.options }
|
|
241
|
+
|
|
242
|
+
if (path.length === 0) {
|
|
243
|
+
return options
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Walk through the command path, merging options
|
|
247
|
+
let current = meta.commands[path[0]]
|
|
248
|
+
if (current) {
|
|
249
|
+
options = { ...options, ...current.options }
|
|
250
|
+
|
|
251
|
+
for (let i = 1; i < path.length; i++) {
|
|
252
|
+
if (current.subcommands && current.subcommands[path[i]]) {
|
|
253
|
+
current = current.subcommands[path[i]]
|
|
254
|
+
options = { ...options, ...current.options }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return options
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Finds the long option name for a short flag
|
|
264
|
+
*/
|
|
265
|
+
function findLongOption(
|
|
266
|
+
shortFlag: string,
|
|
267
|
+
options: Record<string, CLIOption>
|
|
268
|
+
): string | null {
|
|
269
|
+
for (const [name, option] of Object.entries(options)) {
|
|
270
|
+
if (option.short === shortFlag) {
|
|
271
|
+
return name
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Parses an option value based on its definition
|
|
279
|
+
*/
|
|
280
|
+
function parseOptionValue(value: string, optionDef?: CLIOption): any {
|
|
281
|
+
if (!optionDef) {
|
|
282
|
+
// No definition, try to infer type
|
|
283
|
+
if (value === 'true') return true
|
|
284
|
+
if (value === 'false') return false
|
|
285
|
+
if (/^\d+$/.test(value)) return parseInt(value, 10)
|
|
286
|
+
if (/^\d*\.\d+$/.test(value)) return parseFloat(value)
|
|
287
|
+
return value
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Use default type from option definition
|
|
291
|
+
const defaultValue = optionDef.default
|
|
292
|
+
if (typeof defaultValue === 'boolean') {
|
|
293
|
+
return value === 'true' || value === '1' || value === 'yes'
|
|
294
|
+
}
|
|
295
|
+
if (typeof defaultValue === 'number') {
|
|
296
|
+
return parseFloat(value)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Check choices
|
|
300
|
+
if (optionDef.choices && !optionDef.choices.includes(value as any)) {
|
|
301
|
+
// Invalid choice, but return anyway (validation will catch it)
|
|
302
|
+
return value
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return value
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Maps positional arguments to named parameters
|
|
310
|
+
*/
|
|
311
|
+
function mapPositionalArguments(
|
|
312
|
+
positionalDefs: CLIPositional[],
|
|
313
|
+
args: string[],
|
|
314
|
+
result: ParsedCommand
|
|
315
|
+
) {
|
|
316
|
+
let argIndex = 0
|
|
317
|
+
|
|
318
|
+
for (const def of positionalDefs) {
|
|
319
|
+
if (def.variadic) {
|
|
320
|
+
// Collect all remaining arguments
|
|
321
|
+
if (argIndex < args.length) {
|
|
322
|
+
result.positionals[def.name] = args.slice(argIndex)
|
|
323
|
+
argIndex = args.length
|
|
324
|
+
} else if (def.required) {
|
|
325
|
+
result.errors.push(`Missing required argument: ${def.name}`)
|
|
326
|
+
} else {
|
|
327
|
+
result.positionals[def.name] = []
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
// Single argument
|
|
331
|
+
if (argIndex < args.length) {
|
|
332
|
+
result.positionals[def.name] = args[argIndex]
|
|
333
|
+
argIndex++
|
|
334
|
+
} else if (def.required) {
|
|
335
|
+
result.errors.push(`Missing required argument: ${def.name}`)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Check for extra positional arguments
|
|
341
|
+
if (argIndex < args.length) {
|
|
342
|
+
result.errors.push(
|
|
343
|
+
`Unexpected arguments: ${args.slice(argIndex).join(' ')}`
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Applies option defaults and validates choices
|
|
350
|
+
*/
|
|
351
|
+
function applyOptionDefaults(
|
|
352
|
+
optionDefs: Record<string, CLIOption>,
|
|
353
|
+
options: Record<string, any>,
|
|
354
|
+
result: ParsedCommand
|
|
355
|
+
) {
|
|
356
|
+
for (const [name, def] of Object.entries(optionDefs)) {
|
|
357
|
+
// Apply default if not provided
|
|
358
|
+
if (!(name in options) && def.default !== undefined) {
|
|
359
|
+
options[name] = def.default
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Check required
|
|
363
|
+
if (def.required && !(name in options)) {
|
|
364
|
+
result.errors.push(`Missing required option: --${name}`)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Validate choices
|
|
368
|
+
if (def.choices && name in options) {
|
|
369
|
+
const value = options[name]
|
|
370
|
+
if (Array.isArray(value)) {
|
|
371
|
+
for (const v of value) {
|
|
372
|
+
if (!def.choices.includes(v)) {
|
|
373
|
+
result.errors.push(
|
|
374
|
+
`Invalid value for --${name}: ${v}. Valid choices: ${def.choices.join(', ')}`
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} else if (!def.choices.includes(value)) {
|
|
379
|
+
result.errors.push(
|
|
380
|
+
`Invalid value for --${name}: ${value}. Valid choices: ${def.choices.join(', ')}`
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Generates help text for a command in a specific program
|
|
389
|
+
*/
|
|
390
|
+
export function generateCommandHelp(
|
|
391
|
+
programName: string,
|
|
392
|
+
allMeta: CLIMeta,
|
|
393
|
+
commandPath: string[] = []
|
|
394
|
+
): string {
|
|
395
|
+
const lines: string[] = []
|
|
396
|
+
const meta = allMeta.programs[programName]
|
|
397
|
+
|
|
398
|
+
if (!meta) {
|
|
399
|
+
return `Program not found: ${programName}`
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (commandPath.length === 0) {
|
|
403
|
+
// Root help for the program
|
|
404
|
+
lines.push(`Usage: ${programName} <command> [options]`)
|
|
405
|
+
lines.push('')
|
|
406
|
+
lines.push('Commands:')
|
|
407
|
+
|
|
408
|
+
for (const [name, cmd] of Object.entries(meta.commands)) {
|
|
409
|
+
const desc = cmd.description || ''
|
|
410
|
+
const defaultMarker = cmd.isDefault ? ' (default)' : ''
|
|
411
|
+
lines.push(` ${name.padEnd(20)} ${desc}${defaultMarker}`)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (Object.keys(meta.options).length > 0) {
|
|
415
|
+
lines.push('')
|
|
416
|
+
lines.push('Options:')
|
|
417
|
+
formatOptions(meta.options, lines)
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
// Command-specific help
|
|
421
|
+
const commandMeta = getCommandMeta(meta, commandPath)
|
|
422
|
+
if (!commandMeta) {
|
|
423
|
+
return `Unknown command: ${commandPath.join(' ')}`
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Usage line
|
|
427
|
+
let usage = `${programName} ${commandPath.join(' ')}`
|
|
428
|
+
if (commandMeta.parameters) {
|
|
429
|
+
usage += ' ' + commandMeta.parameters
|
|
430
|
+
}
|
|
431
|
+
lines.push(`Usage: ${usage} [options]`)
|
|
432
|
+
|
|
433
|
+
if (commandMeta.description) {
|
|
434
|
+
lines.push('')
|
|
435
|
+
lines.push(commandMeta.description)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Positional arguments
|
|
439
|
+
if (commandMeta.positionals.length > 0) {
|
|
440
|
+
lines.push('')
|
|
441
|
+
lines.push('Arguments:')
|
|
442
|
+
for (const pos of commandMeta.positionals) {
|
|
443
|
+
const marker = pos.required ? '<required>' : '[optional]'
|
|
444
|
+
const variadic = pos.variadic ? '...' : ''
|
|
445
|
+
lines.push(` ${pos.name}${variadic} ${marker}`)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Subcommands
|
|
450
|
+
if (
|
|
451
|
+
commandMeta.subcommands &&
|
|
452
|
+
Object.keys(commandMeta.subcommands).length > 0
|
|
453
|
+
) {
|
|
454
|
+
lines.push('')
|
|
455
|
+
lines.push('Subcommands:')
|
|
456
|
+
for (const [name, sub] of Object.entries(commandMeta.subcommands)) {
|
|
457
|
+
const desc = sub.description || ''
|
|
458
|
+
lines.push(` ${name.padEnd(20)} ${desc}`)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Options
|
|
463
|
+
const availableOptions = collectAvailableOptions(meta, commandPath)
|
|
464
|
+
if (Object.keys(availableOptions).length > 0) {
|
|
465
|
+
lines.push('')
|
|
466
|
+
lines.push('Options:')
|
|
467
|
+
formatOptions(availableOptions, lines)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return lines.join('\n')
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Formats options for help text
|
|
476
|
+
*/
|
|
477
|
+
function formatOptions(options: Record<string, CLIOption>, lines: string[]) {
|
|
478
|
+
for (const [name, opt] of Object.entries(options)) {
|
|
479
|
+
let line = ' '
|
|
480
|
+
if (opt.short) {
|
|
481
|
+
line += `-${opt.short}, `
|
|
482
|
+
} else {
|
|
483
|
+
line += ' '
|
|
484
|
+
}
|
|
485
|
+
line += `--${name}`
|
|
486
|
+
|
|
487
|
+
if (opt.default !== undefined && typeof opt.default !== 'boolean') {
|
|
488
|
+
line += ` <value>`
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
line = line.padEnd(30)
|
|
492
|
+
line += opt.description || ''
|
|
493
|
+
|
|
494
|
+
if (opt.default !== undefined) {
|
|
495
|
+
line += ` (default: ${JSON.stringify(opt.default)})`
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (opt.choices) {
|
|
499
|
+
line += ` [choices: ${opt.choices.join(', ')}]`
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
lines.push(line)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CoreCLICommandConfig } from './cli.types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type-safe helper for defining CLI commands that can be composed.
|
|
5
|
+
* Returns the commands record as-is (identity function) for use with `wireCLI`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* export const chatCommands = defineCLICommands({
|
|
10
|
+
* chat: pikkuCLICommand({ func: chatFunc }),
|
|
11
|
+
* 'chat:history': pikkuCLICommand({ func: historyFunc }),
|
|
12
|
+
* })
|
|
13
|
+
*
|
|
14
|
+
* wireCLI({
|
|
15
|
+
* program: 'my-app',
|
|
16
|
+
* commands: { ...chatCommands, other: pikkuCLICommand({ func: otherFunc }) }
|
|
17
|
+
* })
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function defineCLICommands<
|
|
21
|
+
T extends Record<string, CoreCLICommandConfig<any, any, any, any>>,
|
|
22
|
+
>(commands: T): T {
|
|
23
|
+
return commands
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
wireCLI,
|
|
3
|
+
runCLICommand,
|
|
4
|
+
pikkuCLIRender,
|
|
5
|
+
executeCLI,
|
|
6
|
+
CLIError,
|
|
7
|
+
} from './cli-runner.js'
|
|
8
|
+
|
|
9
|
+
export { parseCLIArguments, generateCommandHelp } from './command-parser.js'
|
|
10
|
+
|
|
11
|
+
export { defineCLICommands } from './define-cli-commands.js'
|
|
12
|
+
export type {
|
|
13
|
+
CLIMeta,
|
|
14
|
+
CLICommandMeta,
|
|
15
|
+
CLIProgramMeta,
|
|
16
|
+
CoreCLI,
|
|
17
|
+
CoreCLICommandConfig,
|
|
18
|
+
CorePikkuCLIRender,
|
|
19
|
+
} from './cli.types.js'
|