wispy-cli 2.6.1 → 2.6.3

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/bin/wispy.mjs +162 -1
  2. package/package.json +1 -1
package/bin/wispy.mjs CHANGED
@@ -881,6 +881,167 @@ 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 — interactive config menu
894
+ const { select: cfgSelect } = await import("@inquirer/prompts");
895
+
896
+ // Show current status first
897
+ const providers = config.providers ? Object.keys(config.providers) : (config.provider ? [config.provider] : []);
898
+ const providerStr = providers.length > 0 ? providers.join(", ") : _dim("not set");
899
+ const defaultProv = config.defaultProvider ?? config.provider ?? _dim("not set");
900
+ const security = config.security ?? config.securityLevel ?? _dim("not set");
901
+ const language = config.language ?? _dim("auto");
902
+ const wsName = config.workstream ?? "default";
903
+
904
+ console.log(`\n${_bold("Wispy Config")}\n`);
905
+ console.log(` Providers: ${providerStr}`);
906
+ console.log(` Default: ${defaultProv}`);
907
+ console.log(` Security: ${security}`);
908
+ console.log(` Language: ${language}`);
909
+ console.log(` Workstream: ${wsName}`);
910
+ console.log(` Config file: ${_dim(CONFIG_PATH)}`);
911
+ console.log("");
912
+
913
+ const action = await cfgSelect({
914
+ message: "What do you want to change?",
915
+ choices: [
916
+ { name: "Providers — add/remove AI providers", value: "provider" },
917
+ { name: "Security — change trust level", value: "security" },
918
+ { name: "Language — set preferred language", value: "language" },
919
+ { name: "Channels — configure messaging bots", value: "channels" },
920
+ { name: "Server — cloud/server settings", value: "server" },
921
+ { name: "View raw config (JSON)", value: "raw" },
922
+ { name: "Reset everything", value: "reset" },
923
+ { name: "Done", value: "done" },
924
+ ],
925
+ });
926
+
927
+ if (action === "done") {
928
+ process.exit(0);
929
+ } else if (action === "raw") {
930
+ const display = JSON.parse(JSON.stringify(config));
931
+ if (display.providers) {
932
+ for (const [k, v] of Object.entries(display.providers)) {
933
+ if (v.apiKey) v.apiKey = v.apiKey.slice(0, 6) + "..." + v.apiKey.slice(-4);
934
+ }
935
+ }
936
+ console.log(JSON.stringify(display, null, 2));
937
+ } else if (action === "reset") {
938
+ const { confirm: cfgConfirm } = await import("@inquirer/prompts");
939
+ const yes = await cfgConfirm({ message: "Reset all configuration?", default: false });
940
+ if (yes) {
941
+ const { writeFile: wf } = await import("node:fs/promises");
942
+ await wf(CONFIG_PATH, "{}\n");
943
+ console.log(`${_green("✓")} Config reset. Run 'wispy setup' to reconfigure.`);
944
+ }
945
+ } else {
946
+ // Delegate to setup wizard step
947
+ const { OnboardingWizard } = await import(
948
+ path.join(__dirname, "..", "core", "onboarding.mjs")
949
+ );
950
+ const wizard = new OnboardingWizard();
951
+ await wizard.runStep(action);
952
+ }
953
+
954
+ } else if (sub === "get") {
955
+ // wispy config get <key>
956
+ const key = args[2];
957
+ if (!key) { console.error(_red("Usage: wispy config get <key>")); process.exit(2); }
958
+ const val = key.split(".").reduce((o, k) => o?.[k], config);
959
+ if (val === undefined) {
960
+ console.log(_dim(`(not set)`));
961
+ } else {
962
+ console.log(typeof val === "object" ? JSON.stringify(val, null, 2) : String(val));
963
+ }
964
+
965
+ } else if (sub === "set") {
966
+ // wispy config set <key> <value>
967
+ const key = args[2];
968
+ const value = args.slice(3).join(" ");
969
+ if (!key || !value) { console.error(_red("Usage: wispy config set <key> <value>")); process.exit(2); }
970
+
971
+ // Parse value
972
+ let parsed = value;
973
+ if (value === "true") parsed = true;
974
+ else if (value === "false") parsed = false;
975
+ else if (/^\d+$/.test(value)) parsed = parseInt(value);
976
+
977
+ // Set nested key
978
+ const keys = key.split(".");
979
+ let obj = config;
980
+ for (let i = 0; i < keys.length - 1; i++) {
981
+ if (!obj[keys[i]]) obj[keys[i]] = {};
982
+ obj = obj[keys[i]];
983
+ }
984
+ obj[keys[keys.length - 1]] = parsed;
985
+ await saveConfig(config);
986
+ console.log(`${_green("✓")} ${key} = ${parsed}`);
987
+
988
+ } else if (sub === "delete" || sub === "unset") {
989
+ // wispy config delete <key>
990
+ const key = args[2];
991
+ if (!key) { console.error(_red("Usage: wispy config delete <key>")); process.exit(2); }
992
+ const keys = key.split(".");
993
+ let obj = config;
994
+ for (let i = 0; i < keys.length - 1; i++) {
995
+ if (!obj[keys[i]]) break;
996
+ obj = obj[keys[i]];
997
+ }
998
+ delete obj[keys[keys.length - 1]];
999
+ await saveConfig(config);
1000
+ console.log(`${_green("✓")} ${key} removed`);
1001
+
1002
+ } else if (sub === "reset") {
1003
+ // wispy config reset
1004
+ const { confirm } = await import("@inquirer/prompts");
1005
+ const yes = await confirm({ message: "Reset all configuration? This cannot be undone.", default: false });
1006
+ if (yes) {
1007
+ const { writeFile } = await import("node:fs/promises");
1008
+ await writeFile(CONFIG_PATH, "{}\n");
1009
+ console.log(`${_green("✓")} Config reset. Run 'wispy setup' to reconfigure.`);
1010
+ } else {
1011
+ console.log(_dim("Cancelled."));
1012
+ }
1013
+
1014
+ } else if (sub === "path") {
1015
+ // wispy config path
1016
+ console.log(CONFIG_PATH);
1017
+
1018
+ } else if (sub === "edit") {
1019
+ // wispy config edit — open in $EDITOR
1020
+ const editor = process.env.EDITOR ?? process.env.VISUAL ?? "nano";
1021
+ const { spawn: sp } = await import("node:child_process");
1022
+ sp(editor, [CONFIG_PATH], { stdio: "inherit" });
1023
+
1024
+ } else {
1025
+ console.log(`
1026
+ ${_bold("wispy config")} — manage configuration
1027
+
1028
+ ${_cyan("wispy config")} Show current config (keys masked)
1029
+ ${_cyan("wispy config get <key>")} Get a specific value (dot notation)
1030
+ ${_cyan("wispy config set <key> <val>")} Set a value
1031
+ ${_cyan("wispy config delete <key>")} Remove a key
1032
+ ${_cyan("wispy config reset")} Reset to defaults
1033
+ ${_cyan("wispy config path")} Show config file path
1034
+ ${_cyan("wispy config edit")} Open in $EDITOR
1035
+
1036
+ ${_dim("Examples:")}
1037
+ ${_dim("wispy config get defaultProvider")}
1038
+ ${_dim("wispy config set security careful")}
1039
+ ${_dim("wispy config set language ko")}
1040
+ `);
1041
+ }
1042
+ process.exit(0);
1043
+ }
1044
+
884
1045
  // ── status sub-command ────────────────────────────────────────────────────────
885
1046
  if (args[0] === "status") {
886
1047
  // Try the enhanced status from onboarding.mjs first
@@ -1909,7 +2070,7 @@ const _KNOWN_COMMANDS = new Set([
1909
2070
  "ws", "trust", "where", "handoff", "skill", "teach", "improve", "dry",
1910
2071
  "setup", "init", "update", "status", "connect", "disconnect", "sync",
1911
2072
  "deploy", "migrate", "cron", "audit", "log", "server", "node", "channel",
1912
- "auth", "tui", "help", "doctor", "completion", "version",
2073
+ "auth", "config", "model", "tui", "help", "doctor", "completion", "version",
1913
2074
  // serve flags (handled below)
1914
2075
  "--serve", "--telegram", "--discord", "--slack", "--server",
1915
2076
  "--help", "-h", "--version", "-v", "--debug", "--tui",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wispy-cli",
3
- "version": "2.6.1",
3
+ "version": "2.6.3",
4
4
  "description": "🌿 Wispy — AI workspace assistant with trustworthy execution (harness, receipts, approvals, diffs)",
5
5
  "license": "MIT",
6
6
  "author": "Minseo & Poropo",