@plures/praxis 0.2.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 (263) hide show
  1. package/FRAMEWORK.md +420 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1310 -0
  4. package/dist/adapters/cli.d.ts +43 -0
  5. package/dist/adapters/cli.d.ts.map +1 -0
  6. package/dist/adapters/cli.js +126 -0
  7. package/dist/adapters/cli.js.map +1 -0
  8. package/dist/cli/commands/auth.d.ts +26 -0
  9. package/dist/cli/commands/auth.d.ts.map +1 -0
  10. package/dist/cli/commands/auth.js +233 -0
  11. package/dist/cli/commands/auth.js.map +1 -0
  12. package/dist/cli/commands/cloud.d.ts +27 -0
  13. package/dist/cli/commands/cloud.d.ts.map +1 -0
  14. package/dist/cli/commands/cloud.js +232 -0
  15. package/dist/cli/commands/cloud.js.map +1 -0
  16. package/dist/cli/commands/generate.d.ts +25 -0
  17. package/dist/cli/commands/generate.d.ts.map +1 -0
  18. package/dist/cli/commands/generate.js +168 -0
  19. package/dist/cli/commands/generate.js.map +1 -0
  20. package/dist/cli/index.d.ts +8 -0
  21. package/dist/cli/index.d.ts.map +1 -0
  22. package/dist/cli/index.js +179 -0
  23. package/dist/cli/index.js.map +1 -0
  24. package/dist/cloud/auth.d.ts +51 -0
  25. package/dist/cloud/auth.d.ts.map +1 -0
  26. package/dist/cloud/auth.js +194 -0
  27. package/dist/cloud/auth.js.map +1 -0
  28. package/dist/cloud/billing.d.ts +184 -0
  29. package/dist/cloud/billing.d.ts.map +1 -0
  30. package/dist/cloud/billing.js +179 -0
  31. package/dist/cloud/billing.js.map +1 -0
  32. package/dist/cloud/client.d.ts +39 -0
  33. package/dist/cloud/client.d.ts.map +1 -0
  34. package/dist/cloud/client.js +176 -0
  35. package/dist/cloud/client.js.map +1 -0
  36. package/dist/cloud/index.d.ts +44 -0
  37. package/dist/cloud/index.d.ts.map +1 -0
  38. package/dist/cloud/index.js +44 -0
  39. package/dist/cloud/index.js.map +1 -0
  40. package/dist/cloud/marketplace.d.ts +166 -0
  41. package/dist/cloud/marketplace.d.ts.map +1 -0
  42. package/dist/cloud/marketplace.js +159 -0
  43. package/dist/cloud/marketplace.js.map +1 -0
  44. package/dist/cloud/provisioning.d.ts +110 -0
  45. package/dist/cloud/provisioning.d.ts.map +1 -0
  46. package/dist/cloud/provisioning.js +148 -0
  47. package/dist/cloud/provisioning.js.map +1 -0
  48. package/dist/cloud/relay/endpoints.d.ts +62 -0
  49. package/dist/cloud/relay/endpoints.d.ts.map +1 -0
  50. package/dist/cloud/relay/endpoints.js +217 -0
  51. package/dist/cloud/relay/endpoints.js.map +1 -0
  52. package/dist/cloud/relay/health/index.d.ts +5 -0
  53. package/dist/cloud/relay/health/index.d.ts.map +1 -0
  54. package/dist/cloud/relay/health/index.js +9 -0
  55. package/dist/cloud/relay/health/index.js.map +1 -0
  56. package/dist/cloud/relay/stats/index.d.ts +5 -0
  57. package/dist/cloud/relay/stats/index.d.ts.map +1 -0
  58. package/dist/cloud/relay/stats/index.js +9 -0
  59. package/dist/cloud/relay/stats/index.js.map +1 -0
  60. package/dist/cloud/relay/sync/index.d.ts +5 -0
  61. package/dist/cloud/relay/sync/index.d.ts.map +1 -0
  62. package/dist/cloud/relay/sync/index.js +9 -0
  63. package/dist/cloud/relay/sync/index.js.map +1 -0
  64. package/dist/cloud/relay/usage/index.d.ts +5 -0
  65. package/dist/cloud/relay/usage/index.d.ts.map +1 -0
  66. package/dist/cloud/relay/usage/index.js +9 -0
  67. package/dist/cloud/relay/usage/index.js.map +1 -0
  68. package/dist/cloud/sponsors.d.ts +81 -0
  69. package/dist/cloud/sponsors.d.ts.map +1 -0
  70. package/dist/cloud/sponsors.js +130 -0
  71. package/dist/cloud/sponsors.js.map +1 -0
  72. package/dist/cloud/types.d.ts +169 -0
  73. package/dist/cloud/types.d.ts.map +1 -0
  74. package/dist/cloud/types.js +7 -0
  75. package/dist/cloud/types.js.map +1 -0
  76. package/dist/components/index.d.ts +43 -0
  77. package/dist/components/index.d.ts.map +1 -0
  78. package/dist/components/index.js +17 -0
  79. package/dist/components/index.js.map +1 -0
  80. package/dist/core/actors.d.ts +95 -0
  81. package/dist/core/actors.d.ts.map +1 -0
  82. package/dist/core/actors.js +158 -0
  83. package/dist/core/actors.js.map +1 -0
  84. package/dist/core/component/generator.d.ts +122 -0
  85. package/dist/core/component/generator.d.ts.map +1 -0
  86. package/dist/core/component/generator.js +307 -0
  87. package/dist/core/component/generator.js.map +1 -0
  88. package/dist/core/engine.d.ts +92 -0
  89. package/dist/core/engine.d.ts.map +1 -0
  90. package/dist/core/engine.js +199 -0
  91. package/dist/core/engine.js.map +1 -0
  92. package/dist/core/introspection.d.ts +141 -0
  93. package/dist/core/introspection.d.ts.map +1 -0
  94. package/dist/core/introspection.js +208 -0
  95. package/dist/core/introspection.js.map +1 -0
  96. package/dist/core/logic/generator.d.ts +76 -0
  97. package/dist/core/logic/generator.d.ts.map +1 -0
  98. package/dist/core/logic/generator.js +339 -0
  99. package/dist/core/logic/generator.js.map +1 -0
  100. package/dist/core/pluresdb/generator.d.ts +58 -0
  101. package/dist/core/pluresdb/generator.d.ts.map +1 -0
  102. package/dist/core/pluresdb/generator.js +162 -0
  103. package/dist/core/pluresdb/generator.js.map +1 -0
  104. package/dist/core/protocol.d.ts +121 -0
  105. package/dist/core/protocol.d.ts.map +1 -0
  106. package/dist/core/protocol.js +46 -0
  107. package/dist/core/protocol.js.map +1 -0
  108. package/dist/core/rules.d.ts +120 -0
  109. package/dist/core/rules.d.ts.map +1 -0
  110. package/dist/core/rules.js +81 -0
  111. package/dist/core/rules.js.map +1 -0
  112. package/dist/core/schema/loader.d.ts +47 -0
  113. package/dist/core/schema/loader.d.ts.map +1 -0
  114. package/dist/core/schema/loader.js +189 -0
  115. package/dist/core/schema/loader.js.map +1 -0
  116. package/dist/core/schema/normalize.d.ts +72 -0
  117. package/dist/core/schema/normalize.d.ts.map +1 -0
  118. package/dist/core/schema/normalize.js +190 -0
  119. package/dist/core/schema/normalize.js.map +1 -0
  120. package/dist/core/schema/types.d.ts +370 -0
  121. package/dist/core/schema/types.d.ts.map +1 -0
  122. package/dist/core/schema/types.js +161 -0
  123. package/dist/core/schema/types.js.map +1 -0
  124. package/dist/dsl/index.d.ts +152 -0
  125. package/dist/dsl/index.d.ts.map +1 -0
  126. package/dist/dsl/index.js +132 -0
  127. package/dist/dsl/index.js.map +1 -0
  128. package/dist/dsl.d.ts +124 -0
  129. package/dist/dsl.d.ts.map +1 -0
  130. package/dist/dsl.js +130 -0
  131. package/dist/dsl.js.map +1 -0
  132. package/dist/examples/advanced-todo/index.d.ts +55 -0
  133. package/dist/examples/advanced-todo/index.d.ts.map +1 -0
  134. package/dist/examples/advanced-todo/index.js +222 -0
  135. package/dist/examples/advanced-todo/index.js.map +1 -0
  136. package/dist/examples/auth-basic/index.d.ts +17 -0
  137. package/dist/examples/auth-basic/index.d.ts.map +1 -0
  138. package/dist/examples/auth-basic/index.js +122 -0
  139. package/dist/examples/auth-basic/index.js.map +1 -0
  140. package/dist/examples/cart/index.d.ts +19 -0
  141. package/dist/examples/cart/index.d.ts.map +1 -0
  142. package/dist/examples/cart/index.js +202 -0
  143. package/dist/examples/cart/index.js.map +1 -0
  144. package/dist/examples/hero-ecommerce/index.d.ts +39 -0
  145. package/dist/examples/hero-ecommerce/index.d.ts.map +1 -0
  146. package/dist/examples/hero-ecommerce/index.js +506 -0
  147. package/dist/examples/hero-ecommerce/index.js.map +1 -0
  148. package/dist/examples/svelte-counter/index.d.ts +31 -0
  149. package/dist/examples/svelte-counter/index.d.ts.map +1 -0
  150. package/dist/examples/svelte-counter/index.js +123 -0
  151. package/dist/examples/svelte-counter/index.js.map +1 -0
  152. package/dist/flows.d.ts +125 -0
  153. package/dist/flows.d.ts.map +1 -0
  154. package/dist/flows.js +160 -0
  155. package/dist/flows.js.map +1 -0
  156. package/dist/index.d.ts +67 -0
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +59 -0
  159. package/dist/index.js.map +1 -0
  160. package/dist/integrations/pluresdb.d.ts +56 -0
  161. package/dist/integrations/pluresdb.d.ts.map +1 -0
  162. package/dist/integrations/pluresdb.js +46 -0
  163. package/dist/integrations/pluresdb.js.map +1 -0
  164. package/dist/integrations/svelte.d.ts +306 -0
  165. package/dist/integrations/svelte.d.ts.map +1 -0
  166. package/dist/integrations/svelte.js +447 -0
  167. package/dist/integrations/svelte.js.map +1 -0
  168. package/dist/registry.d.ts +94 -0
  169. package/dist/registry.d.ts.map +1 -0
  170. package/dist/registry.js +181 -0
  171. package/dist/registry.js.map +1 -0
  172. package/dist/runtime/terminal-adapter.d.ts +105 -0
  173. package/dist/runtime/terminal-adapter.d.ts.map +1 -0
  174. package/dist/runtime/terminal-adapter.js +113 -0
  175. package/dist/runtime/terminal-adapter.js.map +1 -0
  176. package/dist/step.d.ts +34 -0
  177. package/dist/step.d.ts.map +1 -0
  178. package/dist/step.js +111 -0
  179. package/dist/step.js.map +1 -0
  180. package/dist/types.d.ts +63 -0
  181. package/dist/types.d.ts.map +1 -0
  182. package/dist/types.js +6 -0
  183. package/dist/types.js.map +1 -0
  184. package/docs/MONETIZATION.md +394 -0
  185. package/docs/TERMINAL_NODE.md +588 -0
  186. package/docs/guides/canvas.md +389 -0
  187. package/docs/guides/getting-started.md +347 -0
  188. package/docs/guides/history-state-pattern.md +618 -0
  189. package/docs/guides/orchestration.md +617 -0
  190. package/docs/guides/parallel-state-pattern.md +767 -0
  191. package/docs/guides/svelte-integration.md +691 -0
  192. package/package.json +96 -0
  193. package/src/__tests__/actors.test.ts +270 -0
  194. package/src/__tests__/billing.test.ts +175 -0
  195. package/src/__tests__/cloud.test.ts +247 -0
  196. package/src/__tests__/dsl.test.ts +154 -0
  197. package/src/__tests__/edge-cases.test.ts +475 -0
  198. package/src/__tests__/engine.test.ts +137 -0
  199. package/src/__tests__/generators.test.ts +270 -0
  200. package/src/__tests__/introspection.test.ts +321 -0
  201. package/src/__tests__/protocol.test.ts +40 -0
  202. package/src/__tests__/provisioning.test.ts +162 -0
  203. package/src/__tests__/schema.test.ts +241 -0
  204. package/src/__tests__/svelte-integration.test.ts +431 -0
  205. package/src/__tests__/terminal-node.test.ts +352 -0
  206. package/src/adapters/cli.ts +175 -0
  207. package/src/cli/commands/auth.ts +271 -0
  208. package/src/cli/commands/cloud.ts +281 -0
  209. package/src/cli/commands/generate.ts +225 -0
  210. package/src/cli/index.ts +190 -0
  211. package/src/cloud/README.md +383 -0
  212. package/src/cloud/auth.ts +245 -0
  213. package/src/cloud/billing.ts +336 -0
  214. package/src/cloud/client.ts +221 -0
  215. package/src/cloud/index.ts +121 -0
  216. package/src/cloud/marketplace.ts +303 -0
  217. package/src/cloud/provisioning.ts +254 -0
  218. package/src/cloud/relay/endpoints.ts +307 -0
  219. package/src/cloud/relay/health/function.json +17 -0
  220. package/src/cloud/relay/health/index.ts +10 -0
  221. package/src/cloud/relay/host.json +15 -0
  222. package/src/cloud/relay/local.settings.json +8 -0
  223. package/src/cloud/relay/stats/function.json +17 -0
  224. package/src/cloud/relay/stats/index.ts +10 -0
  225. package/src/cloud/relay/sync/function.json +17 -0
  226. package/src/cloud/relay/sync/index.ts +10 -0
  227. package/src/cloud/relay/usage/function.json +17 -0
  228. package/src/cloud/relay/usage/index.ts +10 -0
  229. package/src/cloud/sponsors.ts +213 -0
  230. package/src/cloud/types.ts +198 -0
  231. package/src/components/README.md +125 -0
  232. package/src/components/TerminalNode.svelte +457 -0
  233. package/src/components/index.ts +46 -0
  234. package/src/core/actors.ts +205 -0
  235. package/src/core/component/generator.ts +432 -0
  236. package/src/core/engine.ts +243 -0
  237. package/src/core/introspection.ts +329 -0
  238. package/src/core/logic/generator.ts +420 -0
  239. package/src/core/pluresdb/generator.ts +229 -0
  240. package/src/core/protocol.ts +132 -0
  241. package/src/core/rules.ts +167 -0
  242. package/src/core/schema/loader.ts +247 -0
  243. package/src/core/schema/normalize.ts +322 -0
  244. package/src/core/schema/types.ts +557 -0
  245. package/src/dsl/index.ts +218 -0
  246. package/src/dsl.ts +214 -0
  247. package/src/examples/advanced-todo/App.svelte +506 -0
  248. package/src/examples/advanced-todo/README.md +371 -0
  249. package/src/examples/advanced-todo/index.ts +309 -0
  250. package/src/examples/auth-basic/index.ts +163 -0
  251. package/src/examples/cart/index.ts +259 -0
  252. package/src/examples/hero-ecommerce/index.ts +657 -0
  253. package/src/examples/svelte-counter/index.ts +168 -0
  254. package/src/flows.ts +268 -0
  255. package/src/index.ts +154 -0
  256. package/src/integrations/pluresdb.ts +93 -0
  257. package/src/integrations/svelte.ts +617 -0
  258. package/src/registry.ts +223 -0
  259. package/src/runtime/terminal-adapter.ts +175 -0
  260. package/src/step.ts +151 -0
  261. package/src/types.ts +70 -0
  262. package/templates/basic-app/README.md +147 -0
  263. package/templates/fullstack-app/README.md +279 -0
