wispy-cli 2.7.15 → 2.7.17
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/README.md +671 -353
- package/bin/wispy.mjs +35 -9
- package/core/engine.mjs +14 -4
- package/lib/commands/continuity.mjs +4 -2
- package/lib/wispy-repl.mjs +50 -3
- package/package.json +3 -2
package/bin/wispy.mjs
CHANGED
|
@@ -56,6 +56,7 @@ function hasFlag(flag) {
|
|
|
56
56
|
|
|
57
57
|
// Parse global flags (order doesn't matter, remove from args so REPL doesn't see them)
|
|
58
58
|
const globalProfile = extractFlag(["--profile", "-p"], true);
|
|
59
|
+
const globalWorkstream = extractFlag(["--workstream", "-w"], true);
|
|
59
60
|
const globalPersonality = extractFlag(["--personality"], true);
|
|
60
61
|
const globalJsonMode = hasFlag("--json");
|
|
61
62
|
if (globalJsonMode) { args.splice(args.indexOf("--json"), 1); }
|
|
@@ -86,6 +87,10 @@ const imagePaths = [];
|
|
|
86
87
|
|
|
87
88
|
// Expose for submodules via env
|
|
88
89
|
if (globalProfile) process.env.WISPY_PROFILE = globalProfile;
|
|
90
|
+
// Validate workstream: ignore flag-like values (e.g. --invalid-arg)
|
|
91
|
+
if (globalWorkstream && !globalWorkstream.startsWith("-")) {
|
|
92
|
+
process.env.WISPY_WORKSTREAM = globalWorkstream;
|
|
93
|
+
}
|
|
89
94
|
if (globalPersonality) process.env.WISPY_PERSONALITY = globalPersonality;
|
|
90
95
|
if (imagePaths.length > 0) process.env.WISPY_IMAGES = JSON.stringify(imagePaths);
|
|
91
96
|
if (globalSystemPrompt) process.env.WISPY_SYSTEM_PROMPT = globalSystemPrompt;
|
|
@@ -757,12 +762,31 @@ if (command === "exec") {
|
|
|
757
762
|
const disallowedTools = globalDisallowedTools || null;
|
|
758
763
|
const agent = globalAgent || null;
|
|
759
764
|
|
|
765
|
+
// Load project settings and merge
|
|
766
|
+
const { findProjectSettings, mergeSettings } = await import(join(rootDir, "core/project-settings.mjs"));
|
|
767
|
+
const projectSettings = await findProjectSettings();
|
|
768
|
+
const mergedConfig = mergeSettings(
|
|
769
|
+
profileConfig,
|
|
770
|
+
projectSettings,
|
|
771
|
+
null, // profile already merged into profileConfig via loadConfigWithProfile
|
|
772
|
+
{
|
|
773
|
+
...(personality ? { personality } : {}),
|
|
774
|
+
...(effort ? { effort } : {}),
|
|
775
|
+
...(maxBudgetUsd ? { maxBudgetUsd } : {}),
|
|
776
|
+
...(globalSystemPrompt ? { systemPrompt: globalSystemPrompt } : {}),
|
|
777
|
+
...(globalAppendSystemPrompt ? { appendSystemPrompt: globalAppendSystemPrompt } : {}),
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
|
|
760
781
|
const engine = new WispyEngine({
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
782
|
+
...mergedConfig,
|
|
783
|
+
personality: mergedConfig.personality ?? personality,
|
|
784
|
+
effort: mergedConfig.effort ?? effort,
|
|
785
|
+
maxBudgetUsd: mergedConfig.maxBudgetUsd ?? maxBudgetUsd,
|
|
786
|
+
allowedTools: mergedConfig.allowedTools ?? allowedTools,
|
|
787
|
+
disallowedTools: mergedConfig.disallowedTools ?? disallowedTools,
|
|
788
|
+
systemPrompt: mergedConfig.systemPrompt ?? null,
|
|
789
|
+
appendSystemPrompt: mergedConfig.appendSystemPrompt ?? null,
|
|
766
790
|
workstream: process.env.WISPY_WORKSTREAM ?? "default",
|
|
767
791
|
});
|
|
768
792
|
|
|
@@ -789,6 +813,7 @@ if (command === "exec") {
|
|
|
789
813
|
personality,
|
|
790
814
|
effort,
|
|
791
815
|
agent,
|
|
816
|
+
sessionName: globalSessionName ?? null,
|
|
792
817
|
skipSkillCapture: true,
|
|
793
818
|
skipUserModel: true,
|
|
794
819
|
});
|
|
@@ -976,7 +1001,8 @@ if (command === "sessions") {
|
|
|
976
1001
|
for (const s of sessions) {
|
|
977
1002
|
const ts = new Date(s.updatedAt).toLocaleString();
|
|
978
1003
|
const preview = s.firstMessage ? ` "${s.firstMessage.slice(0, 60)}${s.firstMessage.length > 60 ? "…" : ""}"` : "";
|
|
979
|
-
|
|
1004
|
+
const nameLabel = s.name ? ` · 📎 ${s.name}` : "";
|
|
1005
|
+
console.log(` ${s.id}${nameLabel}`);
|
|
980
1006
|
console.log(` ${ts} · ${s.workstream} · ${s.messageCount} msgs${s.model ? ` · ${s.model}` : ""}`);
|
|
981
1007
|
if (preview) console.log(` ${preview}`);
|
|
982
1008
|
console.log("");
|
|
@@ -987,7 +1013,7 @@ if (command === "sessions") {
|
|
|
987
1013
|
const choices = [
|
|
988
1014
|
{ name: "(exit)", value: null },
|
|
989
1015
|
...sessions.map(s => ({
|
|
990
|
-
name: `${s.id} ${new Date(s.updatedAt).toLocaleString()} "${(s.firstMessage ?? "").slice(0, 50)}"`,
|
|
1016
|
+
name: `${s.id}${s.name ? ` [${s.name}]` : ""} ${new Date(s.updatedAt).toLocaleString()} "${(s.firstMessage ?? "").slice(0, 50)}"`,
|
|
991
1017
|
value: s.id,
|
|
992
1018
|
})),
|
|
993
1019
|
];
|
|
@@ -1056,7 +1082,7 @@ if (command === "resume") {
|
|
|
1056
1082
|
}
|
|
1057
1083
|
|
|
1058
1084
|
const choices = sessions.map(s => ({
|
|
1059
|
-
name: `${new Date(s.updatedAt).toLocaleString()} [${s.workstream}] ${s.messageCount}msgs "${(s.firstMessage ?? "").slice(0, 50)}"`,
|
|
1085
|
+
name: `${new Date(s.updatedAt).toLocaleString()} [${s.workstream}]${s.name ? ` 📎 ${s.name}` : ""} ${s.messageCount}msgs "${(s.firstMessage ?? "").slice(0, 50)}"`,
|
|
1060
1086
|
value: s.id,
|
|
1061
1087
|
}));
|
|
1062
1088
|
|
|
@@ -1105,7 +1131,7 @@ if (command === "fork") {
|
|
|
1105
1131
|
}
|
|
1106
1132
|
|
|
1107
1133
|
const choices = sessions.map(s => ({
|
|
1108
|
-
name: `${new Date(s.updatedAt).toLocaleString()} [${s.workstream}] ${s.messageCount}msgs "${(s.firstMessage ?? "").slice(0, 50)}"`,
|
|
1134
|
+
name: `${new Date(s.updatedAt).toLocaleString()} [${s.workstream}]${s.name ? ` 📎 ${s.name}` : ""} ${s.messageCount}msgs "${(s.firstMessage ?? "").slice(0, 50)}"`,
|
|
1109
1135
|
value: s.id,
|
|
1110
1136
|
}));
|
|
1111
1137
|
|
package/core/engine.mjs
CHANGED
|
@@ -96,12 +96,22 @@ export class WispyEngine {
|
|
|
96
96
|
this._workMdContent = null;
|
|
97
97
|
this._workMdLoaded = false;
|
|
98
98
|
this._workMdPath = null;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
{
|
|
100
|
+
const _ws = config.workstream
|
|
101
|
+
?? process.env.WISPY_WORKSTREAM
|
|
102
|
+
?? process.argv.find((a, i) => (process.argv[i-1] === "-w" || process.argv[i-1] === "--workstream"))
|
|
103
|
+
?? "default";
|
|
104
|
+
// Sanitize: never use a flag-like value as workstream name
|
|
105
|
+
this._activeWorkstream = (_ws && !_ws.startsWith("-")) ? _ws : "default";
|
|
106
|
+
}
|
|
103
107
|
// Personality: from config, or null (use default Wispy personality)
|
|
104
108
|
this._personality = config.personality ?? null;
|
|
109
|
+
// System prompt overrides from config
|
|
110
|
+
this._systemPrompt = config.systemPrompt ?? null; // full replacement
|
|
111
|
+
this._appendSystemPrompt = config.appendSystemPrompt ?? null; // append to default
|
|
112
|
+
// Project settings cache (loaded lazily)
|
|
113
|
+
this._projectSettings = null;
|
|
114
|
+
this._projectSettingsLoaded = false;
|
|
105
115
|
// Agent manager
|
|
106
116
|
this.agentManager = new AgentManager(config);
|
|
107
117
|
// Effort level: low | medium | high | max
|
|
@@ -29,9 +29,11 @@ async function readJsonOr(filePath, fallback = null) {
|
|
|
29
29
|
|
|
30
30
|
async function getActiveWorkstream() {
|
|
31
31
|
const envWs = process.env.WISPY_WORKSTREAM;
|
|
32
|
-
if (envWs) return envWs;
|
|
32
|
+
if (envWs && !envWs.startsWith("-")) return envWs;
|
|
33
33
|
const cfg = await readJsonOr(CONFIG_PATH, {});
|
|
34
|
-
|
|
34
|
+
const ws = cfg.workstream ?? "default";
|
|
35
|
+
// Sanitize: never return a flag-like value (e.g. "--invalid-arg")
|
|
36
|
+
return (ws && !ws.startsWith("-")) ? ws : "default";
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
async function getProviderLabel(cfg) {
|
package/lib/wispy-repl.mjs
CHANGED
|
@@ -62,8 +62,10 @@ function box(lines, { padding = 1 } = {}) {
|
|
|
62
62
|
// ---------------------------------------------------------------------------
|
|
63
63
|
|
|
64
64
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
65
|
-
const
|
|
65
|
+
const _rawWorkstream = process.env.WISPY_WORKSTREAM ??
|
|
66
66
|
process.argv.find((a, i) => (process.argv[i-1] === "-w" || process.argv[i-1] === "--workstream")) ?? "default";
|
|
67
|
+
// Validate: ignore flag-like values to avoid e.g. "--invalid-arg" becoming workstream
|
|
68
|
+
const ACTIVE_WORKSTREAM = (_rawWorkstream && !_rawWorkstream.startsWith("-")) ? _rawWorkstream : "default";
|
|
67
69
|
const HISTORY_FILE = path.join(CONVERSATIONS_DIR, `${ACTIVE_WORKSTREAM}.json`);
|
|
68
70
|
|
|
69
71
|
async function readFileOr(filePath, fallback = null) {
|
|
@@ -327,6 +329,7 @@ ${bold("Wispy Commands:")}
|
|
|
327
329
|
${cyan("/search")} <keyword> Search across workstreams
|
|
328
330
|
${cyan("/skills")} List installed skills
|
|
329
331
|
${cyan("/sessions")} List sessions
|
|
332
|
+
${cyan("/name")} <name> Give this session a display name (e.g. /name refactor-auth)
|
|
330
333
|
${cyan("/mcp")} [list|connect|disconnect|config|reload] MCP management
|
|
331
334
|
${cyan("/remember")} <text> Save text to main memory (MEMORY.md)
|
|
332
335
|
${cyan("/forget")} <key> Delete a memory file
|
|
@@ -1069,6 +1072,28 @@ ${bold("Permissions & Audit (v1.1):")}
|
|
|
1069
1072
|
return true;
|
|
1070
1073
|
}
|
|
1071
1074
|
|
|
1075
|
+
if (cmd.startsWith("/name")) {
|
|
1076
|
+
const nameParts = input.split(" ").slice(1);
|
|
1077
|
+
const sessionName = nameParts.join(" ").trim();
|
|
1078
|
+
if (!sessionName) {
|
|
1079
|
+
console.log(yellow("Usage: /name <display-name> (e.g. /name refactor-auth)"));
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
// Find the active session and set its name
|
|
1083
|
+
try {
|
|
1084
|
+
const currentSession = engine.sessions?.list?.()?.find(Boolean);
|
|
1085
|
+
if (currentSession) {
|
|
1086
|
+
await engine.sessions.setName(currentSession.id, sessionName);
|
|
1087
|
+
console.log(green(`📎 Session named: "${sessionName}" (${currentSession.id})`));
|
|
1088
|
+
} else {
|
|
1089
|
+
console.log(yellow("No active session found."));
|
|
1090
|
+
}
|
|
1091
|
+
} catch (err) {
|
|
1092
|
+
console.log(red(`Failed to set session name: ${err.message}`));
|
|
1093
|
+
}
|
|
1094
|
+
return true;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1072
1097
|
if (cmd === "/quit" || cmd === "/exit") {
|
|
1073
1098
|
console.log(dim(`🌿 Bye! (${engine.providers.formatCost()})`));
|
|
1074
1099
|
engine.destroy();
|
|
@@ -1293,6 +1318,7 @@ async function runRepl(engine) {
|
|
|
1293
1318
|
noSave: true,
|
|
1294
1319
|
dryRun: engine.dryRunMode ?? false,
|
|
1295
1320
|
images: pendingImages,
|
|
1321
|
+
sessionName: process.env.WISPY_SESSION_NAME ?? null,
|
|
1296
1322
|
onSkillLearned: (skill) => {
|
|
1297
1323
|
console.log(cyan(`\n💡 Learned new skill: '${skill.name}' — use /${skill.name} next time`));
|
|
1298
1324
|
},
|
|
@@ -1476,8 +1502,29 @@ if (args[0] && operatorCommands.has(args[0])) {
|
|
|
1476
1502
|
process.exit(0);
|
|
1477
1503
|
}
|
|
1478
1504
|
|
|
1479
|
-
// Initialize engine
|
|
1480
|
-
|
|
1505
|
+
// Initialize engine — merge project settings + env flags
|
|
1506
|
+
let _engineConfig = { workstream: ACTIVE_WORKSTREAM };
|
|
1507
|
+
try {
|
|
1508
|
+
const { findProjectSettings, mergeSettings } = await import("../core/project-settings.mjs");
|
|
1509
|
+
const { loadConfigWithProfile } = await import("../core/config.mjs");
|
|
1510
|
+
const projectSettings = await findProjectSettings();
|
|
1511
|
+
if (projectSettings) {
|
|
1512
|
+
const baseConfig = await loadConfigWithProfile(process.env.WISPY_PROFILE ?? null);
|
|
1513
|
+
_engineConfig = mergeSettings(baseConfig, projectSettings, null, {
|
|
1514
|
+
...(process.env.WISPY_SYSTEM_PROMPT ? { systemPrompt: process.env.WISPY_SYSTEM_PROMPT } : {}),
|
|
1515
|
+
...(process.env.WISPY_APPEND_SYSTEM_PROMPT ? { appendSystemPrompt: process.env.WISPY_APPEND_SYSTEM_PROMPT } : {}),
|
|
1516
|
+
...(process.env.WISPY_EFFORT ? { effort: process.env.WISPY_EFFORT } : {}),
|
|
1517
|
+
});
|
|
1518
|
+
_engineConfig.workstream = _engineConfig.workstream ?? ACTIVE_WORKSTREAM;
|
|
1519
|
+
} else {
|
|
1520
|
+
// No project settings, still pick up env flags
|
|
1521
|
+
if (process.env.WISPY_SYSTEM_PROMPT) _engineConfig.systemPrompt = process.env.WISPY_SYSTEM_PROMPT;
|
|
1522
|
+
if (process.env.WISPY_APPEND_SYSTEM_PROMPT) _engineConfig.appendSystemPrompt = process.env.WISPY_APPEND_SYSTEM_PROMPT;
|
|
1523
|
+
if (process.env.WISPY_EFFORT) _engineConfig.effort = process.env.WISPY_EFFORT;
|
|
1524
|
+
}
|
|
1525
|
+
} catch { /* project settings are optional */ }
|
|
1526
|
+
|
|
1527
|
+
const engine = new WispyEngine(_engineConfig);
|
|
1481
1528
|
const initResult = await engine.init();
|
|
1482
1529
|
|
|
1483
1530
|
if (!initResult) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wispy-cli",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.17",
|
|
4
4
|
"description": "🌿 Wispy — AI workspace assistant with trustworthy execution (harness, receipts, approvals, diffs)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Minseo & Poropo",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"scripts": {
|
|
60
60
|
"test": "node --test --test-force-exit --test-timeout=15000 tests/*.test.mjs",
|
|
61
61
|
"test:basic": "node --test --test-force-exit test/basic.test.mjs",
|
|
62
|
-
"test:verbose": "node --test --test-force-exit --test-timeout=15000 --test-reporter=spec tests/*.test.mjs"
|
|
62
|
+
"test:verbose": "node --test --test-force-exit --test-timeout=15000 --test-reporter=spec tests/*.test.mjs",
|
|
63
|
+
"postpublish": "npm install -g wispy-cli@latest || true"
|
|
63
64
|
},
|
|
64
65
|
"dependencies": {
|
|
65
66
|
"@inquirer/prompts": "^8.3.2",
|