clikit-plugin 0.1.9 → 0.2.1

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
@@ -6,9 +6,17 @@ import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import * as os from "os";
8
8
  var PLUGIN_NAME = "clikit-plugin";
9
- var VERSION = "0.1.9";
9
+ var VERSION = "0.2.0";
10
10
  function getRealHome() {
11
- return process.env.SNAP_REAL_HOME || os.homedir();
11
+ if (process.env.SNAP_REAL_HOME) {
12
+ return process.env.SNAP_REAL_HOME;
13
+ }
14
+ const home = os.homedir();
15
+ const snapMatch = home.match(/^(\/home\/[^/]+)\/snap\//);
16
+ if (snapMatch) {
17
+ return snapMatch[1];
18
+ }
19
+ return home;
12
20
  }
13
21
  function getConfigDir() {
14
22
  const home = getRealHome();
@@ -40,11 +48,25 @@ function parseConfig(configPath) {
40
48
  }
41
49
  const content = fs.readFileSync(configPath, "utf-8");
42
50
  const cleaned = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
43
- return JSON.parse(cleaned);
44
- } catch {
51
+ const cleanedTrailing = cleaned.replace(/,\s*([}\]])/g, "$1");
52
+ return JSON.parse(cleanedTrailing);
53
+ } catch (err) {
54
+ console.error(`Warning: Failed to parse config at ${configPath}: ${err}`);
45
55
  return {};
46
56
  }
47
57
  }