@@ -0,0 +1,352 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateSchema } from '../core/schema/types.js';
3
+ import { loadSchemaFromYaml, loadSchemaFromJson } from '../core/schema/loader.js';
4
+ import {
5
+ createTerminalAdapter,
6
+ runTerminalCommand,
7
+ type TerminalExecutionResult,
8
+ } from '../runtime/terminal-adapter.js';
9
+ import type { PraxisSchema } from '../core/schema/types.js';
10
+
11
+ describe('Terminal Node', () => {
12
+ describe('Schema Validation', () => {
13
+ it('validates a schema with terminal node', () => {
14
+ const schema: PraxisSchema = {
15
+ version: '1.0.0',
16
+ name: 'TestTerminalSchema',
17
+ orchestration: {
18
+ type: 'custom',
19
+ nodes: [
20
+ {
21
+ id: 't1',
22
+ type: 'terminal',
23
+ x: 100,
24
+ y: 80,
25
+ config: {},
26
+ props: {
27
+ inputMode: 'text',
28
+ history: [],
29
+ lastOutput: null,
30
+ },
31
+ },
32
+ ],
33
+ },
34
+ };
35
+
36
+ const result = validateSchema(schema);
37
+ expect(result.valid).toBe(true);
38
+ expect(result.errors).toHaveLength(0);
39
+ });
40
+
41
+ it('validates terminal node with widget input mode', () => {
42
+ const schema: PraxisSchema = {
43
+ version: '1.0.0',
44
+ name: 'TestTerminalSchema',
45
+ orchestration: {
46
+ type: 'custom',
47
+ nodes: [
48
+ {
49
+ id: 't2',
50
+ type: 'terminal',
51
+ config: {},
52
+ props: {
53
+ inputMode: 'widget',
54
+ history: ['ls', 'pwd'],
55
+ lastOutput: 'test output',
56
+ },
57
+ },
58
+ ],
59
+ },
60
+ };
61
+
62
+ const result = validateSchema(schema);
63
+ expect(result.valid).toBe(true);
64
+ expect(result.errors).toHaveLength(0);
65
+ });
66
+
67
+ it('fails validation with invalid inputMode', () => {
68
+ const schema: PraxisSchema = {
69
+ version: '1.0.0',
70
+ name: 'TestTerminalSchema',
71
+ orchestration: {
72
+ type: 'custom',
73
+ nodes: [
74
+ {
75
+ id: 't3',
76
+ type: 'terminal',
77
+ config: {},
78
+ props: {
79
+ inputMode: 'invalid' as any,
80
+ history: [],
81
+ lastOutput: null,
82
+ },
83
+ },
84
+ ],
85
+ },
86
+ };
87
+
88
+ const result = validateSchema(schema);
89
+ expect(result.valid).toBe(false);
90
+ expect(result.errors.length).toBeGreaterThan(0);
91
+ expect(result.errors[0].message).toContain('inputMode');
92
+ });
93
+
94
+ it('fails validation when history is not an array', () => {
95
+ const schema: PraxisSchema = {
96
+ version: '1.0.0',
97
+ name: 'TestTerminalSchema',
98
+ orchestration: {
99
+ type: 'custom',
100
+ nodes: [
101
+ {
102
+ id: 't4',
103
+ type: 'terminal',
104
+ config: {},
105
+ props: {
106
+ inputMode: 'text',
107
+ history: 'not-an-array' as any,
108
+ lastOutput: null,
109
+ },
110
+ },
111
+ ],
112
+ },
113
+ };
114
+
115
+ const result = validateSchema(schema);
116
+ expect(result.valid).toBe(false);
117
+ expect(result.errors.length).toBeGreaterThan(0);
118
+ expect(result.errors[0].message).toContain('history');
119
+ });
120
+
121
+ it('validates terminal node with bindings', () => {
122
+ const schema: PraxisSchema = {
123
+ version: '1.0.0',
124
+ name: 'TestTerminalSchema',
125
+ orchestration: {
126
+ type: 'custom',
127
+ nodes: [
128
+ {
129
+ id: 't5',
130
+ type: 'terminal',
131
+ config: {},
132
+ props: {
133
+ inputMode: 'text',
134
+ history: [],
135
+ lastOutput: null,
136
+ },
137
+ bindings: {
138
+ input: '/terminal/input',
139
+ output: '/terminal/output',
140
+ },
141
+ },
142
+ ],
143
+ },
144
+ };
145
+
146
+ const result = validateSchema(schema);
147
+ expect(result.valid).toBe(true);
148
+ expect(result.errors).toHaveLength(0);
149
+ });
150
+ });
151
+
152
+ describe('YAML Loading', () => {
153
+ it('loads terminal node from YAML', () => {
154
+ const yaml = `
155
+ version: "1.0.0"
156
+ name: "TerminalTestSchema"
157
+ orchestration:
158
+ type: custom
159
+ nodes:
160
+ - id: t1
161
+ type: terminal
162
+ x: 100
163
+ y: 80
164
+ props:
165
+ inputMode: text
166
+ history: []
167
+ lastOutput: null
168
+ `;
169
+
170
+ const result = loadSchemaFromYaml(yaml);
171
+ expect(result.errors).toHaveLength(0);
172
+ expect(result.schema).toBeDefined();
173
+ expect(result.schema?.orchestration?.nodes).toHaveLength(1);
174
+ expect(result.schema?.orchestration?.nodes?.[0].type).toBe('terminal');
175
+ });
176
+
177
+ it('loads terminal node with bindings from YAML', () => {
178
+ const yaml = `
179
+ version: "1.0.0"
180
+ name: "TerminalTestSchema"
181
+ orchestration:
182
+ type: custom
183
+ nodes:
184
+ - id: t1
185
+ type: terminal
186
+ x: 200
187
+ y: 150
188
+ props:
189
+ inputMode: widget
190
+ history:
191
+ - echo hello
192
+ - ls -la
193
+ lastOutput: null
194
+ bindings:
195
+ input: /plures/terminal/input
196
+ output: /plures/terminal/output
197
+ `;
198
+
199
+ const result = loadSchemaFromYaml(yaml);
200
+ expect(result.errors).toHaveLength(0);
201
+ expect(result.schema).toBeDefined();
202
+
203
+ const node = result.schema?.orchestration?.nodes?.[0];
204
+ expect(node?.type).toBe('terminal');
205
+ expect(node?.bindings?.input).toBe('/plures/terminal/input');
206
+ expect(node?.bindings?.output).toBe('/plures/terminal/output');
207
+ });
208
+ });
209
+
210
+ describe('JSON Loading', () => {
211
+ it('loads terminal node from JSON', () => {
212
+ const json = JSON.stringify({
213
+ version: '1.0.0',
214
+ name: 'TerminalTestSchema',
215
+ orchestration: {
216
+ type: 'custom',
217
+ nodes: [
218
+ {
219
+ id: 't1',
220
+ type: 'terminal',
221
+ x: 100,
222
+ y: 80,
223
+ config: {},
224
+ props: {
225
+ inputMode: 'text',
226
+ history: [],
227
+ lastOutput: null,
228
+ },
229
+ },
230
+ ],
231
+ },
232
+ });
233
+
234
+ const result = loadSchemaFromJson(json);
235
+ expect(result.errors).toHaveLength(0);
236
+ expect(result.schema).toBeDefined();
237
+ expect(result.schema?.orchestration?.nodes).toHaveLength(1);
238
+ expect(result.schema?.orchestration?.nodes?.[0].type).toBe('terminal');
239
+ });
240
+ });
241
+
242
+ describe('Terminal Adapter', () => {
243
+ it('creates a terminal adapter', () => {
244
+ const adapter = createTerminalAdapter({
245
+ nodeId: 'test-terminal',
246
+ });
247
+
248
+ expect(adapter).toBeDefined();
249
+ const state = adapter.getState();
250
+ expect(state.nodeId).toBe('test-terminal');
251
+ expect(state.inputMode).toBe('text');
252
+ expect(state.history).toEqual([]);
253
+ expect(state.lastOutput).toBeNull();
254
+ });
255
+
256
+ it('creates terminal adapter with custom props', () => {
257
+ const adapter = createTerminalAdapter({
258
+ nodeId: 'test-terminal',
259
+ props: {
260
+ inputMode: 'widget',
261
+ history: ['echo test'],
262
+ lastOutput: 'test output',
263
+ },
264
+ });
265
+
266
+ const state = adapter.getState();
267
+ expect(state.inputMode).toBe('widget');
268
+ expect(state.history).toEqual(['echo test']);
269
+ expect(state.lastOutput).toBe('test output');
270
+ });
271
+
272
+ it('creates terminal adapter with pluresDB bindings', () => {
273
+ const adapter = createTerminalAdapter({
274
+ nodeId: 'test-terminal',
275
+ inputPath: '/terminal/input',
276
+ outputPath: '/terminal/output',
277
+ });
278
+
279
+ expect(adapter).toBeDefined();
280
+ const state = adapter.getState();
281
+ expect(state.nodeId).toBe('test-terminal');
282
+ });
283
+
284
+ it('executes a command', async () => {
285
+ const adapter = createTerminalAdapter({
286
+ nodeId: 'test-terminal',
287
+ });
288
+
289
+ const result = await adapter.executeCommand('echo hello');
290
+
291
+ expect(result).toBeDefined();
292
+ expect(result.command).toBe('echo hello');
293
+ expect(result.output).toContain('echo hello');
294
+ expect(result.exitCode).toBe(0);
295
+ expect(result.timestamp).toBeGreaterThan(0);
296
+
297
+ const state = adapter.getState();
298
+ expect(state.history).toContain('echo hello');
299
+ expect(state.lastOutput).toBe(result.output);
300
+ });
301
+
302
+ it('maintains command history', async () => {
303
+ const adapter = createTerminalAdapter({
304
+ nodeId: 'test-terminal',
305
+ });
306
+
307
+ await adapter.executeCommand('ls');
308
+ await adapter.executeCommand('pwd');
309
+ await adapter.executeCommand('echo test');
310
+
311
+ const history = adapter.getHistory();
312
+ expect(history).toEqual(['ls', 'pwd', 'echo test']);
313
+ });
314
+
315
+ it('clears command history', async () => {
316
+ const adapter = createTerminalAdapter({
317
+ nodeId: 'test-terminal',
318
+ });
319
+
320
+ await adapter.executeCommand('ls');
321
+ await adapter.executeCommand('pwd');
322
+
323
+ expect(adapter.getHistory()).toHaveLength(2);
324
+
325
+ adapter.clearHistory();
326
+ expect(adapter.getHistory()).toHaveLength(0);
327
+ });
328
+
329
+ it('updates terminal props', () => {
330
+ const adapter = createTerminalAdapter({
331
+ nodeId: 'test-terminal',
332
+ });
333
+
334
+ adapter.updateProps({
335
+ inputMode: 'widget',
336
+ });
337
+
338
+ const state = adapter.getState();
339
+ expect(state.inputMode).toBe('widget');
340
+ });
341
+ });
342
+
343
+ describe('runTerminalCommand convenience function', () => {
344
+ it('executes command using convenience function', async () => {
345
+ const result = await runTerminalCommand('test-node', 'ls -la');
346
+
347
+ expect(result).toBeDefined();
348
+ expect(result.command).toBe('ls -la');
349
+ expect(result.exitCode).toBe(0);
350
+ });
351
+ });
352
+ });
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Praxis CLI Adapter
4
+ *
5
+ * JSON-based CLI interface for cross-language Praxis engine invocation.
6
+ * Reads JSON from stdin, processes through Praxis engine, outputs JSON to stdout.
7
+ *
8
+ * This enables PowerShell, Python, and other languages to use Praxis.
9
+ */
10
+
11
+ import * as fs from "fs";
12
+ import {
13
+ createPraxisEngine,
14
+ PraxisRegistry,
15
+ type PraxisState,
16
+ type PraxisEvent,
17
+ type PraxisStepResult,
18
+ type RuleDescriptor,
19
+ type ConstraintDescriptor,
20
+ } from "../index.js";
21
+
22
+ interface CLIInput {
23
+ state: PraxisState;
24
+ events: PraxisEvent[];
25
+ configPath: string;
26
+ }
27
+
28
+ interface RegistryConfig {
29
+ rules: Array<{
30
+ id: string;
31
+ description: string;
32
+ // Implementation loaded from separate files or inline
33
+ impl?: string; // JavaScript code as string
34
+ }>;
35
+ constraints: Array<{
36
+ id: string;
37
+ description: string;
38
+ impl?: string;
39
+ }>;
40
+ }
41
+
42
+ /**
43
+ * Load registry configuration from JSON file
44
+ */
45
+ function loadRegistryConfig(configPath: string): RegistryConfig {
46
+ try {
47
+ const content = fs.readFileSync(configPath, "utf-8");
48
+ return JSON.parse(content);
49
+ } catch (error) {
50
+ throw new Error(`Failed to load registry config from ${configPath}: ${error}`);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Create a registry from configuration
56
+ * Note: For simplicity, this uses eval for rule implementations.
57
+ * In production, use a safer approach like loading from pre-compiled modules.
58
+ */
59
+ function createRegistryFromConfig<TContext = unknown>(
60
+ config: RegistryConfig
61
+ ): PraxisRegistry<TContext> {
62
+ const registry = new PraxisRegistry<TContext>();
63
+
64
+ // Register rules
65
+ for (const ruleConfig of config.rules) {
66
+ if (!ruleConfig.impl) {
67
+ throw new Error(`Rule ${ruleConfig.id} missing implementation`);
68
+ }
69
+
70
+ try {
71
+ // Eval the implementation (for demo purposes)
72
+ // In production, load from modules or use a safer evaluation method
73
+ const impl = eval(ruleConfig.impl);
74
+
75
+ const rule: RuleDescriptor<TContext> = {
76
+ id: ruleConfig.id,
77
+ description: ruleConfig.description,
78
+ impl,
79
+ };
80
+
81
+ registry.registerRule(rule);
82
+ } catch (error) {
83
+ throw new Error(`Failed to load rule ${ruleConfig.id}: ${error}`);
84
+ }
85
+ }
86
+
87
+ // Register constraints
88
+ for (const constraintConfig of config.constraints) {
89
+ if (!constraintConfig.impl) {
90
+ throw new Error(`Constraint ${constraintConfig.id} missing implementation`);
91
+ }
92
+
93
+ try {
94
+ const impl = eval(constraintConfig.impl);
95
+
96
+ const constraint: ConstraintDescriptor<TContext> = {
97
+ id: constraintConfig.id,
98
+ description: constraintConfig.description,
99
+ impl,
100
+ };
101
+
102
+ registry.registerConstraint(constraint);
103
+ } catch (error) {
104
+ throw new Error(`Failed to load constraint ${constraintConfig.id}: ${error}`);
105
+ }
106
+ }
107
+
108
+ return registry;
109
+ }
110
+
111
+ /**
112
+ * Process a step through the Praxis engine
113
+ */
114
+ function processStep(input: CLIInput): PraxisStepResult {
115
+ // Load registry configuration
116
+ const config = loadRegistryConfig(input.configPath);
117
+ const registry = createRegistryFromConfig(config);
118
+
119
+ // Create engine with state from input
120
+ const engine = createPraxisEngine({
121
+ initialContext: input.state.context,
122
+ initialFacts: input.state.facts,
123
+ initialMeta: input.state.meta,
124
+ registry,
125
+ });
126
+
127
+ // Process events
128
+ return engine.step(input.events);
129
+ }
130
+
131
+ /**
132
+ * Main CLI entry point
133
+ */
134
+ async function main() {
135
+ try {
136
+ // Read input from stdin
137
+ let inputData = "";
138
+
139
+ for await (const chunk of process.stdin) {
140
+ inputData += chunk;
141
+ }
142
+
143
+ if (!inputData.trim()) {
144
+ throw new Error("No input provided");
145
+ }
146
+
147
+ // Parse input
148
+ const input: CLIInput = JSON.parse(inputData);
149
+
150
+ // Validate input
151
+ if (!input.state || !input.events || !input.configPath) {
152
+ throw new Error("Invalid input: must provide state, events, and configPath");
153
+ }
154
+
155
+ // Process step
156
+ const result = processStep(input);
157
+
158
+ // Output result as JSON
159
+ console.log(JSON.stringify(result, null, 2));
160
+ process.exit(0);
161
+ } catch (error) {
162
+ console.error(JSON.stringify({
163
+ error: error instanceof Error ? error.message : String(error),
164
+ stack: error instanceof Error ? error.stack : undefined,
165
+ }, null, 2));
166
+ process.exit(1);
167
+ }
168
+ }
169
+
170
+ // Run if executed directly
171
+ if (import.meta.url === `file://${process.argv[1]}`) {
172
+ main();
173
+ }
174
+
175
+ export { processStep, loadRegistryConfig, createRegistryFromConfig };