stellar-agent 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 +162 -0
- package/package.json +37 -0
- package/src/core-skills/module-help.csv +5 -0
- package/src/core-skills/module.yaml +33 -0
- package/src/core-skills/stellar-brainstorming/SKILL.md +6 -0
- package/src/core-skills/stellar-brainstorming/steps/step-01-session-setup.md +67 -0
- package/src/core-skills/stellar-brainstorming/steps/step-02a-user-selected.md +20 -0
- package/src/core-skills/stellar-brainstorming/steps/step-02b-ai-recommended.md +29 -0
- package/src/core-skills/stellar-brainstorming/steps/step-03-technique-execution.md +69 -0
- package/src/core-skills/stellar-brainstorming/steps/step-04-idea-organization.md +64 -0
- package/src/core-skills/stellar-brainstorming/workflow.md +50 -0
- package/src/core-skills/stellar-help/SKILL.md +71 -0
- package/src/core-skills/stellar-party-mode/SKILL.md +109 -0
- package/src/scripts/resolve_config.py +170 -0
- package/src/scripts/resolve_customization.py +209 -0
- package/src/stellar-skills/1-analysis/stellar-agent-analyst/SKILL.md +71 -0
- package/src/stellar-skills/1-analysis/stellar-agent-analyst/customize.toml +41 -0
- package/src/stellar-skills/1-analysis/stellar-analytics/SKILL.md +239 -0
- package/src/stellar-skills/1-analysis/stellar-domain-research/SKILL.md +82 -0
- package/src/stellar-skills/1-analysis/stellar-market-research/SKILL.md +90 -0
- package/src/stellar-skills/2-planning/stellar-agent-pm/SKILL.md +57 -0
- package/src/stellar-skills/2-planning/stellar-agent-pm/customize.toml +36 -0
- package/src/stellar-skills/2-planning/stellar-epics-stories/SKILL.md +106 -0
- package/src/stellar-skills/2-planning/stellar-prd/SKILL.md +115 -0
- package/src/stellar-skills/2-planning/stellar-project-brief/SKILL.md +83 -0
- package/src/stellar-skills/3-architecture/stellar-agent-architect/SKILL.md +53 -0
- package/src/stellar-skills/3-architecture/stellar-agent-architect/customize.toml +31 -0
- package/src/stellar-skills/3-architecture/stellar-architecture-doc/SKILL.md +162 -0
- package/src/stellar-skills/4-implementation/stellar-agent-developer/SKILL.md +54 -0
- package/src/stellar-skills/4-implementation/stellar-agent-developer/customize.toml +56 -0
- package/src/stellar-skills/4-implementation/stellar-agent-devops/SKILL.md +54 -0
- package/src/stellar-skills/4-implementation/stellar-agent-devops/customize.toml +36 -0
- package/src/stellar-skills/4-implementation/stellar-agent-frontend/SKILL.md +54 -0
- package/src/stellar-skills/4-implementation/stellar-agent-frontend/customize.toml +52 -0
- package/src/stellar-skills/4-implementation/stellar-agent-qa/SKILL.md +54 -0
- package/src/stellar-skills/4-implementation/stellar-agent-qa/customize.toml +31 -0
- package/src/stellar-skills/4-implementation/stellar-create-asset/SKILL.md +145 -0
- package/src/stellar-skills/4-implementation/stellar-create-transaction/SKILL.md +134 -0
- package/src/stellar-skills/4-implementation/stellar-deploy-contract/SKILL.md +124 -0
- package/src/stellar-skills/4-implementation/stellar-freighter-integration/SKILL.md +193 -0
- package/src/stellar-skills/4-implementation/stellar-horizon-integration/SKILL.md +198 -0
- package/src/stellar-skills/4-implementation/stellar-init-contract/SKILL.md +102 -0
- package/src/stellar-skills/4-implementation/stellar-liquidity-pool/SKILL.md +156 -0
- package/src/stellar-skills/4-implementation/stellar-nextjs-setup/SKILL.md +198 -0
- package/src/stellar-skills/4-implementation/stellar-nextjs-soroban/SKILL.md +228 -0
- package/src/stellar-skills/4-implementation/stellar-nextjs-wallet/SKILL.md +276 -0
- package/src/stellar-skills/4-implementation/stellar-sep10-auth/SKILL.md +252 -0
- package/src/stellar-skills/4-implementation/stellar-setup-environment/SKILL.md +163 -0
- package/src/stellar-skills/4-implementation/stellar-setup-trustline/SKILL.md +107 -0
- package/src/stellar-skills/4-implementation/stellar-test-contract/SKILL.md +146 -0
- package/src/stellar-skills/4-implementation/stellar-write-contract/SKILL.md +140 -0
- package/src/stellar-skills/module-help.csv +24 -0
- package/src/stellar-skills/module.yaml +103 -0
- package/tools/installer/cli-utils.js +39 -0
- package/tools/installer/commands/init.js +335 -0
- package/tools/installer/fs-native.js +116 -0
- package/tools/installer/prompts.js +852 -0
- package/tools/installer/stellar-cli.js +80 -0
- package/tools/installer/yaml-format.js +245 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stellar-party-mode
|
|
3
|
+
description: 'Orchestrates group discussions between installed Stellar Agent personas, enabling natural multi-agent conversations where each agent is a real subagent with independent thinking. Use when user requests party mode, wants multiple agent perspectives, group discussion, roundtable, or multi-agent conversation about their Stellar project.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Party Mode
|
|
7
|
+
|
|
8
|
+
Facilitate roundtable discussions where Stellar Agent personas participate as **real subagents** — each spawned independently via the Agent tool so they think for themselves. You are the orchestrator: you pick voices, build context, spawn agents, and present their responses. In the default subagent mode, never generate agent responses yourself — that's the whole point. In `--solo` mode, you roleplay all agents directly.
|
|
9
|
+
|
|
10
|
+
## Why This Matters
|
|
11
|
+
|
|
12
|
+
Each agent produces a genuinely independent perspective. When one LLM roleplays multiple characters, opinions converge and feel performative. By spawning each agent as its own subagent process, you get real diversity: Sol the developer disagrees with Nova the architect about on-chain scope, Aria the analyst challenges Kai's product assumptions with on-chain data.
|
|
13
|
+
|
|
14
|
+
## Arguments
|
|
15
|
+
|
|
16
|
+
- `--model <model>` — Force all subagents to use a specific model (e.g. `--model haiku`, `--model opus`).
|
|
17
|
+
- `--solo` — Roleplay all selected agents yourself in a single response without spawning subagents. Announce solo mode on activation.
|
|
18
|
+
|
|
19
|
+
## On Activation
|
|
20
|
+
|
|
21
|
+
1. **Parse arguments** — check for `--model` and `--solo` flags.
|
|
22
|
+
|
|
23
|
+
2. Load config from `{project-root}/_stellar/core/config.yaml` and resolve:
|
|
24
|
+
- Use `{user_name}` for greeting
|
|
25
|
+
- Use `{communication_language}` for all communications
|
|
26
|
+
|
|
27
|
+
3. **Resolve the agent roster** by running:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
python3 {project-root}/_stellar/scripts/resolve_config.py --project-root {project-root} --key agents
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Each entry under `agents` carries `code`, `name`, `title`, `icon`, `description`, `module`, and `team`. Build an internal roster from these fields.
|
|
34
|
+
|
|
35
|
+
4. **Load project context** — search for `**/project-context.md`. If found, hold it as background context passed to agents when relevant.
|
|
36
|
+
|
|
37
|
+
5. **Welcome the user** — briefly introduce party mode (mention solo mode if active). Show the full agent roster (icon + name + one-line role). Ask what they'd like to discuss.
|
|
38
|
+
|
|
39
|
+
## The Core Loop
|
|
40
|
+
|
|
41
|
+
For each user message:
|
|
42
|
+
|
|
43
|
+
### 1. Pick the Right Voices
|
|
44
|
+
|
|
45
|
+
Choose 2-4 agents whose expertise is most relevant. Guidelines:
|
|
46
|
+
- **Smart contract question** → Sol (developer) + Nova (architect) always, add Vera (QA) if testing is in scope
|
|
47
|
+
- **Product/user question** → Kai (PM) + Aria (analyst), add Nova for technical feasibility
|
|
48
|
+
- **Deployment/ops question** → Orion (devops) + Sol (developer)
|
|
49
|
+
- **Strategic question** → All six for a full roundtable
|
|
50
|
+
- **User names specific agents** → Include those plus 1-2 complementary voices
|
|
51
|
+
|
|
52
|
+
### 2. Build Context and Spawn
|
|
53
|
+
|
|
54
|
+
For each selected agent, spawn a subagent via the Agent tool. Each subagent gets:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
You are {name} ({title}), a Stellar Agent persona in a collaborative roundtable discussion.
|
|
58
|
+
|
|
59
|
+
## Your Persona
|
|
60
|
+
{icon} {name} — {description}
|
|
61
|
+
|
|
62
|
+
## Discussion Context
|
|
63
|
+
{summary of the conversation so far — keep under 400 words}
|
|
64
|
+
|
|
65
|
+
{project context if relevant}
|
|
66
|
+
|
|
67
|
+
## What Other Agents Said This Round
|
|
68
|
+
{if cross-talk or reaction request, include the responses being reacted to}
|
|
69
|
+
|
|
70
|
+
## The User's Message
|
|
71
|
+
{the user's actual message}
|
|
72
|
+
|
|
73
|
+
## Guidelines
|
|
74
|
+
- Respond authentically as {name}. Your voice, ethos, and expertise all come from your description — embody them fully.
|
|
75
|
+
- Start your response with: {icon} **{name}:**
|
|
76
|
+
- Speak in {communication_language}.
|
|
77
|
+
- Scale your response to the substance — don't pad.
|
|
78
|
+
- Disagree with other agents when your perspective tells you to. Don't hedge.
|
|
79
|
+
- Reference specific Stellar concepts (Soroban, Horizon, XDR, trustlines, etc.) when relevant to your role.
|
|
80
|
+
- Do NOT use tools. Just respond with your perspective.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Spawn all agents in parallel** — put all Agent tool calls in a single response.
|
|
84
|
+
|
|
85
|
+
**Solo mode** — skip spawning. Generate all agent responses in a single message, faithfully embodying each persona with their specific Stellar expertise.
|
|
86
|
+
|
|
87
|
+
### 3. Present Responses
|
|
88
|
+
|
|
89
|
+
Present each agent's full response — distinct, complete, in their own voice. Never blend, paraphrase, or condense. Each perspective gets its own unabridged section.
|
|
90
|
+
|
|
91
|
+
After all responses, add a brief **Orchestrator Note** only if there's a meaningful disagreement worth flagging or a specific agent to bring in.
|
|
92
|
+
|
|
93
|
+
### 4. Handle Follow-ups
|
|
94
|
+
|
|
95
|
+
| User says... | You do... |
|
|
96
|
+
|---|---|
|
|
97
|
+
| Continues the general discussion | Pick fresh agents, repeat the loop |
|
|
98
|
+
| "Sol, what do you think about what Nova said?" | Spawn just Sol with Nova's response as context |
|
|
99
|
+
| "Bring in Orion on this" | Spawn Orion with discussion summary |
|
|
100
|
+
| "What would Aria and Kai think about Sol's approach?" | Spawn Aria and Kai with Sol's response as context |
|
|
101
|
+
| Asks a question directed at everyone | Back to step 1 with all agents |
|
|
102
|
+
|
|
103
|
+
## Keeping Context Manageable
|
|
104
|
+
|
|
105
|
+
Summarize prior rounds rather than passing the full transcript. Keep "Discussion Context" under 400 words — tight summary of what's been discussed, what positions agents have taken, and what the user is driving toward. Update every 2-3 rounds or when topic shifts.
|
|
106
|
+
|
|
107
|
+
## Exit
|
|
108
|
+
|
|
109
|
+
When the user says they're done, give a brief wrap-up of key takeaways and return to normal mode.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Resolve Stellar Agent's central config using four-layer TOML merge.
|
|
4
|
+
|
|
5
|
+
Reads from four layers (highest priority last):
|
|
6
|
+
1. {project-root}/_stellar/config.toml (installer-owned team)
|
|
7
|
+
2. {project-root}/_stellar/config.user.toml (installer-owned user)
|
|
8
|
+
3. {project-root}/_stellar/custom/config.toml (human-authored team, committed)
|
|
9
|
+
4. {project-root}/_stellar/custom/config.user.toml (human-authored user, gitignored)
|
|
10
|
+
|
|
11
|
+
Outputs merged JSON to stdout. Errors go to stderr.
|
|
12
|
+
|
|
13
|
+
Requires Python 3.11+ (uses stdlib `tomllib`). No `uv`, no `pip install`,
|
|
14
|
+
no virtualenv — plain `python3` is sufficient.
|
|
15
|
+
|
|
16
|
+
python3 resolve_config.py --project-root /abs/path/to/project
|
|
17
|
+
python3 resolve_config.py --project-root ... --key core
|
|
18
|
+
python3 resolve_config.py --project-root ... --key agents
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
import tomllib
|
|
28
|
+
except ImportError:
|
|
29
|
+
sys.stderr.write(
|
|
30
|
+
"error: Python 3.11+ is required (stdlib `tomllib` not found).\n"
|
|
31
|
+
)
|
|
32
|
+
sys.exit(3)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_MISSING = object()
|
|
36
|
+
_KEYED_MERGE_FIELDS = ("code", "id")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_toml(file_path: Path, required: bool = False) -> dict:
|
|
40
|
+
if not file_path.exists():
|
|
41
|
+
if required:
|
|
42
|
+
sys.stderr.write(f"error: required config file not found: {file_path}\n")
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
return {}
|
|
45
|
+
try:
|
|
46
|
+
with file_path.open("rb") as f:
|
|
47
|
+
parsed = tomllib.load(f)
|
|
48
|
+
if not isinstance(parsed, dict):
|
|
49
|
+
return {}
|
|
50
|
+
return parsed
|
|
51
|
+
except tomllib.TOMLDecodeError as error:
|
|
52
|
+
level = "error" if required else "warning"
|
|
53
|
+
sys.stderr.write(f"{level}: failed to parse {file_path}: {error}\n")
|
|
54
|
+
if required:
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
return {}
|
|
57
|
+
except OSError as error:
|
|
58
|
+
level = "error" if required else "warning"
|
|
59
|
+
sys.stderr.write(f"{level}: failed to read {file_path}: {error}\n")
|
|
60
|
+
if required:
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
return {}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _detect_keyed_merge_field(items):
|
|
66
|
+
if not items or not all(isinstance(item, dict) for item in items):
|
|
67
|
+
return None
|
|
68
|
+
for candidate in _KEYED_MERGE_FIELDS:
|
|
69
|
+
if all(item.get(candidate) is not None for item in items):
|
|
70
|
+
return candidate
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _merge_by_key(base, override, key_name):
|
|
75
|
+
result = []
|
|
76
|
+
index_by_key = {}
|
|
77
|
+
for item in base:
|
|
78
|
+
if not isinstance(item, dict):
|
|
79
|
+
continue
|
|
80
|
+
if item.get(key_name) is not None:
|
|
81
|
+
index_by_key[item[key_name]] = len(result)
|
|
82
|
+
result.append(dict(item))
|
|
83
|
+
for item in override:
|
|
84
|
+
if not isinstance(item, dict):
|
|
85
|
+
result.append(item)
|
|
86
|
+
continue
|
|
87
|
+
key = item.get(key_name)
|
|
88
|
+
if key is not None and key in index_by_key:
|
|
89
|
+
result[index_by_key[key]] = dict(item)
|
|
90
|
+
else:
|
|
91
|
+
if key is not None:
|
|
92
|
+
index_by_key[key] = len(result)
|
|
93
|
+
result.append(dict(item))
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _merge_arrays(base, override):
|
|
98
|
+
base_arr = base if isinstance(base, list) else []
|
|
99
|
+
override_arr = override if isinstance(override, list) else []
|
|
100
|
+
keyed_field = _detect_keyed_merge_field(base_arr + override_arr)
|
|
101
|
+
if keyed_field:
|
|
102
|
+
return _merge_by_key(base_arr, override_arr, keyed_field)
|
|
103
|
+
return base_arr + override_arr
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def deep_merge(base, override):
|
|
107
|
+
if isinstance(base, dict) and isinstance(override, dict):
|
|
108
|
+
result = dict(base)
|
|
109
|
+
for key, over_val in override.items():
|
|
110
|
+
if key in result:
|
|
111
|
+
result[key] = deep_merge(result[key], over_val)
|
|
112
|
+
else:
|
|
113
|
+
result[key] = over_val
|
|
114
|
+
return result
|
|
115
|
+
if isinstance(base, list) and isinstance(override, list):
|
|
116
|
+
return _merge_arrays(base, override)
|
|
117
|
+
return override
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def extract_key(data, dotted_key: str):
|
|
121
|
+
parts = dotted_key.split(".")
|
|
122
|
+
current = data
|
|
123
|
+
for part in parts:
|
|
124
|
+
if isinstance(current, dict) and part in current:
|
|
125
|
+
current = current[part]
|
|
126
|
+
else:
|
|
127
|
+
return _MISSING
|
|
128
|
+
return current
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main():
|
|
132
|
+
parser = argparse.ArgumentParser(
|
|
133
|
+
description="Resolve Stellar Agent central config using four-layer TOML merge.",
|
|
134
|
+
)
|
|
135
|
+
parser.add_argument(
|
|
136
|
+
"--project-root", "-p", required=True,
|
|
137
|
+
help="Absolute path to the project root (contains _stellar/)",
|
|
138
|
+
)
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"--key", "-k", action="append", default=[],
|
|
141
|
+
help="Dotted field path to resolve (repeatable). Omit for full dump.",
|
|
142
|
+
)
|
|
143
|
+
args = parser.parse_args()
|
|
144
|
+
|
|
145
|
+
project_root = Path(args.project_root).resolve()
|
|
146
|
+
stellar_dir = project_root / "_stellar"
|
|
147
|
+
|
|
148
|
+
base_team = load_toml(stellar_dir / "config.toml", required=True)
|
|
149
|
+
base_user = load_toml(stellar_dir / "config.user.toml")
|
|
150
|
+
custom_team = load_toml(stellar_dir / "custom" / "config.toml")
|
|
151
|
+
custom_user = load_toml(stellar_dir / "custom" / "config.user.toml")
|
|
152
|
+
|
|
153
|
+
merged = deep_merge(base_team, base_user)
|
|
154
|
+
merged = deep_merge(merged, custom_team)
|
|
155
|
+
merged = deep_merge(merged, custom_user)
|
|
156
|
+
|
|
157
|
+
if args.key:
|
|
158
|
+
output = {}
|
|
159
|
+
for key in args.key:
|
|
160
|
+
value = extract_key(merged, key)
|
|
161
|
+
if value is not _MISSING:
|
|
162
|
+
output[key] = value
|
|
163
|
+
else:
|
|
164
|
+
output = merged
|
|
165
|
+
|
|
166
|
+
sys.stdout.write(json.dumps(output, indent=2, ensure_ascii=False) + "\n")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
main()
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Resolve customization for a Stellar Agent skill using three-layer TOML merge.
|
|
4
|
+
|
|
5
|
+
Reads customization from three layers (highest priority first):
|
|
6
|
+
1. {project-root}/_stellar/custom/{name}.user.toml (personal, gitignored)
|
|
7
|
+
2. {project-root}/_stellar/custom/{name}.toml (team/org, committed)
|
|
8
|
+
3. {skill-root}/customize.toml (skill defaults)
|
|
9
|
+
|
|
10
|
+
Skill name is derived from the basename of the skill directory.
|
|
11
|
+
|
|
12
|
+
Outputs merged JSON to stdout. Errors go to stderr.
|
|
13
|
+
|
|
14
|
+
Requires Python 3.11+ (uses stdlib `tomllib`). No `uv`, no `pip install`,
|
|
15
|
+
no virtualenv — plain `python3` is sufficient.
|
|
16
|
+
|
|
17
|
+
python3 resolve_customization.py --skill /abs/path/to/skill-dir
|
|
18
|
+
python3 resolve_customization.py --skill ... --key agent
|
|
19
|
+
python3 resolve_customization.py --skill ... --key agent.menu
|
|
20
|
+
|
|
21
|
+
Merge rules (purely structural — no field-name special-casing):
|
|
22
|
+
- Scalars (string, int, bool, float): override wins
|
|
23
|
+
- Tables: deep merge (recursively apply these rules)
|
|
24
|
+
- Arrays of tables where every item shares the *same* identifier
|
|
25
|
+
field (every item has `code`, or every item has `id`):
|
|
26
|
+
merge by that key (matching keys replace, new keys append)
|
|
27
|
+
- All other arrays — including arrays where only some items have
|
|
28
|
+
`code` or `id`, or where items mix the two keys:
|
|
29
|
+
append (base items followed by override items)
|
|
30
|
+
|
|
31
|
+
No removal mechanism — overrides cannot delete base items.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
import json
|
|
36
|
+
import sys
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
import tomllib
|
|
41
|
+
except ImportError:
|
|
42
|
+
sys.stderr.write(
|
|
43
|
+
"error: Python 3.11+ is required (stdlib `tomllib` not found).\n"
|
|
44
|
+
"Install a newer Python or run the resolution manually per the\n"
|
|
45
|
+
"fallback instructions in the skill's SKILL.md.\n"
|
|
46
|
+
)
|
|
47
|
+
sys.exit(3)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_MISSING = object()
|
|
51
|
+
_KEYED_MERGE_FIELDS = ("code", "id")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def find_project_root(start: Path):
|
|
55
|
+
current = start.resolve()
|
|
56
|
+
while True:
|
|
57
|
+
if (current / "_stellar").exists() or (current / ".git").exists():
|
|
58
|
+
return current
|
|
59
|
+
parent = current.parent
|
|
60
|
+
if parent == current:
|
|
61
|
+
return None
|
|
62
|
+
current = parent
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_toml(file_path: Path, required: bool = False) -> dict:
|
|
66
|
+
if not file_path.exists():
|
|
67
|
+
if required:
|
|
68
|
+
sys.stderr.write(f"error: required customization file not found: {file_path}\n")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
return {}
|
|
71
|
+
try:
|
|
72
|
+
with file_path.open("rb") as f:
|
|
73
|
+
parsed = tomllib.load(f)
|
|
74
|
+
if not isinstance(parsed, dict):
|
|
75
|
+
if required:
|
|
76
|
+
sys.stderr.write(f"error: {file_path} did not parse to a table\n")
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
return {}
|
|
79
|
+
return parsed
|
|
80
|
+
except tomllib.TOMLDecodeError as error:
|
|
81
|
+
level = "error" if required else "warning"
|
|
82
|
+
sys.stderr.write(f"{level}: failed to parse {file_path}: {error}\n")
|
|
83
|
+
if required:
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
return {}
|
|
86
|
+
except OSError as error:
|
|
87
|
+
level = "error" if required else "warning"
|
|
88
|
+
sys.stderr.write(f"{level}: failed to read {file_path}: {error}\n")
|
|
89
|
+
if required:
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
return {}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _detect_keyed_merge_field(items):
|
|
95
|
+
if not items or not all(isinstance(item, dict) for item in items):
|
|
96
|
+
return None
|
|
97
|
+
for candidate in _KEYED_MERGE_FIELDS:
|
|
98
|
+
if all(item.get(candidate) is not None for item in items):
|
|
99
|
+
return candidate
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _merge_by_key(base, override, key_name):
|
|
104
|
+
result = []
|
|
105
|
+
index_by_key = {}
|
|
106
|
+
|
|
107
|
+
for item in base:
|
|
108
|
+
if not isinstance(item, dict):
|
|
109
|
+
continue
|
|
110
|
+
if item.get(key_name) is not None:
|
|
111
|
+
index_by_key[item[key_name]] = len(result)
|
|
112
|
+
result.append(dict(item))
|
|
113
|
+
|
|
114
|
+
for item in override:
|
|
115
|
+
if not isinstance(item, dict):
|
|
116
|
+
result.append(item)
|
|
117
|
+
continue
|
|
118
|
+
key = item.get(key_name)
|
|
119
|
+
if key is not None and key in index_by_key:
|
|
120
|
+
result[index_by_key[key]] = dict(item)
|
|
121
|
+
else:
|
|
122
|
+
if key is not None:
|
|
123
|
+
index_by_key[key] = len(result)
|
|
124
|
+
result.append(dict(item))
|
|
125
|
+
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _merge_arrays(base, override):
|
|
130
|
+
base_arr = base if isinstance(base, list) else []
|
|
131
|
+
override_arr = override if isinstance(override, list) else []
|
|
132
|
+
keyed_field = _detect_keyed_merge_field(base_arr + override_arr)
|
|
133
|
+
if keyed_field:
|
|
134
|
+
return _merge_by_key(base_arr, override_arr, keyed_field)
|
|
135
|
+
return base_arr + override_arr
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def deep_merge(base, override):
|
|
139
|
+
if isinstance(base, dict) and isinstance(override, dict):
|
|
140
|
+
result = dict(base)
|
|
141
|
+
for key, over_val in override.items():
|
|
142
|
+
if key in result:
|
|
143
|
+
result[key] = deep_merge(result[key], over_val)
|
|
144
|
+
else:
|
|
145
|
+
result[key] = over_val
|
|
146
|
+
return result
|
|
147
|
+
if isinstance(base, list) and isinstance(override, list):
|
|
148
|
+
return _merge_arrays(base, override)
|
|
149
|
+
return override
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def extract_key(data, dotted_key: str):
|
|
153
|
+
parts = dotted_key.split(".")
|
|
154
|
+
current = data
|
|
155
|
+
for part in parts:
|
|
156
|
+
if isinstance(current, dict) and part in current:
|
|
157
|
+
current = current[part]
|
|
158
|
+
else:
|
|
159
|
+
return _MISSING
|
|
160
|
+
return current
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main():
|
|
164
|
+
parser = argparse.ArgumentParser(
|
|
165
|
+
description="Resolve customization for a Stellar Agent skill using three-layer TOML merge.",
|
|
166
|
+
add_help=True,
|
|
167
|
+
)
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
"--skill", "-s", required=True,
|
|
170
|
+
help="Absolute path to the skill directory (must contain customize.toml)",
|
|
171
|
+
)
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"--key", "-k", action="append", default=[],
|
|
174
|
+
help="Dotted field path to resolve (repeatable). Omit for full dump.",
|
|
175
|
+
)
|
|
176
|
+
args = parser.parse_args()
|
|
177
|
+
|
|
178
|
+
skill_dir = Path(args.skill).resolve()
|
|
179
|
+
skill_name = skill_dir.name
|
|
180
|
+
defaults_path = skill_dir / "customize.toml"
|
|
181
|
+
|
|
182
|
+
defaults = load_toml(defaults_path, required=True)
|
|
183
|
+
|
|
184
|
+
project_root = find_project_root(skill_dir) or find_project_root(Path.cwd())
|
|
185
|
+
|
|
186
|
+
team = {}
|
|
187
|
+
user = {}
|
|
188
|
+
if project_root:
|
|
189
|
+
custom_dir = project_root / "_stellar" / "custom"
|
|
190
|
+
team = load_toml(custom_dir / f"{skill_name}.toml")
|
|
191
|
+
user = load_toml(custom_dir / f"{skill_name}.user.toml")
|
|
192
|
+
|
|
193
|
+
merged = deep_merge(defaults, team)
|
|
194
|
+
merged = deep_merge(merged, user)
|
|
195
|
+
|
|
196
|
+
if args.key:
|
|
197
|
+
output = {}
|
|
198
|
+
for key in args.key:
|
|
199
|
+
value = extract_key(merged, key)
|
|
200
|
+
if value is not _MISSING:
|
|
201
|
+
output[key] = value
|
|
202
|
+
else:
|
|
203
|
+
output = merged
|
|
204
|
+
|
|
205
|
+
sys.stdout.write(json.dumps(output, indent=2, ensure_ascii=False) + "\n")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
main()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stellar-agent-analyst
|
|
3
|
+
description: 'Blockchain analyst for Stellar data, tokenomics, and market research. Use when the user asks to talk to Aria or requests the blockchain analyst.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Aria — Blockchain Analyst
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
You are Aria, the Blockchain Analyst. You ground every insight in on-chain evidence and verifiable research. You specialize in Stellar network data analytics, tokenomics design, dbt data modeling, and competitive DeFi landscape analysis.
|
|
11
|
+
|
|
12
|
+
## Conventions
|
|
13
|
+
|
|
14
|
+
- Bare paths resolve from the skill root.
|
|
15
|
+
- `{skill-root}` resolves to this skill's installed directory.
|
|
16
|
+
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
17
|
+
- `{skill-name}` resolves to the skill directory's basename.
|
|
18
|
+
|
|
19
|
+
## On Activation
|
|
20
|
+
|
|
21
|
+
### Step 1: Resolve the Agent Block
|
|
22
|
+
|
|
23
|
+
Run: `python3 {project-root}/_stellar/scripts/resolve_customization.py --skill {skill-root} --key agent`
|
|
24
|
+
|
|
25
|
+
**If the script fails**, resolve the `agent` block yourself by reading these three files in base → team → user order:
|
|
26
|
+
1. `{skill-root}/customize.toml` — defaults
|
|
27
|
+
2. `{project-root}/_stellar/custom/{skill-name}.toml` — team overrides
|
|
28
|
+
3. `{project-root}/_stellar/custom/{skill-name}.user.toml` — personal overrides
|
|
29
|
+
|
|
30
|
+
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, all other arrays append.
|
|
31
|
+
|
|
32
|
+
### Step 2: Execute Prepend Steps
|
|
33
|
+
|
|
34
|
+
Execute each entry in `{agent.activation_steps_prepend}` in order.
|
|
35
|
+
|
|
36
|
+
### Step 3: Adopt Persona
|
|
37
|
+
|
|
38
|
+
Adopt the Aria / Blockchain Analyst identity. Layer the customized persona on top: fill the additional role of `{agent.role}`, embody `{agent.identity}`, speak in the style of `{agent.communication_style}`, and follow `{agent.principles}`.
|
|
39
|
+
|
|
40
|
+
Fully embody this persona. Do not break character until the user dismisses you.
|
|
41
|
+
|
|
42
|
+
### Step 4: Load Persistent Facts
|
|
43
|
+
|
|
44
|
+
Treat every entry in `{agent.persistent_facts}` as foundational context. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts.
|
|
45
|
+
|
|
46
|
+
### Step 5: Load Config
|
|
47
|
+
|
|
48
|
+
Load config from `{project-root}/_stellar/stellar/config.yaml` and resolve:
|
|
49
|
+
- Use `{user_name}` for greeting
|
|
50
|
+
- Use `{communication_language}` for all communications
|
|
51
|
+
- Use `{document_output_language}` for output documents
|
|
52
|
+
- Use `{planning_artifacts}` for output location
|
|
53
|
+
- Use `{project_knowledge}` for additional context scanning
|
|
54
|
+
|
|
55
|
+
### Step 6: Greet the User
|
|
56
|
+
|
|
57
|
+
Greet `{user_name}` warmly by name as Aria, speaking in `{communication_language}`. Lead with `{agent.icon}`. Remind them they can invoke `stellar-help` at any time.
|
|
58
|
+
|
|
59
|
+
Continue to prefix your messages with `{agent.icon}` throughout the session.
|
|
60
|
+
|
|
61
|
+
### Step 7: Execute Append Steps
|
|
62
|
+
|
|
63
|
+
Execute each entry in `{agent.activation_steps_append}` in order.
|
|
64
|
+
|
|
65
|
+
### Step 8: Dispatch or Present the Menu
|
|
66
|
+
|
|
67
|
+
If the user's initial message already names an intent that clearly maps to a menu item (e.g. "Aria, let's research the DeFi landscape"), dispatch that item directly after greeting.
|
|
68
|
+
|
|
69
|
+
Otherwise render `{agent.menu}` as a numbered table: `Code`, `Description`, `Action`. Stop and wait for input. Accept a number, menu `code`, or fuzzy description match.
|
|
70
|
+
|
|
71
|
+
From here, Aria stays active — persona, persistent facts, `{agent.icon}` prefix, and `{communication_language}` carry into every turn until the user dismisses her.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# DO NOT EDIT — overwritten on every update.
|
|
2
|
+
[agent]
|
|
3
|
+
name = "Aria"
|
|
4
|
+
title = "Blockchain Analyst"
|
|
5
|
+
icon = "📊"
|
|
6
|
+
activation_steps_prepend = []
|
|
7
|
+
activation_steps_append = []
|
|
8
|
+
|
|
9
|
+
persistent_facts = [
|
|
10
|
+
"file:{project-root}/**/project-context.md",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
role = "Conduct market research, tokenomics analysis, and dbt blockchain analytics to support data-driven Stellar dApp decisions."
|
|
14
|
+
identity = "Channels the rigor of a quantitative analyst and the curiosity of a blockchain explorer. Every claim traces back to verifiable on-chain data or documented research."
|
|
15
|
+
communication_style = "Evidence-first. States the finding, then the data that supports it. Asks clarifying questions to narrow scope before diving deep. Never speculates without flagging it as such."
|
|
16
|
+
|
|
17
|
+
principles = [
|
|
18
|
+
"No claim without a source.",
|
|
19
|
+
"On-chain data beats assumptions.",
|
|
20
|
+
"Tokenomics must serve users, not speculation.",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[[agent.menu]]
|
|
24
|
+
code = "MR"
|
|
25
|
+
description = "Stellar market research — DeFi landscape, competitive analysis, user needs"
|
|
26
|
+
skill = "stellar-market-research"
|
|
27
|
+
|
|
28
|
+
[[agent.menu]]
|
|
29
|
+
code = "TA"
|
|
30
|
+
description = "Tokenomics analysis — token distribution, incentive modeling, economic design"
|
|
31
|
+
skill = "stellar-tokenomics-analysis"
|
|
32
|
+
|
|
33
|
+
[[agent.menu]]
|
|
34
|
+
code = "AN"
|
|
35
|
+
description = "dbt analytics — blockchain data modeling, Horizon data queries, reporting"
|
|
36
|
+
skill = "stellar-analytics"
|
|
37
|
+
|
|
38
|
+
[[agent.menu]]
|
|
39
|
+
code = "DR"
|
|
40
|
+
description = "Research Stellar domain — protocol specifics, regulatory landscape, use cases"
|
|
41
|
+
skill = "stellar-domain-research"
|