@illuminis/comprism 0.1.4 → 0.1.5
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/out/agent/command.js +1 -1
- package/out/agent/render.d.ts +39 -6
- package/out/agent/render.js +287 -28
- package/out/agent/session.d.ts +15 -0
- package/out/agent/session.js +64 -9
- package/out/commands/ask.js +12 -3
- package/out/commands/login.js +63 -9
- package/out/commands/repl.d.ts +4 -0
- package/out/commands/repl.js +456 -18
- package/out/commands/welcome.d.ts +25 -1
- package/out/commands/welcome.js +71 -35
- package/out/lib/gateway.d.ts +4 -0
- package/out/lib/keyboard.d.ts +3 -0
- package/out/lib/keyboard.js +7 -0
- package/out/lib/lastlogin.d.ts +14 -0
- package/out/lib/lastlogin.js +105 -0
- package/out/lib/picker.d.ts +1 -0
- package/out/lib/picker.js +158 -0
- package/out/lib/prompt.d.ts +3 -0
- package/out/lib/prompt.js +21 -0
- package/out/lib/startcache.d.ts +13 -0
- package/out/lib/startcache.js +78 -0
- package/out/lib/ui.d.ts +8 -0
- package/out/lib/ui.js +34 -0
- package/out/lib/voice.d.ts +36 -0
- package/out/lib/voice.js +136 -1
- package/out/native/dictate +0 -0
- package/out/thin.js +2 -2
- package/package.json +3 -2
package/out/commands/welcome.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.session = session;
|
|
36
37
|
exports.showWelcome = showWelcome;
|
|
37
38
|
/**
|
|
38
39
|
* The first thing anybody sees.
|
|
@@ -95,33 +96,46 @@ async function session() {
|
|
|
95
96
|
return reply.session;
|
|
96
97
|
}
|
|
97
98
|
/**
|
|
98
|
-
* The mark: a
|
|
99
|
+
* The mark: the app icon as a terminal can draw it cleanly. A five layer prism,
|
|
100
|
+
* each layer two blocks wider than the one above and a shade deeper, with
|
|
101
|
+
* three beams leaving its right side (up, level and down) and a ring of the
|
|
102
|
+
* icon's cyan to purple around it (owner, 28 September 2026).
|
|
99
103
|
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* on a font. A full block renders identically everywhere, so the only thing
|
|
104
|
-
* carrying the design is the color, which is the point of a prism.
|
|
104
|
+
* Solid blocks only, no blended edges and no white, because a terminal draws a
|
|
105
|
+
* solid block sharply in any font and a white one vanishes on a white screen.
|
|
106
|
+
* Drawn two pixels to a character, so it is nine lines tall.
|
|
105
107
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* base carries all eight. That is a prism doing its job: one input at the top,
|
|
109
|
-
* the whole spectrum at the bottom. Banding it by row would have shown four of
|
|
110
|
-
* the eight and read as a striped triangle instead.
|
|
111
|
-
*
|
|
112
|
-
* The eight values come from the same helper as the rule beneath it, so the
|
|
113
|
-
* mark and the rule can never drift apart.
|
|
108
|
+
* Without color the picture cannot be drawn, so the four row block pyramid
|
|
109
|
+
* stands in: full blocks only, which render the same in every font.
|
|
114
110
|
*/
|
|
115
|
-
const
|
|
111
|
+
const ICON = [
|
|
112
|
+
'..........................................53a6ed51a1ed509cee4f98ee4d93ee4c8eee4a89ee..................................................................',
|
|
113
|
+
'..............................54abed53a6ed51a1ed..............................4984ef477fef467bef......................................................',
|
|
114
|
+
'........................54abed53a6ed......................................................4476ef4371ef................................................',
|
|
115
|
+
'..................54abed53a6ed..................................................................426cf04067f0..........................................',
|
|
116
|
+
'..................53a6ed....................................a8d8f7a8d8f7..............................3f62f0..........................................',
|
|
117
|
+
'............53a6ed51a1ed....................................a8d8f7a8d8f7..............................3f5ff0425ef0..................4292f23f8af43b82f6',
|
|
118
|
+
'............51a1ed....................................74aef074aef074aef074aef0..............................455cf0......49a2ee469af0..................',
|
|
119
|
+
'............509cee....................................74aef074aef074aef074aef0........................54bbe851b3ea4daaec..............................',
|
|
120
|
+
'............4f98ee..............................4f86e64f86e64f86e64f86e64f86e64f86e6......5bcbe458c3e6......4b59f1....................................',
|
|
121
|
+
'............4d93ee..............................4f86e64f86e64f86e64f86e64f86e64f86e6f07a36f0e35befe25aeee259ece158eae057e9df56e8de55e6de54e4dd53e3dc52',
|
|
122
|
+
'............4c8eee........................3e60d83e60d83e60d83e60d83e60d83e60d83e60d83e60d8ed7435ea6e34......5056f1....................................',
|
|
123
|
+
'............4a89ee........................3e60d83e60d83e60d83e60d83e60d83e60d83e60d83e60d8............e76932e46331e15d30..............................',
|
|
124
|
+
'............4984ef477fef............2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a......5355f15653f2......de572fdb512e..................',
|
|
125
|
+
'..................467bef............2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a2e3a9a......5653f2........................d84c2cd5462bd2402a',
|
|
126
|
+
'..................4476ef4371ef..................................................................5653f25952f2..........................................',
|
|
127
|
+
'........................426cf04067f0......................................................5653f25952f2................................................',
|
|
128
|
+
'..............................3f62f03f5ff0425ef0..............................5355f15653f25952f2......................................................',
|
|
129
|
+
'..........................................455cf0485bf14b59f14e58f15056f15355f15653f2..................................................................',
|
|
130
|
+
];
|
|
116
131
|
function mark() {
|
|
132
|
+
const picture = ui.pixelArt(ICON);
|
|
133
|
+
if (picture.length)
|
|
134
|
+
return picture;
|
|
117
135
|
const rows = [];
|
|
118
136
|
for (let r = 1; r <= 4; r++) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
let line = ' '.repeat(left);
|
|
122
|
-
for (let i = 0; i < blocks; i++)
|
|
123
|
-
line += ui.spectrumAt(left + i, '\u2588');
|
|
124
|
-
rows.push(line + ' '.repeat(left));
|
|
137
|
+
const left = 4 - r;
|
|
138
|
+
rows.push(' '.repeat(left) + '\u2588'.repeat(r * 2) + ' '.repeat(left));
|
|
125
139
|
}
|
|
126
140
|
return rows;
|
|
127
141
|
}
|
|
@@ -133,19 +147,31 @@ function row(label, value) {
|
|
|
133
147
|
function readable(name) {
|
|
134
148
|
return name.replace(/[-_]/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase());
|
|
135
149
|
}
|
|
136
|
-
|
|
150
|
+
/** `known` is who this machine is, when the caller already has it (the last
|
|
151
|
+
* answer, drawn at once while a fresh one is asked for). */
|
|
152
|
+
async function showWelcome(known, signingIn = false, withMark = true) {
|
|
137
153
|
const conn = (0, connection_1.readConnection)();
|
|
138
154
|
const cfg = (0, config_1.loadConfig)();
|
|
139
155
|
const folder = (0, settings_1.currentWorkspace)();
|
|
140
|
-
const me = await session();
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
156
|
+
const me = known !== undefined ? known : await session();
|
|
157
|
+
// The mark once per start. Signing in first draws it; the session that
|
|
158
|
+
// follows draws only the account, so nobody sees the same screen twice.
|
|
159
|
+
if (withMark) {
|
|
160
|
+
const art = mark();
|
|
161
|
+
// The name and the line under it sit beside the middle of the mark.
|
|
162
|
+
const at = Math.max(0, Math.floor(art.length / 2) - 1);
|
|
163
|
+
const beside = {
|
|
164
|
+
[at]: `${ui.c.bold('CompletionPrism')} ${ui.c.dim(`v${version_1.COMPRISM_VERSION}`)}`,
|
|
165
|
+
[at + 1]: ui.c.muted('Optimize AI completion economics by optimizing work.'),
|
|
166
|
+
};
|
|
167
|
+
out();
|
|
168
|
+
art.forEach((line, i) => out(beside[i] ? ` ${line} ${beside[i]}` : ` ${line}`));
|
|
169
|
+
out(ui.prismRule());
|
|
170
|
+
out();
|
|
171
|
+
}
|
|
172
|
+
// Signing in comes next and says everything else itself.
|
|
173
|
+
if (signingIn && !conn?.url)
|
|
174
|
+
return;
|
|
149
175
|
if (!conn?.url) {
|
|
150
176
|
// Nothing else here is knowable yet, and the one useful thing to say is the
|
|
151
177
|
// single command that changes that.
|
|
@@ -153,8 +179,12 @@ async function showWelcome() {
|
|
|
153
179
|
out(row('Comparing', cfg.defaultModel || 'not set'));
|
|
154
180
|
out(row('Workspace', `${folder.path} ${ui.c.dim('(where you are)')}`));
|
|
155
181
|
out();
|
|
156
|
-
|
|
157
|
-
|
|
182
|
+
// Not said when signing in follows at once: naming a command to type, just
|
|
183
|
+
// above the sign in it would start, reads as a second step.
|
|
184
|
+
if (!signingIn) {
|
|
185
|
+
out(` ${ui.c.dim('start with')} ${ui.c.bold('comprism login')}`);
|
|
186
|
+
out();
|
|
187
|
+
}
|
|
158
188
|
return;
|
|
159
189
|
}
|
|
160
190
|
const company = me?.company || readable(conn.tenant || 'unknown');
|
|
@@ -165,10 +195,16 @@ async function showWelcome() {
|
|
|
165
195
|
// machine happens to hold. A client that can only echo its own stored address
|
|
166
196
|
// can never tell a person that the address is the thing that is wrong, which
|
|
167
197
|
// is exactly what happened when a machine sat pointed at a dead laptop port.
|
|
168
|
-
|
|
198
|
+
// Except on this laptop: there the server names its public address, which is
|
|
199
|
+
// not where this machine is talking, and "you are on production" is the
|
|
200
|
+
// single most confusing thing a developer testing locally can be told.
|
|
201
|
+
const local = (0, connection_1.isLocalAddress)(conn.url);
|
|
202
|
+
const address = local
|
|
203
|
+
? `${conn.url.replace(/^https?:\/\//, '')} ${ui.c.dim('(this laptop)')}`
|
|
204
|
+
: (me?.workspace_url || conn.url).replace(/^https?:\/\//, '');
|
|
169
205
|
out(row('User', who));
|
|
170
206
|
out(row('Company', company));
|
|
171
|
-
out(row('Server', ui.c.dim(address)));
|
|
207
|
+
out(row('Server', local ? address : ui.c.dim(address)));
|
|
172
208
|
out(row('Comparing', `${cfg.defaultModel || ui.c.amber('not set')} ${ui.c.dim('change with `comprism model`')}`));
|
|
173
209
|
out(row('Workspace', `${folder.path} ${ui.c.dim(folder.source === 'cwd' ? '(where you are)' : '(saved)')}`));
|
|
174
210
|
if (plan) {
|
package/out/lib/gateway.d.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface GatewayReply {
|
|
|
68
68
|
answer: string | null;
|
|
69
69
|
/** The receipt, already worded by the server. Clients never compose one. */
|
|
70
70
|
receipt: string | null;
|
|
71
|
+
/** The receipt card, worded by the server for every client. */
|
|
72
|
+
card?: import('../agent/render').ReceiptCard | null;
|
|
71
73
|
/** The model that actually answered. */
|
|
72
74
|
ran_on: string | null;
|
|
73
75
|
/** What this job cost to finish, on the model we ran. */
|
|
@@ -262,6 +264,8 @@ export interface SessionRecord {
|
|
|
262
264
|
conversation_kept?: boolean;
|
|
263
265
|
cost_usd: number;
|
|
264
266
|
jobs: number;
|
|
267
|
+
/** The session as a list entry, in the service's words: name, then where and when. */
|
|
268
|
+
card?: string[];
|
|
265
269
|
}
|
|
266
270
|
export interface SessionsReply extends Partial<SessionRecord> {
|
|
267
271
|
lines: string[];
|
package/out/lib/keyboard.d.ts
CHANGED
|
@@ -47,6 +47,9 @@ export declare class Keyboard {
|
|
|
47
47
|
/** Esc on its own (manual 6.7). Returns true when it was used, so the key
|
|
48
48
|
* goes no further; false passes it to the reader as before. */
|
|
49
49
|
onEscape: (() => boolean) | null;
|
|
50
|
+
/** The command list under the prompt (manual 6.4): Up, Down, Enter and Tab
|
|
51
|
+
* go to it while it is showing. Returns true when it used the key. */
|
|
52
|
+
onMenuKey: ((key: 'up' | 'down' | 'enter' | 'tab') => boolean) | null;
|
|
50
53
|
/** Ctrl+O, the short or full view (manual 6.9). */
|
|
51
54
|
onFullView: (() => void) | null;
|
|
52
55
|
/** Ctrl+G, the request in your own editor (manual 6.6). */
|
package/out/lib/keyboard.js
CHANGED
|
@@ -57,6 +57,9 @@ class Keyboard {
|
|
|
57
57
|
/** Esc on its own (manual 6.7). Returns true when it was used, so the key
|
|
58
58
|
* goes no further; false passes it to the reader as before. */
|
|
59
59
|
onEscape = null;
|
|
60
|
+
/** The command list under the prompt (manual 6.4): Up, Down, Enter and Tab
|
|
61
|
+
* go to it while it is showing. Returns true when it used the key. */
|
|
62
|
+
onMenuKey = null;
|
|
60
63
|
/** Ctrl+O, the short or full view (manual 6.9). */
|
|
61
64
|
onFullView = null;
|
|
62
65
|
/** Ctrl+G, the request in your own editor (manual 6.6). */
|
|
@@ -167,6 +170,10 @@ class Keyboard {
|
|
|
167
170
|
// arrow or another sequence and goes to the reader.
|
|
168
171
|
if (s === '\x1b' && this.onEscape?.())
|
|
169
172
|
return;
|
|
173
|
+
const menu = { '\x1b[A': 'up', '\x1bOA': 'up', '\x1b[B': 'down', '\x1bOB': 'down',
|
|
174
|
+
'\r': 'enter', '\t': 'tab' }[s];
|
|
175
|
+
if (menu && this.onMenuKey?.(menu))
|
|
176
|
+
return;
|
|
170
177
|
if (this.vi) {
|
|
171
178
|
if (s === '\x1b') {
|
|
172
179
|
this.normal = true;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface LastLogin {
|
|
2
|
+
email: string;
|
|
3
|
+
/** Exactly what was typed or found as the workspace, so a local one stays local. */
|
|
4
|
+
workspace: string;
|
|
5
|
+
/** Whether that workspace was typed by the person (only then may it be local). */
|
|
6
|
+
typed: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function readLast(): LastLogin | null;
|
|
9
|
+
export declare function saveLast(last: LastLogin): void;
|
|
10
|
+
export declare function forgetLast(): void;
|
|
11
|
+
export declare const keychainAvailable: () => boolean;
|
|
12
|
+
export declare function savedPassword(email: string, workspace: string): string | null;
|
|
13
|
+
export declare function savePassword(email: string, workspace: string, password: string): boolean;
|
|
14
|
+
export declare function forgetPassword(email: string, workspace: string): void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.keychainAvailable = void 0;
|
|
37
|
+
exports.readLast = readLast;
|
|
38
|
+
exports.saveLast = saveLast;
|
|
39
|
+
exports.forgetLast = forgetLast;
|
|
40
|
+
exports.savedPassword = savedPassword;
|
|
41
|
+
exports.savePassword = savePassword;
|
|
42
|
+
exports.forgetPassword = forgetPassword;
|
|
43
|
+
/**
|
|
44
|
+
* Who signed in last on this machine, so signing in again is one key.
|
|
45
|
+
*
|
|
46
|
+
* The email and the workspace address are kept in a small file. The password
|
|
47
|
+
* is never written to that file or anywhere else this tool controls: on a Mac,
|
|
48
|
+
* and only when the person says yes, it goes into their own login keychain,
|
|
49
|
+
* the same place Safari and git keep theirs, and it is read back from there.
|
|
50
|
+
* Signing out leaves both, so the next `login` is Enter; `login --forget`
|
|
51
|
+
* removes both.
|
|
52
|
+
*/
|
|
53
|
+
const node_child_process_1 = require("node:child_process");
|
|
54
|
+
const fs = __importStar(require("node:fs"));
|
|
55
|
+
const path = __importStar(require("node:path"));
|
|
56
|
+
const config_1 = require("./config");
|
|
57
|
+
function file() {
|
|
58
|
+
return path.join((0, config_1.ensureHome)(), 'last-login.json');
|
|
59
|
+
}
|
|
60
|
+
function readLast() {
|
|
61
|
+
try {
|
|
62
|
+
const got = JSON.parse(fs.readFileSync(file(), 'utf8'));
|
|
63
|
+
return got.email && got.workspace ? got : null;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function saveLast(last) {
|
|
70
|
+
try {
|
|
71
|
+
fs.writeFileSync(file(), JSON.stringify(last), { mode: 0o600 });
|
|
72
|
+
}
|
|
73
|
+
catch { /* next login asks */ }
|
|
74
|
+
}
|
|
75
|
+
function forgetLast() {
|
|
76
|
+
const last = readLast();
|
|
77
|
+
if (last)
|
|
78
|
+
forgetPassword(last.email, last.workspace);
|
|
79
|
+
try {
|
|
80
|
+
fs.rmSync(file(), { force: true });
|
|
81
|
+
}
|
|
82
|
+
catch { /* nothing to remove */ }
|
|
83
|
+
}
|
|
84
|
+
/** The keychain entry's name: one per workspace, so two workspaces never mix. */
|
|
85
|
+
const service = (workspace) => `CompletionPrism ${workspace}`;
|
|
86
|
+
const keychainAvailable = () => process.platform === 'darwin';
|
|
87
|
+
exports.keychainAvailable = keychainAvailable;
|
|
88
|
+
function savedPassword(email, workspace) {
|
|
89
|
+
if (!(0, exports.keychainAvailable)() || !email || !workspace)
|
|
90
|
+
return null;
|
|
91
|
+
const r = (0, node_child_process_1.spawnSync)('security', ['find-generic-password', '-a', email, '-s', service(workspace), '-w'], { encoding: 'utf8' });
|
|
92
|
+
return r.status === 0 ? r.stdout.replace(/\n$/, '') : null;
|
|
93
|
+
}
|
|
94
|
+
function savePassword(email, workspace, password) {
|
|
95
|
+
if (!(0, exports.keychainAvailable)())
|
|
96
|
+
return false;
|
|
97
|
+
const r = (0, node_child_process_1.spawnSync)('security', ['add-generic-password', '-U', '-a', email, '-s', service(workspace),
|
|
98
|
+
'-l', service(workspace), '-w', password], { stdio: 'ignore' });
|
|
99
|
+
return r.status === 0;
|
|
100
|
+
}
|
|
101
|
+
function forgetPassword(email, workspace) {
|
|
102
|
+
if (!(0, exports.keychainAvailable)())
|
|
103
|
+
return;
|
|
104
|
+
(0, node_child_process_1.spawnSync)('security', ['delete-generic-password', '-a', email, '-s', service(workspace)], { stdio: 'ignore' });
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function pick(entries: Array<string | string[]>, hint: string, start?: number, numbered?: boolean): Promise<number | null>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.pick = pick;
|
|
40
|
+
/**
|
|
41
|
+
* Choosing one thing from a list with the arrow keys.
|
|
42
|
+
*
|
|
43
|
+
* Each entry is one or more lines: the first is its name, the rest are quieter
|
|
44
|
+
* detail. Entries are separated by a blank line so each reads as its own item.
|
|
45
|
+
* Up and Down move, Enter picks, Esc (or Ctrl+C) leaves without picking.
|
|
46
|
+
*
|
|
47
|
+
* Only as many entries as fit the window are drawn, and the view follows the
|
|
48
|
+
* marker, so a long list scrolls instead of running off the screen (which would
|
|
49
|
+
* leave the redraw writing over lines it can no longer reach). Every line is
|
|
50
|
+
* cut to the window's width for the same reason: a wrapped line throws the
|
|
51
|
+
* redraw off by one. Off a terminal there is nothing to move through, so it
|
|
52
|
+
* returns null and the caller falls back to its typed form.
|
|
53
|
+
*
|
|
54
|
+
* With `numbered`, every entry carries its number, and typing a number moves
|
|
55
|
+
* the marker straight to it. A list of two hundred sessions is not something
|
|
56
|
+
* anybody should have to arrow through to reach the one they can already see.
|
|
57
|
+
*/
|
|
58
|
+
const node_readline_1 = __importDefault(require("node:readline"));
|
|
59
|
+
const ui = __importStar(require("./ui"));
|
|
60
|
+
const CSI = `${String.fromCharCode(27)}[`;
|
|
61
|
+
function pick(entries, hint, start = 0, numbered = false) {
|
|
62
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || entries.length === 0)
|
|
63
|
+
return Promise.resolve(null);
|
|
64
|
+
const items = entries.map((e) => (Array.isArray(e) ? e : [e]));
|
|
65
|
+
const width = Math.max(20, (process.stdout.columns || 100) - 6);
|
|
66
|
+
const cut = (s) => (s.length > width ? `${s.slice(0, width - 3)}...` : s);
|
|
67
|
+
const tall = Math.max(...items.map((i) => i.length)) + 1;
|
|
68
|
+
// Room for the entries, the hint and a line either side.
|
|
69
|
+
const fits = Math.max(1, Math.floor(((process.stdout.rows || 24) - 4) / tall));
|
|
70
|
+
let at = Math.min(Math.max(0, start), items.length - 1);
|
|
71
|
+
let top = 0;
|
|
72
|
+
let drawn = 0;
|
|
73
|
+
// The number typed so far, when the entries are numbered.
|
|
74
|
+
let typed = '';
|
|
75
|
+
const digits = String(items.length).length;
|
|
76
|
+
const label = (i, line) => (numbered ? `${String(i + 1).padStart(digits)}. ${line}` : line);
|
|
77
|
+
const draw = () => {
|
|
78
|
+
if (at < top)
|
|
79
|
+
top = at;
|
|
80
|
+
if (at >= top + fits)
|
|
81
|
+
top = at - fits + 1;
|
|
82
|
+
if (drawn)
|
|
83
|
+
process.stdout.write(`${CSI}${drawn}A\r`);
|
|
84
|
+
const lines = [];
|
|
85
|
+
if (top > 0)
|
|
86
|
+
lines.push(ui.c.dim(` ${top} more above`));
|
|
87
|
+
items.slice(top, top + fits).forEach((item, k) => {
|
|
88
|
+
const i = top + k;
|
|
89
|
+
const chosen = i === at;
|
|
90
|
+
item.forEach((line, n) => {
|
|
91
|
+
if (n === 0) {
|
|
92
|
+
const name = cut(label(i, line));
|
|
93
|
+
lines.push(chosen ? ` ${ui.c.bold(ui.c.blue(`▸ ${name}`))}` : ` ${ui.c.bold(name)}`);
|
|
94
|
+
}
|
|
95
|
+
else
|
|
96
|
+
lines.push(` ${ui.c.dim(cut(numbered ? `${' '.repeat(digits + 2)}${line}` : line))}`);
|
|
97
|
+
});
|
|
98
|
+
lines.push('');
|
|
99
|
+
});
|
|
100
|
+
const below = items.length - (top + fits);
|
|
101
|
+
if (below > 0)
|
|
102
|
+
lines.push(ui.c.dim(` ${below} more below`));
|
|
103
|
+
lines.push(` ${ui.c.dim(hint)}${typed ? ` ${ui.c.bold(`number ${typed}`)}` : ''}`);
|
|
104
|
+
// The same height every time, so the next redraw lands exactly on this one.
|
|
105
|
+
while (lines.length < drawn)
|
|
106
|
+
lines.push('');
|
|
107
|
+
process.stdout.write(`${lines.map((l) => `${CSI}2K${l}`).join('\n')}\n`);
|
|
108
|
+
drawn = lines.length;
|
|
109
|
+
};
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
node_readline_1.default.emitKeypressEvents(process.stdin);
|
|
112
|
+
const wasRaw = process.stdin.isRaw;
|
|
113
|
+
process.stdin.setRawMode(true);
|
|
114
|
+
process.stdin.resume();
|
|
115
|
+
const done = (choice) => {
|
|
116
|
+
process.stdin.off('keypress', onKey);
|
|
117
|
+
process.stdin.setRawMode(wasRaw);
|
|
118
|
+
process.stdin.pause();
|
|
119
|
+
resolve(choice);
|
|
120
|
+
};
|
|
121
|
+
const onKey = (s, key) => {
|
|
122
|
+
if (!key && !(numbered && /^[0-9]$/.test(s ?? '')))
|
|
123
|
+
return;
|
|
124
|
+
if (numbered && /^[0-9]$/.test(s ?? '')) {
|
|
125
|
+
// A number past the end starts again from this digit, so a mistyped
|
|
126
|
+
// number is corrected by typing the right one, not by clearing first.
|
|
127
|
+
const next = Number(typed + s) <= items.length ? typed + s : s;
|
|
128
|
+
const n = Number(next);
|
|
129
|
+
typed = n >= 1 && n <= items.length ? next : '';
|
|
130
|
+
if (typed)
|
|
131
|
+
at = n - 1;
|
|
132
|
+
draw();
|
|
133
|
+
}
|
|
134
|
+
else if (numbered && key.name === 'backspace' && typed) {
|
|
135
|
+
typed = typed.slice(0, -1);
|
|
136
|
+
if (typed)
|
|
137
|
+
at = Number(typed) - 1;
|
|
138
|
+
draw();
|
|
139
|
+
}
|
|
140
|
+
else if (key.name === 'up' || key.name === 'k') {
|
|
141
|
+
typed = '';
|
|
142
|
+
at = Math.max(0, at - 1);
|
|
143
|
+
draw();
|
|
144
|
+
}
|
|
145
|
+
else if (key.name === 'down' || key.name === 'j') {
|
|
146
|
+
typed = '';
|
|
147
|
+
at = Math.min(items.length - 1, at + 1);
|
|
148
|
+
draw();
|
|
149
|
+
}
|
|
150
|
+
else if (key.name === 'return')
|
|
151
|
+
done(at);
|
|
152
|
+
else if (key.name === 'escape' || key.name === 'q' || (key.ctrl && key.name === 'c'))
|
|
153
|
+
done(null);
|
|
154
|
+
};
|
|
155
|
+
process.stdin.on('keypress', onKey);
|
|
156
|
+
draw();
|
|
157
|
+
});
|
|
158
|
+
}
|
package/out/lib/prompt.d.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
export declare function closePrompt(): void;
|
|
3
3
|
/** Ask a question and wait for the answer. */
|
|
4
4
|
export declare function ask(prompt: string): Promise<string>;
|
|
5
|
+
/** Ask, telling a cancel (Ctrl+C, input closed) apart from an empty answer:
|
|
6
|
+
* null is "never mind", "" is Enter. */
|
|
7
|
+
export declare function askOrCancel(prompt: string): Promise<string | null>;
|
|
5
8
|
/**
|
|
6
9
|
* Ask for a secret, and do not put it on the screen.
|
|
7
10
|
*
|
package/out/lib/prompt.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.closePrompt = closePrompt;
|
|
7
7
|
exports.ask = ask;
|
|
8
|
+
exports.askOrCancel = askOrCancel;
|
|
8
9
|
exports.askHidden = askHidden;
|
|
9
10
|
exports.freeText = freeText;
|
|
10
11
|
exports.subject = subject;
|
|
@@ -24,6 +25,11 @@ function open() {
|
|
|
24
25
|
output: process.stdout,
|
|
25
26
|
terminal: process.stdin.isTTY === true,
|
|
26
27
|
});
|
|
28
|
+
// Ctrl+C closes it. The next question then opens a new one instead of
|
|
29
|
+
// asking a closed reader, which threw and ended the whole program.
|
|
30
|
+
const mine = reader;
|
|
31
|
+
mine.once('close', () => { if (reader === mine)
|
|
32
|
+
reader = null; });
|
|
27
33
|
}
|
|
28
34
|
return reader;
|
|
29
35
|
}
|
|
@@ -51,6 +57,19 @@ function ask(prompt) {
|
|
|
51
57
|
});
|
|
52
58
|
});
|
|
53
59
|
}
|
|
60
|
+
/** Ask, telling a cancel (Ctrl+C, input closed) apart from an empty answer:
|
|
61
|
+
* null is "never mind", "" is Enter. */
|
|
62
|
+
function askOrCancel(prompt) {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
const r = open();
|
|
65
|
+
const closed = () => resolve(null);
|
|
66
|
+
r.once('close', closed);
|
|
67
|
+
r.question(prompt, (answer) => {
|
|
68
|
+
r.off('close', closed);
|
|
69
|
+
resolve(answer.trim());
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
54
73
|
/**
|
|
55
74
|
* Ask for a secret, and do not put it on the screen.
|
|
56
75
|
*
|
|
@@ -86,6 +105,8 @@ function askHidden(prompt) {
|
|
|
86
105
|
process.stdout.write(`\r${prompt}${"\u2022".repeat(inner.line.length)}`);
|
|
87
106
|
};
|
|
88
107
|
return new Promise((resolve) => {
|
|
108
|
+
// Ctrl+C at the password is "never mind", answered as nothing, never a crash.
|
|
109
|
+
reader.once('close', () => { inner._writeToOutput = asItWas; resolve(''); });
|
|
89
110
|
reader.question(prompt, (answer) => {
|
|
90
111
|
// Put readline back the way it was, whatever happened, or every later
|
|
91
112
|
// question on this reader would come out as dots.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SessionOut } from './gateway';
|
|
2
|
+
import type { CommandList } from './commandlist';
|
|
3
|
+
export interface StartCache {
|
|
4
|
+
/** Which workspace these answers came from; another workspace's are never used. */
|
|
5
|
+
url?: string;
|
|
6
|
+
me?: SessionOut | null;
|
|
7
|
+
hasKey?: boolean;
|
|
8
|
+
/** The project line, by project folder. */
|
|
9
|
+
lines?: Record<string, string>;
|
|
10
|
+
list?: CommandList | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function readStart(url: string): StartCache;
|
|
13
|
+
export declare function saveStart(url: string, change: Partial<StartCache>): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readStart = readStart;
|
|
37
|
+
exports.saveStart = saveStart;
|
|
38
|
+
/**
|
|
39
|
+
* What the service said the last time a session opened, kept so the next one
|
|
40
|
+
* can open at once.
|
|
41
|
+
*
|
|
42
|
+
* Opening a session asks the service four things (who you are, whether a key
|
|
43
|
+
* is stored, the project line, the command list). Asked from a laptop whose
|
|
44
|
+
* database is a region away, each takes seconds, and the prompt waited for all
|
|
45
|
+
* of them. Now the last answers are drawn immediately and the same questions
|
|
46
|
+
* are asked again in the background; the fresh answers are kept for next time,
|
|
47
|
+
* and a change that matters (no key any more) is said as soon as it is known.
|
|
48
|
+
*
|
|
49
|
+
* Only the service's own answers are kept, exactly as sent. Nothing here is
|
|
50
|
+
* decided on this machine, and a missing or unreadable file simply means the
|
|
51
|
+
* session waits for the service, as it always did.
|
|
52
|
+
*/
|
|
53
|
+
const fs = __importStar(require("node:fs"));
|
|
54
|
+
const path = __importStar(require("node:path"));
|
|
55
|
+
const config_1 = require("./config");
|
|
56
|
+
function file() {
|
|
57
|
+
return path.join((0, config_1.ensureHome)(), 'start-cache.json');
|
|
58
|
+
}
|
|
59
|
+
function readStart(url) {
|
|
60
|
+
try {
|
|
61
|
+
const got = JSON.parse(fs.readFileSync(file(), 'utf8'));
|
|
62
|
+
return got.url === url ? got : { url };
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return { url };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function saveStart(url, change) {
|
|
69
|
+
try {
|
|
70
|
+
const now = readStart(url);
|
|
71
|
+
const next = {
|
|
72
|
+
...now, ...change, url,
|
|
73
|
+
lines: { ...(now.lines ?? {}), ...(change.lines ?? {}) },
|
|
74
|
+
};
|
|
75
|
+
fs.writeFileSync(file(), JSON.stringify(next), { mode: 0o600 });
|
|
76
|
+
}
|
|
77
|
+
catch { /* a cache that cannot be written only means the next start waits */ }
|
|
78
|
+
}
|
package/out/lib/ui.d.ts
CHANGED
|
@@ -35,6 +35,14 @@ export declare const c: {
|
|
|
35
35
|
* and the brand is the one thing a customer notices before the engine.
|
|
36
36
|
*/
|
|
37
37
|
export declare function spectrumAt(index: number, glyph: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* A small picture, drawn two pixels to a character with the half block glyph:
|
|
40
|
+
* the top pixel is the glyph's color and the bottom one its background, which
|
|
41
|
+
* makes each pixel square in an ordinary terminal font. Each row of `pixels`
|
|
42
|
+
* is six hex digits per pixel, `......` for a clear one, and two rows make one
|
|
43
|
+
* line. Returns nothing without color, because the picture is only color.
|
|
44
|
+
*/
|
|
45
|
+
export declare function pixelArt(pixels: string[]): string[];
|
|
38
46
|
export declare function prismRule(w?: number): string;
|
|
39
47
|
export declare function banner(subtitle?: string): string;
|
|
40
48
|
export declare function money(v: number | null): string;
|