keystone-cli 0.1.0 → 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 (51) hide show
  1. package/README.md +326 -59
  2. package/package.json +1 -1
  3. package/src/cli.ts +90 -81
  4. package/src/db/workflow-db.ts +0 -7
  5. package/src/expression/evaluator.test.ts +42 -0
  6. package/src/expression/evaluator.ts +28 -0
  7. package/src/parser/agent-parser.test.ts +10 -0
  8. package/src/parser/agent-parser.ts +2 -1
  9. package/src/parser/config-schema.ts +13 -5
  10. package/src/parser/workflow-parser.ts +0 -5
  11. package/src/runner/llm-adapter.test.ts +0 -8
  12. package/src/runner/llm-adapter.ts +33 -10
  13. package/src/runner/llm-executor.test.ts +59 -18
  14. package/src/runner/llm-executor.ts +1 -1
  15. package/src/runner/mcp-client.test.ts +166 -88
  16. package/src/runner/mcp-client.ts +156 -22
  17. package/src/runner/mcp-manager.test.ts +73 -15
  18. package/src/runner/mcp-manager.ts +44 -18
  19. package/src/runner/mcp-server.test.ts +4 -1
  20. package/src/runner/mcp-server.ts +25 -11
  21. package/src/runner/shell-executor.ts +3 -3
  22. package/src/runner/step-executor.ts +10 -9
  23. package/src/runner/tool-integration.test.ts +21 -14
  24. package/src/runner/workflow-runner.ts +25 -5
  25. package/src/templates/agents/explore.md +54 -0
  26. package/src/templates/agents/general.md +8 -0
  27. package/src/templates/agents/keystone-architect.md +54 -0
  28. package/src/templates/agents/my-agent.md +3 -0
  29. package/src/templates/agents/summarizer.md +28 -0
  30. package/src/templates/agents/test-agent.md +10 -0
  31. package/src/templates/approval-process.yaml +36 -0
  32. package/src/templates/basic-inputs.yaml +19 -0
  33. package/src/templates/basic-shell.yaml +20 -0
  34. package/src/templates/batch-processor.yaml +43 -0
  35. package/src/templates/cleanup-finally.yaml +22 -0
  36. package/src/templates/composition-child.yaml +13 -0
  37. package/src/templates/composition-parent.yaml +14 -0
  38. package/src/templates/data-pipeline.yaml +38 -0
  39. package/src/templates/full-feature-demo.yaml +64 -0
  40. package/src/templates/human-interaction.yaml +12 -0
  41. package/src/templates/invalid.yaml +5 -0
  42. package/src/templates/llm-agent.yaml +8 -0
  43. package/src/templates/loop-parallel.yaml +37 -0
  44. package/src/templates/retry-policy.yaml +36 -0
  45. package/src/templates/scaffold-feature.yaml +48 -0
  46. package/src/templates/state.db +0 -0
  47. package/src/templates/state.db-shm +0 -0
  48. package/src/templates/state.db-wal +0 -0
  49. package/src/templates/stop-watch.yaml +17 -0
  50. package/src/templates/workflow.db +0 -0
  51. package/src/utils/config-loader.test.ts +2 -2
