clankie 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -199,9 +199,6 @@ Each session maintains its own conversation history. Sessions persist across dae
199
199
  Even though clankie lives in Slack, you also have CLI access:
200
200
 
201
201
  ```bash
202
- # Interactive chat session (local terminal, uses pi's TUI)
203
- clankie chat
204
-
205
202
  # Send a one-off message (prints response and exits)
206
203
  clankie send "What files are in the current directory?"
207
204
 
@@ -297,11 +294,9 @@ Logs are stored in `~/.clankie/logs/daemon.log`.
297
294
 
298
295
  ```bash
299
296
  # Run directly with Bun (no build step)
300
- bun src/cli.ts chat
301
297
  bun src/cli.ts send "hello"
302
298
 
303
299
  # Or use bun scripts
304
- bun run dev chat
305
300
  bun run dev send "hello"
306
301
 
307
302
  # Build for production
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";
@@ -297213,15 +297216,21 @@ function createWorkspaceJailExtension(workspaceDir, allowedPaths = []) {
297213
297216
  let match2;
297214
297217
  while ((match2 = absolutePathPattern.exec(command)) !== null) {
297215
297218
  const pathLike = match2[1];
297216
- if (pathLike.startsWith("/etc") || pathLike.startsWith("/var") || pathLike.startsWith("/usr") || pathLike.startsWith("/sys") || pathLike.startsWith("/proc") || pathLike.startsWith("/root") || pathLike.startsWith("~/")) {
297219
+ const check = isPathAllowed(pathLike);
297220
+ if (!check.allowed) {
297217
297221
  return {
297218
297222
  allowed: false,
297219
- reason: `Blocked: command references path outside workspace: ${pathLike}`
297223
+ reason: check.reason ?? `Blocked: command references path outside allowed scope: ${pathLike}`
297220
297224
  };
297221
297225
  }
297222
297226
  }
297223
- if (/\bcd\s+\//.test(command)) {
297224
- return { allowed: false, reason: "Blocked: 'cd /' or 'cd /path' outside workspace" };
297227
+ const cdMatch = command.match(/(?:^|\s)cd\s+([^\s;|&]+)/);
297228
+ const cdTarget = cdMatch?.[1];
297229
+ if (cdTarget && (cdTarget.startsWith("/") || cdTarget.startsWith("~"))) {
297230
+ const check = isPathAllowed(cdTarget);
297231
+ if (!check.allowed) {
297232
+ return { allowed: false, reason: check.reason ?? `Blocked: cd to disallowed path: ${cdTarget}` };
297233
+ }
297225
297234
  }
297226
297235
  const dotsPattern = /(?:^|\s)cd\s+(?:\.\.\/){3,}/;
297227
297236
  if (dotsPattern.test(command)) {
@@ -297255,11 +297264,13 @@ function createWorkspaceJailExtension(workspaceDir, allowedPaths = []) {
297255
297264
  return;
297256
297265
  });
297257
297266
  pi2.on("before_agent_start", async () => {
297267
+ const allowedPathsNote = normalizedAllowedPaths.length ? `
297268
+ Also allowed: ${normalizedAllowedPaths.join(", ")}` : "";
297258
297269
  return {
297259
297270
  systemPrompt: `
297260
297271
 
297261
- IMPORTANT: You are restricted to working within the directory: ${workspaceDir}
297262
- Do not access files, run commands, or reference paths outside this directory.`
297272
+ IMPORTANT: You are restricted to working within the directory: ${workspaceDir}${allowedPathsNote}
297273
+ Do not access files, run commands, or reference paths outside the allowed directories.`
297263
297274
  };
297264
297275
  });
297265
297276
  };
@@ -297275,7 +297286,9 @@ async function createSession(options2 = {}) {
297275
297286
  const extensionFactories = [];
297276
297287
  const restrictToWorkspace = config.agent?.restrictToWorkspace ?? true;
297277
297288
  if (restrictToWorkspace) {
297278
- const allowedPaths = config.agent?.allowedPaths ?? [];
297289
+ const configuredAllowedPaths = config.agent?.allowedPaths ?? [];
297290
+ const attachmentRoot = join28(getAppDir(), "attachments");
297291
+ const allowedPaths = Array.from(new Set([...configuredAllowedPaths, attachmentRoot]));
297279
297292
  extensionFactories.push(createWorkspaceJailExtension(cwd, allowedPaths));
297280
297293
  }
297281
297294
  const loader = new DefaultResourceLoader({
@@ -297367,16 +297380,16 @@ function buildApiKeyProviders(oauthIds, dynamicProviderIds) {
297367
297380
 
297368
297381
  // src/daemon.ts
297369
297382
  import { existsSync as existsSync28, readFileSync as readFileSync24, unlinkSync as unlinkSync3, watch as watch3, writeFileSync as writeFileSync12 } from "node:fs";
297370
- import { join as join31 } from "node:path";
297383
+ import { join as join32 } from "node:path";
297371
297384
 
297372
297385
  // src/channels/slack.ts
297373
297386
  var import_socket_mode = __toESM(require_src6(), 1);
297374
297387
  var import_web_api = __toESM(require_dist18(), 1);
297375
297388
  import { existsSync as existsSync25, mkdirSync as mkdirSync10, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "node:fs";
297376
297389
  import { homedir as homedir14 } from "node:os";
297377
- import { join as join28 } from "node:path";
297390
+ import { join as join29 } from "node:path";
297378
297391
  var SLACK_MAX_LENGTH = 4000;
297379
- var ACTIVE_THREADS_FILE = join28(homedir14(), ".clankie", "slack-active-threads.json");
297392
+ var ACTIVE_THREADS_FILE = join29(homedir14(), ".clankie", "slack-active-threads.json");
297380
297393
  var THREAD_TTL_DAYS = 7;
297381
297394
 
297382
297395
  class SlackChannel {
@@ -297464,7 +297477,7 @@ class SlackChannel {
297464
297477
  }
297465
297478
  saveActiveThreads() {
297466
297479
  try {
297467
- const dir = join28(homedir14(), ".clankie");
297480
+ const dir = join29(homedir14(), ".clankie");
297468
297481
  if (!existsSync25(dir)) {
297469
297482
  mkdirSync10(dir, { recursive: true, mode: 448 });
297470
297483
  }
@@ -297607,7 +297620,7 @@ class SlackChannel {
297607
297620
  // src/channels/web.ts
297608
297621
  import * as crypto5 from "node:crypto";
297609
297622
  import { existsSync as existsSync27, readdirSync as readdirSync11, readFileSync as readFileSync23, statSync as statSync12 } from "node:fs";
297610
- import { join as join30, resolve as resolve11 } from "node:path";
297623
+ import { join as join31, resolve as resolve11 } from "node:path";
297611
297624
 
297612
297625
  // node_modules/@hono/node-server/dist/index.mjs
297613
297626
  import { createServer as createServerHTTP } from "http";
@@ -299868,7 +299881,7 @@ var Hono2 = class extends Hono {
299868
299881
 
299869
299882
  // src/sessions.ts
299870
299883
  import { existsSync as existsSync26, mkdirSync as mkdirSync11 } from "node:fs";
299871
- import { join as join29 } from "node:path";
299884
+ import { join as join30 } from "node:path";
299872
299885
  var sessionCache = new Map;
299873
299886
  var activeSessionNames = new Map;
299874
299887
  var chatLocks = new Map;
@@ -299887,7 +299900,9 @@ async function getOrCreateSession(chatKey, config) {
299887
299900
  const extensionFactories = [];
299888
299901
  const restrictToWorkspace = config.agent?.restrictToWorkspace ?? true;
299889
299902
  if (restrictToWorkspace) {
299890
- const allowedPaths = config.agent?.allowedPaths ?? [];
299903
+ const configuredAllowedPaths = config.agent?.allowedPaths ?? [];
299904
+ const attachmentRoot = join30(getAppDir(), "attachments");
299905
+ const allowedPaths = Array.from(new Set([...configuredAllowedPaths, attachmentRoot]));
299891
299906
  extensionFactories.push(createWorkspaceJailExtension(cwd, allowedPaths));
299892
299907
  }
299893
299908
  const loader = new DefaultResourceLoader({
@@ -299896,7 +299911,7 @@ async function getOrCreateSession(chatKey, config) {
299896
299911
  extensionFactories
299897
299912
  });
299898
299913
  await loader.reload();
299899
- const sessionDir = join29(getAppDir(), "sessions", chatKey);
299914
+ const sessionDir = join30(getAppDir(), "sessions", chatKey);
299900
299915
  if (!existsSync26(sessionDir)) {
299901
299916
  mkdirSync11(sessionDir, { recursive: true });
299902
299917
  }
@@ -299968,7 +299983,7 @@ async function getOrCreateSession(chatKey, config) {
299968
299983
  return session;
299969
299984
  }
299970
299985
  function listSessionNames(chatIdentifier) {
299971
- const sessionsDir = join29(getAppDir(), "sessions");
299986
+ const sessionsDir = join30(getAppDir(), "sessions");
299972
299987
  if (!existsSync26(sessionsDir)) {
299973
299988
  return [];
299974
299989
  }
@@ -299978,7 +299993,7 @@ function listSessionNames(chatIdentifier) {
299978
299993
  const sessionNames = new Set;
299979
299994
  for (const entry of entries) {
299980
299995
  if (entry.startsWith(`${chatIdentifier}_`)) {
299981
- const entryPath = join29(sessionsDir, entry);
299996
+ const entryPath = join30(sessionsDir, entry);
299982
299997
  if (statSync12(entryPath).isDirectory()) {
299983
299998
  const sessionName = entry.substring(chatIdentifier.length + 1);
299984
299999
  if (sessionName) {
@@ -300012,13 +300027,13 @@ async function saveNonImageAttachments(attachments, chatKey) {
300012
300027
  if (nonImages.length === 0)
300013
300028
  return [];
300014
300029
  const { mkdirSync: mkdirSync12, writeFileSync: writeFileSync12 } = await import("node:fs");
300015
- const { join: join30 } = await import("node:path");
300016
- const dir = join30(getAppDir(), "attachments", chatKey);
300030
+ const { join: join31 } = await import("node:path");
300031
+ const dir = join31(getAppDir(), "attachments", chatKey);
300017
300032
  mkdirSync12(dir, { recursive: true });
300018
300033
  const results = [];
300019
300034
  for (const att of nonImages) {
300020
300035
  const name = att.fileName || `file_${Date.now()}`;
300021
- const filePath = join30(dir, name);
300036
+ const filePath = join31(dir, name);
300022
300037
  writeFileSync12(filePath, Buffer.from(att.data, "base64"));
300023
300038
  results.push({ fileName: name, path: filePath });
300024
300039
  console.log(`[session] Saved attachment: ${filePath} (${att.mimeType})`);
@@ -300119,7 +300134,7 @@ class WebChannel {
300119
300134
  if (pathname === "" || pathname === "/") {
300120
300135
  pathname = "_shell.html";
300121
300136
  }
300122
- const _filePath = join30(this.options.staticDir, pathname);
300137
+ const _filePath = join31(this.options.staticDir, pathname);
300123
300138
  const resolvedPath = resolve11(this.options.staticDir, pathname);
300124
300139
  if (!resolvedPath.startsWith(resolve11(this.options.staticDir))) {
300125
300140
  return c.text("Forbidden", 403);
@@ -300152,7 +300167,7 @@ class WebChannel {
300152
300167
  }
300153
300168
  });
300154
300169
  }
300155
- const shellPath = join30(this.options.staticDir, "_shell.html");
300170
+ const shellPath = join31(this.options.staticDir, "_shell.html");
300156
300171
  if (existsSync27(shellPath)) {
300157
300172
  const content = readFileSync23(shellPath);
300158
300173
  return new Response(content, {
@@ -300290,12 +300305,34 @@ class WebChannel {
300290
300305
  this.sendError(ws2, inbound.sessionId, command.type, err instanceof Error ? err.message : String(err), commandId);
300291
300306
  }
300292
300307
  }
300308
+ injectAttachmentPaths(sessionId, message) {
300309
+ const attachmentNames = Array.from(message.matchAll(/\[Attached:\s*([^\]]+)\]/g)).map((match3) => match3[1]?.trim()).filter((name) => Boolean(name));
300310
+ if (attachmentNames.length === 0)
300311
+ return message;
300312
+ const attachmentDir = join31(getAppDir(), "attachments", sessionId);
300313
+ const resolved = [];
300314
+ for (const name of attachmentNames) {
300315
+ const path14 = join31(attachmentDir, name);
300316
+ if (existsSync27(path14)) {
300317
+ resolved.push(` - ${name}: ${path14}`);
300318
+ }
300319
+ }
300320
+ if (resolved.length === 0)
300321
+ return message;
300322
+ const suffix = `
300323
+
300324
+ [Attached files saved to disk]
300325
+ ${resolved.join(`
300326
+ `)}`;
300327
+ return `${message}${suffix}`;
300328
+ }
300293
300329
  async executeCommand(sessionId, session, command) {
300294
300330
  const id = command.id;
300295
300331
  switch (command.type) {
300296
300332
  case "prompt": {
300297
300333
  console.log(`[web] Executing prompt - session.sessionId: ${session.sessionId}, sessionFile: ${session.sessionFile}`);
300298
- session.prompt(command.message, {
300334
+ const promptWithAttachmentPaths = this.injectAttachmentPaths(sessionId, command.message);
300335
+ session.prompt(promptWithAttachmentPaths, {
300299
300336
  images: command.images,
300300
300337
  streamingBehavior: command.streamingBehavior,
300301
300338
  source: "rpc"
@@ -300320,13 +300357,13 @@ class WebChannel {
300320
300357
  case "upload_attachment": {
300321
300358
  const { fileName, data, mimeType } = command;
300322
300359
  const { mkdirSync: mkdirSync12, writeFileSync: writeFileSync12 } = await import("node:fs");
300323
- const { join: join31 } = await import("node:path");
300324
- const dir = join31(getAppDir(), "attachments", sessionId);
300360
+ const { join: join32 } = await import("node:path");
300361
+ const dir = join32(getAppDir(), "attachments", sessionId);
300325
300362
  mkdirSync12(dir, { recursive: true });
300326
300363
  const timestamp = Date.now();
300327
300364
  const sanitizedName = fileName.replace(/[^a-zA-Z0-9.-]/g, "_");
300328
300365
  const uniqueFileName = `${timestamp}_${sanitizedName}`;
300329
- const filePath = join31(dir, uniqueFileName);
300366
+ const filePath = join32(dir, uniqueFileName);
300330
300367
  writeFileSync12(filePath, Buffer.from(data, "base64"));
300331
300368
  console.log(`[web] Saved attachment: ${filePath} (${mimeType})`);
300332
300369
  return {
@@ -300860,8 +300897,8 @@ class WebChannel {
300860
300897
  try {
300861
300898
  const files = readdirSync11(sessionPath).filter((f3) => f3.endsWith(".jsonl")).map((f3) => ({
300862
300899
  name: f3,
300863
- path: join30(sessionPath, f3),
300864
- mtime: statSync12(join30(sessionPath, f3)).mtime.getTime()
300900
+ path: join31(sessionPath, f3),
300901
+ mtime: statSync12(join31(sessionPath, f3)).mtime.getTime()
300865
300902
  })).sort((a, b) => b.mtime - a.mtime);
300866
300903
  if (files.length === 0)
300867
300904
  return;
@@ -300888,13 +300925,13 @@ class WebChannel {
300888
300925
  }
300889
300926
  async listAllSessions() {
300890
300927
  const sessions = [];
300891
- const sessionsDir = join30(getAppDir(), "sessions");
300928
+ const sessionsDir = join31(getAppDir(), "sessions");
300892
300929
  if (!existsSync27(sessionsDir)) {
300893
300930
  return sessions;
300894
300931
  }
300895
300932
  try {
300896
300933
  const dirs = readdirSync11(sessionsDir);
300897
- const webSessions = dirs.filter((dir) => dir.startsWith("web_")).map((dir) => ({ sessionId: dir, path: join30(sessionsDir, dir) })).filter(({ path: path14 }) => {
300934
+ const webSessions = dirs.filter((dir) => dir.startsWith("web_")).map((dir) => ({ sessionId: dir, path: join31(sessionsDir, dir) })).filter(({ path: path14 }) => {
300898
300935
  try {
300899
300936
  return statSync12(path14).isDirectory();
300900
300937
  } catch {
@@ -300965,7 +301002,7 @@ class WebChannel {
300965
301002
  }
300966
301003
 
300967
301004
  // src/daemon.ts
300968
- var PID_FILE = join31(getAppDir(), "daemon.pid");
301005
+ var PID_FILE = join32(getAppDir(), "daemon.pid");
300969
301006
  function isRunning() {
300970
301007
  if (!existsSync28(PID_FILE))
300971
301008
  return { running: false };
@@ -301223,23 +301260,23 @@ async function restartDaemon() {
301223
301260
  import { execSync as execSync3 } from "node:child_process";
301224
301261
  import { existsSync as existsSync29, mkdirSync as mkdirSync12, readFileSync as readFileSync25, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
301225
301262
  import { homedir as homedir15, platform as platform3 } from "node:os";
301226
- import { dirname as dirname16, join as join32 } from "node:path";
301263
+ import { dirname as dirname16, join as join33 } from "node:path";
301227
301264
  var SERVICE_NAME = "clankie";
301228
301265
  var LAUNCHD_LABEL = "ai.clankie.daemon";
301229
301266
  function resolveProgramArguments() {
301230
301267
  const runtime = process.execPath || process.argv[0];
301231
301268
  const thisFile = import.meta.filename;
301232
- const cliEntry = thisFile.endsWith("service.ts") ? join32(dirname16(thisFile), "cli.ts") : join32(dirname16(thisFile), "cli.js");
301269
+ const cliEntry = thisFile.endsWith("service.ts") ? join33(dirname16(thisFile), "cli.ts") : join33(dirname16(thisFile), "cli.js");
301233
301270
  return [runtime, cliEntry, "start", "--foreground"];
301234
301271
  }
301235
301272
  function systemdUnitPath() {
301236
- return join32(homedir15(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
301273
+ return join33(homedir15(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
301237
301274
  }
301238
301275
  function buildSystemdUnit() {
301239
301276
  const args = resolveProgramArguments();
301240
301277
  const execStart = args.map(systemdEscapeArg).join(" ");
301241
- const workspace = join32(getAppDir(), "workspace");
301242
- const logDir = join32(getAppDir(), "logs");
301278
+ const workspace = join33(getAppDir(), "workspace");
301279
+ const logDir = join33(getAppDir(), "logs");
301243
301280
  return [
301244
301281
  "[Unit]",
301245
301282
  `Description=clankie — personal AI assistant daemon`,
@@ -301254,8 +301291,8 @@ function buildSystemdUnit() {
301254
301291
  "KillMode=process",
301255
301292
  `Environment=HOME=${homedir15()}`,
301256
301293
  `Environment=PATH=${process.env.PATH}`,
301257
- `StandardOutput=append:${join32(logDir, "daemon.log")}`,
301258
- `StandardError=append:${join32(logDir, "daemon.log")}`,
301294
+ `StandardOutput=append:${join33(logDir, "daemon.log")}`,
301295
+ `StandardError=append:${join33(logDir, "daemon.log")}`,
301259
301296
  "",
301260
301297
  "[Install]",
301261
301298
  "WantedBy=default.target",
@@ -301287,8 +301324,8 @@ async function installSystemd() {
301287
301324
  }
301288
301325
  }
301289
301326
  const unitPath = systemdUnitPath();
301290
- const logDir = join32(getAppDir(), "logs");
301291
- const workspace = join32(getAppDir(), "workspace");
301327
+ const logDir = join33(getAppDir(), "logs");
301328
+ const workspace = join33(getAppDir(), "workspace");
301292
301329
  mkdirSync12(dirname16(unitPath), { recursive: true });
301293
301330
  mkdirSync12(logDir, { recursive: true });
301294
301331
  mkdirSync12(workspace, { recursive: true });
@@ -301317,7 +301354,7 @@ async function installSystemd() {
301317
301354
  console.log(`
301318
301355
  ✓ Installed and started systemd service: ${SERVICE_NAME}.service`);
301319
301356
  console.log(` Logs: journalctl --user -u ${SERVICE_NAME} -f`);
301320
- console.log(` Or: ${join32(logDir, "daemon.log")}`);
301357
+ console.log(` Or: ${join33(logDir, "daemon.log")}`);
301321
301358
  }
301322
301359
  async function uninstallSystemd() {
301323
301360
  const unitPath = systemdUnitPath();
@@ -301332,7 +301369,7 @@ async function uninstallSystemd() {
301332
301369
  console.log(`✓ Uninstalled systemd service.`);
301333
301370
  }
301334
301371
  function logsSystemd() {
301335
- const logFile = join32(getAppDir(), "logs", "daemon.log");
301372
+ const logFile = join33(getAppDir(), "logs", "daemon.log");
301336
301373
  console.log(`Log file: ${logFile}
301337
301374
  `);
301338
301375
  const result = execSafe(`journalctl --user -u ${SERVICE_NAME} --no-pager -n 50`);
@@ -301354,15 +301391,15 @@ function statusSystemd() {
301354
301391
  console.log(result.stdout || result.stderr || "Service not found.");
301355
301392
  }
301356
301393
  function launchdPlistPath() {
301357
- return join32(homedir15(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
301394
+ return join33(homedir15(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
301358
301395
  }
301359
301396
  function plistEscape(value2) {
301360
301397
  return value2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
301361
301398
  }
301362
301399
  function buildLaunchdPlist() {
301363
301400
  const args = resolveProgramArguments();
301364
- const logDir = join32(getAppDir(), "logs");
301365
- const workspace = join32(getAppDir(), "workspace");
301401
+ const logDir = join33(getAppDir(), "logs");
301402
+ const workspace = join33(getAppDir(), "workspace");
301366
301403
  const argsXml = args.map((a) => ` <string>${plistEscape(a)}</string>`).join(`
301367
301404
  `);
301368
301405
  const envVars = {};
@@ -301390,9 +301427,9 @@ ${argsXml}
301390
301427
  <key>WorkingDirectory</key>
301391
301428
  <string>${plistEscape(workspace)}</string>
301392
301429
  <key>StandardOutPath</key>
301393
- <string>${plistEscape(join32(logDir, "daemon.log"))}</string>
301430
+ <string>${plistEscape(join33(logDir, "daemon.log"))}</string>
301394
301431
  <key>StandardErrorPath</key>
301395
- <string>${plistEscape(join32(logDir, "daemon.log"))}</string>
301432
+ <string>${plistEscape(join33(logDir, "daemon.log"))}</string>
301396
301433
  <key>EnvironmentVariables</key>
301397
301434
  <dict>
301398
301435
  ${envXml}
@@ -301403,8 +301440,8 @@ ${envXml}
301403
301440
  }
301404
301441
  async function installLaunchd() {
301405
301442
  const plistPath = launchdPlistPath();
301406
- const logDir = join32(getAppDir(), "logs");
301407
- const workspace = join32(getAppDir(), "workspace");
301443
+ const logDir = join33(getAppDir(), "logs");
301444
+ const workspace = join33(getAppDir(), "workspace");
301408
301445
  mkdirSync12(dirname16(plistPath), { recursive: true });
301409
301446
  mkdirSync12(logDir, { recursive: true });
301410
301447
  mkdirSync12(workspace, { recursive: true });
@@ -301421,7 +301458,7 @@ async function installLaunchd() {
301421
301458
  }
301422
301459
  console.log(`
301423
301460
  ✓ Installed and started launchd agent: ${LAUNCHD_LABEL}`);
301424
- console.log(` Logs: tail -f ${join32(logDir, "daemon.log")}`);
301461
+ console.log(` Logs: tail -f ${join33(logDir, "daemon.log")}`);
301425
301462
  }
301426
301463
  async function uninstallLaunchd() {
301427
301464
  const plistPath = launchdPlistPath();
@@ -301435,7 +301472,7 @@ async function uninstallLaunchd() {
301435
301472
  console.log(`✓ Uninstalled launchd agent.`);
301436
301473
  }
301437
301474
  function logsLaunchd() {
301438
- const logFile = join32(getAppDir(), "logs", "daemon.log");
301475
+ const logFile = join33(getAppDir(), "logs", "daemon.log");
301439
301476
  console.log(`Log file: ${logFile}
301440
301477
  `);
301441
301478
  if (existsSync29(logFile)) {
@@ -301493,7 +301530,7 @@ function showServiceLogs() {
301493
301530
  } else if (isLinux) {
301494
301531
  logsSystemd();
301495
301532
  } else {
301496
- const logFile = join32(getAppDir(), "logs", "daemon.log");
301533
+ const logFile = join33(getAppDir(), "logs", "daemon.log");
301497
301534
  if (existsSync29(logFile)) {
301498
301535
  console.log(readFileSync25(logFile, "utf-8"));
301499
301536
  } else {
@@ -301518,7 +301555,6 @@ function printHelp2() {
301518
301555
 
301519
301556
  Usage:
301520
301557
  clankie send "<message>" Send a message, print response, exit
301521
- clankie chat Start interactive chat session
301522
301558
  clankie init Set up clankie (generates auth token, configures web channel)
301523
301559
  clankie login Authenticate with your AI provider
301524
301560
  clankie start [--foreground] Start the daemon (foreground by default)
@@ -301583,7 +301619,7 @@ Credentials are stored at ~/.clankie/auth.json (separate from pi's auth).
301583
301619
  `);
301584
301620
  }
301585
301621
  function printVersion() {
301586
- const packagePath = join33(import.meta.dirname, "..", "package.json");
301622
+ const packagePath = join34(import.meta.dirname, "..", "package.json");
301587
301623
  try {
301588
301624
  const pkg3 = JSON.parse(readFileSync26(packagePath, "utf-8"));
301589
301625
  console.log(`clankie ${pkg3.version}`);
@@ -301608,17 +301644,6 @@ Usage: clankie send "<message>"`);
301608
301644
  initialMessage: message
301609
301645
  });
301610
301646
  }
301611
- async function cmdChat(args) {
301612
- const initialMessage = args.join(" ").trim() || undefined;
301613
- const { session, modelFallbackMessage } = await createSession({
301614
- continueRecent: true
301615
- });
301616
- const mode = new InteractiveMode(session, {
301617
- modelFallbackMessage,
301618
- initialMessage
301619
- });
301620
- await mode.run();
301621
- }
301622
301647
  async function getApiKeyProvidersForLogin(oauthIds) {
301623
301648
  const dynamicProviderIds = new Set;
301624
301649
  try {
@@ -301910,7 +301935,8 @@ async function main2() {
301910
301935
  await cmdSend(rest2);
301911
301936
  break;
301912
301937
  case "chat":
301913
- await cmdChat(rest2);
301938
+ console.error("The 'chat' command has been removed. Use the Web UI or 'clankie send'.");
301939
+ process.exit(1);
301914
301940
  break;
301915
301941
  case "init":
301916
301942
  await cmdInit(rest2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clankie",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
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
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:1772486127078,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>
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:1772487482351,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>