phewsh 0.8.0 → 0.9.0
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/phewsh.js +3 -17
- package/commands/session.js +243 -45
- package/package.json +1 -1
package/bin/phewsh.js
CHANGED
|
@@ -117,23 +117,9 @@ function exitAfterUpdate(code = 0) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
if (!command) {
|
|
120
|
-
// Bare `phewsh` — drop into persistent session
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
const path = require('path');
|
|
124
|
-
const os = require('os');
|
|
125
|
-
const configPath = path.join(os.homedir(), '.phewsh', 'config.json');
|
|
126
|
-
let hasKey = false;
|
|
127
|
-
try {
|
|
128
|
-
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
129
|
-
hasKey = !!cfg.apiKey;
|
|
130
|
-
} catch {}
|
|
131
|
-
if (hasKey) {
|
|
132
|
-
COMMANDS.session();
|
|
133
|
-
} else {
|
|
134
|
-
showHelp();
|
|
135
|
-
exitAfterUpdate(0);
|
|
136
|
-
}
|
|
120
|
+
// Bare `phewsh` — always drop into persistent session
|
|
121
|
+
// Session handles missing API key gracefully with /login and /key commands
|
|
122
|
+
COMMANDS.session();
|
|
137
123
|
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
138
124
|
showHelp();
|
|
139
125
|
exitAfterUpdate(0);
|
package/commands/session.js
CHANGED
|
@@ -8,21 +8,36 @@ const os = require('os');
|
|
|
8
8
|
const readline = require('readline');
|
|
9
9
|
const { trackSap } = require('../lib/supabase');
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const CONFIG_DIR = path.join(os.homedir(), '.phewsh');
|
|
12
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
12
13
|
const INTENT_DIR = path.join(process.cwd(), '.intent');
|
|
13
|
-
const HISTORY_PATH = path.join(os.homedir(), '.phewsh', 'session_history.json');
|
|
14
14
|
|
|
15
15
|
const b = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
16
16
|
const d = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
17
|
+
const w = (s) => `\x1b[97m${s}\x1b[0m`;
|
|
17
18
|
const g = (s) => `\x1b[90m${s}\x1b[0m`;
|
|
18
19
|
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
19
20
|
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
21
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
22
|
+
|
|
23
|
+
const MODELS = {
|
|
24
|
+
'claude-sonnet': { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', provider: 'anthropic' },
|
|
25
|
+
'claude-opus': { id: 'claude-opus-4-6', name: 'Claude Opus 4.6', provider: 'anthropic' },
|
|
26
|
+
'claude-haiku': { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', provider: 'anthropic' },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const DEFAULT_MODEL = 'claude-sonnet';
|
|
20
30
|
|
|
21
31
|
function loadConfig() {
|
|
22
32
|
if (!fs.existsSync(CONFIG_PATH)) return null;
|
|
23
33
|
try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); } catch { return null; }
|
|
24
34
|
}
|
|
25
35
|
|
|
36
|
+
function saveConfig(config) {
|
|
37
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
38
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
function loadIntentContext() {
|
|
27
42
|
const files = ['vision.md', 'plan.md', 'next.md'];
|
|
28
43
|
const loaded = [];
|
|
@@ -49,9 +64,8 @@ function buildSystemPrompt(intentFiles) {
|
|
|
49
64
|
return `${base}\n\nThe user has structured intent artifacts for this project. Use them as primary context — stay aligned with their vision, plan, and next actions.\n\n${sections}`;
|
|
50
65
|
}
|
|
51
66
|
|
|
52
|
-
async function streamChat(apiKey, messages, systemPrompt,
|
|
53
|
-
const
|
|
54
|
-
const body = { model, max_tokens: 2048, messages, stream: true };
|
|
67
|
+
async function streamChat(apiKey, messages, systemPrompt, modelId) {
|
|
68
|
+
const body = { model: modelId, max_tokens: 2048, messages, stream: true };
|
|
55
69
|
if (systemPrompt) body.system = systemPrompt;
|
|
56
70
|
|
|
57
71
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
@@ -97,42 +111,45 @@ async function streamChat(apiKey, messages, systemPrompt, config) {
|
|
|
97
111
|
|
|
98
112
|
process.stdout.write('\n');
|
|
99
113
|
|
|
100
|
-
|
|
101
|
-
trackSap({
|
|
102
|
-
userId: config.supabaseUserId,
|
|
103
|
-
source: 'cli',
|
|
104
|
-
model,
|
|
105
|
-
promptTokens,
|
|
106
|
-
completionTokens,
|
|
107
|
-
accessToken: config.supabaseAccessToken,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return { content: fullResponse, promptTokens, completionTokens };
|
|
114
|
+
return { content: fullResponse, promptTokens, completionTokens, model: modelId };
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
async function main() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
console.log('\n No API key found. Run `phewsh login --set-key` first.');
|
|
118
|
-
console.log(' Or start at: `phewsh login`\n');
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const intentFiles = loadIntentContext();
|
|
123
|
-
const systemPrompt = buildSystemPrompt(intentFiles);
|
|
118
|
+
let config = loadConfig();
|
|
119
|
+
let intentFiles = loadIntentContext();
|
|
120
|
+
let systemPrompt = buildSystemPrompt(intentFiles);
|
|
124
121
|
const messages = []; // conversation history
|
|
125
122
|
const projectName = path.basename(process.cwd());
|
|
123
|
+
let currentModel = DEFAULT_MODEL;
|
|
124
|
+
let totalPromptTokens = 0;
|
|
125
|
+
let totalCompletionTokens = 0;
|
|
126
126
|
|
|
127
|
-
// Session banner
|
|
127
|
+
// Session banner with PHEWSH branding
|
|
128
128
|
console.log('');
|
|
129
129
|
console.log(` ${d('😮💨')} ${d('🤫')}`);
|
|
130
130
|
console.log('');
|
|
131
|
+
console.log(` ${b(w('█▀█ █░█ █▀▀ █░█ █▀ █░█'))}`);
|
|
132
|
+
console.log(` ${b(w('█▀▀ █▀█ ██▄ ▀▄▀ ▄█ █▀█'))}`);
|
|
133
|
+
console.log('');
|
|
134
|
+
|
|
135
|
+
if (!config?.apiKey) {
|
|
136
|
+
console.log(` ${yellow('⚠')} No API key configured.`);
|
|
137
|
+
console.log(` ${g('Run')} /login ${g('to set up identity + API key')}`);
|
|
138
|
+
console.log(` ${g('Or')} /key ${g('to add an API key directly')}`);
|
|
139
|
+
console.log('');
|
|
140
|
+
}
|
|
141
|
+
|
|
131
142
|
if (intentFiles.length > 0) {
|
|
132
|
-
console.log(` ${green('●')}
|
|
143
|
+
console.log(` ${green('●')} ${cyan(projectName)} ${g('·')} ${intentFiles.map(f => f.file).join(', ')}`);
|
|
133
144
|
} else {
|
|
134
|
-
console.log(` ${green('●')}
|
|
145
|
+
console.log(` ${green('●')} ${cyan(projectName)} ${g('·')} no .intent/ context`);
|
|
146
|
+
console.log(` ${g(' run /init to create .intent/ artifacts')}`);
|
|
147
|
+
}
|
|
148
|
+
console.log(` ${g(' model:')} ${MODELS[currentModel].name}`);
|
|
149
|
+
if (config?.email) {
|
|
150
|
+
console.log(` ${g(' user:')} ${config.email}`);
|
|
135
151
|
}
|
|
152
|
+
console.log('');
|
|
136
153
|
console.log(` ${g('type naturally · /help for commands · /quit to exit')}`);
|
|
137
154
|
console.log('');
|
|
138
155
|
|
|
@@ -155,23 +172,40 @@ async function main() {
|
|
|
155
172
|
|
|
156
173
|
// Slash commands
|
|
157
174
|
if (input.startsWith('/')) {
|
|
158
|
-
const
|
|
175
|
+
const parts = input.slice(1).split(/\s+/);
|
|
176
|
+
const cmd = parts[0].toLowerCase();
|
|
177
|
+
const cmdArg = parts.slice(1).join(' ');
|
|
159
178
|
|
|
160
179
|
if (cmd === 'quit' || cmd === 'exit' || cmd === 'q') {
|
|
161
|
-
|
|
180
|
+
const turns = messages.length / 2;
|
|
181
|
+
console.log(`\n ${g('Session ended · ' + turns + ' exchanges · ' + (totalPromptTokens + totalCompletionTokens) + ' tokens')}\n`);
|
|
162
182
|
process.exit(0);
|
|
163
183
|
}
|
|
164
184
|
|
|
165
|
-
if (cmd === 'help') {
|
|
185
|
+
if (cmd === 'help' || cmd === 'h') {
|
|
166
186
|
console.log(`
|
|
167
187
|
${b('Session commands')}
|
|
168
188
|
|
|
169
|
-
|
|
189
|
+
${w('conversation')}
|
|
170
190
|
${g('/clear')} Clear conversation history
|
|
191
|
+
${g('/run')} ${d('<prompt>')} One-shot prompt (doesn't add to conversation)
|
|
192
|
+
${g('/quit')} End session
|
|
193
|
+
|
|
194
|
+
${w('project')}
|
|
195
|
+
${g('/init')} Create .intent/ artifacts in this directory
|
|
171
196
|
${g('/context')} Show loaded .intent/ files
|
|
197
|
+
${g('/reload')} Reload .intent/ context from disk
|
|
172
198
|
${g('/status')} Show session stats
|
|
173
|
-
|
|
174
|
-
|
|
199
|
+
|
|
200
|
+
${w('configuration')}
|
|
201
|
+
${g('/login')} Set up identity + cloud sync
|
|
202
|
+
${g('/key')} Set or update your API key
|
|
203
|
+
${g('/model')} ${d('<name>')} Switch model (sonnet, opus, haiku)
|
|
204
|
+
${g('/models')} List available models
|
|
205
|
+
${g('/provider')} Show current provider info
|
|
206
|
+
|
|
207
|
+
${w('debug')}
|
|
208
|
+
${g('/system')} Show current system prompt
|
|
175
209
|
`);
|
|
176
210
|
rl.prompt();
|
|
177
211
|
return;
|
|
@@ -190,7 +224,7 @@ async function main() {
|
|
|
190
224
|
intentFiles.forEach(f => console.log(` ${green('●')} ${f.file} ${g('(' + f.content.length + ' chars)')}`));
|
|
191
225
|
} else {
|
|
192
226
|
console.log(`\n ${g('No .intent/ context found in')} ${process.cwd()}`);
|
|
193
|
-
console.log(` ${g('Run')}
|
|
227
|
+
console.log(` ${g('Run')} /init ${g('to create one')}`);
|
|
194
228
|
}
|
|
195
229
|
console.log('');
|
|
196
230
|
rl.prompt();
|
|
@@ -199,21 +233,25 @@ async function main() {
|
|
|
199
233
|
|
|
200
234
|
if (cmd === 'status') {
|
|
201
235
|
const turns = messages.length / 2;
|
|
236
|
+
config = loadConfig(); // refresh
|
|
202
237
|
console.log(`\n ${b('Session')}`);
|
|
203
238
|
console.log(` Turns ${turns}`);
|
|
239
|
+
console.log(` Tokens ${totalPromptTokens}→${totalCompletionTokens} (in→out)`);
|
|
204
240
|
console.log(` Project ${projectName}`);
|
|
205
241
|
console.log(` Context ${intentFiles.length > 0 ? intentFiles.map(f => f.file).join(', ') : 'none'}`);
|
|
206
|
-
console.log(`
|
|
242
|
+
console.log(` Model ${MODELS[currentModel].name}`);
|
|
243
|
+
console.log(` Provider ${MODELS[currentModel].provider}`);
|
|
244
|
+
if (config?.email) console.log(` User ${config.email}`);
|
|
245
|
+
console.log(` API key ${config?.apiKey ? config.apiKey.slice(0, 8) + '...' : yellow('not set')}`);
|
|
207
246
|
console.log('');
|
|
208
247
|
rl.prompt();
|
|
209
248
|
return;
|
|
210
249
|
}
|
|
211
250
|
|
|
212
251
|
if (cmd === 'reload') {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
console.log(` ${green('●')} Reloaded ${reloaded.length} artifact${reloaded.length !== 1 ? 's' : ''}`);
|
|
252
|
+
intentFiles = loadIntentContext();
|
|
253
|
+
systemPrompt = buildSystemPrompt(intentFiles);
|
|
254
|
+
console.log(` ${green('●')} Reloaded ${intentFiles.length} artifact${intentFiles.length !== 1 ? 's' : ''}`);
|
|
217
255
|
rl.prompt();
|
|
218
256
|
return;
|
|
219
257
|
}
|
|
@@ -224,22 +262,182 @@ async function main() {
|
|
|
224
262
|
return;
|
|
225
263
|
}
|
|
226
264
|
|
|
227
|
-
|
|
265
|
+
if (cmd === 'init') {
|
|
266
|
+
if (fs.existsSync(path.join(INTENT_DIR, 'vision.md'))) {
|
|
267
|
+
console.log(`\n ${g('.intent/ already exists in')} ${process.cwd()}`);
|
|
268
|
+
console.log(` ${g('Use /reload to refresh context')}\n`);
|
|
269
|
+
} else {
|
|
270
|
+
try {
|
|
271
|
+
// Delegate to the intent --init command
|
|
272
|
+
const { execSync } = require('child_process');
|
|
273
|
+
execSync('node ' + path.join(__dirname, 'intent.js') + ' --init', { stdio: 'inherit' });
|
|
274
|
+
// Reload context after init
|
|
275
|
+
intentFiles = loadIntentContext();
|
|
276
|
+
systemPrompt = buildSystemPrompt(intentFiles);
|
|
277
|
+
if (intentFiles.length > 0) {
|
|
278
|
+
console.log(` ${green('●')} Context loaded: ${intentFiles.map(f => f.file).join(', ')}`);
|
|
279
|
+
}
|
|
280
|
+
} catch (err) {
|
|
281
|
+
console.error(` ${g('Init failed:')} ${err.message}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
console.log('');
|
|
285
|
+
rl.prompt();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (cmd === 'login') {
|
|
290
|
+
try {
|
|
291
|
+
const { execSync } = require('child_process');
|
|
292
|
+
execSync('node ' + path.join(__dirname, 'login.js'), { stdio: 'inherit' });
|
|
293
|
+
config = loadConfig(); // refresh after login
|
|
294
|
+
} catch (err) {
|
|
295
|
+
console.error(` ${g('Login failed:')} ${err.message}`);
|
|
296
|
+
}
|
|
297
|
+
rl.prompt();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (cmd === 'key') {
|
|
302
|
+
const keyRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
303
|
+
keyRl.question(`\n Anthropic API key\n > `, (apiKey) => {
|
|
304
|
+
keyRl.close();
|
|
305
|
+
apiKey = apiKey.trim();
|
|
306
|
+
if (apiKey) {
|
|
307
|
+
config = loadConfig() || {};
|
|
308
|
+
config.apiKey = apiKey;
|
|
309
|
+
saveConfig(config);
|
|
310
|
+
console.log(` ${green('●')} API key saved\n`);
|
|
311
|
+
} else {
|
|
312
|
+
console.log(` ${g('Cancelled')}\n`);
|
|
313
|
+
}
|
|
314
|
+
rl.prompt();
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (cmd === 'models') {
|
|
320
|
+
console.log(`\n ${b('Available models')}\n`);
|
|
321
|
+
for (const [key, model] of Object.entries(MODELS)) {
|
|
322
|
+
const active = key === currentModel ? ` ${green('●')}` : '';
|
|
323
|
+
console.log(` ${w(key.padEnd(16))} ${g(model.name)}${active}`);
|
|
324
|
+
}
|
|
325
|
+
console.log(`\n ${g('Switch with:')} /model <name>\n`);
|
|
326
|
+
rl.prompt();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (cmd === 'model') {
|
|
331
|
+
if (!cmdArg) {
|
|
332
|
+
console.log(` ${g('Current:')} ${MODELS[currentModel].name}`);
|
|
333
|
+
console.log(` ${g('Usage:')} /model <sonnet|opus|haiku>`);
|
|
334
|
+
rl.prompt();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// Fuzzy match model name
|
|
338
|
+
const query = cmdArg.toLowerCase().replace('claude-', '').replace('claude', '');
|
|
339
|
+
const match = Object.keys(MODELS).find(k =>
|
|
340
|
+
k.includes(query) || MODELS[k].name.toLowerCase().includes(query)
|
|
341
|
+
);
|
|
342
|
+
if (match) {
|
|
343
|
+
currentModel = match;
|
|
344
|
+
console.log(` ${green('●')} Switched to ${MODELS[match].name}`);
|
|
345
|
+
} else {
|
|
346
|
+
console.log(` ${g('Unknown model. Available:')} ${Object.keys(MODELS).join(', ')}`);
|
|
347
|
+
}
|
|
348
|
+
rl.prompt();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (cmd === 'provider') {
|
|
353
|
+
const model = MODELS[currentModel];
|
|
354
|
+
console.log(`\n ${b('Provider')}`);
|
|
355
|
+
console.log(` API Anthropic (direct)`);
|
|
356
|
+
console.log(` Model ${model.name}`);
|
|
357
|
+
console.log(` Endpoint api.anthropic.com/v1/messages`);
|
|
358
|
+
console.log(` Key ${config?.apiKey ? config.apiKey.slice(0, 8) + '...' : yellow('not set')}`);
|
|
359
|
+
console.log('');
|
|
360
|
+
rl.prompt();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (cmd === 'run') {
|
|
365
|
+
if (!cmdArg) {
|
|
366
|
+
console.log(` ${g('Usage:')} /run <prompt>`);
|
|
367
|
+
rl.prompt();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (!config?.apiKey) {
|
|
371
|
+
console.log(` ${yellow('⚠')} No API key. Run /key to set one.`);
|
|
372
|
+
rl.prompt();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
// One-shot: don't add to conversation history
|
|
376
|
+
console.log('');
|
|
377
|
+
try {
|
|
378
|
+
const result = await streamChat(
|
|
379
|
+
config.apiKey,
|
|
380
|
+
[{ role: 'user', content: cmdArg }],
|
|
381
|
+
systemPrompt,
|
|
382
|
+
MODELS[currentModel].id
|
|
383
|
+
);
|
|
384
|
+
if (result.promptTokens || result.completionTokens) {
|
|
385
|
+
console.log(g(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens`));
|
|
386
|
+
}
|
|
387
|
+
trackSap({
|
|
388
|
+
userId: config.supabaseUserId,
|
|
389
|
+
source: 'cli',
|
|
390
|
+
model: MODELS[currentModel].id,
|
|
391
|
+
promptTokens: result.promptTokens,
|
|
392
|
+
completionTokens: result.completionTokens,
|
|
393
|
+
accessToken: config.supabaseAccessToken,
|
|
394
|
+
});
|
|
395
|
+
} catch (err) {
|
|
396
|
+
console.error(`\n ${err.message}\n`);
|
|
397
|
+
}
|
|
398
|
+
console.log('');
|
|
399
|
+
rl.prompt();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Unknown slash command
|
|
404
|
+
console.log(` ${g('Unknown command:')} /${cmd} ${g('— type /help for available commands')}`);
|
|
405
|
+
rl.prompt();
|
|
406
|
+
return;
|
|
228
407
|
}
|
|
229
408
|
|
|
230
409
|
// Regular input → send to AI
|
|
231
|
-
|
|
410
|
+
if (!config?.apiKey) {
|
|
411
|
+
console.log(`\n ${yellow('⚠')} No API key configured. Run /key to set one.\n`);
|
|
412
|
+
rl.prompt();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
232
415
|
|
|
416
|
+
messages.push({ role: 'user', content: input });
|
|
233
417
|
console.log('');
|
|
234
418
|
|
|
235
419
|
try {
|
|
236
|
-
const result = await streamChat(config.apiKey, messages, systemPrompt,
|
|
420
|
+
const result = await streamChat(config.apiKey, messages, systemPrompt, MODELS[currentModel].id);
|
|
237
421
|
messages.push({ role: 'assistant', content: result.content });
|
|
238
422
|
|
|
423
|
+
// Track totals
|
|
424
|
+
if (result.promptTokens) totalPromptTokens += result.promptTokens;
|
|
425
|
+
if (result.completionTokens) totalCompletionTokens += result.completionTokens;
|
|
426
|
+
|
|
239
427
|
// Token count footer
|
|
240
428
|
if (result.promptTokens || result.completionTokens) {
|
|
241
|
-
console.log(g(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens`));
|
|
429
|
+
console.log(g(` ${result.promptTokens || '?'}→${result.completionTokens || '?'} tokens · ${MODELS[currentModel].name}`));
|
|
242
430
|
}
|
|
431
|
+
|
|
432
|
+
// SAP tracking (fire-and-forget)
|
|
433
|
+
trackSap({
|
|
434
|
+
userId: config.supabaseUserId,
|
|
435
|
+
source: 'cli',
|
|
436
|
+
model: MODELS[currentModel].id,
|
|
437
|
+
promptTokens: result.promptTokens,
|
|
438
|
+
completionTokens: result.completionTokens,
|
|
439
|
+
accessToken: config.supabaseAccessToken,
|
|
440
|
+
});
|
|
243
441
|
} catch (err) {
|
|
244
442
|
console.error(`\n ${err.message}\n`);
|
|
245
443
|
// Remove the failed user message
|