clankie 0.2.10 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -203502,7 +203502,7 @@ var require_src6 = __commonJS((exports2) => {
203502
203502
  // src/cli.ts
203503
203503
  import { randomBytes as randomBytes3 } from "node:crypto";
203504
203504
  import { readFileSync as readFileSync26 } from "node:fs";
203505
- import { join as join33 } from "node:path";
203505
+ import { join as join34 } from "node:path";
203506
203506
  import * as readline3 from "node:readline/promises";
203507
203507
 
203508
203508
  // node_modules/@mariozechner/pi-coding-agent/dist/index.js
@@ -297043,6 +297043,9 @@ class ThinkingSelectorComponent extends Container {
297043
297043
  // src/cli.ts
297044
297044
  var import_json52 = __toESM(require_lib5(), 1);
297045
297045
 
297046
+ // src/agent.ts
297047
+ import { join as join28 } from "node:path";
297048
+
297046
297049
  // src/config.ts
297047
297050
  var import_json5 = __toESM(require_lib5(), 1);
297048
297051
  import { chmodSync as chmodSync3, existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "node:fs";
@@ -297275,7 +297278,9 @@ async function createSession(options2 = {}) {
297275
297278
  const extensionFactories = [];
297276
297279
  const restrictToWorkspace = config.agent?.restrictToWorkspace ?? true;
297277
297280
  if (restrictToWorkspace) {
297278
- const allowedPaths = config.agent?.allowedPaths ?? [];
297281
+ const configuredAllowedPaths = config.agent?.allowedPaths ?? [];
297282
+ const attachmentRoot = join28(getAppDir(), "attachments");
297283
+ const allowedPaths = Array.from(new Set([...configuredAllowedPaths, attachmentRoot]));
297279
297284
  extensionFactories.push(createWorkspaceJailExtension(cwd, allowedPaths));
297280
297285
  }
297281
297286
  const loader = new DefaultResourceLoader({
@@ -297320,18 +297325,63 @@ async function createSession(options2 = {}) {
297320
297325
  });
297321
297326
  }
297322
297327
 
297328
+ // src/auth/providers.ts
297329
+ var CURATED_API_KEY_PROVIDERS = [
297330
+ { id: "anthropic", name: "Anthropic" },
297331
+ { id: "openai", name: "OpenAI" },
297332
+ { id: "google", name: "Google (Gemini)" },
297333
+ { id: "xai", name: "xAI (Grok)" },
297334
+ { id: "groq", name: "Groq" },
297335
+ { id: "openrouter", name: "OpenRouter" },
297336
+ { id: "mistral", name: "Mistral" }
297337
+ ];
297338
+ function formatProviderName(providerId) {
297339
+ const lower2 = providerId.toLowerCase();
297340
+ if (lower2 === "xai")
297341
+ return "xAI (Grok)";
297342
+ if (lower2 === "openai")
297343
+ return "OpenAI";
297344
+ if (lower2 === "anthropic")
297345
+ return "Anthropic";
297346
+ if (lower2 === "openrouter")
297347
+ return "OpenRouter";
297348
+ if (lower2 === "mistral")
297349
+ return "Mistral";
297350
+ if (lower2 === "groq")
297351
+ return "Groq";
297352
+ if (lower2 === "google")
297353
+ return "Google (Gemini)";
297354
+ return providerId.split(/[-_\s]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
297355
+ }
297356
+ function buildApiKeyProviders(oauthIds, dynamicProviderIds) {
297357
+ const byId = new Map;
297358
+ for (const provider of CURATED_API_KEY_PROVIDERS) {
297359
+ if (!oauthIds.has(provider.id)) {
297360
+ byId.set(provider.id, provider.name);
297361
+ }
297362
+ }
297363
+ for (const providerId of dynamicProviderIds) {
297364
+ if (!providerId || oauthIds.has(providerId))
297365
+ continue;
297366
+ if (!byId.has(providerId)) {
297367
+ byId.set(providerId, formatProviderName(providerId));
297368
+ }
297369
+ }
297370
+ return Array.from(byId.entries()).map(([id, name]) => ({ id, name })).sort((a, b) => a.name.localeCompare(b.name));
297371
+ }
297372
+
297323
297373
  // src/daemon.ts
297324
297374
  import { existsSync as existsSync28, readFileSync as readFileSync24, unlinkSync as unlinkSync3, watch as watch3, writeFileSync as writeFileSync12 } from "node:fs";
297325
- import { join as join31 } from "node:path";
297375
+ import { join as join32 } from "node:path";
297326
297376
 
297327
297377
  // src/channels/slack.ts
297328
297378
  var import_socket_mode = __toESM(require_src6(), 1);
297329
297379
  var import_web_api = __toESM(require_dist18(), 1);
297330
297380
  import { existsSync as existsSync25, mkdirSync as mkdirSync10, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "node:fs";
297331
297381
  import { homedir as homedir14 } from "node:os";
297332
- import { join as join28 } from "node:path";
297382
+ import { join as join29 } from "node:path";
297333
297383
  var SLACK_MAX_LENGTH = 4000;
297334
- var ACTIVE_THREADS_FILE = join28(homedir14(), ".clankie", "slack-active-threads.json");
297384
+ var ACTIVE_THREADS_FILE = join29(homedir14(), ".clankie", "slack-active-threads.json");
297335
297385
  var THREAD_TTL_DAYS = 7;
297336
297386
 
297337
297387
  class SlackChannel {
@@ -297419,7 +297469,7 @@ class SlackChannel {
297419
297469
  }
297420
297470
  saveActiveThreads() {
297421
297471
  try {
297422
- const dir = join28(homedir14(), ".clankie");
297472
+ const dir = join29(homedir14(), ".clankie");
297423
297473
  if (!existsSync25(dir)) {
297424
297474
  mkdirSync10(dir, { recursive: true, mode: 448 });
297425
297475
  }
@@ -297562,7 +297612,7 @@ class SlackChannel {
297562
297612
  // src/channels/web.ts
297563
297613
  import * as crypto5 from "node:crypto";
297564
297614
  import { existsSync as existsSync27, readdirSync as readdirSync11, readFileSync as readFileSync23, statSync as statSync12 } from "node:fs";
297565
- import { join as join30, resolve as resolve11 } from "node:path";
297615
+ import { join as join31, resolve as resolve11 } from "node:path";
297566
297616
 
297567
297617
  // node_modules/@hono/node-server/dist/index.mjs
297568
297618
  import { createServer as createServerHTTP } from "http";
@@ -299823,7 +299873,7 @@ var Hono2 = class extends Hono {
299823
299873
 
299824
299874
  // src/sessions.ts
299825
299875
  import { existsSync as existsSync26, mkdirSync as mkdirSync11 } from "node:fs";
299826
- import { join as join29 } from "node:path";
299876
+ import { join as join30 } from "node:path";
299827
299877
  var sessionCache = new Map;
299828
299878
  var activeSessionNames = new Map;
299829
299879
  var chatLocks = new Map;
@@ -299842,7 +299892,9 @@ async function getOrCreateSession(chatKey, config) {
299842
299892
  const extensionFactories = [];
299843
299893
  const restrictToWorkspace = config.agent?.restrictToWorkspace ?? true;
299844
299894
  if (restrictToWorkspace) {
299845
- const allowedPaths = config.agent?.allowedPaths ?? [];
299895
+ const configuredAllowedPaths = config.agent?.allowedPaths ?? [];
299896
+ const attachmentRoot = join30(getAppDir(), "attachments");
299897
+ const allowedPaths = Array.from(new Set([...configuredAllowedPaths, attachmentRoot]));
299846
299898
  extensionFactories.push(createWorkspaceJailExtension(cwd, allowedPaths));
299847
299899
  }
299848
299900
  const loader = new DefaultResourceLoader({
@@ -299851,7 +299903,7 @@ async function getOrCreateSession(chatKey, config) {
299851
299903
  extensionFactories
299852
299904
  });
299853
299905
  await loader.reload();
299854
- const sessionDir = join29(getAppDir(), "sessions", chatKey);
299906
+ const sessionDir = join30(getAppDir(), "sessions", chatKey);
299855
299907
  if (!existsSync26(sessionDir)) {
299856
299908
  mkdirSync11(sessionDir, { recursive: true });
299857
299909
  }
@@ -299923,7 +299975,7 @@ async function getOrCreateSession(chatKey, config) {
299923
299975
  return session;
299924
299976
  }
299925
299977
  function listSessionNames(chatIdentifier) {
299926
- const sessionsDir = join29(getAppDir(), "sessions");
299978
+ const sessionsDir = join30(getAppDir(), "sessions");
299927
299979
  if (!existsSync26(sessionsDir)) {
299928
299980
  return [];
299929
299981
  }
@@ -299933,7 +299985,7 @@ function listSessionNames(chatIdentifier) {
299933
299985
  const sessionNames = new Set;
299934
299986
  for (const entry of entries) {
299935
299987
  if (entry.startsWith(`${chatIdentifier}_`)) {
299936
- const entryPath = join29(sessionsDir, entry);
299988
+ const entryPath = join30(sessionsDir, entry);
299937
299989
  if (statSync12(entryPath).isDirectory()) {
299938
299990
  const sessionName = entry.substring(chatIdentifier.length + 1);
299939
299991
  if (sessionName) {
@@ -299967,13 +300019,13 @@ async function saveNonImageAttachments(attachments, chatKey) {
299967
300019
  if (nonImages.length === 0)
299968
300020
  return [];
299969
300021
  const { mkdirSync: mkdirSync12, writeFileSync: writeFileSync12 } = await import("node:fs");
299970
- const { join: join30 } = await import("node:path");
299971
- const dir = join30(getAppDir(), "attachments", chatKey);
300022
+ const { join: join31 } = await import("node:path");
300023
+ const dir = join31(getAppDir(), "attachments", chatKey);
299972
300024
  mkdirSync12(dir, { recursive: true });
299973
300025
  const results = [];
299974
300026
  for (const att of nonImages) {
299975
300027
  const name = att.fileName || `file_${Date.now()}`;
299976
- const filePath = join30(dir, name);
300028
+ const filePath = join31(dir, name);
299977
300029
  writeFileSync12(filePath, Buffer.from(att.data, "base64"));
299978
300030
  results.push({ fileName: name, path: filePath });
299979
300031
  console.log(`[session] Saved attachment: ${filePath} (${att.mimeType})`);
@@ -300074,7 +300126,7 @@ class WebChannel {
300074
300126
  if (pathname === "" || pathname === "/") {
300075
300127
  pathname = "_shell.html";
300076
300128
  }
300077
- const _filePath = join30(this.options.staticDir, pathname);
300129
+ const _filePath = join31(this.options.staticDir, pathname);
300078
300130
  const resolvedPath = resolve11(this.options.staticDir, pathname);
300079
300131
  if (!resolvedPath.startsWith(resolve11(this.options.staticDir))) {
300080
300132
  return c.text("Forbidden", 403);
@@ -300107,7 +300159,7 @@ class WebChannel {
300107
300159
  }
300108
300160
  });
300109
300161
  }
300110
- const shellPath = join30(this.options.staticDir, "_shell.html");
300162
+ const shellPath = join31(this.options.staticDir, "_shell.html");
300111
300163
  if (existsSync27(shellPath)) {
300112
300164
  const content = readFileSync23(shellPath);
300113
300165
  return new Response(content, {
@@ -300245,12 +300297,34 @@ class WebChannel {
300245
300297
  this.sendError(ws2, inbound.sessionId, command.type, err instanceof Error ? err.message : String(err), commandId);
300246
300298
  }
300247
300299
  }
300300
+ injectAttachmentPaths(sessionId, message) {
300301
+ const attachmentNames = Array.from(message.matchAll(/\[Attached:\s*([^\]]+)\]/g)).map((match3) => match3[1]?.trim()).filter((name) => Boolean(name));
300302
+ if (attachmentNames.length === 0)
300303
+ return message;
300304
+ const attachmentDir = join31(getAppDir(), "attachments", sessionId);
300305
+ const resolved = [];
300306
+ for (const name of attachmentNames) {
300307
+ const path14 = join31(attachmentDir, name);
300308
+ if (existsSync27(path14)) {
300309
+ resolved.push(` - ${name}: ${path14}`);
300310
+ }
300311
+ }
300312
+ if (resolved.length === 0)
300313
+ return message;
300314
+ const suffix = `
300315
+
300316
+ [Attached files saved to disk]
300317
+ ${resolved.join(`
300318
+ `)}`;
300319
+ return `${message}${suffix}`;
300320
+ }
300248
300321
  async executeCommand(sessionId, session, command) {
300249
300322
  const id = command.id;
300250
300323
  switch (command.type) {
300251
300324
  case "prompt": {
300252
300325
  console.log(`[web] Executing prompt - session.sessionId: ${session.sessionId}, sessionFile: ${session.sessionFile}`);
300253
- session.prompt(command.message, {
300326
+ const promptWithAttachmentPaths = this.injectAttachmentPaths(sessionId, command.message);
300327
+ session.prompt(promptWithAttachmentPaths, {
300254
300328
  images: command.images,
300255
300329
  streamingBehavior: command.streamingBehavior,
300256
300330
  source: "rpc"
@@ -300275,13 +300349,13 @@ class WebChannel {
300275
300349
  case "upload_attachment": {
300276
300350
  const { fileName, data, mimeType } = command;
300277
300351
  const { mkdirSync: mkdirSync12, writeFileSync: writeFileSync12 } = await import("node:fs");
300278
- const { join: join31 } = await import("node:path");
300279
- const dir = join31(getAppDir(), "attachments", sessionId);
300352
+ const { join: join32 } = await import("node:path");
300353
+ const dir = join32(getAppDir(), "attachments", sessionId);
300280
300354
  mkdirSync12(dir, { recursive: true });
300281
300355
  const timestamp = Date.now();
300282
300356
  const sanitizedName = fileName.replace(/[^a-zA-Z0-9.-]/g, "_");
300283
300357
  const uniqueFileName = `${timestamp}_${sanitizedName}`;
300284
- const filePath = join31(dir, uniqueFileName);
300358
+ const filePath = join32(dir, uniqueFileName);
300285
300359
  writeFileSync12(filePath, Buffer.from(data, "base64"));
300286
300360
  console.log(`[web] Saved attachment: ${filePath} (${mimeType})`);
300287
300361
  return {
@@ -300562,15 +300636,7 @@ class WebChannel {
300562
300636
  case "get_auth_providers": {
300563
300637
  const oauthProviders = authStorage.getOAuthProviders();
300564
300638
  const oauthIds = new Set(oauthProviders.map((p) => p.id));
300565
- const apiKeyProviders = [
300566
- { id: "anthropic", name: "Anthropic" },
300567
- { id: "openai", name: "OpenAI" },
300568
- { id: "google", name: "Google (Gemini)" },
300569
- { id: "xai", name: "xAI (Grok)" },
300570
- { id: "groq", name: "Groq" },
300571
- { id: "openrouter", name: "OpenRouter" },
300572
- { id: "mistral", name: "Mistral" }
300573
- ].filter((p) => !oauthIds.has(p.id));
300639
+ const apiKeyProviders = await this.getApiKeyProviders(oauthIds);
300574
300640
  const providers = [
300575
300641
  ...oauthProviders.map((p) => ({
300576
300642
  id: p.id,
@@ -300749,6 +300815,21 @@ class WebChannel {
300749
300815
  });
300750
300816
  }
300751
300817
  }
300818
+ async getApiKeyProviders(oauthIds) {
300819
+ const dynamicProviderIds = new Set;
300820
+ try {
300821
+ const config = loadConfig();
300822
+ const discoverySession = await getOrCreateSession("web_auth_providers", config);
300823
+ const allModels = discoverySession.modelRegistry.getAll();
300824
+ for (const model of allModels) {
300825
+ if (model.provider)
300826
+ dynamicProviderIds.add(model.provider);
300827
+ }
300828
+ } catch (err) {
300829
+ console.warn(`[web] Failed to discover dynamic API key providers: ${err instanceof Error ? err.message : String(err)}`);
300830
+ }
300831
+ return buildApiKeyProviders(oauthIds, dynamicProviderIds);
300832
+ }
300752
300833
  sendAuthEvent(ws2, _loginFlowId, event) {
300753
300834
  const message = {
300754
300835
  sessionId: "_auth",
@@ -300808,8 +300889,8 @@ class WebChannel {
300808
300889
  try {
300809
300890
  const files = readdirSync11(sessionPath).filter((f3) => f3.endsWith(".jsonl")).map((f3) => ({
300810
300891
  name: f3,
300811
- path: join30(sessionPath, f3),
300812
- mtime: statSync12(join30(sessionPath, f3)).mtime.getTime()
300892
+ path: join31(sessionPath, f3),
300893
+ mtime: statSync12(join31(sessionPath, f3)).mtime.getTime()
300813
300894
  })).sort((a, b) => b.mtime - a.mtime);
300814
300895
  if (files.length === 0)
300815
300896
  return;
@@ -300836,13 +300917,13 @@ class WebChannel {
300836
300917
  }
300837
300918
  async listAllSessions() {
300838
300919
  const sessions = [];
300839
- const sessionsDir = join30(getAppDir(), "sessions");
300920
+ const sessionsDir = join31(getAppDir(), "sessions");
300840
300921
  if (!existsSync27(sessionsDir)) {
300841
300922
  return sessions;
300842
300923
  }
300843
300924
  try {
300844
300925
  const dirs = readdirSync11(sessionsDir);
300845
- const webSessions = dirs.filter((dir) => dir.startsWith("web_")).map((dir) => ({ sessionId: dir, path: join30(sessionsDir, dir) })).filter(({ path: path14 }) => {
300926
+ const webSessions = dirs.filter((dir) => dir.startsWith("web_")).map((dir) => ({ sessionId: dir, path: join31(sessionsDir, dir) })).filter(({ path: path14 }) => {
300846
300927
  try {
300847
300928
  return statSync12(path14).isDirectory();
300848
300929
  } catch {
@@ -300913,7 +300994,7 @@ class WebChannel {
300913
300994
  }
300914
300995
 
300915
300996
  // src/daemon.ts
300916
- var PID_FILE = join31(getAppDir(), "daemon.pid");
300997
+ var PID_FILE = join32(getAppDir(), "daemon.pid");
300917
300998
  function isRunning() {
300918
300999
  if (!existsSync28(PID_FILE))
300919
301000
  return { running: false };
@@ -301171,23 +301252,23 @@ async function restartDaemon() {
301171
301252
  import { execSync as execSync3 } from "node:child_process";
301172
301253
  import { existsSync as existsSync29, mkdirSync as mkdirSync12, readFileSync as readFileSync25, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
301173
301254
  import { homedir as homedir15, platform as platform3 } from "node:os";
301174
- import { dirname as dirname16, join as join32 } from "node:path";
301255
+ import { dirname as dirname16, join as join33 } from "node:path";
301175
301256
  var SERVICE_NAME = "clankie";
301176
301257
  var LAUNCHD_LABEL = "ai.clankie.daemon";
301177
301258
  function resolveProgramArguments() {
301178
301259
  const runtime = process.execPath || process.argv[0];
301179
301260
  const thisFile = import.meta.filename;
301180
- const cliEntry = thisFile.endsWith("service.ts") ? join32(dirname16(thisFile), "cli.ts") : join32(dirname16(thisFile), "cli.js");
301261
+ const cliEntry = thisFile.endsWith("service.ts") ? join33(dirname16(thisFile), "cli.ts") : join33(dirname16(thisFile), "cli.js");
301181
301262
  return [runtime, cliEntry, "start", "--foreground"];
301182
301263
  }
301183
301264
  function systemdUnitPath() {
301184
- return join32(homedir15(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
301265
+ return join33(homedir15(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
301185
301266
  }
301186
301267
  function buildSystemdUnit() {
301187
301268
  const args = resolveProgramArguments();
301188
301269
  const execStart = args.map(systemdEscapeArg).join(" ");
301189
- const workspace = join32(getAppDir(), "workspace");
301190
- const logDir = join32(getAppDir(), "logs");
301270
+ const workspace = join33(getAppDir(), "workspace");
301271
+ const logDir = join33(getAppDir(), "logs");
301191
301272
  return [
301192
301273
  "[Unit]",
301193
301274
  `Description=clankie — personal AI assistant daemon`,
@@ -301202,8 +301283,8 @@ function buildSystemdUnit() {
301202
301283
  "KillMode=process",
301203
301284
  `Environment=HOME=${homedir15()}`,
301204
301285
  `Environment=PATH=${process.env.PATH}`,
301205
- `StandardOutput=append:${join32(logDir, "daemon.log")}`,
301206
- `StandardError=append:${join32(logDir, "daemon.log")}`,
301286
+ `StandardOutput=append:${join33(logDir, "daemon.log")}`,
301287
+ `StandardError=append:${join33(logDir, "daemon.log")}`,
301207
301288
  "",
301208
301289
  "[Install]",
301209
301290
  "WantedBy=default.target",
@@ -301235,8 +301316,8 @@ async function installSystemd() {
301235
301316
  }
301236
301317
  }
301237
301318
  const unitPath = systemdUnitPath();
301238
- const logDir = join32(getAppDir(), "logs");
301239
- const workspace = join32(getAppDir(), "workspace");
301319
+ const logDir = join33(getAppDir(), "logs");
301320
+ const workspace = join33(getAppDir(), "workspace");
301240
301321
  mkdirSync12(dirname16(unitPath), { recursive: true });
301241
301322
  mkdirSync12(logDir, { recursive: true });
301242
301323
  mkdirSync12(workspace, { recursive: true });
@@ -301265,7 +301346,7 @@ async function installSystemd() {
301265
301346
  console.log(`
301266
301347
  ✓ Installed and started systemd service: ${SERVICE_NAME}.service`);
301267
301348
  console.log(` Logs: journalctl --user -u ${SERVICE_NAME} -f`);
301268
- console.log(` Or: ${join32(logDir, "daemon.log")}`);
301349
+ console.log(` Or: ${join33(logDir, "daemon.log")}`);
301269
301350
  }
301270
301351
  async function uninstallSystemd() {
301271
301352
  const unitPath = systemdUnitPath();
@@ -301280,7 +301361,7 @@ async function uninstallSystemd() {
301280
301361
  console.log(`✓ Uninstalled systemd service.`);
301281
301362
  }
301282
301363
  function logsSystemd() {
301283
- const logFile = join32(getAppDir(), "logs", "daemon.log");
301364
+ const logFile = join33(getAppDir(), "logs", "daemon.log");
301284
301365
  console.log(`Log file: ${logFile}
301285
301366
  `);
301286
301367
  const result = execSafe(`journalctl --user -u ${SERVICE_NAME} --no-pager -n 50`);
@@ -301302,15 +301383,15 @@ function statusSystemd() {
301302
301383
  console.log(result.stdout || result.stderr || "Service not found.");
301303
301384
  }
301304
301385
  function launchdPlistPath() {
301305
- return join32(homedir15(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
301386
+ return join33(homedir15(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
301306
301387
  }
301307
301388
  function plistEscape(value2) {
301308
301389
  return value2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
301309
301390
  }
301310
301391
  function buildLaunchdPlist() {
301311
301392
  const args = resolveProgramArguments();
301312
- const logDir = join32(getAppDir(), "logs");
301313
- const workspace = join32(getAppDir(), "workspace");
301393
+ const logDir = join33(getAppDir(), "logs");
301394
+ const workspace = join33(getAppDir(), "workspace");
301314
301395
  const argsXml = args.map((a) => ` <string>${plistEscape(a)}</string>`).join(`
301315
301396
  `);
301316
301397
  const envVars = {};
@@ -301338,9 +301419,9 @@ ${argsXml}
301338
301419
  <key>WorkingDirectory</key>
301339
301420
  <string>${plistEscape(workspace)}</string>
301340
301421
  <key>StandardOutPath</key>
301341
- <string>${plistEscape(join32(logDir, "daemon.log"))}</string>
301422
+ <string>${plistEscape(join33(logDir, "daemon.log"))}</string>
301342
301423
  <key>StandardErrorPath</key>
301343
- <string>${plistEscape(join32(logDir, "daemon.log"))}</string>
301424
+ <string>${plistEscape(join33(logDir, "daemon.log"))}</string>
301344
301425
  <key>EnvironmentVariables</key>
301345
301426
  <dict>
301346
301427
  ${envXml}
@@ -301351,8 +301432,8 @@ ${envXml}
301351
301432
  }
301352
301433
  async function installLaunchd() {
301353
301434
  const plistPath = launchdPlistPath();
301354
- const logDir = join32(getAppDir(), "logs");
301355
- const workspace = join32(getAppDir(), "workspace");
301435
+ const logDir = join33(getAppDir(), "logs");
301436
+ const workspace = join33(getAppDir(), "workspace");
301356
301437
  mkdirSync12(dirname16(plistPath), { recursive: true });
301357
301438
  mkdirSync12(logDir, { recursive: true });
301358
301439
  mkdirSync12(workspace, { recursive: true });
@@ -301369,7 +301450,7 @@ async function installLaunchd() {
301369
301450
  }
301370
301451
  console.log(`
301371
301452
  ✓ Installed and started launchd agent: ${LAUNCHD_LABEL}`);
301372
- console.log(` Logs: tail -f ${join32(logDir, "daemon.log")}`);
301453
+ console.log(` Logs: tail -f ${join33(logDir, "daemon.log")}`);
301373
301454
  }
301374
301455
  async function uninstallLaunchd() {
301375
301456
  const plistPath = launchdPlistPath();
@@ -301383,7 +301464,7 @@ async function uninstallLaunchd() {
301383
301464
  console.log(`✓ Uninstalled launchd agent.`);
301384
301465
  }
301385
301466
  function logsLaunchd() {
301386
- const logFile = join32(getAppDir(), "logs", "daemon.log");
301467
+ const logFile = join33(getAppDir(), "logs", "daemon.log");
301387
301468
  console.log(`Log file: ${logFile}
301388
301469
  `);
301389
301470
  if (existsSync29(logFile)) {
@@ -301441,7 +301522,7 @@ function showServiceLogs() {
301441
301522
  } else if (isLinux) {
301442
301523
  logsSystemd();
301443
301524
  } else {
301444
- const logFile = join32(getAppDir(), "logs", "daemon.log");
301525
+ const logFile = join33(getAppDir(), "logs", "daemon.log");
301445
301526
  if (existsSync29(logFile)) {
301446
301527
  console.log(readFileSync25(logFile, "utf-8"));
301447
301528
  } else {
@@ -301531,7 +301612,7 @@ Credentials are stored at ~/.clankie/auth.json (separate from pi's auth).
301531
301612
  `);
301532
301613
  }
301533
301614
  function printVersion() {
301534
- const packagePath = join33(import.meta.dirname, "..", "package.json");
301615
+ const packagePath = join34(import.meta.dirname, "..", "package.json");
301535
301616
  try {
301536
301617
  const pkg3 = JSON.parse(readFileSync26(packagePath, "utf-8"));
301537
301618
  console.log(`clankie ${pkg3.version}`);
@@ -301567,20 +301648,26 @@ async function cmdChat(args) {
301567
301648
  });
301568
301649
  await mode.run();
301569
301650
  }
301651
+ async function getApiKeyProvidersForLogin(oauthIds) {
301652
+ const dynamicProviderIds = new Set;
301653
+ try {
301654
+ const { session } = await createSession({ ephemeral: true });
301655
+ const allModels = session.modelRegistry.getAll();
301656
+ for (const model of allModels) {
301657
+ if (model.provider)
301658
+ dynamicProviderIds.add(model.provider);
301659
+ }
301660
+ } catch (err) {
301661
+ console.warn(`Warning: failed to discover dynamic providers: ${err instanceof Error ? err.message : String(err)}`);
301662
+ }
301663
+ return buildApiKeyProviders(oauthIds, dynamicProviderIds);
301664
+ }
301570
301665
  async function cmdLogin(_args) {
301571
301666
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
301572
301667
  const authStorage = AuthStorage.create(getAuthPath2());
301573
301668
  const oauthProviders = authStorage.getOAuthProviders();
301574
301669
  const oauthIds = new Set(oauthProviders.map((p) => p.id));
301575
- const apiKeyProviders = [
301576
- { id: "anthropic", name: "Anthropic" },
301577
- { id: "openai", name: "OpenAI" },
301578
- { id: "google", name: "Google (Gemini)" },
301579
- { id: "xai", name: "xAI (Grok)" },
301580
- { id: "groq", name: "Groq" },
301581
- { id: "openrouter", name: "OpenRouter" },
301582
- { id: "mistral", name: "Mistral" }
301583
- ].filter((p) => !oauthIds.has(p.id));
301670
+ const apiKeyProviders = await getApiKeyProvidersForLogin(oauthIds);
301584
301671
  const entries = [];
301585
301672
  for (const p of oauthProviders) {
301586
301673
  entries.push({ id: p.id, name: p.name, type: "oauth", hasAuth: authStorage.hasAuth(p.id) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clankie",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
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" class="dark"><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-DUT2mAkp.js"/><link rel="modulepreload" href="/assets/index-QDWEB_Lf.js"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/><link rel="stylesheet" href="/assets/styles-CdE1WgCk.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)]"><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="gap-2 p-2 flex flex-col"><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="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 h-12 text-sm group-data-[collapsible=icon]:p-0! active" href="/" data-status="active" aria-current="page"><div class="flex items-center gap-2"><div class="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 border border-primary/20"><span class="text-sm font-mono font-bold text-primary">c/</span></div><span class="text-base font-mono font-semibold tracking-tight">clankie</span></div></a></li></ul></div><div data-slot="sidebar-content" data-sidebar="content" class="no-scrollbar gap-0 flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden"><div data-slot="sidebar-group" data-sidebar="group" class="p-2 relative flex w-full min-w-0 flex-col"><div data-slot="sidebar-group-content" data-sidebar="group-content" class="text-sm w-full"><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_6qb6_" 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 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 h-8 text-sm bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground duration-200 ease-linear"><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" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M8 12h8"></path><path d="M12 8v8"></path></svg><span>Create Chat</span></button></li></ul></div></div><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"><div data-slot="sidebar-group-label" data-sidebar="group-label" class="text-sidebar-foreground/70 ring-sidebar-ring h-8 rounded-md px-2 text-xs font-medium 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">Recent Sessions</div><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="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 h-8 text-sm" disabled=""><span class="text-sidebar-foreground/70">No sessions yet</span></button></li></ul></div><div data-slot="sidebar-group" data-sidebar="group" class="p-2 relative flex w-full min-w-0 flex-col mt-auto"><div data-slot="sidebar-group-content" data-sidebar="group-content" class="text-sm w-full"><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="/settings" 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 h-8 text-sm"><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" 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></a></li><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="/extensions" 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 h-8 text-sm"><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-puzzle" aria-hidden="true"><path d="M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z"></path></svg><span>Extensions</span></a></li><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><div class="px-2 py-1.5"><span data-slot="badge" data-variant="secondary" class="h-5 gap-1 rounded-4xl border 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 [&amp;&gt;svg]:size-3! inline-flex items-center whitespace-nowrap shrink-0 [&amp;&gt;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 [a]:hover:bg-secondary/80 bg-red-500/10 text-red-500 border-red-500/20 w-full justify-start"><div class="size-2 rounded-full mr-2 bg-red-500 "></div>Disconnected</span></div></li></ul></div></div></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 relative flex w-full flex-1 flex-col"><div class="flex flex-1 flex-col overflow-hidden"><header class="flex h-14 shrink-0 items-center gap-2 border-b px-4"><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><!--$--><!--$--><!--/$--><script></script><!--/$--></div></main></div><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-DUT2mAkp.js"],assets:$R[5]=[$R[6]={tag:"script",attrs:$R[7]={type:"module",async:!0},children:"import(\"/assets/main-DUT2mAkp.js\")"}]}}},matches:$R[8]=[$R[9]={i:"__root__",u:1772468148357,s:"success",ssr:!0}],lastMatchId:"__root__"})($R["tsr"]);$_TSR.e();document.currentScript.remove()</script><script type="module" async="">import("/assets/main-DUT2mAkp.js")</script></body></html>
1
+ <!DOCTYPE html><html lang="en" class="dark"><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-UkFzTTck.js"/><link rel="modulepreload" href="/assets/index-CdxpJyJ4.js"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/><link rel="stylesheet" href="/assets/styles-DAZ1IN1z.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)]"><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="gap-2 p-2 flex flex-col"><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="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 h-12 text-sm group-data-[collapsible=icon]:p-0! active" href="/" data-status="active" aria-current="page"><div class="flex items-center gap-2"><div class="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 border border-primary/20"><span class="text-sm font-mono font-bold text-primary">c/</span></div><span class="text-base font-mono font-semibold tracking-tight">clankie</span></div></a></li></ul></div><div data-slot="sidebar-content" data-sidebar="content" class="no-scrollbar gap-0 flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden"><div data-slot="sidebar-group" data-sidebar="group" class="p-2 relative flex w-full min-w-0 flex-col"><div data-slot="sidebar-group-content" data-sidebar="group-content" class="text-sm w-full"><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_6qb6_" 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 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 h-8 text-sm bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground duration-200 ease-linear"><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" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M8 12h8"></path><path d="M12 8v8"></path></svg><span>Create Chat</span></button></li></ul></div></div><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"><div data-slot="sidebar-group-label" data-sidebar="group-label" class="text-sidebar-foreground/70 ring-sidebar-ring h-8 rounded-md px-2 text-xs font-medium 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">Recent Sessions</div><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="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 h-8 text-sm" disabled=""><span class="text-sidebar-foreground/70">No sessions yet</span></button></li></ul></div><div data-slot="sidebar-group" data-sidebar="group" class="p-2 relative flex w-full min-w-0 flex-col mt-auto"><div data-slot="sidebar-group-content" data-sidebar="group-content" class="text-sm w-full"><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="/settings" 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 h-8 text-sm"><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" 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></a></li><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="/extensions" 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 h-8 text-sm"><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-puzzle" aria-hidden="true"><path d="M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z"></path></svg><span>Extensions</span></a></li><li data-slot="sidebar-menu-item" data-sidebar="menu-item" class="group/menu-item relative"><div class="px-2 py-1.5"><span data-slot="badge" data-variant="secondary" class="h-5 gap-1 rounded-4xl border 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 [&amp;&gt;svg]:size-3! inline-flex items-center whitespace-nowrap shrink-0 [&amp;&gt;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 [a]:hover:bg-secondary/80 bg-red-500/10 text-red-500 border-red-500/20 w-full justify-start"><div class="size-2 rounded-full mr-2 bg-red-500 "></div>Disconnected</span></div></li></ul></div></div></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 relative flex w-full flex-1 flex-col"><div class="flex flex-1 flex-col overflow-hidden"><header class="flex h-14 shrink-0 items-center gap-2 border-b px-4"><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><!--$--><!--$--><!--/$--><script></script><!--/$--></div></main></div><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-UkFzTTck.js"],assets:$R[5]=[$R[6]={tag:"script",attrs:$R[7]={type:"module",async:!0},children:"import(\"/assets/main-UkFzTTck.js\")"}]}}},matches:$R[8]=[$R[9]={i:"__root__",u:1772486954401,s:"success",ssr:!0}],lastMatchId:"__root__"})($R["tsr"]);$_TSR.e();document.currentScript.remove()</script><script type="module" async="">import("/assets/main-UkFzTTck.js")</script></body></html>