@loopstack/cli 0.15.1 → 0.18.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 (176) hide show
  1. package/README.md +66 -0
  2. package/dist/commands/answer.d.ts +3 -0
  3. package/dist/commands/answer.d.ts.map +1 -0
  4. package/dist/commands/answer.js +82 -0
  5. package/dist/commands/answer.js.map +1 -0
  6. package/dist/commands/attach.d.ts +3 -0
  7. package/dist/commands/attach.d.ts.map +1 -0
  8. package/dist/commands/attach.js +103 -0
  9. package/dist/commands/attach.js.map +1 -0
  10. package/dist/commands/create.d.ts +3 -0
  11. package/dist/commands/create.d.ts.map +1 -0
  12. package/dist/commands/create.js +80 -0
  13. package/dist/commands/create.js.map +1 -0
  14. package/dist/commands/env.d.ts +3 -0
  15. package/dist/commands/env.d.ts.map +1 -0
  16. package/dist/commands/env.js +42 -0
  17. package/dist/commands/env.js.map +1 -0
  18. package/dist/commands/list.d.ts +3 -0
  19. package/dist/commands/list.d.ts.map +1 -0
  20. package/dist/commands/list.js +119 -0
  21. package/dist/commands/list.js.map +1 -0
  22. package/dist/commands/login.d.ts +3 -0
  23. package/dist/commands/login.d.ts.map +1 -0
  24. package/dist/commands/login.js +62 -0
  25. package/dist/commands/login.js.map +1 -0
  26. package/dist/commands/run.d.ts +3 -0
  27. package/dist/commands/run.d.ts.map +1 -0
  28. package/dist/commands/run.js +119 -0
  29. package/dist/commands/run.js.map +1 -0
  30. package/dist/commands/runs.d.ts +3 -0
  31. package/dist/commands/runs.d.ts.map +1 -0
  32. package/dist/commands/runs.js +151 -0
  33. package/dist/commands/runs.js.map +1 -0
  34. package/dist/commands/watch.d.ts +3 -0
  35. package/dist/commands/watch.d.ts.map +1 -0
  36. package/dist/commands/watch.js +50 -0
  37. package/dist/commands/watch.js.map +1 -0
  38. package/dist/config/config.d.ts +14 -0
  39. package/dist/config/config.d.ts.map +1 -0
  40. package/dist/config/config.js +25 -0
  41. package/dist/config/config.js.map +1 -0
  42. package/dist/config/resolve.d.ts +18 -0
  43. package/dist/config/resolve.d.ts.map +1 -0
  44. package/dist/config/resolve.js +57 -0
  45. package/dist/config/resolve.js.map +1 -0
  46. package/dist/errors.d.ts +12 -0
  47. package/dist/errors.d.ts.map +1 -0
  48. package/dist/errors.js +36 -0
  49. package/dist/errors.js.map +1 -0
  50. package/dist/hitl/discovery.d.ts +24 -0
  51. package/dist/hitl/discovery.d.ts.map +1 -0
  52. package/dist/hitl/discovery.js +124 -0
  53. package/dist/hitl/discovery.js.map +1 -0
  54. package/dist/hitl/idle-handler.d.ts +7 -0
  55. package/dist/hitl/idle-handler.d.ts.map +1 -0
  56. package/dist/hitl/idle-handler.js +103 -0
  57. package/dist/hitl/idle-handler.js.map +1 -0
  58. package/dist/hitl/pending.d.ts +16 -0
  59. package/dist/hitl/pending.d.ts.map +1 -0
  60. package/dist/hitl/pending.js +45 -0
  61. package/dist/hitl/pending.js.map +1 -0
  62. package/dist/hitl/prompt.d.ts +11 -0
  63. package/dist/hitl/prompt.d.ts.map +1 -0
  64. package/dist/hitl/prompt.js +69 -0
  65. package/dist/hitl/prompt.js.map +1 -0
  66. package/dist/main.d.ts +3 -0
  67. package/dist/main.d.ts.map +1 -0
  68. package/dist/main.js +37 -0
  69. package/dist/main.js.map +1 -0
  70. package/dist/output/format.d.ts +7 -0
  71. package/dist/output/format.d.ts.map +1 -0
  72. package/dist/output/format.js +58 -0
  73. package/dist/output/format.js.map +1 -0
  74. package/dist/output/studio-link.d.ts +4 -0
  75. package/dist/output/studio-link.d.ts.map +1 -0
  76. package/dist/output/studio-link.js +17 -0
  77. package/dist/output/studio-link.js.map +1 -0
  78. package/dist/run/args.d.ts +2 -0
  79. package/dist/run/args.d.ts.map +1 -0
  80. package/dist/run/args.js +41 -0
  81. package/dist/run/args.js.map +1 -0
  82. package/dist/run/documents.d.ts +15 -0
  83. package/dist/run/documents.d.ts.map +1 -0
  84. package/dist/run/documents.js +100 -0
  85. package/dist/run/documents.js.map +1 -0
  86. package/dist/run/follow.d.ts +18 -0
  87. package/dist/run/follow.d.ts.map +1 -0
  88. package/dist/run/follow.js +200 -0
  89. package/dist/run/follow.js.map +1 -0
  90. package/dist/run/retry.d.ts +5 -0
  91. package/dist/run/retry.d.ts.map +1 -0
  92. package/dist/run/retry.js +59 -0
  93. package/dist/run/retry.js.map +1 -0
  94. package/dist/run/trail.d.ts +17 -0
  95. package/dist/run/trail.d.ts.map +1 -0
  96. package/dist/run/trail.js +42 -0
  97. package/dist/run/trail.js.map +1 -0
  98. package/dist/run/workspace.d.ts +3 -0
  99. package/dist/run/workspace.d.ts.map +1 -0
  100. package/dist/run/workspace.js +26 -0
  101. package/dist/run/workspace.js.map +1 -0
  102. package/dist/widgets/collect/form.collect.d.ts +7 -0
  103. package/dist/widgets/collect/form.collect.d.ts.map +1 -0
  104. package/dist/widgets/collect/form.collect.js +71 -0
  105. package/dist/widgets/collect/form.collect.js.map +1 -0
  106. package/dist/widgets/collect/prompts.collect.d.ts +5 -0
  107. package/dist/widgets/collect/prompts.collect.d.ts.map +1 -0
  108. package/dist/widgets/collect/prompts.collect.js +38 -0
  109. package/dist/widgets/collect/prompts.collect.js.map +1 -0
  110. package/dist/widgets/collect/secret-input.collect.d.ts +3 -0
  111. package/dist/widgets/collect/secret-input.collect.d.ts.map +1 -0
  112. package/dist/widgets/collect/secret-input.collect.js +47 -0
  113. package/dist/widgets/collect/secret-input.collect.js.map +1 -0
  114. package/dist/widgets/collect/transition.d.ts +3 -0
  115. package/dist/widgets/collect/transition.d.ts.map +1 -0
  116. package/dist/widgets/collect/transition.js +5 -0
  117. package/dist/widgets/collect/transition.js.map +1 -0
  118. package/dist/widgets/collect/workflow.collect.d.ts +4 -0
  119. package/dist/widgets/collect/workflow.collect.d.ts.map +1 -0
  120. package/dist/widgets/collect/workflow.collect.js +24 -0
  121. package/dist/widgets/collect/workflow.collect.js.map +1 -0
  122. package/dist/widgets/document/form.widget.d.ts +3 -0
  123. package/dist/widgets/document/form.widget.d.ts.map +1 -0
  124. package/dist/widgets/document/form.widget.js +34 -0
  125. package/dist/widgets/document/form.widget.js.map +1 -0
  126. package/dist/widgets/document/json.widget.d.ts +3 -0
  127. package/dist/widgets/document/json.widget.d.ts.map +1 -0
  128. package/dist/widgets/document/json.widget.js +17 -0
  129. package/dist/widgets/document/json.widget.js.map +1 -0
  130. package/dist/widgets/document/link.widget.d.ts +3 -0
  131. package/dist/widgets/document/link.widget.d.ts.map +1 -0
  132. package/dist/widgets/document/link.widget.js +9 -0
  133. package/dist/widgets/document/link.widget.js.map +1 -0
  134. package/dist/widgets/document/llm-message.widget.d.ts +3 -0
  135. package/dist/widgets/document/llm-message.widget.d.ts.map +1 -0
  136. package/dist/widgets/document/llm-message.widget.js +67 -0
  137. package/dist/widgets/document/llm-message.widget.js.map +1 -0
  138. package/dist/widgets/document/message.widget.d.ts +5 -0
  139. package/dist/widgets/document/message.widget.d.ts.map +1 -0
  140. package/dist/widgets/document/message.widget.js +35 -0
  141. package/dist/widgets/document/message.widget.js.map +1 -0
  142. package/dist/widgets/document/oauth-prompt.widget.d.ts +4 -0
  143. package/dist/widgets/document/oauth-prompt.widget.d.ts.map +1 -0
  144. package/dist/widgets/document/oauth-prompt.widget.js +22 -0
  145. package/dist/widgets/document/oauth-prompt.widget.js.map +1 -0
  146. package/dist/widgets/document/secret-request.widget.d.ts +3 -0
  147. package/dist/widgets/document/secret-request.widget.d.ts.map +1 -0
  148. package/dist/widgets/document/secret-request.widget.js +9 -0
  149. package/dist/widgets/document/secret-request.widget.js.map +1 -0
  150. package/dist/widgets/editor.d.ts +3 -0
  151. package/dist/widgets/editor.d.ts.map +1 -0
  152. package/dist/widgets/editor.js +60 -0
  153. package/dist/widgets/editor.js.map +1 -0
  154. package/dist/widgets/field/field-widgets.d.ts +6 -0
  155. package/dist/widgets/field/field-widgets.d.ts.map +1 -0
  156. package/dist/widgets/field/field-widgets.js +64 -0
  157. package/dist/widgets/field/field-widgets.js.map +1 -0
  158. package/dist/widgets/registry.d.ts +6 -0
  159. package/dist/widgets/registry.d.ts.map +1 -0
  160. package/dist/widgets/registry.js +45 -0
  161. package/dist/widgets/registry.js.map +1 -0
  162. package/dist/widgets/types.d.ts +44 -0
  163. package/dist/widgets/types.d.ts.map +1 -0
  164. package/dist/widgets/types.js +2 -0
  165. package/dist/widgets/types.js.map +1 -0
  166. package/fixtures/app/CLAUDE.md +54 -0
  167. package/fixtures/app/README.md +21 -0
  168. package/fixtures/app/docker-compose.yml +33 -0
  169. package/fixtures/app/env.example +16 -0
  170. package/fixtures/app/nest-cli.json +10 -0
  171. package/fixtures/app/src/app.module.ts +8 -0
  172. package/fixtures/app/src/hello/hello.module.ts +12 -0
  173. package/fixtures/app/src/hello/hello.workflow.ts +28 -0
  174. package/fixtures/app/src/main.ts +11 -0
  175. package/package.json +36 -10
  176. package/bin/loopstack.js +0 -33
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-request.widget.js","sourceRoot":"","sources":["../../../src/widgets/document/secret-request.widget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAQ5B,MAAM,CAAC,MAAM,mBAAmB,GAAmB,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IACvE,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAE,OAAO,CAAC,SAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrG,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAiB,EAAE,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC;IAC/G,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC9B,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,YAAY,EAAE,CAAC,0BAA0B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACnG,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function buildFormSkeleton(content: unknown, schema?: Record<string, unknown>): Record<string, unknown>;
2
+ export declare function editPayloadInEditor(initial: Record<string, unknown>, out: NodeJS.WritableStream): unknown;
3
+ //# sourceMappingURL=editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/widgets/editor.ts"],"names":[],"mappings":"AAiCA,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAS7G;AAQD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,cAAc,GAAG,OAAO,CAqBzG"}
@@ -0,0 +1,60 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import pc from 'picocolors';
6
+ function emptyValue(property) {
7
+ if (property.default !== undefined)
8
+ return property.default;
9
+ switch (property.type) {
10
+ case 'string':
11
+ return '';
12
+ case 'number':
13
+ case 'integer':
14
+ return 0;
15
+ case 'boolean':
16
+ return false;
17
+ case 'array':
18
+ return [];
19
+ case 'object':
20
+ return {};
21
+ default:
22
+ return null;
23
+ }
24
+ }
25
+ export function buildFormSkeleton(content, schema) {
26
+ if (content && typeof content === 'object' && Object.keys(content).length > 0) {
27
+ return content;
28
+ }
29
+ const typed = schema;
30
+ if (typed?.type === 'object' && typed.properties) {
31
+ return Object.fromEntries(Object.entries(typed.properties).map(([key, property]) => [key, emptyValue(property)]));
32
+ }
33
+ return {};
34
+ }
35
+ export function editPayloadInEditor(initial, out) {
36
+ const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
37
+ const dir = mkdtempSync(join(tmpdir(), 'loopstack-'));
38
+ const file = join(dir, 'answer.json');
39
+ writeFileSync(file, JSON.stringify(initial, null, 2) + '\n');
40
+ try {
41
+ while (true) {
42
+ const result = spawnSync(`${editor} ${JSON.stringify(file)}`, { shell: true, stdio: 'inherit' });
43
+ if (result.status !== 0)
44
+ return undefined;
45
+ const text = readFileSync(file, 'utf8').trim();
46
+ if (!text)
47
+ return undefined;
48
+ try {
49
+ return JSON.parse(text);
50
+ }
51
+ catch (error) {
52
+ out.write(pc.red(`invalid JSON (${error instanceof Error ? error.message : String(error)}) — reopening…\n`));
53
+ }
54
+ }
55
+ }
56
+ finally {
57
+ rmSync(dir, { recursive: true, force: true });
58
+ }
59
+ }
60
+ //# sourceMappingURL=editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/widgets/editor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { FieldWidget } from '../types.js';
2
+ export declare function resolveFieldWidget(widgetName: string | undefined): FieldWidget;
3
+ export declare function formatEntry(key: string, value: unknown, indent: number): string[];
4
+ export declare function formatEntries(obj: Record<string, unknown>, indent: number): string[];
5
+ export declare function stripCodeFences(text: string): string;
6
+ //# sourceMappingURL=field-widgets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-widgets.d.ts","sourceRoot":"","sources":["../../../src/widgets/field/field-widgets.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAqC/C,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAG9E;AAWD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAuBjF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAEpF;AAQD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGpD"}
@@ -0,0 +1,64 @@
1
+ import pc from 'picocolors';
2
+ const defaultField = (key, value) => formatEntry(key, value, 0);
3
+ const markdownView = (key, value) => labeledBlock(key, typeof value === 'string' ? value : JSON.stringify(value));
4
+ const codeView = (key, value) => labeledBlock(key, typeof value === 'string' ? stripCodeFences(value) : JSON.stringify(value));
5
+ const jsonField = (key, value) => labeledBlock(key, JSON.stringify(value, null, 2));
6
+ const FIELD_WIDGETS = new Map([
7
+ ['input', defaultField],
8
+ ['textarea', defaultField],
9
+ ['number', defaultField],
10
+ ['checkbox', defaultField],
11
+ ['radio', defaultField],
12
+ ['switch', defaultField],
13
+ ['select', defaultField],
14
+ ['slider', defaultField],
15
+ ['markdown-view', markdownView],
16
+ ['code-view', codeView],
17
+ ]);
18
+ export function resolveFieldWidget(widgetName) {
19
+ if (!widgetName)
20
+ return defaultField;
21
+ return FIELD_WIDGETS.get(widgetName) ?? jsonField;
22
+ }
23
+ function labeledBlock(key, text) {
24
+ return [pc.dim(`${key}:`), ...text.split('\n')];
25
+ }
26
+ export function formatEntry(key, value, indent) {
27
+ const pad = ' '.repeat(indent);
28
+ if (value == null)
29
+ return [];
30
+ if (Array.isArray(value)) {
31
+ const lines = [`${pad}${pc.dim(`${key}:`)}`];
32
+ for (const item of value) {
33
+ if (item && typeof item === 'object' && !Array.isArray(item)) {
34
+ lines.push(`${pad} -`);
35
+ lines.push(...formatEntries(item, indent + 2));
36
+ }
37
+ else {
38
+ lines.push(`${pad} - ${formatScalar(item)}`);
39
+ }
40
+ }
41
+ return lines;
42
+ }
43
+ if (typeof value === 'object') {
44
+ return [`${pad}${pc.dim(`${key}:`)}`, ...formatEntries(value, indent + 1)];
45
+ }
46
+ const text = formatScalar(value);
47
+ if (text.includes('\n')) {
48
+ return [`${pad}${pc.dim(`${key}:`)}`, ...text.split('\n').map((line) => `${pad}${line}`)];
49
+ }
50
+ return [`${pad}${pc.dim(`${key}:`)} ${text}`];
51
+ }
52
+ export function formatEntries(obj, indent) {
53
+ return Object.entries(obj).flatMap(([key, value]) => formatEntry(key, value, indent));
54
+ }
55
+ function formatScalar(value) {
56
+ if (typeof value === 'string')
57
+ return stripCodeFences(value);
58
+ return JSON.stringify(value);
59
+ }
60
+ export function stripCodeFences(text) {
61
+ const match = text.trim().match(/^```[^\n]*\n([\s\S]*?)\n?```$/);
62
+ return match ? match[1] : text;
63
+ }
64
+ //# sourceMappingURL=field-widgets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-widgets.js","sourceRoot":"","sources":["../../../src/widgets/field/field-widgets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { CollectWidget, DocumentWidget } from './types.js';
2
+ export declare function resolveDocumentWidget(widgetName: string | undefined): DocumentWidget | undefined;
3
+ export declare function resolveCollectWidget(widgetName: string | undefined): CollectWidget | undefined;
4
+ export declare function isCollectable(widgetName: string | undefined): boolean;
5
+ export declare function resolveHandoff(widgetName: string | undefined, content: Record<string, unknown>): string | undefined;
6
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/widgets/registry.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAa,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAqC3E,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,CAKhG;AAGD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAE9F;AAGD,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAErE;AAGD,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,CAEnH"}
@@ -0,0 +1,45 @@
1
+ import { collectForm } from './collect/form.collect.js';
2
+ import { collectChoices, collectConfirm, collectText } from './collect/prompts.collect.js';
3
+ import { collectSecretInput } from './collect/secret-input.collect.js';
4
+ import { collectButton, collectPromptInput } from './collect/workflow.collect.js';
5
+ import { formWidget } from './document/form.widget.js';
6
+ import { jsonWidget } from './document/json.widget.js';
7
+ import { linkWidget } from './document/link.widget.js';
8
+ import { llmMessageWidget } from './document/llm-message.widget.js';
9
+ import { errorWidget, markdownWidget, messageWidget } from './document/message.widget.js';
10
+ import { oauthHandoff, oauthPromptWidget } from './document/oauth-prompt.widget.js';
11
+ import { secretRequestWidget } from './document/secret-request.widget.js';
12
+ const WIDGETS = new Map([
13
+ ['llm-message', { render: llmMessageWidget }],
14
+ ['message', { render: messageWidget }],
15
+ ['plain', { render: messageWidget }],
16
+ ['markdown', { render: markdownWidget }],
17
+ ['error', { render: errorWidget }],
18
+ ['link', { render: linkWidget }],
19
+ ['form', { render: formWidget, collect: collectForm }],
20
+ ['secret-input', { render: secretRequestWidget, collect: collectSecretInput }],
21
+ ['oauth-prompt', { render: oauthPromptWidget, handoff: oauthHandoff }],
22
+ ['text-prompt', { collect: collectText }],
23
+ ['confirm-prompt', { collect: collectConfirm }],
24
+ ['choices', { collect: collectChoices }],
25
+ ['prompt-input', { collect: collectPromptInput }],
26
+ ['button', { collect: collectButton }],
27
+ ]);
28
+ export function resolveDocumentWidget(widgetName) {
29
+ if (!widgetName)
30
+ return undefined;
31
+ const entry = WIDGETS.get(widgetName);
32
+ if (entry)
33
+ return entry.render;
34
+ return jsonWidget;
35
+ }
36
+ export function resolveCollectWidget(widgetName) {
37
+ return widgetName ? WIDGETS.get(widgetName)?.collect : undefined;
38
+ }
39
+ export function isCollectable(widgetName) {
40
+ return resolveCollectWidget(widgetName) !== undefined;
41
+ }
42
+ export function resolveHandoff(widgetName, content) {
43
+ return widgetName ? WIDGETS.get(widgetName)?.handoff?.(content) : undefined;
44
+ }
45
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/widgets/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3F,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAU1E,MAAM,OAAO,GAAG,IAAI,GAAG,CAAoB;IACzC,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC7C,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACtC,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACpC,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACxC,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAClC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAChC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACtD,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAG9E,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;IAGtE,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACzC,CAAC,gBAAgB,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC/C,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAGxC,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IACjD,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;CACvC,CAAC,CAAC;AAOH,MAAM,UAAU,qBAAqB,CAAC,UAA8B;IAClE,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACtC,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC/B,OAAO,UAAU,CAAC;AACpB,CAAC;AAGD,MAAM,UAAU,oBAAoB,CAAC,UAA8B;IACjE,OAAO,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACnE,CAAC;AAGD,MAAM,UAAU,aAAa,CAAC,UAA8B;IAC1D,OAAO,oBAAoB,CAAC,UAAU,CAAC,KAAK,SAAS,CAAC;AACxD,CAAC;AAGD,MAAM,UAAU,cAAc,CAAC,UAA8B,EAAE,OAAgC;IAC7F,OAAO,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,44 @@
1
+ export interface WidgetContext {
2
+ documentName: string;
3
+ workflowId: string;
4
+ options?: Record<string, unknown>;
5
+ schema?: Record<string, unknown>;
6
+ streamedMessageIds?: ReadonlySet<string>;
7
+ streamedToolCallIds?: ReadonlySet<string>;
8
+ studioUrl?: (workflowId: string) => string | undefined;
9
+ }
10
+ export interface WidgetOutput {
11
+ block(text: string): void;
12
+ line(text: string): void;
13
+ }
14
+ export type DocumentWidget = (content: Record<string, unknown>, ctx: WidgetContext, out: WidgetOutput) => void;
15
+ export type FieldWidget = (key: string, value: unknown) => string[];
16
+ export interface PromptAnswer {
17
+ transitionId: string;
18
+ payload: unknown;
19
+ }
20
+ export interface CollectHttp {
21
+ get<T = unknown>(path: string): Promise<T>;
22
+ put<T = unknown>(path: string, body?: unknown): Promise<T>;
23
+ }
24
+ export interface CollectContext {
25
+ content: Record<string, unknown>;
26
+ documentName?: string;
27
+ options?: Record<string, unknown>;
28
+ schema?: Record<string, unknown>;
29
+ availableTransitions: string[];
30
+ workspaceId?: string;
31
+ http?: CollectHttp;
32
+ }
33
+ export interface CollectIo {
34
+ ask(query: string): Promise<string>;
35
+ askSecret(query: string): Promise<string>;
36
+ out: NodeJS.WritableStream;
37
+ }
38
+ export type CollectWidget = (ctx: CollectContext, io: CollectIo) => Promise<PromptAnswer | undefined>;
39
+ export interface CliWidget {
40
+ render?: DocumentWidget;
41
+ collect?: CollectWidget;
42
+ handoff?: (content: Record<string, unknown>) => string | undefined;
43
+ }
44
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/widgets/types.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IAErB,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEjC,kBAAkB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAEzC,mBAAmB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAE1C,SAAS,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACxD;AAED,MAAM,WAAW,YAAY;IAE3B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;AAG/G,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC;AAGpE,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB;AAOD,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3C,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC5D;AAGD,MAAM,WAAW,cAAc;IAE7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEjC,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAE/B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IAExB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEpC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,GAAG,EAAE,MAAM,CAAC,cAAc,CAAC;CAC5B;AAOD,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,KAAK,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC;AAGtG,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;IAMxB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;CACpE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/widgets/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ # Loopstack App
2
+
3
+ This is a [Loopstack](https://loopstack.ai) app — a NestJS backend where AI workflows are code: state machines with explicit transitions, typed documents, and tools, all observable in Studio and from the CLI.
4
+
5
+ ## Where things live
6
+
7
+ - `src/app.module.ts` — mounts `LoopstackModule.forRoot()` plus your app modules
8
+ - `src/hello/` — a complete example: `hello.module.ts` (`@StudioApp` groups workflows into an app) and `hello.workflow.ts` (a minimal workflow)
9
+ - `docker-compose.yml` — Postgres, Redis, and Studio (http://localhost:5173)
10
+ - `.env` — configuration; add API keys here when a module needs them
11
+
12
+ ## The feedback loop — use the CLI, not guesswork
13
+
14
+ The `loopstack` CLI talks to the running backend (`npm run start:dev`, http://localhost:3000):
15
+
16
+ ```bash
17
+ loopstack list # what can be run right now
18
+ loopstack run <workflow> --arg k=v # run it, streamed live; --json for machine-readable output
19
+ loopstack runs # recent runs, waiting-for-input first
20
+ loopstack runs <run-id> # one run's audit trail (+ pendingPrompt under --json)
21
+ loopstack answer <run-id> --arg k=v # answer a waiting run's prompt non-interactively
22
+ loopstack attach <run-id> # rejoin a run live and answer its prompts interactively
23
+ loopstack watch --json # NDJSON event firehose while you develop
24
+ ```
25
+
26
+ Exit codes: `0` completed, `1` failed, `2` connection/config error, `3` waiting for input in a non-interactive shell. After changing code, the dev server reloads automatically — rerun the workflow to verify.
27
+
28
+ When a run exits `3`, it parked on a human question. Close the loop instead of stopping: read the question with `loopstack runs <run-id> --json` (the `pendingPrompt` field carries the description, the expected answer schema, and the transition), ask the human for their answer, submit it with `loopstack answer <run-id> --arg key=value`, then re-check `loopstack runs <run-id>` for the outcome. Never invent an answer to a human question yourself.
29
+
30
+ ## Writing workflows
31
+
32
+ Follow `src/hello/hello.workflow.ts` as the canonical example:
33
+
34
+ - A workflow is a class with `@Workflow({ title, description, schema })` extending `BaseWorkflow<Args>` (the zod `schema` types and validates its args); register it in the `@StudioApp` module.
35
+ - Transitions are methods with `@Transition({ to: 'x' })` (initial, `from` defaults to `'start'`) or `@Transition({ from: 'x', to: 'end' })` (final). Signatures: `(state, ctx)` for automatic transitions, `(state, payload, ctx)` for wait transitions. Workflow args are on `ctx.args`.
36
+ - Transitions return nothing — mutate via `this.assignState(partial)` and publish results via `this.assignResult(partial)`. Use `async` only when the body awaits.
37
+ - Documents are classes with `@Document({ schema })` (zod) saved via `this.documentStore.save(SomeDocument, data)` — they are what Studio renders.
38
+ - Tools are NestJS providers with `@Tool({ name })`, implementing `protected async handle(args, ctx, options?): Promise<ToolEnvelope>`.
39
+
40
+ ## Testing
41
+
42
+ Tests are ordinary vitest tests that run the real engine in-process — no backend, no database:
43
+
44
+ - `runWorkflow(MyWorkflow, args, { providers, answers })` from `@loopstack/testing` executes the workflow and returns `{ status, path, result, documents, document(key) }` for assertions. Script HITL input with `answers: { waitTransitionName: payload }`; sub-workflows run inline.
45
+ - `testTool().forTool(MyTool)` unit-tests a tool's `handle()` in isolation.
46
+ - For deterministic LLM/tool regression tests, record a live run (`loopstack run <workflow> --trace`, then `loopstack runs <run-id> --record fixture.json`) and pass `replay: replay('fixture.json')` to `runWorkflow`. Omit `replay` to test against real providers — keep those assertions structural and run them on demand, not in CI.
47
+
48
+ Write workflow tests for every non-trivial workflow you build. Guide: https://loopstack.ai/docs/build/testing
49
+
50
+ ## Learning more
51
+
52
+ - Docs: https://loopstack.ai/docs — for agents, the full corpus is at https://loopstack.ai/llms.txt (route via titles, fetch what you need)
53
+ - Registry of feature modules and examples (LLM providers, HITL prompts, agents, git, secrets, sandboxes): https://loopstack.ai/registry — each package README has an installation section; prefer adapting a registry example over building from scratch
54
+ - CLI reference: https://loopstack.ai/docs/reference/cli
@@ -0,0 +1,21 @@
1
+ # Loopstack App
2
+
3
+ Scaffolded with `loopstack create`.
4
+
5
+ ## Quickstart
6
+
7
+ ```bash
8
+ docker compose up -d # Postgres, Redis, and Studio
9
+ npm run start:dev # the backend on http://localhost:3000
10
+ ```
11
+
12
+ Open Studio at [http://localhost:5173](http://localhost:5173), or run the hello workflow from the terminal:
13
+
14
+ ```bash
15
+ npx @loopstack/cli run hello --arg name=You
16
+ ```
17
+
18
+ ## Next steps
19
+
20
+ - Build your first workflow: https://loopstack.ai/docs
21
+ - Add feature modules and example workflows from the registry: https://loopstack.ai/registry
@@ -0,0 +1,33 @@
1
+ name: loopstack
2
+
3
+ services:
4
+ postgres:
5
+ image: postgres:16-alpine
6
+ ports:
7
+ - '5432:5432'
8
+ environment:
9
+ POSTGRES_DB: ${DATABASE_NAME:-postgres}
10
+ POSTGRES_USER: ${DATABASE_USERNAME:-postgres}
11
+ POSTGRES_PASSWORD: ${DATABASE_PASSWORD:-admin}
12
+ volumes:
13
+ - postgres_data:/var/lib/postgresql/data
14
+
15
+ redis:
16
+ image: redis:8-alpine
17
+ ports:
18
+ - '6379:6379'
19
+ command: ['redis-server', '--appendonly', 'yes']
20
+ volumes:
21
+ - redis_data:/data
22
+
23
+ studio:
24
+ image: ghcr.io/loopstack-ai/loopstack-studio:latest
25
+ pull_policy: always
26
+ ports:
27
+ - '5173:3000'
28
+ environment:
29
+ VITE_API_URL: ${VITE_API_URL:-http://localhost:3000}
30
+
31
+ volumes:
32
+ postgres_data:
33
+ redis_data:
@@ -0,0 +1,16 @@
1
+ # All values are optional — the defaults below match docker-compose.yml.
2
+
3
+ # PORT=3000
4
+
5
+ # DATABASE_HOST=localhost
6
+ # DATABASE_PORT=5432
7
+ # DATABASE_USERNAME=postgres
8
+ # DATABASE_PASSWORD=admin
9
+ # DATABASE_NAME=postgres
10
+
11
+ # REDIS_HOST=localhost
12
+ # REDIS_PORT=6379
13
+ # REDIS_PASSWORD=
14
+
15
+ # API keys for LLM feature modules (e.g. @loopstack/claude-module)
16
+ # ANTHROPIC_API_KEY=
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true,
7
+ "assets": ["**/*.yaml"],
8
+ "watchAssets": true
9
+ }
10
+ }
@@ -0,0 +1,8 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { LoopstackModule } from '@loopstack/loopstack-module';
3
+ import { HelloModule } from './hello/hello.module';
4
+
5
+ @Module({
6
+ imports: [LoopstackModule.forRoot(), HelloModule],
7
+ })
8
+ export class AppModule {}
@@ -0,0 +1,12 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { StudioApp } from '@loopstack/common';
3
+ import { HelloWorkflow } from './hello.workflow';
4
+
5
+ @StudioApp({
6
+ title: 'My First App',
7
+ workflows: [HelloWorkflow],
8
+ })
9
+ @Module({
10
+ providers: [HelloWorkflow],
11
+ })
12
+ export class HelloModule {}
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import { BaseWorkflow, MessageDocument, Transition, Workflow } from '@loopstack/common';
3
+ import type { RunContext } from '@loopstack/common';
4
+
5
+ const InputSchema = z.object({
6
+ name: z.string().default('World'),
7
+ });
8
+
9
+ type InputArgs = z.infer<typeof InputSchema>;
10
+
11
+ @Workflow({
12
+ title: 'Hello World',
13
+ description: 'Greets you by name — replace this with your first real workflow.',
14
+ schema: InputSchema,
15
+ })
16
+ export class HelloWorkflow extends BaseWorkflow<InputArgs> {
17
+ @Transition({ from: 'start', to: 'end' })
18
+ async greet(_state: unknown, ctx: RunContext<InputArgs>) {
19
+ const greeting = `Hello, ${ctx.args.name}! 👋`;
20
+
21
+ await this.documentStore.save(MessageDocument, {
22
+ role: 'assistant',
23
+ text: greeting,
24
+ });
25
+
26
+ this.assignResult({ greeting });
27
+ }
28
+ }
@@ -0,0 +1,11 @@
1
+ import { NestFactory } from '@nestjs/core';
2
+ import { AppModule } from './app.module';
3
+
4
+ async function bootstrap() {
5
+ const app = await NestFactory.create(AppModule);
6
+ // Graceful shutdown: on SIGTERM/SIGINT the BullMQ worker finishes in-flight
7
+ // transitions before the process exits, instead of stalling them.
8
+ app.enableShutdownHooks();
9
+ await app.listen(process.env.PORT ?? 3000);
10
+ }
11
+ void bootstrap();
package/package.json CHANGED
@@ -1,15 +1,41 @@
1
1
  {
2
2
  "name": "@loopstack/cli",
3
- "version": "0.15.1",
4
- "description": "Global wrapper for Loopstack local commands",
3
+ "displayName": "Loopstack CLI",
4
+ "description": "Run, trace, and watch Loopstack workflows from the terminal",
5
+ "version": "0.18.0",
6
+ "license": "MIT",
7
+ "author": {
8
+ "name": "Jakob Klippel",
9
+ "url": "https://www.linkedin.com/in/jakob-klippel/"
10
+ },
11
+ "type": "module",
5
12
  "bin": {
6
- "loopstack": "./bin/loopstack.js"
13
+ "loopstack": "dist/main.js"
7
14
  },
8
- "files": [
9
- "bin"
10
- ],
11
- "keywords": ["loopstack", "cli"],
15
+ "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
12
17
  "engines": {
13
- "node": ">=16"
14
- }
15
- }
18
+ "node": ">=20"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.build.json",
22
+ "compile": "tsc --noEmit",
23
+ "format": "prettier --write .",
24
+ "lint": "eslint .",
25
+ "test": "vitest run",
26
+ "watch": "tsc -p tsconfig.build.json --watch"
27
+ },
28
+ "dependencies": {
29
+ "@loopstack/client": "^0.38.0",
30
+ "@loopstack/contracts": "^0.38.0",
31
+ "commander": "^15.0.0",
32
+ "picocolors": "^1.1.1"
33
+ },
34
+ "devDependencies": {
35
+ "vitest": "^4.1.6"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "fixtures"
40
+ ]
41
+ }
package/bin/loopstack.js DELETED
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const { spawnSync } = require('child_process');
4
- const fs = require('fs');
5
- const path = require('path');
6
-
7
- function findPackageJson() {
8
- let dir = process.cwd();
9
- const root = path.parse(dir).root;
10
-
11
- while (dir !== root) {
12
- if (fs.existsSync(path.join(dir, 'package.json'))) {
13
- return dir;
14
- }
15
- dir = path.dirname(dir);
16
- }
17
-
18
- return null;
19
- }
20
-
21
- const projectRoot = findPackageJson();
22
-
23
- if (!projectRoot) {
24
- console.error('ERROR: No package.json found');
25
- process.exit(1);
26
- }
27
-
28
- const result = spawnSync('npm', ['run', 'loopstack', '--', ...process.argv.slice(2)], {
29
- cwd: projectRoot,
30
- stdio: 'inherit'
31
- });
32
-
33
- process.exit(result.status || 0);