clawfire 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 +182 -0
- package/dist/admin.cjs +309 -0
- package/dist/admin.cjs.map +1 -0
- package/dist/admin.d.cts +93 -0
- package/dist/admin.d.ts +93 -0
- package/dist/admin.js +274 -0
- package/dist/admin.js.map +1 -0
- package/dist/auth-DQ3cifhb.d.cts +55 -0
- package/dist/auth-DtnUPbXT.d.ts +55 -0
- package/dist/chunk-37Y2XI7X.js +75 -0
- package/dist/chunk-YGIPORYL.js +339 -0
- package/dist/cli.js +241 -0
- package/dist/client.cjs +97 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +4 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +68 -0
- package/dist/client.js.map +1 -0
- package/dist/codegen.cjs +648 -0
- package/dist/codegen.cjs.map +1 -0
- package/dist/codegen.d.cts +25 -0
- package/dist/codegen.d.ts +25 -0
- package/dist/codegen.js +617 -0
- package/dist/codegen.js.map +1 -0
- package/dist/config-QMBJRn9G.d.cts +46 -0
- package/dist/config-QMBJRn9G.d.ts +46 -0
- package/dist/dev-server-QAVWINAT.js +973 -0
- package/dist/dev.cjs +1388 -0
- package/dist/dev.cjs.map +1 -0
- package/dist/dev.d.cts +111 -0
- package/dist/dev.d.ts +111 -0
- package/dist/dev.js +1349 -0
- package/dist/dev.js.map +1 -0
- package/dist/discover-BPMAZFBD.js +9 -0
- package/dist/discover-DYNqz_ym.d.cts +28 -0
- package/dist/discover-DYNqz_ym.d.ts +28 -0
- package/dist/errors-s_mP7rs9.d.cts +33 -0
- package/dist/errors-s_mP7rs9.d.ts +33 -0
- package/dist/functions.cjs +1156 -0
- package/dist/functions.cjs.map +1 -0
- package/dist/functions.d.cts +115 -0
- package/dist/functions.d.ts +115 -0
- package/dist/functions.js +1108 -0
- package/dist/functions.js.map +1 -0
- package/dist/hosting-7WVFHAYJ.js +85 -0
- package/dist/html-PCUCJGBH.js +7 -0
- package/dist/index.cjs +349 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +312 -0
- package/dist/index.js.map +1 -0
- package/dist/playground.cjs +364 -0
- package/dist/playground.cjs.map +1 -0
- package/dist/playground.d.cts +12 -0
- package/dist/playground.d.ts +12 -0
- package/dist/playground.js +337 -0
- package/dist/playground.js.map +1 -0
- package/dist/router-BVB_I-tu.d.ts +65 -0
- package/dist/router-Cikk8Heq.d.cts +65 -0
- package/dist/schema-BJsictSV.d.cts +172 -0
- package/dist/schema-BJsictSV.d.ts +172 -0
- package/package.json +150 -0
- package/templates/CLAUDE.md +71 -0
- package/templates/app/routes/auth/login.ts +35 -0
- package/templates/app/routes/health.ts +20 -0
- package/templates/app/schemas/user.ts +26 -0
- package/templates/clawfire.config.ts +25 -0
- package/templates/functions/index.ts +43 -0
- package/templates/starter/.claude/skills/clawfire-api/SKILL.md +131 -0
- package/templates/starter/.claude/skills/clawfire-auth/SKILL.md +111 -0
- package/templates/starter/.claude/skills/clawfire-deploy/SKILL.md +95 -0
- package/templates/starter/.claude/skills/clawfire-diagnose/SKILL.md +99 -0
- package/templates/starter/.claude/skills/clawfire-model/SKILL.md +128 -0
- package/templates/starter/CLAUDE.md +227 -0
- package/templates/starter/app/routes/health.ts +20 -0
- package/templates/starter/app/routes/todos/create.ts +25 -0
- package/templates/starter/app/routes/todos/delete.ts +20 -0
- package/templates/starter/app/routes/todos/list.ts +26 -0
- package/templates/starter/app/routes/todos/update.ts +32 -0
- package/templates/starter/app/schemas/todo.ts +16 -0
- package/templates/starter/app/store.ts +56 -0
- package/templates/starter/clawfire.config.ts +25 -0
- package/templates/starter/dev.ts +12 -0
- package/templates/starter/package.json +19 -0
- package/templates/starter/public/index.html +365 -0
- package/templates/starter/tsconfig.json +17 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/playground/html.ts
|
|
4
|
+
function generatePlaygroundHtml(options) {
|
|
5
|
+
const title = options?.title || "Clawfire Playground";
|
|
6
|
+
const apiBaseUrl = options?.apiBaseUrl || "";
|
|
7
|
+
return `<!DOCTYPE html>
|
|
8
|
+
<html lang="en">
|
|
9
|
+
<head>
|
|
10
|
+
<meta charset="UTF-8">
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
12
|
+
<title>${title}</title>
|
|
13
|
+
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #0a0a0a;
|
|
16
|
+
--surface: #141414;
|
|
17
|
+
--surface2: #1e1e1e;
|
|
18
|
+
--border: #2a2a2a;
|
|
19
|
+
--text: #e5e5e5;
|
|
20
|
+
--text2: #a3a3a3;
|
|
21
|
+
--accent: #f97316;
|
|
22
|
+
--accent2: #fb923c;
|
|
23
|
+
--green: #22c55e;
|
|
24
|
+
--red: #ef4444;
|
|
25
|
+
--blue: #3b82f6;
|
|
26
|
+
--yellow: #eab308;
|
|
27
|
+
--radius: 8px;
|
|
28
|
+
--font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
29
|
+
--mono: 'JetBrains Mono', 'Fira Code', monospace;
|
|
30
|
+
}
|
|
31
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
32
|
+
body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; }
|
|
33
|
+
|
|
34
|
+
.layout { display: grid; grid-template-columns: 320px 1fr; min-height: 100vh; }
|
|
35
|
+
.sidebar { background: var(--surface); border-right: 1px solid var(--border); overflow-y: auto; }
|
|
36
|
+
.main { padding: 24px; overflow-y: auto; }
|
|
37
|
+
|
|
38
|
+
.logo { padding: 20px; border-bottom: 1px solid var(--border); }
|
|
39
|
+
.logo h1 { font-size: 20px; font-weight: 700; color: var(--accent); }
|
|
40
|
+
.logo p { font-size: 12px; color: var(--text2); margin-top: 4px; }
|
|
41
|
+
|
|
42
|
+
.auth-section { padding: 16px; border-bottom: 1px solid var(--border); }
|
|
43
|
+
.auth-section label { font-size: 12px; color: var(--text2); display: block; margin-bottom: 6px; }
|
|
44
|
+
.auth-input { width: 100%; padding: 8px 12px; background: var(--surface2); border: 1px solid var(--border);
|
|
45
|
+
border-radius: var(--radius); color: var(--text); font-family: var(--mono); font-size: 12px; }
|
|
46
|
+
.auth-status { font-size: 11px; margin-top: 6px; }
|
|
47
|
+
.auth-status.ok { color: var(--green); }
|
|
48
|
+
.auth-status.no { color: var(--text2); }
|
|
49
|
+
|
|
50
|
+
.search { padding: 12px 16px; border-bottom: 1px solid var(--border); }
|
|
51
|
+
.search input { width: 100%; padding: 8px 12px; background: var(--surface2); border: 1px solid var(--border);
|
|
52
|
+
border-radius: var(--radius); color: var(--text); font-size: 13px; }
|
|
53
|
+
|
|
54
|
+
.api-list { padding: 8px 0; }
|
|
55
|
+
.api-group { padding: 4px 0; }
|
|
56
|
+
.api-group-title { padding: 8px 16px; font-size: 11px; color: var(--text2); text-transform: uppercase;
|
|
57
|
+
letter-spacing: 0.5px; font-weight: 600; }
|
|
58
|
+
.api-item { padding: 8px 16px; cursor: pointer; transition: background 0.15s; display: flex; align-items: center;
|
|
59
|
+
gap: 8px; font-size: 13px; }
|
|
60
|
+
.api-item:hover { background: var(--surface2); }
|
|
61
|
+
.api-item.active { background: var(--surface2); border-left: 2px solid var(--accent); }
|
|
62
|
+
.api-badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 600; }
|
|
63
|
+
.badge-public { background: #22c55e20; color: var(--green); }
|
|
64
|
+
.badge-auth { background: #3b82f620; color: var(--blue); }
|
|
65
|
+
.badge-role { background: #eab30820; color: var(--yellow); }
|
|
66
|
+
.badge-reauth { background: #ef444420; color: var(--red); }
|
|
67
|
+
|
|
68
|
+
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 16px; }
|
|
69
|
+
.panel-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center;
|
|
70
|
+
justify-content: space-between; }
|
|
71
|
+
.panel-header h2 { font-size: 16px; font-weight: 600; }
|
|
72
|
+
.panel-body { padding: 16px; }
|
|
73
|
+
|
|
74
|
+
.field { margin-bottom: 12px; }
|
|
75
|
+
.field label { font-size: 12px; color: var(--text2); display: block; margin-bottom: 4px; }
|
|
76
|
+
.field-type { font-size: 11px; color: var(--text2); font-family: var(--mono); }
|
|
77
|
+
.field-required { color: var(--red); font-size: 11px; }
|
|
78
|
+
|
|
79
|
+
textarea, input[type="text"] { width: 100%; padding: 10px 14px; background: var(--surface2);
|
|
80
|
+
border: 1px solid var(--border); border-radius: var(--radius); color: var(--text);
|
|
81
|
+
font-family: var(--mono); font-size: 13px; resize: vertical; }
|
|
82
|
+
textarea { min-height: 200px; }
|
|
83
|
+
|
|
84
|
+
.btn { padding: 10px 20px; border: none; border-radius: var(--radius); font-size: 14px;
|
|
85
|
+
font-weight: 600; cursor: pointer; transition: all 0.15s; }
|
|
86
|
+
.btn-primary { background: var(--accent); color: white; }
|
|
87
|
+
.btn-primary:hover { background: var(--accent2); }
|
|
88
|
+
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
89
|
+
|
|
90
|
+
.response-section { margin-top: 16px; }
|
|
91
|
+
.status-badge { font-size: 12px; padding: 4px 8px; border-radius: 4px; font-weight: 600; }
|
|
92
|
+
.status-ok { background: #22c55e20; color: var(--green); }
|
|
93
|
+
.status-err { background: #ef444420; color: var(--red); }
|
|
94
|
+
pre { background: var(--surface2); padding: 16px; border-radius: var(--radius); overflow-x: auto;
|
|
95
|
+
font-family: var(--mono); font-size: 13px; line-height: 1.5; white-space: pre-wrap; }
|
|
96
|
+
|
|
97
|
+
.schema-info { font-size: 12px; color: var(--text2); line-height: 1.6; }
|
|
98
|
+
.schema-info code { background: var(--surface2); padding: 2px 6px; border-radius: 4px; font-family: var(--mono);
|
|
99
|
+
font-size: 11px; }
|
|
100
|
+
|
|
101
|
+
.empty-state { text-align: center; padding: 80px 40px; color: var(--text2); }
|
|
102
|
+
.empty-state h2 { font-size: 24px; margin-bottom: 8px; color: var(--text); }
|
|
103
|
+
|
|
104
|
+
.timer { font-size: 12px; color: var(--text2); font-family: var(--mono); }
|
|
105
|
+
|
|
106
|
+
@media (max-width: 768px) {
|
|
107
|
+
.layout { grid-template-columns: 1fr; }
|
|
108
|
+
.sidebar { max-height: 40vh; }
|
|
109
|
+
}
|
|
110
|
+
</style>
|
|
111
|
+
</head>
|
|
112
|
+
<body>
|
|
113
|
+
<div class="layout">
|
|
114
|
+
<div class="sidebar">
|
|
115
|
+
<div class="logo">
|
|
116
|
+
<h1>Clawfire</h1>
|
|
117
|
+
<p>API Playground</p>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="auth-section">
|
|
120
|
+
<label>Bearer Token</label>
|
|
121
|
+
<input type="text" class="auth-input" id="token-input" placeholder="Paste your ID token...">
|
|
122
|
+
<div class="auth-status no" id="auth-status">Not authenticated</div>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="search">
|
|
125
|
+
<input type="text" id="search-input" placeholder="Search APIs...">
|
|
126
|
+
</div>
|
|
127
|
+
<div class="api-list" id="api-list"></div>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="main" id="main-content">
|
|
130
|
+
<div class="empty-state">
|
|
131
|
+
<h2>Select an API</h2>
|
|
132
|
+
<p>Choose an API from the sidebar to test it.</p>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<script>
|
|
138
|
+
const BASE_URL = ${JSON.stringify(apiBaseUrl)} || window.location.origin;
|
|
139
|
+
let manifest = null;
|
|
140
|
+
let selectedApi = null;
|
|
141
|
+
|
|
142
|
+
async function loadManifest() {
|
|
143
|
+
try {
|
|
144
|
+
const res = await fetch(BASE_URL + '/api/__manifest', { method: 'POST' });
|
|
145
|
+
manifest = await res.json();
|
|
146
|
+
renderApiList(manifest.apis);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
document.getElementById('api-list').innerHTML =
|
|
149
|
+
'<div style="padding:16px;color:var(--red);font-size:13px;">Failed to load API manifest. Make sure your server is running.</div>';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderApiList(apis) {
|
|
154
|
+
const groups = {};
|
|
155
|
+
apis.forEach(api => {
|
|
156
|
+
const parts = api.path.split('/').filter(Boolean);
|
|
157
|
+
const group = parts.length > 1 ? parts[0] : 'root';
|
|
158
|
+
if (!groups[group]) groups[group] = [];
|
|
159
|
+
groups[group].push(api);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const el = document.getElementById('api-list');
|
|
163
|
+
el.innerHTML = Object.entries(groups).map(([group, items]) =>
|
|
164
|
+
'<div class="api-group">' +
|
|
165
|
+
'<div class="api-group-title">' + group + '</div>' +
|
|
166
|
+
items.map(api => {
|
|
167
|
+
const auth = api.meta.auth || 'public';
|
|
168
|
+
const badgeClass = 'badge-' + auth;
|
|
169
|
+
return '<div class="api-item" onclick="selectApi(\\'' + api.path + '\\')">' +
|
|
170
|
+
'<span class="api-badge ' + badgeClass + '">' + auth.toUpperCase() + '</span>' +
|
|
171
|
+
'<span>' + api.path + '</span>' +
|
|
172
|
+
'</div>';
|
|
173
|
+
}).join('') +
|
|
174
|
+
'</div>'
|
|
175
|
+
).join('');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function selectApi(path) {
|
|
179
|
+
selectedApi = manifest.apis.find(a => a.path === path);
|
|
180
|
+
if (!selectedApi) return;
|
|
181
|
+
|
|
182
|
+
document.querySelectorAll('.api-item').forEach(el => el.classList.remove('active'));
|
|
183
|
+
event.currentTarget?.classList.add('active');
|
|
184
|
+
|
|
185
|
+
const main = document.getElementById('main-content');
|
|
186
|
+
const exampleInput = selectedApi.meta.exampleInput
|
|
187
|
+
? JSON.stringify(selectedApi.meta.exampleInput, null, 2)
|
|
188
|
+
: generateExampleFromSchema(selectedApi.inputSchema);
|
|
189
|
+
|
|
190
|
+
main.innerHTML =
|
|
191
|
+
'<div class="panel">' +
|
|
192
|
+
'<div class="panel-header">' +
|
|
193
|
+
'<h2>POST ' + selectedApi.path + '</h2>' +
|
|
194
|
+
'<span class="api-badge badge-' + (selectedApi.meta.auth || 'public') + '">' +
|
|
195
|
+
(selectedApi.meta.auth || 'public').toUpperCase() + '</span>' +
|
|
196
|
+
'</div>' +
|
|
197
|
+
'<div class="panel-body">' +
|
|
198
|
+
'<p style="color:var(--text2);margin-bottom:16px;">' + (selectedApi.meta.description || '') + '</p>' +
|
|
199
|
+
(selectedApi.meta.tags ? '<div style="margin-bottom:12px;">' + selectedApi.meta.tags.map(t =>
|
|
200
|
+
'<span style="background:var(--surface2);padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px;">' + t + '</span>'
|
|
201
|
+
).join('') + '</div>' : '') +
|
|
202
|
+
'<div class="schema-info" style="margin-bottom:16px;">' +
|
|
203
|
+
'<strong>Input Schema:</strong><br>' + renderSchemaInfo(selectedApi.inputSchema) +
|
|
204
|
+
'</div>' +
|
|
205
|
+
'<div class="schema-info" style="margin-bottom:16px;">' +
|
|
206
|
+
'<strong>Output Schema:</strong><br>' + renderSchemaInfo(selectedApi.outputSchema) +
|
|
207
|
+
'</div>' +
|
|
208
|
+
'</div>' +
|
|
209
|
+
'</div>' +
|
|
210
|
+
'<div class="panel">' +
|
|
211
|
+
'<div class="panel-header"><h2>Request</h2></div>' +
|
|
212
|
+
'<div class="panel-body">' +
|
|
213
|
+
'<textarea id="req-body" placeholder="Request JSON body">' + exampleInput + '</textarea>' +
|
|
214
|
+
'<div style="margin-top:12px;display:flex;align-items:center;gap:12px;">' +
|
|
215
|
+
'<button class="btn btn-primary" onclick="sendRequest()">Send Request</button>' +
|
|
216
|
+
'<span class="timer" id="timer"></span>' +
|
|
217
|
+
'</div>' +
|
|
218
|
+
'</div>' +
|
|
219
|
+
'</div>' +
|
|
220
|
+
'<div class="response-section" id="response-section"></div>';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function renderSchemaInfo(schema) {
|
|
224
|
+
if (!schema || !schema.properties) return '<code>void</code>';
|
|
225
|
+
return Object.entries(schema.properties).map(([key, prop]) => {
|
|
226
|
+
const required = schema.required?.includes(key);
|
|
227
|
+
const type = prop.type || 'unknown';
|
|
228
|
+
const enumVals = prop.enum ? ' (' + prop.enum.join(', ') + ')' : '';
|
|
229
|
+
return '<code>' + key + '</code>: <span class="field-type">' + type + enumVals + '</span>' +
|
|
230
|
+
(required ? ' <span class="field-required">required</span>' : ' <span style="color:var(--text2);font-size:11px;">optional</span>');
|
|
231
|
+
}).join('<br>');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function generateExampleFromSchema(schema) {
|
|
235
|
+
if (!schema || !schema.properties) return '{}';
|
|
236
|
+
const obj = {};
|
|
237
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
238
|
+
if (prop.enum) { obj[key] = prop.enum[0]; continue; }
|
|
239
|
+
switch (prop.type) {
|
|
240
|
+
case 'string': obj[key] = prop.format === 'email' ? 'user@example.com' : 'string'; break;
|
|
241
|
+
case 'number': obj[key] = 0; break;
|
|
242
|
+
case 'boolean': obj[key] = false; break;
|
|
243
|
+
case 'array': obj[key] = []; break;
|
|
244
|
+
case 'object': obj[key] = {}; break;
|
|
245
|
+
default: obj[key] = null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return JSON.stringify(obj, null, 2);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function sendRequest() {
|
|
252
|
+
if (!selectedApi) return;
|
|
253
|
+
const body = document.getElementById('req-body').value;
|
|
254
|
+
const token = document.getElementById('token-input').value;
|
|
255
|
+
const timer = document.getElementById('timer');
|
|
256
|
+
const section = document.getElementById('response-section');
|
|
257
|
+
|
|
258
|
+
let parsed;
|
|
259
|
+
try { parsed = JSON.parse(body); } catch {
|
|
260
|
+
section.innerHTML = '<div class="panel"><div class="panel-body"><pre style="color:var(--red)">Invalid JSON</pre></div></div>';
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const start = performance.now();
|
|
265
|
+
timer.textContent = 'Sending...';
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
269
|
+
if (token) headers['Authorization'] = 'Bearer ' + token;
|
|
270
|
+
|
|
271
|
+
const res = await fetch(BASE_URL + '/api' + selectedApi.path, {
|
|
272
|
+
method: 'POST', headers, body: JSON.stringify(parsed)
|
|
273
|
+
});
|
|
274
|
+
const elapsed = Math.round(performance.now() - start);
|
|
275
|
+
timer.textContent = elapsed + 'ms';
|
|
276
|
+
|
|
277
|
+
const json = await res.json();
|
|
278
|
+
const isOk = res.ok;
|
|
279
|
+
|
|
280
|
+
section.innerHTML =
|
|
281
|
+
'<div class="panel">' +
|
|
282
|
+
'<div class="panel-header">' +
|
|
283
|
+
'<h2>Response</h2>' +
|
|
284
|
+
'<span class="status-badge ' + (isOk ? 'status-ok' : 'status-err') + '">' +
|
|
285
|
+
res.status + ' ' + res.statusText + '</span>' +
|
|
286
|
+
'</div>' +
|
|
287
|
+
'<div class="panel-body"><pre>' + syntaxHighlight(JSON.stringify(json, null, 2)) + '</pre></div>' +
|
|
288
|
+
'</div>';
|
|
289
|
+
} catch (e) {
|
|
290
|
+
const elapsed = Math.round(performance.now() - start);
|
|
291
|
+
timer.textContent = elapsed + 'ms';
|
|
292
|
+
section.innerHTML =
|
|
293
|
+
'<div class="panel"><div class="panel-body"><pre style="color:var(--red)">Network error: ' + e.message + '</pre></div></div>';
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function syntaxHighlight(json) {
|
|
298
|
+
return json.replace(/("(\\\\u[a-fA-F0-9]{4}|\\\\[^u]|[^\\\\"])*"(\\s*:)?)|\\b(true|false|null)\\b|-?\\d+(\\.\\d+)?([eE][+-]?\\d+)?/g,
|
|
299
|
+
function(match) {
|
|
300
|
+
let cls = 'color:#eab308';
|
|
301
|
+
if (/^"/.test(match)) {
|
|
302
|
+
if (/:$/.test(match)) cls = 'color:#3b82f6';
|
|
303
|
+
else cls = 'color:#22c55e';
|
|
304
|
+
} else if (/true|false/.test(match)) cls = 'color:#f97316';
|
|
305
|
+
else if (/null/.test(match)) cls = 'color:#ef4444';
|
|
306
|
+
return '<span style="' + cls + '">' + match + '</span>';
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Search
|
|
312
|
+
document.getElementById('search-input')?.addEventListener('input', (e) => {
|
|
313
|
+
if (!manifest) return;
|
|
314
|
+
const q = e.target.value.toLowerCase();
|
|
315
|
+
const filtered = manifest.apis.filter(a => a.path.toLowerCase().includes(q) || a.meta.description?.toLowerCase().includes(q));
|
|
316
|
+
renderApiList(filtered);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Token status
|
|
320
|
+
document.getElementById('token-input')?.addEventListener('input', (e) => {
|
|
321
|
+
const el = document.getElementById('auth-status');
|
|
322
|
+
if (e.target.value) {
|
|
323
|
+
el.textContent = 'Token set';
|
|
324
|
+
el.className = 'auth-status ok';
|
|
325
|
+
} else {
|
|
326
|
+
el.textContent = 'Not authenticated';
|
|
327
|
+
el.className = 'auth-status no';
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
loadManifest();
|
|
332
|
+
</script>
|
|
333
|
+
</body>
|
|
334
|
+
</html>`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export {
|
|
338
|
+
generatePlaygroundHtml
|
|
339
|
+
};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { resolve, join, dirname } from "path";
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
statSync,
|
|
11
|
+
copyFileSync
|
|
12
|
+
} from "fs";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var args = process.argv.slice(2);
|
|
15
|
+
var command = args[0];
|
|
16
|
+
function printHelp() {
|
|
17
|
+
console.log(`
|
|
18
|
+
Clawfire \u2014 AI-First Firebase App Framework
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
clawfire init Initialize a new Clawfire project
|
|
22
|
+
clawfire dev Start dev server with hot reload
|
|
23
|
+
clawfire codegen Generate API client from routes
|
|
24
|
+
clawfire manifest Generate manifest.json
|
|
25
|
+
clawfire playground Generate playground HTML
|
|
26
|
+
clawfire rules Generate Firestore security rules
|
|
27
|
+
clawfire help Show this help
|
|
28
|
+
|
|
29
|
+
Most tasks are done through AI Skills, not CLI.
|
|
30
|
+
Run Claude Code and use /clawfire-* commands instead.
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
async function main() {
|
|
34
|
+
switch (command) {
|
|
35
|
+
case "init":
|
|
36
|
+
await initProject();
|
|
37
|
+
break;
|
|
38
|
+
case "dev":
|
|
39
|
+
await runDevServer();
|
|
40
|
+
break;
|
|
41
|
+
case "codegen":
|
|
42
|
+
await runCodegen();
|
|
43
|
+
break;
|
|
44
|
+
case "manifest":
|
|
45
|
+
await runManifest();
|
|
46
|
+
break;
|
|
47
|
+
case "playground":
|
|
48
|
+
await runPlayground();
|
|
49
|
+
break;
|
|
50
|
+
case "rules":
|
|
51
|
+
await runRules();
|
|
52
|
+
break;
|
|
53
|
+
case "help":
|
|
54
|
+
case "--help":
|
|
55
|
+
case "-h":
|
|
56
|
+
default:
|
|
57
|
+
printHelp();
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function findTemplatesDir() {
|
|
62
|
+
const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
63
|
+
const candidates = [
|
|
64
|
+
// From dist/cli.js → ../templates/starter
|
|
65
|
+
resolve(thisDir, "..", "templates", "starter"),
|
|
66
|
+
// From src/cli.ts → ../templates/starter
|
|
67
|
+
resolve(thisDir, "..", "templates", "starter"),
|
|
68
|
+
// From dist/ (nested) → ../../templates/starter
|
|
69
|
+
resolve(thisDir, "..", "..", "templates", "starter")
|
|
70
|
+
];
|
|
71
|
+
for (const candidate of candidates) {
|
|
72
|
+
if (existsSync(candidate) && existsSync(join(candidate, "dev.ts"))) {
|
|
73
|
+
return candidate;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function copyDirRecursive(src, dest) {
|
|
79
|
+
const created = [];
|
|
80
|
+
if (!existsSync(dest)) {
|
|
81
|
+
mkdirSync(dest, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
const entries = readdirSync(src);
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
const srcPath = join(src, entry);
|
|
86
|
+
const destPath = join(dest, entry);
|
|
87
|
+
const stat = statSync(srcPath);
|
|
88
|
+
if (stat.isDirectory()) {
|
|
89
|
+
created.push(...copyDirRecursive(srcPath, destPath));
|
|
90
|
+
} else {
|
|
91
|
+
if (!existsSync(destPath)) {
|
|
92
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
93
|
+
copyFileSync(srcPath, destPath);
|
|
94
|
+
created.push(destPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return created;
|
|
99
|
+
}
|
|
100
|
+
async function initProject() {
|
|
101
|
+
const projectDir = process.cwd();
|
|
102
|
+
console.log("Initializing Clawfire project...\n");
|
|
103
|
+
const templatesDir = findTemplatesDir();
|
|
104
|
+
if (templatesDir) {
|
|
105
|
+
const copied = copyDirRecursive(templatesDir, projectDir);
|
|
106
|
+
for (const file of copied) {
|
|
107
|
+
const rel = file.replace(projectDir + "/", "");
|
|
108
|
+
console.log(` Created ${rel}`);
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
console.log(" (Templates not found, creating minimal structure)");
|
|
112
|
+
const dirs = [
|
|
113
|
+
"app/routes",
|
|
114
|
+
"app/schemas",
|
|
115
|
+
"generated",
|
|
116
|
+
"public",
|
|
117
|
+
"functions"
|
|
118
|
+
];
|
|
119
|
+
for (const dir of dirs) {
|
|
120
|
+
const fullPath = resolve(projectDir, dir);
|
|
121
|
+
if (!existsSync(fullPath)) {
|
|
122
|
+
mkdirSync(fullPath, { recursive: true });
|
|
123
|
+
console.log(` Created ${dir}/`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const dir of ["generated", "functions", ".claude/skills"]) {
|
|
128
|
+
const fullPath = resolve(projectDir, dir);
|
|
129
|
+
if (!existsSync(fullPath)) {
|
|
130
|
+
mkdirSync(fullPath, { recursive: true });
|
|
131
|
+
console.log(` Created ${dir}/`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!existsSync(resolve(projectDir, "firebase.json"))) {
|
|
135
|
+
const { generateFirebaseJson } = await import("./hosting-7WVFHAYJ.js");
|
|
136
|
+
writeFileSync(
|
|
137
|
+
resolve(projectDir, "firebase.json"),
|
|
138
|
+
JSON.stringify(generateFirebaseJson(), null, 2)
|
|
139
|
+
);
|
|
140
|
+
console.log(" Created firebase.json");
|
|
141
|
+
}
|
|
142
|
+
if (!existsSync(resolve(projectDir, "firestore.rules"))) {
|
|
143
|
+
const { generateDefaultRules } = await import("./hosting-7WVFHAYJ.js");
|
|
144
|
+
writeFileSync(resolve(projectDir, "firestore.rules"), generateDefaultRules());
|
|
145
|
+
console.log(" Created firestore.rules");
|
|
146
|
+
}
|
|
147
|
+
if (!existsSync(resolve(projectDir, "firestore.indexes.json"))) {
|
|
148
|
+
writeFileSync(
|
|
149
|
+
resolve(projectDir, "firestore.indexes.json"),
|
|
150
|
+
JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2)
|
|
151
|
+
);
|
|
152
|
+
console.log(" Created firestore.indexes.json");
|
|
153
|
+
}
|
|
154
|
+
const functionsIndex = resolve(projectDir, "functions/index.ts");
|
|
155
|
+
if (!existsSync(functionsIndex)) {
|
|
156
|
+
writeFileSync(
|
|
157
|
+
functionsIndex,
|
|
158
|
+
`/**
|
|
159
|
+
* Clawfire Firebase Functions Entry Point
|
|
160
|
+
*/
|
|
161
|
+
import * as admin from "firebase-admin";
|
|
162
|
+
import * as functions from "firebase-functions";
|
|
163
|
+
import { createRouter, createAdminDB, createSecurityMiddleware } from "clawfire/functions";
|
|
164
|
+
|
|
165
|
+
admin.initializeApp();
|
|
166
|
+
|
|
167
|
+
const db = createAdminDB(admin.firestore());
|
|
168
|
+
const router = createRouter({
|
|
169
|
+
auth: admin.auth(),
|
|
170
|
+
cors: [],
|
|
171
|
+
middleware: createSecurityMiddleware(),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
export const api = functions.https.onRequest((req, res) => {
|
|
175
|
+
router.handleRequest(req as any, res as any);
|
|
176
|
+
});
|
|
177
|
+
`
|
|
178
|
+
);
|
|
179
|
+
console.log(" Created functions/index.ts");
|
|
180
|
+
}
|
|
181
|
+
console.log("\n \x1B[32m\u2713\x1B[0m Clawfire project initialized!\n");
|
|
182
|
+
console.log(" Next steps:");
|
|
183
|
+
console.log(" \x1B[36m1.\x1B[0m npm install");
|
|
184
|
+
console.log(" \x1B[36m2.\x1B[0m npm run dev");
|
|
185
|
+
console.log(" \x1B[36m3.\x1B[0m Open \x1B[1mhttp://localhost:3456\x1B[0m in your browser");
|
|
186
|
+
console.log("");
|
|
187
|
+
console.log(" Your Todo app is ready! Try adding, completing, and deleting todos.");
|
|
188
|
+
console.log(" Edit files in \x1B[1mapp/routes/\x1B[0m and see changes instantly.\n");
|
|
189
|
+
}
|
|
190
|
+
async function runDevServer() {
|
|
191
|
+
const projectDir = process.cwd();
|
|
192
|
+
const portArg = args.find((a) => a.startsWith("--port="));
|
|
193
|
+
const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3456;
|
|
194
|
+
const noHotReload = args.includes("--no-hot-reload");
|
|
195
|
+
const { startDevServer } = await import("./dev-server-QAVWINAT.js");
|
|
196
|
+
await startDevServer({
|
|
197
|
+
projectDir,
|
|
198
|
+
port,
|
|
199
|
+
hotReload: !noHotReload
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
async function runCodegen() {
|
|
203
|
+
const projectDir = process.cwd();
|
|
204
|
+
const routesDir = resolve(projectDir, "app/routes");
|
|
205
|
+
if (!existsSync(routesDir)) {
|
|
206
|
+
console.error("Error: app/routes/ directory not found. Run 'clawfire init' first.");
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
const { discoverRoutes, generateRouteImports } = await import("./discover-BPMAZFBD.js");
|
|
210
|
+
const routes = discoverRoutes(routesDir);
|
|
211
|
+
console.log(`Found ${routes.length} routes:`);
|
|
212
|
+
routes.forEach((r) => console.log(` ${r.apiPath} \u2192 ${r.filePath}`));
|
|
213
|
+
const generatedDir = resolve(projectDir, "generated");
|
|
214
|
+
if (!existsSync(generatedDir)) mkdirSync(generatedDir, { recursive: true });
|
|
215
|
+
const imports = generateRouteImports(routes, routesDir);
|
|
216
|
+
writeFileSync(resolve(generatedDir, "routes.ts"), imports);
|
|
217
|
+
console.log("\nGenerated: generated/routes.ts");
|
|
218
|
+
console.log("\nNote: api-client.ts generation requires running the server to extract manifest.");
|
|
219
|
+
console.log("Use /clawfire-api skill for full client generation.");
|
|
220
|
+
}
|
|
221
|
+
async function runManifest() {
|
|
222
|
+
console.log("Manifest generation requires a running server.");
|
|
223
|
+
console.log("Use /clawfire-api skill or access /api/__manifest endpoint.");
|
|
224
|
+
}
|
|
225
|
+
async function runPlayground() {
|
|
226
|
+
const projectDir = process.cwd();
|
|
227
|
+
const publicDir = resolve(projectDir, "public");
|
|
228
|
+
if (!existsSync(publicDir)) mkdirSync(publicDir, { recursive: true });
|
|
229
|
+
const { generatePlaygroundHtml } = await import("./html-PCUCJGBH.js");
|
|
230
|
+
const html = generatePlaygroundHtml();
|
|
231
|
+
writeFileSync(resolve(publicDir, "playground.html"), html);
|
|
232
|
+
console.log("Generated: public/playground.html");
|
|
233
|
+
}
|
|
234
|
+
async function runRules() {
|
|
235
|
+
console.log("Rules generation requires model definitions.");
|
|
236
|
+
console.log("Use /clawfire-model skill to generate rules from your models.");
|
|
237
|
+
}
|
|
238
|
+
main().catch((err) => {
|
|
239
|
+
console.error("Error:", err.message);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
});
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/client.ts
|
|
21
|
+
var client_exports = {};
|
|
22
|
+
__export(client_exports, {
|
|
23
|
+
ClawfireError: () => ClawfireError,
|
|
24
|
+
createClientAuth: () => createClientAuth,
|
|
25
|
+
z: () => import_zod.z
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(client_exports);
|
|
28
|
+
|
|
29
|
+
// src/core/errors.ts
|
|
30
|
+
var HTTP_STATUS_MAP = {
|
|
31
|
+
VALIDATION_ERROR: 400,
|
|
32
|
+
UNAUTHORIZED: 401,
|
|
33
|
+
FORBIDDEN: 403,
|
|
34
|
+
NOT_FOUND: 404,
|
|
35
|
+
CONFLICT: 409,
|
|
36
|
+
RATE_LIMITED: 429,
|
|
37
|
+
REAUTH_REQUIRED: 401,
|
|
38
|
+
INTERNAL_ERROR: 500,
|
|
39
|
+
SERVICE_UNAVAILABLE: 503
|
|
40
|
+
};
|
|
41
|
+
var ClawfireError = class extends Error {
|
|
42
|
+
code;
|
|
43
|
+
statusCode;
|
|
44
|
+
details;
|
|
45
|
+
constructor(code, message, details) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "ClawfireError";
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.statusCode = HTTP_STATUS_MAP[code];
|
|
50
|
+
this.details = details;
|
|
51
|
+
}
|
|
52
|
+
toJSON() {
|
|
53
|
+
return {
|
|
54
|
+
error: {
|
|
55
|
+
code: this.code,
|
|
56
|
+
message: this.message,
|
|
57
|
+
...this.details ? { details: this.details } : {}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/firebase/auth.ts
|
|
64
|
+
function createClientAuth(firebaseAuth) {
|
|
65
|
+
return {
|
|
66
|
+
/** 현재 사용자 조회 */
|
|
67
|
+
getCurrentUser() {
|
|
68
|
+
return firebaseAuth.currentUser;
|
|
69
|
+
},
|
|
70
|
+
/** ID 토큰 취득 */
|
|
71
|
+
async getIdToken(forceRefresh = false) {
|
|
72
|
+
const user = firebaseAuth.currentUser;
|
|
73
|
+
if (!user) return null;
|
|
74
|
+
return user.getIdToken(forceRefresh);
|
|
75
|
+
},
|
|
76
|
+
/** 인증 상태 변경 리스너 */
|
|
77
|
+
onAuthStateChanged(callback) {
|
|
78
|
+
return firebaseAuth.onAuthStateChanged(callback);
|
|
79
|
+
},
|
|
80
|
+
/** 로그아웃 */
|
|
81
|
+
async signOut() {
|
|
82
|
+
await firebaseAuth.signOut();
|
|
83
|
+
},
|
|
84
|
+
/** 원시 auth 접근 */
|
|
85
|
+
raw: firebaseAuth
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/client.ts
|
|
90
|
+
var import_zod = require("zod");
|
|
91
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
92
|
+
0 && (module.exports = {
|
|
93
|
+
ClawfireError,
|
|
94
|
+
createClientAuth,
|
|
95
|
+
z
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=client.cjs.map
|