ispbills-icli 8.5.4 → 8.5.6
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/icli.js +36 -4
- package/package.json +1 -1
- package/src/commands.js +56 -7
- package/src/session.js +133 -17
- package/src/tui/app.js +911 -43
- package/src/tui/components.js +130 -18
- package/src/tui/composer.js +109 -27
- package/src/tui/run.js +3 -1
package/bin/icli.js
CHANGED
|
@@ -25,7 +25,7 @@ const PKG_NAME = 'ispbills-icli';
|
|
|
25
25
|
const rawArgs = argv.slice(2);
|
|
26
26
|
|
|
27
27
|
// Flags that consume the following token as their value.
|
|
28
|
-
const VALUE_FLAGS = new Set(['--loader-style']);
|
|
28
|
+
const VALUE_FLAGS = new Set(['--loader-style', '-p', '--prompt', '--allow-tool', '--agent']);
|
|
29
29
|
|
|
30
30
|
function has(name) {
|
|
31
31
|
return rawArgs.includes(name);
|
|
@@ -110,12 +110,19 @@ function printHelp() {
|
|
|
110
110
|
console.log(`\n ${BOLD}Flags${RESET}`);
|
|
111
111
|
[
|
|
112
112
|
['--banner', 'Force-show the animated splash banner'],
|
|
113
|
+
['--allow-all / --yolo', 'Auto-approve all tool calls without prompting'],
|
|
114
|
+
['--allow-tool TOOL', 'Pre-approve a specific tool (e.g. shell(git))'],
|
|
115
|
+
['-p / --prompt TEXT', 'One-shot non-interactive mode'],
|
|
116
|
+
['--resume / --continue', 'Resume the most recently closed session'],
|
|
117
|
+
['--experimental', 'Enable experimental features'],
|
|
118
|
+
['--agent=NAME', 'Delegate to a named custom agent'],
|
|
119
|
+
['--cloud', 'Start session inside a cloud sandbox (stub)'],
|
|
113
120
|
['--reasoning', 'Stream reasoning inline (on by default)'],
|
|
114
121
|
['--no-reasoning', 'Hide the reasoning trace'],
|
|
115
122
|
['--loader-style STYLE', 'spinner | gradient | minimal'],
|
|
116
123
|
['--ascii', 'Force ASCII-safe glyphs (classic Windows cmd.exe)'],
|
|
117
124
|
['--unicode', 'Force full Unicode glyphs'],
|
|
118
|
-
].forEach(([c, d]) => console.log(` ${ACCENT}${c.padEnd(
|
|
125
|
+
].forEach(([c, d]) => console.log(` ${ACCENT}${c.padEnd(30)}${RESET}${DIM}${d}${RESET}`));
|
|
119
126
|
|
|
120
127
|
console.log(`\n ${BOLD}Slash commands${RESET}`);
|
|
121
128
|
for (const { cmd: c, hint, desc } of SLASH_COMMANDS) {
|
|
@@ -306,6 +313,25 @@ async function main() {
|
|
|
306
313
|
|
|
307
314
|
const cfg = await ensureAuth();
|
|
308
315
|
|
|
316
|
+
// Apply launch flags to config
|
|
317
|
+
if (has('--allow-all') || has('--yolo')) {
|
|
318
|
+
cfg.tui = { ...(cfg.tui || {}), allowAll: true };
|
|
319
|
+
}
|
|
320
|
+
if (has('--experimental')) {
|
|
321
|
+
cfg.tui = { ...(cfg.tui || {}), experimental: true };
|
|
322
|
+
}
|
|
323
|
+
if (flag('--allow-tool')) {
|
|
324
|
+
const tool = flag('--allow-tool');
|
|
325
|
+
cfg.tui = { ...(cfg.tui || {}), allowedTools: [...(cfg.tui?.allowedTools || []), tool] };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// -p / --prompt: one-shot mode via flag
|
|
329
|
+
const promptFlag = flag('-p') || flag('--prompt');
|
|
330
|
+
if (promptFlag) {
|
|
331
|
+
await singleShot(cfg, promptFlag);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
309
335
|
// Single-shot: any positional (non-flag) args form the question.
|
|
310
336
|
if (positional.length) {
|
|
311
337
|
await singleShot(cfg, positional.join(' '));
|
|
@@ -313,9 +339,15 @@ async function main() {
|
|
|
313
339
|
}
|
|
314
340
|
|
|
315
341
|
// Always use the Ink TUI — Ink handles non-TTY environments gracefully.
|
|
316
|
-
// The old readline REPL fallback is kept only for explicit pipe/script usage.
|
|
317
342
|
const { runTui } = await import('../src/tui/run.js');
|
|
318
|
-
const
|
|
343
|
+
const agentName = flag('--agent') || rawArgs.find((a) => a.startsWith('--agent='))?.split('=')[1];
|
|
344
|
+
const resumeMode = has('--resume') || has('--continue');
|
|
345
|
+
const action = await runTui(cfg, {
|
|
346
|
+
display,
|
|
347
|
+
banner: has('--banner'),
|
|
348
|
+
agentName,
|
|
349
|
+
resumeMode,
|
|
350
|
+
});
|
|
319
351
|
if (action === 'logout') {
|
|
320
352
|
const cleared = clearConfig();
|
|
321
353
|
console.log(`\n ${cleared ? GREEN + glyphs.check + RESET + ' Logged out — credentials cleared.' : YELLOW + 'Already logged out.' + RESET}`);
|
package/package.json
CHANGED
package/src/commands.js
CHANGED
|
@@ -58,12 +58,14 @@ export const SLASH_COMMANDS = [
|
|
|
58
58
|
{ cmd: '/autopilot', hint: '', desc: 'Toggle autopilot mode (auto-apply fixes)', group: 'Session', local: true },
|
|
59
59
|
{ cmd: '/allow-all', hint: '[on|off|show]', desc: 'Toggle auto-approval mode', group: 'Session', local: true },
|
|
60
60
|
{ cmd: '/model', hint: '[name]', desc: 'Show or set the AI model', group: 'Session', local: true },
|
|
61
|
+
{ cmd: '/models', hint: '[name]', desc: 'Alias for /model', group: 'Session', local: true },
|
|
61
62
|
{ cmd: '/reasoning', hint: '', desc: 'Toggle reasoning display (Ctrl+T)', group: 'Session', local: true },
|
|
62
63
|
{ cmd: '/context', hint: '', desc: 'Show context window token usage', group: 'Session', local: true },
|
|
63
64
|
{ cmd: '/usage', hint: '', desc: 'Show session usage metrics', group: 'Session', local: true },
|
|
64
65
|
{ cmd: '/compact', hint: '[focus]', desc: 'Compress conversation history', group: 'Session', local: true },
|
|
65
66
|
{ cmd: '/session', hint: '', desc: 'Show session info (cwd, branch, tokens)', group: 'Session', local: true },
|
|
66
|
-
{ cmd: '/resume', hint: '[id]', desc: '
|
|
67
|
+
{ cmd: '/resume', hint: '[id]', desc: 'Resume a previous session (picker)', group: 'Session', local: true },
|
|
68
|
+
{ cmd: '/continue', hint: '[id]', desc: 'Alias for /resume', group: 'Session', local: true },
|
|
67
69
|
{ cmd: '/rename', hint: '[name]', desc: 'Rename this session', group: 'Session', local: true },
|
|
68
70
|
{ cmd: '/share', hint: '', desc: 'Share session to a GitHub gist', group: 'Session', local: true },
|
|
69
71
|
{ cmd: '/theme', hint: '', desc: 'View current colour theme info', group: 'Session', local: true },
|
|
@@ -72,14 +74,61 @@ export const SLASH_COMMANDS = [
|
|
|
72
74
|
{ cmd: '/env', hint: '', desc: 'Show loaded environment info', group: 'Session', local: true },
|
|
73
75
|
{ cmd: '/streamer-mode', hint: '', desc: 'Hide model names and token counts', group: 'Session', local: true },
|
|
74
76
|
{ cmd: '/new', hint: '', desc: 'Start a fresh conversation', group: 'Session', local: true },
|
|
77
|
+
{ cmd: '/reset', hint: '', desc: 'Alias for /new', group: 'Session', local: true },
|
|
75
78
|
{ cmd: '/history', hint: '', desc: 'Show conversation history', group: 'Session', local: true },
|
|
76
79
|
{ cmd: '/clear', hint: '', desc: 'Clear the screen', group: 'Session', local: true },
|
|
77
|
-
{ cmd: '/
|
|
78
|
-
{ cmd: '/
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{ cmd: '/
|
|
82
|
-
{ cmd: '/
|
|
80
|
+
{ cmd: '/restart', hint: '', desc: 'Restart CLI preserving session', group: 'Session', local: true },
|
|
81
|
+
{ cmd: '/keep-alive', hint: '[on|off|busy|DURATION]', desc: 'Prevent machine sleep', group: 'Session', local: true },
|
|
82
|
+
|
|
83
|
+
// ── Permissions ──
|
|
84
|
+
{ cmd: '/permissions', hint: '[show|reset]', desc: 'View or clear tool/path approvals', group: 'Permissions', local: true },
|
|
85
|
+
{ cmd: '/allow-tool', hint: 'TOOL', desc: 'Pre-approve a specific tool', group: 'Permissions', local: true },
|
|
86
|
+
{ cmd: '/reset-allowed-tools', hint: '', desc: 'Clear all in-session tool approvals', group: 'Permissions', local: true },
|
|
87
|
+
{ cmd: '/add-dir', hint: 'PATH', desc: 'Add a directory to the allowed list', group: 'Permissions', local: true },
|
|
88
|
+
{ cmd: '/list-dirs', hint: '', desc: 'Show all allowed directories', group: 'Permissions', local: true },
|
|
89
|
+
{ cmd: '/sandbox', hint: '[enable|disable]', desc: 'Enable/disable local sandboxing', group: 'Permissions', local: true },
|
|
90
|
+
{ cmd: '/experimental', hint: '[on|off|show]', desc: 'Toggle experimental features', group: 'Permissions', local: true },
|
|
91
|
+
{ cmd: '/init', hint: '', desc: 'Initialize Copilot instructions for this repo', group: 'Permissions', local: true },
|
|
92
|
+
|
|
93
|
+
// ── Agents & models ──
|
|
94
|
+
{ cmd: '/agent', hint: '', desc: 'Browse and select agents', group: 'Agents', local: true },
|
|
95
|
+
{ cmd: '/skills', hint: '', desc: 'Toggle individual skills on/off', group: 'Agents', local: true },
|
|
96
|
+
{ cmd: '/fleet', hint: '[prompt]', desc: 'Enable parallel subagent execution', group: 'Agents', local: true },
|
|
97
|
+
{ cmd: '/tasks', hint: '', desc: 'View and manage background tasks', group: 'Agents', local: true },
|
|
98
|
+
{ cmd: '/rubber-duck', hint: '[prompt]', desc: 'Get a second opinion from rubber-duck', group: 'Agents', local: true },
|
|
99
|
+
{ cmd: '/research', hint: 'TOPIC', desc: 'Run deep research on a topic', group: 'Agents', local: true },
|
|
100
|
+
{ cmd: '/mcp', hint: '[show|add|edit|delete|disable|enable|auth|reload|search]', desc: 'Manage MCP servers', group: 'Agents', local: true },
|
|
101
|
+
{ cmd: '/plugin', hint: '[marketplace|install|uninstall|update|list]', desc: 'Manage plugins', group: 'Agents', local: true },
|
|
102
|
+
{ cmd: '/delegate', hint: '[prompt]', desc: 'Delegate changes to a remote repo', group: 'Agents', local: true },
|
|
103
|
+
|
|
104
|
+
// ── Code ──
|
|
105
|
+
{ cmd: '/terminal-setup', hint: '', desc: 'Configure terminal for Shift+Enter', group: 'Code', local: true },
|
|
106
|
+
{ cmd: '/ide', hint: '', desc: 'Connect to a VS Code workspace', group: 'Code', local: true },
|
|
107
|
+
{ cmd: '/pr', hint: '[view|create|fix|auto]', desc: 'Manage pull requests', group: 'Code', local: true },
|
|
108
|
+
{ cmd: '/lsp', hint: '[show|test|reload|help]', desc: 'Manage language servers', group: 'Code', local: true },
|
|
109
|
+
{ cmd: '/instructions', hint: '', desc: 'View and toggle custom instructions', group: 'Code', local: true },
|
|
110
|
+
|
|
111
|
+
// ── Scheduling (experimental) ──
|
|
112
|
+
{ cmd: '/ask', hint: 'QUESTION', desc: 'Quick side-question without adding to history', group: 'Scheduling', local: true },
|
|
113
|
+
{ cmd: '/after', hint: '[DELAY PROMPT]', desc: 'Schedule a one-shot prompt', group: 'Scheduling', local: true },
|
|
114
|
+
{ cmd: '/every', hint: '[INTERVAL PROMPT]', desc: 'Schedule a recurring prompt', group: 'Scheduling', local: true },
|
|
115
|
+
{ cmd: '/remote', hint: '[on|off]', desc: 'Manage remote steering', group: 'Scheduling', local: true },
|
|
116
|
+
{ cmd: '/chronicle', hint: '[standup|tips|improve|reindex]', desc: 'Session history tools and insights', group: 'Scheduling', local: true },
|
|
117
|
+
|
|
118
|
+
// ── Help & Feedback ──
|
|
119
|
+
{ cmd: '/changelog', hint: '', desc: 'Show recent npm package releases', group: 'Help', local: true },
|
|
120
|
+
{ cmd: '/feedback', hint: '', desc: 'Show the feedback / bug-report URL', group: 'Help', local: true },
|
|
121
|
+
{ cmd: '/update', hint: '', desc: 'Update iCli to the latest version', group: 'Help', local: true },
|
|
122
|
+
{ cmd: '/downgrade', hint: 'VERSION', desc: 'Roll back to a specific CLI version', group: 'Help', local: true },
|
|
123
|
+
{ cmd: '/app', hint: '', desc: 'Launch the GitHub Copilot desktop app', group: 'Help', local: true },
|
|
124
|
+
{ cmd: '/user', hint: '', desc: 'Manage GitHub user list', group: 'Help', local: true },
|
|
125
|
+
{ cmd: '/login', hint: '', desc: 'Log in to Copilot', group: 'Help', local: true },
|
|
126
|
+
{ cmd: '/whoami', hint: '', desc: 'Show current user info', group: 'Help', local: true },
|
|
127
|
+
{ cmd: '/clikit', hint: '[component]', desc: 'Preview CLI UI components', group: 'Help', local: true },
|
|
128
|
+
{ cmd: '/extensions', hint: '[manage|mode]', desc: 'Manage CLI extensions', group: 'Help', local: true },
|
|
129
|
+
{ cmd: '/logout', hint: '', desc: 'Log out and clear credentials', group: 'Help', local: true },
|
|
130
|
+
{ cmd: '/help', hint: '', desc: 'Show help', group: 'Help', local: true },
|
|
131
|
+
{ cmd: '/exit', hint: '', desc: 'Exit iCli', group: 'Help', local: true },
|
|
83
132
|
];
|
|
84
133
|
|
|
85
134
|
export function slashCompleter(line) {
|
package/src/session.js
CHANGED
|
@@ -1,9 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
|
|
5
5
|
const SESSION_DIR = join(homedir(), '.config', 'ispbills-icli', 'sessions');
|
|
6
6
|
|
|
7
|
+
function safeJson(raw, fallback = null) {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(raw);
|
|
10
|
+
} catch {
|
|
11
|
+
return fallback;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeMessages(messages = []) {
|
|
16
|
+
return messages
|
|
17
|
+
.filter((m) => m && typeof m.role === 'string')
|
|
18
|
+
.map((m) => ({ role: m.role, content: String(m.content ?? '') }));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function autoNameFromMessages(messages = []) {
|
|
22
|
+
const firstUser = messages.find((m) => m?.role === 'user' && String(m.content || '').trim());
|
|
23
|
+
const text = String(firstUser?.content || '').replace(/\s+/g, ' ').trim();
|
|
24
|
+
if (!text) return 'Untitled session';
|
|
25
|
+
return text.slice(0, 40).trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeSession(session = {}) {
|
|
29
|
+
const now = new Date().toISOString();
|
|
30
|
+
const messages = normalizeMessages(session.messages);
|
|
31
|
+
const id = String(session.id || Date.now().toString(36));
|
|
32
|
+
return {
|
|
33
|
+
id,
|
|
34
|
+
name: String(session.name || autoNameFromMessages(messages) || 'Untitled session'),
|
|
35
|
+
cwd: String(session.cwd || process.cwd()),
|
|
36
|
+
branch: String(session.branch || ''),
|
|
37
|
+
model: String(session.model || ''),
|
|
38
|
+
createdAt: session.createdAt || now,
|
|
39
|
+
updatedAt: session.updatedAt || now,
|
|
40
|
+
messages,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sessionPathFor(id) {
|
|
45
|
+
return join(SESSION_DIR, `${id}.json`);
|
|
46
|
+
}
|
|
47
|
+
|
|
7
48
|
export function sessionDir() {
|
|
8
49
|
return SESSION_DIR;
|
|
9
50
|
}
|
|
@@ -12,30 +53,105 @@ export function initSessionDir() {
|
|
|
12
53
|
if (!existsSync(SESSION_DIR)) mkdirSync(SESSION_DIR, { recursive: true });
|
|
13
54
|
}
|
|
14
55
|
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
|
|
56
|
+
export function saveSession(session) {
|
|
57
|
+
try {
|
|
58
|
+
initSessionDir();
|
|
59
|
+
const existing = loadSessionById(session?.id);
|
|
60
|
+
const normalized = normalizeSession({
|
|
61
|
+
...existing,
|
|
62
|
+
...session,
|
|
63
|
+
id: session?.id || existing?.id,
|
|
64
|
+
createdAt: session?.createdAt || existing?.createdAt,
|
|
65
|
+
updatedAt: new Date().toISOString(),
|
|
66
|
+
name: session?.name || existing?.name,
|
|
67
|
+
});
|
|
68
|
+
if (!normalized.name || normalized.name === 'Untitled session') {
|
|
69
|
+
normalized.name = autoNameFromMessages(normalized.messages);
|
|
70
|
+
}
|
|
71
|
+
writeFileSync(sessionPathFor(normalized.id), JSON.stringify(normalized, null, 2) + '\n');
|
|
72
|
+
return normalized;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
18
76
|
}
|
|
19
77
|
|
|
20
|
-
|
|
21
|
-
|
|
78
|
+
export function loadSessionById(id) {
|
|
79
|
+
if (!id) return null;
|
|
80
|
+
const path = sessionPathFor(id);
|
|
81
|
+
if (!existsSync(path)) return null;
|
|
82
|
+
const loaded = safeJson(readFileSync(path, 'utf8'), null);
|
|
83
|
+
return loaded ? normalizeSession(loaded) : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function deleteSession(id) {
|
|
22
87
|
try {
|
|
23
|
-
|
|
88
|
+
const path = sessionPathFor(id);
|
|
89
|
+
if (!existsSync(path)) return false;
|
|
90
|
+
unlinkSync(path);
|
|
91
|
+
return true;
|
|
24
92
|
} catch {
|
|
25
|
-
|
|
93
|
+
return false;
|
|
26
94
|
}
|
|
27
95
|
}
|
|
28
96
|
|
|
29
|
-
export function
|
|
97
|
+
export function listSessionsDetailed() {
|
|
30
98
|
if (!existsSync(SESSION_DIR)) return [];
|
|
31
|
-
return readdirSync(SESSION_DIR)
|
|
99
|
+
return readdirSync(SESSION_DIR)
|
|
100
|
+
.filter((f) => f.endsWith('.json'))
|
|
101
|
+
.map((file) => {
|
|
102
|
+
const full = join(SESSION_DIR, file);
|
|
103
|
+
const session = safeJson(readFileSync(full, 'utf8'), null);
|
|
104
|
+
if (!session) return null;
|
|
105
|
+
const normalized = normalizeSession(session);
|
|
106
|
+
const stats = statSync(full);
|
|
107
|
+
return {
|
|
108
|
+
id: normalized.id,
|
|
109
|
+
name: normalized.name || autoNameFromMessages(normalized.messages),
|
|
110
|
+
cwd: normalized.cwd,
|
|
111
|
+
branch: normalized.branch,
|
|
112
|
+
model: normalized.model,
|
|
113
|
+
createdAt: normalized.createdAt,
|
|
114
|
+
updatedAt: normalized.updatedAt,
|
|
115
|
+
messageCount: normalized.messages.length,
|
|
116
|
+
messages: normalized.messages,
|
|
117
|
+
mtimeMs: stats.mtimeMs,
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.sort((a, b) => new Date(b.updatedAt || 0).getTime() - new Date(a.updatedAt || 0).getTime());
|
|
32
122
|
}
|
|
33
123
|
|
|
34
|
-
export function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
124
|
+
export function loadMostRecent() {
|
|
125
|
+
const [session] = listSessionsDetailed();
|
|
126
|
+
if (!session?.id) return null;
|
|
127
|
+
return loadSessionById(session.id);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function listSessions() {
|
|
131
|
+
return listSessionsDetailed().map((s) => s.id);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function loadSession(sessionPathOrId) {
|
|
135
|
+
if (!sessionPathOrId) return [];
|
|
136
|
+
if (sessionPathOrId.endsWith?.('.json')) {
|
|
137
|
+
const session = safeJson(readFileSync(sessionPathOrId, 'utf8'), null);
|
|
138
|
+
return normalizeSession(session || {}).messages;
|
|
139
|
+
}
|
|
140
|
+
return loadSessionById(sessionPathOrId)?.messages || [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function newSessionPath() {
|
|
144
|
+
initSessionDir();
|
|
145
|
+
return sessionPathFor(Date.now().toString(36));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function saveMessage(sessionPathOrId, message) {
|
|
149
|
+
if (!message) return null;
|
|
150
|
+
const id = String(sessionPathOrId || Date.now().toString(36)).replace(/\.json$/, '');
|
|
151
|
+
const existing = loadSessionById(id);
|
|
152
|
+
return saveSession({
|
|
153
|
+
...(existing || {}),
|
|
154
|
+
id,
|
|
155
|
+
messages: [...(existing?.messages || []), message],
|
|
156
|
+
});
|
|
41
157
|
}
|