create-janux 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 ADDED
@@ -0,0 +1,10 @@
1
+ # create-janux
2
+
3
+ Scaffold a new [Janux](https://github.com/aralroca/Janux) app:
4
+
5
+ ```bash
6
+ bunx create-janux my-app
7
+ cd my-app && bun install && bun run dev
8
+ ```
9
+
10
+ Includes a resumable counter island, an `api()` example, and the built-in copilot (configure with `JANUX_MODEL` or a provider API key).
package/bin.ts ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bun
2
+ import { cpSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ const name = process.argv[2];
6
+
7
+ if (!name || !/^[a-z0-9-]+$/.test(name)) {
8
+ console.error('Usage: create-janux <app-name> (kebab-case)');
9
+ process.exit(1);
10
+ }
11
+ const target = join(process.cwd(), name);
12
+
13
+ if (existsSync(target)) {
14
+ console.error(`create-janux: "${name}" already exists`);
15
+ process.exit(1);
16
+ }
17
+ cpSync(join(import.meta.dirname, 'template'), target, { recursive: true });
18
+
19
+ const pkgPath = join(target, 'package.json');
20
+
21
+ writeFileSync(pkgPath, readFileSync(pkgPath, 'utf-8').replace(/__APP_NAME__/g, name));
22
+ console.log(`✔ ${name} created
23
+
24
+ cd ${name}
25
+ bun install
26
+ bun run dev
27
+
28
+ Configure the copilot with JANUX_MODEL or a provider API key (optional).`);
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "create-janux",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a new Janux app: bunx create-janux my-app",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/aralroca/Janux.git"
8
+ },
9
+ "license": "MIT",
10
+ "author": {
11
+ "name": "Aral Roca Gomez",
12
+ "email": "contact@aralroca.com"
13
+ },
14
+ "type": "module",
15
+ "bin": {
16
+ "create-janux": "./bin.ts"
17
+ },
18
+ "files": [
19
+ "bin.ts",
20
+ "template"
21
+ ],
22
+ "homepage": "https://github.com/aralroca/Janux#readme",
23
+ "bugs": "https://github.com/aralroca/Janux/issues",
24
+ "keywords": [
25
+ "janux",
26
+ "agentic-ui",
27
+ "mcp",
28
+ "webmcp",
29
+ "ai-agents",
30
+ "fullstack",
31
+ "framework",
32
+ "bun",
33
+ "vite",
34
+ "resumability",
35
+ "copilot"
36
+ ],
37
+ "engines": {
38
+ "bun": ">=1.3.0"
39
+ }
40
+ }
@@ -0,0 +1,9 @@
1
+ # Copilot model — pick ONE option (RFC §8.1 resolution order)
2
+
3
+ # Option 1: explicit model
4
+ # JANUX_MODEL=anthropic/claude-fable-5
5
+
6
+ # Option 2: provider key only — default model inferred
7
+ # ANTHROPIC_API_KEY=sk-ant-...
8
+ # OPENAI_API_KEY=sk-...
9
+ # GOOGLE_GENERATIVE_AI_API_KEY=...
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "janux dev",
8
+ "build": "janux build",
9
+ "start": "janux start"
10
+ },
11
+ "dependencies": {
12
+ "janux": "^0.1.0",
13
+ "@janux/server": "^0.1.0",
14
+ "@janux/agent": "^0.1.0",
15
+ "@janux/cli": "^0.1.0"
16
+ }
17
+ }
@@ -0,0 +1,5 @@
1
+ import { defineAgent } from '@janux/agent';
2
+
3
+ export default defineAgent({
4
+ instructions: 'You are this app’s copilot. Use the counter tools and api.hello.greet.',
5
+ });
@@ -0,0 +1,4 @@
1
+ import { boot } from 'janux/client';
2
+ import { Counter } from './components/Counter';
3
+
4
+ boot({ defs: [Counter] });
@@ -0,0 +1,29 @@
1
+ import { component, intent, schema, int } from 'janux';
2
+
3
+ export const Counter = component({
4
+ name: 'counter',
5
+ description: 'A counter. Agents can read it (ui://counter) and bump it (counter.inc).',
6
+
7
+ state: schema({ n: int() }),
8
+
9
+ intents: {
10
+ inc: intent({
11
+ description: 'Increment the counter',
12
+ input: schema({ by: int().default(1) }),
13
+ run: ({ state, input }) => (state.n += input.by),
14
+ }),
15
+ reset: intent({
16
+ description: 'Reset the counter to zero',
17
+ guard: 'confirm',
18
+ run: ({ state }) => (state.n = 0),
19
+ }),
20
+ },
21
+
22
+ view: ({ state, intents }) => (
23
+ <section>
24
+ <output>{state.n}</output>
25
+ <button on={intents.inc}>+1</button>
26
+ <button on={intents.reset}>Reset</button>
27
+ </section>
28
+ ),
29
+ });
@@ -0,0 +1,14 @@
1
+ import { Counter } from '../components/Counter';
2
+
3
+ export default function Home() {
4
+ return (
5
+ <main>
6
+ <h1>Welcome to Janux</h1>
7
+ <p>This heading is static HTML (0 KB JS). The counter below is a resumable island.</p>
8
+ <Counter />
9
+ <p>
10
+ Try the agent surface: <code>curl localhost:3000/_janux/manifest</code>
11
+ </p>
12
+ </main>
13
+ );
14
+ }
@@ -0,0 +1,8 @@
1
+ import { api } from '@janux/server';
2
+ import { schema, str } from 'janux';
3
+
4
+ export const greet = api({
5
+ description: 'Greet someone by name',
6
+ input: schema({ name: str() }),
7
+ run: ({ input }) => ({ message: `Hello, ${input.name}! — from a Janux api() tool` }),
8
+ });
@@ -0,0 +1,220 @@
1
+ :root {
2
+ --ink: #1e1b4b;
3
+ --violet: #7c3aed;
4
+ --cyan: #06b6d4;
5
+ --text: #334155;
6
+ --border: #e2e8f0;
7
+ --mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
8
+ }
9
+
10
+ * {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ margin: 0;
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, sans-serif;
17
+ color: var(--text);
18
+ background: #f8fafc;
19
+ -webkit-font-smoothing: antialiased;
20
+ }
21
+
22
+ main {
23
+ max-width: 960px;
24
+ margin: 0 auto;
25
+ padding: 48px 24px 120px;
26
+ }
27
+
28
+ main.shop {
29
+ display: grid;
30
+ grid-template-columns: 1fr 400px;
31
+ gap: 28px;
32
+ }
33
+
34
+ main.shop > h1 {
35
+ grid-column: 1 / -1;
36
+ }
37
+
38
+ h1 {
39
+ margin: 0 0 20px;
40
+ font-size: 2.1rem;
41
+ font-weight: 800;
42
+ letter-spacing: -0.03em;
43
+ color: var(--ink);
44
+ }
45
+
46
+ h2,
47
+ h3 {
48
+ color: var(--ink);
49
+ letter-spacing: -0.02em;
50
+ }
51
+
52
+ a {
53
+ color: var(--violet);
54
+ font-weight: 600;
55
+ }
56
+
57
+ code {
58
+ padding: 2px 6px;
59
+ border-radius: 6px;
60
+ background: #ede9fe;
61
+ color: #5b21b6;
62
+ font-family: var(--mono);
63
+ font-size: 0.88em;
64
+ }
65
+
66
+ button {
67
+ padding: 8px 16px;
68
+ border: none;
69
+ border-radius: 9px;
70
+ background: var(--ink);
71
+ color: #fff;
72
+ font-size: 14px;
73
+ font-weight: 600;
74
+ cursor: pointer;
75
+ transition: transform 0.12s, opacity 0.12s;
76
+ }
77
+
78
+ button:hover {
79
+ opacity: 0.9;
80
+ transform: translateY(-1px);
81
+ }
82
+
83
+ /* ── Cart ───────────────────────────────────────────────────────── */
84
+ .cart {
85
+ padding: 24px;
86
+ border: 1px solid var(--border);
87
+ border-radius: 16px;
88
+ background: #fff;
89
+ box-shadow: 0 8px 30px -18px rgba(30, 27, 75, 0.35);
90
+ }
91
+
92
+ .cart ul {
93
+ margin: 12px 0;
94
+ padding: 0;
95
+ list-style: none;
96
+ }
97
+
98
+ .cart .catalog li,
99
+ .cart .items li {
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: space-between;
103
+ gap: 12px;
104
+ padding: 10px 4px;
105
+ border-bottom: 1px solid var(--border);
106
+ font-size: 15px;
107
+ }
108
+
109
+ .cart .catalog button {
110
+ background: linear-gradient(90deg, var(--violet), var(--cyan));
111
+ }
112
+
113
+ .cart .items li {
114
+ color: var(--ink);
115
+ font-weight: 600;
116
+ }
117
+
118
+ .cart .total {
119
+ margin: 16px 0 8px;
120
+ font-size: 1.2rem;
121
+ font-weight: 800;
122
+ color: var(--ink);
123
+ }
124
+
125
+ .cart .order {
126
+ padding: 10px 14px;
127
+ border-radius: 10px;
128
+ background: #ecfdf5;
129
+ color: #047857;
130
+ font-weight: 600;
131
+ }
132
+
133
+ /* ── Copilot ────────────────────────────────────────────────────── */
134
+ .copilot {
135
+ display: flex;
136
+ flex-direction: column;
137
+ height: fit-content;
138
+ padding: 20px;
139
+ border: 1px solid var(--border);
140
+ border-radius: 16px;
141
+ background: #fff;
142
+ box-shadow: 0 8px 30px -18px rgba(30, 27, 75, 0.35);
143
+ }
144
+
145
+ .copilot h3 {
146
+ margin: 0 0 12px;
147
+ }
148
+
149
+ .copilot .chat {
150
+ margin: 0 0 12px;
151
+ padding: 0;
152
+ list-style: none;
153
+ max-height: 340px;
154
+ overflow-y: auto;
155
+ }
156
+
157
+ .copilot .chat li {
158
+ margin-bottom: 8px;
159
+ padding: 9px 13px;
160
+ border-radius: 12px;
161
+ font-size: 14px;
162
+ line-height: 1.5;
163
+ max-width: 88%;
164
+ }
165
+
166
+ .copilot .chat li.user {
167
+ margin-left: auto;
168
+ background: linear-gradient(90deg, var(--violet), #6d28d9);
169
+ color: #fff;
170
+ }
171
+
172
+ .copilot .chat li.assistant {
173
+ background: #f1f5f9;
174
+ color: var(--ink);
175
+ }
176
+
177
+ .copilot form {
178
+ display: flex;
179
+ gap: 8px;
180
+ }
181
+
182
+ .copilot input {
183
+ flex: 1;
184
+ padding: 9px 13px;
185
+ border: 1px solid var(--border);
186
+ border-radius: 9px;
187
+ font-size: 14px;
188
+ outline: none;
189
+ }
190
+
191
+ .copilot input:focus {
192
+ border-color: var(--violet);
193
+ box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.15);
194
+ }
195
+
196
+ .proposal {
197
+ margin: 0 0 12px;
198
+ padding: 14px;
199
+ border: 1px solid #fde68a;
200
+ border-radius: 12px;
201
+ background: #fffbeb;
202
+ }
203
+
204
+ .proposal p {
205
+ margin: 0 0 10px;
206
+ font-size: 14px;
207
+ color: #92400e;
208
+ }
209
+
210
+ .proposal button + button {
211
+ margin-left: 8px;
212
+ background: #e2e8f0;
213
+ color: var(--ink);
214
+ }
215
+
216
+ @media (max-width: 900px) {
217
+ main.shop {
218
+ grid-template-columns: 1fr;
219
+ }
220
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "esnext",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "noEmit": true,
9
+ "jsx": "react-jsx",
10
+ "jsxImportSource": "janux"
11
+ },
12
+ "include": ["src/**/*"]
13
+ }