agenttwin 0.3.3 → 0.3.5

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.
Files changed (47) hide show
  1. package/dist/api.js +91 -5
  2. package/dist/cli.js +15 -15
  3. package/dist/dashboard/assets/dashboard/agent-home.css +12 -9
  4. package/dist/dashboard/assets/dashboard/dashboard.css +4 -0
  5. package/dist/dashboard/assets/dashboard/inventory.json +1 -1
  6. package/dist/dashboard/assets/dashboard/runs.json +1 -1
  7. package/dist/dashboard/assets/next/static/It81cabE0Vc-SUEO_z8CQ/_buildManifest.js +1 -0
  8. package/dist/dashboard/assets/next/static/It81cabE0Vc-SUEO_z8CQ/_ssgManifest.js +1 -0
  9. package/dist/dashboard/assets/next/static/chunks/255-09fb0d129bb12f51.js +1 -0
  10. package/dist/dashboard/assets/next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  11. package/dist/dashboard/assets/next/static/chunks/772-85561381eedc95da.js +67 -0
  12. package/dist/dashboard/assets/next/static/chunks/app/_not-found/page-5dc6a60918e8652d.js +1 -0
  13. package/dist/dashboard/assets/next/static/chunks/app/dashboard/layout-fa1f6ba56d7af38a.js +1 -0
  14. package/dist/dashboard/assets/next/static/chunks/app/dashboard/page-9b4372b1e47bc3bc.js +1 -0
  15. package/dist/dashboard/assets/next/static/chunks/app/demo/page-760a47a5e4dee43f.js +1 -0
  16. package/dist/dashboard/assets/next/static/chunks/app/docs/[[...slug]]/page-80a0c2c82d5b4efd.js +1 -0
  17. package/dist/dashboard/assets/next/static/chunks/app/layout-1a40c59e535513d4.js +1 -0
  18. package/dist/dashboard/assets/next/static/chunks/app/page-de4735052adab906.js +1 -0
  19. package/dist/dashboard/assets/next/static/chunks/app/pricing/page-7703fc31d6dff0f7.js +1 -0
  20. package/dist/dashboard/assets/next/static/chunks/app/sitemap.xml/route-ef5a216a578eb7a8.js +1 -0
  21. package/dist/dashboard/assets/next/static/chunks/framework-292291387d6b2e39.js +1 -0
  22. package/dist/dashboard/assets/next/static/chunks/main-aea6e71289384293.js +1 -0
  23. package/dist/dashboard/assets/next/static/chunks/main-app-99295a8cc37e8a47.js +1 -0
  24. package/dist/dashboard/assets/next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  25. package/dist/dashboard/assets/next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  26. package/dist/dashboard/assets/next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  27. package/dist/dashboard/assets/next/static/chunks/webpack-330c9b9dd99c0d55.js +1 -0
  28. package/dist/dashboard/assets/next/static/css/1323d7da89b3fa5e.css +1 -0
  29. package/dist/dashboard/assets/next/static/css/45513ebc8c6ae800.css +1 -0
  30. package/dist/dashboard/assets/next/static/css/61ec74d503f353d4.css +1 -0
  31. package/dist/dashboard/assets/next/static/css/62e3209163fa5e55.css +1 -0
  32. package/dist/dashboard/assets/next/static/css/9282330a2fd1ab73.css +1 -0
  33. package/dist/dashboard/assets/next/static/css/c5ac36ef5726e746.css +1 -0
  34. package/dist/dashboard/assets/next/static/media/inter-var.e4af272c.woff2 +0 -0
  35. package/dist/dashboard/assets/next/static/media/moonlit-coast.bbe901a1.png +0 -0
  36. package/dist/dashboard/assets/next/static/media/plexmono-400.d3ebbfd6.woff2 +0 -0
  37. package/dist/dashboard/index.html +1 -140
  38. package/dist/gateway.js +91 -5
  39. package/package.json +1 -1
  40. package/dist/dashboard/assets/dashboard/agent-home.js +0 -264
  41. package/dist/dashboard/assets/dashboard/dashboard.js +0 -436
  42. package/dist/dashboard/assets/dashboard/hosted.js +0 -89
  43. package/dist/dashboard/assets/dashboard/workflows.js +0 -39
  44. package/dist/dashboard/assets/vendor/dompurify.LICENSE +0 -202
  45. package/dist/dashboard/assets/vendor/marked.LICENSE +0 -44
  46. package/dist/dashboard/assets/vendor/marked.js +0 -82
  47. package/dist/dashboard/assets/vendor/purify.min.js +0 -7
