@loopstack/cli 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.
- package/README.md +61 -0
- package/dist/commands/create.d.ts +3 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +80 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/env.d.ts +3 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +42 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +32 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +62 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +87 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/runs.d.ts +3 -0
- package/dist/commands/runs.d.ts.map +1 -0
- package/dist/commands/runs.js +147 -0
- package/dist/commands/runs.js.map +1 -0
- package/dist/commands/watch.d.ts +3 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +48 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/config/config.d.ts +14 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +25 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/resolve.d.ts +18 -0
- package/dist/config/resolve.d.ts.map +1 -0
- package/dist/config/resolve.js +57 -0
- package/dist/config/resolve.js.map +1 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +36 -0
- package/dist/errors.js.map +1 -0
- package/dist/hitl/discovery.d.ts +15 -0
- package/dist/hitl/discovery.d.ts.map +1 -0
- package/dist/hitl/discovery.js +53 -0
- package/dist/hitl/discovery.js.map +1 -0
- package/dist/hitl/editor.d.ts +4 -0
- package/dist/hitl/editor.d.ts.map +1 -0
- package/dist/hitl/editor.js +73 -0
- package/dist/hitl/editor.js.map +1 -0
- package/dist/hitl/idle-handler.d.ts +8 -0
- package/dist/hitl/idle-handler.d.ts.map +1 -0
- package/dist/hitl/idle-handler.js +70 -0
- package/dist/hitl/idle-handler.js.map +1 -0
- package/dist/hitl/prompt.d.ts +14 -0
- package/dist/hitl/prompt.d.ts.map +1 -0
- package/dist/hitl/prompt.js +117 -0
- package/dist/hitl/prompt.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +33 -0
- package/dist/main.js.map +1 -0
- package/dist/output/format.d.ts +6 -0
- package/dist/output/format.d.ts.map +1 -0
- package/dist/output/format.js +39 -0
- package/dist/output/format.js.map +1 -0
- package/dist/output/studio-link.d.ts +4 -0
- package/dist/output/studio-link.d.ts.map +1 -0
- package/dist/output/studio-link.js +17 -0
- package/dist/output/studio-link.js.map +1 -0
- package/dist/run/args.d.ts +2 -0
- package/dist/run/args.d.ts.map +1 -0
- package/dist/run/args.js +41 -0
- package/dist/run/args.js.map +1 -0
- package/dist/run/follow.d.ts +13 -0
- package/dist/run/follow.d.ts.map +1 -0
- package/dist/run/follow.js +130 -0
- package/dist/run/follow.js.map +1 -0
- package/dist/run/workspace.d.ts +3 -0
- package/dist/run/workspace.d.ts.map +1 -0
- package/dist/run/workspace.js +26 -0
- package/dist/run/workspace.js.map +1 -0
- package/fixtures/app/CLAUDE.md +40 -0
- package/fixtures/app/README.md +21 -0
- package/fixtures/app/docker-compose.yml +33 -0
- package/fixtures/app/env.example +16 -0
- package/fixtures/app/nest-cli.json +10 -0
- package/fixtures/app/src/app.module.ts +8 -0
- package/fixtures/app/src/hello/hello.module.ts +12 -0
- package/fixtures/app/src/hello/hello.workflow.ts +28 -0
- package/fixtures/app/src/main.ts +8 -0
- package/package.json +41 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { stdin } from 'node:process';
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { buildFormSkeleton, describeFormFields, editPayloadInEditor } from './editor.js';
|
|
5
|
+
function availableTransitionIds(prompt) {
|
|
6
|
+
return prompt.workflow.availableTransitions?.map((transition) => transition.id) ?? [];
|
|
7
|
+
}
|
|
8
|
+
function resolveTransitionId(prompt) {
|
|
9
|
+
const available = availableTransitionIds(prompt);
|
|
10
|
+
const configured = prompt.widget?.options?.transition;
|
|
11
|
+
if (configured && available.includes(configured))
|
|
12
|
+
return configured;
|
|
13
|
+
if (available.length === 1)
|
|
14
|
+
return available[0];
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
function formProperties(prompt) {
|
|
18
|
+
const schema = prompt.widget?.schema;
|
|
19
|
+
if (schema?.type !== 'object')
|
|
20
|
+
return {};
|
|
21
|
+
return schema.properties ?? {};
|
|
22
|
+
}
|
|
23
|
+
export async function promptForAnswer(prompt, out, options = {}) {
|
|
24
|
+
const rl = createInterface({ input: stdin, output: out });
|
|
25
|
+
const ask = (query) => rl.question(query, { signal: options.signal });
|
|
26
|
+
try {
|
|
27
|
+
const widget = prompt.widget?.widget;
|
|
28
|
+
const content = (prompt.document?.content ?? {});
|
|
29
|
+
if (widget === 'text-prompt' || widget === 'confirm-prompt' || widget === 'choices') {
|
|
30
|
+
const transitionId = resolveTransitionId(prompt);
|
|
31
|
+
if (!transitionId)
|
|
32
|
+
return await pickTransition(prompt, ask, out);
|
|
33
|
+
out.write(`\n${String(content.question ?? 'Input required')}\n`);
|
|
34
|
+
if (widget === 'confirm-prompt') {
|
|
35
|
+
const raw = (await ask(`${pc.bold('[y]es / [n]o:')} `)).trim().toLowerCase();
|
|
36
|
+
return { transitionId, payload: { answer: raw === 'y' || raw === 'yes' ? 'yes' : 'no' } };
|
|
37
|
+
}
|
|
38
|
+
if (widget === 'choices') {
|
|
39
|
+
const choices = Array.isArray(content.options) ? content.options : [];
|
|
40
|
+
const allowCustom = content.allowCustomAnswer === true;
|
|
41
|
+
choices.forEach((option, index) => out.write(` ${pc.bold(String(index + 1))}. ${option}\n`));
|
|
42
|
+
if (allowCustom)
|
|
43
|
+
out.write(pc.dim(' (or type a custom answer)\n'));
|
|
44
|
+
const raw = (await ask(`${pc.bold('answer:')} `)).trim();
|
|
45
|
+
const byNumber = choices[Number(raw) - 1];
|
|
46
|
+
const answer = byNumber ?? (allowCustom || choices.includes(raw) ? raw : undefined);
|
|
47
|
+
if (!answer)
|
|
48
|
+
return undefined;
|
|
49
|
+
return { transitionId, payload: { answer } };
|
|
50
|
+
}
|
|
51
|
+
const answer = (await ask(`${pc.bold('>')} `)).trim();
|
|
52
|
+
if (!answer)
|
|
53
|
+
return undefined;
|
|
54
|
+
return { transitionId, payload: { answer } };
|
|
55
|
+
}
|
|
56
|
+
if (widget === 'form') {
|
|
57
|
+
const actions = (prompt.widget?.options?.actions ?? []).filter((action) => action.transition && availableTransitionIds(prompt).includes(action.transition));
|
|
58
|
+
if (actions.length === 0)
|
|
59
|
+
return await pickTransition(prompt, ask, out);
|
|
60
|
+
const markdown = typeof content.markdown === 'string' ? content.markdown : undefined;
|
|
61
|
+
const title = prompt.widget?.options?.title ?? prompt.document?.documentName;
|
|
62
|
+
out.write(`\n${markdown ?? pc.bold(title ?? 'Form')}\n`);
|
|
63
|
+
const properties = formProperties(prompt);
|
|
64
|
+
const hasFields = Object.keys(properties).length > 0;
|
|
65
|
+
if (hasFields) {
|
|
66
|
+
if (!options.useEditor && options.studioUrl) {
|
|
67
|
+
out.write(`${pc.dim('⧉ answer in Studio:')} ${options.studioUrl}\n`);
|
|
68
|
+
out.write(pc.dim('waiting for the answer — Ctrl+C to detach, or rerun with --editor to answer here\n'));
|
|
69
|
+
return 'handoff';
|
|
70
|
+
}
|
|
71
|
+
describeFormFields(prompt.widget?.schema).forEach((line) => out.write(pc.dim(`${line}\n`)));
|
|
72
|
+
out.write(pc.dim('opening $EDITOR — save to submit, empty the file to cancel\n'));
|
|
73
|
+
const payload = editPayloadInEditor(buildFormSkeleton(prompt.document?.content, prompt.widget?.schema), out);
|
|
74
|
+
if (payload === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
const action = await pickFormAction(actions, ask, out);
|
|
77
|
+
if (!action)
|
|
78
|
+
return undefined;
|
|
79
|
+
return { transitionId: action.transition, payload };
|
|
80
|
+
}
|
|
81
|
+
const action = await pickFormAction(actions, ask, out);
|
|
82
|
+
if (!action)
|
|
83
|
+
return undefined;
|
|
84
|
+
return { transitionId: action.transition, payload: {} };
|
|
85
|
+
}
|
|
86
|
+
return await pickTransition(prompt, ask, out);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
rl.close();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function pickFormAction(actions, ask, out) {
|
|
93
|
+
actions.forEach((action, index) => out.write(` ${pc.bold(String(index + 1))}. ${action.label ?? action.transition}\n`));
|
|
94
|
+
const raw = (await ask(`${pc.bold('action:')} `)).trim();
|
|
95
|
+
return actions[Number(raw) - 1] ?? actions.find((candidate) => candidate.label === raw);
|
|
96
|
+
}
|
|
97
|
+
async function pickTransition(prompt, ask, out) {
|
|
98
|
+
const available = availableTransitionIds(prompt);
|
|
99
|
+
if (available.length === 0)
|
|
100
|
+
return undefined;
|
|
101
|
+
out.write(`\nRun is waiting — available transitions:\n`);
|
|
102
|
+
available.forEach((id, index) => out.write(` ${pc.bold(String(index + 1))}. ${id}\n`));
|
|
103
|
+
const raw = (await ask(`${pc.bold('transition:')} `)).trim();
|
|
104
|
+
const transitionId = available[Number(raw) - 1] ?? (available.includes(raw) ? raw : undefined);
|
|
105
|
+
if (!transitionId)
|
|
106
|
+
return undefined;
|
|
107
|
+
return { transitionId, payload: {} };
|
|
108
|
+
}
|
|
109
|
+
export function describePrompt(prompt) {
|
|
110
|
+
const content = (prompt.document?.content ?? {});
|
|
111
|
+
if (typeof content.question === 'string')
|
|
112
|
+
return content.question;
|
|
113
|
+
if (typeof content.markdown === 'string')
|
|
114
|
+
return content.markdown.split('\n')[0];
|
|
115
|
+
return `transitions: ${availableTransitionIds(prompt).join(', ')}`;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/hitl/prompt.ts"],"names":[],"mappings":"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"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { registerCreateCommand } from './commands/create.js';
|
|
5
|
+
import { registerEnvCommand } from './commands/env.js';
|
|
6
|
+
import { registerListCommand } from './commands/list.js';
|
|
7
|
+
import { registerLoginCommand } from './commands/login.js';
|
|
8
|
+
import { registerRunCommand } from './commands/run.js';
|
|
9
|
+
import { registerRunsCommand } from './commands/runs.js';
|
|
10
|
+
import { registerWatchCommand } from './commands/watch.js';
|
|
11
|
+
import { handleError } from './errors.js';
|
|
12
|
+
const { version } = createRequire(import.meta.url)('../package.json');
|
|
13
|
+
const program = new Command('loopstack')
|
|
14
|
+
.description('Scaffold, run, inspect, and watch Loopstack workflows from the terminal')
|
|
15
|
+
.version(version)
|
|
16
|
+
.option('--env <name>', 'named environment from ~/.loopstack/config.json')
|
|
17
|
+
.option('--url <url>', 'backend URL (overrides --env and the config file)')
|
|
18
|
+
.option('--token <token>', 'API token (lsk_…); LOOPSTACK_TOKEN works too')
|
|
19
|
+
.option('--json', 'machine-readable JSON output on stdout');
|
|
20
|
+
registerCreateCommand(program);
|
|
21
|
+
registerLoginCommand(program);
|
|
22
|
+
registerEnvCommand(program);
|
|
23
|
+
registerRunCommand(program);
|
|
24
|
+
registerRunsCommand(program);
|
|
25
|
+
registerWatchCommand(program);
|
|
26
|
+
registerListCommand(program);
|
|
27
|
+
try {
|
|
28
|
+
await program.parseAsync();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
handleError(error);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAwB,CAAC;AAE7F,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,WAAW,CAAC;KACrC,WAAW,CAAC,yEAAyE,CAAC;KACtF,OAAO,CAAC,OAAO,CAAC;KAChB,MAAM,CAAC,cAAc,EAAE,iDAAiD,CAAC;KACzE,MAAM,CAAC,aAAa,EAAE,mDAAmD,CAAC;KAC1E,MAAM,CAAC,iBAAiB,EAAE,8CAA8C,CAAC;KACzE,MAAM,CAAC,QAAQ,EAAE,wCAAwC,CAAC,CAAC;AAE9D,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAE7B,IAAI,CAAC;IACH,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;AAC7B,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,WAAW,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function renderTable(headers: string[], rows: string[][]): string;
|
|
2
|
+
export declare function colorStatus(status: string): string;
|
|
3
|
+
export declare function formatDuration(ms: number): string;
|
|
4
|
+
export declare function printData(text: string): void;
|
|
5
|
+
export declare function printStatus(text: string): void;
|
|
6
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/output/format.ts"],"names":[],"mappings":"AAIA,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAUvE;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAclD;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAIjD;AAGD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9C"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { WorkflowState } from '@loopstack/contracts/enums';
|
|
3
|
+
export function renderTable(headers, rows) {
|
|
4
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => (row[column] ?? '').length)));
|
|
5
|
+
const renderRow = (cells) => widths
|
|
6
|
+
.map((width, column) => (cells[column] ?? '').padEnd(width))
|
|
7
|
+
.join(' ')
|
|
8
|
+
.trimEnd();
|
|
9
|
+
return [pc.dim(renderRow(headers)), ...rows.map(renderRow)].join('\n');
|
|
10
|
+
}
|
|
11
|
+
export function colorStatus(status) {
|
|
12
|
+
switch (status) {
|
|
13
|
+
case WorkflowState.Completed:
|
|
14
|
+
return pc.green(status);
|
|
15
|
+
case WorkflowState.Failed:
|
|
16
|
+
return pc.red(status);
|
|
17
|
+
case WorkflowState.Waiting:
|
|
18
|
+
case WorkflowState.Paused:
|
|
19
|
+
return pc.yellow(status);
|
|
20
|
+
case WorkflowState.Running:
|
|
21
|
+
return pc.cyan(status);
|
|
22
|
+
default:
|
|
23
|
+
return status;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function formatDuration(ms) {
|
|
27
|
+
if (ms < 1000)
|
|
28
|
+
return `${Math.round(ms)}ms`;
|
|
29
|
+
if (ms < 60_000)
|
|
30
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
31
|
+
return `${Math.floor(ms / 60_000)}m ${Math.round((ms % 60_000) / 1000)}s`;
|
|
32
|
+
}
|
|
33
|
+
export function printData(text) {
|
|
34
|
+
console.log(text);
|
|
35
|
+
}
|
|
36
|
+
export function printStatus(text) {
|
|
37
|
+
console.error(text);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/output/format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAG3D,MAAM,UAAU,WAAW,CAAC,OAAiB,EAAE,IAAgB;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAC5C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAC1E,CAAC;IACF,MAAM,SAAS,GAAG,CAAC,KAAe,EAAE,EAAE,CACpC,MAAM;SACH,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;SAC3D,IAAI,CAAC,IAAI,CAAC;SACV,OAAO,EAAE,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,aAAa,CAAC,SAAS;YAC1B,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1B,KAAK,aAAa,CAAC,MAAM;YACvB,OAAO,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxB,KAAK,aAAa,CAAC,OAAO,CAAC;QAC3B,KAAK,aAAa,CAAC,MAAM;YACvB,OAAO,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3B,KAAK,aAAa,CAAC,OAAO;YACxB,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzB;YACE,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC;IAC5C,IAAI,EAAE,GAAG,MAAM;QAAE,OAAO,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACrD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;AAC5E,CAAC;AAGD,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ResolvedConnection } from '../config/resolve.js';
|
|
2
|
+
export declare function studioRunUrl(connection: ResolvedConnection, workflowId: string): string | undefined;
|
|
3
|
+
export declare function openInBrowser(url: string): void;
|
|
4
|
+
//# sourceMappingURL=studio-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studio-link.d.ts","sourceRoot":"","sources":["../../src/output/studio-link.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,wBAAgB,YAAY,CAAC,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGnG;AAGD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAU/C"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export function studioRunUrl(connection, workflowId) {
|
|
3
|
+
if (!connection.studioUrl)
|
|
4
|
+
return undefined;
|
|
5
|
+
return `${connection.studioUrl.replace(/\/+$/, '')}/workflows/${workflowId}`;
|
|
6
|
+
}
|
|
7
|
+
export function openInBrowser(url) {
|
|
8
|
+
const [command, args] = process.platform === 'darwin'
|
|
9
|
+
? ['open', [url]]
|
|
10
|
+
: process.platform === 'win32'
|
|
11
|
+
? ['cmd', ['/c', 'start', '', url]]
|
|
12
|
+
: ['xdg-open', [url]];
|
|
13
|
+
spawn(command, args, { detached: true, stdio: 'ignore' })
|
|
14
|
+
.on('error', () => { })
|
|
15
|
+
.unref();
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=studio-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studio-link.js","sourceRoot":"","sources":["../../src/output/studio-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAI3C,MAAM,UAAU,YAAY,CAAC,UAA8B,EAAE,UAAkB;IAC7E,IAAI,CAAC,UAAU,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5C,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,cAAc,UAAU,EAAE,CAAC;AAC/E,CAAC;AAGD,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GACnB,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC3B,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC5B,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,KAAK,CAAC,OAAiB,EAAE,IAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;SAC5E,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC;SACrB,KAAK,EAAE,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/run/args.ts"],"names":[],"mappings":"AAUA,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAA6C,EACzE,SAAS,GAAE,MAAM,MAAsC,GACtD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAczB"}
|
package/dist/run/args.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { CliError } from '../errors.js';
|
|
3
|
+
export function parseRunArgs(pairs, readFile = (path) => readFileSync(path, 'utf8'), readStdin = () => readFileSync(0, 'utf8')) {
|
|
4
|
+
const args = {};
|
|
5
|
+
for (const pair of pairs) {
|
|
6
|
+
const separator = pair.indexOf('=');
|
|
7
|
+
if (separator <= 0) {
|
|
8
|
+
throw new CliError(`Invalid --arg "${pair}" — expected key=value or key=@file.`);
|
|
9
|
+
}
|
|
10
|
+
const key = pair.slice(0, separator);
|
|
11
|
+
const raw = pair.slice(separator + 1);
|
|
12
|
+
const value = raw === '@-' ? readStdinArg(readStdin) : raw.startsWith('@') ? readArgFile(raw.slice(1), readFile) : raw;
|
|
13
|
+
args[key] = coerce(value);
|
|
14
|
+
}
|
|
15
|
+
return args;
|
|
16
|
+
}
|
|
17
|
+
function readStdinArg(readStdin) {
|
|
18
|
+
try {
|
|
19
|
+
return readStdin();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new CliError('Cannot read --arg value from stdin (@-).');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function readArgFile(path, readFile) {
|
|
26
|
+
try {
|
|
27
|
+
return readFile(path);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new CliError(`Cannot read --arg file "${path}".`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function coerce(raw) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return raw;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/run/args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AASxC,MAAM,UAAU,YAAY,CAC1B,KAAe,EACf,WAAqC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EACzE,YAA0B,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC;IAEvD,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,MAAM,IAAI,QAAQ,CAAC,kBAAkB,IAAI,sCAAsC,CAAC,CAAC;QACnF,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QACtC,MAAM,KAAK,GACT,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAC3G,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,YAAY,CAAC,SAAuB;IAC3C,IAAI,CAAC;QACH,OAAO,SAAS,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,0CAA0C,CAAC,CAAC;IACjE,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,QAAkC;IACnE,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,2BAA2B,IAAI,IAAI,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,GAAW;IACzB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { WorkflowState } from '@loopstack/contracts/enums';
|
|
2
|
+
import type { ClientMessage } from '@loopstack/contracts/events';
|
|
3
|
+
export interface FollowOutcome {
|
|
4
|
+
status: WorkflowState;
|
|
5
|
+
durationMs: number;
|
|
6
|
+
}
|
|
7
|
+
export type IdleOutcome = 'answered' | 'external' | 'stop';
|
|
8
|
+
export interface FollowOptions {
|
|
9
|
+
onIdle?: (signal: AbortSignal, onPromptWorkflow: (id: string) => void) => Promise<IdleOutcome>;
|
|
10
|
+
initiallyIdle?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function followRun(events: AsyncIterableIterator<ClientMessage>, workflowId: string, out: NodeJS.WritableStream, options?: FollowOptions): Promise<FollowOutcome>;
|
|
13
|
+
//# sourceMappingURL=follow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"follow.d.ts","sourceRoot":"","sources":["../../src/run/follow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIjE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAQD,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;AAE3D,MAAM,WAAW,aAAa;IAQ5B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAE/F,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AA0BD,wBAAsB,SAAS,CAC7B,MAAM,EAAE,qBAAqB,CAAC,aAAa,CAAC,EAC5C,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,CAAC,cAAc,EAC1B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAwHxB"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { WorkflowState } from '@loopstack/contracts/enums';
|
|
3
|
+
import { CliError } from '../errors.js';
|
|
4
|
+
import { formatDuration } from '../output/format.js';
|
|
5
|
+
const TERMINAL_STATES = [
|
|
6
|
+
WorkflowState.Completed,
|
|
7
|
+
WorkflowState.Failed,
|
|
8
|
+
WorkflowState.Canceled,
|
|
9
|
+
];
|
|
10
|
+
const IDLE_STATES = [WorkflowState.Waiting, WorkflowState.Paused];
|
|
11
|
+
export async function followRun(events, workflowId, out, options = {}) {
|
|
12
|
+
const startedAt = Date.now();
|
|
13
|
+
let currentPlace;
|
|
14
|
+
let placeEnteredAt = startedAt;
|
|
15
|
+
let streaming = false;
|
|
16
|
+
const endTokenLine = () => {
|
|
17
|
+
if (!streaming)
|
|
18
|
+
return;
|
|
19
|
+
out.write('\n');
|
|
20
|
+
streaming = false;
|
|
21
|
+
};
|
|
22
|
+
const finishStep = (mark = pc.green('✓')) => {
|
|
23
|
+
if (!currentPlace)
|
|
24
|
+
return;
|
|
25
|
+
endTokenLine();
|
|
26
|
+
out.write(`${mark} ${currentPlace} ${pc.dim(`(${formatDuration(Date.now() - placeEnteredAt)})`)}\n`);
|
|
27
|
+
currentPlace = undefined;
|
|
28
|
+
};
|
|
29
|
+
const outcome = (status) => ({ status, durationMs: Date.now() - startedAt });
|
|
30
|
+
const handleEvent = (event) => {
|
|
31
|
+
if (!('workflowId' in event) || event.workflowId !== workflowId)
|
|
32
|
+
return undefined;
|
|
33
|
+
switch (event.type) {
|
|
34
|
+
case 'llm.response.text_delta':
|
|
35
|
+
streaming = true;
|
|
36
|
+
out.write(event.delta);
|
|
37
|
+
break;
|
|
38
|
+
case 'llm.response.done':
|
|
39
|
+
case 'llm.response.error':
|
|
40
|
+
endTokenLine();
|
|
41
|
+
break;
|
|
42
|
+
case 'workflow.updated': {
|
|
43
|
+
if (event.place && event.place !== currentPlace) {
|
|
44
|
+
finishStep();
|
|
45
|
+
if (event.place !== 'end') {
|
|
46
|
+
currentPlace = event.place;
|
|
47
|
+
placeEnteredAt = Date.now();
|
|
48
|
+
if (!TERMINAL_STATES.includes(event.status)) {
|
|
49
|
+
out.write(pc.dim(`▸ ${currentPlace}\n`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (TERMINAL_STATES.includes(event.status)) {
|
|
54
|
+
endTokenLine();
|
|
55
|
+
return outcome(event.status);
|
|
56
|
+
}
|
|
57
|
+
if (IDLE_STATES.includes(event.status)) {
|
|
58
|
+
finishStep(pc.yellow('⏸'));
|
|
59
|
+
return 'idle';
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
};
|
|
66
|
+
const startPrompt = () => {
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
const pending = { controller };
|
|
69
|
+
pending.result = options.onIdle(controller.signal, (id) => {
|
|
70
|
+
pending.workflowId = id;
|
|
71
|
+
});
|
|
72
|
+
return pending;
|
|
73
|
+
};
|
|
74
|
+
const resolvesPrompt = (prompt, event) => {
|
|
75
|
+
if (event.type !== 'workflow.updated' || !('workflowId' in event))
|
|
76
|
+
return false;
|
|
77
|
+
const promptWorkflowId = prompt.workflowId ?? workflowId;
|
|
78
|
+
return event.workflowId === promptWorkflowId && !IDLE_STATES.includes(event.status);
|
|
79
|
+
};
|
|
80
|
+
let prompt = options.initiallyIdle && options.onIdle ? startPrompt() : null;
|
|
81
|
+
let pendingNext = null;
|
|
82
|
+
const pullNext = () => (pendingNext ??= events.next());
|
|
83
|
+
while (true) {
|
|
84
|
+
let event;
|
|
85
|
+
if (prompt) {
|
|
86
|
+
const raced = await Promise.race([
|
|
87
|
+
pullNext().then((result) => ({ kind: 'event', result })),
|
|
88
|
+
prompt.result.then((idle) => ({ kind: 'idle', idle })),
|
|
89
|
+
]);
|
|
90
|
+
if (raced.kind === 'idle') {
|
|
91
|
+
prompt = null;
|
|
92
|
+
if (raced.idle === 'stop')
|
|
93
|
+
return outcome(WorkflowState.Waiting);
|
|
94
|
+
if (raced.idle === 'external')
|
|
95
|
+
out.write(`${pc.green('✓')} answered in Studio\n`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
pendingNext = null;
|
|
99
|
+
if (raced.result.done)
|
|
100
|
+
throw new CliError('Event stream ended before the run reached a final state.');
|
|
101
|
+
event = raced.result.value;
|
|
102
|
+
if (resolvesPrompt(prompt, event)) {
|
|
103
|
+
prompt.controller.abort();
|
|
104
|
+
const idle = await prompt.result;
|
|
105
|
+
prompt = null;
|
|
106
|
+
if (idle === 'stop')
|
|
107
|
+
return outcome(WorkflowState.Waiting);
|
|
108
|
+
if (idle === 'external')
|
|
109
|
+
out.write(`${pc.green('✓')} answered in Studio\n`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const { value, done } = await pullNext();
|
|
114
|
+
pendingNext = null;
|
|
115
|
+
if (done)
|
|
116
|
+
throw new CliError('Event stream ended before the run reached a final state.');
|
|
117
|
+
event = value;
|
|
118
|
+
}
|
|
119
|
+
const handled = handleEvent(event);
|
|
120
|
+
if (handled === 'idle') {
|
|
121
|
+
if (!options.onIdle)
|
|
122
|
+
return outcome(WorkflowState.Waiting);
|
|
123
|
+
prompt ??= startPrompt();
|
|
124
|
+
}
|
|
125
|
+
else if (handled) {
|
|
126
|
+
return handled;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=follow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"follow.js","sourceRoot":"","sources":["../../src/run/follow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/run/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AASzD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,eAAe,EACvB,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SortOrder } from '@loopstack/contracts/enums';
|
|
2
|
+
import { CliError } from '../errors.js';
|
|
3
|
+
export async function resolveWorkspaceId(client, workflowName, explicit, pinned) {
|
|
4
|
+
if (explicit)
|
|
5
|
+
return explicit;
|
|
6
|
+
if (pinned)
|
|
7
|
+
return pinned;
|
|
8
|
+
const apps = await client.config.apps();
|
|
9
|
+
const app = apps.find((candidate) => candidate.workflows?.some((workflow) => workflow.workflowName === workflowName));
|
|
10
|
+
if (!app) {
|
|
11
|
+
const available = apps.flatMap((candidate) => candidate.workflows?.map((w) => w.workflowName) ?? []);
|
|
12
|
+
throw new CliError(`Unknown workflow "${workflowName}". Available workflows: ${available.sort().join(', ') || '(none)'}`);
|
|
13
|
+
}
|
|
14
|
+
const page = await client.workspaces.list({
|
|
15
|
+
filter: { appName: app.appName },
|
|
16
|
+
sortBy: [{ field: 'createdAt', order: SortOrder.DESC }],
|
|
17
|
+
page: 0,
|
|
18
|
+
limit: 1,
|
|
19
|
+
});
|
|
20
|
+
const existing = page.data[0];
|
|
21
|
+
if (existing)
|
|
22
|
+
return existing.id;
|
|
23
|
+
const created = await client.workspaces.create({ title: 'CLI', appName: app.appName });
|
|
24
|
+
return created.id;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/run/workspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAOxC,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAuB,EACvB,YAAoB,EACpB,QAAiB,EACjB,MAAe;IAEf,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,KAAK,YAAY,CAAC,CAAC,CAAC;IACtH,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;QACrG,MAAM,IAAI,QAAQ,CAChB,qBAAqB,YAAY,2BAA2B,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE,CACtG,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;QACxC,MAAM,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE;QAChC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC;QACvD,IAAI,EAAE,CAAC;QACP,KAAK,EAAE,CAAC;KACT,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,EAAE,CAAC;IAEjC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACvF,OAAO,OAAO,CAAC,EAAE,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
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> --follow # audit trail; reattach to a waiting run and answer prompts
|
|
21
|
+
loopstack watch --json # NDJSON event firehose while you develop
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
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.
|
|
25
|
+
|
|
26
|
+
## Writing workflows
|
|
27
|
+
|
|
28
|
+
Follow `src/hello/hello.workflow.ts` as the canonical example:
|
|
29
|
+
|
|
30
|
+
- 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.
|
|
31
|
+
- 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`.
|
|
32
|
+
- Transitions return nothing — mutate via `this.assignState(partial)` and publish results via `this.assignResult(partial)`. Use `async` only when the body awaits.
|
|
33
|
+
- Documents are classes with `@Document({ schema })` (zod) saved via `this.documentStore.save(SomeDocument, data)` — they are what Studio renders.
|
|
34
|
+
- Tools are NestJS providers with `@Tool({ name })`, implementing `protected async handle(args, ctx, options?): Promise<ToolEnvelope>`.
|
|
35
|
+
|
|
36
|
+
## Learning more
|
|
37
|
+
|
|
38
|
+
- Docs: https://loopstack.ai/docs — for agents, the full corpus is at https://loopstack.ai/llms.txt (route via titles, fetch what you need)
|
|
39
|
+
- 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
|
|
40
|
+
- 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:
|