@@ -0,0 +1,43 @@
1
+ name: batch-processor
2
+ description: "Process multiple files in parallel"
3
+
4
+ inputs:
5
+ target_dir: { type: string, default: "./data" }
6
+
7
+ outputs:
8
+ processed_count: ${{ steps.process_files.outputs.length }}
9
+
10
+ env:
11
+ API_KEY: ${{ secrets.API_KEY }}
12
+
13
+ steps:
14
+ # 1. Dynamic Input Generation
15
+ - id: list_files
16
+ type: shell
17
+ run: "ls ${{ inputs.target_dir }}/*.txt"
18
+ # Extract stdout lines into an array
19
+ transform: "stdout.split('\\n').filter(Boolean)"
20
+
21
+ # 2. Matrix/Looping (The Missing Piece)
22
+ - id: process_files
23
+ type: llm
24
+ needs: [list_files]
25
+ # Iterates over the array from the previous step
26
+ foreach: ${{ steps.list_files.output }}
27
+ concurrency: 5
28
+ # Robustness (The Missing Piece)
29
+ retry:
30
+ count: 3
31
+ backoff: "exponential"
32
+ timeout: 30000 # 30s limit per item
33
+ # Step Definition
34
+ agent: summarizer
35
+ prompt: "Summarize this file: ${{ item }}"
36
+
37
+ # 3. Conditional Logic
38
+ - id: notify
39
+ type: request
40
+ needs: [process_files]
41
+ if: ${{ steps.process_files.items.every(s => s.status == 'success') }}
42
+ method: POST
43
+ url: "https://webhook.site/..."
@@ -0,0 +1,22 @@
1
+ name: cleanup-finally
2
+ description: "Test the finally block"
3
+
4
+ steps:
5
+ - id: step1
6
+ type: shell
7
+ run: echo "Main step 1"
8
+
9
+ - id: fail_step
10
+ type: shell
11
+ run: "exit 1"
12
+ if: ${{ inputs.should_fail }}
13
+
14
+ finally:
15
+ - id: cleanup
16
+ type: shell
17
+ run: echo "Cleanup task executed"
18
+
19
+ - id: cleanup_with_dep
20
+ type: shell
21
+ needs: [cleanup]
22
+ run: echo "Cleanup with dependency executed"
@@ -0,0 +1,13 @@
1
+ name: composition-child
2
+ description: "A simple child workflow"
3
+
4
+ inputs:
5
+ name: { type: string }
6
+
7
+ outputs:
8
+ result: "Hello from child, ${{ inputs.name }}!"
9
+
10
+ steps:
11
+ - id: echo_step
12
+ type: shell
13
+ run: echo "Processing ${{ inputs.name }}"
@@ -0,0 +1,14 @@
1
+ name: composition-parent
2
+ description: "A parent workflow that calls a child workflow"
3
+
4
+ steps:
5
+ - id: run_child
6
+ type: workflow
7
+ path: "workflows/composition-child.yaml"
8
+ inputs:
9
+ name: "Keystone User"
10
+
11
+ - id: print_result
12
+ type: shell
13
+ needs: [run_child]
14
+ run: echo "Child workflow result - ${{ steps.run_child.outputs.result }}"
@@ -0,0 +1,38 @@
1
+ name: data-pipeline
2
+ description: "A workflow that demonstrates file operations and cleanup with 'finally'"
3
+
4
+ inputs:
5
+ input_file: { type: string, default: "input.txt" }
6
+ output_file: { type: string, default: "output.txt" }
7
+
8
+ steps:
9
+ - id: prepare_data
10
+ type: file
11
+ op: write
12
+ path: ${{ inputs.input_file }}
13
+ content: "line 1\nline 2\nline 3"
14
+
15
+ - id: read_data
16
+ type: file
17
+ op: read
18
+ path: ${{ inputs.input_file }}
19
+ needs: [prepare_data]
20
+
21
+ - id: process_data
22
+ type: shell
23
+ run: |
24
+ echo "${{ steps.read_data.output }}" | tr '[:lower:]' '[:upper:]'
25
+ transform: "stdout.trim()"
26
+ needs: [read_data]
27
+
28
+ - id: save_result
29
+ type: file
30
+ op: write
31
+ path: ${{ inputs.output_file }}
32
+ content: ${{ steps.process_data.output }}
33
+ needs: [process_data]
34
+
35
+ finally:
36
+ - id: cleanup
37
+ type: shell
38
+ run: rm ${{ inputs.input_file }}
@@ -0,0 +1,64 @@
1
+ name: full-feature-demo
2
+ description: "A comprehensive workflow demonstrating multiple feature types"
3
+
4
+ inputs:
5
+ message:
6
+ type: string
7
+ default: "Hello from Keystone!"
8
+
9
+ outputs:
10
+ greeting: ${{ steps.greet.output }}
11
+ file_count: ${{ steps.count_files.output }}
12
+ timestamp: ${{ steps.get_date.output }}
13
+
14
+ steps:
15
+ # Test shell execution
16
+ - id: greet
17
+ type: shell
18
+ run: echo "${{ inputs.message }}"
19
+
20
+ # Test shell with transform
21
+ - id: get_date
22
+ type: shell
23
+ run: date +%s
24
+ transform: parseInt(stdout.trim())
25
+
26
+ # Test file write
27
+ - id: write_file
28
+ type: file
29
+ op: write
30
+ path: /tmp/keystone-test.txt
31
+ content: "Test file created at ${{ steps.get_date.output }}"
32
+
33
+ # Test file read
34
+ - id: read_file
35
+ type: file
36
+ op: read
37
+ path: /tmp/keystone-test.txt
38
+ needs: [write_file]
39
+
40
+ # Test shell with dependencies
41
+ - id: count_files
42
+ type: shell
43
+ needs: [write_file]
44
+ run: ls /tmp/keystone-*.txt | wc -l
45
+ transform: parseInt(stdout.trim())
46
+
47
+ # Test conditional execution
48
+ - id: success_message
49
+ type: shell
50
+ if: ${{ steps.count_files.output > 0 }}
51
+ needs: [count_files]
52
+ run: echo "Found files!"
53
+
54
+ # Test HTTP request
55
+ - id: api_test
56
+ type: request
57
+ url: https://httpbin.org/get
58
+ method: GET
59
+
60
+ # Test sleep
61
+ - id: wait
62
+ type: sleep
63
+ duration: 100
64
+ needs: [api_test]
@@ -0,0 +1,12 @@
1
+ name: human-interaction
2
+ description: A workflow that prompts the user for input and uses it in another step.
3
+
4
+ steps:
5
+ - id: ask_name
6
+ type: human
7
+ message: "What is your name?"
8
+ inputType: text
9
+
10
+ - id: greet
11
+ type: shell
12
+ run: echo "Hello, ${{ escape(steps.ask_name.output) }}!"
@@ -0,0 +1,5 @@
1
+ name: invalid-workflow
2
+ steps:
3
+ - id: step1
4
+ type: shell
5
+ # Missing 'run' property
@@ -0,0 +1,8 @@
1
+ name: llm-agent
2
+ description: "Test LLM step"
3
+
4
+ steps:
5
+ - id: ask_llm
6
+ type: llm
7
+ agent: summarizer
8
+ prompt: "Hello, who are you?"
@@ -0,0 +1,37 @@
1
+ name: loop-parallel
2
+ description: "Test the foreach race condition fix and .every() support"
3
+
4
+ outputs:
5
+ all_success: ${{ steps.process_items.items.every(s => s.status == 'success') }}
6
+ item_count: ${{ steps.process_items.outputs.length }}
7
+ first_output: ${{ steps.process_items.items[0].output }}
8
+
9
+ steps:
10
+ # Generate test data
11
+ - id: generate_items
12
+ type: shell
13
+ run: "echo 'item1\nitem2\nitem3\nitem4\nitem5'"
14
+ transform: "stdout.split('\\n').filter(Boolean)"
15
+
16
+ # Process items with concurrency (tests race condition fix)
17
+ - id: process_items
18
+ type: shell
19
+ needs: [generate_items]
20
+ foreach: ${{ steps.generate_items.output }}
21
+ concurrency: 3
22
+ run: "echo 'Processing: ${{ item }}' && sleep 0.1"
23
+ transform: "stdout.trim()"
24
+
25
+ # Test conditional using .every() (tests aggregation fix)
26
+ - id: success_notification
27
+ type: shell
28
+ needs: [process_items]
29
+ if: ${{ steps.process_items.items.every(s => s.status == 'success') }}
30
+ run: "echo 'All items processed successfully!'"
31
+
32
+ # Test accessing individual item status
33
+ - id: check_first
34
+ type: shell
35
+ needs: [process_items]
36
+ if: ${{ steps.process_items.items[0].status == 'success' }}
37
+ run: "echo 'First item was successful'"
@@ -0,0 +1,36 @@
1
+ name: retry-policy
2
+ description: "Test retry and timeout features"
3
+
4
+ steps:
5
+ # Test retry with a command that fails first few times
6
+ - id: flaky_command
7
+ type: shell
8
+ run: |
9
+ if [ ! -f /tmp/keystone-retry-test ]; then
10
+ echo "1" > /tmp/keystone-retry-test
11
+ exit 1
12
+ else
13
+ count=$(cat /tmp/keystone-retry-test)
14
+ count=$((count + 1))
15
+ echo $count > /tmp/keystone-retry-test
16
+ if [ $count -lt 3 ]; then
17
+ exit 1
18
+ fi
19
+ echo "Success after $count attempts"
20
+ fi
21
+ retry:
22
+ count: 3
23
+ backoff: exponential
24
+
25
+ # Test timeout (should complete before timeout)
26
+ - id: quick_task
27
+ type: shell
28
+ run: sleep 0.1 && echo "Completed"
29
+ timeout: 5000
30
+ needs: [flaky_command]
31
+
32
+ # Cleanup
33
+ - id: cleanup
34
+ type: shell
35
+ run: rm -f /tmp/keystone-retry-test
36
+ needs: [quick_task]
@@ -0,0 +1,48 @@
1
+ name: scaffold-feature
2
+ description: "Autonomously build new Keystone workflows and agents"
3
+
4
+ steps:
5
+ - id: get_requirements
6
+ type: human
7
+ message: "Describe the workflow you want to build:"
8
+ inputType: text
9
+
10
+ - id: design
11
+ type: llm
12
+ agent: keystone-architect
13
+ needs: [get_requirements]
14
+ prompt: |
15
+ The user wants to build the following:
16
+ <user_requirements>
17
+ ${{ steps.get_requirements.output }}
18
+ </user_requirements>
19
+ Generate the necessary Keystone workflow and optionally create an agent where appropriate.
20
+ schema:
21
+ type: object
22
+ properties:
23
+ files:
24
+ type: array
25
+ items:
26
+ type: object
27
+ properties:
28
+ path:
29
+ type: string
30
+ content:
31
+ type: string
32
+ required: [path, content]
33
+ required: [files]
34
+
35
+ - id: write_files
36
+ type: file
37
+ needs: [design]
38
+ foreach: ${{ steps.design.output.files }}
39
+ op: write
40
+ path: ${{ item.path }}
41
+ content: ${{ item.content }}
42
+
43
+ - id: summary
44
+ type: shell
45
+ needs: [write_files]
46
+ run: |
47
+ echo "Scaffolding complete. Files created:"
48
+ echo "${{ steps.design.output.files.map(f => f.path).join('\n') }}"
Binary file
Binary file
Binary file
@@ -0,0 +1,17 @@
1
+ name: stop-watch
2
+ description: "A simple stopwatch workflow"
3
+ steps:
4
+ - id: get_duration
5
+ type: human
6
+ message: "Please enter the duration (in seconds) for the stopwatch."
7
+ inputType: text
8
+ - id: waiting_period
9
+ type: sleep
10
+ needs: [get_duration]
11
+ duration: ${{ Number(steps.get_duration.output) * 1000 }}
12
+ - id: notify_completion
13
+ type: human
14
+ needs: [waiting_period]
15
+ message: "The stopwatch is complete."
16
+ inputType: confirm
17
+ outputs: {}
File without changes
@@ -1,6 +1,6 @@
1
- import { describe, expect, it, afterEach } from 'bun:test';
2
- import { ConfigLoader } from './config-loader';
1
+ import { afterEach, describe, expect, it } from 'bun:test';
3
2
  import type { Config } from '../parser/config-schema';
3
+ import { ConfigLoader } from './config-loader';
4
4
 
5
5
  describe('ConfigLoader', () => {
6
6
  afterEach(() => {