reslop 0.2.7 → 0.3.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/CHANGELOG.md +17 -1
- package/README.md +16 -14
- package/bin/reslop.js +8 -5
- package/lib/agents/agents.js +504 -0
- package/lib/agents/allow.js +71 -0
- package/lib/agents/allow.json +16 -0
- package/lib/agents/models.js +460 -0
- package/lib/api.js +15 -12
- package/lib/cli.js +43 -68
- package/lib/{files.js → common/files.js} +48 -55
- package/lib/{find.js → common/find.js} +1 -1
- package/lib/common/format.js +101 -0
- package/lib/{utilities.js → common/process.js} +7 -95
- package/lib/common/tasks.json +27 -0
- package/lib/common/utilities.js +112 -0
- package/lib/dashboard/git.js +9 -16
- package/lib/dashboard/model.js +90 -49
- package/lib/dashboard/npm.js +17 -13
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +36 -31
- package/lib/{workspace.js → dashboard/workspace.js} +23 -38
- package/lib/{deps.js → deps/deps.js} +31 -45
- package/lib/{deps-diff.js → deps/diff.js} +35 -25
- package/lib/{manifest.js → deps/manifest.js} +47 -30
- package/lib/diff/align.js +1 -2
- package/lib/diff/diff.js +2 -2
- package/lib/diff/display.js +29 -5
- package/lib/diff/inline.js +10 -14
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +39 -3
- package/lib/diff/unit.js +45 -16
- package/lib/{git-branches.js → git/branches.js} +53 -48
- package/lib/{git-deps.js → git/deps.js} +123 -96
- package/lib/git/git.js +71 -0
- package/lib/git/worktree.js +475 -0
- package/lib/highlight/bash.js +1 -2
- package/lib/highlight/csv.js +1 -2
- package/lib/highlight/dot.js +1 -2
- package/lib/{highlight.js → highlight/highlight.js} +42 -11
- package/lib/highlight/html.js +20 -18
- package/lib/highlight/json.js +1 -2
- package/lib/highlight/log.js +1 -2
- package/lib/highlight/txt.js +1 -2
- package/lib/{session → input}/actions.js +111 -48
- package/lib/{editor.js → input/editor.js} +7 -8
- package/lib/{keys.js → input/keys.js} +15 -43
- package/lib/input/line-edit.js +77 -0
- package/lib/input/menu-pick.js +70 -0
- package/lib/render/agents.js +640 -0
- package/lib/render/branches.js +4 -5
- package/lib/render/commits.js +54 -64
- package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
- package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
- package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
- package/lib/render/diff.js +166 -45
- package/lib/render/field.js +131 -0
- package/lib/render/files.js +9 -14
- package/lib/render/footer.js +383 -0
- package/lib/render/frame.js +123 -25
- package/lib/render/header.js +176 -0
- package/lib/render/log.js +319 -0
- package/lib/render/menu.js +60 -0
- package/lib/render/notes.js +16 -11
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +9 -13
- package/lib/render/primitives.js +101 -15
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +13 -25
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +13 -60
- package/lib/render/tiles.js +109 -48
- package/lib/render/unit.js +32 -13
- package/lib/{report-model.js → report/model.js} +4 -6
- package/lib/{report-parse.js → report/parse.js} +9 -14
- package/lib/{report-render.js → report/render.js} +46 -14
- package/lib/{report-run.js → report/run.js} +42 -30
- package/lib/{report-test.js → report/test.js} +56 -55
- package/lib/{report-tools.js → report/tools.js} +24 -33
- package/lib/review/agent-instructions.md +9 -0
- package/lib/review/format.js +426 -0
- package/lib/{review-merge.js → review/merge.js} +9 -8
- package/lib/review/review.js +190 -0
- package/lib/review/store.js +281 -0
- package/lib/review/templates.js +83 -0
- package/lib/runs/commands.js +14 -0
- package/lib/runs/logs.js +299 -0
- package/lib/runs/output.js +174 -0
- package/lib/runs/process.js +113 -0
- package/lib/{runs.js → runs/runs.js} +84 -36
- package/lib/runs/scripts.js +150 -0
- package/lib/session/agents/agents.js +958 -0
- package/lib/session/agents/job.js +320 -0
- package/lib/session/agents/jobs.js +355 -0
- package/lib/session/agents/options.js +153 -0
- package/lib/session/agents/plans.js +83 -0
- package/lib/session/branches.js +33 -39
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +91 -52
- package/lib/session/compose.js +181 -145
- package/lib/session/dashboard.js +23 -22
- package/lib/session/dispatch.js +85 -21
- package/lib/session/files-pane.js +47 -26
- package/lib/session/find.js +16 -29
- package/lib/session/import.js +82 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +75 -72
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +6 -3
- package/lib/session/npm/history.js +249 -0
- package/lib/session/npm/npm.js +815 -0
- package/lib/session/{packages.js → npm/packages.js} +21 -14
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +202 -10
- package/lib/session/prefs.js +112 -0
- package/lib/session/reads.js +109 -0
- package/lib/session/review.js +28 -10
- package/lib/session/run-panel.js +40 -0
- package/lib/{session.js → session/session.js} +156 -63
- package/lib/session/tasks.js +320 -65
- package/lib/session/templates.js +1 -1
- package/lib/session/terminal.js +94 -12
- package/lib/session/unit.js +35 -30
- package/lib/session/update.js +194 -6
- package/lib/session/view.js +65 -65
- package/lib/session/workspace.js +39 -78
- package/lib/{capabilities.js → source/capabilities.js} +3 -12
- package/lib/{github.js → source/github.js} +102 -32
- package/lib/{gitlab.js → source/gitlab.js} +87 -10
- package/lib/{remote.js → source/remote.js} +144 -40
- package/lib/source/review-threads.graphql +20 -0
- package/lib/source/source.js +74 -0
- package/lib/term/ansi.js +396 -0
- package/lib/{clipboard.js → term/clipboard.js} +3 -6
- package/lib/{select.js → term/select.js} +2 -2
- package/lib/term/themes/dark.json +122 -0
- package/lib/term/themes/light.json +122 -0
- package/lib/term/wide.txt +21 -0
- package/lib/{wrap.js → term/wrap.js} +4 -4
- package/package.json +2 -2
- package/lib/ansi.js +0 -545
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -455
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -806
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/render/search.js +0 -25
- package/lib/review.js +0 -745
- package/lib/session/accessors.js +0 -26
- package/lib/session/edit-click.js +0 -57
- package/lib/session/npm.js +0 -452
- package/lib/session/progress.js +0 -41
- package/lib/source.js +0 -36
- package/lib/update.js +0 -192
- /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { jsonParse, isHashObject } = require('metautil');
|
|
7
|
+
const { writeJson, trimText } = require('../common/utilities.js');
|
|
8
|
+
|
|
9
|
+
const { planAllows: PLAN_ALLOWS } = require('./allow.json');
|
|
10
|
+
|
|
11
|
+
const cursorConfigPath = (env = process.env) => {
|
|
12
|
+
const override = trimText(env.RESLOP_CURSOR_CONFIG);
|
|
13
|
+
if (override) return override;
|
|
14
|
+
const root = trimText(env.CURSOR_CONFIG_DIR);
|
|
15
|
+
const dir = root || path.join(os.homedir(), '.cursor');
|
|
16
|
+
return path.join(dir, 'cli-config.json');
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const readConfig = (file) => {
|
|
20
|
+
let text = null;
|
|
21
|
+
try {
|
|
22
|
+
text = fs.readFileSync(file, 'utf8');
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (!error || error.code !== 'ENOENT') return null;
|
|
25
|
+
}
|
|
26
|
+
if (text === null) return {};
|
|
27
|
+
const data = jsonParse(text);
|
|
28
|
+
if (!isHashObject(data)) return null;
|
|
29
|
+
return data;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const allowList = (data) => {
|
|
33
|
+
if (!data.permissions || typeof data.permissions !== 'object') {
|
|
34
|
+
data.permissions = { allow: [], deny: [] };
|
|
35
|
+
}
|
|
36
|
+
if (!Array.isArray(data.permissions.allow)) data.permissions.allow = [];
|
|
37
|
+
if (!Array.isArray(data.permissions.deny)) data.permissions.deny = [];
|
|
38
|
+
return data.permissions.allow;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const ensureAllows = (file, entries) => {
|
|
42
|
+
const data = readConfig(file);
|
|
43
|
+
if (!data) return false;
|
|
44
|
+
const allow = allowList(data);
|
|
45
|
+
let changed = false;
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const token = trimText(entry);
|
|
48
|
+
if (!token || allow.includes(token)) continue;
|
|
49
|
+
allow.push(token);
|
|
50
|
+
changed = true;
|
|
51
|
+
}
|
|
52
|
+
if (!changed) return true;
|
|
53
|
+
if (!data.version) data.version = 1;
|
|
54
|
+
if (!data.approvalMode) data.approvalMode = 'allowlist';
|
|
55
|
+
try {
|
|
56
|
+
writeJson(file, data);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const ensurePlanAllows = (env = process.env) =>
|
|
64
|
+
ensureAllows(cursorConfigPath(env), PLAN_ALLOWS);
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
PLAN_ALLOWS,
|
|
68
|
+
cursorConfigPath,
|
|
69
|
+
ensureAllows,
|
|
70
|
+
ensurePlanAllows,
|
|
71
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"planAllows": [
|
|
3
|
+
"Write(.plan/**)",
|
|
4
|
+
"Write(.plan/**/*.md)",
|
|
5
|
+
"Write(**/.plan/**)",
|
|
6
|
+
"Write(**/.plan/**/*.md)",
|
|
7
|
+
"Shell(npm)",
|
|
8
|
+
"Shell(npm test)",
|
|
9
|
+
"Shell(npm t)",
|
|
10
|
+
"Shell(npx)",
|
|
11
|
+
"Shell(node)",
|
|
12
|
+
"Shell(node --test)",
|
|
13
|
+
"Shell(reslop)",
|
|
14
|
+
"Shell(reslop t)"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { jsonParse, isHashObject } = require('metautil');
|
|
4
|
+
const { stripAnsi } = require('../term/ansi.js');
|
|
5
|
+
const { trimText } = require('../common/utilities.js');
|
|
6
|
+
|
|
7
|
+
const MODEL_ID = /^[A-Za-z][A-Za-z0-9_./:+#-]*$/;
|
|
8
|
+
const SKIP_TOKEN = new RegExp(
|
|
9
|
+
'^(no|available|loading|fetching|usage|help|try|tip|error|' +
|
|
10
|
+
'warning|command|commands|options|flag|version|' +
|
|
11
|
+
'unknown|invalid)$',
|
|
12
|
+
'i',
|
|
13
|
+
);
|
|
14
|
+
const CURSOR_ROW = /^([A-Za-z][A-Za-z0-9_./:+#-]*)\s+-\s+\S/;
|
|
15
|
+
const CURSOR_NOISE = /^(?:loading models|available models$|tip:|no models)/i;
|
|
16
|
+
const AVAILABLE_LINE = /^Available models:\s*(.+)$/i;
|
|
17
|
+
const WIDE_LABEL = /\b1M\b/;
|
|
18
|
+
const CONTEXT_TAG = /\[context=[^\]]*\]$/;
|
|
19
|
+
// list-models repeats one slug for every context, so 500k is unlabeled
|
|
20
|
+
const MODEL_CONTEXTS = {
|
|
21
|
+
'grok-4.7': ['256k', '500k'],
|
|
22
|
+
};
|
|
23
|
+
const JSON_KEYS = ['slug', 'id', 'model', 'modelID', 'name'];
|
|
24
|
+
const OPTION_LINE = /^\s*(?:-\w,\s*)?--[\w-]+/;
|
|
25
|
+
// (a, b), <a|b>, {a,b} or [possible values: a, b] in an option's help
|
|
26
|
+
const VALUE_LIST =
|
|
27
|
+
/[(<[{](?:[a-z ]+:)?\s*("?[\w-]+"?(?:\s*[,|]\s*"?[\w-]+"?)+)\s*[)>\]}]/i;
|
|
28
|
+
const QUOTED_NAME = /'([A-Za-z][\w.:-]*)'/g;
|
|
29
|
+
const FAST = '-fast';
|
|
30
|
+
const THINKING = '-thinking';
|
|
31
|
+
|
|
32
|
+
const EFFORT_LEVELS = [
|
|
33
|
+
'extra-high',
|
|
34
|
+
'xhigh',
|
|
35
|
+
'minimal',
|
|
36
|
+
'medium',
|
|
37
|
+
'none',
|
|
38
|
+
'high',
|
|
39
|
+
'max',
|
|
40
|
+
'low',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const EFFORT_RANK = [
|
|
44
|
+
'default',
|
|
45
|
+
'none',
|
|
46
|
+
'minimal',
|
|
47
|
+
'low',
|
|
48
|
+
'medium',
|
|
49
|
+
'high',
|
|
50
|
+
'xhigh',
|
|
51
|
+
'extra-high',
|
|
52
|
+
'max',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const textLines = (text) => {
|
|
56
|
+
const lines = [];
|
|
57
|
+
for (const raw of stripAnsi(`${text ?? ''}`).split('\n')) {
|
|
58
|
+
const line = raw.trim();
|
|
59
|
+
if (line) lines.push(line);
|
|
60
|
+
}
|
|
61
|
+
return lines;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const uniqueNames = (names) => {
|
|
65
|
+
const unique = new Set();
|
|
66
|
+
for (const raw of names) {
|
|
67
|
+
const name = trimText(raw);
|
|
68
|
+
if (name) unique.add(name);
|
|
69
|
+
}
|
|
70
|
+
return [...unique];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const parseNameList = (text) => {
|
|
74
|
+
const names = [];
|
|
75
|
+
for (const line of textLines(text)) {
|
|
76
|
+
const token = line.split(/\s+/)[0].replace(/:+$/, '');
|
|
77
|
+
if (MODEL_ID.test(token) && !SKIP_TOKEN.test(token)) names.push(token);
|
|
78
|
+
}
|
|
79
|
+
return uniqueNames(names);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const parseCursorModels = (text) => {
|
|
83
|
+
const names = [];
|
|
84
|
+
for (const line of textLines(text)) {
|
|
85
|
+
if (CURSOR_NOISE.test(line)) continue;
|
|
86
|
+
const listed = AVAILABLE_LINE.exec(line);
|
|
87
|
+
const row = listed ? null : CURSOR_ROW.exec(line);
|
|
88
|
+
if (listed) names.push(...listed[1].split(','));
|
|
89
|
+
else if (row) names.push(row[1]);
|
|
90
|
+
}
|
|
91
|
+
return names.length ? uniqueNames(names) : parseNameList(text);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const parseCursorWide = (text) => {
|
|
95
|
+
const names = [];
|
|
96
|
+
for (const line of textLines(text)) {
|
|
97
|
+
const row = CURSOR_ROW.exec(line);
|
|
98
|
+
if (row && WIDE_LABEL.test(line)) names.push(row[1]);
|
|
99
|
+
}
|
|
100
|
+
return uniqueNames(names);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// The help of one option: its line and the indented lines that follow
|
|
104
|
+
const optionHelp = (text, flag) => {
|
|
105
|
+
const lines = stripAnsi(`${text ?? ''}`).split('\n');
|
|
106
|
+
const named = (line) => OPTION_LINE.test(line) && line.includes(flag);
|
|
107
|
+
const at = flag ? lines.findIndex(named) : -1;
|
|
108
|
+
if (at < 0) return '';
|
|
109
|
+
const block = [lines[at]];
|
|
110
|
+
for (const line of lines.slice(at + 1)) {
|
|
111
|
+
if (OPTION_LINE.test(line) || /^\S/.test(line)) break;
|
|
112
|
+
block.push(line.trim());
|
|
113
|
+
}
|
|
114
|
+
return block.join(' ');
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const optionValues = (text, flag) => {
|
|
118
|
+
const listed = VALUE_LIST.exec(optionHelp(text, flag));
|
|
119
|
+
if (!listed) return [];
|
|
120
|
+
const values = listed[1].split(/[,|]/);
|
|
121
|
+
return uniqueNames(values.map((value) => value.replaceAll('"', '')));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const parseHelpModels = (text) => {
|
|
125
|
+
const help = optionHelp(text, '--model');
|
|
126
|
+
return uniqueNames([...help.matchAll(QUOTED_NAME)].map((hit) => hit[1]));
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const jsonStart = (text) => {
|
|
130
|
+
const starts = [text.indexOf('{'), text.indexOf('[')].filter((at) => at >= 0);
|
|
131
|
+
return starts.length ? Math.min(...starts) : -1;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const jsonModelId = (value) => {
|
|
135
|
+
for (const key of JSON_KEYS) {
|
|
136
|
+
const id = value[key];
|
|
137
|
+
if (typeof id === 'string' && id.trim()) return id.trim();
|
|
138
|
+
}
|
|
139
|
+
return '';
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const jsonEfforts = (value) => {
|
|
143
|
+
const listed = value.supported_reasoning_levels;
|
|
144
|
+
if (!Array.isArray(listed)) return [];
|
|
145
|
+
const levels = listed.map((item) =>
|
|
146
|
+
isHashObject(item) ? item.effort : item,
|
|
147
|
+
);
|
|
148
|
+
return uniqueNames(levels.filter((level) => typeof level === 'string'));
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const collectJsonModels = (value, found) => {
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
for (const item of value) {
|
|
154
|
+
if (typeof item === 'string') found.push({ id: item, efforts: [] });
|
|
155
|
+
else collectJsonModels(item, found);
|
|
156
|
+
}
|
|
157
|
+
return found;
|
|
158
|
+
}
|
|
159
|
+
if (!isHashObject(value)) return found;
|
|
160
|
+
const id = jsonModelId(value);
|
|
161
|
+
const shown = id && value.visibility !== 'hide';
|
|
162
|
+
if (shown) found.push({ id, efforts: jsonEfforts(value) });
|
|
163
|
+
if (value.models) collectJsonModels(value.models, found);
|
|
164
|
+
if (value.data) collectJsonModels(value.data, found);
|
|
165
|
+
return found;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const jsonModels = (text) => {
|
|
169
|
+
const start = jsonStart(text);
|
|
170
|
+
const data = start < 0 ? null : jsonParse(text.slice(start));
|
|
171
|
+
return collectJsonModels(data, []);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const parseJsonModels = (text) => {
|
|
175
|
+
const plain = stripAnsi(`${text ?? ''}`);
|
|
176
|
+
const names = jsonModels(plain).map((model) => model.id);
|
|
177
|
+
return names.length ? uniqueNames(names) : parseNameList(plain);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Effort levels each model lists for itself, keyed by the model id
|
|
181
|
+
const parseJsonEfforts = (text) => {
|
|
182
|
+
const efforts = new Map();
|
|
183
|
+
for (const model of jsonModels(stripAnsi(`${text ?? ''}`))) {
|
|
184
|
+
if (model.efforts.length) efforts.set(model.id, model.efforts);
|
|
185
|
+
}
|
|
186
|
+
return efforts;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// A full listing replaces the built-in names, a partial one extends them
|
|
190
|
+
const mergeModels = (fallback, listed, partial = false) => {
|
|
191
|
+
const names = listed ?? [];
|
|
192
|
+
const kept = partial || !names.length;
|
|
193
|
+
const known = kept && fallback ? fallback : [];
|
|
194
|
+
return uniqueNames(['default', ...names, ...known]);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const cutSuffix = (name, suffix) => {
|
|
198
|
+
if (!name.endsWith(suffix) || name.length <= suffix.length) return '';
|
|
199
|
+
return name.slice(0, -suffix.length);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const withoutFast = (name) => cutSuffix(name, FAST) || name;
|
|
203
|
+
|
|
204
|
+
const effortTail = (name) => {
|
|
205
|
+
for (const level of EFFORT_LEVELS) {
|
|
206
|
+
const base = cutSuffix(name, `-${level}`);
|
|
207
|
+
if (base) return { base, level };
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const hasFastTail = (name) => {
|
|
213
|
+
const unfast = cutSuffix(name, FAST);
|
|
214
|
+
if (!unfast) return false;
|
|
215
|
+
const unthink = cutSuffix(unfast, THINKING);
|
|
216
|
+
return Boolean(effortTail(unfast) || (unthink && effortTail(unthink)));
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const splitEffortModel = (id) => {
|
|
220
|
+
const name = trimText(id);
|
|
221
|
+
if (!name) return null;
|
|
222
|
+
const fast = hasFastTail(name);
|
|
223
|
+
let rest = fast ? cutSuffix(name, FAST) : name;
|
|
224
|
+
let thinking = '';
|
|
225
|
+
const unthink = cutSuffix(rest, THINKING);
|
|
226
|
+
if (unthink && effortTail(unthink)) {
|
|
227
|
+
rest = unthink;
|
|
228
|
+
thinking = 'after';
|
|
229
|
+
}
|
|
230
|
+
const hit = effortTail(rest);
|
|
231
|
+
if (!hit) return null;
|
|
232
|
+
const before = cutSuffix(hit.base, THINKING);
|
|
233
|
+
if (before) thinking = 'before';
|
|
234
|
+
const base = before || hit.base;
|
|
235
|
+
return { base, effort: hit.level, thinking, fast, id: name };
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const contextFamily = (model) => {
|
|
239
|
+
const raw = `${model ?? ''}`.replace(CONTEXT_TAG, '').trim();
|
|
240
|
+
const base = withoutFast(raw);
|
|
241
|
+
const part = splitEffortModel(base);
|
|
242
|
+
return part ? part.base : base;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const contextMenu = (model, wide, catalog, fallback) => {
|
|
246
|
+
const sizes = [];
|
|
247
|
+
for (const size of fallback ?? []) {
|
|
248
|
+
const text = trimText(size);
|
|
249
|
+
if (text && text !== 'default') sizes.push(text);
|
|
250
|
+
}
|
|
251
|
+
if (!sizes.length) return [];
|
|
252
|
+
const named = MODEL_CONTEXTS[contextFamily(model)];
|
|
253
|
+
if (named) return named.slice();
|
|
254
|
+
if (!trimText(model) || catalog.hasWide(wide, model)) return sizes;
|
|
255
|
+
return sizes.filter((size) => size !== '1m');
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const modelLabel = (part) => {
|
|
259
|
+
const thinking = part.thinking ? THINKING : '';
|
|
260
|
+
return `${part.base}${thinking}${part.fast ? FAST : ''}`;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const groupKey = (part) =>
|
|
264
|
+
`${part.base}\0${part.thinking}\0${part.fast ? 'fast' : ''}`;
|
|
265
|
+
|
|
266
|
+
const rankedLevels = (ids) => {
|
|
267
|
+
const levels = EFFORT_RANK.filter((level) => ids.has(level));
|
|
268
|
+
for (const level of ids.keys()) {
|
|
269
|
+
if (!levels.includes(level)) levels.push(level);
|
|
270
|
+
}
|
|
271
|
+
return levels;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const groupEntry = (group) => ({
|
|
275
|
+
model: group.model,
|
|
276
|
+
encoded: true,
|
|
277
|
+
levels: rankedLevels(group.ids),
|
|
278
|
+
ids: group.ids,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const plainEntry = (name) => ({
|
|
282
|
+
model: name,
|
|
283
|
+
encoded: false,
|
|
284
|
+
levels: [],
|
|
285
|
+
ids: new Map([['default', name]]),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const defaultGroup = (groups, name) => {
|
|
289
|
+
const direct = groups.get(`${name}\0\0`);
|
|
290
|
+
if (direct && !direct.ids.has('default')) return direct;
|
|
291
|
+
for (const group of groups.values()) {
|
|
292
|
+
if (group.model === name && !group.ids.has('default')) return group;
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const groupModels = (ids) => {
|
|
298
|
+
const names = uniqueNames(ids ?? []);
|
|
299
|
+
const groups = new Map();
|
|
300
|
+
const owners = new Map();
|
|
301
|
+
const plain = [];
|
|
302
|
+
for (const name of names) {
|
|
303
|
+
const part = splitEffortModel(name);
|
|
304
|
+
if (!part) {
|
|
305
|
+
plain.push(name);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const key = groupKey(part);
|
|
309
|
+
if (!groups.has(key)) {
|
|
310
|
+
groups.set(key, { model: modelLabel(part), ids: new Map() });
|
|
311
|
+
}
|
|
312
|
+
const group = groups.get(key);
|
|
313
|
+
if (!group.ids.has(part.effort)) group.ids.set(part.effort, name);
|
|
314
|
+
owners.set(name, group);
|
|
315
|
+
}
|
|
316
|
+
for (const name of plain) {
|
|
317
|
+
const group = defaultGroup(groups, name);
|
|
318
|
+
if (!group) continue;
|
|
319
|
+
group.ids.set('default', name);
|
|
320
|
+
owners.set(name, group);
|
|
321
|
+
}
|
|
322
|
+
const entries = [];
|
|
323
|
+
const listed = new Set();
|
|
324
|
+
for (const name of names) {
|
|
325
|
+
const group = owners.get(name) ?? null;
|
|
326
|
+
const slot = group ?? name;
|
|
327
|
+
if (listed.has(slot)) continue;
|
|
328
|
+
listed.add(slot);
|
|
329
|
+
entries.push(group ? groupEntry(group) : plainEntry(name));
|
|
330
|
+
}
|
|
331
|
+
return entries;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const fitEffort = (levels, effort) => {
|
|
335
|
+
const want = trimText(effort) || 'default';
|
|
336
|
+
if (!levels || !levels.length) return 'default';
|
|
337
|
+
if (levels.includes(want)) return want;
|
|
338
|
+
const named = levels.find((level) => {
|
|
339
|
+
const name = trimText(level);
|
|
340
|
+
return name && name !== 'default';
|
|
341
|
+
});
|
|
342
|
+
return named ?? levels[0];
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const plainResolved = (model, effort) => ({
|
|
346
|
+
model,
|
|
347
|
+
encoded: false,
|
|
348
|
+
effort,
|
|
349
|
+
family: model,
|
|
350
|
+
levels: [],
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const entryResolved = (entry, effort) => {
|
|
354
|
+
if (!entry.encoded) return plainResolved(entry.model, effort);
|
|
355
|
+
const level = fitEffort(entry.levels, effort);
|
|
356
|
+
return {
|
|
357
|
+
model: entry.ids.get(level) ?? entry.model,
|
|
358
|
+
encoded: true,
|
|
359
|
+
effort: level,
|
|
360
|
+
family: entry.model,
|
|
361
|
+
levels: entry.levels,
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const entryNames = (entry) => [entry.model, ...entry.ids.values()];
|
|
366
|
+
|
|
367
|
+
class ModelCatalog {
|
|
368
|
+
constructor(ids) {
|
|
369
|
+
this.entries = groupModels(ids);
|
|
370
|
+
this.byName = new Map();
|
|
371
|
+
for (const entry of this.entries) {
|
|
372
|
+
if (!this.byName.has(entry.model)) this.byName.set(entry.model, entry);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
find(name) {
|
|
377
|
+
return this.byName.get(name) ?? null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
hasFast() {
|
|
381
|
+
return this.entries.some((entry) => entry.model.endsWith(FAST));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
prefixed(entry, name) {
|
|
385
|
+
if (entry && entry.encoded) return null;
|
|
386
|
+
const base = withoutFast(entry ? entry.model : trimText(name));
|
|
387
|
+
if (!base || base.startsWith('cursor-')) return null;
|
|
388
|
+
const found = this.find(`cursor-${base}`);
|
|
389
|
+
return found && found.encoded ? found : null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
resolve(choice) {
|
|
393
|
+
const effort = trimText(choice.effort) || 'default';
|
|
394
|
+
const entry = this.find(choice.model);
|
|
395
|
+
const prefixed = this.prefixed(entry, choice.model);
|
|
396
|
+
if (prefixed) return entryResolved(prefixed, effort);
|
|
397
|
+
if (entry) return entryResolved(entry, effort);
|
|
398
|
+
const part = splitEffortModel(choice.model);
|
|
399
|
+
const family = part ? this.find(modelLabel(part)) : null;
|
|
400
|
+
if (!family) return plainResolved(choice.model, effort);
|
|
401
|
+
return entryResolved(family, effort === 'default' ? part.effort : effort);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
withFast(resolved, fast) {
|
|
405
|
+
const family = `${resolved.family ?? ''}`;
|
|
406
|
+
const onFast = family.endsWith(FAST);
|
|
407
|
+
if (!family || (fast === true) === onFast) return resolved;
|
|
408
|
+
const other = onFast ? withoutFast(family) : `${family}${FAST}`;
|
|
409
|
+
const entry = this.find(other);
|
|
410
|
+
return entry ? entryResolved(entry, resolved.effort) : resolved;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
hasWide(wide, model) {
|
|
414
|
+
if (!(wide instanceof Set)) return true;
|
|
415
|
+
const raw = `${model ?? ''}`.replace(CONTEXT_TAG, '');
|
|
416
|
+
const base = withoutFast(raw);
|
|
417
|
+
if (!base || base === 'default') return true;
|
|
418
|
+
if (wide.has(raw) || wide.has(base)) return true;
|
|
419
|
+
const wanted = new Set([base, `${base}${FAST}`, raw]);
|
|
420
|
+
return this.entries.some((entry) => {
|
|
421
|
+
const names = entryNames(entry);
|
|
422
|
+
if (!names.some((name) => wanted.has(name))) return false;
|
|
423
|
+
return names.some((name) => wide.has(name));
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const EMPTY_CATALOG = new ModelCatalog([]);
|
|
429
|
+
const catalogs = new WeakMap();
|
|
430
|
+
|
|
431
|
+
const catalogOf = (models) => {
|
|
432
|
+
if (!Array.isArray(models)) return EMPTY_CATALOG;
|
|
433
|
+
if (!catalogs.has(models)) catalogs.set(models, new ModelCatalog(models));
|
|
434
|
+
return catalogs.get(models);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const emptyChoice = (row) => ({
|
|
438
|
+
model: row && row.models && row.models[0] ? row.models[0] : 'default',
|
|
439
|
+
effort: 'default',
|
|
440
|
+
extra: '',
|
|
441
|
+
fast: false,
|
|
442
|
+
context: '',
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
module.exports = {
|
|
446
|
+
catalogOf,
|
|
447
|
+
parseNameList,
|
|
448
|
+
parseCursorModels,
|
|
449
|
+
parseCursorWide,
|
|
450
|
+
parseJsonModels,
|
|
451
|
+
parseJsonEfforts,
|
|
452
|
+
parseHelpModels,
|
|
453
|
+
optionValues,
|
|
454
|
+
mergeModels,
|
|
455
|
+
contextMenu,
|
|
456
|
+
groupModels,
|
|
457
|
+
fitEffort,
|
|
458
|
+
withoutFast,
|
|
459
|
+
emptyChoice,
|
|
460
|
+
};
|
package/lib/api.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
|
-
const ansi = require('./ansi.js');
|
|
2
|
+
const ansi = require('./term/ansi.js');
|
|
4
3
|
const diff = require('./diff/diff.js');
|
|
5
|
-
const git = require('./git.js');
|
|
6
|
-
const keys = require('./keys.js');
|
|
4
|
+
const git = require('./git/git.js');
|
|
5
|
+
const keys = require('./input/keys.js');
|
|
7
6
|
const render = require('./render/render.js');
|
|
8
|
-
const highlight = require('./highlight.js');
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const session = require('./session.js');
|
|
14
|
-
const { Session } = session;
|
|
7
|
+
const highlight = require('./highlight/highlight.js');
|
|
8
|
+
const github = require('./source/github.js');
|
|
9
|
+
const gitlab = require('./source/gitlab.js');
|
|
10
|
+
const source = require('./source/source.js');
|
|
11
|
+
const { Session } = require('./session/session.js');
|
|
15
12
|
const cli = require('./cli.js');
|
|
16
13
|
|
|
17
14
|
module.exports = {
|
|
@@ -28,16 +25,22 @@ module.exports = {
|
|
|
28
25
|
itemsFromFiles: diff.itemsFromFiles,
|
|
29
26
|
createGitRepo: git.createGitRepo,
|
|
30
27
|
parseGithubPrUrl: github.parseGithubPrUrl,
|
|
28
|
+
parseGithubIssueUrl: github.parseGithubIssueUrl,
|
|
29
|
+
parseGithubIssueListUrl: github.parseGithubIssueListUrl,
|
|
31
30
|
loadPullRequest: github.loadPullRequest,
|
|
31
|
+
loadGithubIssue: github.loadGithubIssue,
|
|
32
32
|
parseGitlabMrUrl: gitlab.parseGitlabMrUrl,
|
|
33
|
+
parseGitlabIssueUrl: gitlab.parseGitlabIssueUrl,
|
|
34
|
+
parseGitlabIssueListUrl: gitlab.parseGitlabIssueListUrl,
|
|
33
35
|
loadMergeRequest: gitlab.loadMergeRequest,
|
|
36
|
+
loadGitlabIssue: gitlab.loadGitlabIssue,
|
|
34
37
|
selectChangeSource: source.selectChangeSource,
|
|
35
38
|
decodeChunk: keys.decodeChunk,
|
|
36
39
|
hitAction: keys.hitAction,
|
|
37
40
|
renderFrame: render.renderFrame,
|
|
38
41
|
tokenize: highlight.tokenize,
|
|
39
42
|
overlayTokens: highlight.overlayTokens,
|
|
40
|
-
detectLang:
|
|
43
|
+
detectLang: highlight.detectLang,
|
|
41
44
|
run: cli.run,
|
|
42
45
|
Session,
|
|
43
46
|
};
|