wispy-cli 2.6.0 → 2.6.2

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/bin/wispy.mjs CHANGED
@@ -881,6 +881,119 @@ if (args[0] === "model") {
881
881
  process.exit(1);
882
882
  }
883
883
 
884
+ // ── config sub-command ────────────────────────────────────────────────────────
885
+ if (args[0] === "config") {
886
+ const { loadConfig, saveConfig, WISPY_DIR, CONFIG_PATH } = await import(
887
+ path.join(__dirname, "..", "core", "config.mjs")
888
+ );
889
+ const sub = args[1];
890
+ const config = await loadConfig();
891
+
892
+ if (!sub || sub === "show") {
893
+ // wispy config — show current config (mask secrets)
894
+ const display = JSON.parse(JSON.stringify(config));
895
+ // Mask API keys
896
+ if (display.providers) {
897
+ for (const [k, v] of Object.entries(display.providers)) {
898
+ if (v.apiKey) v.apiKey = v.apiKey.slice(0, 6) + "..." + v.apiKey.slice(-4);
899
+ }
900
+ }
901
+ if (display.apiKey) display.apiKey = display.apiKey.slice(0, 6) + "..." + display.apiKey.slice(-4);
902
+ console.log(`\n${_bold("Wispy Config")} ${_dim(CONFIG_PATH)}\n`);
903
+ console.log(JSON.stringify(display, null, 2));
904
+ console.log("");
905
+
906
+ } else if (sub === "get") {
907
+ // wispy config get <key>
908
+ const key = args[2];
909
+ if (!key) { console.error(_red("Usage: wispy config get <key>")); process.exit(2); }
910
+ const val = key.split(".").reduce((o, k) => o?.[k], config);
911
+ if (val === undefined) {
912
+ console.log(_dim(`(not set)`));
913
+ } else {
914
+ console.log(typeof val === "object" ? JSON.stringify(val, null, 2) : String(val));
915
+ }
916
+
917
+ } else if (sub === "set") {
918
+ // wispy config set <key> <value>
919
+ const key = args[2];
920
+ const value = args.slice(3).join(" ");
921
+ if (!key || !value) { console.error(_red("Usage: wispy config set <key> <value>")); process.exit(2); }
922
+
923
+ // Parse value
924
+ let parsed = value;
925
+ if (value === "true") parsed = true;
926
+ else if (value === "false") parsed = false;
927
+ else if (/^\d+$/.test(value)) parsed = parseInt(value);
928
+
929
+ // Set nested key
930
+ const keys = key.split(".");
931
+ let obj = config;
932
+ for (let i = 0; i < keys.length - 1; i++) {
933
+ if (!obj[keys[i]]) obj[keys[i]] = {};
934
+ obj = obj[keys[i]];
935
+ }
936
+ obj[keys[keys.length - 1]] = parsed;
937
+ await saveConfig(config);
938
+ console.log(`${_green("✓")} ${key} = ${parsed}`);
939
+
940
+ } else if (sub === "delete" || sub === "unset") {
941
+ // wispy config delete <key>
942
+ const key = args[2];
943
+ if (!key) { console.error(_red("Usage: wispy config delete <key>")); process.exit(2); }
944
+ const keys = key.split(".");
945
+ let obj = config;
946
+ for (let i = 0; i < keys.length - 1; i++) {
947
+ if (!obj[keys[i]]) break;
948
+ obj = obj[keys[i]];
949
+ }
950
+ delete obj[keys[keys.length - 1]];
951
+ await saveConfig(config);
952
+ console.log(`${_green("✓")} ${key} removed`);
953
+
954
+ } else if (sub === "reset") {
955
+ // wispy config reset
956
+ const { confirm } = await import("@inquirer/prompts");
957
+ const yes = await confirm({ message: "Reset all configuration? This cannot be undone.", default: false });
958
+ if (yes) {
959
+ const { writeFile } = await import("node:fs/promises");
960
+ await writeFile(CONFIG_PATH, "{}\n");
961
+ console.log(`${_green("✓")} Config reset. Run 'wispy setup' to reconfigure.`);
962
+ } else {
963
+ console.log(_dim("Cancelled."));
964
+ }
965
+
966
+ } else if (sub === "path") {
967
+ // wispy config path
968
+ console.log(CONFIG_PATH);
969
+
970
+ } else if (sub === "edit") {
971
+ // wispy config edit — open in $EDITOR
972
+ const editor = process.env.EDITOR ?? process.env.VISUAL ?? "nano";
973
+ const { spawn: sp } = await import("node:child_process");
974
+ sp(editor, [CONFIG_PATH], { stdio: "inherit" });
975
+
976
+ } else {
977
+ console.log(`
978
+ ${_bold("wispy config")} — manage configuration
979
+
980
+ ${_cyan("wispy config")} Show current config (keys masked)
981
+ ${_cyan("wispy config get <key>")} Get a specific value (dot notation)
982
+ ${_cyan("wispy config set <key> <val>")} Set a value
983
+ ${_cyan("wispy config delete <key>")} Remove a key
984
+ ${_cyan("wispy config reset")} Reset to defaults
985
+ ${_cyan("wispy config path")} Show config file path
986
+ ${_cyan("wispy config edit")} Open in $EDITOR
987
+
988
+ ${_dim("Examples:")}
989
+ ${_dim("wispy config get defaultProvider")}
990
+ ${_dim("wispy config set security careful")}
991
+ ${_dim("wispy config set language ko")}
992
+ `);
993
+ }
994
+ process.exit(0);
995
+ }
996
+
884
997
  // ── status sub-command ────────────────────────────────────────────────────────
