clankie 0.10.1 → 0.11.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.
Files changed (27) hide show
  1. package/dist/cli.js +393 -3
  2. package/package.json +1 -1
  3. package/web-ui-dist/_shell.html +2 -2
  4. package/web-ui-dist/assets/auth-DxmWVlOj.js +1 -0
  5. package/web-ui-dist/assets/{badge-3e57zy_2.js → badge-B27MkVic.js} +1 -1
  6. package/web-ui-dist/assets/check-D2XSq1UZ.js +1 -0
  7. package/web-ui-dist/assets/circle-x-Bek90N0j.js +1 -0
  8. package/web-ui-dist/assets/{connection-D4rgB5k2.js → connection-Bh7ctiLV.js} +1 -1
  9. package/web-ui-dist/assets/{extensions-BxWXmCbJ.js → extensions-S_3zi2ye.js} +1 -1
  10. package/web-ui-dist/assets/{extensions-Cf8QLmLt.js → extensions-dzmh5XjT.js} +1 -1
  11. package/web-ui-dist/assets/{field-DfBj0pPw.js → field-Dy_c96vc.js} +1 -1
  12. package/web-ui-dist/assets/index-DTLVg2XM.js +1 -0
  13. package/web-ui-dist/assets/{index-Ff5WtXhh.js → index-DuhjveLi.js} +1 -1
  14. package/web-ui-dist/assets/{json-render-renderer-BrlU1n47.js → json-render-renderer-BEA6E5Sp.js} +1 -1
  15. package/web-ui-dist/assets/main-CKIteeQH.js +35 -0
  16. package/web-ui-dist/assets/notifications-CJ14tHG8.js +1 -0
  17. package/web-ui-dist/assets/{scoped-models-DDH_ssLY.js → scoped-models-D-DbdhBI.js} +1 -1
  18. package/web-ui-dist/assets/{sessions._sessionId-CWtQlITL.js → sessions._sessionId-DpLOfRZu.js} +1 -1
  19. package/web-ui-dist/assets/{skills-Clk3tV2m.js → skills-suEsMWsc.js} +1 -1
  20. package/web-ui-dist/assets/styles-CuRG0ztT.css +1 -0
  21. package/web-ui-dist/assets/{theme-e79Jvep_.js → theme-DfHdxP7k.js} +2 -2
  22. package/web-ui-dist/assets/auth-B20mIC_p.js +0 -1
  23. package/web-ui-dist/assets/check-CePvKusa.js +0 -1
  24. package/web-ui-dist/assets/circle-x-B1Pwi07a.js +0 -1
  25. package/web-ui-dist/assets/index-TBNB5eLy.js +0 -1
  26. package/web-ui-dist/assets/main-CP6prmzV.js +0 -35
  27. package/web-ui-dist/assets/styles-KEhqa3CU.css +0 -1
package/dist/cli.js CHANGED
@@ -273899,6 +273899,197 @@ var _ = 30 * 1e3, y = [], R = class {
273899
273899
  }
273900
273900
  };
273901
273901
  //#endregion
273902
+ //#region src/notifications.ts
273903
+ /**
273904
+ * Notification storage and management system.
273905
+ *
273906
+ * Notifications are persisted to disk (~/.clankie/notifications.json)
273907
+ * and broadcast to connected WebSocket clients in real-time.
273908
+ *
273909
+ * Features:
273910
+ * - Auto-pruning of old notifications (500 entry limit)
273911
+ * - Deduplication (prevents spam from frequent failures)
273912
+ * - Atomic file writes (write to temp, then rename)
273913
+ * - Real-time broadcast callback for WebSocket delivery
273914
+ */
273915
+ const MAX_NOTIFICATIONS = 500;
273916
+ const PRUNE_TARGET = 400;
273917
+ const NOTIFICATIONS_FILE = "notifications.json";
273918
+ let notifications = [];
273919
+ let writeQueue = Promise.resolve();
273920
+ let broadcastCallback = null;
273921
+ function getNotificationsPath() {
273922
+ return join(getAppDir(), NOTIFICATIONS_FILE);
273923
+ }
273924
+ function loadNotifications() {
273925
+ const filePath = getNotificationsPath();
273926
+ if (!existsSync(filePath)) return [];
273927
+ try {
273928
+ const content = readFileSync(filePath, "utf-8");
273929
+ const parsed = JSON.parse(content);
273930
+ if (Array.isArray(parsed)) return parsed;
273931
+ } catch (err) {
273932
+ console.error("[notifications] Failed to load notifications:", err);
273933
+ }
273934
+ return [];
273935
+ }
273936
+ function atomicWrite(filePath, data) {
273937
+ const tempPath = `${filePath}.tmp`;
273938
+ writeFileSync(tempPath, data, "utf-8");
273939
+ renameSync(tempPath, filePath);
273940
+ }
273941
+ function persistNotifications() {
273942
+ const filePath = getNotificationsPath();
273943
+ const dir = filePath.substring(0, filePath.lastIndexOf("/"));
273944
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
273945
+ atomicWrite(filePath, JSON.stringify(notifications, null, 2));
273946
+ }
273947
+ function queuePersist() {
273948
+ writeQueue = writeQueue.then(() => {
273949
+ try {
273950
+ persistNotifications();
273951
+ } catch (err) {
273952
+ console.error("[notifications] Failed to persist:", err);
273953
+ }
273954
+ });
273955
+ }
273956
+ function pruneNotifications() {
273957
+ if (notifications.length <= MAX_NOTIFICATIONS) return;
273958
+ const sorted = [...notifications].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
273959
+ let toKeep = sorted.filter((n) => !n.dismissed);
273960
+ if (toKeep.length > PRUNE_TARGET) {
273961
+ const unread = toKeep.filter((n) => !n.read);
273962
+ const readToKeep = toKeep.filter((n) => n.read).slice(0, Math.max(0, PRUNE_TARGET - unread.length));
273963
+ toKeep = [...unread, ...readToKeep];
273964
+ }
273965
+ if (toKeep.length > PRUNE_TARGET) toKeep = toKeep.slice(0, PRUNE_TARGET);
273966
+ notifications = toKeep.sort((a, b) => {
273967
+ const timeDiff = new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
273968
+ if (timeDiff !== 0) return timeDiff;
273969
+ return a.id.localeCompare(b.id);
273970
+ });
273971
+ console.log(`[notifications] Pruned from ${sorted.length} to ${notifications.length} entries`);
273972
+ }
273973
+ function findDuplicate(input) {
273974
+ if (!input.dedupKey) return void 0;
273975
+ return notifications.find((n) => {
273976
+ if (n.read || n.dismissed) return false;
273977
+ if (n.source !== input.source) return false;
273978
+ return n.metadata?.dedupKey === input.dedupKey;
273979
+ });
273980
+ }
273981
+ /**
273982
+ * Initialize the notification store. Call once at startup.
273983
+ */
273984
+ function initNotifications() {
273985
+ notifications = loadNotifications();
273986
+ console.log(`[notifications] Loaded ${notifications.length} notifications`);
273987
+ }
273988
+ /**
273989
+ * Set a callback to be called when a new notification is created.
273990
+ * The WebChannel uses this to broadcast to all connected clients.
273991
+ */
273992
+ function setBroadcastCallback(callback) {
273993
+ broadcastCallback = callback;
273994
+ }
273995
+ /**
273996
+ * Create a new notification.
273997
+ * If dedupKey is provided and matches an existing unread notification,
273998
+ * the existing notification is updated instead of creating a new one.
273999
+ */
274000
+ function createNotification(input) {
274001
+ const duplicate = findDuplicate(input);
274002
+ if (duplicate) {
274003
+ duplicate.message = input.message;
274004
+ duplicate.timestamp = (/* @__PURE__ */ new Date()).toISOString();
274005
+ duplicate.title = input.title;
274006
+ duplicate.type = input.type;
274007
+ duplicate.metadata = {
274008
+ ...duplicate.metadata,
274009
+ ...input.metadata
274010
+ };
274011
+ queuePersist();
274012
+ if (broadcastCallback) broadcastCallback(duplicate);
274013
+ return duplicate;
274014
+ }
274015
+ const notification = {
274016
+ id: randomUUID(),
274017
+ type: input.type,
274018
+ source: input.source,
274019
+ title: input.title,
274020
+ message: input.message,
274021
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
274022
+ read: false,
274023
+ dismissed: false,
274024
+ sessionId: input.sessionId,
274025
+ actionUrl: input.actionUrl,
274026
+ metadata: input.metadata
274027
+ };
274028
+ notifications.push(notification);
274029
+ pruneNotifications();
274030
+ queuePersist();
274031
+ if (broadcastCallback) broadcastCallback(notification);
274032
+ return notification;
274033
+ }
274034
+ /**
274035
+ * Get notifications matching the filter criteria.
274036
+ */
274037
+ function getNotifications(filter = {}) {
274038
+ const { includeRead = true, includeDismissed = false, source, limit = 100 } = filter;
274039
+ let result = notifications;
274040
+ if (!includeDismissed) result = result.filter((n) => !n.dismissed);
274041
+ if (!includeRead) result = result.filter((n) => !n.read);
274042
+ if (source) result = result.filter((n) => n.source === source);
274043
+ result = result.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
274044
+ return result.slice(0, limit);
274045
+ }
274046
+ /**
274047
+ * Mark a notification as read.
274048
+ */
274049
+ function markRead(id) {
274050
+ const notification = notifications.find((n) => n.id === id);
274051
+ if (notification && !notification.read) {
274052
+ notification.read = true;
274053
+ queuePersist();
274054
+ }
274055
+ return notification;
274056
+ }
274057
+ /**
274058
+ * Mark all notifications as read.
274059
+ */
274060
+ function markAllRead() {
274061
+ let count = 0;
274062
+ for (const notification of notifications) if (!notification.read && !notification.dismissed) {
274063
+ notification.read = true;
274064
+ count++;
274065
+ }
274066
+ if (count > 0) queuePersist();
274067
+ return count;
274068
+ }
274069
+ /**
274070
+ * Dismiss a notification (soft delete - keeps in storage but hidden from UI).
274071
+ */
274072
+ function dismissNotification(id) {
274073
+ const notification = notifications.find((n) => n.id === id);
274074
+ if (notification && !notification.dismissed) {
274075
+ notification.dismissed = true;
274076
+ queuePersist();
274077
+ }
274078
+ return notification;
274079
+ }
274080
+ /**
274081
+ * Dismiss all notifications.
274082
+ */
274083
+ function dismissAll() {
274084
+ let count = 0;
274085
+ for (const notification of notifications) if (!notification.dismissed) {
274086
+ notification.dismissed = true;
274087
+ count++;
274088
+ }
274089
+ if (count > 0) queuePersist();
274090
+ return count;
274091
+ }
274092
+ //#endregion
273902
274093
  //#region src/extensions/cron/lock.ts
