dotdotdot-cli 1.0.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/lib/planner.js ADDED
@@ -0,0 +1,169 @@
1
+ 'use strict';
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // planner.js — Multi-step task orchestrator (compact, minimal output)
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+
7
+ const { bold, dim, cyan, green, red, yellow, symbols } = require('./colors');
8
+ const { Spinner, stepLine, taskPlan, printError, printWarning, printInfo, commandBox, truncate } = require('./renderer');
9
+ const { runCommand, copyToClipboard, stripShellWrapper } = require('./executor');
10
+ const { selectMenu, confirm } = require('./menu');
11
+ const { analyzeSteps } = require('./safety');
12
+ const { queryLLM } = require('./llm');
13
+ const { setUserIntent, addEntry } = require('./session');
14
+ const { recordUsage, tokenLine, estimateCost } = require('./tokens');
15
+
16
+ async function runTask(userInput, context, config, opts = {}) {
17
+ const debug = opts.debug || false;
18
+ // ─── Plan ─────────────────────────────────────────────────────────────
19
+ const spinner = new Spinner('planning...').start();
20
+
21
+ let plan;
22
+ try {
23
+ plan = await queryLLM(userInput, context, config, 'task');
24
+ } catch (err) {
25
+ spinner.fail(err.message);
26
+ if (err.debugLog) {
27
+ const { subtle } = require('./renderer');
28
+ process.stderr.write(` ${subtle('log: ' + err.debugLog)}\n`);
29
+ }
30
+ process.exit(1);
31
+ }
32
+
33
+ if (!plan?.steps?.length) {
34
+ // Show the summary if the LLM explained why (e.g. "no_git is true")
35
+ const reason = plan?.summary || 'No steps generated. Try rephrasing.';
36
+ spinner.fail(reason);
37
+ if (plan?._debugLog) {
38
+ const { subtle } = require('./renderer');
39
+ process.stderr.write(` ${subtle('log: ' + plan._debugLog)}\n`);
40
+ }
41
+ process.exit(1);
42
+ }
43
+
44
+ // Record and display token usage
45
+ const tokenUsage = plan._tokenUsage;
46
+ if (tokenUsage) {
47
+ recordUsage(tokenUsage, config.provider, config.model);
48
+ }
49
+
50
+ const steps = analyzeSteps(plan.steps);
51
+ const cost = estimateCost(tokenUsage, config.provider, config.model);
52
+ const costStr = cost ? dim(` ~$${cost}`) : '';
53
+ spinner.succeed(`${steps.length} steps planned | ${tokenLine(tokenUsage)}${costStr}`);
54
+
55
+ if (debug && plan._debugLog) {
56
+ const { subtle } = require('./renderer');
57
+ process.stderr.write(` ${subtle('log: ' + plan._debugLog)}\n`);
58
+ }
59
+
60
+ // ─── Show plan ────────────────────────────────────────────────────────
61
+ if (plan.summary) printInfo(plan.summary);
62
+ console.log();
63
+ console.log(taskPlan(steps));
64
+
65
+ const hasRisk = steps.some(s => s.computedRisk === 'high');
66
+ if (hasRisk) printWarning('has destructive steps — will ask before those');
67
+
68
+ console.log();
69
+ const proceed = await selectMenu([
70
+ { label: 'Run', key: 'a' },
71
+ { label: 'Step by step', key: 's' },
72
+ { label: 'Copy', key: 'c' },
73
+ { label: 'Cancel', key: 'q' },
74
+ ]);
75
+
76
+ if (!proceed || proceed === 'q') { console.log(` ${dim('cancelled')}`); return; }
77
+
78
+ if (proceed === 'c') {
79
+ const all = steps.map((s, i) => `# ${i+1}. ${s.description}\n${s.command}`).join('\n\n');
80
+ if (copyToClipboard(all)) console.log(` ${green(symbols.check)} ${dim('copied')}`);
81
+ else console.log('\n' + all + '\n');
82
+ return;
83
+ }
84
+
85
+ const stepByStep = proceed === 's';
86
+
87
+ // ─── Execute ──────────────────────────────────────────────────────────
88
+ const states = new Array(steps.length).fill('pending'); // done, fail, skip, pending
89
+ let aborted = false;
90
+
91
+ for (let i = 0; i < steps.length; i++) {
92
+ const step = steps[i];
93
+ states[i] = 'current';
94
+
95
+ // Print current step header
96
+ console.log();
97
+ console.log(stepLine(i, steps.length, step.description, 'current'));
98
+ if (step.command) {
99
+ const w = (process.stdout.columns || 80) - 12;
100
+ console.log(` ${dim('$')} ${dim(truncate(step.command, w))}`);
101
+ }
102
+
103
+ // Only pause if: step-by-step mode, or this specific step is high-risk
104
+ const needsOk = stepByStep || step.computedRisk === 'high';
105
+
106
+ if (needsOk) {
107
+ const action = await selectMenu([
108
+ { label: 'Run', key: 'e' },
109
+ { label: 'Skip', key: 's' },
110
+ { label: 'Abort', key: 'q' },
111
+ ]);
112
+
113
+ if (!action || action === 'q') { aborted = true; states[i] = 'skip'; break; }
114
+ if (action === 's') { states[i] = 'skip'; continue; }
115
+ }
116
+
117
+ // Execute
118
+ setUserIntent(`${userInput} [${i+1}/${steps.length}]`);
119
+
120
+ // Safety: strip shell wrappers if the LLM accidentally added them
121
+ // (e.g. "powershell -Command ..." when already running in PowerShell)
122
+ const cmd = stripShellWrapper(step.command);
123
+
124
+ // Always show output to the user — never use captureOnly in task mode.
125
+ // Output is captured via the returned string regardless.
126
+ const { code, output } = await runCommand(cmd, config, {
127
+ silent: false,
128
+ captureOnly: false,
129
+ });
130
+
131
+ if (code === 0) {
132
+ states[i] = 'done';
133
+ } else {
134
+ states[i] = 'fail';
135
+
136
+ // In "Run" mode, auto-continue on failure (user chose to run all steps).
137
+ // In "Step by step" mode, ask what to do.
138
+ if (stepByStep) {
139
+ const next = await selectMenu([
140
+ { label: 'Continue', key: 'c' },
141
+ { label: 'Retry', key: 'r' },
142
+ { label: 'Abort', key: 'q' },
143
+ ]);
144
+ if (!next || next === 'q') { aborted = true; break; }
145
+ if (next === 'r') { states[i] = 'pending'; i--; continue; }
146
+ }
147
+ // In "Run" mode, just continue to next step automatically
148
+ }
149
+
150
+ if (step.captureOutput && output) {
151
+ addEntry(step.command, output, code, `step ${i+1}`);
152
+ }
153
+ }
154
+
155
+ // ─── Summary (one line) ───────────────────────────────────────────────
156
+ const ok = states.filter(s => s === 'done').length;
157
+ const fail = states.filter(s => s === 'fail').length;
158
+ const skip = states.filter(s => s === 'skip').length;
159
+
160
+ console.log();
161
+ let line = ` ${ok === steps.length ? green(symbols.check) : yellow(symbols.warning)} ${ok}/${steps.length} done`;
162
+ if (fail) line += ` ${red(fail + ' failed')}`;
163
+ if (skip) line += ` ${yellow(skip + ' skipped')}`;
164
+ console.log(line);
165
+
166
+ process.exit(fail > 0 ? 1 : 0);
167
+ }
168
+
169
+ module.exports = { runTask };
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const G = '\x1b[32m'; // green
5
+ const C = '\x1b[36m'; // cyan
6
+ const D = '\x1b[2m'; // dim
7
+ const B = '\x1b[1m'; // bold
8
+ const W = '\x1b[97m'; // bright white
9
+ const R = '\x1b[0m'; // reset
10
+
11
+ console.log();
12
+ console.log(` ${G}\u2714${R} ${B}${W}dotdotdot${R} installed`);
13
+ console.log();
14
+ console.log(` ${B}Get started:${R}`);
15
+ console.log(` ${C}... -c${R} ${D}Configure your API key${R}`);
16
+ console.log(` ${C}... list all png files${R} ${D}Try a quick command${R}`);
17
+ console.log(` ${C}... find tmp files then delete${R} ${D}Try a multi-step task${R}`);
18
+ console.log();
19
+ console.log(` ${D}Zero dependencies. 5 providers. Your terminal, your way.${R}`);
20
+ console.log();
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // renderer.js — Premium terminal UI
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+
7
+ const { bold, dim, cyan, gray, green, yellow, red, brightWhite, brightCyan,
8
+ stripAnsi, visibleLength, symbols, c256, rgb, bg256 } = require('./colors');
9
+
10
+ const termWidth = () => Math.min(process.stdout.columns || 80, 90);
11
+
12
+ // ─── Accent colors ──────────────────────────────────────────────────────────
13
+
14
+ const dot1 = c256(39); // bright blue
15
+ const dot2 = c256(44); // teal
16
+ const dot3 = c256(49); // mint
17
+ const accent = c256(39); // bright blue
18
+ const subtle = c256(240); // dark gray
19
+ const mid = c256(245); // medium gray
20
+
21
+ // ─── Truncate ───────────────────────────────────────────────────────────────
22
+
23
+ function truncate(str, max) {
24
+ if (!str) return str;
25
+ if (str.length <= max) return str;
26
+ return str.slice(0, max - 1) + '\u2026';
27
+ }
28
+
29
+ function wordWrap(str, max, indent = ' ') {
30
+ if (!str || str.length <= max) return indent + str;
31
+ const words = str.split(' ');
32
+ const lines = [];
33
+ let line = '';
34
+ for (const word of words) {
35
+ if (line && (line.length + 1 + word.length) > max) {
36
+ lines.push(indent + line);
37
+ line = word;
38
+ } else {
39
+ line = line ? line + ' ' + word : word;
40
+ }
41
+ }
42
+ if (line) lines.push(indent + line);
43
+ return lines.join('\n');
44
+ }
45
+
46
+ // ─── Command display ────────────────────────────────────────────────────────
47
+
48
+ function commandBox(command, explanation, warning) {
49
+ const w = termWidth() - 6;
50
+ const out = [];
51
+ if (warning) out.push(` ${yellow(symbols.warning)} ${dim(warning)}`);
52
+ out.push(` ${accent('\u276F')} ${bold(brightWhite(truncate(command, w)))}`);
53
+ if (explanation) out.push(subtle(wordWrap(explanation, w)));
54
+ return out.join('\n');
55
+ }
56
+
57
+ // ─── Step line ──────────────────────────────────────────────────────────────
58
+
59
+ function stepLine(i, total, label, state) {
60
+ const num = subtle(`${i+1}/${total}`);
61
+ if (state === 'done') return ` ${green(symbols.check)} ${num} ${mid(label)}`;
62
+ if (state === 'current') return ` ${accent(symbols.arrowRight)} ${num} ${brightWhite(label)}`;
63
+ if (state === 'skip') return ` ${yellow('-')} ${num} ${subtle(label)}`;
64
+ if (state === 'fail') return ` ${red(symbols.cross)} ${num} ${subtle(label)}`;
65
+ return ` ${subtle('\u2500')} ${num} ${subtle(label)}`;
66
+ }
67
+
68
+ // ─── Spinner ────────────────────────────────────────────────────────────────
69
+
70
+ class Spinner {
71
+ constructor(message = '', color = accent) {
72
+ this.message = message;
73
+ this.color = color;
74
+ this.frameIndex = 0;
75
+ this.interval = null;
76
+ this.stream = process.stderr;
77
+ }
78
+
79
+ start() {
80
+ if (!this.stream.isTTY) return this;
81
+ this.stream.write('\x1b[?25l');
82
+ this.interval = setInterval(() => {
83
+ const f = symbols.spinnerFrames[this.frameIndex];
84
+ this.frameIndex = (this.frameIndex + 1) % symbols.spinnerFrames.length;
85
+ this.stream.write(`\r\x1b[2K ${this.color(f)} ${subtle(this.message)}`);
86
+ }, 80);
87
+ return this;
88
+ }
89
+
90
+ update(msg) { this.message = msg; }
91
+
92
+ succeed(msg) {
93
+ this._end();
94
+ this.stream.write(`\r\x1b[2K ${green(symbols.check)} ${subtle(msg || this.message)}\n`);
95
+ }
96
+
97
+ fail(msg) {
98
+ this._end();
99
+ this.stream.write(`\r\x1b[2K ${red(symbols.cross)} ${msg || this.message}\n`);
100
+ }
101
+
102
+ stop() { this._end(); this.stream.write('\r\x1b[2K'); }
103
+
104
+ _end() {
105
+ if (this.interval) { clearInterval(this.interval); this.interval = null; }
106
+ if (this.stream.isTTY) this.stream.write('\x1b[?25h');
107
+ }
108
+ }
109
+
110
+ // ─── Banner ─────────────────────────────────────────────────────────────────
111
+
112
+ function printBanner() {
113
+ console.log();
114
+ console.log(` ${dot1('\u25CF')} ${dot2('\u25CF')} ${dot3('\u25CF')} ${bold(brightWhite('dotdotdot'))}`);
115
+ }
116
+
117
+ // ─── Task plan ──────────────────────────────────────────────────────────────
118
+
119
+ function taskPlan(steps) {
120
+ const w = termWidth() - 12;
121
+ const lines = [];
122
+ for (let i = 0; i < steps.length; i++) {
123
+ const s = steps[i];
124
+ const risk = s.computedRisk === 'high' ? red('!') : s.computedRisk === 'medium' ? yellow('~') : subtle('\u2500');
125
+ lines.push(` ${risk} ${subtle(`${i+1}.`)} ${truncate(s.description, w)}`);
126
+ if (s.command) lines.push(` ${subtle('$')} ${subtle(truncate(s.command, w - 4))}`);
127
+ }
128
+ return lines.join('\n');
129
+ }
130
+
131
+ // ─── One-liners ─────────────────────────────────────────────────────────────
132
+
133
+ function printError(msg) { console.error(` ${red(symbols.cross)} ${msg}`); }
134
+ function printWarning(msg) { console.error(` ${yellow(symbols.warning)} ${msg}`); }
135
+ function printSuccess(msg) { console.log(` ${green(symbols.check)} ${msg}`); }
136
+ function printInfo(msg) { console.log(` ${subtle(msg)}`); }
137
+ function keyValue(label, value, indent = 4) {
138
+ return `${' '.repeat(indent)}${subtle(label)} ${brightWhite(value)}`;
139
+ }
140
+
141
+ module.exports = {
142
+ commandBox, stepLine, Spinner, printBanner, taskPlan, truncate,
143
+ printError, printWarning, printSuccess, printInfo, keyValue, termWidth,
144
+ accent, subtle, mid, dot1, dot2, dot3,
145
+ };
package/lib/safety.js ADDED
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // safety.js — Command risk analysis
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+
7
+ const HIGH_RISK = [
8
+ // Deletion — recursive/forced only
9
+ { pattern: /\brm\s+(-[a-z]*r|-[a-z]*f|--recursive|--force)/i, reason: 'Recursive/forced deletion' },
10
+ { pattern: /\bRemove-Item\b.*(-Recurse|-Force)/i, reason: 'Recursive/forced deletion' },
11
+ { pattern: /\bdel\s+\/[sS]/i, reason: 'Recursive deletion' },
12
+ { pattern: /\brm\s+-rf\s+[\/~]/i, reason: 'Deleting from root or home' },
13
+
14
+ // System-level — only actual disk format commands, not PowerShell Format-*
15
+ { pattern: /\bformat\s+[a-zA-Z]:/i, reason: 'Disk formatting' },
16
+ { pattern: /\bmkfs\b/i, reason: 'Filesystem creation' },
17
+ { pattern: /\bdd\s+if=/i, reason: 'Low-level disk write' },
18
+ { pattern: /\bchmod\s+777/i, reason: 'Insecure permissions' },
19
+
20
+ // Dangerous pipes
21
+ { pattern: /\bcurl\b.*\|\s*(bash|sh|zsh)/i, reason: 'Download and execute' },
22
+
23
+ // Elevated
24
+ { pattern: /\bsudo\b/i, reason: 'Elevated privileges' },
25
+ { pattern: /\brunas\b/i, reason: 'Elevated privileges' },
26
+ { pattern: /Set-ExecutionPolicy\s+Unrestricted/i, reason: 'Weakening execution policy' },
27
+
28
+ // Registry
29
+ { pattern: /\breg\s+delete\b/i, reason: 'Registry deletion' },
30
+ { pattern: /Remove-ItemProperty.*HKLM/i, reason: 'System registry modification' },
31
+
32
+ // Firewall
33
+ { pattern: /\biptables\b/i, reason: 'Firewall modification' },
34
+ ];
35
+
36
+ const MEDIUM_RISK = [
37
+ { pattern: /\brm\b(?!.*Format)/i, reason: 'File deletion' },
38
+ { pattern: /\bRemove-Item\b(?!Property)/i, reason: 'File deletion' },
39
+ { pattern: /\bMove-Item\b|\bmv\b/i, reason: 'Moving files' },
40
+ { pattern: /\bkill\b|\bStop-Process\b|\btaskkill\b/i, reason: 'Process termination' },
41
+ { pattern: /\bnpm\s+(install|uninstall)\s+-g/i, reason: 'Global package change' },
42
+ { pattern: /\bgit\s+(push|reset\s+--hard|rebase|force)/i, reason: 'Git history change' },
43
+ { pattern: /\bdocker\s+(rm|rmi|stop|kill|prune)/i, reason: 'Docker resource removal' },
44
+ ];
45
+
46
+ function analyzeRisk(command) {
47
+ if (!command) return { level: 'low', reasons: [] };
48
+
49
+ for (const { pattern, reason } of HIGH_RISK) {
50
+ if (pattern.test(command)) return { level: 'high', reasons: [reason] };
51
+ }
52
+ for (const { pattern, reason } of MEDIUM_RISK) {
53
+ if (pattern.test(command)) return { level: 'medium', reasons: [reason] };
54
+ }
55
+ return { level: 'low', reasons: [] };
56
+ }
57
+
58
+ function analyzeSteps(steps) {
59
+ return steps.map(step => {
60
+ const risk = analyzeRisk(step.command);
61
+ return {
62
+ ...step,
63
+ computedRisk: risk.level,
64
+ riskReasons: risk.reasons,
65
+ // Only force approval on actually dangerous steps
66
+ needsApproval: step.needsApproval || risk.level === 'high',
67
+ };
68
+ });
69
+ }
70
+
71
+ module.exports = { analyzeRisk, analyzeSteps };
package/lib/session.js ADDED
@@ -0,0 +1,165 @@
1
+ 'use strict';
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // session.js — Session history for conversational follow-ups & multi-step tasks
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const os = require('os');
10
+
11
+ const SESSION_FILE = path.join(os.tmpdir(), 'dotdotdot-session.json');
12
+ const MAX_ENTRIES = 20;
13
+ const SESSION_TTL = 30 * 60 * 1000; // 30 minutes
14
+ const MAX_OUTPUT = 2000; // max chars of output to store (reduced from 4000)
15
+
16
+ // Terminal session ID — detect new shell sessions to clear stale context.
17
+ // Uses parent PID + shell PID as a fingerprint. New terminal = new session.
18
+ function getTerminalSessionId() {
19
+ try {
20
+ return `${process.ppid || 0}`;
21
+ } catch { return '0'; }
22
+ }
23
+
24
+ // ─── Load session ───────────────────────────────────────────────────────────
25
+
26
+ function loadSession() {
27
+ const empty = { entries: [], previousSummary: null, taskId: null, taskSteps: null, terminalId: null };
28
+ try {
29
+ const raw = fs.readFileSync(SESSION_FILE, 'utf8');
30
+ const session = JSON.parse(raw);
31
+
32
+ // Guard against malformed/old session files
33
+ if (!session || !Array.isArray(session.entries)) {
34
+ return empty;
35
+ }
36
+
37
+ // Clear on new terminal session — user unlikely wants old context
38
+ const currentTerminal = getTerminalSessionId();
39
+ if (session.terminalId && session.terminalId !== currentTerminal) {
40
+ return { ...empty, terminalId: currentTerminal };
41
+ }
42
+
43
+ // Check timeout
44
+ if (session.entries.length > 0) {
45
+ const lastTime = session.entries[session.entries.length - 1].time;
46
+ if (Date.now() - lastTime > SESSION_TTL) {
47
+ return { ...empty, terminalId: currentTerminal };
48
+ }
49
+ }
50
+
51
+ return { ...empty, ...session, entries: session.entries, terminalId: currentTerminal };
52
+ } catch {
53
+ return empty;
54
+ }
55
+ }
56
+
57
+ // ─── Save session ───────────────────────────────────────────────────────────
58
+
59
+ function saveSession(session) {
60
+ try {
61
+ // Trim entries
62
+ if (session.entries.length > MAX_ENTRIES) {
63
+ session.entries = session.entries.slice(-MAX_ENTRIES);
64
+ }
65
+ fs.writeFileSync(SESSION_FILE, JSON.stringify(session));
66
+ } catch { /* ignore write errors */ }
67
+ }
68
+
69
+ // ─── Add entry ──────────────────────────────────────────────────────────────
70
+
71
+ function addEntry(command, output, exitCode, intent) {
72
+ const session = loadSession();
73
+ session.entries.push({
74
+ command,
75
+ output: output ? output.slice(0, MAX_OUTPUT) : '',
76
+ exitCode,
77
+ intent: intent || '',
78
+ time: Date.now(),
79
+ });
80
+ saveSession(session);
81
+ return session;
82
+ }
83
+
84
+ // ─── Get session history formatted for LLM ──────────────────────────────────
85
+
86
+ function getHistory() {
87
+ const session = loadSession();
88
+ if (!session.entries.length) return null;
89
+
90
+ // Return compact array — only last 5 entries, minimal data
91
+ const compact = session.entries.slice(-5).map(e => {
92
+ const h = { cmd: e.command, ok: e.exitCode === 0 };
93
+ if (e.intent) h.q = e.intent;
94
+ // Include output snippet — follow-ups need prior output (e.g. "delete those files")
95
+ if (e.output) {
96
+ const maxOut = e.exitCode !== 0 ? 300 : 300;
97
+ h.out = e.output.length > maxOut ? e.output.slice(0, maxOut) + '…' : e.output;
98
+ }
99
+ return h;
100
+ });
101
+
102
+ return compact;
103
+ }
104
+
105
+ // ─── Task state management (for multi-step tasks) ───────────────────────────
106
+
107
+ function setTaskState(taskId, steps, currentStep) {
108
+ const session = loadSession();
109
+ session.taskId = taskId;
110
+ session.taskSteps = steps;
111
+ session.taskCurrentStep = currentStep || 0;
112
+ saveSession(session);
113
+ }
114
+
115
+ function getTaskState() {
116
+ const session = loadSession();
117
+ return {
118
+ taskId: session.taskId,
119
+ steps: session.taskSteps,
120
+ currentStep: session.taskCurrentStep || 0,
121
+ };
122
+ }
123
+
124
+ function clearTaskState() {
125
+ const session = loadSession();
126
+ session.taskId = null;
127
+ session.taskSteps = null;
128
+ session.taskCurrentStep = 0;
129
+ saveSession(session);
130
+ }
131
+
132
+ // ─── Set user intent for next entry ─────────────────────────────────────────
133
+
134
+ let _pendingIntent = '';
135
+
136
+ function setUserIntent(intent) {
137
+ _pendingIntent = intent;
138
+ }
139
+
140
+ function getUserIntent() {
141
+ const i = _pendingIntent;
142
+ _pendingIntent = '';
143
+ return i;
144
+ }
145
+
146
+ // ─── Clear entire session ───────────────────────────────────────────────────
147
+
148
+ function clearSession() {
149
+ try {
150
+ fs.unlinkSync(SESSION_FILE);
151
+ } catch { /* ignore if already gone */ }
152
+ }
153
+
154
+ module.exports = {
155
+ loadSession,
156
+ saveSession,
157
+ addEntry,
158
+ getHistory,
159
+ setTaskState,
160
+ getTaskState,
161
+ clearTaskState,
162
+ clearSession,
163
+ setUserIntent,
164
+ getUserIntent,
165
+ };