885
998
  if (args[0] === "status") {
886
999
  // Try the enhanced status from onboarding.mjs first
@@ -1909,7 +2022,7 @@ const _KNOWN_COMMANDS = new Set([
1909
2022
  "ws", "trust", "where", "handoff", "skill", "teach", "improve", "dry",
1910
2023
  "setup", "init", "update", "status", "connect", "disconnect", "sync",
1911
2024
  "deploy", "migrate", "cron", "audit", "log", "server", "node", "channel",
1912
- "auth", "tui", "help", "doctor", "completion", "version",
2025
+ "auth", "config", "model", "tui", "help", "doctor", "completion", "version",
1913
2026
  // serve flags (handled below)
1914
2027
  "--serve", "--telegram", "--discord", "--slack", "--server",
1915
2028
  "--help", "-h", "--version", "-v", "--debug", "--tui",
package/core/config.mjs CHANGED
@@ -63,12 +63,25 @@ export const PROVIDERS = {
63
63
  };
64
64
 
65
65
  async function tryKeychainKey(service) {
66
+ if (process.platform !== "darwin") return null;
66
67
  try {
67
68
  const { execFile } = await import("node:child_process");
68
69
  const { promisify } = await import("node:util");
69
70
  const exec = promisify(execFile);
70
- const { stdout } = await exec("security", ["find-generic-password", "-s", service, "-a", "poropo", "-w"], { timeout: 3000 });
71
- return stdout.trim() || null;
71
+
72
+ // Try multiple account names: wispy, poropo (OpenClaw compat), then no account (any)
73
+ const accounts = ["wispy", "poropo"];
74
+ for (const account of accounts) {
75
+ try {
76
+ const { stdout } = await exec("security", ["find-generic-password", "-s", service, "-a", account, "-w"], { timeout: 2000 });
77
+ if (stdout.trim()) return stdout.trim();
78
+ } catch { /* try next */ }
79
+ }
80
+ // Fallback: no account filter (finds any entry with this service name)
81
+ try {
82
+ const { stdout } = await exec("security", ["find-generic-password", "-s", service, "-w"], { timeout: 2000 });
83
+ return stdout.trim() || null;
84
+ } catch { return null; }
72
85
  } catch { return null; }
73
86
  }
74
87
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "🌿 Wispy — AI workspace assistant with trustworthy execution (harness, receipts, approvals, diffs)",
5
5
  "license": "MIT",
6
6
  "author": "Minseo & Poropo",