@giovannijecha/jecode 0.1.8 → 0.2.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/README.md +41 -27
- package/dist/account-lock.js +112 -0
- package/dist/accounts.js +100 -0
- package/dist/cli-info.js +2 -2
- package/dist/commands.js +27 -102
- package/dist/controller.js +3 -0
- package/dist/credential-commands.js +40 -14
- package/dist/credential-safety.js +2 -1
- package/dist/external-browser.js +53 -0
- package/dist/oauth-http.js +114 -0
- package/dist/openai-account-command.js +124 -0
- package/dist/openai-account.js +91 -0
- package/dist/openai-oauth-callback.js +186 -0
- package/dist/openai-oauth-tokens.js +65 -0
- package/dist/openai-oauth.js +203 -0
- package/dist/permission-command.js +107 -0
- package/dist/permissions.js +113 -0
- package/dist/provider-commands.js +22 -62
- package/dist/provider-label.js +10 -0
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/index.js +2 -1
- package/dist/providers/ollama.js +1 -1
- package/dist/providers/openai-codex.js +114 -0
- package/dist/providers/openai-stream.js +13 -9
- package/dist/providers/openai-wire.js +4 -4
- package/dist/providers/openai.js +2 -2
- package/dist/providers/sse.js +1 -1
- package/dist/settings-command.js +13 -6
- package/dist/tools/shell.js +1 -1
- package/dist/transcript.js +0 -3
- package/dist/tui/app-workflows.js +17 -17
- package/dist/tui/app.js +6 -11
- package/dist/tui/approve.js +6 -34
- package/dist/tui/blocks.js +1 -3
- package/dist/tui/complete.js +4 -4
- package/dist/tui/components/menu.js +4 -3
- package/dist/tui/components/misc.js +0 -6
- package/dist/tui/components/status.js +6 -7
- package/dist/tui/feedback.js +11 -9
- package/dist/tui/help.js +29 -0
- package/dist/tui/modal.js +22 -11
- package/dist/tui/overlay.js +15 -4
- package/dist/tui/picker.js +1 -1
- package/dist/tui/session-view.js +12 -6
- package/dist/version.js +14 -0
- package/docs/assets/brand/jeco-256.png +0 -0
- package/package.json +5 -1
package/dist/tui/approve.js
CHANGED
|
@@ -4,28 +4,15 @@
|
|
|
4
4
|
// worth more than a line of prose and a key to guess at. It is a menu — the
|
|
5
5
|
// generic one — with an attention title and three answers written out.
|
|
6
6
|
// Nothing is approved by a key nobody meant to press.
|
|
7
|
+
import { scopeFor } from "../permissions.js";
|
|
8
|
+
export { scopeFor };
|
|
7
9
|
/**
|
|
8
|
-
* The
|
|
10
|
+
* The three answers, in the order a person actually wants them.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* "Always" is scoped to the tool and to this session: it is a way to stop being
|
|
13
|
+
* asked about `write_file` twenty times in a row, not a setting that outlives
|
|
14
|
+
* the window it was granted in.
|
|
13
15
|
*/
|
|
14
|
-
export function scopeFor(call) {
|
|
15
|
-
const path = typeof call.input.path === "string" ? call.input.path : undefined;
|
|
16
|
-
if ((call.name === "write_file" || call.name === "edit_file") && path !== undefined) {
|
|
17
|
-
return { key: `file\0${path}`, label: `changes to ${path}`, summary: `file changes · ${path}` };
|
|
18
|
-
}
|
|
19
|
-
const command = typeof call.input.command === "string" ? call.input.command : undefined;
|
|
20
|
-
if (call.name === "run_command" && command !== undefined) {
|
|
21
|
-
return { key: `command\0${command}`, label: "this exact command", summary: `command · ${command}` };
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
key: `${call.name}\0${stable(call.input)}`,
|
|
25
|
-
label: "this exact call",
|
|
26
|
-
summary: `${call.name} · ${target(call.input)}`,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
16
|
export function promptFor(call, target, pal) {
|
|
30
17
|
const scope = scopeFor(call);
|
|
31
18
|
const options = [
|
|
@@ -56,21 +43,6 @@ function scopeNoun(scope) {
|
|
|
56
43
|
return "this command";
|
|
57
44
|
return "this call";
|
|
58
45
|
}
|
|
59
|
-
function stable(value) {
|
|
60
|
-
if (Array.isArray(value))
|
|
61
|
-
return `[${value.map(stable).join(",")}]`;
|
|
62
|
-
if (value !== null && typeof value === "object") {
|
|
63
|
-
return `{${Object.entries(value)
|
|
64
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
65
|
-
.map(([key, item]) => `${JSON.stringify(key)}:${stable(item)}`)
|
|
66
|
-
.join(",")}}`;
|
|
67
|
-
}
|
|
68
|
-
return JSON.stringify(value);
|
|
69
|
-
}
|
|
70
|
-
function target(input) {
|
|
71
|
-
const value = input.path ?? input.command;
|
|
72
|
-
return typeof value === "string" ? value : stable(input);
|
|
73
|
-
}
|
|
74
46
|
const ANSWERS = ["once", "always", "no"];
|
|
75
47
|
/** What picking row `index` meant. Anything unrecognised refuses. */
|
|
76
48
|
export function answerAt(index) {
|
package/dist/tui/blocks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Semantic transcript blocks routed to small, owned production components.
|
|
2
2
|
import { renderAnswer, renderReasoning, renderUser } from "./components/messages.js";
|
|
3
|
-
import {
|
|
3
|
+
import { renderNotice } from "./components/misc.js";
|
|
4
4
|
import { renderTool } from "./components/tool.js";
|
|
5
5
|
export function render(block, width, pal, context = {}) {
|
|
6
6
|
switch (block.kind) {
|
|
@@ -19,8 +19,6 @@ export function render(block, width, pal, context = {}) {
|
|
|
19
19
|
});
|
|
20
20
|
case "notice":
|
|
21
21
|
return renderNotice(block, width, pal);
|
|
22
|
-
case "list":
|
|
23
|
-
return renderList(block, width, pal);
|
|
24
22
|
}
|
|
25
23
|
}
|
|
26
24
|
export function renderAll(blocks, width, pal, context = {}) {
|
package/dist/tui/complete.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Completing a slash command.
|
|
2
2
|
//
|
|
3
|
-
// A command the user cannot remember is a command that does not exist
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// A command the user cannot remember is a command that does not exist. `/help`
|
|
4
|
+
// owns keyboard controls; discovery belongs here, while the line is being
|
|
5
|
+
// typed. Selection is state, not an edit: arrows move through the list without
|
|
6
|
+
// rewriting what the user typed.
|
|
7
7
|
import { COMMANDS } from "../commands.js";
|
|
8
8
|
/**
|
|
9
9
|
* The commands a half-typed line still matches.
|
|
@@ -16,10 +16,11 @@ export function menuWindow(length, selected, visible) {
|
|
|
16
16
|
return { first, last: Math.min(count, first + room) };
|
|
17
17
|
}
|
|
18
18
|
function renderEntry(entry, labelWidth, width, pal) {
|
|
19
|
-
// Colour terminals use one quiet selection band
|
|
20
|
-
//
|
|
19
|
+
// Colour terminals use one quiet selection band and keep every label on the
|
|
20
|
+
// composer's content edge. Monochrome has no band, so it alone reserves a
|
|
21
|
+
// fixed arrow column to keep selection visible without shifting peer rows.
|
|
21
22
|
const monochrome = !hasColor();
|
|
22
|
-
const selectedMark = entry.selected
|
|
23
|
+
const selectedMark = monochrome ? (entry.selected ? "→ " : " ") : "";
|
|
23
24
|
const fg = entry.selected ? pal.ink.bright : pal.ink.fg;
|
|
24
25
|
const primary = [
|
|
25
26
|
{ text: selectedMark, fg: entry.selected ? pal.accent : fg },
|
|
@@ -14,9 +14,3 @@ export function renderNotice(block, width, pal) {
|
|
|
14
14
|
], [], undefined, 1)),
|
|
15
15
|
];
|
|
16
16
|
}
|
|
17
|
-
export function renderList(block, width, pal) {
|
|
18
|
-
return [
|
|
19
|
-
"",
|
|
20
|
-
...block.items.map((item) => row(width, [{ text: item.text, fg: item.dim ? pal.ink.muted : pal.ink.fg }], [], undefined, 1)),
|
|
21
|
-
];
|
|
22
|
-
}
|
|
@@ -16,15 +16,14 @@ export function renderStatus(info, pal) {
|
|
|
16
16
|
return info.readiness === undefined ? [] : feedbackSegments(info.readiness, pal);
|
|
17
17
|
}
|
|
18
18
|
function feedbackSegments(feedback, pal) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
19
|
+
if (feedback.tone === "info") {
|
|
20
|
+
return [{ text: feedback.text, fg: pal.ink.muted }];
|
|
21
|
+
}
|
|
22
|
+
const mark = feedback.tone === "error" ? "×" : "!";
|
|
23
|
+
const markColor = feedback.tone === "error" ? pal.ink.removed : pal.ink.attention;
|
|
25
24
|
const textColor = feedback.tone === "error" ? pal.ink.removed : pal.ink.muted;
|
|
26
25
|
return [
|
|
27
|
-
{ text: `${mark} `, fg: markColor
|
|
26
|
+
{ text: `${mark} `, fg: markColor, bold: true },
|
|
28
27
|
{ text: feedback.text, fg: textColor },
|
|
29
28
|
];
|
|
30
29
|
}
|
package/dist/tui/feedback.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Operational feedback belongs in the footer, not in the conversation.
|
|
2
|
-
|
|
2
|
+
import { providerLabel } from "../provider-label.js";
|
|
3
|
+
const INFO_MS = 2_200;
|
|
3
4
|
const WARN_MS = 4_200;
|
|
4
5
|
const ERROR_MS = 6_000;
|
|
5
6
|
/** Own replacement and expiry without leaking timers into the TUI shell. */
|
|
@@ -41,8 +42,6 @@ export function feedbackController(changed) {
|
|
|
41
42
|
}
|
|
42
43
|
/** Turn a command notice into one replaceable message in the footer status channel. */
|
|
43
44
|
export function commandFeedback(block) {
|
|
44
|
-
if (block.kind !== "notice")
|
|
45
|
-
return undefined;
|
|
46
45
|
return {
|
|
47
46
|
text: block.text,
|
|
48
47
|
tone: block.tone,
|
|
@@ -52,23 +51,26 @@ export function commandFeedback(block) {
|
|
|
52
51
|
/** Explain why a model turn cannot start, without exposing configuration internals. */
|
|
53
52
|
export function turnBlocker(session) {
|
|
54
53
|
const blocked = session.provider.blocked();
|
|
55
|
-
|
|
54
|
+
const auth = session.provider.auth;
|
|
55
|
+
const expected = auth.kind === "api-key"
|
|
56
|
+
? blocked?.startsWith(`${auth.keyVar} `) === true
|
|
57
|
+
: blocked !== undefined;
|
|
58
|
+
if (blocked !== undefined && !expected) {
|
|
56
59
|
return { text: blocked, tone: "error" };
|
|
57
60
|
}
|
|
58
61
|
if (blocked !== undefined) {
|
|
59
62
|
return {
|
|
60
|
-
text:
|
|
63
|
+
text: auth.kind === "oauth"
|
|
64
|
+
? `${providerLabel(session.provider.id)} needs ${auth.label} sign-in · /settings`
|
|
65
|
+
: `${providerLabel(session.provider.id)} needs an API key · /settings`,
|
|
61
66
|
tone: "warn",
|
|
62
67
|
};
|
|
63
68
|
}
|
|
64
69
|
if (session.model === "") {
|
|
65
70
|
return {
|
|
66
|
-
text: `${
|
|
71
|
+
text: `${providerLabel(session.provider.id)} needs a model · /models`,
|
|
67
72
|
tone: "warn",
|
|
68
73
|
};
|
|
69
74
|
}
|
|
70
75
|
return undefined;
|
|
71
76
|
}
|
|
72
|
-
function providerName(id) {
|
|
73
|
-
return id === "" ? "Provider" : `${id[0]?.toUpperCase() ?? ""}${id.slice(1)}`;
|
|
74
|
-
}
|
package/dist/tui/help.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The compact, non-persistent keyboard reference shown inside the dock.
|
|
2
|
+
import { row } from "../ui/render.js";
|
|
3
|
+
import { textWidth } from "../ui/width.js";
|
|
4
|
+
const CONTROL_WIDTH = 18;
|
|
5
|
+
const CONTROLS = [
|
|
6
|
+
{ key: "up / down", description: "move through menus or history" },
|
|
7
|
+
{ key: "enter / tab", description: "select or send · complete" },
|
|
8
|
+
{ key: "alt+enter", description: "insert a new line" },
|
|
9
|
+
{ key: "esc", description: "close UI or interrupt work" },
|
|
10
|
+
{ key: "ctrl+c", description: "interrupt work or exit" },
|
|
11
|
+
{ key: "ctrl+o", description: "toggle reasoning or tool details" },
|
|
12
|
+
{ key: "wheel / pgup/dn", description: "scroll the transcript" },
|
|
13
|
+
{ key: "ctrl+l", description: "redraw the screen" },
|
|
14
|
+
];
|
|
15
|
+
export function panel(width, pal, maxRows = CONTROLS.length + 1) {
|
|
16
|
+
const heading = row(width, [
|
|
17
|
+
{ text: "help ", fg: pal.accent, bold: true },
|
|
18
|
+
{ text: "keyboard controls", fg: pal.ink.fg },
|
|
19
|
+
], [{ text: "esc close", fg: pal.ink.muted }]);
|
|
20
|
+
const controls = CONTROLS.map((control) => {
|
|
21
|
+
const gap = Math.max(2, CONTROL_WIDTH - textWidth(control.key));
|
|
22
|
+
return row(width, [
|
|
23
|
+
{ text: control.key, fg: pal.ink.bright, bold: true },
|
|
24
|
+
{ text: " ".repeat(gap) },
|
|
25
|
+
{ text: control.description, fg: pal.ink.muted },
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
return [heading, ...controls].slice(0, Math.max(1, maxRows));
|
|
29
|
+
}
|
package/dist/tui/modal.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
// What can take the dock over, and how it draws.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// Three kinds, and the union exists so that everything between the command that
|
|
4
4
|
// opens one and the frame that draws it — the view, the shell, the key handler
|
|
5
|
-
// — speaks about "the thing that is open" rather than about
|
|
6
|
-
//
|
|
5
|
+
// — speaks about "the thing that is open" rather than about each interaction
|
|
6
|
+
// separately.
|
|
7
7
|
import * as picker from "./picker.js";
|
|
8
8
|
import * as field from "./field.js";
|
|
9
|
+
import * as help from "./help.js";
|
|
9
10
|
export function panel(modal, width, pal, maxRows) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
switch (modal.kind) {
|
|
12
|
+
case "pick":
|
|
13
|
+
return picker.panel(modal.picker, width, pal, maxRows);
|
|
14
|
+
case "type":
|
|
15
|
+
return maxRows === undefined
|
|
16
|
+
? field.panel(modal.field, width, pal)
|
|
17
|
+
: field.panel(modal.field, width, pal).slice(0, maxRows);
|
|
18
|
+
case "help":
|
|
19
|
+
return help.panel(width, pal, maxRows);
|
|
20
|
+
}
|
|
15
21
|
}
|
|
16
22
|
/**
|
|
17
23
|
* Where the caret goes while this is open, if it goes anywhere.
|
|
@@ -20,7 +26,12 @@ export function panel(modal, width, pal, maxRows) {
|
|
|
20
26
|
* query prompt and place the terminal caret at its real editing position.
|
|
21
27
|
*/
|
|
22
28
|
export function caret(modal, width, maxRows) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
switch (modal.kind) {
|
|
30
|
+
case "pick":
|
|
31
|
+
return picker.caret(modal.picker, width, maxRows);
|
|
32
|
+
case "type":
|
|
33
|
+
return field.caret(modal.field, width);
|
|
34
|
+
case "help":
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
26
37
|
}
|
package/dist/tui/overlay.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
// The modal interaction layer:
|
|
1
|
+
// The modal interaction layer: a picker, a single-line field, or read-only help.
|
|
2
2
|
import * as picker from "./picker.js";
|
|
3
3
|
import { oneLine } from "./field.js";
|
|
4
4
|
import { applyKey } from "./input.js";
|
|
5
5
|
export function shown(open) {
|
|
6
6
|
if (open === undefined)
|
|
7
7
|
return undefined;
|
|
8
|
-
|
|
8
|
+
if ("picker" in open)
|
|
9
|
+
return { kind: "pick", picker: open.picker };
|
|
10
|
+
if ("field" in open)
|
|
11
|
+
return { kind: "type", field: open.field };
|
|
12
|
+
return { kind: "help" };
|
|
9
13
|
}
|
|
10
14
|
export function cancel(open) {
|
|
11
15
|
if (open === undefined)
|
|
12
16
|
return undefined;
|
|
13
|
-
open
|
|
17
|
+
if ("help" in open)
|
|
18
|
+
open.settle();
|
|
19
|
+
else
|
|
20
|
+
open.settle(undefined);
|
|
14
21
|
return undefined;
|
|
15
22
|
}
|
|
16
23
|
export function handle(open, key) {
|
|
@@ -26,7 +33,11 @@ export function handle(open, key) {
|
|
|
26
33
|
cancel(open);
|
|
27
34
|
return {};
|
|
28
35
|
}
|
|
29
|
-
|
|
36
|
+
if ("picker" in open)
|
|
37
|
+
return handlePicker(open, key);
|
|
38
|
+
if ("field" in open)
|
|
39
|
+
return handleField(open, key);
|
|
40
|
+
return { open };
|
|
30
41
|
}
|
|
31
42
|
function handlePicker(open, key) {
|
|
32
43
|
switch (key.name) {
|
package/dist/tui/picker.js
CHANGED
|
@@ -64,7 +64,7 @@ function layout(picker, width, pal, maxRows) {
|
|
|
64
64
|
const options = colors === undefined
|
|
65
65
|
? []
|
|
66
66
|
: shown.length === 0
|
|
67
|
-
? [row(width, [{ text: "
|
|
67
|
+
? [row(width, [{ text: "no matches", fg: colors.ink.muted }])]
|
|
68
68
|
: renderMenuRows(shown.map(({ option, index }) => ({
|
|
69
69
|
label: option.label,
|
|
70
70
|
hint: option.hint,
|
package/dist/tui/session-view.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Small projections of the live session used by the shell and footer.
|
|
2
2
|
import { credentialSource } from "../credentials.js";
|
|
3
3
|
import { providerFailure } from "../provider-errors.js";
|
|
4
|
-
export function controllerOptions(session) {
|
|
4
|
+
export function controllerOptions(session, tools = session.tools) {
|
|
5
5
|
return {
|
|
6
6
|
provider: session.provider,
|
|
7
|
-
tools
|
|
7
|
+
tools,
|
|
8
8
|
model: session.model,
|
|
9
9
|
system: session.system,
|
|
10
10
|
maxTokens: session.config.maxTokens,
|
|
@@ -26,10 +26,16 @@ export function turnFailure(session, error, aborted) {
|
|
|
26
26
|
return { kind: "notice", text: "[interrupted]", tone: "warn" };
|
|
27
27
|
let text = providerFailure(session.provider, error);
|
|
28
28
|
if (/\b401\b/.test(text)) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const auth = session.provider.auth;
|
|
30
|
+
if (auth.kind === "oauth") {
|
|
31
|
+
text += ` · reconnect ${auth.label} in /settings`;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const source = credentialSource(auth.keyVar);
|
|
35
|
+
text += source === "environment"
|
|
36
|
+
? ` · update ${auth.keyVar} in the environment and restart`
|
|
37
|
+
: " · check credentials with /settings";
|
|
38
|
+
}
|
|
33
39
|
}
|
|
34
40
|
return { kind: "notice", text, tone: "error" };
|
|
35
41
|
}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The package version is runtime identity, not configuration.
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
let cached;
|
|
5
|
+
export function applicationVersion() {
|
|
6
|
+
if (cached !== undefined)
|
|
7
|
+
return cached;
|
|
8
|
+
const manifest = JSON.parse(readFileSync(path.resolve(import.meta.dirname, "..", "package.json"), "utf8"));
|
|
9
|
+
if (typeof manifest.version !== "string" || manifest.version === "") {
|
|
10
|
+
throw new Error("package version is missing");
|
|
11
|
+
}
|
|
12
|
+
cached = manifest.version;
|
|
13
|
+
return cached;
|
|
14
|
+
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giovannijecha/jecode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "An owned coding agent with zero external runtime dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -16,12 +16,16 @@
|
|
|
16
16
|
"terminal",
|
|
17
17
|
"tui",
|
|
18
18
|
"ollama",
|
|
19
|
+
"openai",
|
|
20
|
+
"chatgpt",
|
|
21
|
+
"oauth",
|
|
19
22
|
"typescript"
|
|
20
23
|
],
|
|
21
24
|
"type": "module",
|
|
22
25
|
"files": [
|
|
23
26
|
"bin/",
|
|
24
27
|
"dist/",
|
|
28
|
+
"docs/assets/brand/jeco-256.png",
|
|
25
29
|
"LICENSE",
|
|
26
30
|
"README.md"
|
|
27
31
|
],
|