273903
274094
  const LOCK_PATH$1 = join(getAppDir(), "cron", "cron.lock");
273904
274095
  function isProcessAlive$1(pid) {
@@ -274116,7 +274307,36 @@ var CronScheduler = class {
274116
274307
  this.persist();
274117
274308
  } catch (err) {
274118
274309
  job.consecutiveFailures += 1;
274119
- if (job.consecutiveFailures >= 5) job.enabled = false;
274310
+ const errorMessage = err instanceof Error ? err.message : String(err);
274311
+ createNotification({
274312
+ type: "error",
274313
+ source: "cron",
274314
+ title: `Cron job failed: ${job.name}`,
274315
+ message: errorMessage,
274316
+ dedupKey: `cron-fail-${job.jobId}`,
274317
+ metadata: {
274318
+ dedupKey: `cron-fail-${job.jobId}`,
274319
+ jobId: job.jobId,
274320
+ jobName: job.name,
274321
+ consecutiveFailures: job.consecutiveFailures,
274322
+ error: errorMessage
274323
+ }
274324
+ });
274325
+ if (job.consecutiveFailures >= 5) {
274326
+ job.enabled = false;
274327
+ createNotification({
274328
+ type: "warning",
274329
+ source: "cron",
274330
+ title: `Cron job disabled: ${job.name}`,
274331
+ message: `Job "${job.name}" was automatically disabled after 5 consecutive failures.`,
274332
+ metadata: {
274333
+ jobId: job.jobId,
274334
+ jobName: job.name,
274335
+ consecutiveFailures: job.consecutiveFailures,
274336
+ autoDisabled: true
274337
+ }
274338
+ });
274339
+ }
274120
274340
  this.persist();
274121
274341
  console.error(`[cron] Job failed (${job.name}):`, err);
274122
274342
  } finally {
@@ -274598,7 +274818,23 @@ var HeartbeatRunner = class {
274598
274818
  this.stats.lastResult = result;
274599
274819
  this.stats.runCount += 1;
274600
274820
  if (result.ok) this.stats.okCount += 1;
274601
- else this.stats.alertCount += 1;
274821
+ else {
274822
+ this.stats.alertCount += 1;
274823
+ const message = result.error ? `Error: ${result.error}` : result.normalizedResponse || "Heartbeat check failed";
274824
+ createNotification({
274825
+ type: result.error ? "error" : "warning",
274826
+ source: "heartbeat",
274827
+ title: "Heartbeat Alert",
274828
+ message,
274829
+ dedupKey: `heartbeat-${this.cwd}`,
274830
+ metadata: {
274831
+ dedupKey: `heartbeat-${this.cwd}`,
274832
+ ok: result.ok,
274833
+ error: result.error,
274834
+ timestamp: result.timestamp
274835
+ }
274836
+ });
274837
+ }
274602
274838
  }
274603
274839
  async runHeartbeatPrompt(prompt) {
274604
274840
  const config = loadConfig();
@@ -274923,6 +275159,75 @@ function createReloadRuntimeExtension(reloadAllSessions) {
274923
275159
  };
274924
275160
  }
274925
275161
  //#endregion
275162
+ //#region src/extensions/web-notifications.ts
275163
+ const NotifyWebUIParamsSchema = Type$1.Object({
275164
+ title: Type$1.String({
275165
+ description: "Short notification title (max 100 characters)",
275166
+ maxLength: 100
275167
+ }),
275168
+ message: Type$1.String({ description: "Detailed notification message" }),
275169
+ type: StringEnum([
275170
+ "info",
275171
+ "warning",
275172
+ "error",
275173
+ "success"
275174
+ ]),
275175
+ actionUrl: Type$1.Optional(Type$1.String({ description: "Optional URL path (e.g., /sessions/abc123) to link to when user clicks the notification" }))
275176
+ });
275177
+ /**
275178
+ * Create the web notifications extension factory.
275179
+ *
275180
+ * WEB UI CHANNEL ONLY: This extension provides notifications exclusively
275181
+ * for the web UI channel. Do not use for other channels.
275182
+ */
275183
+ function createWebNotificationsExtension() {
275184
+ return function webNotificationsExtension(pi) {
275185
+ pi.registerTool({
275186
+ name: "notify_web_ui",
275187
+ label: "Notify Web UI",
275188
+ description: "[WEB UI CHANNEL ONLY] Send a notification to the user through the web UI. This creates a persistent notification that appears in the web UI notifications panel and triggers a real-time toast alert. Use this to inform users about important events, task completions, errors, or status updates when they may not be actively viewing the session. IMPORTANT: This tool is exclusive to the web UI channel and has no effect on other channels.",
275189
+ parameters: NotifyWebUIParamsSchema,
275190
+ async execute(_toolCallId, rawParams) {
275191
+ const params = rawParams;
275192
+ try {
275193
+ const notification = createNotification({
275194
+ type: params.type,
275195
+ source: "session",
275196
+ title: params.title,
275197
+ message: params.message,
275198
+ actionUrl: params.actionUrl
275199
+ });
275200
+ return {
275201
+ content: [{
275202
+ type: "text",
275203
+ text: `Web UI notification sent: "${params.title}"`
275204
+ }],
275205
+ details: {
275206
+ success: true,
275207
+ notificationId: notification.id,
275208
+ timestamp: notification.timestamp
275209
+ }
275210
+ };
275211
+ } catch (err) {
275212
+ return {
275213
+ content: [{
275214
+ type: "text",
275215
+ text: `Failed to send web UI notification: ${err instanceof Error ? err.message : String(err)}`
275216
+ }],
275217
+ details: {
275218
+ success: false,
275219
+ error: err instanceof Error ? err.message : String(err)
275220
+ }
275221
+ };
275222
+ }
275223
+ }
275224
+ });
275225
+ pi.on("before_agent_start", async (event) => {
275226
+ return { systemPrompt: event.systemPrompt + "\n\n[Web UI Channel] You can send notifications to the web UI using the notify_web_ui tool. This is useful for: task completions, errors requiring attention, long-running operations finishing, or critical status updates. Notifications appear in the user's web UI notification panel, trigger toast alerts, and persist across page refreshes. Note: This tool is specific to the web UI channel." };
275227
+ });
275228
+ };
275229
+ }
275230
+ //#endregion
274926
275231
  //#region src/lib/scoped-model-resolver.ts
274927
275232
  const THINKING_LEVEL_SUFFIXES = new Set([
274928
275233
  "off",
@@ -275263,6 +275568,7 @@ function buildExtensionFactories(config, cwd) {
275263
275568
  const extensionFactories = [];
275264
275569
  extensionFactories.push(createCronExtension());
275265
275570
  extensionFactories.push(createHeartbeatExtension());
275571
+ extensionFactories.push(createWebNotificationsExtension());
275266
275572
  extensionFactories.push(createPackageManagerExtension(reloadAllSessions));
275267
275573
  extensionFactories.push(createReloadRuntimeExtension(reloadAllSessions));
275268
275574
  if (config.agent?.restrictToWorkspace ?? true) {
@@ -278107,8 +278413,12 @@ var WebChannel = class {
278107
278413
  pendingExtensionRequests = /* @__PURE__ */ new Map();
278108
278414
  /** Pending auth login flows: Map<loginFlowId, { ws, inputResolver, abortController }> */
278109
278415
  pendingLoginFlows = /* @__PURE__ */ new Map();
278416
+ /** All connected WebSocket clients (for broadcasting) */
278417
+ allConnections = /* @__PURE__ */ new Set();
278110
278418
  /** Heartbeat interval for keeping WebSocket connections alive */
278111
278419
  heartbeatInterval = null;
278420
+ /** Notification broadcast callback cleanup function */
278421
+ notificationCleanup = null;
278112
278422
  constructor(options) {
278113
278423
  this.options = options;
278114
278424
  }
@@ -278131,8 +278441,9 @@ var WebChannel = class {
278131
278441
  if (!origin || !this.options.allowedOrigins.includes(origin)) return c.text("Forbidden", 403);
278132
278442
  }
278133
278443
  return {
278134
- onOpen: (_evt, _ws) => {
278444
+ onOpen: (_evt, ws) => {
278135
278445
  console.log("[web] Client connected");
278446
+ this.allConnections.add(ws);
278136
278447
  },
278137
278448
  onMessage: async (evt, ws) => {
278138
278449
  try {
@@ -278151,6 +278462,7 @@ var WebChannel = class {
278151
278462
  },
278152
278463
  onClose: (_evt, ws) => {
278153
278464
  console.log("[web] Client disconnected");
278465
+ this.allConnections.delete(ws);
278154
278466
  for (const [sessionId, subscribers] of this.sessionSubscriptions.entries()) {
278155
278467
  subscribers.delete(ws);
278156
278468
  if (subscribers.size === 0) this.sessionSubscriptions.delete(sessionId);
@@ -278272,6 +278584,23 @@ var WebChannel = class {
278272
278584
  }, HEARTBEAT_INTERVAL);
278273
278585
  console.log(`[web] WebSocket server listening on port ${this.options.port}`);
278274
278586
  console.log(`[web] Open in browser: http://localhost:${this.options.port}?token=${this.options.authToken}`);
278587
+ const broadcastToAll = (notification) => {
278588
+ const message = {
278589
+ sessionId: "_notifications",
278590
+ event: {
278591
+ type: "notification",
278592
+ notification
278593
+ }
278594
+ };
278595
+ const json = JSON.stringify(message);
278596
+ for (const ws of this.allConnections) try {
278597
+ ws.send(json);
278598
+ } catch (err) {
278599
+ console.error("[web] Failed to broadcast notification:", err);
278600
+ }
278601
+ };
278602
+ setBroadcastCallback(broadcastToAll);
278603
+ this.notificationCleanup = () => setBroadcastCallback(null);
278275
278604
  }
278276
278605
  async send(_chatId, _text, _options) {}
278277
278606
  async stop() {
@@ -278279,6 +278608,11 @@ var WebChannel = class {
278279
278608
  clearInterval(this.heartbeatInterval);
278280
278609
  this.heartbeatInterval = null;
278281
278610
  }
278611
+ if (this.notificationCleanup) {
278612
+ this.notificationCleanup();
278613
+ this.notificationCleanup = null;
278614
+ }
278615
+ this.allConnections.clear();
278282
278616
  if (this.wss) {
278283
278617
  for (const client of this.wss.clients) try {
278284
278618
  client.close(1001, "Server shutting down");
@@ -278343,6 +278677,61 @@ var WebChannel = class {
278343
278677
  });
278344
278678
  return;
278345
278679
  }
278680
+ if (command.type === "get_notifications") {
278681
+ const notifications = getNotifications();
278682
+ this.sendResponse(ws, void 0, {
278683
+ id: commandId,
278684
+ type: "response",
278685
+ command: "get_notifications",
278686
+ success: true,
278687
+ data: { notifications }
278688
+ });
278689
+ return;
278690
+ }
278691
+ if (command.type === "mark_notification_read") {
278692
+ const notification = markRead(command.notificationId);
278693
+ this.sendResponse(ws, void 0, {
278694
+ id: commandId,
278695
+ type: "response",
278696
+ command: "mark_notification_read",
278697
+ success: true,
278698
+ data: { notification }
278699
+ });
278700
+ return;
278701
+ }
278702
+ if (command.type === "mark_all_notifications_read") {
278703
+ const count = markAllRead();
278704
+ this.sendResponse(ws, void 0, {
278705
+ id: commandId,
278706
+ type: "response",
278707
+ command: "mark_all_notifications_read",
278708
+ success: true,
278709
+ data: { count }
278710
+ });
278711
+ return;
278712
+ }
278713
+ if (command.type === "dismiss_notification") {
278714
+ const notification = dismissNotification(command.notificationId);
278715
+ this.sendResponse(ws, void 0, {
278716
+ id: commandId,
278717
+ type: "response",
278718
+ command: "dismiss_notification",
278719
+ success: true,
278720
+ data: { notification }
278721
+ });
278722
+ return;
278723
+ }
278724
+ if (command.type === "dismiss_all_notifications") {
278725
+ const count = dismissAll();
278726
+ this.sendResponse(ws, void 0, {
278727
+ id: commandId,
278728
+ type: "response",
278729
+ command: "dismiss_all_notifications",
278730
+ success: true,
278731
+ data: { count }
278732
+ });
278733
+ return;
278734
+ }
278346
278735
  if (!inbound.sessionId) {
278347
278736
  this.sendError(ws, void 0, command.type, "sessionId is required", commandId);
278348
278737
  return;
@@ -279490,6 +279879,7 @@ async function restartChannels() {
279490
279879
  async function startDaemon() {
279491
279880
  writePidFile();
279492
279881
  console.log(`[daemon] Starting clankie daemon (pid ${process.pid})...`);
279882
+ initNotifications();
279493
279883
  await initializeChannels();
279494
279884
  const configPath = getConfigPath();
279495
279885
  const cronJobsPath = getCronJobsPath();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clankie",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "A minimal personal AI assistant built on pi's SDK",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,2 +1,2 @@
1
- <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>clankie — Personal AI Assistant</title><link rel="modulepreload" href="/assets/main-CP6prmzV.js"/><link rel="modulepreload" href="/assets/index-TBNB5eLy.js"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/><link rel="stylesheet" href="/assets/styles-KEhqa3CU.css"/></head><body><div data-slot="sidebar-wrapper" style="--sidebar-width:16rem;--sidebar-width-icon:3rem" class="group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full"><div class="group peer text-sidebar-foreground hidden md:block" data-state="expanded" data-collapsible="" data-variant="inset" data-side="left" data-slot="sidebar"><div data-slot="sidebar-gap" class="transition-[width] duration-200 ease-linear relative w-(--sidebar-width) bg-transparent group-data-[collapsible=offcanvas]:w-0 group-data-[side=right]:rotate-180 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"></div><div data-slot="sidebar-container" data-side="left" class="fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)] border-r border-border/20"><div data-sidebar="sidebar" data-slot="sidebar-inner" class="bg-sidebar group-data-[variant=floating]:ring-sidebar-border group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 flex size-full flex-col"><div data-slot="sidebar-header" data-sidebar="header" class="p-2 flex flex-col gap-3 px-4 py-4"><ul data-slot="sidebar-menu" data-sidebar="menu" class="gap-0 flex w-full min-w-0 flex-col"><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><button type="button" data-slot="sidebar-menu-button" data-sidebar="menu-button" data-size="lg" class="ring-sidebar-ring active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&amp;&gt;span:last-child]:truncate [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground text-sm group-data-[collapsible=icon]:p-0! h-10"><div class="flex items-center gap-3"><div class="flex items-center justify-center w-8 h-8 rounded-xl bg-primary/15 border border-primary/20"><span class="text-sm font-mono font-bold text-primary">c/</span></div><span class="text-lg font-mono font-semibold tracking-tight text-sidebar-foreground">clankie</span></div></button></li></ul><ul data-slot="sidebar-menu" data-sidebar="menu" class="gap-0 flex w-full min-w-0 flex-col"><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><button type="button" id="base-ui-_R_1phb6_" data-slot="sidebar-menu-button" data-sidebar="menu-button" data-size="default" class="ring-sidebar-ring data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 p-2 text-left group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&amp;&gt;span:last-child]:truncate [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 h-10 text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground transition-all rounded-xl"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-plus h-4 w-4 mr-1" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M8 12h8"></path><path d="M12 8v8"></path></svg><span>New Chat</span></button></li></ul></div><div data-slot="sidebar-content" data-sidebar="content" class="no-scrollbar flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden gap-2 px-2 py-2"><div data-slot="sidebar-group" data-sidebar="group" class="p-2 relative flex w-full min-w-0 flex-col group-data-[collapsible=icon]:hidden py-2"><div data-slot="sidebar-group-label" data-sidebar="group-label" class="ring-sidebar-ring h-8 rounded-md transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&amp;&gt;svg]:size-4 flex shrink-0 items-center outline-hidden [&amp;&gt;svg]:shrink-0 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/40 px-3 py-2">Recent</div><ul data-slot="sidebar-menu" data-sidebar="menu" class="flex w-full min-w-0 flex-col gap-1 px-1"><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><button type="button" data-slot="sidebar-menu-button" data-sidebar="menu-button" data-size="default" class="ring-sidebar-ring active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&amp;&gt;span:last-child]:truncate [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground text-sm h-12 opacity-40" disabled=""><span class="text-sm text-sidebar-foreground/40">No sessions yet</span></button></li></ul></div></div><div data-slot="sidebar-footer" data-sidebar="footer" class="gap-2 p-2 flex flex-col px-2 py-2"><ul data-slot="sidebar-menu" data-sidebar="menu" class="gap-0 flex w-full min-w-0 flex-col"><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><button type="button" data-slot="dropdown-menu-trigger" data-sidebar="menu-button" data-size="default" class="ring-sidebar-ring active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 p-2 text-left transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&amp;&gt;span:last-child]:truncate [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 hover:bg-sidebar-accent h-10 text-sm text-sidebar-foreground/70 hover:text-sidebar-foreground rounded-xl" tabindex="0" aria-haspopup="menu" id="base-ui-_R_fhb6_"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-settings h-4 w-4" aria-hidden="true"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"></path><circle cx="12" cy="12" r="3"></circle></svg><span>Settings</span></button></li></ul></div></div></div></div><main data-slot="sidebar-inset" class="bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 flex w-full flex-1 flex-col relative min-h-0"><header class="topbar-glass sticky top-0 z-40 flex h-14 shrink-0 items-center border-b px-4 md:hidden"><button type="button" tabindex="0" data-slot="sidebar-trigger" data-sidebar="trigger" class="focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 [&amp;_svg:not([class*=&#x27;size-&#x27;])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none shrink-0 [&amp;_svg]:shrink-0 outline-none group/button select-none hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-panel-left" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M9 3v18"></path></svg><span class="sr-only">Toggle Sidebar</span></button></header><div class="min-h-0 flex-1"><!--$--><!--$--><!--/$--><script></script><!--/$--></div></main></div><section aria-label="Notifications alt+T" tabindex="-1" aria-live="polite" aria-relevant="additions text" aria-atomic="false"></section><script class="$tsr" id="$tsr-stream-barrier">(self.$R=self.$R||{})["tsr"]=[];self.$_TSR={h(){this.hydrated=!0,this.c()},e(){this.streamEnded=!0,this.c()},c(){this.hydrated&&this.streamEnded&&(delete self.$_TSR,delete self.$R.tsr)},p(e){this.initialized?e():this.buffer.push(e)},buffer:[]};
2
- ;$_TSR.router=($R=>$R[0]={manifest:$R[1]={routes:$R[2]={__root__:$R[3]={preloads:$R[4]=["/assets/main-CP6prmzV.js"],assets:$R[5]=[$R[6]={tag:"script",attrs:$R[7]={type:"module",async:!0},children:"import(\"/assets/main-CP6prmzV.js\")"}]}}},matches:$R[8]=[$R[9]={i:"__root__",u:1772808407662,s:"success",ssr:!0}],lastMatchId:"__root__"})($R["tsr"]);$_TSR.e();document.currentScript.remove()</script><script type="module" async="">import("/assets/main-CP6prmzV.js")</script></body></html>
1
+ <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>clankie — Personal AI Assistant</title><link rel="modulepreload" href="/assets/main-CKIteeQH.js"/><link rel="modulepreload" href="/assets/index-DTLVg2XM.js"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/><link rel="stylesheet" href="/assets/styles-CuRG0ztT.css"/></head><body><div data-slot="sidebar-wrapper" style="--sidebar-width:16rem;--sidebar-width-icon:3rem" class="group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full"><div class="group peer text-sidebar-foreground hidden md:block" data-state="expanded" data-collapsible="" data-variant="inset" data-side="left" data-slot="sidebar"><div data-slot="sidebar-gap" class="transition-[width] duration-200 ease-linear relative w-(--sidebar-width) bg-transparent group-data-[collapsible=offcanvas]:w-0 group-data-[side=right]:rotate-180 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"></div><div data-slot="sidebar-container" data-side="left" class="fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)] border-r border-border/20"><div data-sidebar="sidebar" data-slot="sidebar-inner" class="bg-sidebar group-data-[variant=floating]:ring-sidebar-border group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 flex size-full flex-col"><div data-slot="sidebar-header" data-sidebar="header" class="p-2 flex flex-col gap-3 px-4 py-4"><ul data-slot="sidebar-menu" data-sidebar="menu" class="gap-0 flex w-full min-w-0 flex-col"><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><button type="button" data-slot="sidebar-menu-button" data-sidebar="menu-button" data-size="lg" class="ring-sidebar-ring active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&amp;&gt;span:last-child]:truncate [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground text-sm group-data-[collapsible=icon]:p-0! h-10"><div class="flex items-center gap-3"><div class="flex items-center justify-center w-8 h-8 rounded-xl bg-primary/15 border border-primary/20"><span class="text-sm font-mono font-bold text-primary">c/</span></div><span class="text-lg font-mono font-semibold tracking-tight text-sidebar-foreground">clankie</span></div></button></li></ul><ul data-slot="sidebar-menu" data-sidebar="menu" class="gap-0 flex w-full min-w-0 flex-col"><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><button type="button" id="base-ui-_R_1phb6_" data-slot="sidebar-menu-button" data-sidebar="menu-button" data-size="default" class="ring-sidebar-ring data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 p-2 text-left group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&amp;&gt;span:last-child]:truncate [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 h-10 text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground transition-all rounded-xl"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-plus h-4 w-4 mr-1" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M8 12h8"></path><path d="M12 8v8"></path></svg><span>New Chat</span></button></li></ul></div><div data-slot="sidebar-content" data-sidebar="content" class="no-scrollbar flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden gap-2 px-2 py-2"><div data-slot="sidebar-group" data-sidebar="group" class="p-2 relative flex w-full min-w-0 flex-col group-data-[collapsible=icon]:hidden py-2"><div data-slot="sidebar-group-label" data-sidebar="group-label" class="ring-sidebar-ring h-8 rounded-md transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&amp;&gt;svg]:size-4 flex shrink-0 items-center outline-hidden [&amp;&gt;svg]:shrink-0 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/40 px-3 py-2">Recent</div><ul data-slot="sidebar-menu" data-sidebar="menu" class="flex w-full min-w-0 flex-col gap-1 px-1"><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><button type="button" data-slot="sidebar-menu-button" data-sidebar="menu-button" data-size="default" class="ring-sidebar-ring active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&amp;&gt;span:last-child]:truncate [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground text-sm h-12 opacity-40" disabled=""><span class="text-sm text-sidebar-foreground/40">No sessions yet</span></button></li></ul></div></div><div data-slot="sidebar-footer" data-sidebar="footer" class="gap-2 p-2 flex flex-col px-2 py-2"><ul data-slot="sidebar-menu" data-sidebar="menu" class="gap-0 flex w-full min-w-0 flex-col"><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><a data-slot="sidebar-menu-button" data-sidebar="menu-button" data-size="default" href="/notifications" class="ring-sidebar-ring active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 p-2 text-left group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&amp;&gt;span:last-child]:truncate [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 hover:bg-sidebar-accent h-10 text-sm rounded-xl transition-colors text-sidebar-foreground/70 hover:text-sidebar-foreground"><div class="relative flex items-center justify-center w-4"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell h-4 w-4" aria-hidden="true"><path d="M10.268 21a2 2 0 0 0 3.464 0"></path><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"></path></svg></div><span>Notifications</span></a></li><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><button type="button" data-slot="dropdown-menu-trigger" data-sidebar="menu-button" data-size="default" class="ring-sidebar-ring active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 p-2 text-left transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button flex w-full items-center overflow-hidden outline-hidden group/menu-button disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&amp;&gt;span:last-child]:truncate [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 hover:bg-sidebar-accent h-10 text-sm text-sidebar-foreground/70 hover:text-sidebar-foreground rounded-xl" tabindex="0" aria-haspopup="menu" id="base-ui-_R_1rhb6_"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-settings h-4 w-4" aria-hidden="true"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"></path><circle cx="12" cy="12" r="3"></circle></svg><span>Settings</span></button></li></ul></div></div></div></div><main data-slot="sidebar-inset" class="bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 flex w-full flex-1 flex-col relative min-h-0"><header class="topbar-glass sticky top-0 z-40 flex h-14 shrink-0 items-center border-b px-4 md:hidden"><button type="button" tabindex="0" data-slot="sidebar-trigger" data-sidebar="trigger" class="focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 [&amp;_svg:not([class*=&#x27;size-&#x27;])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none shrink-0 [&amp;_svg]:shrink-0 outline-none group/button select-none hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-panel-left" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M9 3v18"></path></svg><span class="sr-only">Toggle Sidebar</span></button></header><div class="min-h-0 flex-1"><!--$--><!--$--><!--/$--><script></script><!--/$--></div></main></div><section aria-label="Notifications alt+T" tabindex="-1" aria-live="polite" aria-relevant="additions text" aria-atomic="false"></section><script class="$tsr" id="$tsr-stream-barrier">(self.$R=self.$R||{})["tsr"]=[];self.$_TSR={h(){this.hydrated=!0,this.c()},e(){this.streamEnded=!0,this.c()},c(){this.hydrated&&this.streamEnded&&(delete self.$_TSR,delete self.$R.tsr)},p(e){this.initialized?e():this.buffer.push(e)},buffer:[]};
2
+ ;$_TSR.router=($R=>$R[0]={manifest:$R[1]={routes:$R[2]={__root__:$R[3]={preloads:$R[4]=["/assets/main-CKIteeQH.js"],assets:$R[5]=[$R[6]={tag:"script",attrs:$R[7]={type:"module",async:!0},children:"import(\"/assets/main-CKIteeQH.js\")"}]}}},matches:$R[8]=[$R[9]={i:"__root__",u:1772820806652,s:"success",ssr:!0}],lastMatchId:"__root__"})($R["tsr"]);$_TSR.e();document.currentScript.remove()</script><script type="module" async="">import("/assets/main-CKIteeQH.js")</script></body></html>
@@ -0,0 +1 @@
1
+ import{c as b,V as K,W as R,Y as O,Z as E,r as u,j as e,_ as T,$ as B,i as p,a0 as V,a1 as $,B as x,a2 as q,a3 as H,a4 as U,u as A,a5 as S,a6 as Y,d as j,J as y,Q as k,q as W,a7 as J,a8 as w,s as Q,a9 as X,h as D,x as P,y as F,H as Z,k as L,K as G,aa as ee,ab as te}from"./main-CKIteeQH.js";import{F as I,a as z}from"./field-Dy_c96vc.js";import{C as M}from"./circle-x-Bek90N0j.js";import{B as se}from"./badge-B27MkVic.js";const ae=[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]],ne=b("circle-check-big",ae);const re=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],ie=b("external-link",re);const oe=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]],_=b("shield",oe);function le(t){const{children:a,open:s,defaultOpen:l=!1,onOpenChange:r,onOpenChangeComplete:i,actionsRef:m,handle:f,triggerId:g,defaultTriggerId:h=null}=t,v=K(),N=!!v,c=R(()=>f?.store??new O({open:l,openProp:s,activeTriggerId:h,triggerIdProp:g,modal:!0,disablePointerDismissal:!0,nested:N,role:"alertdialog"})).current;c.useControlledProp("openProp",s),c.useControlledProp("triggerIdProp",g),c.useSyncedValue("nested",N),c.useContextCallback("onOpenChange",r),c.useContextCallback("onOpenChangeComplete",i);const C=c.useState("payload");E({store:c,actionsRef:m,parentContext:v?.store.context});const n=u.useMemo(()=>({store:c}),[c]);return e.jsx(T.Provider,{value:n,children:typeof a=="function"?a({payload:C}):a})}function ce({...t}){return e.jsx(le,{"data-slot":"alert-dialog",...t})}function de({...t}){return e.jsx(H,{"data-slot":"alert-dialog-portal",...t})}function ue({className:t,...a}){return e.jsx(U,{"data-slot":"alert-dialog-overlay",className:p("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50",t),...a})}function me({className:t,size:a="default",...s}){return e.jsxs(de,{children:[e.jsx(ue,{}),e.jsx(B,{"data-slot":"alert-dialog-content","data-size":a,className:p("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-4 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",t),...s})]})}function xe({className:t,...a}){return e.jsx("div",{"data-slot":"alert-dialog-header",className:p("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",t),...a})}function ge({className:t,...a}){return e.jsx("div",{"data-slot":"alert-dialog-footer",className:p("bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",t),...a})}function he({className:t,...a}){return e.jsx(V,{"data-slot":"alert-dialog-title",className:p("text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",t),...a})}function pe({className:t,...a}){return e.jsx($,{"data-slot":"alert-dialog-description",className:p("text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3",t),...a})}function fe({className:t,...a}){return e.jsx(x,{"data-slot":"alert-dialog-action",className:p(t),...a})}function je({className:t,variant:a="outline",size:s="default",...l}){return e.jsx(q,{"data-slot":"alert-dialog-cancel",className:p(t),render:e.jsx(x,{variant:a,size:s}),...l})}function ye({open:t,onOpenChange:a}){const{loginFlow:s}=A(S,i=>({loginFlow:i.loginFlow}));if(u.useEffect(()=>{!t&&s&&Y()},[t,s]),u.useEffect(()=>{if(t&&s?.status==="complete"&&s.success===!0){const i=setTimeout(()=>{a(!1)},1500);return()=>clearTimeout(i)}},[t,s,a]),!s)return null;const l=()=>{s.loginFlowId&&s.status!=="complete"&&s.status!=="error"&&j.getClient()?.authLoginCancel(s.loginFlowId),a(!1)},r=()=>{a(!1)};return e.jsx(ce,{open:t,onOpenChange:a,children:e.jsxs(me,{children:[e.jsxs(xe,{children:[e.jsx(he,{children:s.status==="complete"&&s.success?"Login Successful":s.status==="error"?"Login Failed":`Sign in to ${s.providerId}`}),e.jsx(pe,{children:e.jsx(ve,{flow:s})})]}),e.jsx(ge,{children:s.status==="complete"||s.status==="error"?e.jsx(fe,{onClick:r,children:"Close"}):e.jsx(je,{onClick:l,children:"Cancel"})})]})})}function ve({flow:t}){const a=j.getClient(),s=u.useRef(null),l=i=>{t.loginFlowId&&a&&a.authLoginInput(t.loginFlowId,i)},r=i=>{t.loginFlowId&&a&&a.authLoginInput(t.loginFlowId,i)};return u.useEffect(()=>{t.status==="waiting_url"&&t.url&&s.current!==t.url&&(s.current=t.url,window.open(t.url,"_blank"))},[t.status,t.url]),t.status==="idle"?e.jsxs("div",{className:"flex items-center gap-3 py-4",children:[e.jsx(y,{className:"h-5 w-5 animate-spin text-muted-foreground"}),e.jsx("span",{className:"text-sm",children:"Starting login..."})]}):t.status==="waiting_url"&&t.url?e.jsxs("div",{className:"space-y-4 py-4",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(y,{className:"h-5 w-5 animate-spin text-muted-foreground"}),e.jsx("span",{className:"text-sm",children:"Complete the authentication in your browser..."})]}),t.instructions&&e.jsx("p",{className:"text-xs text-muted-foreground rounded-md bg-muted p-2",children:t.instructions}),e.jsxs(x,{onClick:()=>window.open(t.url,"_blank"),className:"w-full",variant:"outline",size:"sm",children:[e.jsx(ie,{className:"mr-2 h-4 w-4"}),"Open in Browser"]}),t.showManualInput&&e.jsx(Ne,{onSubmit:l}),t.progressMessage&&e.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground",children:[e.jsx(y,{className:"h-3 w-3 animate-spin"}),e.jsx("span",{children:t.progressMessage})]})]}):t.status==="waiting_input"&&t.promptMessage?e.jsx("div",{className:"py-4",children:e.jsx(Ce,{message:t.promptMessage,placeholder:t.promptPlaceholder,onSubmit:r})}):t.status==="in_progress"?e.jsxs("div",{className:"flex items-center gap-3 py-4",children:[e.jsx(y,{className:"h-5 w-5 animate-spin text-muted-foreground"}),e.jsx("span",{className:"text-sm",children:t.progressMessage||"Completing authentication..."})]}):t.status==="complete"&&t.success?e.jsxs("div",{className:"flex items-center gap-3 py-4 text-green-600",children:[e.jsx(ne,{className:"h-5 w-5"}),e.jsxs("span",{className:"text-sm",children:["Successfully authenticated with ",t.providerId]})]}):t.status==="error"||t.status==="complete"&&!t.success?e.jsxs("div",{className:"space-y-2 py-4",children:[e.jsxs("div",{className:"flex items-center gap-3 text-destructive",children:[e.jsx(M,{className:"h-5 w-5"}),e.jsx("span",{className:"text-sm font-medium",children:"Authentication failed"})]}),t.error&&e.jsx("p",{className:"text-xs text-muted-foreground rounded-md bg-muted p-2",children:t.error})]}):null}function Ne({onSubmit:t}){const a=s=>{s.preventDefault();const r=new FormData(s.currentTarget).get("code");r?.trim()&&t(r.trim())};return e.jsx("form",{onSubmit:a,className:"space-y-2",children:e.jsxs(I,{children:[e.jsx(z,{htmlFor:"manual-code",children:"Or paste the authorization code/URL here:"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(k,{id:"manual-code",name:"code",type:"text",placeholder:"Paste code or redirect URL",className:"flex-1"}),e.jsx(x,{type:"submit",size:"sm",children:"Submit"})]})]})})}function Ce({message:t,placeholder:a,onSubmit:s}){const l=r=>{r.preventDefault();const m=new FormData(r.currentTarget).get("prompt-value");m?.trim()&&s(m.trim())};return e.jsxs("form",{onSubmit:l,className:"space-y-3",children:[e.jsx("p",{className:"text-sm",children:t}),e.jsx(I,{children:e.jsxs("div",{className:"flex gap-2",children:[e.jsx(k,{id:"prompt-value",name:"prompt-value",type:"text",placeholder:a||"Enter value",className:"flex-1",autoFocus:!0}),e.jsx(x,{type:"submit",size:"sm",children:"Submit"})]})})]})}function Pe(){const{status:t}=A(W,s=>({status:s.status}));return t==="connected"?e.jsx(be,{}):e.jsx("div",{className:"h-full flex items-center justify-center chat-background",children:e.jsxs("div",{className:"text-center space-y-4 max-w-md p-8",children:[e.jsx("div",{className:"inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-destructive/10 border border-destructive/20 mb-2",children:e.jsx(_,{className:"h-8 w-8 text-destructive"})}),e.jsxs("div",{className:"space-y-2",children:[e.jsx("h2",{className:"text-2xl font-semibold",children:"Not Connected"}),e.jsx("p",{className:"text-muted-foreground",children:"Connect to clankie to manage AI provider authentication"})]})]})})}function be(){const{providers:t,isLoadingProviders:a,loginFlow:s}=A(S,n=>({providers:n.providers,isLoadingProviders:n.isLoadingProviders,loginFlow:n.loginFlow})),[l,r]=u.useState(!1),[i,m]=u.useState(null),[f,g]=u.useState(""),h=u.useCallback(async()=>{const n=j.getClient();if(n){J(!0);try{const{providers:o}=await n.getAuthProviders();w(o)}catch(o){console.error("Failed to load auth providers:",o),w([])}}},[]);u.useEffect(()=>{h()},[h]),u.useEffect(()=>{if(s?.status==="complete"&&s.success===!0){h();const{activeSessionId:n}=Q.state;if(n){const o=j.getClient();o&&o.getAvailableModels(n).then(({models:d})=>{X(d),console.log("[settings/auth] Refreshed available models after OAuth login")}).catch(d=>{console.error("[settings/auth] Failed to refresh available models:",d)})}}},[s?.status,s?.success,h]);const v=async n=>{const o=j.getClient();if(o)try{const{loginFlowId:d}=await o.authLogin(n);te(d,n),r(!0)}catch(d){console.error("Failed to start login:",d)}},N=n=>{m(n),g("")},c=async n=>{const o=j.getClient();if(!(!o||!f.trim()))try{await o.authSetApiKey(n,f.trim()),m(null),g(""),await h()}catch(d){console.error("Failed to save API key:",d)}},C=async n=>{const o=j.getClient();if(o)try{await o.authLogout(n),await h()}catch(d){console.error("Failed to logout:",d)}};return e.jsxs("div",{className:"h-full overflow-y-auto chat-background",children:[e.jsxs("div",{className:"container max-w-2xl py-8 px-4",children:[e.jsxs(D,{className:"card-depth",children:[e.jsxs(P,{children:[e.jsx(F,{children:"AI Provider Authentication"}),e.jsx(Z,{children:"Configure authentication for AI providers (OpenAI, Anthropic, etc.)"})]}),e.jsx(L,{children:a?e.jsx("div",{className:"flex items-center justify-center py-8",children:e.jsx(y,{className:"h-6 w-6 animate-spin text-muted-foreground"})}):t.length===0?e.jsx("p",{className:"text-sm text-muted-foreground py-4",children:"No providers available. Make sure clankie is configured with at least one AI provider."}):e.jsx("div",{className:"space-y-3",children:t.map(n=>e.jsx(Ae,{provider:n,isEditing:i===n.id,apiKeyValue:f,onApiKeyChange:g,onLogin:()=>n.type==="oauth"?v(n.id):N(n.id),onSaveApiKey:()=>c(n.id),onCancelApiKey:()=>m(null),onLogout:()=>C(n.id)},n.id))})})]}),e.jsxs(D,{className:"mt-4 card-depth",children:[e.jsx(P,{children:e.jsx(F,{children:"About Provider Authentication"})}),e.jsxs(L,{className:"space-y-3 text-sm text-muted-foreground",children:[e.jsx("p",{children:"AI providers require authentication to access their APIs. You can authenticate using:"}),e.jsxs("ul",{className:"list-disc list-inside space-y-1",children:[e.jsxs("li",{children:[e.jsx("strong",{children:"OAuth"})," - Browser-based authentication flow for supported providers"]}),e.jsxs("li",{children:[e.jsx("strong",{children:"API Key"})," - Direct API key entry for providers that support it"]})]}),e.jsx("p",{className:"text-xs",children:"Your credentials are stored securely by clankie and are never shared with the web UI."})]})]})]}),e.jsx(ye,{open:l,onOpenChange:r})]})}function Ae({provider:t,isEditing:a,apiKeyValue:s,onApiKeyChange:l,onLogin:r,onSaveApiKey:i,onCancelApiKey:m,onLogout:f}){return e.jsx("div",{className:"rounded-lg border p-4",children:e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-1",children:[e.jsx("h4",{className:"font-medium",children:t.name}),e.jsx(se,{variant:t.type==="oauth"?"default":"secondary",className:"text-xs",children:t.type==="oauth"?e.jsxs(e.Fragment,{children:[e.jsx(_,{className:"h-3 w-3 mr-1"}),"OAuth"]}):e.jsxs(e.Fragment,{children:[e.jsx(G,{className:"h-3 w-3 mr-1"}),"API Key"]})}),t.hasAuth?e.jsx(ee,{className:"h-4 w-4 text-green-600"}):e.jsx(M,{className:"h-4 w-4 text-muted-foreground"})]}),e.jsx("p",{className:"text-xs text-muted-foreground",children:t.hasAuth?"Authenticated":"Not configured"}),a&&t.type==="apikey"&&e.jsxs("div",{className:"mt-3 space-y-2",children:[e.jsxs(I,{children:[e.jsx(z,{htmlFor:`api-key-${t.id}`,children:"API Key"}),e.jsx(k,{id:`api-key-${t.id}`,type:"password",placeholder:"Enter API key",value:s,onChange:g=>l(g.target.value),autoFocus:!0})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(x,{size:"sm",onClick:i,disabled:!s.trim(),children:"Save"}),e.jsx(x,{size:"sm",variant:"outline",onClick:m,children:"Cancel"})]})]})]}),!a&&e.jsx("div",{className:"flex gap-2",children:t.hasAuth?e.jsx(x,{size:"sm",variant:"outline",onClick:f,children:"Logout"}):e.jsx(x,{size:"sm",onClick:r,children:"Login"})})]})})}export{Pe as component};
@@ -1 +1 @@
1
- import{a0 as i,a1 as n,x as d,a2 as s}from"./main-CP6prmzV.js";const o=s("h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",{variants:{variant:{default:"bg-primary text-primary-foreground [a]:hover:bg-primary/80",secondary:"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",destructive:"bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",outline:"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",ghost:"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",link:"text-primary underline-offset-4 hover:underline"}},defaultVariants:{variant:"default"}});function g({className:r,variant:e="default",render:t,...a}){return i({defaultTagName:"span",props:n({className:d(o({variant:e}),r)},a),render:t,state:{slot:"badge",variant:e}})}export{g as B};
1
+ import{ac as i,ad as n,i as d,ae as s}from"./main-CKIteeQH.js";const o=s("h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",{variants:{variant:{default:"bg-primary text-primary-foreground [a]:hover:bg-primary/80",secondary:"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",destructive:"bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",outline:"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",ghost:"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",link:"text-primary underline-offset-4 hover:underline"}},defaultVariants:{variant:"default"}});function g({className:r,variant:e="default",render:t,...a}){return i({defaultTagName:"span",props:n({className:d(o({variant:e}),r)},a),render:t,state:{slot:"badge",variant:e}})}export{g as B};
@@ -0,0 +1 @@
1
+ import{c}from"./main-CKIteeQH.js";const e=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],t=c("check",e);export{t as C};
@@ -0,0 +1 @@
1
+ import{c}from"./main-CKIteeQH.js";const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]],o=c("circle-x",e);export{o as C};
@@ -1 +1 @@
1
- import{a as S,c as w,r,j as e,C as d,g as h,h as x,n as y,e as u,I as m,B as c,L as T,S as F,z as j,m as p}from"./main-CP6prmzV.js";import{F as g,a as k}from"./field-DfBj0pPw.js";function A(){const{settings:o,status:l}=S(w,t=>({settings:t.settings,status:t.status})),[a,b]=r.useState(o.url),[n,v]=r.useState(o.authToken),s=l==="connected",i=l==="connecting",C=()=>{j({url:a,authToken:n})},N=()=>{j({url:a,authToken:n}),p.connect()},f=()=>{p.disconnect()};return e.jsx("div",{className:"h-full overflow-y-auto chat-background",children:e.jsxs("div",{className:"container max-w-2xl py-8 px-4",children:[e.jsxs(d,{className:"card-depth",children:[e.jsxs(h,{children:[e.jsx(x,{children:"Connection Settings"}),e.jsx(y,{children:"Configure the WebSocket connection to your clankie instance"})]}),e.jsxs(u,{className:"space-y-4",children:[e.jsxs(g,{children:[e.jsx(k,{htmlFor:"ws-url",children:"WebSocket URL"}),e.jsx(m,{id:"ws-url",type:"text",placeholder:"ws://localhost:3100",value:a,onChange:t=>b(t.target.value),disabled:s})]}),e.jsxs(g,{children:[e.jsx(k,{htmlFor:"auth-token",children:"Auth Token"}),e.jsx(m,{id:"auth-token",type:"password",placeholder:"Enter your authentication token",value:n,onChange:t=>v(t.target.value),disabled:s}),e.jsxs("p",{className:"text-xs text-muted-foreground mt-1",children:["Set with:"," ",e.jsx("code",{className:"rounded bg-muted px-1 py-0.5",children:'clankie config set channels.web.authToken "your-token"'})]})]}),e.jsx("div",{className:"flex gap-2 pt-2",children:s?e.jsx(c,{variant:"destructive",onClick:f,children:"Disconnect"}):e.jsxs(e.Fragment,{children:[e.jsx(c,{onClick:N,disabled:i||!n,children:i?"Connecting...":"Connect"}),e.jsx(c,{variant:"outline",onClick:C,disabled:i,children:"Save"})]})}),!n&&e.jsxs("div",{className:"rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive",children:[e.jsx("p",{className:"font-medium",children:"Auth token required"}),e.jsx("p",{className:"text-xs mt-1",children:"Configure the token in clankie and enter it above to connect."})]})]})]}),e.jsxs(d,{className:"mt-4 card-depth",children:[e.jsx(h,{children:e.jsx(x,{children:"Setup Instructions"})}),e.jsxs(u,{className:"space-y-3 text-sm",children:[e.jsxs("div",{children:[e.jsx("p",{className:"font-medium",children:"1. Enable the web channel in clankie"}),e.jsxs("code",{className:"block mt-1 rounded bg-muted p-2 text-xs",children:['clankie config set channels.web.authToken "your-secret-token"',e.jsx("br",{}),"clankie config set channels.web.port 3100"]})]}),e.jsxs("div",{children:[e.jsx("p",{className:"font-medium",children:"2. Start the clankie daemon"}),e.jsx("code",{className:"block mt-1 rounded bg-muted p-2 text-xs",children:"clankie start"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"font-medium",children:"3. Enter the token above and connect"}),e.jsx("p",{className:"text-xs text-muted-foreground mt-1",children:"The web-ui will connect to ws://localhost:3100 by default"})]})]})]}),!s&&e.jsx("div",{className:"mt-4 text-center",children:e.jsx(T,{to:"/settings",children:e.jsxs(c,{variant:"outline",children:[e.jsx(F,{className:"mr-2 h-4 w-4"}),"Back to Settings"]})})})]})})}export{A as component};
1
+ import{u as S,q as w,r,j as e,h as d,x as h,y as x,H as y,k as u,Q as m,B as c,L as T,S as F,U as j,d as p}from"./main-CKIteeQH.js";import{F as k,a as g}from"./field-Dy_c96vc.js";function A(){const{settings:o,status:l}=S(w,t=>({settings:t.settings,status:t.status})),[a,b]=r.useState(o.url),[n,v]=r.useState(o.authToken),s=l==="connected",i=l==="connecting",C=()=>{j({url:a,authToken:n})},N=()=>{j({url:a,authToken:n}),p.connect()},f=()=>{p.disconnect()};return e.jsx("div",{className:"h-full overflow-y-auto chat-background",children:e.jsxs("div",{className:"container max-w-2xl py-8 px-4",children:[e.jsxs(d,{className:"card-depth",children:[e.jsxs(h,{children:[e.jsx(x,{children:"Connection Settings"}),e.jsx(y,{children:"Configure the WebSocket connection to your clankie instance"})]}),e.jsxs(u,{className:"space-y-4",children:[e.jsxs(k,{children:[e.jsx(g,{htmlFor:"ws-url",children:"WebSocket URL"}),e.jsx(m,{id:"ws-url",type:"text",placeholder:"ws://localhost:3100",value:a,onChange:t=>b(t.target.value),disabled:s})]}),e.jsxs(k,{children:[e.jsx(g,{htmlFor:"auth-token",children:"Auth Token"}),e.jsx(m,{id:"auth-token",type:"password",placeholder:"Enter your authentication token",value:n,onChange:t=>v(t.target.value),disabled:s}),e.jsxs("p",{className:"text-xs text-muted-foreground mt-1",children:["Set with:"," ",e.jsx("code",{className:"rounded bg-muted px-1 py-0.5",children:'clankie config set channels.web.authToken "your-token"'})]})]}),e.jsx("div",{className:"flex gap-2 pt-2",children:s?e.jsx(c,{variant:"destructive",onClick:f,children:"Disconnect"}):e.jsxs(e.Fragment,{children:[e.jsx(c,{onClick:N,disabled:i||!n,children:i?"Connecting...":"Connect"}),e.jsx(c,{variant:"outline",onClick:C,disabled:i,children:"Save"})]})}),!n&&e.jsxs("div",{className:"rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive",children:[e.jsx("p",{className:"font-medium",children:"Auth token required"}),e.jsx("p",{className:"text-xs mt-1",children:"Configure the token in clankie and enter it above to connect."})]})]})]}),e.jsxs(d,{className:"mt-4 card-depth",children:[e.jsx(h,{children:e.jsx(x,{children:"Setup Instructions"})}),e.jsxs(u,{className:"space-y-3 text-sm",children:[e.jsxs("div",{children:[e.jsx("p",{className:"font-medium",children:"1. Enable the web channel in clankie"}),e.jsxs("code",{className:"block mt-1 rounded bg-muted p-2 text-xs",children:['clankie config set channels.web.authToken "your-secret-token"',e.jsx("br",{}),"clankie config set channels.web.port 3100"]})]}),e.jsxs("div",{children:[e.jsx("p",{className:"font-medium",children:"2. Start the clankie daemon"}),e.jsx("code",{className:"block mt-1 rounded bg-muted p-2 text-xs",children:"clankie start"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"font-medium",children:"3. Enter the token above and connect"}),e.jsx("p",{className:"text-xs text-muted-foreground mt-1",children:"The web-ui will connect to ws://localhost:3100 by default"})]})]})]}),!s&&e.jsx("div",{className:"mt-4 text-center",children:e.jsx(T,{to:"/settings",children:e.jsxs(c,{variant:"outline",children:[e.jsx(F,{className:"mr-2 h-4 w-4"}),"Back to Settings"]})})})]})})}export{A as component};
@@ -1 +1 @@
1
- import{i as a,y as i}from"./main-CP6prmzV.js";const o=[["path",{d:"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5",key:"1gvzjb"}],["path",{d:"M9 18h6",key:"x1upvd"}],["path",{d:"M10 22h4",key:"ceow96"}]],c=a("lightbulb",o),l={extensions:[],extensionErrors:[],skills:[],skillDiagnostics:[],isLoading:!1,installStatus:{isInstalling:!1,output:"",exitCode:null}},e=new i(l);function r(t){e.setState(s=>({...s,isLoading:t}))}function S(t,s){e.setState(n=>({...n,extensions:t,extensionErrors:s,isLoading:!1}))}function d(t,s){e.setState(n=>({...n,skills:t,skillDiagnostics:s,isLoading:!1}))}function g(t){e.setState(s=>({...s,installStatus:{...s.installStatus,...t}}))}function f(){e.setState(t=>({...t,installStatus:{isInstalling:!1,output:"",exitCode:null,error:void 0}}))}export{c as L,d as a,S as b,g as c,e,f as r,r as s};
1
+ import{c as a,R as i}from"./main-CKIteeQH.js";const o=[["path",{d:"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5",key:"1gvzjb"}],["path",{d:"M9 18h6",key:"x1upvd"}],["path",{d:"M10 22h4",key:"ceow96"}]],c=a("lightbulb",o),l={extensions:[],extensionErrors:[],skills:[],skillDiagnostics:[],isLoading:!1,installStatus:{isInstalling:!1,output:"",exitCode:null}},e=new i(l);function r(t){e.setState(s=>({...s,isLoading:t}))}function S(t,s){e.setState(n=>({...n,extensions:t,extensionErrors:s,isLoading:!1}))}function d(t,s){e.setState(n=>({...n,skills:t,skillDiagnostics:s,isLoading:!1}))}function g(t){e.setState(s=>({...s,installStatus:{...s.installStatus,...t}}))}function f(){e.setState(t=>({...t,installStatus:{isInstalling:!1,output:"",exitCode:null,error:void 0}}))}export{c as L,d as a,S as b,g as c,e,f as r,r as s};