ramenos 1.0.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.
@@ -0,0 +1,37 @@
1
+ name: CI & Publish
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["main"]
8
+ release:
9
+ types: [published]
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ - name: Use Node.js
17
+ uses: actions/setup-node@v6
18
+ with:
19
+ node-version: "24.x"
20
+ - name: Run tests
21
+ run: npm test
22
+
23
+ publish:
24
+ needs: test
25
+ if: github.event_name == 'release'
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v6
29
+ - name: Use Node.js
30
+ uses: actions/setup-node@v6
31
+ with:
32
+ node-version: "24.x"
33
+ registry-url: "https://registry.npmjs.org"
34
+ - name: Publish to npm
35
+ run: npm publish
36
+ env:
37
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # 🤖 Ramenos CLI
2
+
3
+ [![npm version](https://img.shields.io/npm/v/ramenos.svg?style=flat-square)](https://www.npmjs.com/package/ramenos)
4
+ [![0 dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg?style=flat-square)](https://www.npmjs.com/package/ramenos)
5
+ [![CI Tests](https://img.shields.io/github/actions/workflow/status/maxylev/ramenos/ci.yml?style=flat-square)](https://github.com/maxylev/ramenos/actions)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)
7
+
8
+ A lightning-fast, **zero-dependency** CLI to share and install AI multi-agent orchestration configurations directly from GitHub into your local workspace.
9
+
10
+ Designed for modern AI coding assistants like [OpenCode](https://opencode.ai/). Bootstrapping an elite multi-agent AI team is now just one command away.
11
+
12
+ ---
13
+
14
+ ## ✨ Features
15
+
16
+ - 🚀 **Zero Dependencies**: Built with native Node.js. Incredibly fast execution.
17
+ - 🔗 **Smart Caching**: Uses local caching to safely symlink agent files without cluttering your project.
18
+ - 🐙 **Flexible Git Support**: Supports GitHub shorthand, raw HTTPS, SSH, and deep folder trees.
19
+ - 🎯 **Dynamic Frameworks**: By default targets `opencode`, but supports dynamic path generation for *any* agent framework you provide.
20
+
21
+ ## 🚀 Quick Start
22
+
23
+ No global install required! Just run it via `npx` inside your project directory:
24
+
25
+ ```bash
26
+ # Add an AI agent team to your current project (defaults to OpenCode)
27
+ npx ramenos add maxylev/ramenos
28
+
29
+ # Add agents globally to your machine
30
+ npx ramenos add maxylev/ramenos --global
31
+ ```
32
+
33
+ ## 📖 Usage Options
34
+
35
+ ```text
36
+ Usage: ramenos add <repository> [options]
37
+
38
+ Options:
39
+ -g, --global Install to user directory instead of project workspace
40
+ -a, --agent <agents...> Target specific agent frameworks (defaults to 'opencode')
41
+ --copy Copy files instead of symlinking (useful to modify them locally)
42
+ -y, --yes Skip all confirmation prompts
43
+ -h, --help Display help message
44
+ ```
45
+
46
+ ## 🎯 Dynamic Target Paths
47
+
48
+ `ramenos` dynamically generates file paths based on the `--agent` parameter you pass. If omitted, it gracefully defaults to `opencode`.
49
+
50
+ | Command | Local Path Generated | Global Path Generated (`-g`) |
51
+ | :--- | :--- | :--- |
52
+ | `ramenos add repo` | `.opencode/agents/` | `~/.config/opencode/agents/` |
53
+ | `ramenos add repo -a myapp` | `.myapp/agents/` | `~/.config/myapp/agents/` |
54
+ | `ramenos add repo -a foo bar` | `.foo/agents/` & `.bar/agents/` | `~/.config/foo/agents/` & `~/.config/bar/agents/` |
55
+
56
+ ## 🔗 Supported Repository Formats
57
+
58
+ Ramenos looks for an `agents/` folder by default in the root of the repository, but supports deep links if you want to pull a specific sub-folder.
59
+
60
+ **1. GitHub Shorthand**:
61
+ ```bash
62
+ npx ramenos add my-labs/my-awesome-agents
63
+ ```
64
+
65
+ **2. Direct Sub-folder Links** (Grab specific agents from a larger monorepo):
66
+ ```bash
67
+ npx ramenos add https://github.com/ai-labs/my-agents/tree/main/agents/orchestration
68
+ ```
69
+
70
+ **3. SSH Formats** (For private repositories, assuming SSH keys are configured):
71
+ ```bash
72
+ npx ramenos add git@github.com:my-company/internal-agents.git
73
+ ```
74
+
75
+ ## 💡 Examples
76
+
77
+ **Install to multiple AI assistant structures at once:**
78
+ ```bash
79
+ npx ramenos add maxylev/ramenos -a opencode claude-code codex
80
+ ```
81
+
82
+ **Copy files instead of symlinking (so you can edit the prompts):**
83
+ ```bash
84
+ npx ramenos add maxylev/ramenos --copy
85
+ ```
86
+
87
+ **CI/CD or Automated Scripting (Skip all prompts):**
88
+ ```bash
89
+ npx ramenos add maxylev/ramenos --yes
90
+ ```
91
+
92
+ ## 🛠️ Creating Your Own Agent Repo
93
+
94
+ Want to share your own agents via `ramenos`? Just create a public GitHub repository and place your markdown (`.md`) or JSON config files inside an `agents/` directory at the root.
95
+
96
+ ```text
97
+ my-awesome-agents/
98
+ ├── README.md
99
+ └── agents/
100
+ ├── orchestrator.md
101
+ ├── developer.md
102
+ └── tester.md
103
+ ```
104
+
105
+ Then anyone can install your setup using: `npx ramenos add your-username/my-awesome-agents`
106
+
107
+ ## Use 9router
108
+
109
+ Run the following command to start the 9router:
110
+
111
+ ```bash
112
+ npm install -g 9router
113
+ 9router
114
+ ```
115
+
116
+ - Configure 9router:
117
+
118
+ `http://localhost:20128`
119
+
120
+ Create model combos with fallback support:
121
+ - chef
122
+ - cook
123
+ - coordinator
124
+ - critic
125
+ - innovator
126
+ - logistician
127
+ - master
128
+ - taster
129
+
130
+ - Configure opencode:
131
+
132
+ ```json
133
+ {
134
+ "$schema": "https://opencode.ai/config.json",
135
+ "provider": {
136
+ "router": {
137
+ "npm": "@ai-sdk/openai-compatible",
138
+ "name": "router",
139
+ "options": {
140
+ "baseURL": "http://localhost:20128/v1"
141
+ },
142
+ "models": {
143
+ "chef": {
144
+ "name": "chef"
145
+ },
146
+ "cook": {
147
+ "name": "cook"
148
+ },
149
+ "coordinator": {
150
+ "name": "coordinator"
151
+ },
152
+ "critic": {
153
+ "name": "critic"
154
+ },
155
+ "innovator": {
156
+ "name": "innovator"
157
+ },
158
+ "logistician": {
159
+ "name": "logistician"
160
+ },
161
+ "master": {
162
+ "name": "master"
163
+ },
164
+ "taster": {
165
+ "name": "taster"
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ Run opencode with websearch:
174
+
175
+ ```bash
176
+ OPENCODE_ENABLE_EXA=1 opencode
177
+ ```
package/agents/chef.md ADDED
@@ -0,0 +1,41 @@
1
+ ---
2
+ description: Orchestrator. Manages all agents, delegates tasks, maintains full context, evaluates responses, and strictly enforces zero-trust best practices.
3
+ mode: primary
4
+ color: "#27CCF5"
5
+ model: router/chef
6
+ temperature: 0.1
7
+ permission:
8
+ task: allow
9
+ edit: allow
10
+ bash: allow
11
+ websearch: allow
12
+ webfetch: allow
13
+ question: allow
14
+ websearch_*: allow
15
+ webfetch_*: allow
16
+ ---
17
+
18
+ You are the Orchestrator, a strict, stern, and fair perfectionist managing a highly autonomous AI engineering team. You are strictly accountable to the human user.
19
+
20
+ **MISSION:**
21
+ To conceptualize, build, test, and deploy a self-sustaining, subsistence-profitable startup (generating just enough revenue to cover VPS rental, LLM tokens, and utility bills to buy time for growth without VC funding). The business MUST be entirely crypto-native (EVM, SVM, Bitcoin, etc.) and bypass the traditional banking system entirely. All payments and receipts must be in cryptocurrency.
22
+
23
+ **YOUR WORKFLOW:**
24
+ At the start of the session, ALWAYS ask the user for their preference:
25
+ 1. Do they want you to report back and ask for approval after every single stage?
26
+ 2. Or should you remain completely autonomous until the final deployment is operational?
27
+
28
+ You manage the following pipeline via subagents:
29
+ 1. `@innovator` -> Brainstorms in `1_AMAZING_IDEAS.md`
30
+ 2. `@taster` -> Validates/filters ideas in `2_REALISTIC_IDEAS.md`
31
+ 3. `@master` -> Architect. Chooses an idea, builds `.agents/skills/[package]/SKILL.md` references, writes `3_IDEA_SPECIFICATION.md`
32
+ 4. `@coordinator` -> Creates task breakdown in `4_IMPLEMENTATION_PLAN.md`
33
+ 5. `@cook` -> Developer. Implements code and marks off plan tasks.
34
+ 6. `@critic` -> Tester. Creates `5_E2E_TESTING.md` and runs real-data tests.
35
+ 7. `@logistician` -> DevOps. Writes `6_PROJECT_DEPLOYMENT.md` and automates release.
36
+
37
+ **ZERO-TRUST POLICY:**
38
+ - You trust NO ONE. You double-check all work returned by subagents.
39
+ - NO MOCKS, NO PLACEHOLDERS, NO HARDCODED DATA. If a subagent uses them, you immediately reject the work and send it back for revision.
40
+ - If the output does not meet enterprise-grade best practices, send it back with stern feedback.
41
+ - You do not do the work yourself. You delegate to your specialized subagents using the task tool and evaluate their output.
package/agents/cook.md ADDED
@@ -0,0 +1,24 @@
1
+ ---
2
+ description: Developer. Lead software engineer implementing project tasks based strictly on the plan.
3
+ mode: subagent
4
+ model: router/cook
5
+ temperature: 0.2
6
+ permission:
7
+ edit: allow
8
+ bash: allow
9
+ websearch: allow
10
+ webfetch: allow
11
+ question: allow
12
+ hidden: true
13
+ ---
14
+
15
+ You are the Developer. You are the lead software engineer responsible for writing the actual production code.
16
+
17
+ Your tasks:
18
+ 1. Follow `4_IMPLEMENTATION_PLAN.md` sequentially. Do not skip steps.
19
+ 2. Write real, production-ready code.
20
+ 3. **CRITICAL RULE:** Zero mocks. Zero placeholders like "TODO: implement later". Zero hardcoded business logic data. You write the actual working implementation.
21
+ 4. Read `.agents/skills/[package]/SKILL.md` before using any library to ensure you conform to the Architect's gathered best practices.
22
+ 5. Write unit tests for your code as you go.
23
+ 6. Update `4_IMPLEMENTATION_PLAN.md` meticulously, changing states to `[~]`, `[x]`, or `[!]` as you work.
24
+ 7. If you encounter an impossible hurdle, mark it `[!]` and report back to the Orchestrator with terminal outputs and error logs.
@@ -0,0 +1,27 @@
1
+ ---
2
+ description: Planner. Analyzes specs and skills to create a rigorous, step-by-step implementation plan.
3
+ mode: subagent
4
+ model: router/coordinator
5
+ temperature: 0.1
6
+ permission:
7
+ edit: allow
8
+ bash: allow
9
+ websearch: allow
10
+ webfetch: allow
11
+ question: allow
12
+ hidden: true
13
+ ---
14
+
15
+ You are the Planner. Your goal is to translate abstract architecture into a concrete, executable roadmap for the Developer.
16
+
17
+ Your tasks:
18
+ 1. Deeply analyze `3_IDEA_SPECIFICATION.md` and all files inside `.agents/skills/`.
19
+ 2. Create a comprehensive implementation roadmap in `4_IMPLEMENTATION_PLAN.md`.
20
+ 3. Break every single technical requirement into atomic subtasks.
21
+ 4. You MUST format the task states precisely using checkboxes:
22
+ - `[ ]` Pending
23
+ - `[x]` Completed
24
+ - `[~]` In Progress
25
+ - `[!]` Blocked / Error
26
+ 5. Order tasks strictly by dependency (e.g., environment setup -> database -> core logic -> API -> crypto integrations -> frontend).
27
+ 6. State explicitly that mocks, placeholders, and hardcoded values are banned from the codebase.
@@ -0,0 +1,22 @@
1
+ ---
2
+ description: Tester. QA engineer focused on executing end-to-end tests using real data and network environments.
3
+ mode: subagent
4
+ model: router/critic
5
+ temperature: 0.1
6
+ permission:
7
+ edit: allow
8
+ bash: allow
9
+ websearch: allow
10
+ webfetch: allow
11
+ question: allow
12
+ hidden: true
13
+ ---
14
+
15
+ You are the Tester. Your responsibility is to ensure the software works in reality, not just in isolated unit tests.
16
+
17
+ Your tasks:
18
+ 1. Once the Developer achieves major milestones, formulate a rigorous testing plan in `5_E2E_TESTING.md`.
19
+ 2. Your E2E plan must connect to real APIs, real testnets (for crypto transactions), and use real data.
20
+ 3. Mocks are explicitly forbidden during your validation phase.
21
+ 4. Execute the tests via bash commands. Verify that cryptocurrency transactions are correctly indexed, recognized, and trigger the proper business logic.
22
+ 5. If an E2E test fails, aggressively document the failure, capture the trace, and report it back to the Orchestrator so the Developer can be disciplined and fix it.
@@ -0,0 +1,26 @@
1
+ ---
2
+ description: Dreamer. Conceives highly original, unique, and out-of-the-box startup ideas.
3
+ mode: subagent
4
+ model: router/innovator
5
+ temperature: 0.9
6
+ top_p: 0.9
7
+ permission:
8
+ edit: allow
9
+ bash: allow
10
+ websearch: allow
11
+ webfetch: allow
12
+ question: allow
13
+ hidden: true
14
+ ---
15
+
16
+ You are the Dreamer. Your goal is to conceptualize the most unusual, incredible, completely original, and unique startup ideas.
17
+
18
+ Your constraints are:
19
+ 1. The business must be capable of reaching subsistence-profitability (covering basic running costs like VPS, LLM API tokens, utilities) quickly.
20
+ 2. It MUST operate completely outside the traditional banking system. Only cryptocurrency operations (EVM, SVM, Bitcoin, etc.) are permitted for revenue generation and operational payments.
21
+
22
+ Your tasks:
23
+ - Brainstorm wildly. Do not restrict yourself to what is currently normal.
24
+ - Create or update the file `1_AMAZING_IDEAS.md`.
25
+ - Detail the core premise, the crypto-monetization loop, and why the idea is extraordinary.
26
+ - Provide a wide array of options (at least 10). Do not worry about technical feasibility; your only job is maximum originality.
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: DevOps. Automates CI/CD processes, deployment pipelines, and sets up hosting.
3
+ mode: subagent
4
+ model: router/logistician
5
+ temperature: 0.1
6
+ permission:
7
+ edit: allow
8
+ bash: allow
9
+ websearch: allow
10
+ webfetch: allow
11
+ question: allow
12
+ hidden: true
13
+ ---
14
+
15
+ You are the DevOps Engineer. You handle the bridge between the codebase and the public internet.
16
+
17
+ Your tasks:
18
+ 1. Draft the deployment strategy in `6_PROJECT_DEPLOYMENT.md`.
19
+ 2. Configure automated pipelines (e.g., GitHub Actions, GitLab CI) for linting, testing, and building.
20
+ 3. Configure deployment manifests (Docker, PM2, systemd, etc.) for a Linux VPS environment.
21
+ 4. Keep in mind the strict requirement: we must pay for the infrastructure using cryptocurrency. If applicable, configure deployments leveraging decentralized hosting (like IPFS, Arweave, Akash, or crypto-accepting VPS providers).
22
+ 5. Ensure environment variables and secrets are handled securely without exposing them in the repository.
23
+ 6. Do the actual execution to test the build and deployment processes. Ensure the final product is live and functional.
@@ -0,0 +1,22 @@
1
+ ---
2
+ description: Architect. Selects the idea, gathers latest tech details via web, builds technical specification and dependency skills.
3
+ mode: subagent
4
+ model: router/master
5
+ temperature: 0.1
6
+ permission:
7
+ edit: allow
8
+ bash: allow
9
+ websearch: allow
10
+ webfetch: allow
11
+ question: allow
12
+ hidden: true
13
+ ---
14
+
15
+ You are the Architect. You define how the software will actually be built, ensuring the team uses modern, exact, and error-free tooling.
16
+
17
+ Your tasks:
18
+ 1. Read `2_REALISTIC_IDEAS.md`. Optionally ask the Orchestrator for the final selection, or pick the highest feasibility score if told to proceed autonomously.
19
+ 2. Once the idea is locked, research the absolute latest versions of all required libraries, packages, frameworks, and crypto SDKs.
20
+ 3. For every core dependency, create a dedicated skill file in `.agents/skills/[package_name]/SKILL.md`. This file MUST contain the most critical, up-to-date best practices, initialization snippets, and anti-patterns for that specific package based on current documentation.
21
+ 4. Write a highly detailed, rigid technical specification into `3_IDEA_SPECIFICATION.md`.
22
+ 5. The specification must define the exact tech stack, database schema, smart contract architectures (if applicable), and strictly mandate how crypto payments will be processed and verified.
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Realist. Researches the potential of wild ideas online, filters them, and identifies viable solutions for the real world.
3
+ mode: subagent
4
+ model: router/taster
5
+ temperature: 0.3
6
+ permission:
7
+ edit: allow
8
+ bash: allow
9
+ websearch: allow
10
+ webfetch: allow
11
+ question: allow
12
+ hidden: true
13
+ ---
14
+
15
+ You are the Realist. Your job is to bring the Dreamer's ideas down to earth using data and research.
16
+
17
+ Your tasks:
18
+ 1. Read `1_AMAZING_IDEAS.md`.
19
+ 2. Conduct deep web research to validate the market potential, existing competitors, and technological viability of the ideas.
20
+ 3. Filter out ideas that are impossible or technically unachievable for a AI agent team.
21
+ 4. Modify and refine the surviving ideas into actionable, practical business models.
22
+ 5. Ensure the crypto-native payment requirement (EVM, SVM, Bitcoin, etc.) is fully viable without relying on traditional fiat off-ramps/on-ramps that require KYC/banking.
23
+ 6. Output the refined list of viable ideas to `2_REALISTIC_IDEAS.md`. Include a strict feasibility score and required core technologies for each.
package/bin/ramenos.js ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ parseArgs,
5
+ parseInput,
6
+ fetchRepo,
7
+ installAgents,
8
+ colorize,
9
+ } from "../src/index.js";
10
+
11
+ function printHelp() {
12
+ console.log(`
13
+ ${colorize("cyan", "🤖 Ramenos CLI")}
14
+ A fast, zero-dependency CLI to share and install AI multi-agent orchestration files.
15
+
16
+ ${colorize("yellow", "Usage:")}
17
+ ramenos add <repository> [options]
18
+
19
+ ${colorize("yellow", "Options:")}
20
+ -g, --global Install globally to user directory (~/.config/) instead of local project
21
+ -a, --agent <agents...> Target frameworks. Defaults to 'opencode' if omitted.
22
+ (e.g., '-a xxx yyy' creates .xxx/agents and .yyy/agents)
23
+ --copy Copy files completely instead of creating symlinks
24
+ -y, --yes Skip all confirmation prompts
25
+ -h, --help Display this help message
26
+
27
+ ${colorize("yellow", "Examples:")}
28
+ ramenos add ai-labs/my-agents
29
+ ramenos add ./my-labs/my-awesome-agents
30
+ ramenos add ai-labs/my-agents -g -a opencode codex claude-code
31
+ ramenos add https://github.com/ai-labs/my-agents/tree/main/agents/my --copy
32
+ `);
33
+ }
34
+
35
+ async function run() {
36
+ const args = process.argv.slice(2);
37
+ const options = parseArgs(args);
38
+
39
+ if (options.help || !options.command) {
40
+ printHelp();
41
+ process.exit(0);
42
+ }
43
+
44
+ if (options.command === "add") {
45
+ if (!options.repository) {
46
+ console.error(
47
+ colorize("red", "\n❌ Error: Repository argument is required."),
48
+ );
49
+ console.log(colorize("gray", " Try: ramenos add user/repo\n"));
50
+ process.exit(1);
51
+ }
52
+
53
+ try {
54
+ const repoConfig = parseInput(options.repository);
55
+ if (!repoConfig) throw new Error("Invalid repository format provided.");
56
+
57
+ const sourcePath = fetchRepo(repoConfig);
58
+ await installAgents(sourcePath, options);
59
+
60
+ console.log(
61
+ colorize("green", "\n✨ Done! Agent files successfully installed.\n"),
62
+ );
63
+ } catch (err) {
64
+ console.error(colorize("red", `\n❌ Fatal Error: ${err.message}\n`));
65
+ process.exit(1);
66
+ }
67
+ } else {
68
+ console.error(
69
+ colorize("red", `\n❌ Error: Unknown command '${options.command}'\n`),
70
+ );
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ run();
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "ramenos",
3
+ "version": "1.0.0",
4
+ "description": "A fast CLI to share and install AI multi-agent orchestration files",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "ramenos": "bin/ramenos.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node bin/ramenos.js",
12
+ "test": "node --test"
13
+ },
14
+ "engines": {
15
+ "node": ">=24.0.0"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/maxylev/ramenos.git"
20
+ },
21
+ "keywords": [
22
+ "ai",
23
+ "agents",
24
+ "opencode",
25
+ "cli"
26
+ ],
27
+ "author": "Max Ylev",
28
+ "license": "MIT"
29
+ }
package/src/index.js ADDED
@@ -0,0 +1,313 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import { execSync } from "child_process";
5
+ import readline from "readline";
6
+
7
+ export const RAMENOS_CACHE_DIR = path.join(os.homedir(), ".ramenos", "cache");
8
+
9
+ export const colors = {
10
+ reset: "\x1b[0m",
11
+ red: "\x1b[31m",
12
+ green: "\x1b[32m",
13
+ yellow: "\x1b[33m",
14
+ blue: "\x1b[34m",
15
+ cyan: "\x1b[36m",
16
+ gray: "\x1b[90m",
17
+ };
18
+
19
+ /**
20
+ * Applies terminal colors to text
21
+ */
22
+ export function colorize(color, text) {
23
+ return `${colors[color]}${text}${colors.reset}`;
24
+ }
25
+
26
+ /**
27
+ * Dynamically resolves global and local paths based on the agent/framework name
28
+ * @param {string} framework - The framework name (e.g., 'opencode', 'claude')
29
+ * @returns {{ global: string, local: string }}
30
+ */
31
+ export function getTargetPaths(framework) {
32
+ // Sanitize to prevent directory traversal (e.g., ../)
33
+ const safeName = framework.toLowerCase().replace(/[^a-z0-9-]/g, "");
34
+
35
+ return {
36
+ global: path.join(os.homedir(), ".config", safeName, "agents"),
37
+ local: path.join(process.cwd(), `.${safeName}`, "agents"),
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Parses CLI arguments manually to keep the CLI zero-dependency
43
+ */
44
+ export function parseArgs(args) {
45
+ const options = {
46
+ global: false,
47
+ agent: [],
48
+ copy: false,
49
+ yes: false,
50
+ command: null,
51
+ repository: null,
52
+ help: false,
53
+ };
54
+
55
+ for (let i = 0; i < args.length; i++) {
56
+ const arg = args[i];
57
+ if (arg === "-h" || arg === "--help") {
58
+ options.help = true;
59
+ } else if (arg === "add" && !options.command) {
60
+ options.command = "add";
61
+ if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
62
+ options.repository = args[++i];
63
+ }
64
+ } else if (arg === "-g" || arg === "--global") {
65
+ options.global = true;
66
+ } else if (arg === "-y" || arg === "--yes") {
67
+ options.yes = true;
68
+ } else if (arg === "--copy") {
69
+ options.copy = true;
70
+ } else if (arg === "-a" || arg === "--agent") {
71
+ while (i + 1 < args.length && !args[i + 1].startsWith("-")) {
72
+ options.agent.push(args[++i]);
73
+ }
74
+ }
75
+ }
76
+ return options;
77
+ }
78
+
79
+ /**
80
+ * Extracts URL, branch, and subpaths from Git input, OR resolves local directories
81
+ */
82
+ export function parseInput(input) {
83
+ if (!input) return null;
84
+
85
+ // 1. Resolve local path
86
+ let resolvedPath = input;
87
+ if (input.startsWith("~/")) {
88
+ resolvedPath = path.join(os.homedir(), input.slice(2));
89
+ } else {
90
+ resolvedPath = path.resolve(input);
91
+ }
92
+
93
+ // 2. Check if it's explicitly a local path OR if the directory actually exists
94
+ const isExplicitLocal =
95
+ input.startsWith("./") ||
96
+ input.startsWith("../") ||
97
+ input.startsWith("/") ||
98
+ input.startsWith("~/") ||
99
+ input.startsWith(".\\") || // Windows relative
100
+ input.startsWith("..\\") || // Windows relative
101
+ /^[A-Za-z]:[\\/]/.test(input); // Windows absolute (e.g. C:\)
102
+
103
+ if (
104
+ isExplicitLocal ||
105
+ (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory())
106
+ ) {
107
+ // If the folder contains an "agents" subdirectory, use it automatically!
108
+ const agentsSubdir = path.join(resolvedPath, "agents");
109
+ if (
110
+ fs.existsSync(agentsSubdir) &&
111
+ fs.statSync(agentsSubdir).isDirectory()
112
+ ) {
113
+ resolvedPath = agentsSubdir;
114
+ }
115
+
116
+ return {
117
+ isLocal: true,
118
+ sourcePath: resolvedPath,
119
+ };
120
+ }
121
+
122
+ // 3. Matches: https://github.com/user/repo/tree/branch/path
123
+ const treeMatch = input.match(
124
+ /github\.com\/([^/]+\/[^/]+)\/tree\/([^/]+)\/(.+)/,
125
+ );
126
+ if (treeMatch) {
127
+ return {
128
+ url: `https://github.com/${treeMatch[1]}.git`,
129
+ branch: treeMatch[2],
130
+ subpath: treeMatch[3],
131
+ id: treeMatch[1].replace("/", "_"),
132
+ };
133
+ }
134
+
135
+ // 4. Matches SSH or raw HTTPs
136
+ if (input.startsWith("git@") || input.startsWith("http")) {
137
+ const idMatch = input.match(/([^/:]+\/[^/.]+)(\.git)?$/);
138
+ return {
139
+ url: input,
140
+ branch: null,
141
+ subpath: "agents",
142
+ id: idMatch
143
+ ? idMatch[1].replace("/", "_").replace(".git", "")
144
+ : "custom_repo",
145
+ };
146
+ }
147
+
148
+ // 5. Matches shorthand: user/repo
149
+ return {
150
+ url: `https://github.com/${input}.git`,
151
+ branch: null,
152
+ subpath: "agents",
153
+ id: input.replace("/", "_"),
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Creates an interactive Yes/No prompt
159
+ */
160
+ export function askConfirm(question) {
161
+ const rl = readline.createInterface({
162
+ input: process.stdin,
163
+ output: process.stdout,
164
+ });
165
+ return new Promise((resolve) => {
166
+ rl.question(`${question} (Y/n) `, (answer) => {
167
+ rl.close();
168
+ const clean = answer.trim().toLowerCase();
169
+ resolve(clean === "" || clean === "y" || clean === "yes");
170
+ });
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Clones the repository into cache, OR returns the local directory path
176
+ */
177
+ export function fetchRepo(repoConfig) {
178
+ // If it's a local directory, bypass git and cache entirely
179
+ if (repoConfig.isLocal) {
180
+ if (!fs.existsSync(repoConfig.sourcePath)) {
181
+ throw new Error(`Local path does not exist: ${repoConfig.sourcePath}`);
182
+ }
183
+ console.log(
184
+ colorize("cyan", `\n📁 Using local directory: ${repoConfig.sourcePath}`),
185
+ );
186
+ return repoConfig.sourcePath;
187
+ }
188
+
189
+ // Otherwise, handle remote git repository
190
+ if (!fs.existsSync(RAMENOS_CACHE_DIR)) {
191
+ fs.mkdirSync(RAMENOS_CACHE_DIR, { recursive: true });
192
+ }
193
+
194
+ const targetDir = path.join(RAMENOS_CACHE_DIR, repoConfig.id);
195
+ console.log(
196
+ colorize("cyan", `\n📥 Fetching repository: ${repoConfig.url}...`),
197
+ );
198
+
199
+ try {
200
+ if (fs.existsSync(targetDir)) {
201
+ execSync(`git fetch --all`, { cwd: targetDir, stdio: "ignore" });
202
+ if (repoConfig.branch) {
203
+ execSync(`git reset --hard origin/${repoConfig.branch}`, {
204
+ cwd: targetDir,
205
+ stdio: "ignore",
206
+ });
207
+ } else {
208
+ execSync(`git reset --hard HEAD`, { cwd: targetDir, stdio: "ignore" });
209
+ execSync(`git pull`, { cwd: targetDir, stdio: "ignore" });
210
+ }
211
+ } else {
212
+ const branchFlag = repoConfig.branch ? `-b ${repoConfig.branch}` : "";
213
+ execSync(`git clone ${branchFlag} ${repoConfig.url} ${targetDir}`, {
214
+ stdio: "ignore",
215
+ });
216
+ }
217
+ } catch (error) {
218
+ throw new Error(
219
+ "Failed to fetch repository. Ensure it exists, is public/accessible, and git is installed.",
220
+ );
221
+ }
222
+
223
+ const sourcePath = path.join(targetDir, repoConfig.subpath);
224
+ if (!fs.existsSync(sourcePath)) {
225
+ throw new Error(
226
+ `Could not find path '${repoConfig.subpath}' inside the repository.`,
227
+ );
228
+ }
229
+
230
+ return sourcePath;
231
+ }
232
+
233
+ /**
234
+ * Installs (symlinks or copies) the agents to the specified target directories
235
+ */
236
+ export async function installAgents(sourcePath, options) {
237
+ // Default strictly to "opencode" if no explicit agents provided
238
+ const targetFrameworks =
239
+ options.agent.length > 0 ? options.agent : ["opencode"];
240
+ const scopeName = options.global ? "Global" : "Project Local";
241
+
242
+ for (const framework of targetFrameworks) {
243
+ const paths = getTargetPaths(framework);
244
+ const destDir = options.global ? paths.global : paths.local;
245
+
246
+ console.log(
247
+ colorize("blue", `\n🚀 Target Framework: [${framework}] -> ${destDir}`),
248
+ );
249
+
250
+ if (!options.yes) {
251
+ const isConfirmed = await askConfirm(
252
+ `Install agents to ${scopeName} ${framework} directory?`,
253
+ );
254
+ if (!isConfirmed) {
255
+ console.log(colorize("gray", " Skipped."));
256
+ continue;
257
+ }
258
+ }
259
+
260
+ if (!fs.existsSync(destDir)) {
261
+ fs.mkdirSync(destDir, { recursive: true });
262
+ }
263
+
264
+ const files = fs.readdirSync(sourcePath);
265
+ let installedCount = 0;
266
+
267
+ for (const file of files) {
268
+ // Ignore hidden files like .git
269
+ if (file.startsWith(".")) continue;
270
+
271
+ const srcFile = path.join(sourcePath, file);
272
+ const destFile = path.join(destDir, file);
273
+
274
+ // Clean existing target files/symlinks
275
+ if (
276
+ fs.existsSync(destFile) ||
277
+ fs.lstatSync(destFile, { throwIfNoEntry: false })
278
+ ) {
279
+ fs.unlinkSync(destFile);
280
+ }
281
+
282
+ try {
283
+ if (options.copy) {
284
+ fs.cpSync(srcFile, destFile, { recursive: true });
285
+ console.log(colorize("green", ` ✓ Copied: ${file}`));
286
+ } else {
287
+ fs.symlinkSync(srcFile, destFile);
288
+ console.log(colorize("green", ` ✓ Symlinked: ${file}`));
289
+ }
290
+ installedCount++;
291
+ } catch (err) {
292
+ // Handle Windows permission issues with symlinks gracefully
293
+ if (err.code === "EPERM" && !options.copy) {
294
+ fs.cpSync(srcFile, destFile, { recursive: true });
295
+ console.log(
296
+ colorize("green", ` ✓ Copied (Symlink fallback): ${file}`),
297
+ );
298
+ installedCount++;
299
+ } else {
300
+ console.error(
301
+ colorize("red", ` ❌ Failed to install ${file}: ${err.message}`),
302
+ );
303
+ }
304
+ }
305
+ }
306
+
307
+ if (installedCount === 0) {
308
+ console.log(
309
+ colorize("yellow", ` ⚠️ No valid files found in source directory.`),
310
+ );
311
+ }
312
+ }
313
+ }
@@ -0,0 +1,105 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert";
3
+ import path from "path";
4
+ import os from "os";
5
+ import { parseInput, parseArgs, getTargetPaths } from "../src/index.js";
6
+
7
+ test("getTargetPaths: Default opencode", () => {
8
+ const paths = getTargetPaths("opencode");
9
+ assert.strictEqual(
10
+ paths.global,
11
+ path.join(os.homedir(), ".config", "opencode", "agents"),
12
+ );
13
+ assert.strictEqual(
14
+ paths.local,
15
+ path.join(process.cwd(), ".opencode", "agents"),
16
+ );
17
+ });
18
+
19
+ test("getTargetPaths: Dynamic sanitized names", () => {
20
+ const paths = getTargetPaths("My-Agent_123!");
21
+ // Should lowercase and strip non-alphanumeric (except dashes)
22
+ assert.strictEqual(
23
+ paths.local,
24
+ path.join(process.cwd(), ".my-agent123", "agents"),
25
+ );
26
+ });
27
+
28
+ test("parseInput: Shorthand syntax", () => {
29
+ const res = parseInput("maxylev/ramenos");
30
+ assert.strictEqual(res.url, "https://github.com/maxylev/ramenos.git");
31
+ assert.strictEqual(res.id, "maxylev_ramenos");
32
+ assert.strictEqual(res.subpath, "agents");
33
+ });
34
+
35
+ test("parseInput: Full HTTPS syntax", () => {
36
+ const res = parseInput("https://github.com/my-labs/agent.git");
37
+ assert.strictEqual(res.url, "https://github.com/my-labs/agent.git");
38
+ assert.strictEqual(res.id, "my-labs_agent");
39
+ assert.strictEqual(res.subpath, "agents");
40
+ });
41
+
42
+ test("parseInput: SSH syntax", () => {
43
+ const res = parseInput("git@github.com:ai-labs/my-agents.git");
44
+ assert.strictEqual(res.url, "git@github.com:ai-labs/my-agents.git");
45
+ assert.strictEqual(res.id, "ai-labs_my-agents");
46
+ assert.strictEqual(res.subpath, "agents");
47
+ });
48
+
49
+ test("parseInput: Deep tree path", () => {
50
+ const res = parseInput(
51
+ "https://github.com/ai-labs/my-agents/tree/main/agents/my",
52
+ );
53
+ assert.strictEqual(res.url, "https://github.com/ai-labs/my-agents.git");
54
+ assert.strictEqual(res.branch, "main");
55
+ assert.strictEqual(res.subpath, "agents/my");
56
+ assert.strictEqual(res.id, "ai-labs_my-agents");
57
+ });
58
+
59
+ test("parseArgs: Basic add command", () => {
60
+ const opts = parseArgs(["add", "maxylev/ramenos"]);
61
+ assert.strictEqual(opts.command, "add");
62
+ assert.strictEqual(opts.repository, "maxylev/ramenos");
63
+ assert.strictEqual(opts.global, false);
64
+ assert.deepStrictEqual(opts.agent, []); // Empty defaults to opencode down the line
65
+ });
66
+
67
+ test("parseArgs: Multiple flags and agents", () => {
68
+ const opts = parseArgs([
69
+ "add",
70
+ "maxylev/ramenos",
71
+ "-g",
72
+ "-y",
73
+ "-a",
74
+ "opencode",
75
+ "codex",
76
+ "--copy",
77
+ ]);
78
+ assert.strictEqual(opts.command, "add");
79
+ assert.strictEqual(opts.global, true);
80
+ assert.strictEqual(opts.yes, true);
81
+ assert.strictEqual(opts.copy, true);
82
+ assert.deepStrictEqual(opts.agent, ["opencode", "codex"]);
83
+ });
84
+
85
+ test("parseArgs: Help flag", () => {
86
+ const opts1 = parseArgs(["-h"]);
87
+ const opts2 = parseArgs(["--help"]);
88
+ assert.strictEqual(opts1.help, true);
89
+ assert.strictEqual(opts2.help, true);
90
+ });
91
+
92
+ test("parseInput: Explicit local relative path", () => {
93
+ const res = parseInput("./my-labs/my-awesome-agents");
94
+ assert.strictEqual(res.isLocal, true);
95
+ assert.strictEqual(
96
+ res.sourcePath,
97
+ path.resolve("./my-labs/my-awesome-agents"),
98
+ );
99
+ });
100
+
101
+ test("parseInput: Explicit local absolute path", () => {
102
+ const res = parseInput("/usr/local/agents");
103
+ assert.strictEqual(res.isLocal, true);
104
+ assert.strictEqual(res.sourcePath, path.resolve("/usr/local/agents"));
105
+ });