clankie 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +3 -0
  2. package/dist/cli.js +305 -39
  3. package/package.json +3 -2
  4. package/skills/clankie-admin/SKILL.md +64 -0
  5. package/web-ui-dist/_shell.html +2 -2
  6. package/web-ui-dist/assets/{auth-CszIWbjg.js → auth-B20mIC_p.js} +1 -1
  7. package/web-ui-dist/assets/badge-3e57zy_2.js +1 -0
  8. package/web-ui-dist/assets/check-CePvKusa.js +1 -0
  9. package/web-ui-dist/assets/{circle-x-8a_H31zB.js → circle-x-B1Pwi07a.js} +1 -1
  10. package/web-ui-dist/assets/{connection-CJ5rxoTn.js → connection-D4rgB5k2.js} +1 -1
  11. package/web-ui-dist/assets/extensions-BxWXmCbJ.js +1 -0
  12. package/web-ui-dist/assets/extensions-Cf8QLmLt.js +1 -0
  13. package/web-ui-dist/assets/{field-B65zDnKR.js → field-DfBj0pPw.js} +1 -1
  14. package/web-ui-dist/assets/{index-DagqCCaE.js → index-Ff5WtXhh.js} +1 -1
  15. package/web-ui-dist/assets/{index-BPe4bC0P.js → index-TBNB5eLy.js} +1 -1
  16. package/web-ui-dist/assets/{json-render-renderer-LfYi54yN.js → json-render-renderer-BrlU1n47.js} +1 -1
  17. package/web-ui-dist/assets/main-CP6prmzV.js +35 -0
  18. package/web-ui-dist/assets/{scoped-models-Dn75_0eE.js → scoped-models-DDH_ssLY.js} +1 -1
  19. package/web-ui-dist/assets/{sessions._sessionId-BMqy9VwK.js → sessions._sessionId-CWtQlITL.js} +24 -24
  20. package/web-ui-dist/assets/{skills-DZkToxg_.js → skills-Clk3tV2m.js} +1 -1
  21. package/web-ui-dist/assets/styles-KEhqa3CU.css +1 -0
  22. package/web-ui-dist/assets/{theme-g799ir3H.js → theme-e79Jvep_.js} +1 -1
  23. package/web-ui-dist/assets/badge-n982dtcJ.js +0 -1
  24. package/web-ui-dist/assets/check-CFMzbw3W.js +0 -1
  25. package/web-ui-dist/assets/extensions-C5MIyE-l.js +0 -1
  26. package/web-ui-dist/assets/extensions-CnpFPAZe.js +0 -1
  27. package/web-ui-dist/assets/main-DLbCrY1P.js +0 -35
  28. package/web-ui-dist/assets/styles-BsoKVYca.css +0 -1
package/README.md CHANGED
@@ -118,6 +118,9 @@ clankie "Summarize recent git commits"
118
118
  # Check daemon status
119
119
  clankie status
120
120
 
121
+ # Update clankie and restart daemon/service if needed
122
+ clankie self-update
123
+
121
124
  # Stop daemon
122
125
  clankie stop
123
126
 
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
+ import { execSync, spawn, spawnSync } from "node:child_process";
3
4
  import * as crypto$2 from "node:crypto";
4
5
  import { createHash, randomBytes, randomUUID } from "node:crypto";
5
6
  import * as fs$6 from "node:fs";
