@orcalang/orca-lang 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 (183) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +128 -0
  3. package/dist/auth/index.d.ts +6 -0
  4. package/dist/auth/index.d.ts.map +1 -0
  5. package/dist/auth/index.js +6 -0
  6. package/dist/auth/index.js.map +1 -0
  7. package/dist/auth/lock.d.ts +2 -0
  8. package/dist/auth/lock.d.ts.map +1 -0
  9. package/dist/auth/lock.js +59 -0
  10. package/dist/auth/lock.js.map +1 -0
  11. package/dist/auth/providers/anthropic.d.ts +14 -0
  12. package/dist/auth/providers/anthropic.d.ts.map +1 -0
  13. package/dist/auth/providers/anthropic.js +145 -0
  14. package/dist/auth/providers/anthropic.js.map +1 -0
  15. package/dist/auth/providers/index.d.ts +3 -0
  16. package/dist/auth/providers/index.d.ts.map +1 -0
  17. package/dist/auth/providers/index.js +3 -0
  18. package/dist/auth/providers/index.js.map +1 -0
  19. package/dist/auth/providers/minimax.d.ts +6 -0
  20. package/dist/auth/providers/minimax.d.ts.map +1 -0
  21. package/dist/auth/providers/minimax.js +65 -0
  22. package/dist/auth/providers/minimax.js.map +1 -0
  23. package/dist/auth/refresh.d.ts +8 -0
  24. package/dist/auth/refresh.d.ts.map +1 -0
  25. package/dist/auth/refresh.js +104 -0
  26. package/dist/auth/refresh.js.map +1 -0
  27. package/dist/auth/store.d.ts +11 -0
  28. package/dist/auth/store.d.ts.map +1 -0
  29. package/dist/auth/store.js +63 -0
  30. package/dist/auth/store.js.map +1 -0
  31. package/dist/auth/types.d.ts +51 -0
  32. package/dist/auth/types.d.ts.map +1 -0
  33. package/dist/auth/types.js +2 -0
  34. package/dist/auth/types.js.map +1 -0
  35. package/dist/compiler/mermaid.d.ts +3 -0
  36. package/dist/compiler/mermaid.d.ts.map +1 -0
  37. package/dist/compiler/mermaid.js +86 -0
  38. package/dist/compiler/mermaid.js.map +1 -0
  39. package/dist/compiler/xstate.d.ts +15 -0
  40. package/dist/compiler/xstate.d.ts.map +1 -0
  41. package/dist/compiler/xstate.js +542 -0
  42. package/dist/compiler/xstate.js.map +1 -0
  43. package/dist/config/index.d.ts +3 -0
  44. package/dist/config/index.d.ts.map +1 -0
  45. package/dist/config/index.js +3 -0
  46. package/dist/config/index.js.map +1 -0
  47. package/dist/config/loader.d.ts +4 -0
  48. package/dist/config/loader.d.ts.map +1 -0
  49. package/dist/config/loader.js +109 -0
  50. package/dist/config/loader.js.map +1 -0
  51. package/dist/config/types.d.ts +13 -0
  52. package/dist/config/types.d.ts.map +1 -0
  53. package/dist/config/types.js +8 -0
  54. package/dist/config/types.js.map +1 -0
  55. package/dist/generators/index.d.ts +5 -0
  56. package/dist/generators/index.d.ts.map +1 -0
  57. package/dist/generators/index.js +5 -0
  58. package/dist/generators/index.js.map +1 -0
  59. package/dist/generators/registry.d.ts +12 -0
  60. package/dist/generators/registry.d.ts.map +1 -0
  61. package/dist/generators/registry.js +15 -0
  62. package/dist/generators/registry.js.map +1 -0
  63. package/dist/generators/typescript.d.ts +9 -0
  64. package/dist/generators/typescript.d.ts.map +1 -0
  65. package/dist/generators/typescript.js +55 -0
  66. package/dist/generators/typescript.js.map +1 -0
  67. package/dist/index.d.ts +10 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +630 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/llm/anthropic.d.ts +14 -0
  72. package/dist/llm/anthropic.d.ts.map +1 -0
  73. package/dist/llm/anthropic.js +87 -0
  74. package/dist/llm/anthropic.js.map +1 -0
  75. package/dist/llm/grok.d.ts +13 -0
  76. package/dist/llm/grok.d.ts.map +1 -0
  77. package/dist/llm/grok.js +60 -0
  78. package/dist/llm/grok.js.map +1 -0
  79. package/dist/llm/index.d.ts +11 -0
  80. package/dist/llm/index.d.ts.map +1 -0
  81. package/dist/llm/index.js +23 -0
  82. package/dist/llm/index.js.map +1 -0
  83. package/dist/llm/ollama.d.ts +11 -0
  84. package/dist/llm/ollama.d.ts.map +1 -0
  85. package/dist/llm/ollama.js +51 -0
  86. package/dist/llm/ollama.js.map +1 -0
  87. package/dist/llm/openai.d.ts +13 -0
  88. package/dist/llm/openai.d.ts.map +1 -0
  89. package/dist/llm/openai.js +61 -0
  90. package/dist/llm/openai.js.map +1 -0
  91. package/dist/llm/provider.d.ts +32 -0
  92. package/dist/llm/provider.d.ts.map +1 -0
  93. package/dist/llm/provider.js +2 -0
  94. package/dist/llm/provider.js.map +1 -0
  95. package/dist/parser/ast-to-markdown.d.ts +3 -0
  96. package/dist/parser/ast-to-markdown.d.ts.map +1 -0
  97. package/dist/parser/ast-to-markdown.js +209 -0
  98. package/dist/parser/ast-to-markdown.js.map +1 -0
  99. package/dist/parser/ast.d.ts +183 -0
  100. package/dist/parser/ast.d.ts.map +1 -0
  101. package/dist/parser/ast.js +3 -0
  102. package/dist/parser/ast.js.map +1 -0
  103. package/dist/parser/markdown-parser.d.ts +8 -0
  104. package/dist/parser/markdown-parser.d.ts.map +1 -0
  105. package/dist/parser/markdown-parser.js +838 -0
  106. package/dist/parser/markdown-parser.js.map +1 -0
  107. package/dist/runtime/effects.d.ts +17 -0
  108. package/dist/runtime/effects.d.ts.map +1 -0
  109. package/dist/runtime/effects.js +28 -0
  110. package/dist/runtime/effects.js.map +1 -0
  111. package/dist/runtime/machine.d.ts +8 -0
  112. package/dist/runtime/machine.d.ts.map +1 -0
  113. package/dist/runtime/machine.js +158 -0
  114. package/dist/runtime/machine.js.map +1 -0
  115. package/dist/runtime/types.d.ts +37 -0
  116. package/dist/runtime/types.d.ts.map +1 -0
  117. package/dist/runtime/types.js +3 -0
  118. package/dist/runtime/types.js.map +1 -0
  119. package/dist/skills.d.ts +114 -0
  120. package/dist/skills.d.ts.map +1 -0
  121. package/dist/skills.js +1103 -0
  122. package/dist/skills.js.map +1 -0
  123. package/dist/tools.d.ts +18 -0
  124. package/dist/tools.d.ts.map +1 -0
  125. package/dist/tools.js +124 -0
  126. package/dist/tools.js.map +1 -0
  127. package/dist/verifier/completeness.d.ts +4 -0
  128. package/dist/verifier/completeness.d.ts.map +1 -0
  129. package/dist/verifier/completeness.js +82 -0
  130. package/dist/verifier/completeness.js.map +1 -0
  131. package/dist/verifier/determinism.d.ts +17 -0
  132. package/dist/verifier/determinism.d.ts.map +1 -0
  133. package/dist/verifier/determinism.js +301 -0
  134. package/dist/verifier/determinism.js.map +1 -0
  135. package/dist/verifier/properties.d.ts +6 -0
  136. package/dist/verifier/properties.d.ts.map +1 -0
  137. package/dist/verifier/properties.js +404 -0
  138. package/dist/verifier/properties.js.map +1 -0
  139. package/dist/verifier/structural.d.ts +50 -0
  140. package/dist/verifier/structural.d.ts.map +1 -0
  141. package/dist/verifier/structural.js +692 -0
  142. package/dist/verifier/structural.js.map +1 -0
  143. package/dist/verifier/types.d.ts +40 -0
  144. package/dist/verifier/types.d.ts.map +1 -0
  145. package/dist/verifier/types.js +2 -0
  146. package/dist/verifier/types.js.map +1 -0
  147. package/package.json +49 -0
  148. package/src/auth/index.ts +5 -0
  149. package/src/auth/lock.ts +71 -0
  150. package/src/auth/providers/anthropic.ts +192 -0
  151. package/src/auth/providers/index.ts +17 -0
  152. package/src/auth/providers/minimax.ts +100 -0
  153. package/src/auth/refresh.ts +138 -0
  154. package/src/auth/store.ts +75 -0
  155. package/src/auth/types.ts +62 -0
  156. package/src/compiler/mermaid.ts +109 -0
  157. package/src/compiler/xstate.ts +615 -0
  158. package/src/config/index.ts +2 -0
  159. package/src/config/loader.ts +122 -0
  160. package/src/config/types.ts +21 -0
  161. package/src/generators/index.ts +6 -0
  162. package/src/generators/registry.ts +27 -0
  163. package/src/generators/typescript.ts +67 -0
  164. package/src/index.ts +671 -0
  165. package/src/llm/anthropic.ts +102 -0
  166. package/src/llm/grok.ts +73 -0
  167. package/src/llm/index.ts +29 -0
  168. package/src/llm/ollama.ts +62 -0
  169. package/src/llm/openai.ts +74 -0
  170. package/src/llm/provider.ts +35 -0
  171. package/src/parser/ast-to-markdown.ts +220 -0
  172. package/src/parser/ast.ts +236 -0
  173. package/src/parser/markdown-parser.ts +844 -0
  174. package/src/runtime/effects.ts +48 -0
  175. package/src/runtime/machine.ts +201 -0
  176. package/src/runtime/types.ts +44 -0
  177. package/src/skills.ts +1339 -0
  178. package/src/tools.ts +144 -0
  179. package/src/verifier/completeness.ts +89 -0
  180. package/src/verifier/determinism.ts +328 -0
  181. package/src/verifier/properties.ts +507 -0
  182. package/src/verifier/structural.ts +803 -0
  183. package/src/verifier/types.ts +45 -0
