@usestatemachines/cli 0.0.0-stage → 0.1.0

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.
Files changed (169) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -2
  3. package/dist/agents/bundled.d.ts +5 -0
  4. package/dist/agents/bundled.js +6 -0
  5. package/dist/agents/destinations.d.ts +13 -0
  6. package/dist/agents/destinations.js +16 -0
  7. package/dist/api-internal.d.ts +62 -0
  8. package/dist/api-internal.js +348 -0
  9. package/dist/bin.d.ts +2 -0
  10. package/dist/bin.js +4 -0
  11. package/dist/command.d.ts +119 -0
  12. package/dist/command.js +195 -0
  13. package/dist/commands/api/command.d.ts +15 -0
  14. package/dist/commands/api/command.js +38 -0
  15. package/dist/commands/api/run.d.ts +4 -0
  16. package/dist/commands/api/run.js +24 -0
  17. package/dist/commands/apps/command.d.ts +19 -0
  18. package/dist/commands/apps/command.js +29 -0
  19. package/dist/commands/apps/list.d.ts +8 -0
  20. package/dist/commands/apps/list.js +10 -0
  21. package/dist/commands/connect/command.d.ts +34 -0
  22. package/dist/commands/connect/command.js +70 -0
  23. package/dist/commands/connect/run.d.ts +10 -0
  24. package/dist/commands/connect/run.js +71 -0
  25. package/dist/commands/doctor/command.d.ts +29 -0
  26. package/dist/commands/doctor/command.js +36 -0
  27. package/dist/commands/doctor/run.d.ts +13 -0
  28. package/dist/commands/doctor/run.js +12 -0
  29. package/dist/commands/env/command.d.ts +5 -0
  30. package/dist/commands/env/command.js +19 -0
  31. package/dist/commands/env/pull.d.ts +6 -0
  32. package/dist/commands/env/pull.js +6 -0
  33. package/dist/commands/environments/command.d.ts +121 -0
  34. package/dist/commands/environments/command.js +170 -0
  35. package/dist/commands/environments/create.d.ts +9 -0
  36. package/dist/commands/environments/create.js +42 -0
  37. package/dist/commands/environments/delete.d.ts +7 -0
  38. package/dist/commands/environments/delete.js +48 -0
  39. package/dist/commands/environments/get.d.ts +39 -0
  40. package/dist/commands/environments/get.js +5 -0
  41. package/dist/commands/environments/list.d.ts +41 -0
  42. package/dist/commands/environments/list.js +18 -0
  43. package/dist/commands/environments/wait.d.ts +39 -0
  44. package/dist/commands/environments/wait.js +21 -0
  45. package/dist/commands/init/command.d.ts +68 -0
  46. package/dist/commands/init/command.js +90 -0
  47. package/dist/commands/init/run.d.ts +8 -0
  48. package/dist/commands/init/run.js +422 -0
  49. package/dist/commands/link/command.d.ts +13 -0
  50. package/dist/commands/link/command.js +32 -0
  51. package/dist/commands/link/run.d.ts +12 -0
  52. package/dist/commands/link/run.js +7 -0
  53. package/dist/commands/login/command.d.ts +1 -0
  54. package/dist/commands/login/command.js +43 -0
  55. package/dist/commands/login/run.d.ts +30 -0
  56. package/dist/commands/login/run.js +43 -0
  57. package/dist/commands/login/schema.d.ts +33 -0
  58. package/dist/commands/login/schema.js +33 -0
  59. package/dist/commands/logout/command.d.ts +1 -0
  60. package/dist/commands/logout/command.js +24 -0
  61. package/dist/commands/logout/run.d.ts +10 -0
  62. package/dist/commands/logout/run.js +19 -0
  63. package/dist/commands/logout/schema.d.ts +10 -0
  64. package/dist/commands/logout/schema.js +10 -0
  65. package/dist/commands/profiles/command.d.ts +2 -0
  66. package/dist/commands/profiles/command.js +29 -0
  67. package/dist/commands/profiles/list.d.ts +20 -0
  68. package/dist/commands/profiles/list.js +10 -0
  69. package/dist/commands/profiles/schema.d.ts +24 -0
  70. package/dist/commands/profiles/schema.js +19 -0
  71. package/dist/commands/profiles/use.d.ts +7 -0
  72. package/dist/commands/profiles/use.js +7 -0
  73. package/dist/commands/requests/command.d.ts +39 -0
  74. package/dist/commands/requests/command.js +65 -0
  75. package/dist/commands/requests/get.d.ts +32 -0
  76. package/dist/commands/requests/get.js +15 -0
  77. package/dist/commands/requests/list.d.ts +27 -0
  78. package/dist/commands/requests/list.js +25 -0
  79. package/dist/commands/snapshots/command.d.ts +48 -0
  80. package/dist/commands/snapshots/command.js +80 -0
  81. package/dist/commands/snapshots/create.d.ts +7 -0
  82. package/dist/commands/snapshots/create.js +26 -0
  83. package/dist/commands/snapshots/delete.d.ts +34 -0
  84. package/dist/commands/snapshots/delete.js +8 -0
  85. package/dist/commands/snapshots/get.d.ts +35 -0
  86. package/dist/commands/snapshots/get.js +5 -0
  87. package/dist/commands/snapshots/list.d.ts +37 -0
  88. package/dist/commands/snapshots/list.js +10 -0
  89. package/dist/commands/unlink/command.d.ts +1 -0
  90. package/dist/commands/unlink/command.js +15 -0
  91. package/dist/commands/unlink/run.d.ts +7 -0
  92. package/dist/commands/unlink/run.js +8 -0
  93. package/dist/commands/whoami/command.d.ts +28 -0
  94. package/dist/commands/whoami/command.js +44 -0
  95. package/dist/commands/whoami/run.d.ts +5 -0
  96. package/dist/commands/whoami/run.js +39 -0
  97. package/dist/commands/workspaces/command.d.ts +33 -0
  98. package/dist/commands/workspaces/command.js +73 -0
  99. package/dist/commands/workspaces/list.d.ts +5 -0
  100. package/dist/commands/workspaces/list.js +42 -0
  101. package/dist/commands/workspaces/use.d.ts +5 -0
  102. package/dist/commands/workspaces/use.js +18 -0
  103. package/dist/config/project.d.ts +21 -0
  104. package/dist/config/project.js +105 -0
  105. package/dist/config/schema.d.ts +616 -0
  106. package/dist/config/schema.js +282 -0
  107. package/dist/context-types.d.ts +89 -0
  108. package/dist/context-types.js +56 -0
  109. package/dist/context.d.ts +13 -0
  110. package/dist/context.js +290 -0
  111. package/dist/credentials/sealing.d.ts +10 -0
  112. package/dist/credentials/sealing.js +72 -0
  113. package/dist/credentials/store.d.ts +17 -0
  114. package/dist/credentials/store.js +156 -0
  115. package/dist/doctor.d.ts +4 -0
  116. package/dist/doctor.js +239 -0
  117. package/dist/env-file.d.ts +10 -0
  118. package/dist/env-file.js +254 -0
  119. package/dist/errors.d.ts +784 -0
  120. package/dist/errors.js +459 -0
  121. package/dist/flags.d.ts +22 -0
  122. package/dist/flags.js +56 -0
  123. package/dist/generated/internal.d.ts +138 -0
  124. package/dist/generated/internal.js +2627 -0
  125. package/dist/generated/retry.d.ts +1 -0
  126. package/dist/generated/retry.js +11 -0
  127. package/dist/help.d.ts +408 -0
  128. package/dist/help.js +220 -0
  129. package/dist/io-types.d.ts +127 -0
  130. package/dist/io-types.js +1 -0
  131. package/dist/io.d.ts +13 -0
  132. package/dist/io.js +1017 -0
  133. package/dist/keys.d.ts +50 -0
  134. package/dist/keys.js +691 -0
  135. package/dist/link.d.ts +6 -0
  136. package/dist/link.js +58 -0
  137. package/dist/login.d.ts +92 -0
  138. package/dist/login.js +965 -0
  139. package/dist/main.d.ts +2 -0
  140. package/dist/main.js +126 -0
  141. package/dist/native-helper.d.ts +1 -0
  142. package/dist/native-helper.js +2 -0
  143. package/dist/output.d.ts +8 -0
  144. package/dist/output.js +135 -0
  145. package/dist/parser.d.ts +28 -0
  146. package/dist/parser.js +250 -0
  147. package/dist/program-types.d.ts +67 -0
  148. package/dist/program-types.js +14 -0
  149. package/dist/programs.d.ts +2 -0
  150. package/dist/programs.js +44 -0
  151. package/dist/project-roots.d.ts +3 -0
  152. package/dist/project-roots.js +26 -0
  153. package/dist/prompt.d.ts +3 -0
  154. package/dist/prompt.js +61 -0
  155. package/dist/registry.d.ts +2 -0
  156. package/dist/registry.js +44 -0
  157. package/dist/resources/create.d.ts +9 -0
  158. package/dist/resources/create.js +55 -0
  159. package/dist/resources/read.d.ts +15 -0
  160. package/dist/resources/read.js +69 -0
  161. package/dist/resources/schema.d.ts +177 -0
  162. package/dist/resources/schema.js +158 -0
  163. package/dist/text.d.ts +3 -0
  164. package/dist/text.js +19 -0
  165. package/dist/update.d.ts +22 -0
  166. package/dist/update.js +126 -0
  167. package/dist/version.d.ts +2 -0
  168. package/dist/version.js +2 -0
  169. package/package.json +52 -3
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { ChildResult, Io } from './io-types.js';
2
+ export declare function main(argv: readonly string[], io: Io): Promise<number | ChildResult>;
package/dist/main.js ADDED
@@ -0,0 +1,126 @@
1
+ import { CliError } from './errors.js';
2
+ import { commandError, printError, printExecution, printResult, } from './output.js';
3
+ import { parseCommand } from './parser.js';
4
+ import { cliVersion, sdkVersion } from './version.js';
5
+ function presentationFor(io, globals, agent) {
6
+ const env = io.host.environment;
7
+ const interactive = !globals.noInput &&
8
+ env.STATEMACHINES_NO_INPUT !== '1' &&
9
+ env.CI === undefined &&
10
+ agent === null &&
11
+ io.host.terminal === 'both';
12
+ return {
13
+ mode: interactive ? 'interactive' : 'non-interactive',
14
+ format: globals.format ?? (interactive ? 'text' : 'json'),
15
+ stream: globals.stream,
16
+ };
17
+ }
18
+ export async function main(argv, io) {
19
+ let presentation = presentationFor(io, {
20
+ profile: null,
21
+ workspace: null,
22
+ format: null,
23
+ noInput: false,
24
+ yes: false,
25
+ stream: false,
26
+ }, null);
27
+ let session = null;
28
+ let errorsToStderr = false;
29
+ try {
30
+ const { commands } = await import('./registry.js');
31
+ const parsed = parseCommand(argv, commands);
32
+ errorsToStderr =
33
+ parsed.kind === 'run' || (parsed.kind === 'invalid' && parsed.stderr);
34
+ const env = io.host.environment;
35
+ const fastPath = parsed.kind !== 'command' && parsed.kind !== 'run';
36
+ const agent = !fastPath ||
37
+ (io.host.terminal === 'both' &&
38
+ !parsed.globals.noInput &&
39
+ env.STATEMACHINES_NO_INPUT !== '1' &&
40
+ env.CI === undefined)
41
+ ? await io.detectAgent()
42
+ : null;
43
+ presentation = presentationFor(io, parsed.globals, agent);
44
+ if (parsed.kind === 'invalid') {
45
+ throw parsed.error;
46
+ }
47
+ if (presentation.stream && presentation.format !== 'json') {
48
+ throw new CliError('cli.invalid_arguments', {
49
+ message: 'Use --json with --stream.',
50
+ });
51
+ }
52
+ if (parsed.kind === 'help') {
53
+ const { helpDocument, helpText } = await import('./help.js');
54
+ await printResult(io, presentation, helpDocument(commands, parsed.command, parsed.group), () => helpText(commands, parsed.command, parsed.group));
55
+ return 0;
56
+ }
57
+ if (parsed.kind === 'version') {
58
+ const result = {
59
+ cli: cliVersion,
60
+ sdk: sdkVersion,
61
+ node: io.host.nodeVersion,
62
+ };
63
+ await printResult(io, presentation, result, () => `CLI ${result.cli}\nSDK ${result.sdk}\nNode ${result.node}`);
64
+ return 0;
65
+ }
66
+ if (parsed.kind === 'completion') {
67
+ const { completionScript } = await import('./help.js');
68
+ await io.stdout.write(`${completionScript(commands, parsed.shell)}\n`);
69
+ return 0;
70
+ }
71
+ if (io.signal.aborted) {
72
+ throw new CliError('cli.cancelled');
73
+ }
74
+ if (parsed.kind === 'run') {
75
+ const { createContextSession } = await import('./context.js');
76
+ session = await createContextSession({
77
+ io,
78
+ globals: parsed.globals,
79
+ presentation,
80
+ version: cliVersion,
81
+ agent,
82
+ });
83
+ const context = await session.contexts.workspace();
84
+ return await context.programs.run({
85
+ command: parsed.command,
86
+ args: parsed.args,
87
+ });
88
+ }
89
+ if (parsed.command.help.destructive && !parsed.globals.yes) {
90
+ if (presentation.mode !== 'interactive') {
91
+ throw new CliError('cli.input_required', {
92
+ message: 'Add --yes to confirm this action.',
93
+ });
94
+ }
95
+ if (parsed.command.help.confirmation !== 'handler' &&
96
+ !(await io.prompt.confirm(`Continue with ${parsed.command.syntax.name.join(' ')}?`, { signal: io.signal, deadline: null }))) {
97
+ throw new CliError('cli.cancelled');
98
+ }
99
+ }
100
+ const { createContextSession } = await import('./context.js');
101
+ session = await createContextSession({
102
+ io,
103
+ globals: parsed.globals,
104
+ presentation,
105
+ version: cliVersion,
106
+ agent,
107
+ });
108
+ const exitCode = await printExecution(io, presentation, parsed.command.invoke(parsed.input, {
109
+ contexts: session.contexts,
110
+ presentation,
111
+ }));
112
+ if (!io.signal.aborted && presentation.mode === 'interactive') {
113
+ const { notifyUpdate } = await import('./update.js');
114
+ await notifyUpdate({ io, presentation, version: cliVersion, agent });
115
+ }
116
+ return exitCode;
117
+ }
118
+ catch (cause) {
119
+ const error = await commandError(cause, io.signal);
120
+ await printError(io, presentation, error, errorsToStderr);
121
+ return error.exitCode;
122
+ }
123
+ finally {
124
+ await session?.close();
125
+ }
126
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { runNativeHelper } from './io.js';
2
+ runNativeHelper();
@@ -0,0 +1,8 @@
1
+ import type { Printable } from './command.js';
2
+ import type { Presentation } from './context-types.js';
3
+ import { ApiError, CliError, type ExitCode } from './errors.js';
4
+ import type { Io } from './io-types.js';
5
+ export declare function printResult(io: Io, presentation: Presentation, result: unknown, text: () => string): Promise<void>;
6
+ export declare function commandError(error: unknown, signal?: AbortSignal): Promise<CliError | ApiError>;
7
+ export declare function printError(io: Io, presentation: Presentation, error: CliError | ApiError, stderr?: boolean): Promise<void>;
8
+ export declare function printExecution(io: Io, presentation: Presentation, events: AsyncIterable<Printable>): Promise<ExitCode>;
package/dist/output.js ADDED
@@ -0,0 +1,135 @@
1
+ import { ApiError, CliError, normalizeError } from './errors.js';
2
+ import { untrustedResult, untrustedText } from './text.js';
3
+ export async function printResult(io, presentation, result, text) {
4
+ await io.stdout.write(`${presentation.format === 'json' ? JSON.stringify(result) : text().replace(/[\r\n]+$/u, '')}\n`);
5
+ }
6
+ export async function commandError(error, signal) {
7
+ if (error instanceof CliError && error.exitCode === 130) {
8
+ return error;
9
+ }
10
+ if (signal?.aborted) {
11
+ return new CliError('cli.cancelled');
12
+ }
13
+ if (error instanceof CliError ||
14
+ error instanceof ApiError ||
15
+ (error instanceof Error && error.name === 'AbortError')) {
16
+ return normalizeError(error);
17
+ }
18
+ const { StateMachinesError, EnvironmentFailedError, SnapshotFailedError, UnexpectedStatusError, WaitTimeoutError, } = await import('@usestatemachines/sdk');
19
+ const seen = new Set();
20
+ let current = error;
21
+ while (current instanceof Error && !seen.has(current) && seen.size < 8) {
22
+ seen.add(current);
23
+ if (current instanceof CliError || current instanceof ApiError) {
24
+ return current;
25
+ }
26
+ if (current instanceof WaitTimeoutError) {
27
+ return new CliError('cli.wait_timeout', {
28
+ message: current.message,
29
+ details: {
30
+ id: current.resource?.id ?? null,
31
+ status: current.resource?.status ?? null,
32
+ timeoutMs: current.timeoutMs,
33
+ },
34
+ });
35
+ }
36
+ if (current instanceof EnvironmentFailedError) {
37
+ return new CliError('cli.resource_failed', {
38
+ message: current.message,
39
+ details: {
40
+ id: current.environment.id,
41
+ status: current.environment.status,
42
+ failure: current.environment.failure,
43
+ },
44
+ });
45
+ }
46
+ if (current instanceof SnapshotFailedError) {
47
+ return new CliError('cli.resource_failed', {
48
+ message: current.message,
49
+ details: {
50
+ id: current.snapshot.id,
51
+ status: current.snapshot.status,
52
+ failure: current.snapshot.failure,
53
+ },
54
+ });
55
+ }
56
+ if (current instanceof UnexpectedStatusError) {
57
+ return new CliError('cli.unexpected_status', {
58
+ message: current.message,
59
+ details: { id: current.resource.id, status: current.status },
60
+ });
61
+ }
62
+ if (current instanceof StateMachinesError && current.status !== null) {
63
+ return new ApiError({
64
+ status: current.status,
65
+ code: current.code,
66
+ message: current.message,
67
+ requestId: current.requestId,
68
+ details: current.fields,
69
+ });
70
+ }
71
+ current = current.cause;
72
+ }
73
+ if (error instanceof StateMachinesError) {
74
+ return new CliError('cli.network');
75
+ }
76
+ return normalizeError(error);
77
+ }
78
+ export async function printError(io, presentation, error, stderr = false) {
79
+ const destination = stderr || presentation.format === 'text' ? io.stderr : io.stdout;
80
+ const first = error.document.error.next[0];
81
+ const details = error.document.error.details;
82
+ if (presentation.format === 'json' &&
83
+ error.exitCode === 130 &&
84
+ details !== null &&
85
+ typeof details === 'object' &&
86
+ 'resource' in details &&
87
+ details.resource === 'environment' &&
88
+ 'id' in details &&
89
+ typeof details.id === 'string') {
90
+ await io.stderr.write(`Stopped waiting for environment ${untrustedText(details.id)}.${first === undefined ? '' : `\nCleanup: ${untrustedText(first.command)}`}\n`);
91
+ }
92
+ await destination.write(presentation.format === 'json'
93
+ ? `${JSON.stringify(error.document)}\n`
94
+ : `${untrustedText(error.message)}${first === undefined ? '' : `\nNext: ${untrustedText(first.command)} (${untrustedText(first.reason)})`}\n`);
95
+ }
96
+ export async function printExecution(io, presentation, events) {
97
+ let raw = false;
98
+ try {
99
+ for await (const event of events) {
100
+ switch (event.kind) {
101
+ case 'progress':
102
+ if (presentation.stream) {
103
+ await io.stdout.write(`${JSON.stringify(event.json)}\n`);
104
+ }
105
+ else if (presentation.mode === 'interactive' || event.always) {
106
+ await io.stderr.write(`${event.text?.() ?? JSON.stringify(untrustedResult(event.json))}\n`);
107
+ }
108
+ break;
109
+ case 'result':
110
+ await printResult(io, { ...presentation, format: event.format ?? presentation.format }, event.json, event.text);
111
+ return event.exitCode;
112
+ case 'literal':
113
+ raw = true;
114
+ await io.stdout.write(event.text.endsWith('\n') ? event.text : `${event.text}\n`);
115
+ return event.exitCode;
116
+ case 'raw':
117
+ raw = true;
118
+ for await (const chunk of event.response.body) {
119
+ await io.stdout.write(chunk);
120
+ }
121
+ return event.response.status >= 200 && event.response.status < 300
122
+ ? 0
123
+ : event.response.status === 401
124
+ ? 3
125
+ : 1;
126
+ }
127
+ }
128
+ throw new CliError('cli.invalid_result');
129
+ }
130
+ catch (cause) {
131
+ const error = await commandError(cause, io.signal);
132
+ await printError(io, presentation, error, raw);
133
+ return error.exitCode;
134
+ }
135
+ }
@@ -0,0 +1,28 @@
1
+ import type { CommandHelp, RegisteredCommand } from './command.js';
2
+ import { type Globals } from './context-types.js';
3
+ import { CliError } from './errors.js';
4
+ export type ParsedCommand = {
5
+ readonly globals: Globals;
6
+ } & ({
7
+ readonly kind: 'help';
8
+ readonly command: CommandHelp | null;
9
+ readonly group: string | null;
10
+ } | {
11
+ readonly kind: 'version';
12
+ } | {
13
+ readonly kind: 'invalid';
14
+ readonly error: CliError;
15
+ readonly stderr: boolean;
16
+ } | {
17
+ readonly kind: 'run';
18
+ readonly command: string;
19
+ readonly args: readonly string[];
20
+ } | {
21
+ readonly kind: 'completion';
22
+ readonly shell: 'bash' | 'zsh' | 'fish';
23
+ } | {
24
+ readonly kind: 'command';
25
+ readonly command: RegisteredCommand;
26
+ readonly input: Readonly<Record<string, unknown>>;
27
+ });
28
+ export declare function parseCommand(argv: readonly string[], commands: readonly RegisteredCommand[]): ParsedCommand;
package/dist/parser.js ADDED
@@ -0,0 +1,250 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { z } from 'zod';
3
+ import { profileNameSchema } from './context-types.js';
4
+ import { CliError } from './errors.js';
5
+ import { globalFlags } from './flags.js';
6
+ import { utilityCommands } from './help.js';
7
+ const globalsSchema = z.strictObject({
8
+ profile: profileNameSchema.optional(),
9
+ workspace: z.string().min(1).optional(),
10
+ format: z.enum(['text', 'json']).optional(),
11
+ json: z.boolean().optional(),
12
+ noInput: z.boolean().optional(),
13
+ yes: z.boolean().optional(),
14
+ stream: z.boolean().optional(),
15
+ help: z.boolean().optional(),
16
+ });
17
+ function invalid(message = 'Check the command arguments with statemachines help.') {
18
+ throw new CliError('cli.invalid_arguments', { message });
19
+ }
20
+ function flagNames(flag) {
21
+ return flag.type === 'boolean' && flag.negatedName !== null
22
+ ? [flag.name, flag.negatedName]
23
+ : [flag.name];
24
+ }
25
+ function tokenize(argv, options) {
26
+ try {
27
+ return parseArgs({
28
+ args: [...argv],
29
+ options,
30
+ strict: false,
31
+ allowPositionals: true,
32
+ tokens: true,
33
+ });
34
+ }
35
+ catch {
36
+ return invalid();
37
+ }
38
+ }
39
+ function firstCommand(argv) {
40
+ for (let index = 0; index < argv.length; index++) {
41
+ const word = argv[index];
42
+ if (word === undefined) {
43
+ return null;
44
+ }
45
+ if (word === '--') {
46
+ return argv[index + 1] ?? null;
47
+ }
48
+ if (!word.startsWith('-')) {
49
+ return word;
50
+ }
51
+ const name = word.slice(2).split('=')[0];
52
+ const flag = globalFlags.find((value) => value.name === name);
53
+ if (flag === undefined) {
54
+ return null;
55
+ }
56
+ if (flag.type === 'string' && !word.includes('=')) {
57
+ index++;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+ export function parseCommand(argv, commands) {
63
+ let globals = {
64
+ profile: null,
65
+ workspace: null,
66
+ format: null,
67
+ noInput: false,
68
+ yes: false,
69
+ stream: false,
70
+ };
71
+ try {
72
+ const flags = [
73
+ ...globalFlags,
74
+ ...commands.flatMap((command) => command.syntax.flags),
75
+ ];
76
+ const options = {};
77
+ for (const flag of flags) {
78
+ for (const name of flagNames(flag)) {
79
+ const option = {
80
+ type: flag.type === 'boolean' ? 'boolean' : 'string',
81
+ multiple: flag.type === 'string[]',
82
+ };
83
+ if (options[name] !== undefined && options[name].type !== option.type) {
84
+ throw new Error('Commands must agree on shared flag types.');
85
+ }
86
+ options[name] = {
87
+ ...option,
88
+ multiple: options[name]?.multiple || option.multiple,
89
+ ...(flag.short === undefined || name !== flag.name
90
+ ? {}
91
+ : { short: flag.short }),
92
+ };
93
+ }
94
+ }
95
+ const parsed = tokenize(argv, options);
96
+ for (const token of parsed.tokens) {
97
+ if (token.kind === 'option' &&
98
+ token.inlineValue === true &&
99
+ options[token.name]?.type === 'boolean') {
100
+ return invalid(`--${token.name} does not take a value.`);
101
+ }
102
+ }
103
+ const globalValues = {};
104
+ for (const flag of globalFlags) {
105
+ if (parsed.values[flag.name] !== undefined) {
106
+ globalValues[flag.key] = parsed.values[flag.name];
107
+ }
108
+ }
109
+ const validated = globalsSchema.safeParse(globalValues);
110
+ if (!validated.success) {
111
+ return invalid();
112
+ }
113
+ const values = validated.data;
114
+ if (values.json && values.format === 'text') {
115
+ return invalid('Use either --json or --format text.');
116
+ }
117
+ globals = {
118
+ profile: values.profile ?? null,
119
+ workspace: values.workspace ?? null,
120
+ format: values.json ? 'json' : (values.format ?? null),
121
+ noInput: values.noInput ?? false,
122
+ yes: values.yes ?? false,
123
+ stream: values.stream ?? false,
124
+ };
125
+ const positionals = parsed.positionals;
126
+ const help = values.help || positionals[0] === 'help' || positionals.length === 0;
127
+ const words = positionals[0] === 'help' ? positionals.slice(1) : positionals;
128
+ const command = commands.find((entry) => entry.syntax.name.every((word, index) => word === words[index]));
129
+ const selectedHelp = command?.help ??
130
+ utilityCommands.find((entry) => entry.name[0] === words[0]);
131
+ const allowedFlags = [...globalFlags, ...(command?.syntax.flags ?? [])];
132
+ const seen = new Set();
133
+ for (const token of parsed.tokens) {
134
+ if (token.kind !== 'option') {
135
+ continue;
136
+ }
137
+ const flag = allowedFlags.find((candidate) => flagNames(candidate).includes(token.name));
138
+ if (flag === undefined) {
139
+ return invalid();
140
+ }
141
+ if (flag.type !== 'string[]' && seen.has(flag.key)) {
142
+ return invalid('Specify each option only once.');
143
+ }
144
+ if (flag.type !== 'boolean' &&
145
+ !token.inlineValue &&
146
+ token.value?.startsWith('-')) {
147
+ return invalid(`--${flag.name} needs a value. To pass a value that starts with -, use --${flag.name}=<value>.`);
148
+ }
149
+ seen.add(flag.key);
150
+ }
151
+ if (globals.workspace !== null && !selectedHelp?.acceptsWorkspace) {
152
+ return invalid('This command does not accept --workspace.');
153
+ }
154
+ if (globals.stream && (!command?.help.streams || help)) {
155
+ return invalid('This command does not support --stream.');
156
+ }
157
+ const group = command === undefined &&
158
+ words.length === 1 &&
159
+ commands.some((entry) => entry.syntax.name.length > 1 && entry.syntax.name[0] === words[0])
160
+ ? (words[0] ?? null)
161
+ : null;
162
+ if (group !== null) {
163
+ return { kind: 'help', command: null, group, globals };
164
+ }
165
+ if (help) {
166
+ const target = command?.help ??
167
+ (words.length === 1
168
+ ? utilityCommands.find((entry) => entry.name[0] === words[0])
169
+ : undefined);
170
+ if (words.length !== 0 &&
171
+ (target === undefined || words.length !== target.name.length)) {
172
+ return invalid();
173
+ }
174
+ return { kind: 'help', command: target ?? null, group: null, globals };
175
+ }
176
+ if (words.length === 1 && words[0] === 'version') {
177
+ return { kind: 'version', globals };
178
+ }
179
+ if (words[0] === 'run') {
180
+ const delimiter = parsed.tokens.find((token) => token.kind === 'option-terminator');
181
+ if (delimiter === undefined || positionals[0] !== 'run') {
182
+ return invalid('Use statemachines run -- <command> [args].');
183
+ }
184
+ const child = argv[delimiter.index + 1];
185
+ if (child === undefined ||
186
+ child.length === 0 ||
187
+ parsed.tokens.filter((token) => token.kind === 'positional' && token.index < delimiter.index).length !== 1) {
188
+ return invalid('Use statemachines run -- <command> [args].');
189
+ }
190
+ return {
191
+ kind: 'run',
192
+ globals,
193
+ command: child,
194
+ args: argv.slice(delimiter.index + 2),
195
+ };
196
+ }
197
+ if (words[0] === 'completion') {
198
+ const shell = z.enum(['bash', 'zsh', 'fish']).safeParse(words[1]);
199
+ if (!shell.success || words.length !== 2) {
200
+ return invalid('Use completion bash, completion zsh, or completion fish.');
201
+ }
202
+ return { kind: 'completion', shell: shell.data, globals };
203
+ }
204
+ if (command === undefined) {
205
+ return invalid();
206
+ }
207
+ const input = {};
208
+ const args = words.slice(command.syntax.name.length);
209
+ let index = 0;
210
+ for (const argument of command.syntax.arguments) {
211
+ if (argument.many) {
212
+ if (args.length > index) {
213
+ input[argument.key] = args.slice(index);
214
+ }
215
+ index = args.length;
216
+ }
217
+ else if (args[index] !== undefined) {
218
+ input[argument.key] = args[index++];
219
+ }
220
+ }
221
+ if (index !== args.length) {
222
+ return invalid();
223
+ }
224
+ for (const flag of command.syntax.flags) {
225
+ const value = parsed.values[flag.name];
226
+ if (value !== undefined) {
227
+ input[flag.key] =
228
+ flag.type !== 'string[]' && Array.isArray(value) ? value[0] : value;
229
+ }
230
+ if (flag.type === 'boolean' &&
231
+ flag.negatedName !== null &&
232
+ parsed.values[flag.negatedName] === true) {
233
+ input[flag.key] = false;
234
+ }
235
+ }
236
+ command.validate(input);
237
+ return { kind: 'command', command, input, globals };
238
+ }
239
+ catch (error) {
240
+ if (error instanceof CliError) {
241
+ return {
242
+ kind: 'invalid',
243
+ globals,
244
+ error,
245
+ stderr: firstCommand(argv) === 'run',
246
+ };
247
+ }
248
+ throw error;
249
+ }
250
+ }
@@ -0,0 +1,67 @@
1
+ import { z } from 'zod';
2
+ import { type Account, type ApiOrigin, type WorkspaceId } from './context-types.js';
3
+ import type { ChildResult, EffectOptions, Io } from './io-types.js';
4
+ export declare const environmentTargetSchema: z.ZodObject<{
5
+ repositoryRoot: z.ZodString;
6
+ path: z.ZodString;
7
+ expectedManagedDigest: z.ZodString;
8
+ }, z.core.$strict>;
9
+ export type EnvironmentTarget = z.infer<typeof environmentTargetSchema>;
10
+ export declare const envPullResultSchema: z.ZodObject<{
11
+ path: z.ZodString;
12
+ workspaceId: z.core.$ZodBranded<z.ZodString, "WorkspaceId", "out">;
13
+ keyId: z.ZodString;
14
+ expiresAt: z.ZodString;
15
+ pendingRevocation: z.ZodBoolean;
16
+ }, z.core.$strict>;
17
+ export type EnvPullResult = z.infer<typeof envPullResultSchema>;
18
+ export interface EnvironmentFiles {
19
+ prepare(file: string, options: EffectOptions): Promise<EnvironmentTarget>;
20
+ write(target: EnvironmentTarget, credential: {
21
+ readonly key: string;
22
+ readonly origin: ApiOrigin;
23
+ }, options: EffectOptions, commit: () => Promise<void>): Promise<void>;
24
+ commitIfMatches(target: EnvironmentTarget, credential: {
25
+ readonly key: string;
26
+ readonly origin: ApiOrigin;
27
+ }, options: EffectOptions, commit: () => Promise<void>): Promise<boolean>;
28
+ }
29
+ export interface ProgramKeys {
30
+ withRunKey<Value>(input: {
31
+ readonly account: Extract<Account, {
32
+ kind: 'cli-token';
33
+ }>;
34
+ readonly workspace: WorkspaceId;
35
+ readonly consume: (key: string) => Promise<Value>;
36
+ }, options: EffectOptions): Promise<Value>;
37
+ pullEnvironment(input: {
38
+ readonly account: Extract<Account, {
39
+ kind: 'cli-token';
40
+ }>;
41
+ readonly workspace: WorkspaceId;
42
+ readonly target: EnvironmentTarget;
43
+ }, options: EffectOptions): Promise<EnvPullResult>;
44
+ }
45
+ export interface Programs {
46
+ run(input: {
47
+ readonly command: string;
48
+ readonly args: readonly string[];
49
+ }, options?: EffectOptions): Promise<ChildResult>;
50
+ pullEnvironment(file: string, options?: EffectOptions): Promise<EnvPullResult>;
51
+ }
52
+ export interface ProgramOptions {
53
+ readonly io: Io;
54
+ readonly origin: ApiOrigin;
55
+ readonly workspace: WorkspaceId;
56
+ readonly files: EnvironmentFiles;
57
+ readonly credential: {
58
+ readonly kind: 'workspace-key';
59
+ readonly key: string;
60
+ } | {
61
+ readonly kind: 'cli-token';
62
+ readonly account: Extract<Account, {
63
+ kind: 'cli-token';
64
+ }>;
65
+ readonly keys: ProgramKeys;
66
+ };
67
+ }
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod';
2
+ import { workspaceIdSchema, } from './context-types.js';
3
+ export const environmentTargetSchema = z.strictObject({
4
+ repositoryRoot: z.string().min(1),
5
+ path: z.string().min(1),
6
+ expectedManagedDigest: z.string().regex(/^[a-f0-9]{64}$/),
7
+ });
8
+ export const envPullResultSchema = z.strictObject({
9
+ path: z.string(),
10
+ workspaceId: workspaceIdSchema,
11
+ keyId: z.string(),
12
+ expiresAt: z.string(),
13
+ pendingRevocation: z.boolean(),
14
+ });
@@ -0,0 +1,2 @@
1
+ import type { ProgramOptions, Programs } from './program-types.js';
2
+ export declare function createPrograms(options: ProgramOptions): Programs;