create-janux 0.2.0 → 0.2.1

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.
@@ -1,29 +1,27 @@
1
- import { loadTasks } from '../server/tasks.api';
2
- import { TaskBoard } from '../components/TaskBoard';
3
- import { ThemeToggle } from '../components/ThemeToggle';
4
- import { Copilot } from '../components/Copilot';
1
+ import { Counter } from '../components/Counter';
2
+ import { AgentPanel } from '../components/AgentPanel';
5
3
 
6
4
  export const meta = {
7
- title: 'Tasks a Janux app',
8
- description: 'A task board with two faces: a UI for you, typed tools for your copilot.',
5
+ title: 'Welcome to Janux',
6
+ description: 'One component, two faces: a UI for you, typed tools for agents.',
9
7
  };
10
8
 
11
- export default async function Home() {
12
- const saved: any = await loadTasks({});
13
-
9
+ export default function Home() {
14
10
  return (
15
11
  <div class="app">
16
- <header class="topbar">
17
- <span class="brand">✦ Tasks</span>
18
- <ThemeToggle />
12
+ <header class="bar">
13
+ <span class="brand">⚡ Janux</span>
14
+ <span class="bar-hint">The counter is your UI · the right panel is what an agent sees</span>
19
15
  </header>
20
- <main>
21
- <TaskBoard initial={{ tasks: saved.tasks, filter: 'all' }} />
22
- <p class="hint">
23
- This board is also an agent surface — <code>curl localhost:3000/_janux/manifest</code>
24
- </p>
16
+ <main class="split">
17
+ <section class="preview">
18
+ <Counter />
19
+ <p class="curl">
20
+ Same surface over HTTP: <code>curl localhost:3000/_janux/manifest</code>
21
+ </p>
22
+ </section>
23
+ <AgentPanel eager />
25
24
  </main>
26
- <Copilot persist />
27
25
  </div>
28
26
  );
29
27
  }
@@ -1,24 +1,9 @@
1
1
  :root {
2
- --bg: #0e0d24;
3
- --card: #1b1938;
4
- --card-2: #242150;
5
- --text: #e2e8f0;
6
- --muted: #94a3b8;
7
- --violet: #a78bfa;
8
- --cyan: #22d3ee;
9
- --border: #312e81;
10
- --danger: #f87171;
11
- }
12
-
13
- :root[data-theme='light'] {
14
- --bg: #f8fafc;
15
- --card: #ffffff;
16
- --card-2: #eef2ff;
17
- --text: #0f172a;
18
- --muted: #64748b;
2
+ --ink: #0f172a;
19
3
  --violet: #7c3aed;
20
- --cyan: #0891b2;
21
4
  --border: #e2e8f0;
5
+ --muted: #64748b;
6
+ --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
22
7
  }
23
8
 
24
9
  * {
@@ -28,305 +13,265 @@
28
13
  body {
29
14
  margin: 0;
30
15
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif;
31
- background: var(--bg);
32
- color: var(--text);
33
- transition: background 0.25s, color 0.25s;
16
+ background: #fff;
17
+ color: var(--ink);
34
18
  }
35
19
 
36
- .topbar {
20
+ .bar {
37
21
  display: flex;
38
- justify-content: space-between;
39
- align-items: center;
40
- max-width: 560px;
41
- margin: 0 auto;
42
- padding: 22px 16px 0;
22
+ align-items: baseline;
23
+ gap: 12px;
24
+ padding: 12px 16px;
25
+ border-bottom: 1px solid var(--border);
43
26
  }
44
27
 
45
28
  .brand {
46
29
  font-weight: 800;
47
- font-size: 20px;
48
- background: linear-gradient(90deg, var(--violet), var(--cyan));
49
- -webkit-background-clip: text;
50
- background-clip: text;
51
- color: transparent;
52
- }
53
-
54
- .theme-toggle {
55
- padding: 7px 14px;
56
- border: 1px solid var(--border);
57
- border-radius: 999px;
58
- background: var(--card);
59
- color: var(--text);
60
- cursor: pointer;
61
- font-size: 13px;
62
- }
63
-
64
- main {
65
- max-width: 560px;
66
- margin: 24px auto 120px;
67
- padding: 0 16px;
30
+ font-size: 1rem;
68
31
  }
69
32
 
70
- .hint {
71
- text-align: center;
33
+ .bar-hint {
72
34
  color: var(--muted);
73
35
  font-size: 13px;
74
36
  }
75
37
 
76
- .hint code {
77
- background: var(--card-2);
78
- padding: 2px 8px;
79
- border-radius: 6px;
80
- font-size: 12px;
38
+ .split {
39
+ display: grid;
40
+ grid-template-columns: minmax(0, 1fr) 340px;
41
+ height: calc(100vh - 46px);
81
42
  }
82
43
 
83
- /* ── Board ──────────────────────────────────────────────────────── */
84
- .board {
85
- background: var(--card);
86
- border: 1px solid var(--border);
87
- border-radius: 18px;
88
- padding: 22px;
89
- box-shadow: 0 20px 50px -30px rgba(0, 0, 0, 0.6);
44
+ .preview {
45
+ display: flex;
46
+ flex-direction: column;
47
+ align-items: center;
48
+ justify-content: center;
49
+ gap: 28px;
50
+ border-right: 1px solid var(--border);
90
51
  }
91
52
 
92
- .board header {
53
+ /* ── Counter ────────────────────────────────────────────────────── */
54
+ .counter {
93
55
  display: flex;
94
- justify-content: space-between;
95
- align-items: baseline;
96
- margin-bottom: 14px;
56
+ flex-direction: column;
57
+ align-items: center;
58
+ gap: 24px;
97
59
  }
98
60
 
99
- .board h2 {
61
+ .counter h1 {
100
62
  margin: 0;
101
- font-size: 1.3rem;
102
- letter-spacing: -0.02em;
103
- }
104
-
105
- .board .count {
106
- color: var(--muted);
107
- font-size: 13px;
63
+ font-size: 3.75rem;
64
+ font-weight: 800;
65
+ color: #1e1b4b;
108
66
  }
109
67
 
110
- .board form {
68
+ .counter .row {
111
69
  display: flex;
112
- gap: 8px;
70
+ gap: 12px;
113
71
  }
114
72
 
115
- .board input {
116
- flex: 1;
117
- padding: 11px 14px;
118
- border: 1px solid var(--border);
119
- border-radius: 10px;
120
- background: var(--bg);
121
- color: var(--text);
122
- font-size: 14.5px;
123
- outline: none;
73
+ .counter button {
74
+ padding: 10px 28px;
75
+ border: none;
76
+ border-radius: 12px;
77
+ font-size: 1.25rem;
78
+ font-weight: 700;
79
+ color: #fff;
80
+ cursor: pointer;
81
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.15);
124
82
  }
125
83
 
126
- .board input:focus {
127
- border-color: var(--violet);
84
+ .counter .dec {
85
+ background: #64748b;
128
86
  }
129
87
 
130
- .board button {
131
- border: none;
132
- border-radius: 10px;
133
- cursor: pointer;
134
- font-weight: 600;
88
+ .counter .dec:hover {
89
+ background: #475569;
135
90
  }
136
91
 
137
- .board form button {
138
- padding: 0 18px;
92
+ .counter .inc {
139
93
  background: linear-gradient(90deg, #7c3aed, #06b6d4);
140
- color: #fff;
141
94
  }
142
95
 
143
- .filters {
144
- display: flex;
145
- gap: 6px;
146
- margin: 14px 0 6px;
96
+ .counter .inc:hover {
97
+ opacity: 0.9;
147
98
  }
148
99
 
149
- .filters button {
150
- padding: 5px 14px;
151
- border-radius: 999px;
152
- background: transparent;
100
+ .curl {
153
101
  color: var(--muted);
154
- border: 1px solid transparent;
155
102
  font-size: 13px;
156
- text-transform: capitalize;
157
- }
158
-
159
- .filters button.on {
160
- background: var(--card-2);
161
- color: var(--text);
162
- border-color: var(--border);
163
103
  }
164
104
 
165
- .list {
166
- list-style: none;
167
- margin: 8px 0;
168
- padding: 0;
169
- }
170
-
171
- .list li {
172
- display: flex;
173
- align-items: center;
174
- gap: 12px;
175
- padding: 10px 4px;
176
- border-bottom: 1px solid var(--border);
105
+ .curl code {
106
+ padding: 2px 8px;
107
+ border-radius: 6px;
108
+ background: #ede9fe;
109
+ color: #5b21b6;
110
+ font-size: 12px;
111
+ font-family: var(--mono);
177
112
  }
178
113
 
179
- .list li span {
180
- flex: 1;
181
- font-size: 15px;
114
+ /* ── Agent pane ─────────────────────────────────────────────────── */
115
+ .agent-pane {
116
+ overflow-y: auto;
117
+ padding: 14px;
118
+ background: #f8fafc;
182
119
  }
183
120
 
184
- .list li.done span {
185
- text-decoration: line-through;
186
- color: var(--muted);
121
+ .agent-pane h2 {
122
+ margin: 0 0 10px;
123
+ font-size: 0.95rem;
187
124
  }
188
125
 
189
- .check {
190
- width: 26px;
191
- height: 26px;
192
- border-radius: 50%;
193
- border: 2px solid var(--violet) !important;
194
- background: transparent;
195
- color: var(--cyan);
196
- font-size: 14px;
197
- line-height: 1;
126
+ .agent-pane .tool-row {
127
+ margin-bottom: 8px;
128
+ padding: 10px 12px;
129
+ border: 1px solid var(--border);
130
+ border-radius: 10px;
131
+ background: #fff;
132
+ font-size: 13px;
198
133
  }
199
134
 
200
- li.done .check {
201
- background: var(--violet);
202
- color: #fff;
135
+ .agent-pane .tool-row code {
136
+ font-size: 12px;
137
+ font-family: var(--mono);
203
138
  }
204
139
 
205
- .x {
206
- background: transparent;
140
+ .agent-pane .tool-row small {
141
+ display: block;
142
+ margin-top: 2px;
207
143
  color: var(--muted);
208
- font-size: 14px;
209
- padding: 4px 8px;
210
144
  }
211
145
 
212
- .x:hover {
213
- color: var(--danger);
146
+ .agent-pane .tool-row .example {
147
+ display: inline-block;
148
+ margin-top: 6px;
149
+ padding: 2px 8px;
150
+ border-radius: 6px;
151
+ background: #f1f5f9;
152
+ color: var(--muted);
214
153
  }
215
154
 
216
- .board footer {
217
- display: flex;
218
- justify-content: flex-end;
219
- margin-top: 10px;
155
+ .agent-pane .guard {
156
+ margin-left: 6px;
157
+ padding: 1px 8px;
158
+ border-radius: 999px;
159
+ font-size: 11px;
160
+ font-weight: 700;
220
161
  }
221
162
 
222
- .clear {
223
- background: transparent;
224
- color: var(--muted);
225
- font-size: 13px;
226
- padding: 6px 10px;
163
+ .agent-pane .guard.auto {
164
+ background: #ecfdf5;
165
+ color: #047857;
227
166
  }
228
167
 
229
- .clear:hover {
230
- color: var(--danger);
168
+ .agent-pane .guard.confirm {
169
+ background: #fffbeb;
170
+ color: #92400e;
231
171
  }
232
172
 
233
- /* ── Copilot ────────────────────────────────────────────────────── */
234
- .copilot-toggle {
235
- position: fixed;
236
- right: 22px;
237
- bottom: 22px;
238
- z-index: 50;
239
- padding: 12px 20px;
173
+ .agent-pane .tool-row button {
174
+ display: block;
175
+ margin-top: 8px;
176
+ padding: 5px 12px;
240
177
  border: none;
241
- border-radius: 999px;
242
- background: linear-gradient(90deg, #7c3aed, #06b6d4);
178
+ border-radius: 8px;
179
+ background: var(--ink);
243
180
  color: #fff;
244
- font-weight: 700;
181
+ font-size: 12px;
245
182
  cursor: pointer;
246
- box-shadow: 0 10px 30px -8px rgba(124, 58, 237, 0.6);
247
183
  }
248
184
 
249
- .copilot-panel {
250
- position: fixed;
251
- right: 22px;
252
- bottom: 78px;
253
- z-index: 50;
254
- display: flex;
255
- flex-direction: column;
256
- width: 340px;
257
- max-height: 480px;
258
- border: 1px solid var(--border);
259
- border-radius: 16px;
260
- background: var(--card);
261
- box-shadow: 0 24px 60px -12px rgba(0, 0, 0, 0.5);
262
- overflow: hidden;
185
+ .agent-pane .resource-block {
186
+ margin-top: 14px;
263
187
  }
264
188
 
265
- .copilot-panel .chat {
266
- flex: 1;
189
+ .agent-pane .resource {
267
190
  margin: 0;
268
- padding: 14px;
269
- list-style: none;
270
- overflow-y: auto;
271
- min-height: 100px;
191
+ padding: 12px;
192
+ border-radius: 10px;
193
+ max-height: 220px;
194
+ overflow: auto;
195
+ background: #16143a;
196
+ color: #a5f3fc;
197
+ font-size: 12px;
198
+ font-family: var(--mono);
272
199
  }
273
200
 
274
- .copilot-panel .chat li {
275
- margin-bottom: 8px;
276
- padding: 9px 13px;
201
+ /* ── Proposal (guard: confirm) ──────────────────────────────────── */
202
+ .proposal-card {
203
+ margin-top: 12px;
204
+ padding: 14px;
205
+ border: 2px solid #dc2626;
277
206
  border-radius: 12px;
207
+ background: #fef2f2;
208
+ color: #991b1b;
209
+ font-size: 13px;
210
+ animation: proposal-pulse 1.2s ease-in-out 2;
211
+ }
212
+
213
+ .proposal-card p {
214
+ margin: 0 0 6px;
215
+ line-height: 1.5;
216
+ }
217
+
218
+ .proposal-card .proposal-title {
219
+ font-weight: 800;
278
220
  font-size: 13.5px;
279
- max-width: 88%;
221
+ color: #b91c1c;
280
222
  }
281
223
 
282
- .copilot-panel .chat li.user {
283
- margin-left: auto;
284
- background: #6d28d9;
285
- color: #fff;
224
+ .proposal-card .proposal-why {
225
+ color: #b45309;
286
226
  }
287
227
 
288
- .copilot-panel .chat li.assistant {
289
- background: var(--card-2);
228
+ .proposal-card code {
229
+ background: #fee2e2;
230
+ color: #991b1b;
231
+ font-family: var(--mono);
290
232
  }
291
233
 
292
- .copilot-panel form {
234
+ .proposal-actions {
293
235
  display: flex;
294
236
  gap: 8px;
295
- padding: 10px;
296
- border-top: 1px solid var(--border);
297
- }
298
-
299
- .copilot-panel input {
300
- flex: 1;
301
- padding: 9px 12px;
302
- border: 1px solid var(--border);
303
- border-radius: 9px;
304
- background: var(--bg);
305
- color: var(--text);
306
- font-size: 13.5px;
307
- outline: none;
237
+ margin-top: 10px;
308
238
  }
309
239
 
310
- .copilot-panel button {
240
+ .proposal-actions .approve {
241
+ padding: 8px 18px;
311
242
  border: none;
312
- border-radius: 9px;
313
- padding: 9px 14px;
314
- background: var(--violet);
243
+ border-radius: 8px;
244
+ background: #dc2626;
315
245
  color: #fff;
316
- font-weight: 600;
246
+ font-weight: 700;
317
247
  cursor: pointer;
318
248
  }
319
249
 
320
- .proposal {
321
- margin: 0 14px 10px;
322
- padding: 10px 12px;
323
- border: 1px solid #fbbf24;
324
- border-radius: 10px;
325
- background: rgba(251, 191, 36, 0.1);
326
- font-size: 13px;
250
+ .proposal-actions .reject {
251
+ padding: 8px 18px;
252
+ border: 1px solid #dc2626;
253
+ border-radius: 8px;
254
+ background: #fff;
255
+ color: #b91c1c;
256
+ font-weight: 700;
257
+ cursor: pointer;
327
258
  }
328
259
 
329
- .proposal button {
330
- margin-right: 6px;
331
- margin-top: 6px;
260
+ @keyframes proposal-pulse {
261
+ 50% {
262
+ box-shadow: 0 0 0 5px rgba(220, 38, 38, 0.25);
263
+ }
264
+ }
265
+
266
+ @media (max-width: 800px) {
267
+ .split {
268
+ grid-template-columns: 1fr;
269
+ height: auto;
270
+ }
271
+
272
+ .preview {
273
+ min-height: 60vh;
274
+ border-right: none;
275
+ border-bottom: 1px solid var(--border);
276
+ }
332
277
  }
@@ -5,6 +5,7 @@
5
5
  "moduleResolution": "bundler",
6
6
  "strict": true,
7
7
  "skipLibCheck": true,
8
+ "types": ["bun"],
8
9
  "noEmit": true,
9
10
  "jsx": "react-jsx",
10
11
  "jsxImportSource": "janux"
@@ -1,9 +0,0 @@
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=...
@@ -1,8 +0,0 @@
1
- import { defineAgent } from '@janux/agent';
2
-
3
- export default defineAgent({
4
- instructions:
5
- 'You are this task app’s copilot. Use the tasks.* tools to add, toggle, filter and clear ' +
6
- 'tasks, api.tasks.taskStats for stats, and theme.toggle for appearance. clearDone needs ' +
7
- 'human approval — propose it, never insist.',
8
- });
@@ -1,103 +0,0 @@
1
- import { component, intent, schema, str, bool, enums, list } from 'janux';
2
-
3
- let wire: any[] = [];
4
-
5
- async function postAgent(messages: unknown[], path: string): Promise<any> {
6
- const response = await fetch('/_janux/agent', {
7
- method: 'POST',
8
- headers: { 'content-type': 'application/json' },
9
- body: JSON.stringify({ messages, path }),
10
- });
11
-
12
- return response.json();
13
- }
14
-
15
- async function converse(state: any, path: string): Promise<void> {
16
- let reply = await postAgent(wire, path);
17
-
18
- while (reply.type === 'ui_calls') {
19
- wire = reply.messages;
20
- for (const call of reply.calls) {
21
- const result = await (window as any).janux
22
- .call(call.name, call.input)
23
- .catch((error: unknown) => ({ error: String(error) }));
24
-
25
- if (result?.status === 'proposal') state.proposal = { id: result.id, tool: call.name };
26
- wire.push({ role: 'tool', toolCallId: call.id, content: JSON.stringify(result ?? null) });
27
- }
28
- reply = await postAgent(wire, path);
29
- }
30
- wire = reply.messages ?? wire;
31
- state.messages.push({ role: 'assistant', text: reply.type === 'setup' ? reply.message : reply.text });
32
- }
33
-
34
- export const Copilot = component({
35
- name: 'copilot',
36
- description: 'Built-in copilot operating this app through the agent bridge.',
37
-
38
- state: schema({
39
- open: bool(),
40
- busy: bool(),
41
- messages: list({ role: enums(['user', 'assistant']), text: str() }),
42
- proposal: schema({ id: str(), tool: str() }).nullable(),
43
- }),
44
-
45
- intents: {
46
- toggle: intent({ description: 'Open/close the copilot', run: ({ state }: any) => (state.open = !state.open) }),
47
- send: intent({
48
- description: 'Send a message to the copilot',
49
- input: schema({ text: str().min(1) }),
50
- run: async ({ state, input }: any) => {
51
- state.messages.push({ role: 'user', text: input.text });
52
- state.busy = true;
53
- wire.push({ role: 'user', content: input.text });
54
- await converse(state, window.location.pathname).finally(() => (state.busy = false));
55
- },
56
- }),
57
- approve: intent({
58
- guard: 'forbidden',
59
- run: async ({ state }: any) => {
60
- await (window as any).janux.approve(state.proposal.id);
61
- state.messages.push({ role: 'assistant', text: `Approved ${state.proposal.tool} ✔` });
62
- state.proposal = null;
63
- },
64
- }),
65
- reject: intent({
66
- guard: 'forbidden',
67
- run: ({ state }: any) => {
68
- (window as any).janux.reject(state.proposal?.id);
69
- state.proposal = null;
70
- },
71
- }),
72
- },
73
-
74
- view: ({ state, intents }: any) => (
75
- <aside class="copilot">
76
- <button class="copilot-toggle" on={intents.toggle}>
77
- {state.open ? '×' : '✦ Copilot'}
78
- </button>
79
- {state.open ? (
80
- <div class="copilot-panel">
81
- <ol class="chat">
82
- {state.messages.map((message: any, index: number) => (
83
- <li key={String(index)} class={message.role}>
84
- {message.text}
85
- </li>
86
- ))}
87
- </ol>
88
- {state.proposal ? (
89
- <div class="proposal">
90
- <p>Run “{state.proposal.tool}”?</p>
91
- <button on={intents.approve}>Approve</button>
92
- <button on={intents.reject}>Reject</button>
93
- </div>
94
- ) : null}
95
- <form intent={intents.send}>
96
- <input name="text" placeholder={state.busy ? 'Thinking…' : 'Try: add a task to buy milk'} />
97
- <button type="submit">Send</button>
98
- </form>
99
- </div>
100
- ) : null}
101
- </aside>
102
- ),
103
- });