package/src/index.ts ADDED
@@ -0,0 +1,671 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
4
+ import { join, basename } from 'path';
5
+ import { setAuthProfile, deleteAuthProfile, listAuthProfiles, getAuthProfile } from './auth/store.js';
6
+ import { getValidAccessToken } from './auth/refresh.js';
7
+ import {
8
+ buildAuthorizationUrl as buildAnthropicUrl,
9
+ getDeviceCode,
10
+ pollForToken,
11
+ promptForCode,
12
+ openBrowser,
13
+ anthropicOAuthProvider,
14
+ } from './auth/providers/anthropic.js';
15
+ import {
16
+ buildAuthorizationUrl as buildMiniMaxUrl,
17
+ minimaxOAuthProvider,
18
+ } from './auth/providers/minimax.js';
19
+ import { parseMarkdown } from './parser/markdown-parser.js';
20
+ import { machineToMarkdown } from './parser/ast-to-markdown.js';
21
+ import { checkStructural, analyzeFile } from './verifier/structural.js';
22
+ import { checkCompleteness } from './verifier/completeness.js';
23
+ import { checkDeterminism } from './verifier/determinism.js';
24
+ import { checkProperties } from './verifier/properties.js';
25
+ import { compileToXState, compileToXStateMachine } from './compiler/xstate.js';
26
+ import { compileToMermaid } from './compiler/mermaid.js';
27
+ import { verifySkill, compileSkill, generateActionsSkill, refineSkill, generateOrcaSkill, generateOrcaMultiSkill, parseSkill, type SkillInput } from './skills.js';
28
+ import { ORCA_TOOLS } from './tools.js';
29
+ import { createOrcaMachine } from './runtime/machine.js';
30
+ import type { OrcaMachine, OrcaMachineOptions, OrcaState } from './runtime/types.js';
31
+
32
+ // Re-export for use as a library
33
+ export { parseMarkdown } from './parser/markdown-parser.js';
34
+ export { machineToMarkdown } from './parser/ast-to-markdown.js';
35
+ export { compileToXState, compileToXStateMachine } from './compiler/xstate.js';
36
+ export { compileToMermaid } from './compiler/mermaid.js';
37
+ export { checkProperties } from './verifier/properties.js';
38
+ export { createOrcaMachine };
39
+ export type { OrcaMachine, OrcaMachineOptions, OrcaState, EffectHandlers, EffectResult, Effect } from './runtime/types.js';
40
+
41
+ import type { MachineDef } from './parser/ast.js';
42
+
43
+ // ── Stdin helpers ─────────────────────────────────────────────────────────────
44
+
45
+ async function readStdin(): Promise<string> {
46
+ const chunks: Buffer[] = [];
47
+ for await (const chunk of process.stdin) {
48
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string));
49
+ }
50
+ return Buffer.concat(chunks).toString('utf-8');
51
+ }
52
+
53
+ /** Resolve a SkillInput from either a file path or stdin. Pass `-` as fileArg to force stdin. */
54
+ async function getInput(fileArg: string | undefined, useStdin: boolean): Promise<SkillInput> {
55
+ if (useStdin || fileArg === '-') return { source: await readStdin() };
56
+ if (fileArg) return { file: fileArg };
57
+ throw new Error('No input file specified. Provide a file path or use --stdin.');
58
+ }
59
+
60
+ /** Read raw source from a SkillInput (used by functions that need the source string directly). */
61
+ function sourceFromInput(input: SkillInput): string {
62
+ if (input.source !== undefined) return input.source;
63
+ if (input.file !== undefined) return readFileSync(input.file, 'utf-8');
64
+ throw new Error('SkillInput requires either source or file');
65
+ }
66
+
67
+ /** Parse an Orca machine definition file (markdown format) */
68
+ function parseFile(filePath: string, source: string): MachineDef {
69
+ const { file } = parseMarkdown(source);
70
+ if (file.machines.length > 1) {
71
+ throw new Error(`File ${filePath} contains multiple machines. Use a command that supports multi-machine files.`);
72
+ }
73
+ return file.machines[0];
74
+ }
75
+
76
+ async function login(provider: string, profileId: string = 'default'): Promise<void> {
77
+ console.log(`Logging in to ${provider}...`);
78
+
79
+ try {
80
+ if (provider === 'anthropic') {
81
+ // Use device code flow for simpler CLI experience
82
+ const deviceCodes = await getDeviceCode();
83
+ console.log(`Please visit: ${deviceCodes.verification_uri}`);
84
+ console.log(`And enter code: ${deviceCodes.user_code}`);
85
+ await openBrowser(deviceCodes.verification_uri);
86
+
87
+ const tokens = await pollForToken(deviceCodes.device_code);
88
+
89
+ setAuthProfile(profileId, {
90
+ mode: 'oauth',
91
+ provider: 'anthropic',
92
+ access: tokens.access,
93
+ refresh: tokens.refresh ?? '',
94
+ expires: tokens.expires,
95
+ });
96
+
97
+ console.log('Successfully logged in!');
98
+ } else if (provider === 'minimax') {
99
+ // Build authorization URL
100
+ const authUrl = buildMiniMaxUrl('orca-cli', 'http://localhost:9999/callback');
101
+ console.log(`Please visit: ${authUrl}`);
102
+ await openBrowser(authUrl);
103
+
104
+ console.log('After authorizing, you will be redirected to a callback URL.');
105
+ console.log('Copy the authorization code from the URL and paste it here:');
106
+ const code = await promptForCode();
107
+
108
+ // Note: In a full implementation, we'd exchange the code for tokens
109
+ // For now, this is a placeholder
110
+ console.log('MiniMax OAuth flow requires server callback - this is a placeholder.');
111
+ } else {
112
+ console.error(`Unsupported provider: ${provider}`);
113
+ process.exit(1);
114
+ }
115
+ } catch (err) {
116
+ console.error(`Login failed: ${err instanceof Error ? err.message : String(err)}`);
117
+ process.exit(1);
118
+ }
119
+ }
120
+
121
+ async function logout(profileId?: string): Promise<void> {
122
+ const profiles = profileId ? [profileId] : listAuthProfiles();
123
+
124
+ if (profiles.length === 0) {
125
+ console.log('No auth profiles found.');
126
+ return;
127
+ }
128
+
129
+ for (const id of profiles) {
130
+ if (deleteAuthProfile(id)) {
131
+ console.log(`Logged out: ${id}`);
132
+ }
133
+ }
134
+ }
135
+
136
+ async function auth(showDoctor: boolean = false): Promise<void> {
137
+ const profiles = listAuthProfiles();
138
+
139
+ if (profiles.length === 0) {
140
+ console.log('No auth profiles configured.');
141
+ console.log('Run "orca login" to set up authentication.');
142
+ return;
143
+ }
144
+
145
+ console.log('Auth profiles:');
146
+ for (const profileId of profiles) {
147
+ const profile = getAuthProfile(profileId);
148
+ if (!profile) continue;
149
+
150
+ const status = profile.mode === 'oauth' && profile.expires
151
+ ? (Date.now() < profile.expires ? 'valid' : 'expired')
152
+ : 'valid';
153
+
154
+ console.log(` ${profileId}:`);
155
+ console.log(` provider: ${profile.provider}`);
156
+ console.log(` mode: ${profile.mode}`);
157
+ console.log(` status: ${status}`);
158
+ if (profile.email) {
159
+ console.log(` email: ${profile.email}`);
160
+ }
161
+ }
162
+
163
+ // Try to get a valid token for the default profile
164
+ const defaultCreds = await getValidAccessToken('default');
165
+ if (defaultCreds) {
166
+ console.log('\nDefault profile is ready to use.');
167
+ } else if (profiles.includes('default')) {
168
+ console.log('\nDefault profile needs re-authentication. Run "orca login".');
169
+ }
170
+ }
171
+
172
+ function formatErrors(errors: { code: string; message: string; severity: string; suggestion?: string }[]): void {
173
+ for (const err of errors) {
174
+ const prefix = err.severity === 'error' ? 'ERROR' : 'WARN';
175
+ console.log(`[${prefix}] ${err.code}: ${err.message}`);
176
+ if (err.suggestion) {
177
+ console.log(` Suggestion: ${err.suggestion}`);
178
+ }
179
+ }
180
+ }
181
+
182
+ function stripCodeFence(code: string): string {
183
+ return code
184
+ .replace(/^```typescript\n/, '')
185
+ .replace(/^```\n/, '')
186
+ .replace(/\n```$/, '')
187
+ .trim();
188
+ }
189
+
190
+ async function verify(input: SkillInput, json: boolean = false): Promise<void> {
191
+ if (json) {
192
+ const result = await verifySkill(input);
193
+ console.log(JSON.stringify(result, null, 2));
194
+ process.exit(result.status === 'valid' ? 0 : 1);
195
+ }
196
+
197
+ const label = input.file ?? '<stdin>';
198
+ console.log(`Verifying ${label}...`);
199
+ const source = sourceFromInput(input);
200
+ const { file } = parseMarkdown(source);
201
+
202
+ if (file.machines.length > 1) {
203
+ // Multi-machine verification
204
+ const fileAnalysis = analyzeFile(file);
205
+ console.log(`Parsed ${file.machines.length} machines:`);
206
+ for (const machine of file.machines) {
207
+ console.log(` - ${machine.name}`);
208
+ }
209
+
210
+ const allErrors = [...fileAnalysis.errors];
211
+ const allWarnings = [...fileAnalysis.warnings];
212
+
213
+ if (allErrors.length > 0) {
214
+ const errorCount = allErrors.filter(e => e.severity === 'error').length;
215
+ const warningCount = allErrors.filter(e => e.severity === 'warning').length + allWarnings.length;
216
+ console.log(`\nFound ${errorCount} error(s)${warningCount > 0 ? ` and ${warningCount} warning(s)` : ''}:`);
217
+ formatErrors([...allErrors, ...allWarnings]);
218
+ process.exit(1);
219
+ } else {
220
+ console.log('\nVerification passed!');
221
+ }
222
+ return;
223
+ }
224
+
225
+ // Single-machine verification
226
+ const machine = file.machines[0];
227
+ console.log(`Parsed machine: ${machine.name}`);
228
+ console.log(` States: ${machine.states.length}`);
229
+ console.log(` Events: ${machine.events.length}`);
230
+ console.log(` Transitions: ${machine.transitions.length}`);
231
+
232
+ const structural = checkStructural(machine);
233
+ const completeness = checkCompleteness(machine);
234
+ const determinism = checkDeterminism(machine);
235
+ const properties = checkProperties(machine);
236
+
237
+ const allErrors = [
238
+ ...structural.errors,
239
+ ...completeness.errors,
240
+ ...determinism.errors,
241
+ ...properties.errors,
242
+ ];
243
+
244
+ if (allErrors.length > 0) {
245
+ const errorCount = allErrors.filter(e => e.severity === 'error').length;
246
+ const warningCount = allErrors.filter(e => e.severity === 'warning').length;
247
+ if (errorCount > 0) {
248
+ console.log(`\nFound ${errorCount} error(s)${warningCount > 0 ? ` and ${warningCount} warning(s)` : ''}:`);
249
+ } else {
250
+ console.log(`\nFound ${warningCount} warning(s):`);
251
+ }
252
+ formatErrors(allErrors);
253
+ if (errorCount > 0) process.exit(1);
254
+ } else {
255
+ const propCount = machine.properties?.length ?? 0;
256
+ if (propCount > 0) {
257
+ console.log(`\nVerification passed! (${propCount} properties checked)`);
258
+ } else {
259
+ console.log('\nVerification passed!');
260
+ }
261
+ }
262
+ }
263
+
264
+ async function compileXState(input: SkillInput, json: boolean = false): Promise<void> {
265
+ if (json) {
266
+ const result = await compileSkill(input, 'xstate');
267
+ console.log(JSON.stringify(result, null, 2));
268
+ return;
269
+ }
270
+ const source = sourceFromInput(input);
271
+ const { file } = parseMarkdown(source);
272
+ if (file.machines.length > 1) {
273
+ console.error('Multi-machine XState compilation not yet fully implemented. Compiling first machine only.');
274
+ }
275
+ const machine = file.machines[0];
276
+ const output = compileToXState(machine);
277
+ console.log(output);
278
+ }
279
+
280
+ async function compileMermaid(input: SkillInput, json: boolean = false): Promise<void> {
281
+ if (json) {
282
+ const result = await compileSkill(input, 'mermaid');
283
+ console.log(JSON.stringify(result, null, 2));
284
+ return;
285
+ }
286
+ const source = sourceFromInput(input);
287
+ const machine = parseFile(input.file ?? '<stdin>', source);
288
+ const output = compileToMermaid(machine);
289
+ console.log(output);
290
+ }
291
+
292
+ async function visualize(input: SkillInput): Promise<void> {
293
+ const source = sourceFromInput(input);
294
+ const machine = parseFile(input.file ?? '<stdin>', source);
295
+ const mermaid = compileToMermaid(machine);
296
+ console.log('Mermaid diagram:');
297
+ console.log(mermaid);
298
+ console.log('\nYou can render this at: https://mermaid.live');
299
+ }
300
+
301
+ function langExt(language: string): { src: string; test: string; testSuffix: string } {
302
+ switch (language) {
303
+ case 'python': return { src: '.py', test: '_test.py', testSuffix: '.py' };
304
+ case 'go': return { src: '.go', test: '_test.go', testSuffix: '.go' };
305
+ default: return { src: '.ts', test: '.test.ts', testSuffix: '.ts' };
306
+ }
307
+ }
308
+
309
+ async function generateActions(input: SkillInput, language: string, json: boolean = false, useLLM: boolean = false, outputPath?: string, generateTests: boolean = false): Promise<void> {
310
+ const result = await generateActionsSkill(input, language, useLLM, undefined, generateTests);
311
+ const ext = langExt(language);
312
+
313
+ if (outputPath) {
314
+ // Write scaffolds to output directory or file
315
+ const isDir = outputPath.endsWith('/') || !outputPath.includes('.');
316
+ if (isDir) {
317
+ if (!existsSync(outputPath)) {
318
+ mkdirSync(outputPath, { recursive: true });
319
+ }
320
+ for (const [name, scaffold] of Object.entries(result.scaffolds)) {
321
+ const fileName = `${name}${ext.src}`;
322
+ const code = stripCodeFence(scaffold);
323
+ writeFileSync(join(outputPath, fileName), code);
324
+ console.log(`Wrote: ${join(outputPath, fileName)}`);
325
+ }
326
+ if (result.tests) {
327
+ for (const [name, test] of Object.entries(result.tests)) {
328
+ const fileName = `${name}${ext.test}`;
329
+ const code = stripCodeFence(test);
330
+ writeFileSync(join(outputPath, fileName), code);
331
+ console.log(`Wrote: ${join(outputPath, fileName)}`);
332
+ }
333
+ }
334
+ } else {
335
+ // Combine all scaffolds into single file
336
+ const combined = Object.entries(result.scaffolds)
337
+ .map(([name, scaffold]) => stripCodeFence(scaffold))
338
+ .join('\n\n');
339
+ writeFileSync(outputPath, combined);
340
+ console.log(`Wrote: ${outputPath}`);
341
+
342
+ if (result.tests) {
343
+ const dotIdx = outputPath.lastIndexOf('.');
344
+ const testPath = dotIdx !== -1
345
+ ? outputPath.slice(0, dotIdx) + ext.test
346
+ : outputPath + ext.test;
347
+ const testCombined = Object.entries(result.tests)
348
+ .map(([_, test]) => stripCodeFence(test))
349
+ .join('\n\n');
350
+ writeFileSync(testPath, testCombined);
351
+ console.log(`Wrote: ${testPath}`);
352
+ }
353
+ }
354
+ return;
355
+ }
356
+
357
+ if (json) {
358
+ console.log(JSON.stringify(result, null, 2));
359
+ return;
360
+ }
361
+
362
+ console.log(`Generated action scaffolds for ${result.machine}:`);
363
+ for (const [name, scaffold] of Object.entries(result.scaffolds)) {
364
+ console.log(`\n--- ${name} ---`);
365
+ console.log(scaffold);
366
+ if (result.tests?.[name]) {
367
+ console.log(`\n--- ${name} Tests ---`);
368
+ console.log(result.tests[name]);
369
+ }
370
+ }
371
+ }
372
+
373
+ async function refine(input: SkillInput, errorsJson: string, json: boolean = false): Promise<void> {
374
+ const errors = JSON.parse(errorsJson);
375
+ const result = await refineSkill(input, errors);
376
+
377
+ if (json) {
378
+ console.log(JSON.stringify(result, null, 2));
379
+ return;
380
+ }
381
+
382
+ if (result.status === 'requires_refinement') {
383
+ console.error(`Refinement incomplete after ${result.iterations} iteration(s). Remaining errors:`);
384
+ for (const e of result.verification?.errors ?? []) {
385
+ console.error(` [${e.severity.toUpperCase()}] ${e.code}: ${e.message}`);
386
+ }
387
+ console.log(result.corrected);
388
+ process.exit(1);
389
+ } else if (result.status === 'error') {
390
+ console.error(`Refinement failed: ${result.error}`);
391
+ process.exit(1);
392
+ } else {
393
+ console.log(result.corrected);
394
+ }
395
+ }
396
+
397
+ async function main(): Promise<void> {
398
+ const args = process.argv.slice(2);
399
+
400
+ if (args.length === 0) {
401
+ console.log('Orca CLI');
402
+ console.log('Usage:');
403
+ console.log(' orca verify [--json] <file.orca> [--stdin] - Parse and verify a machine');
404
+ console.log(' orca compile [--json] xstate <file.orca> [--stdin] - Compile to XState v5');
405
+ console.log(' orca compile [--json] mermaid <file.orca> [--stdin] - Compile to Mermaid diagram');
406
+ console.log(' orca visualize <file.orca> [--stdin] - Compile and show Mermaid');
407
+ console.log(' orca actions [--json] [--lang <lang>] [--output <path>] [--tests] <file.orca> [--stdin]');
408
+ console.log(' orca --tools --json - List all tools as JSON');
409
+ console.log('');
410
+ console.log('Auth commands:');
411
+ console.log(' orca login [--provider <provider>] [--profile <id>] - Login to an LLM provider');
412
+ console.log(' orca logout [--profile <id>] - Remove auth credentials');
413
+ console.log(' orca auth [--doctor] - Show auth status');
414
+ console.log('');
415
+ console.log('Skills (LLM-friendly):');
416
+ console.log(' orca /parse-machine [<file.orca.md>] [--stdin] - Parse and return AST as JSON');
417
+ console.log(' orca /verify-orca [<file.orca>] [--stdin] - Structured JSON verification');
418
+ console.log(' orca /compile-orca [target] [<file.orca>] [--stdin] - Structured JSON compilation');
419
+ console.log(' orca /generate-orca "spec" [--output=<file.orca>] - Generate Orca from natural language');
420
+ console.log(' orca /generate-orca-multi "spec" [--output=<file.orca.md>] - Generate coordinated multi-machine Orca');
421
+ console.log(' orca /generate-actions [--use-llm] [--lang <lang>] [--output <path>] [--tests] [<file>] [--stdin]');
422
+ console.log(' orca /refine-orca [<file.orca>] [--stdin] - Fix verification errors (requires LLM)');
423
+ process.exit(1);
424
+ }
425
+
426
+ // Handle auth commands (before stdin/tools processing)
427
+ if (args[0] === 'login') {
428
+ let provider = 'anthropic';
429
+ let profileId = 'default';
430
+ for (let i = 1; i < args.length; i++) {
431
+ if (args[i] === '--provider' && args[i + 1]) provider = args[++i];
432
+ if (args[i] === '--profile' && args[i + 1]) profileId = args[++i];
433
+ }
434
+ await login(provider, profileId);
435
+ return;
436
+ }
437
+
438
+ if (args[0] === 'logout') {
439
+ let profileId: string | undefined;
440
+ for (let i = 1; i < args.length; i++) {
441
+ if (args[i] === '--profile' && args[i + 1]) profileId = args[++i];
442
+ }
443
+ await logout(profileId);
444
+ return;
445
+ }
446
+
447
+ if (args[0] === 'auth') {
448
+ await auth(args.includes('--doctor'));
449
+ return;
450
+ }
451
+
452
+ // B3: tool discovery
453
+ if (args[0] === '--tools' && args[1] === '--json') {
454
+ console.log(JSON.stringify(ORCA_TOOLS, null, 2));
455
+ return;
456
+ }
457
+
458
+ // B2: strip --stdin from arg list
459
+ const useStdin = args.includes('--stdin');
460
+ const cleanArgs = args.filter(a => a !== '--stdin');
461
+
462
+ // Check for skill invocations (starting with /)
463
+ if (cleanArgs[0].startsWith('/')) {
464
+ const skill = cleanArgs[0];
465
+ const skillArgs = cleanArgs.slice(1);
466
+
467
+ if (skill === '/parse-machine') {
468
+ const fileArg = skillArgs.find(a => !a.startsWith('-'));
469
+ const input = await getInput(fileArg, useStdin);
470
+ const result = parseSkill(input);
471
+ console.log(JSON.stringify(result, null, 2));
472
+ process.exit(result.status === 'success' ? 0 : 1);
473
+ }
474
+
475
+ if (skill === '/verify-orca') {
476
+ const fileArg = skillArgs.find(a => !a.startsWith('-'));
477
+ const input = await getInput(fileArg, useStdin);
478
+ const result = await verifySkill(input);
479
+ console.log(JSON.stringify(result, null, 2));
480
+ process.exit(result.status === 'valid' ? 0 : 1);
481
+ }
482
+
483
+ if (skill === '/compile-orca') {
484
+ const isTarget = (s: string) => s === 'xstate' || s === 'mermaid';
485
+ const target = (skillArgs.find(isTarget) as 'xstate' | 'mermaid') ?? 'xstate';
486
+ const fileArg = skillArgs.find(a => !a.startsWith('-') && !isTarget(a));
487
+ const input = await getInput(fileArg, useStdin);
488
+ const result = await compileSkill(input, target);
489
+ console.log(JSON.stringify(result, null, 2));
490
+ return;
491
+ }
492
+
493
+ if (skill === '/generate-actions') {
494
+ let useLLM = false;
495
+ let generateTests = false;
496
+ let lang = 'typescript';
497
+ let outputPath: string | undefined;
498
+ let fileArg: string | undefined;
499
+
500
+ for (let i = 0; i < skillArgs.length; i++) {
501
+ const arg = skillArgs[i];
502
+ if (arg === '--use-llm') useLLM = true;
503
+ if (arg === '--tests') generateTests = true;
504
+ if (arg === '--lang' && skillArgs[i + 1]) lang = skillArgs[++i];
505
+ if ((arg === '--output' || arg === '-o') && skillArgs[i + 1]) outputPath = skillArgs[++i];
506
+ if (arg?.endsWith('.orca') || arg?.endsWith('.orca.md')) fileArg = arg;
507
+ }
508
+
509
+ const input = await getInput(fileArg, useStdin);
510
+ await generateActions(input, lang, false, useLLM, outputPath, generateTests);
511
+ return;
512
+ }
513
+
514
+ if (skill === '/refine-orca') {
515
+ const fileArg = skillArgs.find(a => a.endsWith('.orca') || a.endsWith('.orca.md'));
516
+ const errorsJson = skillArgs.find(a => a.startsWith('[')) || '[]';
517
+ const input = await getInput(fileArg, useStdin);
518
+ const result = await refineSkill(input, JSON.parse(errorsJson));
519
+ console.log(JSON.stringify(result, null, 2));
520
+ return;
521
+ }
522
+
523
+ if (skill === '/generate-orca') {
524
+ const spec = skillArgs[0] || skillArgs.find(a => !a.startsWith('--')) || '';
525
+ const outputPath = skillArgs.find(a => a.startsWith('--output='))?.replace('--output=', '') ||
526
+ skillArgs.find(a => a.startsWith('-o='))?.replace('-o=', '');
527
+
528
+ if (!spec) {
529
+ console.error('Usage: /generate-orca "natural language specification" [--output=<file.orca>]');
530
+ process.exit(1);
531
+ }
532
+
533
+ const result = await generateOrcaSkill(spec);
534
+
535
+ if (result.status === 'success' && result.orca) {
536
+ if (outputPath) {
537
+ writeFileSync(outputPath, result.orca);
538
+ console.log(`Generated: ${outputPath}`);
539
+ } else {
540
+ console.log(result.orca);
541
+ }
542
+ } else if (result.status === 'requires_refinement' && result.orca) {
543
+ console.log('Machine generated but verification found issues. Outputting for manual review:');
544
+ console.log(result.orca);
545
+ if (result.verification?.errors.length) {
546
+ console.log('\nVerification issues:');
547
+ for (const err of result.verification.errors) {
548
+ console.log(` [${err.severity.toUpperCase()}] ${err.code}: ${err.message}`);
549
+ if (err.suggestion) console.log(` Suggestion: ${err.suggestion}`);
550
+ }
551
+ }
552
+ } else {
553
+ console.error(`Generation failed: ${result.error}`);
554
+ process.exit(1);
555
+ }
556
+ return;
557
+ }
558
+
559
+ if (skill === '/generate-orca-multi') {
560
+ const spec = skillArgs[0] || skillArgs.find(a => !a.startsWith('--')) || '';
561
+ const outputPath = skillArgs.find(a => a.startsWith('--output='))?.replace('--output=', '') ||
562
+ skillArgs.find(a => a.startsWith('-o='))?.replace('-o=', '');
563
+
564
+ if (!spec) {
565
+ console.error('Usage: /generate-orca-multi "natural language specification" [--output=<file.orca.md>]');
566
+ process.exit(1);
567
+ }
568
+
569
+ const result = await generateOrcaMultiSkill(spec);
570
+
571
+ if (result.status === 'success' && result.orca) {
572
+ if (outputPath) {
573
+ writeFileSync(outputPath, result.orca);
574
+ console.log(`Generated: ${outputPath} (machines: ${result.machines?.join(', ')})`);
575
+ } else {
576
+ console.log(result.orca);
577
+ }
578
+ } else if (result.status === 'requires_refinement' && result.orca) {
579
+ console.log('Machines generated but verification found issues. Outputting for manual review:');
580
+ console.log(result.orca);
581
+ if (result.errors?.length) {
582
+ console.log('\nVerification issues:');
583
+ for (const err of result.errors) {
584
+ console.log(` [${err.severity.toUpperCase()}] ${err.code}: ${err.message}`);
585
+ if (err.suggestion) console.log(` Suggestion: ${err.suggestion}`);
586
+ }
587
+ }
588
+ } else {
589
+ console.error(`Generation failed: ${result.error}`);
590
+ process.exit(1);
591
+ }
592
+ return;
593
+ }
594
+
595
+ console.error(`Unknown skill: ${skill}`);
596
+ process.exit(1);
597
+ }
598
+
599
+ // Standard commands
600
+ const command = cleanArgs[0];
601
+
602
+ // Check for --json flag
603
+ let json = false;
604
+ let filteredArgs = cleanArgs;
605
+ if (cleanArgs[1] === '--json') {
606
+ json = true;
607
+ filteredArgs = [cleanArgs[0], ...cleanArgs.slice(2)];
608
+ }
609
+
610
+ try {
611
+ if (command === 'verify') {
612
+ const input = await getInput(filteredArgs[1], useStdin);
613
+ await verify(input, json);
614
+ } else if (command === 'compile' && filteredArgs[1] === 'xstate') {
615
+ const input = await getInput(filteredArgs[2], useStdin);
616
+ await compileXState(input, json);
617
+ } else if (command === 'compile' && filteredArgs[1] === 'mermaid') {
618
+ const input = await getInput(filteredArgs[2], useStdin);
619
+ await compileMermaid(input, json);
620
+ } else if (command === 'visualize') {
621
+ const input = await getInput(filteredArgs[1], useStdin);
622
+ await visualize(input);
623
+ } else if (command === 'convert') {
624
+ const inputPath = filteredArgs[1];
625
+ if (!inputPath) {
626
+ console.error('Usage: orca convert <input.orca> [-o <output.orca.md>]');
627
+ process.exit(1);
628
+ }
629
+ const outputIdx = filteredArgs.indexOf('-o');
630
+ const outputPath = outputIdx !== -1 ? filteredArgs[outputIdx + 1] : inputPath.replace(/\.orca$/, '.orca.md');
631
+ const source = readFileSync(inputPath, 'utf-8');
632
+ const machine = parseFile(inputPath, source);
633
+ const md = machineToMarkdown(machine);
634
+ writeFileSync(outputPath, md);
635
+ console.log(`Converted: ${inputPath} -> ${outputPath}`);
636
+ // Verify round-trip
637
+ const roundTrip = parseMarkdown(md).file.machines[0];
638
+ console.log(`Round-trip verification: ${JSON.stringify(machine) === JSON.stringify(roundTrip) ? 'PASS' : 'WARN: ASTs differ'}`);
639
+ } else if (command === 'actions') {
640
+ let lang = 'typescript';
641
+ let useLLM = false;
642
+ let generateTests = false;
643
+ let outputPath: string | undefined;
644
+ let fileArg: string | undefined;
645
+ for (let i = 1; i < filteredArgs.length; i++) {
646
+ if (filteredArgs[i] === '--lang' && filteredArgs[i + 1]) lang = filteredArgs[++i];
647
+ if ((filteredArgs[i] === '--output' || filteredArgs[i] === '-o') && filteredArgs[i + 1]) outputPath = filteredArgs[++i];
648
+ if (filteredArgs[i] === '--use-llm') useLLM = true;
649
+ if (filteredArgs[i] === '--tests') generateTests = true;
650
+ if (filteredArgs[i]?.endsWith('.orca') || filteredArgs[i]?.endsWith('.orca.md')) fileArg = filteredArgs[i];
651
+ }
652
+ if (!fileArg) fileArg = filteredArgs[filteredArgs.length - 1];
653
+ const input = await getInput(fileArg === command ? undefined : fileArg, useStdin);
654
+ await generateActions(input, lang, json, useLLM, outputPath, generateTests);
655
+ } else {
656
+ console.error(`Unknown command: ${command}`);
657
+ process.exit(1);
658
+ }
659
+ } catch (err) {
660
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
661
+ process.exit(1);
662
+ }
663
+ }
664
+
665
+ // Only run main() when this file is executed directly (not imported as a module)
666
+ // In ESM, we check if this module is the main entry point
667
+ import { fileURLToPath } from 'url';
668
+ const __filename = fileURLToPath(import.meta.url);
669
+ if (process.argv[1] && process.argv[1].endsWith(__filename)) {
670
+ main();
671
+ }