-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add an agent skill for working with setup chain #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+468
−39
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,7 +45,7 @@ pipeline { | |
| axes { | ||
| axis { | ||
| name 'NODE_VERSION' | ||
| values '12', '14', '16' | ||
| values '20', '22', '24' | ||
| } | ||
| } | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| 'use strict' | ||
|
|
||
| const {defineConfig} = require('eslint/config') | ||
| const logdna = require('eslint-config-logdna') | ||
|
|
||
| module.exports = defineConfig([ | ||
| { | ||
| 'extends': [logdna] | ||
| , 'ignores': ['skills/**'] | ||
| , 'languageOptions': { | ||
| ecmaVersion: 2022 | ||
| , sourceType: 'script' | ||
| , globals: { | ||
| fetch: 'readonly' | ||
| } | ||
| } | ||
| , 'rules': { | ||
| 'sensible/check-require': [2, 'always', { | ||
| root: __dirname | ||
| }] | ||
| } | ||
| } | ||
| ]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| --- | ||
| name: setup-chain | ||
| description: create new actions, functions or workflows for logdna/setup-chain | ||
| license: MIT | ||
| metadata: | ||
| author: Mezmo Inc,. | ||
| --- | ||
|
|
||
| # Setup-chain Agent | ||
|
|
||
| Adds new actions and functions to logdna setup-chain | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| 1. Confirm `node` executable is installed on the host | ||
| 2. Make sure there is a package.json in the current working directory | ||
| 3. package.json should have `@logdna/setup-chain` in dependencies or devDependencies | ||
|
|
||
| If any of the above are not met, explain what is missing. Do not proceed with custom action creation. | ||
|
|
||
| ## Creating a Custom Action | ||
|
|
||
| Follow these steps to create and wire up a custom action: | ||
|
|
||
| 1. **Define the action function** in an actions object | ||
| - **Requirement**: Actions MUST be `async` functions or return a `Promise` | ||
| ```javascript | ||
| const actions = { | ||
| myAction: async (opts) => { | ||
| return opts.value || 'default' | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| 2. **Create or extend SetupChain class** | ||
| ```javascript | ||
| class MyChain extends SetupChain { | ||
| constructor(state) { | ||
| super(state, actions) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| 3. **Use the action** in your chain | ||
| ```javascript | ||
| await new MyChain().myAction({value: 'test'}, 'result').execute() | ||
| // state: {result: 'test'} | ||
| ``` | ||
|
|
||
| 4. **Validate** your implementation works as expected | ||
|
|
||
| ## Custom Signatures (Advanced) | ||
|
|
||
| Only manually push `this.tasks` when action signature deviates from `(opts, label)`: | ||
|
|
||
| ```javascript | ||
| class MyChain extends SetupChain { | ||
| constructor(state) { | ||
| super(state, yourActions) | ||
| } | ||
|
|
||
| customAction(arg1, arg2, label) { | ||
| this.tasks.push(['customAction', label, arg1, arg2]) | ||
| return this | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| See `create-action.md` for detailed patterns and examples. | ||
|
|
||
| ## Usage & Workflow | ||
|
|
||
| SetupChain implements chain-of-responsibility pattern. Actions are available as top level functions on a chain instance. | ||
| Chain actions, execute, and results are stored in state. | ||
|
|
||
| ```javascript | ||
| const chain = new MyChain() | ||
|
|
||
| const state = await chain | ||
| .set('user', 'alice') | ||
| .map('#user', n => n * 2, 'doubled') | ||
| .execute() | ||
|
|
||
| // state: {user: 'alice', doubled: [2, 4, 6]} | ||
| ``` | ||
|
|
||
| Best practices: | ||
| - Use labels explicitly to avoid state key collisions | ||
| - Chain actions for readability: `.action1().action2().execute()` | ||
| - Reuse state across instances for persistence: `new SetupChain(state2)` | ||
| - Group Long action chains by use case with comments | ||
|
|
||
| ```javascript | ||
| const chain = new MyChain() | ||
|
|
||
| // test scenerio 1 | ||
| chain | ||
| .account({}, 'account_one') | ||
| .user({account: '#account_one'}, 'user_one') | ||
|
|
||
| // test scenerio 1 | ||
| chain | ||
| .account({}, 'account_two') | ||
| .user({account: '#account_two'}, 'user_two') | ||
|
|
||
| const state = await chain.execute() | ||
| ``` | ||
|
|
||
| ## Additional Resources | ||
|
|
||
| - [Detailed action patterns](./references/create-action.md) | ||
| - [Adding helper functions](./references/create-function.md) | ||
| - [Quick reference](./references/QUICKREF.md) | ||
| - [Code examples](./references/examples/) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # Creating Custom Actions | ||
|
|
||
|
|
||
| ## Steps to Create and Wire a Custom Action | ||
|
|
||
| 1. **Define your action function** | ||
| - **Requirement**: Actions MUST be `async` functions or return a `Promise` | ||
| - Accept single `opts` object | ||
| - Return the result directly | ||
| ```javascript | ||
| const actions = { | ||
| hello: async (opts) => opts.name || 'World' | ||
| } | ||
| ``` | ||
|
|
||
| 2. **Create SetupChain class** | ||
| - Pass actions object to constructor | ||
| ```javascript | ||
| class MyChain extends SetupChain { | ||
| constructor(state) { | ||
| super(state, actions) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| 3. **Call action with label** | ||
| ```javascript | ||
| await new MyChain().hello({name: 'Alice'}, 'result').execute() | ||
| ``` | ||
| - Label is key in state for this action's result | ||
|
|
||
| 4. **Test your implementation** | ||
|
|
||
| ## Custom Signature (Use Only When Needed) | ||
|
|
||
| 1. Define action function manually | ||
| 2. Override with custom method signature | ||
| 3. Manually push task with correct format | ||
| 4. Return `this` for chaining | ||
|
|
||
| ```javascript | ||
| const actions = { | ||
| printNames: async (opts) => [opts.first, opts.last].join(', ') | ||
| } | ||
|
|
||
| class MyChain extends SetupChain { | ||
| constructor(state) { | ||
| super(state, actions) | ||
| } | ||
|
|
||
| printNames(first, last, label) { | ||
| this.tasks.push(['printNames', label, first, last]) | ||
| return this | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Pattern: State-Dependent with Defaults | ||
|
|
||
| 1. Define defaults with template placeholders | ||
| 2. Use `this.lookup()` to merge defaults and opts | ||
| 3. Use `#this` to reference action's result context | ||
|
esatterwhite marked this conversation as resolved.
|
||
| 4. Use `assert` module to validate values after lookup resolution | ||
|
|
||
| ```javascript | ||
| const actions = { | ||
| person: async (opts) => { | ||
| const defaults = { | ||
| first: 'bobby' | ||
| , last: 'fischer' | ||
| , full: '!template:"{{#this.first}} {{#this.last}}"' | ||
| } | ||
| const result = this.lookup({...defaults, ...opts}) | ||
|
|
||
| // Validate required fields exist | ||
| assert.ok(result.first, 'First name is required') | ||
| assert.ok(result.last, 'Last name is required') | ||
|
|
||
| // Validate types | ||
| assert.equal(typeof result.first, 'string', 'First name must be a string') | ||
| assert.equal(typeof result.last, 'string', 'Last name must be a string') | ||
|
|
||
| return result | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| 5. **Validate** after executing with various inputs | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| # Adding Helper Functions | ||
|
|
||
| ## Overview | ||
|
|
||
| Helper functions are synchronous methods defined on a chain class that extend the capabilities of the `lookup` system. They are called when the `lookup` function encounters a value starting with `!`. | ||
|
|
||
| ## Creating a Helper Function | ||
|
|
||
| 1. **Define the method on your chain class** | ||
| - **Prefix**: Method name MUST start with `$` (e.g., `$slugify`) | ||
| - **Naming**: Keep names short, single-word, and lowercase (`$<verb>`) | ||
| - **Synchronous**: Functions MUST be synchronous; they cannot be `async` by design. Use [Actions](./create-action.md) for async operations. | ||
| - **Arguments**: They can accept any number of arguments. | ||
|
|
||
| ```javascript | ||
| class MyChain extends SetupChain { | ||
| $slugify(text) { | ||
| return text.toLowerCase().replace(/\s+/g, '-'); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| 2. **Use the function via `lookup`** | ||
| - Call the function by using the `!` prefix in lookup strings or objects. | ||
| - The `$` prefix is removed when calling via lookup (e.g., `$slugify` is called as `!slugify`). | ||
|
|
||
| ## Usage Patterns | ||
|
|
||
| ### Basic Call | ||
| Pass arguments directly after the function name, separated by commas. | ||
| ```javascript | ||
| chain.lookup('!slugify:"Hello World"') | ||
| // returns: "hello-world" | ||
| ``` | ||
|
|
||
| ### Nested & Complex Lookups | ||
| Functions can accept other lookup values (state properties or other functions) as arguments. | ||
|
|
||
| - **Using State Properties**: | ||
| ```javascript | ||
| await chain.set('title', 'My Page').execute(); | ||
| chain.lookup('!slugify:#title') | ||
| // returns: "my-page" | ||
| ``` | ||
|
|
||
| - **Array of Lookups**: | ||
| ```javascript | ||
| await chain.set(('one', 'ONE').set('two', 'TWO').execute() | ||
| chain.lookup(['!lower:#one', '!lower:#two']) | ||
|
esatterwhite marked this conversation as resolved.
|
||
| // returns ['one', 'two'] | ||
| ``` | ||
|
|
||
| - **Deeply Nested Functions**: | ||
| ```javascript | ||
| var chain = new MyChain({bar: 'Hello World}) | ||
| chain.lookup({one: '!lower(!slugify(#bar))' }) | ||
| // returns {one: 'hello-world'} | ||
| ``` | ||
|
|
||
| - **Inside Action Options**: | ||
| You can pass function calls as values in action options; they will be resolved by the action's `this.lookup()` call. | ||
| ```javascript | ||
| await chain.set({title: 'Hello World'}).myAction({ slug: '!slugify(#title)' }).execute() | ||
| // returns {slug: 'hello-world'} | ||
| ``` | ||
|
|
||
| ## Summary Checklist | ||
| - [ ] Method starts with `$` | ||
| - [ ] Method is synchronous | ||
| - [ ] Name is short, lowercase, single-word | ||
| - [ ] Called using `!` in `lookup` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // Basic set() usage | ||
| const SetupChain = require('@logdna/setup-chain') | ||
| const chain = new SetupChain() | ||
| await chain.set('hello', 'world').set('goodbye', 'world').execute() | ||
| // state: {hello: 'world', goodbye: 'world'} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Custom actions with defaults | ||
| const SetupChain = require('@logdna/setup-chain') | ||
|
|
||
| const defaults = { | ||
| first: 'bobby' | ||
| , last: 'fischer' | ||
| , full: '!template:"{{#this.first}} {{#this.last}}"' | ||
| } | ||
|
|
||
| const actions = { | ||
| person: async function person(opts) { | ||
| return this.lookup({...defaults, ...opts}) | ||
| } | ||
| } | ||
|
|
||
| const chain = new SetupChain(null, actions) | ||
| const state = await chain | ||
| .person({}, 'bobby') // uses defaults | ||
| .person({first: 'fred'}, 'fred') | ||
| .person({last: 'williams'}, 'williams') | ||
| .execute() | ||
| // state: { | ||
| // bobby: {first: 'bobby', last: 'fischer', full: 'bobby fischer'}, | ||
| // fred: {first: 'fred', last: 'fischer', full: 'fred fischer'}, | ||
| // williams: {first: 'bobby', last: 'williams', full: 'bobby williams'} | ||
| // } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if
(opts, label)is the true default signature, perhaps the signature in number 1 should be(opts, _label)or(opts, label)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for number 1 - the label isn't passed to the actual action function, Thats only for defining the custom hook on the chain instance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vs