codebay 0.2.0 → 0.3.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/.mochi/manifest.json +144 -140
- package/.mochi/svelte-client/HydratableIsland-wrq79y0w.js +1 -0
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-faqf4hmc.js +2 -0
- package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-3w16y9jh.js +13 -0
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-5g8meb6a.js +1 -0
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-jsw4edxb.js +23 -0
- package/.mochi/svelte-client/chunk-04qv4w6w.js +2 -0
- package/.mochi/svelte-client/{chunk-x843txjc.js → chunk-0jtmkpx4.js} +2 -2
- package/.mochi/svelte-client/chunk-597d17x0.js +1 -0
- package/.mochi/svelte-client/chunk-5p31aqh0.js +2 -0
- package/.mochi/svelte-client/chunk-gjjhya9m.js +2 -0
- package/.mochi/svelte-client/{chunk-kgn1b2t1.js → chunk-qxwt25hn.js} +1 -1
- package/.mochi/svelte-compile/{App-8k1gyxdc.server.js → App-f0q5n4rk.server.js} +3 -2
- package/.mochi/svelte-compile/{Avatars-f7c6rp96.server.js → Avatars-p8gfef7x.server.js} +35 -54
- package/.mochi/svelte-compile/{Instance-yvps12zg.server.js → Instance-atjwsczx.server.js} +3 -2
- package/.mochi/svelte-compile/{Settings-jwg3httb.server.js → Settings-q88xw6h8.server.js} +9 -5
- package/.mochi/svelte-compile/{chunk-dqgstte4.js → chunk-btmcxhqy.js} +7 -1
- package/.mochi/svelte-css/SettingsView-1nfdwnm16cpp2.css +1 -0
- package/README.md +1 -0
- package/bin/codebay.ts +42 -1
- package/package.json +1 -1
- package/src/avatars/name-prompts.ts +30 -0
- package/src/components/AvatarEditor.svelte +3 -27
- package/src/components/AvatarsView.svelte +3 -1
- package/src/components/InstanceView.svelte +4 -2
- package/src/components/PortsBox.svelte +3 -2
- package/src/components/SettingsView.svelte +11 -1
- package/src/lib/config.server.ts +18 -0
- package/src/lib/devcontainer.server.ts +10 -2
- package/src/lib/links.ts +9 -0
- package/src/pages/Avatars.svelte +3 -1
- package/src/pages/Settings.svelte +4 -1
- package/src/routes.ts +9 -5
- package/.mochi/svelte-client/HydratableIsland-ps09gjy2.js +0 -1
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-62w5zwww.js +0 -2
- package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-jmm501zf.js +0 -13
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-ewk4n2c0.js +0 -1
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-e7e89md0.js +0 -23
- package/.mochi/svelte-client/chunk-jx5cekhh.js +0 -2
- package/.mochi/svelte-client/chunk-k3my1snp.js +0 -2
- package/.mochi/svelte-client/chunk-ry7q5zk5.js +0 -1
- package/.mochi/svelte-client/chunk-zjcg8kw5.js +0 -2
- package/.mochi/svelte-css/SettingsView-3bbkzsvnrth3v.css +0 -1
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
} from "./chunk-sxqys3nw.js";
|
|
14
14
|
import {
|
|
15
15
|
HydratableBoundary,
|
|
16
|
-
Icon
|
|
16
|
+
Icon,
|
|
17
|
+
emitIslandProps
|
|
17
18
|
} from "./chunk-v6796ga4.js";
|
|
18
19
|
import"./chunk-we0ggtws.js";
|
|
19
20
|
|
|
@@ -146,31 +147,7 @@ function toIssueUrl(art) {
|
|
|
146
147
|
// src/components/AvatarEditor.svelte
|
|
147
148
|
function AvatarEditor($$renderer, $$props) {
|
|
148
149
|
$$renderer.component(($$renderer2) => {
|
|
149
|
-
|
|
150
|
-
"dragon",
|
|
151
|
-
"unicorn",
|
|
152
|
-
"cactus",
|
|
153
|
-
"comet",
|
|
154
|
-
"pretzel",
|
|
155
|
-
"volcano",
|
|
156
|
-
"narwhal",
|
|
157
|
-
"waffle",
|
|
158
|
-
"satellite",
|
|
159
|
-
"pineapple",
|
|
160
|
-
"kraken",
|
|
161
|
-
"yeti",
|
|
162
|
-
"moose",
|
|
163
|
-
"lighthouse",
|
|
164
|
-
"toaster",
|
|
165
|
-
"hedgehog",
|
|
166
|
-
"walrus",
|
|
167
|
-
"pumpkin",
|
|
168
|
-
"jellyfish",
|
|
169
|
-
"tornado",
|
|
170
|
-
"igloo",
|
|
171
|
-
"boomerang"
|
|
172
|
-
];
|
|
173
|
-
const namePlaceholder = NAME_PROMPTS[Math.floor(Math.random() * NAME_PROMPTS.length)];
|
|
150
|
+
let { namePlaceholder } = $$props;
|
|
174
151
|
let cells = Array(ROWS * COLS).fill(OFF);
|
|
175
152
|
let avatarName = "";
|
|
176
153
|
const slug = $5.derived(() => normalizeName(avatarName));
|
|
@@ -223,7 +200,7 @@ function AvatarEditor($$renderer, $$props) {
|
|
|
223
200
|
dist_default.error(`Copy failed: ${err.message}`);
|
|
224
201
|
}
|
|
225
202
|
}
|
|
226
|
-
$$renderer2.push(`<section class="editor svelte-qu6vp4"><div class="head svelte-qu6vp4"><h2 class="svelte-qu6vp4">Draw your avatar</h2></div> <div class="body svelte-qu6vp4"><div class="grid-frame svelte-qu6vp4"><!--[-->`);
|
|
203
|
+
$$renderer2.push(`<section class="editor svelte-qu6vp4"><div class="head svelte-qu6vp4"><h2 class="svelte-qu6vp4">Draw your own avatar</h2></div> <div class="body svelte-qu6vp4"><div class="grid-frame svelte-qu6vp4"><!--[-->`);
|
|
227
204
|
const each_array = $5.ensure_array_like(RING_CELLS);
|
|
228
205
|
for (let i = 0, $$length = each_array.length;i < $$length; i++) {
|
|
229
206
|
let { r, c } = each_array[i];
|
|
@@ -291,6 +268,7 @@ function AvatarEditor($$renderer, $$props) {
|
|
|
291
268
|
// src/components/AvatarsView.svelte
|
|
292
269
|
function AvatarsView($$renderer, $$props) {
|
|
293
270
|
$$renderer.component(($$renderer2) => {
|
|
271
|
+
let { namePlaceholder } = $$props;
|
|
294
272
|
$$renderer2.push(`<div class="page svelte-fkq7sr">`);
|
|
295
273
|
AppBar($$renderer2, {
|
|
296
274
|
children: ($$renderer3) => {
|
|
@@ -299,7 +277,7 @@ function AvatarsView($$renderer, $$props) {
|
|
|
299
277
|
$$slots: { default: true }
|
|
300
278
|
});
|
|
301
279
|
$$renderer2.push(`<!----> <main class="content svelte-fkq7sr">`);
|
|
302
|
-
AvatarEditor($$renderer2, {});
|
|
280
|
+
AvatarEditor($$renderer2, { namePlaceholder });
|
|
303
281
|
$$renderer2.push(`<!----> <section class="catalog svelte-fkq7sr"><header class="svelte-fkq7sr"><h2 class="svelte-fkq7sr">The set</h2> <p class="svelte-fkq7sr">${$6.escape(avatars.length)} hand-crafted 8\xD78 sprites</p></header> <ul class="svelte-fkq7sr"><!--[-->`);
|
|
304
282
|
const each_array = $6.ensure_array_like(avatars);
|
|
305
283
|
for (let $$index = 0, $$length = each_array.length;$$index < $$length; $$index++) {
|
|
@@ -319,32 +297,35 @@ function AvatarsView($$renderer, $$props) {
|
|
|
319
297
|
}
|
|
320
298
|
|
|
321
299
|
// src/pages/Avatars.svelte
|
|
322
|
-
function Avatars($$renderer) {
|
|
323
|
-
|
|
324
|
-
let
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
$$renderer2.push(
|
|
330
|
-
{
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
$$
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
300
|
+
function Avatars($$renderer, $$props) {
|
|
301
|
+
$$renderer.component(($$renderer2) => {
|
|
302
|
+
let { namePlaceholder } = $$props;
|
|
303
|
+
if (true) {
|
|
304
|
+
let failed = function($$renderer3, error) {
|
|
305
|
+
$$renderer3.push(`<mochi-island-failure data-component="AvatarsView"${$7.attr("data-message", error.message)}></mochi-island-failure>`);
|
|
306
|
+
};
|
|
307
|
+
$$renderer2.push("<!--[0-->");
|
|
308
|
+
$$renderer2.boundary({ failed }, ($$renderer3) => {
|
|
309
|
+
$$renderer3.push(`<!--[-->`);
|
|
310
|
+
{
|
|
311
|
+
HydratableBoundary($$renderer3, {
|
|
312
|
+
children: ($$renderer4) => {
|
|
313
|
+
$$renderer4.push(`<mochi-hydratable-island component-name="AvatarsView_257s9yq1wwr4f" component-url="__MOCHI_COMPONENT_URL__AvatarsView_257s9yq1wwr4f__"${$7.attr("props-ref", emitIslandProps({ namePlaceholder }))}>`);
|
|
314
|
+
$$renderer4.push(`<!--[-->`);
|
|
315
|
+
{
|
|
316
|
+
AvatarsView($$renderer4, { namePlaceholder });
|
|
317
|
+
}
|
|
318
|
+
$$renderer4.push(`<!--]-->`);
|
|
319
|
+
$$renderer4.push(`</mochi-hydratable-island>`);
|
|
320
|
+
},
|
|
321
|
+
$$slots: { default: true }
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
$$renderer3.push(`<!--]-->`);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
$$renderer2.push(`<!--]-->`);
|
|
328
|
+
});
|
|
348
329
|
}
|
|
349
330
|
export {
|
|
350
331
|
Avatars as default
|
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
StatusBadge,
|
|
4
4
|
apiDelete,
|
|
5
5
|
apiPost,
|
|
6
|
+
forwardedPortUrl,
|
|
6
7
|
ideUrl
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-btmcxhqy.js";
|
|
8
9
|
import {
|
|
9
10
|
X
|
|
10
11
|
} from "./chunk-4kwy539a.js";
|
|
@@ -285,7 +286,7 @@ function InstanceView($$renderer, $$props) {
|
|
|
285
286
|
const each_array = $6.ensure_array_like(forwards());
|
|
286
287
|
for (let $$index = 0, $$length = each_array.length;$$index < $$length; $$index++) {
|
|
287
288
|
let f = each_array[$$index];
|
|
288
|
-
$$renderer2.push(`<li class="svelte-15fxkvm"><span class="cp svelte-15fxkvm">:${$6.escape(f.container_port)}</span> <span class="arr svelte-15fxkvm">\u2192</span> <a${$6.attr("href",
|
|
289
|
+
$$renderer2.push(`<li class="svelte-15fxkvm"><span class="cp svelte-15fxkvm">:${$6.escape(f.container_port)}</span> <span class="arr svelte-15fxkvm">\u2192</span> <a${$6.attr("href", forwardedPortUrl(f.host_port))} target="_blank" rel="noopener" class="svelte-15fxkvm">${$6.escape(forwardedPortUrl(f.host_port).replace("http://", ""))} `);
|
|
289
290
|
Arrow_up_right($$renderer2, { size: 12 });
|
|
290
291
|
$$renderer2.push(`<!----></a> <button class="rm svelte-15fxkvm" title="Remove">\xD7</button></li>`);
|
|
291
292
|
}
|
|
@@ -367,7 +367,8 @@ function SettingsView($$renderer, $$props) {
|
|
|
367
367
|
customEndpointModel,
|
|
368
368
|
hostEnvVarsEnabled,
|
|
369
369
|
hostEnvVarNames,
|
|
370
|
-
hostEnvVarPresence
|
|
370
|
+
hostEnvVarPresence,
|
|
371
|
+
version
|
|
371
372
|
} = $$props;
|
|
372
373
|
let sound = soundEnabled();
|
|
373
374
|
let shuttingDown = false;
|
|
@@ -1060,7 +1061,7 @@ function SettingsView($$renderer, $$props) {
|
|
|
1060
1061
|
}
|
|
1061
1062
|
$$renderer2.push(`<!--]--></form></section> `);
|
|
1062
1063
|
CoinButton($$renderer2, {});
|
|
1063
|
-
$$renderer2.push(
|
|
1064
|
+
$$renderer2.push(`<!----> <div class="version svelte-1n6ht58">Codebay v${$17.escape(version)}</div></main></div> `);
|
|
1064
1065
|
Toaster($$renderer2, {
|
|
1065
1066
|
toastOptions: {
|
|
1066
1067
|
style: "border:1px solid var(--ink); background:var(--bg-card); color:var(--ink); box-shadow:4px 4px 0 var(--ink); font-family:var(--font-mono); font-size:13px;"
|
|
@@ -1093,7 +1094,8 @@ function Settings($$renderer, $$props) {
|
|
|
1093
1094
|
customEndpointModel,
|
|
1094
1095
|
hostEnvVarsEnabled,
|
|
1095
1096
|
hostEnvVarNames,
|
|
1096
|
-
hostEnvVarPresence
|
|
1097
|
+
hostEnvVarPresence,
|
|
1098
|
+
version
|
|
1097
1099
|
} = $$props;
|
|
1098
1100
|
if (true) {
|
|
1099
1101
|
let failed = function($$renderer3, error) {
|
|
@@ -1125,7 +1127,8 @@ function Settings($$renderer, $$props) {
|
|
|
1125
1127
|
customEndpointModel,
|
|
1126
1128
|
hostEnvVarsEnabled,
|
|
1127
1129
|
hostEnvVarNames,
|
|
1128
|
-
hostEnvVarPresence
|
|
1130
|
+
hostEnvVarPresence,
|
|
1131
|
+
version
|
|
1129
1132
|
}))}>`);
|
|
1130
1133
|
$$renderer4.push(`<!--[-->`);
|
|
1131
1134
|
{
|
|
@@ -1149,7 +1152,8 @@ function Settings($$renderer, $$props) {
|
|
|
1149
1152
|
customEndpointModel,
|
|
1150
1153
|
hostEnvVarsEnabled,
|
|
1151
1154
|
hostEnvVarNames,
|
|
1152
|
-
hostEnvVarPresence
|
|
1155
|
+
hostEnvVarPresence,
|
|
1156
|
+
version
|
|
1153
1157
|
});
|
|
1154
1158
|
}
|
|
1155
1159
|
$$renderer4.push(`<!--]-->`);
|
|
@@ -35,6 +35,12 @@ function apiJson(method, url, body, fallbackMessage) {
|
|
|
35
35
|
}, fallbackMessage);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// src/lib/links.ts
|
|
39
|
+
function forwardedPortUrl(hostPort) {
|
|
40
|
+
const host = typeof window === "undefined" ? "localhost" : window.location.hostname;
|
|
41
|
+
return `http://${host}:${hostPort}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
// src/components/StatusBadge.svelte
|
|
39
45
|
import"svelte/internal/flags/async";
|
|
40
46
|
import * as $ from "svelte/internal/server";
|
|
@@ -49,4 +55,4 @@ function StatusBadge($$renderer, $$props) {
|
|
|
49
55
|
$$renderer.push(`<span${$.attr_class(`status ${$.stringify(status)}`, "svelte-1hqr9xd")}>${$.escape(label[status])}</span>`);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
export { ideUrl, apiFetch, apiPost, apiDelete, StatusBadge };
|
|
58
|
+
export { ideUrl, apiFetch, apiPost, apiDelete, forwardedPortUrl, StatusBadge };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.page.svelte-1n6ht58{display:flex;flex-direction:column;min-height:100vh}.title.svelte-1n6ht58{display:inline-flex;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-soft);align-items: center;padding:0 14px;font-size:12px}.content.svelte-1n6ht58{display:flex;flex-direction:column;flex:1;align-items: center;gap:16px;padding:32px 20px}.card.svelte-1n6ht58{background:var(--bg-card);border:1px solid var(--rule);width:100%;max-width:560px;height:max-content}.danger-card.svelte-1n6ht58{border-color:var(--danger)}.disabled-card.svelte-1n6ht58{opacity:.55;pointer-events:none}.danger-card.svelte-1n6ht58 .btn{white-space:nowrap;flex:none}.version.svelte-1n6ht58{font-family:var(--font-mono);letter-spacing:.08em;color:var(--ink-soft);font-size:11px}.shutting.svelte-1n6ht58{font-family:var(--font-mono);color:var(--danger);text-align:right;flex:none;max-width:200px;font-size:12px;line-height:1.4}.row.svelte-1n6ht58{display:flex;justify-content:space-between;align-items: center;gap:16px;padding:18px}.row.divided.svelte-1n6ht58{border-top:1px solid var(--rule);align-items: flex-start}.row.divided.svelte-1n6ht58 .btn{white-space:nowrap;flex:none}.sub.svelte-1n6ht58{padding:0 18px 14px}code.svelte-1n6ht58{font-family:var(--font-mono);background:var(--bg);border:1px solid var(--rule);border-radius:3px;padding:1px 4px;font-size:.92em}.label.svelte-1n6ht58{display:flex;color:var(--ink);align-items: flex-start;gap:12px;min-width:0}.text.svelte-1n6ht58{min-width:0}.name.svelte-1n6ht58{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.06em;font-size:13px}.arch.svelte-1n6ht58{letter-spacing:.04em;color:var(--ink-soft);border:1px solid var(--rule);vertical-align:middle;border-radius:3px;margin-left:8px;padding:2px 6px;font-size:11px}.tip.svelte-1n6ht58{color:var(--ink-soft);background:color-mix(in srgb,var(--info)10%,transparent);border:1px solid color-mix(in srgb,var(--info)35%,transparent);border-radius:4px;width:100%;margin-top:4px;padding:8px 10px}.desc.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink-faint);margin-top:4px;font-size:12px;line-height:1.4}.image-row.svelte-1n6ht58,.token-row.svelte-1n6ht58{flex-wrap:wrap}.image-controls.svelte-1n6ht58{display:flex;flex:1;justify-content:flex-end;align-items: center;gap:8px;min-width:220px}.image-input.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink);background:var(--bg);border:1px solid var(--rule);flex:1;min-width:0;padding:8px 10px;font-size:12px}.image-input.svelte-1n6ht58:focus-visible{outline:2px solid var(--ink);outline-offset:1px}.image-controls.svelte-1n6ht58 .btn{flex:none}.msg.svelte-1n6ht58{font-family:var(--font-mono);width:100%;margin-top:2px;font-size:12px;line-height:1.4}.msg.error.svelte-1n6ht58{color:var(--danger)}.msg.ok.svelte-1n6ht58{color:var(--ink-soft)}.switch.svelte-1n6ht58{position:relative;cursor:pointer;flex:none}.switch.svelte-1n6ht58 input:where(.svelte-1n6ht58){position:absolute;opacity:0;cursor:pointer;margin:0;inset:0}.track.svelte-1n6ht58{display:block;border:1px solid var(--rule);background:var(--bg);border-radius:999px;width:44px;height:24px;transition:background .15s}.thumb.svelte-1n6ht58{display:block;background:var(--ink-faint);border-radius:999px;width:18px;height:18px;margin:2px;transition:transform .15s,background .15s}.switch.svelte-1n6ht58 input:where(.svelte-1n6ht58):checked+.track:where(.svelte-1n6ht58){background:var(--switch-on-bg)}.switch.svelte-1n6ht58 input:where(.svelte-1n6ht58):checked+.track:where(.svelte-1n6ht58) .thumb:where(.svelte-1n6ht58){background:var(--ink);transform:translate(20px)}.switch.svelte-1n6ht58 input:where(.svelte-1n6ht58):focus-visible+.track:where(.svelte-1n6ht58){outline:2px solid var(--ink);outline-offset:2px}.model-fields.svelte-1n6ht58{display:flex;flex-direction:column;flex:1;gap:6px;min-width:220px}.model-row.svelte-1n6ht58{display:flex;align-items: center;gap:8px}.model-label.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink-soft);text-align:right;flex:none;width:72px;font-size:11px}.model-row.svelte-1n6ht58 .image-input:where(.svelte-1n6ht58){flex:1}.model-save-row.svelte-1n6ht58{display:flex;justify-content:flex-end;margin-top:4px}.var-row.svelte-1n6ht58{flex-direction:column;align-items:stretch;gap:10px}.var-list.svelte-1n6ht58{display:flex;flex-direction:column;gap:6px}.var-item.svelte-1n6ht58{display:flex;background:var(--bg);border:1px solid var(--rule);align-items: center;gap:8px;padding:6px 8px}.var-name.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink);word-break:break-all;flex:1;min-width:0;font-size:12px}.var-status.svelte-1n6ht58{font-family:var(--font-mono);letter-spacing:.04em;color:var(--danger);flex:none;font-size:11px}.var-status.present.svelte-1n6ht58{color:var(--ink-soft)}.var-remove.svelte-1n6ht58{display:inline-flex;color:var(--ink-soft);border:1px solid var(--rule);cursor:pointer;background:0 0;flex:none;justify-content:center;align-items: center;width:22px;height:22px;padding:0}.var-remove.svelte-1n6ht58:hover:not(:disabled){color:var(--danger);border-color:var(--danger)}.var-remove.svelte-1n6ht58:disabled{opacity:.4;cursor:not-allowed}.var-empty.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink-faint);font-size:12px}.var-add.svelte-1n6ht58{display:flex;gap:8px}.var-add.svelte-1n6ht58 .btn{flex:none}
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ The UI opens at `http://localhost:6969`. State (SQLite DB + per-instance workspa
|
|
|
17
17
|
- `PORT` — server port (default `6969`)
|
|
18
18
|
- `DATA_DIR` — where state lives (default `~/.codebay`)
|
|
19
19
|
- `DOCKER_HOST` — Docker socket/URL (defaults to your active Docker context)
|
|
20
|
+
- `HOST` — bind address (default `127.0.0.1`). Set `0.0.0.0` to reach codebay from other machines; your instances' forwarded app ports are then published on all interfaces too. Each container's code-server port stays loopback-only regardless — it runs without a password of its own and is reached through the Basic-Auth-gated `/p/:id/` proxy instead. Existing instances need a **Restart** to pick up the new binding.
|
|
20
21
|
- `BASIC_AUTH_PASSWORD` — enables HTTP Basic Auth over the whole UI (disabled when unset); required if you bind beyond loopback with `HOST=0.0.0.0`
|
|
21
22
|
- `CODEBAY_CLAUDE_CODE_TOKEN` — Claude Code token to inject into every container (e.g. from `claude setup-token`) instead of discovering the host's credentials
|
|
22
23
|
- `CODEBAY_GITHUB_TOKEN` — GitHub token to inject instead of reading `gh auth token` from the host
|
package/bin/codebay.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// ./public, and the .mochi manifest relative to process.cwd(), so chdir into the
|
|
4
4
|
// package root before booting. Dynamic import() of a relative specifier resolves
|
|
5
5
|
// against this module's URL (not cwd), so the chdir doesn't affect it.
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
8
9
|
|
|
@@ -11,6 +12,46 @@ if (typeof Bun === 'undefined') {
|
|
|
11
12
|
process.exit(1);
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
+
|
|
17
|
+
function readVersion(): string {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8')).version ?? 'unknown';
|
|
20
|
+
} catch {
|
|
21
|
+
return 'unknown';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
|
|
27
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
28
|
+
console.log(readVersion());
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
33
|
+
console.log(`codebay ${readVersion()} — spin up isolated devcontainers with a browser IDE.
|
|
34
|
+
|
|
35
|
+
Usage: bunx codebay [options]
|
|
36
|
+
|
|
37
|
+
Starts the web UI (default http://localhost:6969) and opens it in your browser.
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
-h, --help Show this help and exit
|
|
41
|
+
-v, --version Print the version and exit
|
|
42
|
+
|
|
43
|
+
Environment variables:
|
|
44
|
+
PORT Server port (default 6969)
|
|
45
|
+
DATA_DIR Where state lives (default ~/.codebay)
|
|
46
|
+
DOCKER_HOST Docker socket/URL (defaults to your active Docker context)
|
|
47
|
+
HOST Bind address (default 127.0.0.1; set 0.0.0.0 for LAN access)
|
|
48
|
+
BASIC_AUTH_PASSWORD Enable HTTP Basic Auth over the whole UI (required for 0.0.0.0)
|
|
49
|
+
CODEBAY_CLAUDE_CODE_TOKEN Claude Code token to inject into every container
|
|
50
|
+
CODEBAY_GITHUB_TOKEN GitHub token to inject into every container
|
|
51
|
+
DISABLE_OPEN_BROWSER=1 Skip opening the browser on startup`);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
14
55
|
// config.server.ts resolves a relative DATA_DIR against process.cwd(). Pin it to the
|
|
15
56
|
// directory the user actually ran the command from, before the chdir below moves cwd
|
|
16
57
|
// into the (possibly npm-cached) package root.
|
|
@@ -18,5 +59,5 @@ if (process.env.DATA_DIR && !isAbsolute(process.env.DATA_DIR)) {
|
|
|
18
59
|
process.env.DATA_DIR = resolve(process.cwd(), process.env.DATA_DIR);
|
|
19
60
|
}
|
|
20
61
|
|
|
21
|
-
process.chdir(
|
|
62
|
+
process.chdir(pkgRoot);
|
|
22
63
|
await import('../src/index.ts');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Web UI for spinning up isolated devcontainer instances from any local folder, each with a browser-based VS Code (code-server) — built for parallel Claude Code workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Lives outside the editor component so the route can roll the hint server-side —
|
|
2
|
+
// rolling it during component init would re-roll it again at hydration.
|
|
3
|
+
export const NAME_PROMPTS = [
|
|
4
|
+
'dragon',
|
|
5
|
+
'unicorn',
|
|
6
|
+
'cactus',
|
|
7
|
+
'comet',
|
|
8
|
+
'pretzel',
|
|
9
|
+
'volcano',
|
|
10
|
+
'narwhal',
|
|
11
|
+
'waffle',
|
|
12
|
+
'satellite',
|
|
13
|
+
'pineapple',
|
|
14
|
+
'kraken',
|
|
15
|
+
'yeti',
|
|
16
|
+
'moose',
|
|
17
|
+
'lighthouse',
|
|
18
|
+
'toaster',
|
|
19
|
+
'hedgehog',
|
|
20
|
+
'walrus',
|
|
21
|
+
'pumpkin',
|
|
22
|
+
'jellyfish',
|
|
23
|
+
'tornado',
|
|
24
|
+
'igloo',
|
|
25
|
+
'boomerang'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export function pickNamePrompt(): string {
|
|
29
|
+
return NAME_PROMPTS[Math.floor(Math.random() * NAME_PROMPTS.length)]!;
|
|
30
|
+
}
|
|
@@ -14,32 +14,8 @@
|
|
|
14
14
|
import Avatar from './Avatar.svelte';
|
|
15
15
|
import Button from './Button.svelte';
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
'dragon',
|
|
20
|
-
'unicorn',
|
|
21
|
-
'cactus',
|
|
22
|
-
'comet',
|
|
23
|
-
'pretzel',
|
|
24
|
-
'volcano',
|
|
25
|
-
'narwhal',
|
|
26
|
-
'waffle',
|
|
27
|
-
'satellite',
|
|
28
|
-
'pineapple',
|
|
29
|
-
'kraken',
|
|
30
|
-
'yeti',
|
|
31
|
-
'moose',
|
|
32
|
-
'lighthouse',
|
|
33
|
-
'toaster',
|
|
34
|
-
'hedgehog',
|
|
35
|
-
'walrus',
|
|
36
|
-
'pumpkin',
|
|
37
|
-
'jellyfish',
|
|
38
|
-
'tornado',
|
|
39
|
-
'igloo',
|
|
40
|
-
'boomerang'
|
|
41
|
-
];
|
|
42
|
-
const namePlaceholder = NAME_PROMPTS[Math.floor(Math.random() * NAME_PROMPTS.length)];
|
|
17
|
+
// Rolled by the route, not here, so hydration doesn't pick a different word.
|
|
18
|
+
let { namePlaceholder }: { namePlaceholder: string } = $props();
|
|
43
19
|
|
|
44
20
|
// Same shape `decode()` produces, so the two round-trip.
|
|
45
21
|
let cells = $state<number[]>(Array(ROWS * COLS).fill(OFF));
|
|
@@ -109,7 +85,7 @@
|
|
|
109
85
|
|
|
110
86
|
<section class="editor">
|
|
111
87
|
<div class="head">
|
|
112
|
-
<h2>Draw your avatar</h2>
|
|
88
|
+
<h2>Draw your own avatar</h2>
|
|
113
89
|
</div>
|
|
114
90
|
|
|
115
91
|
<div class="body">
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import Avatar from './Avatar.svelte';
|
|
5
5
|
import AvatarEditor from './AvatarEditor.svelte';
|
|
6
6
|
import { avatars } from '../avatars/index.ts';
|
|
7
|
+
|
|
8
|
+
let { namePlaceholder }: { namePlaceholder: string } = $props();
|
|
7
9
|
</script>
|
|
8
10
|
|
|
9
11
|
<div class="page">
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
</AppBar>
|
|
13
15
|
|
|
14
16
|
<main class="content">
|
|
15
|
-
<AvatarEditor />
|
|
17
|
+
<AvatarEditor {namePlaceholder} />
|
|
16
18
|
|
|
17
19
|
<section class="catalog">
|
|
18
20
|
<header>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import HealthBox from './HealthBox.svelte';
|
|
6
6
|
import StatusBadge from './StatusBadge.svelte';
|
|
7
7
|
import Skeleton from './Skeleton.svelte';
|
|
8
|
+
import { forwardedPortUrl } from '../lib/links.ts';
|
|
8
9
|
import { liveSocket, liveStream } from '../live.ts';
|
|
9
10
|
import { apiPost, apiDelete } from '../api.ts';
|
|
10
11
|
import { enhance } from 'mochi-framework';
|
|
@@ -189,8 +190,9 @@
|
|
|
189
190
|
<li>
|
|
190
191
|
<span class="cp">:{f.container_port}</span>
|
|
191
192
|
<span class="arr">→</span>
|
|
192
|
-
<a href={
|
|
193
|
-
>
|
|
193
|
+
<a href={forwardedPortUrl(f.host_port)} target="_blank" rel="noopener"
|
|
194
|
+
>{forwardedPortUrl(f.host_port).replace('http://', '')}
|
|
195
|
+
<ArrowUpRight size={12} /></a
|
|
194
196
|
>
|
|
195
197
|
<button class="rm" title="Remove" onclick={() => removePort(f.container_port)}
|
|
196
198
|
>×</button
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Cable from '@lucide/svelte/icons/cable';
|
|
3
|
+
import { forwardedPortUrl } from '../lib/links.ts';
|
|
3
4
|
import type { PortForward } from '../types.ts';
|
|
4
5
|
|
|
5
6
|
let { ports }: { ports: PortForward[] } = $props();
|
|
@@ -13,10 +14,10 @@
|
|
|
13
14
|
<a
|
|
14
15
|
class="fport"
|
|
15
16
|
class:open={f.open}
|
|
16
|
-
href={
|
|
17
|
+
href={forwardedPortUrl(f.host_port)}
|
|
17
18
|
target="_blank"
|
|
18
19
|
rel="noopener"
|
|
19
|
-
title={`container :${f.container_port} →
|
|
20
|
+
title={`container :${f.container_port} → ${forwardedPortUrl(f.host_port)} ${f.open ? '(open)' : '(not published yet)'}`}
|
|
20
21
|
>{f.container_port}</a
|
|
21
22
|
>
|
|
22
23
|
{/each}
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
customEndpointModel,
|
|
47
47
|
hostEnvVarsEnabled,
|
|
48
48
|
hostEnvVarNames,
|
|
49
|
-
hostEnvVarPresence
|
|
49
|
+
hostEnvVarPresence,
|
|
50
|
+
version
|
|
50
51
|
}: {
|
|
51
52
|
defaultImage: string;
|
|
52
53
|
builtinImage: string;
|
|
@@ -68,6 +69,7 @@
|
|
|
68
69
|
hostEnvVarsEnabled: boolean;
|
|
69
70
|
hostEnvVarNames: string[];
|
|
70
71
|
hostEnvVarPresence: Record<string, boolean>;
|
|
72
|
+
version: string;
|
|
71
73
|
} = $props();
|
|
72
74
|
|
|
73
75
|
// Defaults to on during SSR, where localStorage doesn't exist.
|
|
@@ -1164,6 +1166,8 @@
|
|
|
1164
1166
|
</section>
|
|
1165
1167
|
|
|
1166
1168
|
<CoinButton />
|
|
1169
|
+
|
|
1170
|
+
<div class="version">Codebay v{version}</div>
|
|
1167
1171
|
</main>
|
|
1168
1172
|
</div>
|
|
1169
1173
|
|
|
@@ -1218,6 +1222,12 @@
|
|
|
1218
1222
|
flex: none;
|
|
1219
1223
|
white-space: nowrap;
|
|
1220
1224
|
}
|
|
1225
|
+
.version {
|
|
1226
|
+
font-family: var(--font-mono);
|
|
1227
|
+
font-size: 11px;
|
|
1228
|
+
letter-spacing: 0.08em;
|
|
1229
|
+
color: var(--ink-soft);
|
|
1230
|
+
}
|
|
1221
1231
|
.shutting {
|
|
1222
1232
|
flex: none;
|
|
1223
1233
|
max-width: 200px;
|
package/src/lib/config.server.ts
CHANGED
|
@@ -37,6 +37,17 @@ export function ensureMochiKey(): void {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** Read relative to this module, not cwd, because under `bunx codebay` cwd is the user's folder. */
|
|
41
|
+
export const APP_VERSION: string = (() => {
|
|
42
|
+
try {
|
|
43
|
+
const path = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
|
|
44
|
+
const version = JSON.parse(readFileSync(path, 'utf8')).version;
|
|
45
|
+
return typeof version === 'string' ? version : 'unknown';
|
|
46
|
+
} catch {
|
|
47
|
+
return 'unknown';
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
|
|
40
51
|
/** Per-instance working copies live here: <INSTANCES_DIR>/<id>/workspace. */
|
|
41
52
|
export const INSTANCES_DIR = join(DATA_DIR, 'instances');
|
|
42
53
|
|
|
@@ -58,6 +69,13 @@ export const BASIC_AUTH_PASSWORD = process.env.BASIC_AUTH_PASSWORD || '';
|
|
|
58
69
|
/** Loopback by default so an instance with no password isn't exposed to the LAN. */
|
|
59
70
|
export const HOST = process.env.HOST || '127.0.0.1';
|
|
60
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Forwarded app ports follow the server's bind; code-server's own port never does
|
|
74
|
+
* (it runs with auth disabled). Anything but `0.0.0.0` degrades to loopback rather
|
|
75
|
+
* than risking an appPort string Docker rejects.
|
|
76
|
+
*/
|
|
77
|
+
export const PUBLISH_HOST = HOST === '0.0.0.0' ? '0.0.0.0' : '127.0.0.1';
|
|
78
|
+
|
|
61
79
|
export const PORT = Number(process.env.PORT) || 6969;
|
|
62
80
|
|
|
63
81
|
/** Mochi's CSRF origin check compares against these; override when behind a proxy. */
|
|
@@ -3,7 +3,13 @@ import { existsSync, statSync } from 'node:fs';
|
|
|
3
3
|
import { basename, dirname, join, relative } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { INSTALL_SCRIPT as TMUX_INSTALL_SCRIPT } from '../container-injections/tmux.ts';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
CODE_SERVER_PORT,
|
|
8
|
+
DEFAULT_IMAGE,
|
|
9
|
+
PUBLISH_HOST,
|
|
10
|
+
devcontainerBin,
|
|
11
|
+
dockerEnv
|
|
12
|
+
} from './config.server.ts';
|
|
7
13
|
import { spawnCapture } from './spawn.server.ts';
|
|
8
14
|
import type { PortForward } from '../types.ts';
|
|
9
15
|
|
|
@@ -294,9 +300,11 @@ export async function writeOverrideConfig(
|
|
|
294
300
|
};
|
|
295
301
|
|
|
296
302
|
// Rendered from scratch rather than merged, so removing a forward actually drops its mapping.
|
|
303
|
+
// code-server stays loopback even under HOST=0.0.0.0 — it has no auth of its own, so the LAN
|
|
304
|
+
// reaches it only through the Basic-Auth-gated /p/:id/ proxy.
|
|
297
305
|
config.appPort = [
|
|
298
306
|
`127.0.0.1:${hostPort}:${CODE_SERVER_PORT}`,
|
|
299
|
-
...forwards.map((f) =>
|
|
307
|
+
...forwards.map((f) => `${PUBLISH_HOST}:${f.host_port}:${f.container_port}`)
|
|
300
308
|
];
|
|
301
309
|
|
|
302
310
|
// host.docker.internal isn't automatic on Colima/Linux Docker.
|
package/src/lib/links.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwarded ports are published on the machine running codebay, which is only
|
|
3
|
+
* `localhost` when the browser is on that same machine — under HOST=0.0.0.0 it
|
|
4
|
+
* isn't. SSR has no `window`, so the first paint falls back to localhost.
|
|
5
|
+
*/
|
|
6
|
+
export function forwardedPortUrl(hostPort: number): string {
|
|
7
|
+
const host = typeof window === 'undefined' ? 'localhost' : window.location.hostname;
|
|
8
|
+
return `http://${host}:${hostPort}`;
|
|
9
|
+
}
|
package/src/pages/Avatars.svelte
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import '@fontsource-variable/doto';
|
|
3
3
|
import '@fontsource-variable/jetbrains-mono';
|
|
4
4
|
import AvatarsView from '../components/AvatarsView.svelte';
|
|
5
|
+
|
|
6
|
+
let { namePlaceholder }: { namePlaceholder: string } = $props();
|
|
5
7
|
</script>
|
|
6
8
|
|
|
7
|
-
<AvatarsView mochi:hydrate />
|
|
9
|
+
<AvatarsView {namePlaceholder} mochi:hydrate />
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
customEndpointModel,
|
|
24
24
|
hostEnvVarsEnabled,
|
|
25
25
|
hostEnvVarNames,
|
|
26
|
-
hostEnvVarPresence
|
|
26
|
+
hostEnvVarPresence,
|
|
27
|
+
version
|
|
27
28
|
}: {
|
|
28
29
|
defaultImage: string;
|
|
29
30
|
builtinImage: string;
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
hostEnvVarsEnabled: boolean;
|
|
46
47
|
hostEnvVarNames: string[];
|
|
47
48
|
hostEnvVarPresence: Record<string, boolean>;
|
|
49
|
+
version: string;
|
|
48
50
|
} = $props();
|
|
49
51
|
</script>
|
|
50
52
|
|
|
@@ -69,5 +71,6 @@
|
|
|
69
71
|
{hostEnvVarsEnabled}
|
|
70
72
|
{hostEnvVarNames}
|
|
71
73
|
{hostEnvVarPresence}
|
|
74
|
+
{version}
|
|
72
75
|
mochi:hydrate
|
|
73
76
|
/>
|
package/src/routes.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from './container-injections/claude-code-custom.ts';
|
|
21
21
|
import { hostEnvVarPresence, parseHostEnvVarNames } from './container-injections/host-env-vars.ts';
|
|
22
22
|
import { browse } from './lib/picker.server.ts';
|
|
23
|
+
import { pickNamePrompt } from './avatars/name-prompts.ts';
|
|
23
24
|
import {
|
|
24
25
|
addForwardedPort,
|
|
25
26
|
createInstance,
|
|
@@ -45,7 +46,7 @@ import {
|
|
|
45
46
|
listFolderHistory,
|
|
46
47
|
setOption
|
|
47
48
|
} from './lib/db.server.ts';
|
|
48
|
-
import { DEFAULT_COPY_IGNORE, DEFAULT_IMAGE } from './lib/config.server.ts';
|
|
49
|
+
import { APP_VERSION, DEFAULT_COPY_IGNORE, DEFAULT_IMAGE } from './lib/config.server.ts';
|
|
49
50
|
import { wsUpgradeAllowed } from './lib/auth.server.ts';
|
|
50
51
|
import { clearAttention, setAttention } from './lib/bridge.server.ts';
|
|
51
52
|
import { timingSafeEqualStr } from './lib/crypto.server.ts';
|
|
@@ -144,9 +145,11 @@ export const routes: Record<string, MochiRouteValue> = {
|
|
|
144
145
|
}
|
|
145
146
|
}),
|
|
146
147
|
|
|
147
|
-
// Reached from the settings page's coin
|
|
148
|
-
//
|
|
149
|
-
'/avatars': Mochi.page('./src/pages/Avatars.svelte'
|
|
148
|
+
// Reached from the settings page's coin. The name hint is rolled here rather than in
|
|
149
|
+
// the editor so hydration can't re-roll it and swap the word out mid-load.
|
|
150
|
+
'/avatars': Mochi.page('./src/pages/Avatars.svelte', {
|
|
151
|
+
serverProps: () => ({ namePlaceholder: pickNamePrompt() })
|
|
152
|
+
}),
|
|
150
153
|
|
|
151
154
|
// Settings mutations are Form actions rather than JSON routes, so each control
|
|
152
155
|
// still works without JS and `enhance` only upgrades it.
|
|
@@ -181,7 +184,8 @@ export const routes: Record<string, MochiRouteValue> = {
|
|
|
181
184
|
customEndpointModel: getOption('custom_endpoint_model') ?? DEFAULT_MODEL,
|
|
182
185
|
hostEnvVarsEnabled: getOption('host_env_vars_enabled') === '1',
|
|
183
186
|
hostEnvVarNames,
|
|
184
|
-
hostEnvVarPresence: hostEnvVarPresence(hostEnvVarNames)
|
|
187
|
+
hostEnvVarPresence: hostEnvVarPresence(hostEnvVarNames),
|
|
188
|
+
version: APP_VERSION
|
|
185
189
|
};
|
|
186
190
|
},
|
|
187
191
|
actions: {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Ta as a}from"/_mochi/client/chunk-kgn1b2t1.js";export{a as registerComponent};
|