58
+ function backupConfig(configPath) {
59
+ try {
60
+ if (!fs.existsSync(configPath)) {
61
+ return null;
62
+ }
63
+ const backupPath = `${configPath}.backup-${Date.now()}`;
64
+ fs.copyFileSync(configPath, backupPath);
65
+ return backupPath;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
48
70
  function writeConfig(configPath, config) {
49
71
  ensureConfigDir();
50
72
  const tmpPath = `${configPath}.tmp`;
@@ -67,12 +89,20 @@ async function install() {
67
89
  }
68
90
  const configPath = getConfigPath();
69
91
  try {
70
- const config = parseConfig(configPath) || {};
71
- const plugins = config.plugin || [];
72
- const filteredPlugins = plugins.filter((p) => p !== PLUGIN_NAME && !p.startsWith(`${PLUGIN_NAME}@`));
92
+ const backupPath = backupConfig(configPath);
93
+ if (backupPath) {
94
+ console.log(` Backup created: ${backupPath}`);
95
+ }
96
+ const config = parseConfig(configPath);
97
+ const existingPlugins = Array.isArray(config.plugin) ? config.plugin : [];
98
+ const preservedKeys = Object.keys(config).filter((k) => k !== "plugin");
99
+ if (preservedKeys.length > 0) {
100
+ console.log(` Preserving existing config: ${preservedKeys.join(", ")}`);
101
+ }
102
+ const filteredPlugins = existingPlugins.filter((p) => p !== PLUGIN_NAME && !p.startsWith(`${PLUGIN_NAME}@`));
73
103
  filteredPlugins.push(PLUGIN_NAME);
74
- config.plugin = filteredPlugins;
75
- writeConfig(configPath, config);
104
+ const newConfig = { ...config, plugin: filteredPlugins };
105
+ writeConfig(configPath, newConfig);
76
106
  console.log(`\u2713 Plugin added to ${configPath}`);
77
107
  } catch (err) {
78
108
  console.error(`\u2717 Failed to update OpenCode config: ${err}`);
@@ -11,7 +11,7 @@ export interface CommentCheckResult {
11
11
  totalLines: number;
12
12
  ratio: number;
13
13
  }
14
- export declare function checkCommentDensity(content: string, threshold?: number): CommentCheckResult;
15
- export declare function hasExcessiveAIComments(content: string): boolean;
14
+ export declare function checkCommentDensity(content: unknown, threshold?: number): CommentCheckResult;
15
+ export declare function hasExcessiveAIComments(content: unknown): boolean;
16
16
  export declare function formatCommentWarning(result: CommentCheckResult): string;
17
17
  //# sourceMappingURL=comment-checker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"comment-checker.d.ts","sourceRoot":"","sources":["../../src/hooks/comment-checker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,kBAAkB,CAkDhG;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAS/D;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAGvE"}
1
+ {"version":3,"file":"comment-checker.d.ts","sourceRoot":"","sources":["../../src/hooks/comment-checker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,GAAE,MAAY,GAAG,kBAAkB,CAqDjG;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAYhE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAGvE"}
@@ -1 +1 @@
1
- {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/hooks/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAuFzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,SAAS,EAAE,CAyDlF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,GAAE,MAAa,GAAG,MAAM,CA2ChG;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,gBAAgB,GACxB,iBAAiB,CAYnB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAMtE"}
1
+ {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/hooks/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAuFzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,SAAS,EAAE,CA2DlF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,GAAE,MAAa,GAAG,MAAM,CA4ChG;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,gBAAgB,GACxB,iBAAiB,CAYnB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAMtE"}
@@ -1 +1 @@
1
- {"version":3,"file":"security-check.d.ts","sourceRoot":"","sources":["../../src/hooks/security-check.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyBH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAkB7F;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAUzE"}
1
+ {"version":3,"file":"security-check.d.ts","sourceRoot":"","sources":["../../src/hooks/security-check.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyBH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAmB7F;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAUzE"}
@@ -17,7 +17,7 @@ export interface NotificationPayload {
17
17
  urgency?: "low" | "normal" | "critical";
18
18
  }
19
19
  export declare function sendNotification(payload: NotificationPayload): boolean;
20
- export declare function buildIdleNotification(sessionId?: string, prefix?: string): NotificationPayload;
21
- export declare function buildErrorNotification(error: unknown, sessionId?: string, prefix?: string): NotificationPayload;
20
+ export declare function buildIdleNotification(sessionId?: unknown, prefix?: string): NotificationPayload;
21
+ export declare function buildErrorNotification(error: unknown, sessionId?: unknown, prefix?: string): NotificationPayload;
22
22
  export declare function formatNotificationLog(payload: NotificationPayload, sent: boolean): string;
23
23
  //# sourceMappingURL=session-notification.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-notification.d.ts","sourceRoot":"","sources":["../../src/hooks/session-notification.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,CAAC;CACzC;AA0BD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAsBtE;AAED,wBAAgB,qBAAqB,CACnC,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,mBAAmB,CASrB;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,mBAAmB,CAcrB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAIzF"}
1
+ {"version":3,"file":"session-notification.d.ts","sourceRoot":"","sources":["../../src/hooks/session-notification.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,CAAC;CACzC;AA0BD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAsBtE;AAED,wBAAgB,qBAAqB,CACnC,SAAS,CAAC,EAAE,OAAO,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,mBAAmB,CAUrB;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,SAAS,CAAC,EAAE,OAAO,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,mBAAmB,CAerB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAIzF"}
@@ -1 +1 @@
1
- {"version":3,"file":"truncator.d.ts","sourceRoot":"","sources":["../../src/hooks/truncator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAUT;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,eAAe,GACvB,cAAc,CA2EhB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAIlE"}
1
+ {"version":3,"file":"truncator.d.ts","sourceRoot":"","sources":["../../src/hooks/truncator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAYT;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,eAAe,GACvB,cAAc,CAsFhB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAIlE"}
package/dist/index.js CHANGED
@@ -3917,6 +3917,8 @@ var SENSITIVE_FILES = [
3917
3917
  /id_ed25519$/
3918
3918
  ];
3919
3919
  function scanContentForSecrets(content, filename) {
3920
+ if (typeof content !== "string")
3921
+ return { safe: true, findings: [] };
3920
3922
  const findings = [];
3921
3923
  const lines = content.split(`
3922
3924
  `);
@@ -3984,6 +3986,9 @@ var EXCESSIVE_COMMENT_PATTERNS = [
3984
3986
  /#\s*(?:This|The|A|An) (?:function|method|class|script)/i
3985
3987
  ];
3986
3988
  function checkCommentDensity(content, threshold = 0.3) {
3989
+ if (typeof content !== "string") {
3990
+ return { excessive: false, count: 0, totalLines: 0, ratio: 0 };
3991
+ }
3987
3992
  const lines = content.split(`
3988
3993
  `);
3989
3994
  const totalLines = lines.filter((l) => l.trim().length > 0).length;
@@ -4026,6 +4031,9 @@ function checkCommentDensity(content, threshold = 0.3) {
4026
4031
  };
4027
4032
  }
4028
4033
  function hasExcessiveAIComments(content) {
4034
+ if (typeof content !== "string") {
4035
+ return false;
4036
+ }
4029
4037
  let matches = 0;
4030
4038
  for (const pattern of EXCESSIVE_COMMENT_PATTERNS) {
4031
4039
  const found = content.match(new RegExp(pattern, "gm"));
@@ -4424,18 +4432,20 @@ function sendNotification(payload) {
4424
4432
  }
4425
4433
  function buildIdleNotification(sessionId, prefix) {
4426
4434
  const titlePrefix = prefix || "OpenCode";
4435
+ const sid = typeof sessionId === "string" ? sessionId : undefined;
4427
4436
  return {
4428
4437
  title: `${titlePrefix} \u2014 Task Complete`,
4429
- body: sessionId ? `Session ${sessionId.substring(0, 8)} is idle and waiting for input.` : "Session is idle and waiting for input.",
4438
+ body: sid ? `Session ${sid.substring(0, 8)} is idle and waiting for input.` : "Session is idle and waiting for input.",
4430
4439
  urgency: "normal"
4431
4440
  };
4432
4441
  }
4433
4442
  function buildErrorNotification(error, sessionId, prefix) {
4434
4443
  const titlePrefix = prefix || "OpenCode";
4435
4444
  const errorStr = typeof error === "string" ? error : error instanceof Error ? error.message : String(error);
4445
+ const sid = typeof sessionId === "string" ? sessionId : undefined;
4436
4446
  return {
4437
4447
  title: `${titlePrefix} \u2014 Error`,
4438
- body: sessionId ? `Session ${sessionId.substring(0, 8)}: ${errorStr.substring(0, 100)}` : errorStr.substring(0, 120),
4448
+ body: sid ? `Session ${sid.substring(0, 8)}: ${errorStr.substring(0, 100)}` : errorStr.substring(0, 120),
4439
4449
  urgency: "critical"
4440
4450
  };
4441
4451
  }
@@ -4448,6 +4458,8 @@ var DEFAULT_MAX_LINES = 500;
4448
4458
  var DEFAULT_HEAD_LINES = 50;
4449
4459
  var DEFAULT_TAIL_LINES = 50;
4450
4460
  function shouldTruncate(content, config) {
4461
+ if (typeof content !== "string")
4462
+ return false;
4451
4463
  const maxChars = config?.max_output_chars ?? DEFAULT_MAX_CHARS;
4452
4464
  const maxLines = config?.max_output_lines ?? DEFAULT_MAX_LINES;
4453
4465
  if (content.length > maxChars)
@@ -4459,6 +4471,16 @@ function shouldTruncate(content, config) {
4459
4471
  return false;
4460
4472
  }
4461
4473
  function truncateOutput(content, config) {
4474
+ if (typeof content !== "string") {
4475
+ return {
4476
+ truncated: false,
4477
+ originalLength: 0,
4478
+ truncatedLength: 0,
4479
+ originalLines: 0,
4480
+ truncatedLines: 0,
4481
+ content: ""
4482
+ };
4483
+ }
4462
4484
  const maxChars = config?.max_output_chars ?? DEFAULT_MAX_CHARS;
4463
4485
  const maxLines = config?.max_output_lines ?? DEFAULT_MAX_LINES;
4464
4486
  const headLines = config?.preserve_head_lines ?? DEFAULT_HEAD_LINES;
@@ -4630,6 +4652,8 @@ function readMemoryRefs(projectDir, limit = 10) {
4630
4652
  const fullPath = path7.join(subdirPath, file);
4631
4653
  const stat = fs7.statSync(fullPath);
4632
4654
  const raw = fs7.readFileSync(fullPath, "utf-8");
4655
+ if (typeof raw !== "string" || !raw.trim())
4656
+ continue;
4633
4657
  let summary;
4634
4658
  const ext = path7.extname(file);
4635
4659
  if (ext === ".md") {
@@ -4637,7 +4661,8 @@ function readMemoryRefs(projectDir, limit = 10) {
4637
4661
  summary = headingMatch ? headingMatch[1] : raw.substring(0, 100).trim();
4638
4662
  } else if (ext === ".json") {
4639
4663
  const parsed = JSON.parse(raw);
4640
- summary = parsed.summary || parsed.title || parsed.content?.substring(0, 100) || "";
4664
+ const content = parsed.content;
4665
+ summary = parsed.summary || parsed.title || (typeof content === "string" ? content.substring(0, 100) : "") || "";
4641
4666
  } else {
4642
4667
  summary = raw.substring(0, 100).trim();
4643
4668
  }
@@ -4698,6 +4723,9 @@ Recent Memory References:`);
4698
4723
  lines.push("</compaction-context>");
4699
4724
  const block = lines.join(`
4700
4725
  `);
4726
+ if (typeof block !== "string")
4727
+ return `<compaction-context>
4728
+ </compaction-context>`;
4701
4729
  if (block.length > maxChars) {
4702
4730
  return block.substring(0, maxChars) + `
4703
4731
  ... [compaction context truncated]
@@ -0,0 +1,140 @@
1
+ ---
2
+ date: 2026-02-15
3
+ phase: implementing
4
+ branch: main
5
+ ---
6
+
7
+ # Handoff: CliKit Plugin Installation & Runtime Fixes
8
+
9
+ ---
10
+
11
+ ## Status Summary
12
+
13
+ CliKit plugin published to npm (v0.1.9 → v0.2.0). Fixed snap/bun path detection and multiple runtime errors. **CRITICAL ISSUE IDENTIFIED**: Installer overwrites `opencode.json` instead of merging with existing config, deleting user's existing settings. Multiple `.split()`, `.substring()` errors still occurring in hooks due to non-string arguments.
14
+
15
+ ---
16
+
17
+ ## Artifacts
18
+
19
+ | Type | Path | Status |
20
+ |------|------|--------|
21
+ | Handoff | `.opencode/memory/handoffs/2026-02-15-installing.md` | 📚 Previous |
22
+ | Config | `~/.config/opencode/opencode.json` | ⚠️ Gets overwritten |
23
+
24
+ ---
25
+
26
+ ## Task Status
27
+
28
+ ### ✅ Completed
29
+ - [x] T-001-T-007: Plugin implementation (10 agents, 19 commands, 48 skills, 14 hooks, 6 tools)
30
+ - [x] Fix snap/bun path detection using `SNAP_REAL_HOME` and regex fallback
31
+ - [x] Fix `buildErrorNotification` to handle Error objects
32
+ - [x] Fix `buildIdleNotification` to handle non-string sessionId
33
+ - [x] Fix `checkCommentDensity` and `hasExcessiveAIComments` to handle non-string content
34
+ - [x] Published v0.1.7, v0.1.8, v0.1.9, v0.2.0 to npm
35
+
36
+ ### 🔄 In Progress
37
+ - [ ] **CRITICAL**: Fix installer to MERGE with existing opencode.json instead of overwriting
38
+ - **Current state:** `parseConfig()` reads existing config, but may not preserve all keys
39
+ - **User report:** "nó ghi đè file opencode.json làm xóa config có sẵn của tôi"
40
+ - **Root cause:** Need to deep merge, not shallow overwrite
41
+
42
+ - [ ] Fix remaining runtime errors in hooks
43
+ - **Files needing defensive type checks:**
44
+ - `src/hooks/truncator.ts` - `shouldTruncate()`, `truncateOutput()`
45
+ - `src/hooks/security-check.ts` - `scanContentForSecrets()`
46
+ - `src/hooks/compaction.ts` - multiple `.substring()` calls
47
+ - **Pattern:** Add `if (typeof content !== "string") return defaultResult;` at function start
48
+
49
+ ### 📋 Not Started
50
+ - [ ] T-009: Verify plugin loads when OpenCode starts
51
+ - [ ] T-010: Test installation in fresh environment
52
+ - [ ] Add automated tests for installer
53
+
54
+ ---
55
+
56
+ ## Files Modified
57
+
58
+ | File | Status | Notes |
59
+ |------|--------|-------|
60
+ | `src/cli.ts` | Modified | Added `getRealHome()` for snap path detection |
61
+ | `src/hooks/session-notification.ts` | Modified | Fixed `buildIdleNotification`, `buildErrorNotification` |
62
+ | `src/hooks/comment-checker.ts` | Modified | Added type guards for content param |
63
+ | `src/index.ts` | Modified | Pass raw error to `buildErrorNotification` |
64
+ | `package.json` | Modified | Version bumps to 0.2.0 |
65
+
66
+ ---
67
+
68
+ ## Git State
69
+
70
+ - **Branch:** `main`
71
+ - **Last commit:** `31b200c` - Update README with simplified installation
72
+ - **Uncommitted changes:** Yes (5 modified files)
73
+
74
+ ---
75
+
76
+ ## Known Issues
77
+
78
+ 1. **CRITICAL: Installer overwrites opencode.json** - deletes user's existing config (mcp, provider settings)
79
+ - Need to deep merge instead of shallow merge
80
+ - Should backup existing config before modifying
81
+
82
+ 2. **Runtime TypeError: `.split()`, `.substring()` on undefined**
83
+ - Multiple hooks receive non-string arguments from OpenCode events
84
+ - Need defensive type checks in ALL functions that call string methods
85
+
86
+ 3. **Snap/bun path mismatch** - partially fixed but user still sees wrong path
87
+ - `bun x` may cache old versions
88
+ - Need to verify `SNAP_REAL_HOME` detection works in user's environment
89
+
90
+ ---
91
+
92
+ ## Next Steps
93
+
94
+ 1. [ ] **FIX INSTALLER** - Merge with existing config, don't overwrite:
95
+ ```typescript
96
+ // In cli.ts, preserve existing config keys
97
+ const existingConfig = parseConfig(configPath);
98
+ const newConfig = { ...existingConfig, plugin: filteredPlugins };
99
+ writeConfig(configPath, newConfig);
100
+ ```
101
+
102
+ 2. [ ] Add defensive type checks to all hooks that use string methods:
103
+ - `truncator.ts`: `shouldTruncate()`, `truncateOutput()`
104
+ - `security-check.ts`: `scanContentForSecrets()`
105
+ - `compaction.ts`: All substring calls
106
+
107
+ 3. [ ] Publish v0.2.1 with fixes
108
+
109
+ 4. [ ] Test: `bun x clikit-plugin@latest install` should preserve existing config
110
+
111
+ ---
112
+
113
+ ## Context for Resumption
114
+
115
+ ### The Core Problem
116
+ The installer's `writeConfig()` function writes the entire config object to disk. The current code does spread, but needs to ensure `parseConfig()` returns complete existing config.
117
+
118
+ ### Solution Approach
119
+ ```typescript
120
+ // cli.ts - install() function:
121
+ const existingConfig = parseConfig(configPath) || {};
122
+ const plugins = Array.isArray(existingConfig.plugin) ? [...existingConfig.plugin] : [];
123
+ // ... modify plugins array ...
124
+ config.plugin = filteredPlugins;
125
+ // existingConfig already has all keys, we just update plugin
126
+ ```
127
+
128
+ ### Files to Review
129
+ - `src/cli.ts:55-108` — Installer logic
130
+ - `src/hooks/truncator.ts` — Needs type guards
131
+ - `src/hooks/security-check.ts` — Needs type guards
132
+
133
+ ---
134
+
135
+ ## Resume Command
136
+
137
+ To resume, use `/resume` and it will:
138
+ 1. Load this handoff
139
+ 2. Check for drift
140
+ 3. Propose fixing installer merge logic first
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clikit-plugin",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "OpenCode plugin with 10 agents, 19 commands, 48 skills, 14 hooks",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",