create-oma-app 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 open-multi-agent contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # create-oma-app
2
+
3
+ Scaffold a runnable multi-agent demo on [`@open-multi-agent/core`](https://www.npmjs.com/package/@open-multi-agent/core) — one command from zero to a live agent DAG.
4
+
5
+ ```bash
6
+ npm create oma-app@latest
7
+ ```
8
+
9
+ Answer one prompt (the project name) and you get a small project that, on its first run, shows a **coordinator breaking a single goal into a multi-agent DAG** — agents running in parallel and in dependency order — then opens a dashboard of the run in your browser.
10
+
11
+ ## What you get
12
+
13
+ ```
14
+ my-demo/
15
+ ├── src/index.ts # the demo: one goal → multi-agent DAG → dashboard
16
+ ├── .env.example # OpenAI, or any OpenAI-compatible provider
17
+ ├── package.json # one runtime dependency: @open-multi-agent/core
18
+ ├── tsconfig.json
19
+ └── README.md
20
+ ```
21
+
22
+ - **One runtime dependency** — `@open-multi-agent/core`; `tsx` is the only dev dependency.
23
+ - **Provider-neutral** — OpenAI out of the box, or any OpenAI-compatible endpoint (DeepSeek, Groq, Ollama, …) via `OPENAI_BASE_URL` + `OMA_MODEL`.
24
+ - **No tools, no filesystem writes** — the default demo is pure reasoning, so the first run is fast and robust across providers.
25
+
26
+ ## Run it
27
+
28
+ ```bash
29
+ npm create oma-app@latest my-demo
30
+ cd my-demo
31
+ npm install
32
+ cp .env.example .env # add your key
33
+ npm run dev
34
+ ```
35
+
36
+ ## Next steps
37
+
38
+ Open `src/index.ts` and change the goal, add an agent, or give an agent tools. See the [examples](https://github.com/open-multi-agent/open-multi-agent/tree/main/packages/core/examples) for tool use, MCP, structured output, and providers.
39
+
40
+ ## License
41
+
42
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-oma-app — scaffold a runnable multi-agent demo on @open-multi-agent/core.
4
+ *
5
+ * Usage:
6
+ * npm create oma-app@latest [project-name]
7
+ *
8
+ * One command from zero to a live coordinator-driven agent DAG. The copy logic
9
+ * lives in scaffold.ts; this file is the interactive CLI. Zero runtime
10
+ * dependencies — Node.js built-ins only, so `npm create oma-app` stays a fast,
11
+ * clean one-shot whose own deps never reach the generated project or core.
12
+ */
13
+ import { basename, resolve } from 'node:path';
14
+ import { createInterface } from 'node:readline';
15
+ import { isNonEmptyDir, scaffold } from './scaffold.js';
16
+ // Minimal ANSI styling — no dependency.
17
+ const ESC = {
18
+ reset: '\x1b[0m',
19
+ bold: '\x1b[1m',
20
+ dim: '\x1b[2m',
21
+ cyan: '\x1b[36m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ red: '\x1b[31m',
25
+ };
26
+ const bold = (s) => `${ESC.bold}${s}${ESC.reset}`;
27
+ const dim = (s) => `${ESC.dim}${s}${ESC.reset}`;
28
+ const cyan = (s) => `${ESC.cyan}${s}${ESC.reset}`;
29
+ const green = (s) => `${ESC.green}${s}${ESC.reset}`;
30
+ function prompt(question) {
31
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
32
+ return new Promise((res) => {
33
+ rl.question(question, (answer) => {
34
+ rl.close();
35
+ res(answer.trim());
36
+ });
37
+ });
38
+ }
39
+ async function main() {
40
+ console.log();
41
+ console.log(` ${bold('create-oma-app')}${dim(' — scaffold a multi-agent demo')}`);
42
+ console.log();
43
+ // 1. Resolve the project directory name (argv, else one prompt).
44
+ let projectName = process.argv[2];
45
+ if (!projectName)
46
+ projectName = await prompt(` Project name: ${dim('(oma-demo)')} `);
47
+ projectName = (projectName || 'oma-demo').trim();
48
+ const dirName = basename(projectName);
49
+ const targetDir = resolve(process.cwd(), projectName);
50
+ // 2. Refuse to overwrite a non-empty directory without confirmation.
51
+ if (isNonEmptyDir(targetDir)) {
52
+ console.log();
53
+ console.log(` ${ESC.yellow}!${ESC.reset} ${bold(dirName)} already exists and is not empty.`);
54
+ const answer = (await prompt(` Write into it anyway? ${dim('(y/N)')} `)).toLowerCase();
55
+ if (answer !== 'y' && answer !== 'yes') {
56
+ console.log(` ${ESC.red}✗${ESC.reset} Aborted.`);
57
+ process.exitCode = 1;
58
+ return;
59
+ }
60
+ }
61
+ // 3. Scaffold. Use the basename for the package name so a path-y project
62
+ // name (e.g. ./apps/my-demo) still yields a clean npm name.
63
+ scaffold({ targetDir, projectName: dirName });
64
+ // 4. Next steps.
65
+ console.log();
66
+ console.log(` ${green('✓')} Created ${bold(dirName)}`);
67
+ console.log();
68
+ console.log(' Next steps:');
69
+ console.log();
70
+ console.log(` ${cyan(`cd ${projectName}`)}`);
71
+ console.log(` ${cyan('npm install')}`);
72
+ console.log(` ${cyan('cp .env.example .env')} ${dim('# then add your API key')}`);
73
+ console.log(` ${cyan('npm run dev')}`);
74
+ console.log();
75
+ console.log(dim(' One goal becomes a multi-agent DAG, then a dashboard of the run'));
76
+ console.log(dim(' opens in your browser. OpenAI or any OpenAI-compatible endpoint'));
77
+ console.log(dim(' (DeepSeek, Groq, Ollama, …) — see .env.example.'));
78
+ console.log();
79
+ }
80
+ main().catch((err) => {
81
+ console.error(err);
82
+ process.exitCode = 1;
83
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * create-oma-app scaffold core — copy the bundled template into a target directory,
3
+ * restore the shipped dotfiles, and stamp the project name. No console output,
4
+ * no prompts: the CLI in index.ts handles interaction so this stays testable.
5
+ */
6
+ import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ /** template/ ships alongside dist/ (and src/) one level under the package root. */
10
+ export const TEMPLATE_DIR = fileURLToPath(new URL('../template', import.meta.url));
11
+ /** Turn an arbitrary project name into a valid npm package name. */
12
+ export function toPackageName(input) {
13
+ return (input
14
+ .trim()
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9-~]+/g, '-')
17
+ .replace(/^-+|-+$/g, '') || 'oma-demo');
18
+ }
19
+ /** True when `dir` exists and contains at least one entry. */
20
+ export function isNonEmptyDir(dir) {
21
+ return existsSync(dir) && readdirSync(dir).length > 0;
22
+ }
23
+ /** Copy the template into `targetDir`, restore dotfiles, stamp the name. */
24
+ export function scaffold({ targetDir, projectName, templateDir = TEMPLATE_DIR }) {
25
+ cpSync(templateDir, targetDir, { recursive: true });
26
+ restoreDotfile(targetDir, '_gitignore', '.gitignore');
27
+ restoreDotfile(targetDir, '_env.example', '.env.example');
28
+ const pkgPath = join(targetDir, 'package.json');
29
+ const stamped = readFileSync(pkgPath, 'utf8').replace(/__PROJECT_NAME__/g, toPackageName(projectName));
30
+ writeFileSync(pkgPath, stamped);
31
+ }
32
+ /**
33
+ * Restore a shipped dotfile. npm strips/renames real dotfiles (notably
34
+ * `.gitignore`) on publish, so the template ships them as `_gitignore` /
35
+ * `_env.example` and we rename them on scaffold.
36
+ */
37
+ function restoreDotfile(dir, from, to) {
38
+ const fromPath = join(dir, from);
39
+ if (existsSync(fromPath))
40
+ renameSync(fromPath, join(dir, to));
41
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "create-oma-app",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a runnable multi-agent demo on @open-multi-agent/core — one command from zero to a live agent DAG.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-oma-app": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "template",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "lint": "tsc --noEmit",
21
+ "typecheck:template": "tsc -p template/tsconfig.json",
22
+ "test": "vitest run",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "open-multi-agent",
27
+ "multi-agent",
28
+ "scaffold",
29
+ "create",
30
+ "starter",
31
+ "ai",
32
+ "agent"
33
+ ],
34
+ "author": {
35
+ "name": "open-multi-agent",
36
+ "url": "https://github.com/open-multi-agent"
37
+ },
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/open-multi-agent/open-multi-agent.git",
42
+ "directory": "packages/create-oma-app"
43
+ },
44
+ "homepage": "https://github.com/open-multi-agent/open-multi-agent/tree/main/packages/create-oma-app#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/open-multi-agent/open-multi-agent/issues"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^22.0.0",
53
+ "typescript": "^5.6.0",
54
+ "vitest": "^2.1.0"
55
+ }
56
+ }
@@ -0,0 +1,27 @@
1
+ # Multi-agent demo
2
+
3
+ Built on [`@open-multi-agent/core`](https://www.npmjs.com/package/@open-multi-agent/core), scaffolded with `npm create oma-app`.
4
+
5
+ ## Run
6
+
7
+ ```bash
8
+ npm install
9
+ cp .env.example .env # add your API key
10
+ npm run dev
11
+ ```
12
+
13
+ You'll see a **coordinator** break one goal into a task DAG, several agents run in parallel and in dependency order, and a **dashboard** of the run open in your browser (`dashboard.html`).
14
+
15
+ Works with OpenAI out of the box, or any OpenAI-compatible provider — set `OPENAI_BASE_URL` + `OMA_MODEL` in `.env` (DeepSeek, Groq, Ollama, …). See `.env.example`.
16
+
17
+ ## What's next
18
+
19
+ Everything lives in [`src/index.ts`](src/index.ts):
20
+
21
+ - **Change the goal** — swap the goal string for your own multi-step task.
22
+ - **Add an agent** — add an `AgentConfig` to the team roster; the coordinator routes work to it.
23
+ - **Give agents tools** — add e.g. `tools: ['file_read', 'file_write', 'bash']` to an agent so it can read/write files, run commands, or call MCP servers.
24
+
25
+ More patterns (tool use, MCP, structured output, providers) are in the [examples](https://github.com/open-multi-agent/open-multi-agent/tree/main/packages/core/examples).
26
+
27
+ > Tip: for full editor type-checking, `npm i -D @types/node`. Not needed to run.
@@ -0,0 +1,22 @@
1
+ # Set ONE provider. The demo uses an OpenAI-compatible client.
2
+
3
+ # --- OpenAI (default) -------------------------------------------------------
4
+ OPENAI_API_KEY=sk-...
5
+
6
+ # --- Or any OpenAI-compatible endpoint --------------------------------------
7
+ # Set OPENAI_BASE_URL at the provider and OMA_MODEL to one of its models.
8
+ #
9
+ # DeepSeek:
10
+ # OPENAI_API_KEY=sk-...
11
+ # OPENAI_BASE_URL=https://api.deepseek.com
12
+ # OMA_MODEL=deepseek-chat
13
+ #
14
+ # Groq:
15
+ # OPENAI_API_KEY=gsk_...
16
+ # OPENAI_BASE_URL=https://api.groq.com/openai/v1
17
+ # OMA_MODEL=llama-3.3-70b-versatile
18
+ #
19
+ # Ollama (local, no cloud key — any non-empty value works):
20
+ # OPENAI_API_KEY=ollama
21
+ # OPENAI_BASE_URL=http://localhost:11434/v1
22
+ # OMA_MODEL=llama3.2
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ .env
3
+ dashboard.html
4
+ .agent-workspace/
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx src/index.ts"
8
+ },
9
+ "dependencies": {
10
+ "@open-multi-agent/core": "1.7.0"
11
+ },
12
+ "devDependencies": {
13
+ "tsx": "^4.21.0"
14
+ }
15
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Multi-agent demo — one goal, automatically decomposed into a task DAG.
3
+ *
4
+ * The OpenMultiAgent coordinator reads the goal below, breaks it into tasks,
5
+ * assigns them to the right agents, and runs them in parallel and in dependency
6
+ * order. When the run finishes, a dashboard of the DAG opens in your browser.
7
+ *
8
+ * These agents use NO tools — pure reasoning — so the first run is fast and
9
+ * works on any OpenAI-compatible model. To watch agents use tools (read/write
10
+ * files, run commands, call MCP servers), give an agent a `tools` array; see
11
+ * the examples linked in the README.
12
+ *
13
+ * Run: npm run dev
14
+ */
15
+ import { spawn } from 'node:child_process'
16
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs'
17
+ import { resolve } from 'node:path'
18
+ import { pathToFileURL } from 'node:url'
19
+ import { OpenMultiAgent, renderTeamRunDashboard } from '@open-multi-agent/core'
20
+ import type { AgentConfig, OrchestratorEvent } from '@open-multi-agent/core'
21
+
22
+ // --- Minimal .env loader ----------------------------------------------------
23
+ // Reads .env into process.env so `npm run dev` works right after
24
+ // `cp .env.example .env`. Inlined to keep the project at ONE runtime
25
+ // dependency (no dotenv). Existing env vars win.
26
+ function loadEnv(path = '.env'): void {
27
+ if (!existsSync(path)) return
28
+ for (const raw of readFileSync(path, 'utf8').split('\n')) {
29
+ const line = raw.trim()
30
+ if (!line || line.startsWith('#')) continue
31
+ const eq = line.indexOf('=')
32
+ if (eq === -1) continue
33
+ const key = line.slice(0, eq).trim()
34
+ const val = line.slice(eq + 1).trim().replace(/^["']|["']$/g, '')
35
+ if (key && !(key in process.env)) process.env[key] = val
36
+ }
37
+ }
38
+ loadEnv()
39
+
40
+ // --- Provider (OpenAI-compatible, env-driven) -------------------------------
41
+ // OPENAI_API_KEY is read automatically by the OpenAI client. For another
42
+ // provider, set OPENAI_BASE_URL + OMA_MODEL in .env (DeepSeek, Groq, Ollama…).
43
+ const model = process.env.OMA_MODEL ?? 'gpt-5.4'
44
+
45
+ if (!process.env.OPENAI_API_KEY) {
46
+ console.error(
47
+ '\n Missing OPENAI_API_KEY.\n\n' +
48
+ ' 1. cp .env.example .env\n' +
49
+ ' 2. add a key — OpenAI, or any OpenAI-compatible provider\n' +
50
+ ' (DeepSeek, Groq, Ollama, …; see .env.example)\n',
51
+ )
52
+ process.exit(1)
53
+ }
54
+
55
+ // --- The team: four specialists, no tools, pure reasoning -------------------
56
+ const strategist: AgentConfig = {
57
+ name: 'strategist',
58
+ model,
59
+ systemPrompt: `You define sharp, outcome-focused goals.
60
+ Given a brief, name the few outcomes that matter most. Concrete bullet points, no filler.`,
61
+ maxTurns: 1,
62
+ temperature: 0.3,
63
+ }
64
+
65
+ const planner: AgentConfig = {
66
+ name: 'planner',
67
+ model,
68
+ systemPrompt: `You turn outcomes into a concrete, time-boxed plan.
69
+ Produce clear weekly milestones — each a short, checkable line. No filler.`,
70
+ maxTurns: 1,
71
+ temperature: 0.2,
72
+ }
73
+
74
+ const riskAnalyst: AgentConfig = {
75
+ name: 'risk-analyst',
76
+ model,
77
+ systemPrompt: `You stress-test plans.
78
+ Name the few highest-impact risks and give one concrete, specific mitigation for each.`,
79
+ maxTurns: 1,
80
+ temperature: 0.4,
81
+ }
82
+
83
+ const synthesizer: AgentConfig = {
84
+ name: 'synthesizer',
85
+ model,
86
+ systemPrompt: `You assemble the team's work into one clean deliverable.
87
+ Merge the outcomes, weekly plan, and risk mitigations into a single concise document
88
+ a manager could hand over directly. Use short markdown headings.`,
89
+ maxTurns: 1,
90
+ temperature: 0.2,
91
+ }
92
+
93
+ // --- Live progress: watch the DAG run ---------------------------------------
94
+ function onProgress(event: OrchestratorEvent): void {
95
+ switch (event.type) {
96
+ case 'task_start':
97
+ if (event.agent) console.log(` ▶ ${event.agent} working…`)
98
+ break
99
+ case 'task_complete':
100
+ if (event.agent) console.log(` ✓ ${event.agent} done`)
101
+ break
102
+ case 'error':
103
+ console.error(` ✗ ${event.agent ?? 'run'} failed`)
104
+ break
105
+ }
106
+ }
107
+
108
+ /** Best-effort open in the default browser; prints the path if it can't. */
109
+ function openInBrowser(file: string): void {
110
+ // pathToFileURL handles drive letters, slashes, and spaces on every OS —
111
+ // a hand-built `file://` string breaks on Windows paths.
112
+ const url = pathToFileURL(resolve(file)).href
113
+ const fallback = (): void => console.log(` Open it manually: ${url}`)
114
+ try {
115
+ // On Windows the opener is cmd's `start`, whose first quoted arg is the
116
+ // window title — pass an empty title ("") so the URL isn't swallowed.
117
+ const child =
118
+ process.platform === 'win32'
119
+ ? spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true })
120
+ : spawn(process.platform === 'darwin' ? 'open' : 'xdg-open', [url], { stdio: 'ignore', detached: true })
121
+ child.on('error', fallback)
122
+ child.unref()
123
+ } catch {
124
+ fallback()
125
+ }
126
+ }
127
+
128
+ const orchestrator = new OpenMultiAgent({
129
+ defaultProvider: 'openai',
130
+ defaultModel: model,
131
+ defaultBaseURL: process.env.OPENAI_BASE_URL, // unset = OpenAI
132
+ onProgress,
133
+ })
134
+
135
+ const team = orchestrator.createTeam('demo-team', {
136
+ name: 'demo-team',
137
+ agents: [strategist, planner, riskAnalyst, synthesizer],
138
+ sharedMemory: true,
139
+ })
140
+
141
+ // --- The goal: multi-step, so the coordinator MUST decompose it -------------
142
+ // Kept well over 200 characters so it never hits the single-agent
143
+ // short-circuit — that is what guarantees you see a multi-agent DAG, not one
144
+ // agent. (create-oma-app's tests assert this length.)
145
+ const goal = `Design a 30-day onboarding plan for a software engineer joining a fast-moving startup.
146
+ Step 1: identify the 4 outcomes the new hire should reach by day 30.
147
+ Step 2: turn those outcomes into concrete weekly milestones for weeks 1 through 4.
148
+ Step 3: list the 3 risks most likely to derail onboarding, each with a specific mitigation.
149
+ Step 4: synthesize everything into one concise plan a manager could hand over on day one.`
150
+
151
+ console.log(`\n Goal: ${goal.split('\n')[0]}\n`)
152
+ console.log(' The coordinator is decomposing the goal into a task DAG:\n')
153
+
154
+ const result = await orchestrator.runTeam(team, goal)
155
+
156
+ // --- Summary: the DAG the coordinator built from one goal -------------------
157
+ const line = '─'.repeat(52)
158
+ console.log(`\n ${line}`)
159
+ console.log(` Run complete — success: ${result.success}`)
160
+ console.log(` Tokens: ${result.totalTokenUsage.input_tokens} in / ${result.totalTokenUsage.output_tokens} out`)
161
+ console.log('\n Task DAG (auto-decomposed — you wrote none of this wiring):')
162
+ for (const task of result.tasks ?? []) {
163
+ console.log(` • ${task.title} [${task.assignee ?? '—'}] ${task.status}`)
164
+ }
165
+
166
+ // --- Final plan: the synthesizer merged the team's work into one document ---
167
+ const plan = result.agentResults.get('synthesizer')
168
+ if (plan?.success && plan.output.trim()) {
169
+ console.log(`\n Final plan (by synthesizer):\n ${line}`)
170
+ console.log(plan.output)
171
+ }
172
+
173
+ // --- Dashboard: render the DAG and open it ----------------------------------
174
+ const dashboardPath = 'dashboard.html'
175
+ writeFileSync(dashboardPath, renderTeamRunDashboard(result))
176
+ console.log(`\n ${line}`)
177
+ console.log(` DAG dashboard → ${dashboardPath} (opening in your browser…)`)
178
+ openInBrowser(dashboardPath)
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true
11
+ },
12
+ "include": ["src"]
13
+ }