@@ -14,7 +15,7 @@ import path, { basename as basename$1, dirname as dirname$1, isAbsolute as isAbs
14
15
  import { fileURLToPath } from "url";
15
16
  import * as os$2 from "node:os";
16
17
  import os, { homedir as homedir$1, platform as platform$1, tmpdir } from "node:os";
17
- import { exec, execSync, spawn, spawnSync } from "child_process";
18
+ import { exec, execSync as execSync$1, spawn as spawn$1, spawnSync as spawnSync$1 } from "child_process";
18
19
  import { Readable } from "stream";
19
20
  import crypto$1, { randomUUID as randomUUID$1 } from "crypto";
20
21
  import { createServer } from "http";
@@ -36,7 +37,6 @@ import { access as access$1, lstat, open, readFile as readFile$1, readdir as rea
36
37
  import { EventEmitter as EventEmitter$1 } from "node:events";
37
38
  import tty from "node:tty";
38
39
  import { createRequire as createRequire$1 } from "module";
39
- import { execSync as execSync$1, spawn as spawn$1, spawnSync as spawnSync$1 } from "node:child_process";
40
40
  import { StringDecoder } from "node:string_decoder";
41
41
  import { finished as finished$1 } from "stream/promises";
42
42
  import { createInterface } from "node:readline";
@@ -213478,7 +213478,7 @@ let cachedShellConfig = null;
213478
213478
  function findBashOnPath() {
213479
213479
  if (process.platform === "win32") {
213480
213480
  try {
213481
- const result = spawnSync("where", ["bash.exe"], {
213481
+ const result = spawnSync$1("where", ["bash.exe"], {
213482
213482
  encoding: "utf-8",
213483
213483
  timeout: 5e3
213484
213484
  });
@@ -213490,7 +213490,7 @@ function findBashOnPath() {
213490
213490
  return null;
213491
213491
  }
213492
213492
  try {
213493
- const result = spawnSync("which", ["bash"], {
213493
+ const result = spawnSync$1("which", ["bash"], {
213494
213494
  encoding: "utf-8",
213495
213495
  timeout: 5e3
213496
213496
  });
@@ -213601,7 +213601,7 @@ function sanitizeBinaryOutput(str) {
213601
213601
  */
213602
213602
  function killProcessTree(pid) {
213603
213603
  if (process.platform === "win32") try {
213604
- spawn("taskkill", [
213604
+ spawn$1("taskkill", [
213605
213605
  "/F",
213606
213606
  "/T",
213607
213607
  "/PID",
@@ -213822,7 +213822,7 @@ function truncateLine(line, maxChars = 500) {
213822
213822
  function executeBash(command, options) {
213823
213823
  return new Promise((resolve, reject) => {
213824
213824
  const { shell, args } = getShellConfig();
213825
- const child = spawn(shell, [...args, command], {
213825
+ const child = spawn$1(shell, [...args, command], {
213826
213826
  detached: true,
213827
213827
  env: getShellEnv(),
213828
213828
  stdio: [
@@ -263025,7 +263025,7 @@ function walkDirectoryWithFd(baseDir, fdPath, query, maxResults) {
263025
263025
  ".git/**"
263026
263026
  ];
263027
263027
  if (query) args.push(query);
263028
- const result = spawnSync(fdPath, args, {
263028
+ const result = spawnSync$1(fdPath, args, {
263029
263029
  encoding: "utf-8",
263030
263030
  stdio: [
263031
263031
  "pipe",
@@ -271480,7 +271480,7 @@ function createEventBus() {
271480
271480
  */
271481
271481
  async function execCommand(command, args, cwd, options) {
271482
271482
  return new Promise((resolve) => {
271483
- const proc = spawn$1(command, args, {
271483
+ const proc = spawn(command, args, {
271484
271484
  cwd,
271485
271485
  shell: false,
271486
271486
  stdio: [
@@ -273542,7 +273542,7 @@ const defaultBashOperations = { exec: (command, cwd, { onData, signal, timeout,
273542
273542
  reject(/* @__PURE__ */ new Error(`Working directory does not exist: ${cwd}\nCannot execute bash commands.`));
273543
273543
  return;
273544
273544
  }
273545
- const child = spawn(shell, [...args, command], {
273545
+ const child = spawn$1(shell, [...args, command], {
273546
273546
  cwd,
273547
273547
  detached: true,
273548
273548
  env: env ?? getShellEnv(),
@@ -279228,7 +279228,7 @@ const TOOLS = {
279228
279228
  };
279229
279229
  function commandExists(cmd) {
279230
279230
  try {
279231
- const result = spawnSync(cmd, ["--version"], { stdio: "pipe" });
279231
+ const result = spawnSync$1(cmd, ["--version"], { stdio: "pipe" });
279232
279232
  return result.error === void 0 || result.error === null;
279233
279233
  } catch {
279234
279234
  return false;
@@ -279289,7 +279289,7 @@ async function downloadTool(tool) {
279289
279289
  mkdirSync$1(extractDir, { recursive: true });
279290
279290
  try {
279291
279291
  if (assetName.endsWith(".tar.gz")) {
279292
- const extractResult = spawnSync("tar", [
279292
+ const extractResult = spawnSync$1("tar", [
279293
279293
  "xzf",
279294
279294
  archivePath,
279295
279295
  "-C",
@@ -279451,7 +279451,7 @@ function createFindTool(cwd, options) {
279451
279451
  } catch {}
279452
279452
  for (const gitignorePath of gitignoreFiles) args.push("--ignore-file", gitignorePath);
279453
279453
  args.push(pattern, searchPath);
279454
- const result = spawnSync(fdPath, args, {
279454
+ const result = spawnSync$1(fdPath, args, {
279455
279455
  encoding: "utf-8",
279456
279456
  maxBuffer: 10 * 1024 * 1024
279457
279457
  });
@@ -279606,7 +279606,7 @@ function createGrepTool(cwd, options) {
279606
279606
  if (literal) args.push("--fixed-strings");
279607
279607
  if (glob) args.push("--glob", glob);
279608
279608
  args.push(pattern, searchPath);
279609
- const child = spawn(rgPath, args, { stdio: [
279609
+ const child = spawn$1(rgPath, args, { stdio: [
279610
279610
  "ignore",
279611
279611
  "pipe",
279612
279612
  "pipe"
@@ -285304,7 +285304,7 @@ function executeCommand(commandConfig) {
285304
285304
  const command = commandConfig.slice(1);
285305
285305
  let result;
285306
285306
  try {
285307
- result = execSync(command, {
285307
+ result = execSync$1(command, {
285308
285308
  encoding: "utf-8",
285309
285309
  timeout: 1e4,
285310
285310
  stdio: [
@@ -289898,7 +289898,7 @@ var DefaultPackageManager = class {
289898
289898
  }
289899
289899
  runCommand(command, args, options) {
289900
289900
  return new Promise((resolvePromise, reject) => {
289901
- const child = spawn$1(command, args, {
289901
+ const child = spawn(command, args, {
289902
289902
  cwd: options?.cwd,
289903
289903
  stdio: "inherit",
289904
289904
  shell: process.platform === "win32"
@@ -289911,7 +289911,7 @@ var DefaultPackageManager = class {
289911
289911
  });
289912
289912
  }
289913
289913
  runCommandSync(command, args) {
289914
- const result = spawnSync$1(command, args, {
289914
+ const result = spawnSync(command, args, {
289915
289915
  stdio: [
289916
289916
  "ignore",
289917
289917
  "pipe",
@@ -292560,7 +292560,7 @@ var SessionList = class {
292560
292560
  * Delete a session file, trying the `trash` CLI first, then falling back to unlink
292561
292561
  */
292562
292562
  async function deleteSessionFile(sessionPath) {
292563
- const trashResult = spawnSync$1("trash", sessionPath.startsWith("-") ? ["--", sessionPath] : [sessionPath], { encoding: "utf-8" });
292563
+ const trashResult = spawnSync("trash", sessionPath.startsWith("-") ? ["--", sessionPath] : [sessionPath], { encoding: "utf-8" });
292564
292564
  const getTrashErrorHint = () => {
292565
292565
  const parts = [];
292566
292566
  if (trashResult.error) parts.push(trashResult.error.message);
@@ -293321,7 +293321,7 @@ async function convertToPng$1(bytes) {
293321
293321
  }
293322
293322
  }
293323
293323
  function runCommand(command, args, options) {
293324
- const result = spawnSync(command, args, {
293324
+ const result = spawnSync$1(command, args, {
293325
293325
  timeout: options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS,
293326
293326
  maxBuffer: options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES
293327
293327
  });
@@ -293418,16 +293418,16 @@ function copyToClipboard(text) {
293418
293418
  timeout: 5e3
293419
293419
  };
293420
293420
  try {
293421
- if (p === "darwin") execSync("pbcopy", options);
293422
- else if (p === "win32") execSync("clip", options);
293421
+ if (p === "darwin") execSync$1("pbcopy", options);
293422
+ else if (p === "win32") execSync$1("clip", options);
293423
293423
  else {
293424
293424
  if (process.env.TERMUX_VERSION) try {
293425
- execSync("termux-clipboard-set", options);
293425
+ execSync$1("termux-clipboard-set", options);
293426
293426
  return;
293427
293427
  } catch {}
293428
293428
  if (isWaylandSession()) try {
293429
- execSync("which wl-copy", { stdio: "ignore" });
293430
- const proc = spawn("wl-copy", [], { stdio: [
293429
+ execSync$1("which wl-copy", { stdio: "ignore" });
293430
+ const proc = spawn$1("wl-copy", [], { stdio: [
293431
293431
  "pipe",
293432
293432
  "ignore",
293433
293433
  "ignore"
@@ -293438,15 +293438,15 @@ function copyToClipboard(text) {
293438
293438
  proc.unref();
293439
293439
  } catch {
293440
293440
  try {
293441
- execSync("xclip -selection clipboard", options);
293441
+ execSync$1("xclip -selection clipboard", options);
293442
293442
  } catch {
293443
- execSync("xsel --clipboard --input", options);
293443
+ execSync$1("xsel --clipboard --input", options);
293444
293444
  }
293445
293445
  }
293446
293446
  else try {
293447
- execSync("xclip -selection clipboard", options);
293447
+ execSync$1("xclip -selection clipboard", options);
293448
293448
  } catch {
293449
- execSync("xsel --clipboard --input", options);
293449
+ execSync$1("xsel --clipboard --input", options);
293450
293450
  }
293451
293451
  }
293452
293452
  } catch {}
@@ -294465,7 +294465,7 @@ var ExtensionEditorComponent = class extends Container {
294465
294465
  fs$6.writeFileSync(tmpFile, currentText, "utf-8");
294466
294466
  this.tui.stop();
294467
294467
  const [editor, ...editorArgs] = editorCmd.split(" ");
294468
- if (spawnSync$1(editor, [...editorArgs, tmpFile], { stdio: "inherit" }).status === 0) {
294468
+ if (spawnSync(editor, [...editorArgs, tmpFile], { stdio: "inherit" }).status === 0) {
294469
294469
  const newContent = fs$6.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
294470
294470
  this.editor.setText(newContent);
294471
294471
  }
@@ -298986,7 +298986,7 @@ var InteractiveMode = class InteractiveMode {
298986
298986
  fs$6.writeFileSync(tmpFile, currentText, "utf-8");
298987
298987
  this.ui.stop();
298988
298988
  const [editor, ...editorArgs] = editorCmd.split(" ");
298989
- if (spawnSync(editor, [...editorArgs, tmpFile], { stdio: "inherit" }).status === 0) {
298989
+ if (spawnSync$1(editor, [...editorArgs, tmpFile], { stdio: "inherit" }).status === 0) {
298990
298990
  const newContent = fs$6.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
298991
298991
  this.editor.setText(newContent);
298992
298992
  }
@@ -299749,7 +299749,7 @@ var InteractiveMode = class InteractiveMode {
299749
299749
  }
299750
299750
  async handleShareCommand() {
299751
299751
  try {
299752
- if (spawnSync("gh", ["auth", "status"], { encoding: "utf-8" }).status !== 0) {
299752
+ if (spawnSync$1("gh", ["auth", "status"], { encoding: "utf-8" }).status !== 0) {
299753
299753
  this.showError("GitHub CLI is not logged in. Run 'gh auth login' first.");
299754
299754
  return;
299755
299755
  }
@@ -299786,7 +299786,7 @@ var InteractiveMode = class InteractiveMode {
299786
299786
  };
299787
299787
  try {
299788
299788
  const result = await new Promise((resolve) => {
299789
- proc = spawn("gh", [
299789
+ proc = spawn$1("gh", [
299790
299790
  "gist",
299791
299791
  "create",
299792
299792
  "--public=false",
@@ -302538,14 +302538,14 @@ function parseSchedule(params) {
302538
302538
  }
302539
302539
  function createCronExtension() {
302540
302540
  return function cronExtension(pi) {
302541
- pi.on("before_agent_start", async () => {
302541
+ pi.on("before_agent_start", async (event) => {
302542
302542
  const now = /* @__PURE__ */ new Date();
302543
302543
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
302544
302544
  const local = now.toLocaleString("sv-SE", {
302545
302545
  timeZone: timezone,
302546
302546
  hour12: false
302547
302547
  }).replace(" ", "T");
302548
- return { systemPrompt: `\n\nYou can manage scheduled tasks with the cron tool.\nCurrent UTC time: ${now.toISOString()}\nCurrent timezone: ${timezone} (local: ${local})` };
302548
+ return { systemPrompt: `${event.systemPrompt}\n\nYou can manage scheduled tasks with the cron tool.\nCurrent UTC time: ${now.toISOString()}\nCurrent timezone: ${timezone} (local: ${local})` };
302549
302549
  });
302550
302550
  pi.registerTool({
302551
302551
  name: "cron",
@@ -303414,6 +303414,10 @@ function createWorkspaceJailExtension(workspaceDir, allowedPaths = []) {
303414
303414
  * This is defense-in-depth, not a complete sandbox.
303415
303415
  */
303416
303416
  function scanBashCommand(command) {
303417
+ if (/(^|\s)(pi\s+(install|remove|update))(\s|$)/.test(command)) return {
303418
+ allowed: false,
303419
+ reason: "Blocked: use the manage_packages tool (or Settings → Extensions install UI) instead of running `pi install/remove/update` in bash."
303420
+ };
303417
303421
  const absolutePathPattern = /(?:^|\s)([~/][\w\-./]+)/g;
303418
303422
  let match;
303419
303423
  while ((match = absolutePathPattern.exec(command)) !== null) {
@@ -303468,8 +303472,9 @@ function createWorkspaceJailExtension(workspaceDir, allowedPaths = []) {
303468
303472
  }
303469
303473
  }
303470
303474
  });
303471
- pi.on("before_agent_start", async () => {
303472
- return { systemPrompt: `\n\nIMPORTANT: You are restricted to working within the directory: ${workspaceDir}${normalizedAllowedPaths.length ? `\nAlso allowed: ${normalizedAllowedPaths.join(", ")}` : ""}
303475
+ pi.on("before_agent_start", async (event) => {
303476
+ const allowedPathsNote = normalizedAllowedPaths.length ? `\nAlso allowed: ${normalizedAllowedPaths.join(", ")}` : "";
303477
+ return { systemPrompt: `${event.systemPrompt}\n\nIMPORTANT: You are restricted to working within the directory: ${workspaceDir}${allowedPathsNote}
303473
303478
  Do not access files, run commands, or reference paths outside the allowed directories.` };
303474
303479
  });
303475
303480
  };
@@ -303943,6 +303948,147 @@ function createHeartbeatExtension() {
303943
303948
  };
303944
303949
  }
303945
303950
  //#endregion
303951
+ //#region src/extensions/package-manager.ts
303952
+ const PackageManagerParamsSchema = Type$1.Object({
303953
+ action: StringEnum([
303954
+ "install",
303955
+ "remove",
303956
+ "update",
303957
+ "list"
303958
+ ]),
303959
+ source: Type$1.Optional(Type$1.String({ description: "Package source, e.g. npm:@scope/pkg, git:github.com/user/repo, or local path" })),
303960
+ scope: Type$1.Optional(StringEnum(["user", "project"]))
303961
+ });
303962
+ function stringifyPackageSource(source) {
303963
+ if (typeof source === "string") return source;
303964
+ return source.source;
303965
+ }
303966
+ function createPackageManagerExtension(reloadAllSessions) {
303967
+ return function packageManagerExtension(pi) {
303968
+ let pendingReload = false;
303969
+ pi.on("agent_end", () => {
303970
+ if (!pendingReload) return;
303971
+ pendingReload = false;
303972
+ setTimeout(() => {
303973
+ reloadAllSessions().catch((err) => {
303974
+ console.error("[package-manager] Failed to reload sessions:", err);
303975
+ });
303976
+ }, 0);
303977
+ });
303978
+ pi.registerTool({
303979
+ name: "manage_packages",
303980
+ label: "Manage Packages",
303981
+ description: "Install, remove, update, or list clankie packages in the correct clankie directories. Defaults to user scope.",
303982
+ parameters: PackageManagerParamsSchema,
303983
+ async execute(_toolCallId, rawParams) {
303984
+ const params = rawParams;
303985
+ const config = loadConfig();
303986
+ const cwd = getWorkspace(config);
303987
+ const agentDir = getAgentDir(config);
303988
+ const settingsManager = SettingsManager.create(cwd, agentDir);
303989
+ const packageManager = new DefaultPackageManager({
303990
+ cwd,
303991
+ agentDir,
303992
+ settingsManager
303993
+ });
303994
+ const output = [];
303995
+ packageManager.setProgressCallback((event) => {
303996
+ if (!event.message) return;
303997
+ output.push(event.message);
303998
+ });
303999
+ const scope = params.scope ?? "user";
304000
+ const local = scope === "project";
304001
+ const detailsBase = {
304002
+ action: params.action,
304003
+ scope,
304004
+ source: params.source?.trim() ?? null,
304005
+ output
304006
+ };
304007
+ try {
304008
+ switch (params.action) {
304009
+ case "list": {
304010
+ const globalPackages = (settingsManager.getGlobalSettings().packages ?? []).map(stringifyPackageSource);
304011
+ const projectPackages = (settingsManager.getProjectSettings().packages ?? []).map(stringifyPackageSource);
304012
+ return {
304013
+ content: [{
304014
+ type: "text",
304015
+ text: [
304016
+ "Configured package sources:",
304017
+ `- User (${globalPackages.length}): ${globalPackages.length > 0 ? globalPackages.join(", ") : "(none)"}`,
304018
+ `- Project (${projectPackages.length}): ${projectPackages.length > 0 ? projectPackages.join(", ") : "(none)"}`
304019
+ ].join("\n")
304020
+ }],
304021
+ details: {
304022
+ ...detailsBase,
304023
+ globalPackages,
304024
+ projectPackages
304025
+ }
304026
+ };
304027
+ }
304028
+ case "install": {
304029
+ if (!params.source?.trim()) throw new Error("install requires source");
304030
+ const source = params.source.trim();
304031
+ await packageManager.install(source, { local });
304032
+ packageManager.addSourceToSettings(source, { local });
304033
+ pendingReload = true;
304034
+ return {
304035
+ content: [{
304036
+ type: "text",
304037
+ text: output.join("\n") || `Installed ${source} in ${scope} scope and scheduled runtime reload.`
304038
+ }],
304039
+ details: {
304040
+ ...detailsBase,
304041
+ source
304042
+ }
304043
+ };
304044
+ }
304045
+ case "remove": {
304046
+ if (!params.source?.trim()) throw new Error("remove requires source");
304047
+ const source = params.source.trim();
304048
+ await packageManager.remove(source, { local });
304049
+ packageManager.removeSourceFromSettings(source, { local });
304050
+ pendingReload = true;
304051
+ return {
304052
+ content: [{
304053
+ type: "text",
304054
+ text: output.join("\n") || `Removed ${source} from ${scope} scope and scheduled runtime reload.`
304055
+ }],
304056
+ details: {
304057
+ ...detailsBase,
304058
+ source
304059
+ }
304060
+ };
304061
+ }
304062
+ case "update": {
304063
+ const source = params.source?.trim();
304064
+ await packageManager.update(source && source.length > 0 ? source : void 0);
304065
+ pendingReload = true;
304066
+ return {
304067
+ content: [{
304068
+ type: "text",
304069
+ text: output.join("\n") || `Updated ${source && source.length > 0 ? source : "all configured packages"} and scheduled runtime reload.`
304070
+ }],
304071
+ details: {
304072
+ ...detailsBase,
304073
+ source: source ?? null
304074
+ }
304075
+ };
304076
+ }
304077
+ }
304078
+ } catch (err) {
304079
+ return {
304080
+ content: [{
304081
+ type: "text",
304082
+ text: `Package manager error: ${err instanceof Error ? err.message : String(err)}`
304083
+ }],
304084
+ details: detailsBase
304085
+ };
304086
+ }
304087
+ }
304088
+ });
304089
+ };
304090
+ }
304091
+ //#endregion
303946
304092
  //#region src/extensions/reload-runtime.ts
303947
304093
  /**
303948
304094
  * Create the reload runtime extension factory.
@@ -304327,6 +304473,7 @@ function buildExtensionFactories(config, cwd) {
304327
304473
  const extensionFactories = [];
304328
304474
  extensionFactories.push(createCronExtension());
304329
304475
  extensionFactories.push(createHeartbeatExtension());
304476
+ extensionFactories.push(createPackageManagerExtension(reloadAllSessions));
304330
304477
  extensionFactories.push(createReloadRuntimeExtension(reloadAllSessions));
304331
304478
  if (config.agent?.restrictToWorkspace ?? true) {
304332
304479
  const configuredAllowedPaths = config.agent?.allowedPaths ?? [];
@@ -308691,7 +308838,7 @@ function execSafe(cmd) {
308691
308838
  try {
308692
308839
  return {
308693
308840
  ok: true,
308694
- stdout: execSync$1(cmd, {
308841
+ stdout: execSync(cmd, {
308695
308842
  encoding: "utf-8",
308696
308843
  stdio: [
308697
308844
  "pipe",
@@ -308880,6 +309027,51 @@ async function uninstallService() {
308880
309027
  process.exit(1);
308881
309028
  }
308882
309029
  }
309030
+ function hasInstalledService() {
309031
+ if (isMac) return existsSync(launchdPlistPath());
309032
+ if (isLinux) return existsSync(systemdUnitPath());
309033
+ return false;
309034
+ }
309035
+ function restartInstalledService() {
309036
+ if (isMac) {
309037
+ const plistPath = launchdPlistPath();
309038
+ if (!existsSync(plistPath)) {
309039
+ console.error("Clankie is not installed as a launchd agent.");
309040
+ process.exit(1);
309041
+ }
309042
+ const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
309043
+ if (uid === void 0) {
309044
+ console.error("Could not determine current user ID for launchd restart.");
309045
+ process.exit(1);
309046
+ }
309047
+ if (!execSafe(`launchctl bootout gui/${uid} "${plistPath}"`).ok) execSafe(`launchctl unload "${plistPath}"`);
309048
+ const bootstrap = execSafe(`launchctl bootstrap gui/${uid} "${plistPath}"`);
309049
+ if (!bootstrap.ok) {
309050
+ const load = execSafe(`launchctl load "${plistPath}"`);
309051
+ if (!load.ok) {
309052
+ console.error(`launchctl restart failed: ${bootstrap.stderr || load.stderr}`);
309053
+ process.exit(1);
309054
+ }
309055
+ }
309056
+ console.log(`Restarted launchd agent: ${LAUNCHD_LABEL}`);
309057
+ return;
309058
+ }
309059
+ if (isLinux) {
309060
+ if (!existsSync(systemdUnitPath())) {
309061
+ console.error("Clankie is not installed as a systemd user service.");
309062
+ process.exit(1);
309063
+ }
309064
+ const restart = execSafe(`systemctl --user restart ${SERVICE_NAME}.service`);
309065
+ if (!restart.ok) {
309066
+ console.error(`systemd restart failed: ${restart.stderr || restart.stdout}`);
309067
+ process.exit(1);
309068
+ }
309069
+ console.log(`Restarted systemd service: ${SERVICE_NAME}.service`);
309070
+ return;
309071
+ }
309072
+ console.error(`Service management not supported on ${platform$1()}.`);
309073
+ process.exit(1);
309074
+ }
308883
309075
  function showServiceLogs() {
308884
309076
  if (isMac) logsLaunchd();
308885
309077
  else if (isLinux) logsSystemd();
@@ -308908,6 +309100,8 @@ function showServiceStatus() {
308908
309100
  * clankie start Start the daemon (channels + agent)
308909
309101
  * clankie stop Stop the daemon
308910
309102
  * clankie restart Restart the daemon
309103
+ * clankie update Update clankie via npm and restart daemon/service if needed
309104
+ * clankie self-update Alias for update
308911
309105
  * clankie status Check daemon status
308912
309106
  * clankie daemon install Install as a system service (systemd/launchd)
308913
309107
  * clankie daemon uninstall Remove the system service
@@ -308929,6 +309123,8 @@ Usage:
308929
309123
  clankie start [--foreground] Start the daemon (foreground by default)
308930
309124
  clankie stop Stop the daemon
308931
309125
  clankie restart Restart the daemon
309126
+ clankie update Update clankie via npm and restart daemon/service if needed
309127
+ clankie self-update Alias for update
308932
309128
  clankie status Check if daemon is running
308933
309129
  clankie daemon install Install as a system service (systemd/launchd)
308934
309130
  clankie daemon uninstall Remove the system service
@@ -308978,15 +309174,40 @@ Examples:
308978
309174
  Credentials are stored at ~/.clankie/auth.json (separate from pi's auth).
308979
309175
  `);
308980
309176
  }
308981
- function printVersion() {
309177
+ function getCurrentVersion() {
308982
309178
  const packagePath = join(import.meta.dirname, "..", "package.json");
308983
309179
  try {
308984
309180
  const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
308985
- console.log(`clankie ${pkg.version}`);
309181
+ return typeof pkg.version === "string" ? pkg.version : void 0;
308986
309182
  } catch {
308987
- console.log("clankie (version unknown)");
309183
+ return;
308988
309184
  }
308989
309185
  }
309186
+ function printVersion() {
309187
+ const version = getCurrentVersion();
309188
+ if (version) console.log(`clankie ${version}`);
309189
+ else console.log("clankie (version unknown)");
309190
+ }
309191
+ function getGlobalNpmRoot() {
309192
+ const result = spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
309193
+ if (result.status !== 0) return void 0;
309194
+ return result.stdout.trim() || void 0;
309195
+ }
309196
+ function isInstalledFromGlobalNpm() {
309197
+ const globalRoot = getGlobalNpmRoot();
309198
+ if (!globalRoot) return false;
309199
+ const packageRoot = join(import.meta.dirname, "..");
309200
+ return packageRoot === join(globalRoot, "clankie") || packageRoot.startsWith(`${join(globalRoot, "clankie")}/`);
309201
+ }
309202
+ function getLatestPublishedVersion() {
309203
+ const result = spawnSync("npm", [
309204
+ "view",
309205
+ "clankie",
309206
+ "version"
309207
+ ], { encoding: "utf-8" });
309208
+ if (result.status !== 0) return void 0;
309209
+ return result.stdout.trim() || void 0;
309210
+ }
308990
309211
  async function cmdSend(args) {
308991
309212
  const message = args.join(" ").trim();
308992
309213
  if (!message) {
@@ -309148,6 +309369,47 @@ function cmdStop() {
309148
309369
  async function cmdRestart() {
309149
309370
  await restartDaemon();
309150
309371
  }
309372
+ async function cmdUpdate() {
309373
+ const packageName = "clankie";
309374
+ const currentVersion = getCurrentVersion();
309375
+ const latestVersion = getLatestPublishedVersion();
309376
+ const serviceInstalled = hasInstalledService();
309377
+ const daemonRunning = isRunning().running;
309378
+ if (!isInstalledFromGlobalNpm()) {
309379
+ console.error("Self-update only supports npm global installs.");
309380
+ console.error("Please update clankie with the same package manager or workflow you used to install it.");
309381
+ process.exit(1);
309382
+ }
309383
+ if (currentVersion && latestVersion) {
309384
+ console.log(`Current version: ${currentVersion}`);
309385
+ console.log(`Latest version: ${latestVersion}`);
309386
+ if (currentVersion === latestVersion) {
309387
+ console.log("clankie is already up to date.");
309388
+ return;
309389
+ }
309390
+ console.log();
309391
+ } else console.log("Checking latest version via npm failed or returned no version; proceeding with update.");
309392
+ console.log(`Updating ${packageName} via npm...`);
309393
+ const update = spawnSync("npm", [
309394
+ "install",
309395
+ "-g",
309396
+ `${packageName}@latest`
309397
+ ], { stdio: "inherit" });
309398
+ if (update.status !== 0) process.exit(update.status ?? 1);
309399
+ console.log("\n✓ clankie updated.");
309400
+ if (serviceInstalled) {
309401
+ console.log("Restarting installed service...");
309402
+ restartInstalledService();
309403
+ return;
309404
+ }
309405
+ if (daemonRunning) {
309406
+ console.log("Restarting daemon with the updated CLI...");
309407
+ const restart = spawnSync("clankie", ["restart"], { stdio: "inherit" });
309408
+ if (restart.status !== 0) process.exit(restart.status ?? 1);
309409
+ return;
309410
+ }
309411
+ console.log("Daemon is not running. Start it with 'clankie start' when ready.");
309412
+ }
309151
309413
  function cmdStatus() {
309152
309414
  const status = isRunning();
309153
309415
  if (status.running) console.log(`Daemon is running (pid ${status.pid}).`);
@@ -309271,6 +309533,10 @@ async function main() {
309271
309533
  case "restart":
309272
309534
  await cmdRestart();
309273
309535
  break;
309536
+ case "update":
309537
+ case "self-update":
309538
+ await cmdUpdate();
309539
+ break;
309274
309540
  case "status":
309275
309541
  cmdStatus();
309276
309542
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clankie",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "A minimal personal AI assistant built on pi's SDK",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "dist/",
11
11
  "web-ui-dist/",
12
+ "skills/",
12
13
  "package.json",
13
14
  "README.md"
14
15
  ],
@@ -21,7 +22,7 @@
21
22
  "check:fix": "biome check --fix .",
22
23
  "format": "biome format --write .",
23
24
  "typecheck": "tsgo --noEmit",
24
- "typecheck:all": "tsgo --noEmit && cd web-ui && npx tsgo --noEmit && cd ../extensions/clankie-memory && npx tsgo --noEmit"
25
+ "typecheck:all": "tsgo --noEmit && cd web-ui && npx tsgo --noEmit && cd ../extensions/clankie-memory && npx tsgo --noEmit && cd ../clankie-json-ui-render && npx tsgo --noEmit"
25
26
  },
26
27
  "engines": {
27
28
  "node": ">=18.0.0"