package/dist/api.js CHANGED
@@ -938,7 +938,13 @@ async function launchSession(cwd) {
938
938
  });
939
939
  }
940
940
  async function loadDashboardDiscovery(cwd, inventory) {
941
- const base = discoveryTransfer(dashboardInventory(cwd, inventory));
941
+ let previousHead;
942
+ try {
943
+ previousHead = JSON.parse(await readFile6(join6(cwd, ".agenttwin/git.json"), "utf8")).head;
944
+ } catch {
945
+ }
946
+ const git = await captureGit(cwd, previousHead);
947
+ const base = discoveryTransfer({ ...dashboardInventory(cwd, inventory), ...git ? { git } : {} });
942
948
  let raw;
943
949
  try {
944
950
  raw = await readFile6(join6(cwd, ".agenttwin/agent-discovery.json"), "utf8");
@@ -960,6 +966,7 @@ async function loadDashboardDiscovery(cwd, inventory) {
960
966
  var init_session = __esm({
961
967
  "src/commands/session.ts"() {
962
968
  "use strict";
969
+ init_git();
963
970
  init_project_products();
964
971
  init_dashboard_transfer();
965
972
  init_dashboard();
@@ -974,6 +981,28 @@ import { mkdir as mkdir3, readFile as readFile8, writeFile as writeFile4, rename
974
981
  import { resolve as resolve2, join as join8, dirname as dirname2, extname } from "path";
975
982
  import { tmpdir as tmpdir2 } from "os";
976
983
 
984
+ // src/runtime/transcription.ts
985
+ var transcriptionConfiguration = () => ({ available: Boolean(process.env.OPENAI_API_KEY), model: "gpt-4o-mini-transcribe" });
986
+ var formats = { "audio/webm": "webm", "audio/mp4": "mp4", "audio/ogg": "ogg", "audio/wav": "wav", "audio/mpeg": "mp3" };
987
+ function transcriptionAudio(input) {
988
+ if (typeof input.audio !== "string" || typeof input.mimeType !== "string" || !formats[input.mimeType]) throw new Error("Unsupported recording format");
989
+ const bytes = Buffer.from(input.audio, "base64");
990
+ if (!bytes.length || bytes.length > 4 * 1024 * 1024 || bytes.toString("base64") !== input.audio) throw new Error("Recording must be valid base64 and at most 4 MiB");
991
+ return { bytes, mimeType: input.mimeType, extension: formats[input.mimeType] };
992
+ }
993
+ async function transcribeAudio(audio, signal) {
994
+ if (!transcriptionConfiguration().available) throw new Error("transcription_not_configured");
995
+ const form = new FormData();
996
+ form.set("model", "gpt-4o-mini-transcribe");
997
+ form.set("response_format", "json");
998
+ form.set("file", new Blob([new Uint8Array(audio.bytes)], { type: audio.mimeType }), `recording.${audio.extension}`);
999
+ const response = await fetch("https://api.openai.com/v1/audio/transcriptions", { method: "POST", redirect: "error", signal, headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, body: form });
1000
+ if (!response.ok) throw new Error(`transcription_request_failed_${response.status}`);
1001
+ const data = await response.json();
1002
+ if (typeof data.text !== "string" || !data.text.trim() || data.text.length > 16e3) throw new Error("transcription_returned_no_text");
1003
+ return data.text.trim();
1004
+ }
1005
+
977
1006
  // src/runtime/chat.ts
978
1007
  function chatConfiguration() {
979
1008
  return { available: Boolean(process.env.OPENAI_API_KEY && process.env.AGENTTWIN_CHAT_MODEL), provider: "openai", model: process.env.AGENTTWIN_CHAT_MODEL ?? null };
@@ -1621,12 +1650,12 @@ function uploadPath(path) {
1621
1650
  if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || path.split("/").some((p) => !p || p === "." || p === ".." || /^(\.env.*|\.git|\.ssh|\.aws|\.azure|\.config|\.npmrc|\.netrc|credentials.*)$/i.test(p)) || /\.(pem|key|p12|pfx)$/i.test(path)) throw new Error("Unsafe uploaded file path");
1622
1651
  return path;
1623
1652
  }
1624
- async function body(req) {
1653
+ async function body(req, limit = 28 * 1024 * 1024) {
1625
1654
  const chunks = [];
1626
1655
  let bytes = 0;
1627
1656
  for await (const chunk of req) {
1628
1657
  bytes += chunk.length;
1629
- if (bytes > 28 * 1024 * 1024) throw new Error("Request exceeds 28 MiB");
1658
+ if (bytes > limit) throw new Error("Request exceeds size limit");
1630
1659
  chunks.push(Buffer.from(chunk));
1631
1660
  }
1632
1661
  return JSON.parse(Buffer.concat(chunks).toString("utf8"));
@@ -1649,6 +1678,7 @@ async function startGateway(opts) {
1649
1678
  const controllers = /* @__PURE__ */ new Map();
1650
1679
  const chatTasks = /* @__PURE__ */ new Set();
1651
1680
  const chatControllers = /* @__PURE__ */ new Map();
1681
+ const voiceControllers = /* @__PURE__ */ new Map();
1652
1682
  const directory = resolve2(opts.directory);
1653
1683
  await mkdir3(directory, { recursive: true, mode: 448 });
1654
1684
  const storePath = join8(directory, "state.json");
@@ -2027,7 +2057,63 @@ async function startGateway(opts) {
2027
2057
  if (path === "/v1/chat" && req.method === "GET") {
2028
2058
  const project = selectedInventory?.name ?? null;
2029
2059
  const thread = store.chats?.find((t) => t.accountId === account.id && t.project === project);
2030
- send(res, 200, { ...chatConfiguration(), project, thread: thread ?? null, busy: chatControllers.has(account.id) });
2060
+ send(res, 200, { ...chatConfiguration(), transcription: transcriptionConfiguration(), project, thread: thread ?? null, busy: chatControllers.has(account.id) });
2061
+ return;
2062
+ }
2063
+ if (path === "/v1/transcriptions" && req.method === "POST") {
2064
+ if (stopping) {
2065
+ send(res, 503, { error: "stopping" });
2066
+ return;
2067
+ }
2068
+ if (!transcriptionConfiguration().available) {
2069
+ send(res, 503, { error: "transcription_not_configured", message: "Voice input is unavailable on this server." });
2070
+ return;
2071
+ }
2072
+ if (voiceControllers.has(account.id) || voiceControllers.size >= 4) {
2073
+ send(res, 429, { error: "transcription_busy" });
2074
+ return;
2075
+ }
2076
+ const audio = transcriptionAudio(await body(req, 6 * 1024 * 1024));
2077
+ if (!account.active) {
2078
+ send(res, 401, { error: "unauthorized" });
2079
+ return;
2080
+ }
2081
+ if (voiceControllers.has(account.id) || voiceControllers.size >= 4) {
2082
+ send(res, 429, { error: "transcription_busy" });
2083
+ return;
2084
+ }
2085
+ const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2086
+ const usage = account.voiceUsageDay === day ? account.voiceRequests ?? 0 : 0;
2087
+ if (usage >= 50) {
2088
+ send(res, 429, { error: "daily_voice_limit" });
2089
+ return;
2090
+ }
2091
+ const controller = new AbortController();
2092
+ voiceControllers.set(account.id, controller);
2093
+ const timeout = setTimeout(() => controller.abort(), 3e4);
2094
+ let finish;
2095
+ const task = new Promise((done) => {
2096
+ finish = done;
2097
+ });
2098
+ chatTasks.add(task);
2099
+ try {
2100
+ account.voiceUsageDay = day;
2101
+ account.voiceRequests = usage + 1;
2102
+ await save();
2103
+ const text = await transcribeAudio(audio, controller.signal);
2104
+ if (!account.active) {
2105
+ send(res, 401, { error: "unauthorized" });
2106
+ return;
2107
+ }
2108
+ send(res, 200, { text });
2109
+ } catch {
2110
+ send(res, 502, { error: "transcription_failed", message: "Could not transcribe this recording. Please try again or type your message." });
2111
+ } finally {
2112
+ clearTimeout(timeout);
2113
+ voiceControllers.delete(account.id);
2114
+ chatTasks.delete(task);
2115
+ finish();
2116
+ }
2031
2117
  return;
2032
2118
  }
2033
2119
  if (path === "/v1/chat" && req.method === "POST") {
@@ -2180,7 +2266,7 @@ async function startGateway(opts) {
2180
2266
  });
2181
2267
  return { server, async close() {
2182
2268
  stopping = true;
2183
- for (const controller of chatControllers.values()) controller.abort();
2269
+ for (const controller of [...chatControllers.values(), ...voiceControllers.values()]) controller.abort();
2184
2270
  server.close();
2185
2271
  await Promise.allSettled([...workers, ...chatTasks]);
2186
2272
  await writes;
package/dist/cli.js CHANGED
@@ -897,17 +897,17 @@ async function writeDashboard(cwd, inventory) {
897
897
  try {
898
898
  await readFile4(join4(bundle, "index.html"));
899
899
  } catch {
900
- bundle = fileURLToPath(new URL("../../../../site/", import.meta.url));
900
+ bundle = fileURLToPath(new URL("../../dist/dashboard/", import.meta.url));
901
901
  }
902
902
  await mkdir(join4(directory, "assets", "dashboard"), { recursive: true });
903
- const html = await readFile4(join4(bundle, bundle.endsWith("/site/") ? "dashboard.html" : "index.html"), "utf8");
904
- for (const folder of ["dashboard", "icons", "logo", "fonts", "vendor"]) {
903
+ const html = await readFile4(join4(bundle, "index.html"), "utf8");
904
+ for (const folder of ["dashboard", "icons", "logo", "fonts", "next"]) {
905
905
  await cp(join4(bundle, "assets", folder), join4(directory, "assets", folder), { recursive: true });
906
906
  }
907
907
  await cp(join4(bundle, "assets", "brand.css"), join4(directory, "assets", "brand.css"));
908
908
  const data = await loadDashboardDiscovery(cwd, inventory);
909
909
  const payload = JSON.stringify(data).replace(/</g, "\\u003c");
910
- const page2 = html.replaceAll('type="module"', "defer").replace('<script src="assets/dashboard/dashboard.js', `<script id="agenttwin-discovery" type="application/json">${payload}</script><script src="assets/dashboard/dashboard.js`).replace(/href="index.html"/g, 'href="../report.html"').replace(/href="docs\/"/g, 'href="../report.html"').replace("Docs \u2197", "Discovery report \u2197").replace("Runtime documentation \u2197", "Coverage report \u2197").replace("Back to website \u2197", "Discovery report \u2197");
910
+ const page2 = html.replace("</head>", `<script id="agenttwin-discovery" type="application/json">${payload}</script></head>`).replace(/href="index.html"/g, 'href="../report.html"').replace(/href="docs\/"/g, 'href="../report.html"').replace("Docs \u2197", "Discovery report \u2197").replace("Runtime documentation \u2197", "Coverage report \u2197").replace("Back to website \u2197", "Discovery report \u2197");
911
911
  await writeFile(join4(directory, "index.html"), page2, "utf8");
912
912
  await writeFile(join4(directory, "assets", "dashboard", "inventory.json"), JSON.stringify(data), "utf8");
913
913
  await writeFile(join4(directory, "assets", "dashboard", "runs.json"), '{"runs":[]}', "utf8");
@@ -971,7 +971,13 @@ async function launchSession(cwd) {
971
971
  });
972
972
  }
973
973
  async function loadDashboardDiscovery(cwd, inventory) {
974
- const base = discoveryTransfer(dashboardInventory(cwd, inventory));
974
+ let previousHead;
975
+ try {
976
+ previousHead = JSON.parse(await readFile5(join5(cwd, ".agenttwin/git.json"), "utf8")).head;
977
+ } catch {
978
+ }
979
+ const git = await captureGit(cwd, previousHead);
980
+ const base = discoveryTransfer({ ...dashboardInventory(cwd, inventory), ...git ? { git } : {} });
975
981
  let raw;
976
982
  try {
977
983
  raw = await readFile5(join5(cwd, ".agenttwin/agent-discovery.json"), "utf8");
@@ -993,6 +999,7 @@ async function loadDashboardDiscovery(cwd, inventory) {
993
999
  var init_session = __esm({
994
1000
  "src/commands/session.ts"() {
995
1001
  "use strict";
1002
+ init_git();
996
1003
  init_project_products();
997
1004
  init_dashboard_transfer();
998
1005
  init_dashboard();
@@ -1048,7 +1055,6 @@ import { fileURLToPath as fileURLToPath3 } from "url";
1048
1055
  import { parseArgs } from "util";
1049
1056
 
1050
1057
  // src/commands/sync.ts
1051
- init_git();
1052
1058
  import { readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1053
1059
  import { join as join6 } from "path";
1054
1060
 
@@ -1200,19 +1206,13 @@ async function runSync(cwd) {
1200
1206
  const base = apiBase(process.env.AGENTTWIN_API_URL || "https://agenttwin-api.212.56.40.125.sslip.io");
1201
1207
  const inventory = JSON.parse(await readFile6(join6(cwd, ".agenttwin/inventory.json"), "utf8"));
1202
1208
  const payload = await loadDashboardDiscovery(cwd, inventory);
1203
- let previousHead;
1204
- try {
1205
- previousHead = JSON.parse(await readFile6(join6(cwd, ".agenttwin/git.json"), "utf8")).head;
1206
- } catch {
1207
- }
1208
- const git = await captureGit(cwd, previousHead);
1209
- Object.assign(payload, git ? { git } : {});
1209
+ const git = payload.git;
1210
1210
  const response = await fetch(base + "/v1/discovery", { method: "POST", headers: { authorization: "Bearer " + token, "content-type": "application/json" }, body: JSON.stringify(payload), redirect: "error", signal: AbortSignal.timeout(2e4) });
1211
1211
  const result = await response.json();
1212
1212
  if (!response.ok) throw new Error(result.error || "Discovery sync failed: " + response.status);
1213
1213
  if (git) await writeFile3(join6(cwd, ".agenttwin/git.json"), JSON.stringify(git) + "\n", { mode: 384 });
1214
1214
  const receipts = await syncReceipts(cwd, base, token);
1215
- return { receipts, synced: true, tools: payload.tools.length, workflows: payload.workflows.length, dashboard: "https://agenttwin.si/site/dashboard.html", uploaded: "discovery, Git metadata and run evidence; no application source or credentials" };
1215
+ return { receipts, synced: true, tools: payload.tools.length, workflows: payload.workflows.length, dashboard: "https://agenttwin.si/dashboard/", uploaded: "discovery, Git metadata and run evidence; no application source or credentials" };
1216
1216
  }
1217
1217
 
1218
1218
  // src/commands/prepare.ts
@@ -5677,7 +5677,7 @@ async function runInit(opts) {
5677
5677
  await launchSession(opts.cwd);
5678
5678
  await writeDashboard(opts.cwd, finalInventory);
5679
5679
  }
5680
- if (opts.sync) {
5680
+ if (opts.sync || !opts.json && process.env.AGENTTWIN_TOKEN) {
5681
5681
  const synced = await runSync(opts.cwd);
5682
5682
  if (!opts.json) ok(`Synced ${synced.workflows} candidates: ${synced.dashboard}`);
5683
5683
  } else if (!opts.json) info("Send reviewed discovery to your account: npx agenttwin sync (requires AGENTTWIN_TOKEN).");
@@ -377,13 +377,6 @@ body.view-home #api-message:not(:empty){position:absolute;top:58px;right:20px;ma
377
377
  .home-widgets .widget h2{font-size:12px;font-weight:500;color:#d9dce2}
378
378
  .home-widgets .widget .w-empty{font-size:11px;line-height:1.6;color:#90949f;margin:0}
379
379
  .home-widgets .w-head-meta{font-size:10px;color:#9398a4}
380
- .home-widgets #widget-receipts{grid-column:2;grid-row:1 / span 2}
381
- .home-widgets #widget-project{grid-column:1;grid-row:1}
382
- .home-widgets #widget-git{grid-column:1;grid-row:2}
383
- .home-widgets #widget-working{grid-column:1;grid-row:3}
384
- .home-widgets #widget-services{grid-column:2;grid-row:3 / span 2}
385
- .home-widgets #widget-sandbox{grid-column:1;grid-row:4}
386
- .home-widgets #widget-failures{grid-column:1 / -1;grid-row:5}
387
380
  .review-row{grid-template-columns:16px minmax(0,1fr) auto;gap:8px;padding:9px 0}
388
381
  .review-row .ref{grid-column:2;font-size:10px}.review-row .r-time{display:none}
389
382
  .review-row strong{font-size:12px;font-weight:450}.review-row .r-status{font-size:10px}
@@ -400,6 +393,16 @@ body.view-home #api-message:not(:empty){position:absolute;top:58px;right:20px;ma
400
393
  body.view-home #api-message:not(:empty){position:relative;top:auto;right:auto;margin:8px 20px;text-align:left;max-width:none}
401
394
  .home-hero{min-height:310px;padding:162px 20px 24px}.home-hero #home-sky{height:410px!important;background-size:auto 410px;background-position:23% top}
402
395
  .home-overview-title{font-size:21px}.home-body{padding:0 16px 28px}.home-body .home-action{flex-direction:row;padding:12px;gap:12px}.home-action p{display:none}
403
- .home-widgets{grid-template-columns:minmax(0,1fr)}.home-widgets #widget-project,.home-widgets #widget-working,.home-widgets #widget-receipts,.home-widgets #widget-git,.home-widgets #widget-services,.home-widgets #widget-sandbox,.home-widgets #widget-failures{grid-column:auto;grid-row:auto}
404
- #assistant-dock-panel{max-height:410px}.home-assistant-bottom{bottom:auto}.home-grid-head{margin-top:0}
396
+ .home-widgets{grid-template-columns:minmax(0,1fr)}.home-widgets #widget-project,.home-widgets #widget-working,.home-widgets #widget-receipts,.home-widgets #widget-git,.home-widgets #widget-services,.home-widgets #widget-sandbox, #assistant-dock-panel{max-height:410px}.home-assistant-bottom{bottom:auto}.home-grid-head{margin-top:0}
405
397
  }
398
+
399
+ /* Dotted coast fades into the workspace; voice sits inside the input pill. */
400
+ .home-hero #home-sky{background-image:radial-gradient(circle,#11172388 .65px,transparent .95px),url('moonlit-coast.png');background-size:3px 3px,100% auto;background-position:center top;background-repeat:repeat,no-repeat;mask-image:linear-gradient(#000 0%,#000 40%,#0009 61%,transparent 94%)}
401
+ .assistant-entry{display:flex;align-items:center;gap:8px;padding:5px 7px 5px 4px;border:1px solid #ffffff20;border-radius:32px;background:#1b1e25d9;backdrop-filter:blur(18px);box-shadow:0 10px 40px #080a1040}.assistant-entry #assistant-dock-toggle{flex:1;width:auto;min-width:0;padding:12px 14px;border:0;border-radius:28px;background:transparent;box-shadow:none;backdrop-filter:none}.home-assistant-bottom:has(#assistant-dock-panel:not([hidden])) .assistant-entry{display:none}.home-assistant-bottom .home-composer{border-radius:26px}.voice-button{display:grid;place-items:center;flex:none;width:36px;height:36px;min-height:36px;padding:8px;border-radius:50%;border:1px solid #ffffff14;background:#ffffff08;color:#c9d1df}.voice-button svg{width:18px;height:18px}.voice-button:hover{background:#ffffff14}.voice-button.recording{color:#ffb6b6;background:#662c3c;border-color:#b46777}.voice-button:disabled{opacity:.45}.composer-row .assistant-send-hint{flex:1;min-width:0}.composer-send{border-radius:22px}
402
+ @media(max-width:600px){.home-hero #home-sky{background-size:3px 3px,auto 410px;background-position:center top,23% top}.assistant-entry #assistant-dock-toggle{font-size:12px}.assistant-send-hint{font-size:10px}}
403
+
404
+ /* Each column packs its own cards; short and hidden cards reserve no rows. */
405
+ .home-widgets>.widget-column{display:flex;flex-direction:column;gap:10px;min-width:0}
406
+ .home-widgets>.widget-column:not(:has(>.widget:not([hidden]))){display:none}
407
+ .home-widgets:has(>.widget-column:not(:has(>.widget:not([hidden])))){grid-template-columns:minmax(0,1fr)}
408
+ @media(max-width:600px){.home-widgets>.widget-column{display:contents}.home-widgets #widget-project{order:0}.home-widgets #widget-receipts{order:1}.home-widgets #widget-git{order:2}.home-widgets #widget-working{order:3}.home-widgets #widget-services{order:4}.home-widgets #widget-sandbox{order:5}.home-widgets #widget-failures{order:6}}
@@ -189,3 +189,7 @@ button,.upload{background:#202329;border-color:#353944;color:var(--ink);border-r
189
189
  #environment-workflows{max-height:320px;overflow:auto}.discovery-onboarding .terminal-boundary code{overflow-wrap:anywhere}
190
190
 
191
191
  .sidebar-workflow-group{margin:4px 0 12px}.sidebar-workflow-group summary{cursor:pointer;color:var(--text-muted,#93949f);padding:8px 10px;font-size:12px;display:flex;justify-content:space-between}.sidebar-workflow-group summary span{opacity:.65}.sidebar-workflow-group .sidebar-workflow{padding-left:20px;line-height:1.5;white-space:normal;text-align:left}.sidebar-workflow-group summary:hover{color:var(--text,#eee)}
192
+
193
+ /* Actionable resource coverage and focused setup. */
194
+ button.resource-badge{min-height:26px;border:1px solid currentColor;cursor:pointer;text-align:left;white-space:normal}button.resource-badge:hover{filter:brightness(1.2)}button.resource-badge:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
195
+ .resource-setup{width:min(600px,calc(100vw - 32px));max-height:calc(100dvh - 40px);overflow:auto;margin:auto;padding:26px;color:var(--ink);background:#16181c;border:1px solid var(--line);border-radius:16px;box-shadow:0 24px 80px #0008}.resource-setup::backdrop{background:#0009;backdrop-filter:blur(5px)}.resource-setup header{display:flex;justify-content:space-between;align-items:start;margin-bottom:24px;gap:16px}.resource-setup header button{min-width:36px;font-size:20px}.resource-setup h2{font-size:24px;margin-top:4px}.resource-setup h3{font-size:15px;margin:18px 0 8px}.resource-setup p,.resource-setup li{font-size:13px;line-height:1.7;color:var(--muted)}.resource-setup ol{padding-left:20px;margin:16px 0}.resource-setup .actions{flex-wrap:wrap}.resource-setup section{border-top:1px solid var(--line);margin-top:22px;padding-top:4px}.resource-setup-workflows{display:grid;gap:8px;max-height:220px;overflow:auto;margin:12px 0 20px}.resource-setup-workflows button{display:flex;justify-content:space-between;gap:16px;text-align:left;font-size:13px}.resource-setup-workflows button span{white-space:nowrap;color:var(--muted)}#resource-task-status:empty{display:none}
@@ -1 +1 @@
1
- {"schema":"company-inventory/v1","name":"Your project","tools":[],"workflows":[]}
1
+ {"schema": "company-inventory/v1", "name": "Your project", "tools": [], "workflows": []}
@@ -1 +1 @@
1
- {"runs":[]}
1
+ {"runs": []}
@@ -0,0 +1 @@
1
+ self.__BUILD_MANIFEST=function(e,r,t,_){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:6,errorRate:1e-4,numBits:116,numHashes:14,bitArray:[1,1,0,0,r,r,e,e,e,e,r,e,r,r,r,e,r,r,e,e,e,e,r,r,r,e,e,r,e,r,e,r,e,e,r,e,e,e,e,e,r,e,e,e,e,r,r,r,e,r,r,e,e,r,r,r,e,e,e,e,e,e,r,e,r,r,e,e,r,e,e,e,e,r,r,e,e,r,e,r,e,e,r,r,r,r,r,e,e,e,r,e,e,r,r,r,r,e,r,r,r,e,e,e,r,e,e,e,r,e,r,e,r,r,e,r]},__routerFilterDynamic:{numItems:e,errorRate:1e-4,numBits:20,numHashes:14,bitArray:[r,r,e,e,r,r,e,e,e,e,r,r,e,e,r,e,r,e,e,r]},"/_error":["static/chunks/pages/_error-cb2a52f75f2162e2.js"],sortedPages:["/_app","/_error"]}}(1,0,1e-4,14),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
@@ -0,0 +1 @@
1
+ self.__SSG_MANIFEST=new Set(["\u002Fdocs\u002F[[...slug]]"]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()