wispy-cli 0.3.0 → 0.3.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.
Files changed (2) hide show
  1. package/lib/wispy-repl.mjs +88 -1
  2. package/package.json +1 -1
@@ -724,6 +724,20 @@ const TOOL_DEFINITIONS = [
724
724
  required: ["url"],
725
725
  },
726
726
  },
727
+ {
728
+ name: "keychain",
729
+ description: "Manage macOS Keychain secrets. Read (masked), store, or delete credentials. Values are never shown in full — only first 4 + last 4 chars.",
730
+ parameters: {
731
+ type: "object",
732
+ properties: {
733
+ action: { type: "string", enum: ["get", "set", "delete", "list"], description: "get: read secret (masked), set: store secret, delete: remove, list: search" },
734
+ service: { type: "string", description: "Service name (e.g., 'google-ai-key', 'my-api-token')" },
735
+ account: { type: "string", description: "Account name (default: 'wispy')" },
736
+ value: { type: "string", description: "Secret value (only for 'set' action)" },
737
+ },
738
+ required: ["action", "service"],
739
+ },
740
+ },
727
741
  {
728
742
  name: "clipboard",
729
743
  description: "Copy text to clipboard (macOS/Linux) or read current clipboard contents.",
@@ -923,6 +937,10 @@ async function executeTool(name, args) {
923
937
  }
924
938
 
925
939
  case "run_command": {
940
+ // Block direct keychain password reads via run_command — use keychain tool instead
941
+ if (/security\s+find-generic-password.*-w/i.test(args.command)) {
942
+ return { success: false, error: "Use the 'keychain' tool instead of run_command for secrets. It masks sensitive values." };
943
+ }
926
944
  console.log(dim(` $ ${args.command}`));
927
945
  const { stdout, stderr } = await execAsync("/bin/bash", ["-c", args.command], {
928
946
  timeout: 30_000,
@@ -1068,6 +1086,73 @@ async function executeTool(name, args) {
1068
1086
  }
1069
1087
  }
1070
1088
 
1089
+ case "keychain": {
1090
+ const { promisify: prom } = await import("node:util");
1091
+ const { execFile: ef2 } = await import("node:child_process");
1092
+ const exec2 = prom(ef2);
1093
+ const account = args.account ?? "wispy";
1094
+
1095
+ if (process.platform !== "darwin") {
1096
+ return { success: false, error: "Keychain is only supported on macOS" };
1097
+ }
1098
+
1099
+ if (args.action === "get") {
1100
+ try {
1101
+ const { stdout } = await exec2("security", [
1102
+ "find-generic-password", "-s", args.service, "-a", account, "-w"
1103
+ ], { timeout: 5000 });
1104
+ const val = stdout.trim();
1105
+ // NEVER expose full secret — mask middle
1106
+ const masked = val.length > 8
1107
+ ? `${val.slice(0, 4)}${"*".repeat(Math.min(val.length - 8, 20))}${val.slice(-4)}`
1108
+ : "****";
1109
+ return { success: true, service: args.service, account, value_masked: masked, length: val.length };
1110
+ } catch {
1111
+ return { success: false, error: `No keychain entry found for service="${args.service}" account="${account}"` };
1112
+ }
1113
+ }
1114
+
1115
+ if (args.action === "set") {
1116
+ if (!args.value) return { success: false, error: "value is required for set action" };
1117
+ try {
1118
+ // Delete existing first (ignore error if not found)
1119
+ await exec2("security", [
1120
+ "delete-generic-password", "-s", args.service, "-a", account
1121
+ ]).catch(() => {});
1122
+ await exec2("security", [
1123
+ "add-generic-password", "-s", args.service, "-a", account, "-w", args.value
1124
+ ], { timeout: 5000 });
1125
+ return { success: true, message: `Stored secret for service="${args.service}" account="${account}"` };
1126
+ } catch (err) {
1127
+ return { success: false, error: err.message };
1128
+ }
1129
+ }
1130
+
1131
+ if (args.action === "delete") {
1132
+ try {
1133
+ await exec2("security", [
1134
+ "delete-generic-password", "-s", args.service, "-a", account
1135
+ ], { timeout: 5000 });
1136
+ return { success: true, message: `Deleted keychain entry for service="${args.service}"` };
1137
+ } catch {
1138
+ return { success: false, error: `No entry found for service="${args.service}"` };
1139
+ }
1140
+ }
1141
+
1142
+ if (args.action === "list") {
1143
+ try {
1144
+ const { stdout } = await exec2("/bin/bash", ["-c",
1145
+ `security dump-keychain 2>/dev/null | grep -A 4 "\"svce\"" | grep -E "svce|acct" | head -20`
1146
+ ], { timeout: 5000 });
1147
+ return { success: true, entries: stdout.trim() || "No entries found" };
1148
+ } catch {
1149
+ return { success: true, entries: "No entries found" };
1150
+ }
1151
+ }
1152
+
1153
+ return { success: false, error: "action must be get, set, delete, or list" };
1154
+ }
1155
+
1071
1156
  case "clipboard": {
1072
1157
  const { promisify: prom } = await import("node:util");
1073
1158
  const { execFile: ef2 } = await import("node:child_process");
@@ -1439,12 +1524,14 @@ async function buildSystemPrompt(messages = []) {
1439
1524
  " - NEVER reply in Korean when the user wrote in English.",
1440
1525
  "",
1441
1526
  "## Tools",
1442
- "You have these tools: read_file, write_file, file_edit, file_search, run_command, list_directory, git, web_search, web_fetch, clipboard, spawn_agent, spawn_async_agent, pipeline, ralph_loop, update_plan, list_agents, get_agent_result.",
1527
+ "You have 18 tools: read_file, write_file, file_edit, file_search, run_command, list_directory, git, web_search, web_fetch, keychain, clipboard, spawn_agent, spawn_async_agent, pipeline, ralph_loop, update_plan, list_agents, get_agent_result.",
1443
1528
  "- file_edit: for targeted text replacement (prefer over write_file for edits)",
1444
1529
  "- file_search: grep across codebase",
1445
1530
  "- git: any git command",
1446
1531
  "- web_fetch: read URL content",
1532
+ "- keychain: macOS Keychain secrets (ALWAYS use this for secrets, NEVER run_command)",
1447
1533
  "- clipboard: copy/paste system clipboard",
1534
+ "- SECURITY: Never show full API keys or secrets. Always use keychain tool which masks values.",
1448
1535
  "Use them proactively. Briefly mention what you're doing.",
1449
1536
  "",
1450
1537
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "🌿 Wispy — AI workspace assistant with multi-agent orchestration",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Minseo & Poropo",