ultrahope 0.1.11 → 0.1.12
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 +5 -1
- package/dist/git-ultrahope.js +112 -7
- package/dist/index.js +205 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install -g ultrahope
|
|
|
12
12
|
|
|
13
13
|
### Login
|
|
14
14
|
|
|
15
|
-
You can try Ultrahope without logging in first. The CLI automatically creates an anonymous session and allows up to 5 requests per day with the
|
|
15
|
+
You can try Ultrahope without logging in first. The CLI automatically creates an anonymous session and allows up to 5 requests per day with the Free plan limits.
|
|
16
16
|
|
|
17
17
|
When you want to keep going, authenticate with your Ultrahope account using device flow:
|
|
18
18
|
|
|
@@ -22,6 +22,10 @@ ultrahope login
|
|
|
22
22
|
|
|
23
23
|
This will display a URL and code. Open the URL in your browser, sign in, and enter the code to authorize the CLI. On successful login, the CLI replaces the anonymous session with your authenticated one while keeping the local installation identity.
|
|
24
24
|
|
|
25
|
+
Escalation (`Shift+E`) uses the Pro model set (`anthropic/claude-sonnet-4.6`,
|
|
26
|
+
`openai/gpt-5.3-codex`). If your account is not Pro, escalation is not shown and
|
|
27
|
+
requesting Pro-only models is rejected by the API.
|
|
28
|
+
|
|
25
29
|
### Translate
|
|
26
30
|
|
|
27
31
|
Translate input to various formats. Pipe content to the command:
|
package/dist/git-ultrahope.js
CHANGED
|
@@ -55,7 +55,7 @@ var InsufficientBalanceError = class extends Error {
|
|
|
55
55
|
}
|
|
56
56
|
} else {
|
|
57
57
|
lines.push(
|
|
58
|
-
"Error:
|
|
58
|
+
"Error: Free plan usage is limited. Upgrade to Pro for unlimited requests with $1 included credit."
|
|
59
59
|
);
|
|
60
60
|
if (this.actions?.upgrade) {
|
|
61
61
|
lines.push(` Upgrade: ${this.actions.upgrade}`);
|
|
@@ -404,6 +404,23 @@ function createApiClient(token) {
|
|
|
404
404
|
log("command_execution response", data);
|
|
405
405
|
return data;
|
|
406
406
|
},
|
|
407
|
+
async getEntitlement() {
|
|
408
|
+
log("entitlement request");
|
|
409
|
+
const res = await fetch(`${API_BASE_URL}/api/v1/entitlement`, {
|
|
410
|
+
method: "GET",
|
|
411
|
+
headers: jsonHeaders()
|
|
412
|
+
});
|
|
413
|
+
if (res.status === 401) {
|
|
414
|
+
log("entitlement error (401)");
|
|
415
|
+
throw new UnauthorizedError();
|
|
416
|
+
}
|
|
417
|
+
if (!res.ok) {
|
|
418
|
+
const text = await getErrorText(res, null);
|
|
419
|
+
log("entitlement error", { status: res.status, text });
|
|
420
|
+
throw new Error(`API error: ${res.status} ${text}`);
|
|
421
|
+
}
|
|
422
|
+
return res.json();
|
|
423
|
+
},
|
|
407
424
|
async generateCommitMessage(req, options) {
|
|
408
425
|
log("generateCommitMessage request", req);
|
|
409
426
|
const { data, error, response } = await client.POST(
|
|
@@ -1490,6 +1507,77 @@ function formatDiffStats(stats) {
|
|
|
1490
1507
|
return parts.join(", ");
|
|
1491
1508
|
}
|
|
1492
1509
|
|
|
1510
|
+
// lib/entitlement-cache.ts
|
|
1511
|
+
import fs3 from "fs/promises";
|
|
1512
|
+
import * as os3 from "os";
|
|
1513
|
+
import path3 from "path";
|
|
1514
|
+
var ENTITLEMENT_CACHE_TTL_MS = 1e3 * 60 * 15;
|
|
1515
|
+
function getEntitlementCachePath() {
|
|
1516
|
+
const configDir = process.env.XDG_CONFIG_HOME ?? path3.join(os3.homedir(), ".config");
|
|
1517
|
+
const filename = "entitlement-cache.json";
|
|
1518
|
+
return path3.join(configDir, "ultrahope", filename);
|
|
1519
|
+
}
|
|
1520
|
+
async function readEntitlementCache() {
|
|
1521
|
+
const cachePath = getEntitlementCachePath();
|
|
1522
|
+
try {
|
|
1523
|
+
const raw = await fs3.readFile(cachePath, "utf-8");
|
|
1524
|
+
const parsed = JSON.parse(raw);
|
|
1525
|
+
if (parsed && typeof parsed === "object" && "entitlement" in parsed && "fetchedAt" in parsed && typeof parsed.entitlement === "string" && typeof parsed.fetchedAt === "string") {
|
|
1526
|
+
if (parsed.entitlement === "anonymous" || parsed.entitlement === "authenticated_unpaid" || parsed.entitlement === "pro") {
|
|
1527
|
+
return {
|
|
1528
|
+
entitlement: parsed.entitlement,
|
|
1529
|
+
fetchedAt: parsed.fetchedAt
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return null;
|
|
1534
|
+
} catch {
|
|
1535
|
+
return null;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
function normalizeCachedAt(fetchedAt) {
|
|
1539
|
+
const value = Date.parse(fetchedAt);
|
|
1540
|
+
return Number.isFinite(value) ? value : NaN;
|
|
1541
|
+
}
|
|
1542
|
+
function isEntitlementCacheFresh(record) {
|
|
1543
|
+
const fetchedAt = normalizeCachedAt(record.fetchedAt);
|
|
1544
|
+
if (!Number.isFinite(fetchedAt)) return false;
|
|
1545
|
+
return Date.now() - fetchedAt <= ENTITLEMENT_CACHE_TTL_MS;
|
|
1546
|
+
}
|
|
1547
|
+
async function writeEntitlementCache(entitlement) {
|
|
1548
|
+
const cachePath = getEntitlementCachePath();
|
|
1549
|
+
const dir = path3.dirname(cachePath);
|
|
1550
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
1551
|
+
const payload = {
|
|
1552
|
+
entitlement,
|
|
1553
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1554
|
+
};
|
|
1555
|
+
await fs3.writeFile(cachePath, JSON.stringify(payload), { mode: 384 });
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// lib/entitlement-capability.ts
|
|
1559
|
+
async function resolveEntitlementCapability(api, authKind) {
|
|
1560
|
+
if (authKind !== "authenticated") {
|
|
1561
|
+
return { escalate: false };
|
|
1562
|
+
}
|
|
1563
|
+
const cache = await readEntitlementCache();
|
|
1564
|
+
if (cache && isEntitlementCacheFresh(cache)) {
|
|
1565
|
+
return {
|
|
1566
|
+
escalate: cache.entitlement === "pro"
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
const capability = { escalate: true };
|
|
1570
|
+
void (async () => {
|
|
1571
|
+
try {
|
|
1572
|
+
const response = await api.getEntitlement();
|
|
1573
|
+
capability.escalate = response.entitlement === "pro";
|
|
1574
|
+
await writeEntitlementCache(response.entitlement);
|
|
1575
|
+
} catch {
|
|
1576
|
+
}
|
|
1577
|
+
})();
|
|
1578
|
+
return capability;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1493
1581
|
// lib/renderer.ts
|
|
1494
1582
|
import * as readline2 from "readline";
|
|
1495
1583
|
|
|
@@ -3067,6 +3155,12 @@ var selectorRenderCapabilities = {
|
|
|
3067
3155
|
escalate: true,
|
|
3068
3156
|
clickConfirm: false
|
|
3069
3157
|
};
|
|
3158
|
+
function resolveSelectorCapabilities(options) {
|
|
3159
|
+
return {
|
|
3160
|
+
...selectorRenderCapabilities,
|
|
3161
|
+
escalate: options?.escalate ?? selectorRenderCapabilities.escalate
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3070
3164
|
function renderError(error, slotsLength, output) {
|
|
3071
3165
|
const readyCount = slotsLength;
|
|
3072
3166
|
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
@@ -3215,9 +3309,13 @@ async function selectCandidate(options) {
|
|
|
3215
3309
|
let generationRun = 0;
|
|
3216
3310
|
let generationController = null;
|
|
3217
3311
|
let isPromptOpen = false;
|
|
3312
|
+
let dynamicCapabilities = resolveSelectorCapabilities(options.capabilities);
|
|
3218
3313
|
const ttyReader = ttyInput;
|
|
3219
3314
|
const ttyWriter = ttyOutput;
|
|
3220
3315
|
const renderer = createRenderer(ttyWriter);
|
|
3316
|
+
const updateDynamicCapabilities = () => {
|
|
3317
|
+
dynamicCapabilities = resolveSelectorCapabilities(options.capabilities);
|
|
3318
|
+
};
|
|
3221
3319
|
const setRawModeSafe2 = (enabled) => {
|
|
3222
3320
|
try {
|
|
3223
3321
|
const r = ttyReader;
|
|
@@ -3229,6 +3327,7 @@ async function selectCandidate(options) {
|
|
|
3229
3327
|
setRawModeSafe2(true);
|
|
3230
3328
|
ttyReader.resume();
|
|
3231
3329
|
const render = () => {
|
|
3330
|
+
updateDynamicCapabilities();
|
|
3232
3331
|
const allowPromptRender = context.mode === "prompt" && context.promptKind === "edit";
|
|
3233
3332
|
if (!cleanedUp && (!isPromptOpen || allowPromptRender)) {
|
|
3234
3333
|
const frame = selectorRenderFrame({
|
|
@@ -3241,7 +3340,7 @@ async function selectCandidate(options) {
|
|
|
3241
3340
|
nowMs: Date.now(),
|
|
3242
3341
|
spinnerFrames: SPINNER_FRAMES,
|
|
3243
3342
|
copy: renderCopy,
|
|
3244
|
-
capabilities:
|
|
3343
|
+
capabilities: dynamicCapabilities
|
|
3245
3344
|
});
|
|
3246
3345
|
renderer.render(renderSelectorTextFromRenderFrame(frame));
|
|
3247
3346
|
}
|
|
@@ -3257,7 +3356,7 @@ async function selectCandidate(options) {
|
|
|
3257
3356
|
nowMs: Date.now(),
|
|
3258
3357
|
spinnerFrames: SPINNER_FRAMES,
|
|
3259
3358
|
copy: renderCopy,
|
|
3260
|
-
capabilities:
|
|
3359
|
+
capabilities: dynamicCapabilities
|
|
3261
3360
|
});
|
|
3262
3361
|
const selected = result.selectedCandidate?.content ?? result.selected ?? "";
|
|
3263
3362
|
const selectedTitle = normalizeCandidateContentForDisplay(selected) || selected;
|
|
@@ -3382,7 +3481,7 @@ async function selectCandidate(options) {
|
|
|
3382
3481
|
nowMs: Date.now(),
|
|
3383
3482
|
spinnerFrames: SPINNER_FRAMES,
|
|
3384
3483
|
copy: renderCopy,
|
|
3385
|
-
capabilities:
|
|
3484
|
+
capabilities: dynamicCapabilities
|
|
3386
3485
|
});
|
|
3387
3486
|
const costSuffix = frame.viewModel.header.totalCostLabel ? ` (total: ${frame.viewModel.header.totalCostLabel})` : "";
|
|
3388
3487
|
const generatedLine = `${frame.viewModel.header.generatedLabel}${costSuffix}`;
|
|
@@ -3503,7 +3602,7 @@ async function selectCandidate(options) {
|
|
|
3503
3602
|
nowMs: Date.now(),
|
|
3504
3603
|
spinnerFrames: SPINNER_FRAMES,
|
|
3505
3604
|
copy: renderCopy,
|
|
3506
|
-
capabilities:
|
|
3605
|
+
capabilities: dynamicCapabilities,
|
|
3507
3606
|
bufferText: buffer.getText()
|
|
3508
3607
|
});
|
|
3509
3608
|
const prompt = frame.prompt;
|
|
@@ -3579,7 +3678,7 @@ async function selectCandidate(options) {
|
|
|
3579
3678
|
nowMs: Date.now(),
|
|
3580
3679
|
spinnerFrames: SPINNER_FRAMES,
|
|
3581
3680
|
copy: renderCopy,
|
|
3582
|
-
capabilities:
|
|
3681
|
+
capabilities: dynamicCapabilities,
|
|
3583
3682
|
bufferText: buffer.getText()
|
|
3584
3683
|
});
|
|
3585
3684
|
const prompt = frame.prompt;
|
|
@@ -3663,6 +3762,8 @@ async function selectCandidate(options) {
|
|
|
3663
3762
|
}
|
|
3664
3763
|
if (key.name === "e" && key.shift) {
|
|
3665
3764
|
if (!hasReadySlot(context.slots)) return;
|
|
3765
|
+
updateDynamicCapabilities();
|
|
3766
|
+
if (!dynamicCapabilities.escalate) return;
|
|
3666
3767
|
applyResult(transitionSelectorFlow(context, { type: "ESCALATE" }));
|
|
3667
3768
|
return;
|
|
3668
3769
|
}
|
|
@@ -3993,9 +4094,12 @@ async function commit(args2) {
|
|
|
3993
4094
|
process.exit(1);
|
|
3994
4095
|
}
|
|
3995
4096
|
try {
|
|
4097
|
+
const existingCredentials = await getCredentials();
|
|
4098
|
+
const authKind = existingCredentials?.authKind ?? "anonymous";
|
|
3996
4099
|
const token = await getToken();
|
|
3997
4100
|
const installationId = await getInstallationId();
|
|
3998
4101
|
const api = createApiClient(token);
|
|
4102
|
+
const capabilities = await resolveEntitlementCapability(api, authKind);
|
|
3999
4103
|
const apiClient = api;
|
|
4000
4104
|
let guideHint;
|
|
4001
4105
|
let refineMessage;
|
|
@@ -4073,7 +4177,8 @@ async function commit(args2) {
|
|
|
4073
4177
|
models,
|
|
4074
4178
|
inlineEditPrompt: true,
|
|
4075
4179
|
initialGuideHint: guideHint,
|
|
4076
|
-
isEscalation
|
|
4180
|
+
isEscalation,
|
|
4181
|
+
capabilities
|
|
4077
4182
|
});
|
|
4078
4183
|
if (result.action === "abort") {
|
|
4079
4184
|
if (result.error instanceof InvalidModelError) {
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ var InsufficientBalanceError = class extends Error {
|
|
|
55
55
|
}
|
|
56
56
|
} else {
|
|
57
57
|
lines.push(
|
|
58
|
-
"Error:
|
|
58
|
+
"Error: Free plan usage is limited. Upgrade to Pro for unlimited requests with $1 included credit."
|
|
59
59
|
);
|
|
60
60
|
if (this.actions?.upgrade) {
|
|
61
61
|
lines.push(` Upgrade: ${this.actions.upgrade}`);
|
|
@@ -404,6 +404,23 @@ function createApiClient(token) {
|
|
|
404
404
|
log("command_execution response", data);
|
|
405
405
|
return data;
|
|
406
406
|
},
|
|
407
|
+
async getEntitlement() {
|
|
408
|
+
log("entitlement request");
|
|
409
|
+
const res = await fetch(`${API_BASE_URL}/api/v1/entitlement`, {
|
|
410
|
+
method: "GET",
|
|
411
|
+
headers: jsonHeaders()
|
|
412
|
+
});
|
|
413
|
+
if (res.status === 401) {
|
|
414
|
+
log("entitlement error (401)");
|
|
415
|
+
throw new UnauthorizedError();
|
|
416
|
+
}
|
|
417
|
+
if (!res.ok) {
|
|
418
|
+
const text = await getErrorText(res, null);
|
|
419
|
+
log("entitlement error", { status: res.status, text });
|
|
420
|
+
throw new Error(`API error: ${res.status} ${text}`);
|
|
421
|
+
}
|
|
422
|
+
return res.json();
|
|
423
|
+
},
|
|
407
424
|
async generateCommitMessage(req, options) {
|
|
408
425
|
log("generateCommitMessage request", req);
|
|
409
426
|
const { data, error, response } = await client.POST(
|
|
@@ -1508,6 +1525,77 @@ function formatDiffStats(stats) {
|
|
|
1508
1525
|
return parts.join(", ");
|
|
1509
1526
|
}
|
|
1510
1527
|
|
|
1528
|
+
// lib/entitlement-cache.ts
|
|
1529
|
+
import fs3 from "fs/promises";
|
|
1530
|
+
import * as os3 from "os";
|
|
1531
|
+
import path3 from "path";
|
|
1532
|
+
var ENTITLEMENT_CACHE_TTL_MS = 1e3 * 60 * 15;
|
|
1533
|
+
function getEntitlementCachePath() {
|
|
1534
|
+
const configDir = process.env.XDG_CONFIG_HOME ?? path3.join(os3.homedir(), ".config");
|
|
1535
|
+
const filename = "entitlement-cache.json";
|
|
1536
|
+
return path3.join(configDir, "ultrahope", filename);
|
|
1537
|
+
}
|
|
1538
|
+
async function readEntitlementCache() {
|
|
1539
|
+
const cachePath = getEntitlementCachePath();
|
|
1540
|
+
try {
|
|
1541
|
+
const raw = await fs3.readFile(cachePath, "utf-8");
|
|
1542
|
+
const parsed = JSON.parse(raw);
|
|
1543
|
+
if (parsed && typeof parsed === "object" && "entitlement" in parsed && "fetchedAt" in parsed && typeof parsed.entitlement === "string" && typeof parsed.fetchedAt === "string") {
|
|
1544
|
+
if (parsed.entitlement === "anonymous" || parsed.entitlement === "authenticated_unpaid" || parsed.entitlement === "pro") {
|
|
1545
|
+
return {
|
|
1546
|
+
entitlement: parsed.entitlement,
|
|
1547
|
+
fetchedAt: parsed.fetchedAt
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return null;
|
|
1552
|
+
} catch {
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
function normalizeCachedAt(fetchedAt) {
|
|
1557
|
+
const value = Date.parse(fetchedAt);
|
|
1558
|
+
return Number.isFinite(value) ? value : NaN;
|
|
1559
|
+
}
|
|
1560
|
+
function isEntitlementCacheFresh(record) {
|
|
1561
|
+
const fetchedAt = normalizeCachedAt(record.fetchedAt);
|
|
1562
|
+
if (!Number.isFinite(fetchedAt)) return false;
|
|
1563
|
+
return Date.now() - fetchedAt <= ENTITLEMENT_CACHE_TTL_MS;
|
|
1564
|
+
}
|
|
1565
|
+
async function writeEntitlementCache(entitlement) {
|
|
1566
|
+
const cachePath = getEntitlementCachePath();
|
|
1567
|
+
const dir = path3.dirname(cachePath);
|
|
1568
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
1569
|
+
const payload = {
|
|
1570
|
+
entitlement,
|
|
1571
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1572
|
+
};
|
|
1573
|
+
await fs3.writeFile(cachePath, JSON.stringify(payload), { mode: 384 });
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// lib/entitlement-capability.ts
|
|
1577
|
+
async function resolveEntitlementCapability(api, authKind) {
|
|
1578
|
+
if (authKind !== "authenticated") {
|
|
1579
|
+
return { escalate: false };
|
|
1580
|
+
}
|
|
1581
|
+
const cache = await readEntitlementCache();
|
|
1582
|
+
if (cache && isEntitlementCacheFresh(cache)) {
|
|
1583
|
+
return {
|
|
1584
|
+
escalate: cache.entitlement === "pro"
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
const capability = { escalate: true };
|
|
1588
|
+
void (async () => {
|
|
1589
|
+
try {
|
|
1590
|
+
const response = await api.getEntitlement();
|
|
1591
|
+
capability.escalate = response.entitlement === "pro";
|
|
1592
|
+
await writeEntitlementCache(response.entitlement);
|
|
1593
|
+
} catch {
|
|
1594
|
+
}
|
|
1595
|
+
})();
|
|
1596
|
+
return capability;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1511
1599
|
// lib/renderer.ts
|
|
1512
1600
|
import * as readline2 from "readline";
|
|
1513
1601
|
|
|
@@ -3085,6 +3173,12 @@ var selectorRenderCapabilities = {
|
|
|
3085
3173
|
escalate: true,
|
|
3086
3174
|
clickConfirm: false
|
|
3087
3175
|
};
|
|
3176
|
+
function resolveSelectorCapabilities(options) {
|
|
3177
|
+
return {
|
|
3178
|
+
...selectorRenderCapabilities,
|
|
3179
|
+
escalate: options?.escalate ?? selectorRenderCapabilities.escalate
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3088
3182
|
function renderError(error, slotsLength, output) {
|
|
3089
3183
|
const readyCount = slotsLength;
|
|
3090
3184
|
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
@@ -3233,9 +3327,13 @@ async function selectCandidate(options) {
|
|
|
3233
3327
|
let generationRun = 0;
|
|
3234
3328
|
let generationController = null;
|
|
3235
3329
|
let isPromptOpen = false;
|
|
3330
|
+
let dynamicCapabilities = resolveSelectorCapabilities(options.capabilities);
|
|
3236
3331
|
const ttyReader = ttyInput;
|
|
3237
3332
|
const ttyWriter = ttyOutput;
|
|
3238
3333
|
const renderer = createRenderer(ttyWriter);
|
|
3334
|
+
const updateDynamicCapabilities = () => {
|
|
3335
|
+
dynamicCapabilities = resolveSelectorCapabilities(options.capabilities);
|
|
3336
|
+
};
|
|
3239
3337
|
const setRawModeSafe2 = (enabled) => {
|
|
3240
3338
|
try {
|
|
3241
3339
|
const r = ttyReader;
|
|
@@ -3247,6 +3345,7 @@ async function selectCandidate(options) {
|
|
|
3247
3345
|
setRawModeSafe2(true);
|
|
3248
3346
|
ttyReader.resume();
|
|
3249
3347
|
const render = () => {
|
|
3348
|
+
updateDynamicCapabilities();
|
|
3250
3349
|
const allowPromptRender = context.mode === "prompt" && context.promptKind === "edit";
|
|
3251
3350
|
if (!cleanedUp && (!isPromptOpen || allowPromptRender)) {
|
|
3252
3351
|
const frame = selectorRenderFrame({
|
|
@@ -3259,7 +3358,7 @@ async function selectCandidate(options) {
|
|
|
3259
3358
|
nowMs: Date.now(),
|
|
3260
3359
|
spinnerFrames: SPINNER_FRAMES,
|
|
3261
3360
|
copy: renderCopy,
|
|
3262
|
-
capabilities:
|
|
3361
|
+
capabilities: dynamicCapabilities
|
|
3263
3362
|
});
|
|
3264
3363
|
renderer.render(renderSelectorTextFromRenderFrame(frame));
|
|
3265
3364
|
}
|
|
@@ -3275,7 +3374,7 @@ async function selectCandidate(options) {
|
|
|
3275
3374
|
nowMs: Date.now(),
|
|
3276
3375
|
spinnerFrames: SPINNER_FRAMES,
|
|
3277
3376
|
copy: renderCopy,
|
|
3278
|
-
capabilities:
|
|
3377
|
+
capabilities: dynamicCapabilities
|
|
3279
3378
|
});
|
|
3280
3379
|
const selected = result.selectedCandidate?.content ?? result.selected ?? "";
|
|
3281
3380
|
const selectedTitle = normalizeCandidateContentForDisplay(selected) || selected;
|
|
@@ -3400,7 +3499,7 @@ async function selectCandidate(options) {
|
|
|
3400
3499
|
nowMs: Date.now(),
|
|
3401
3500
|
spinnerFrames: SPINNER_FRAMES,
|
|
3402
3501
|
copy: renderCopy,
|
|
3403
|
-
capabilities:
|
|
3502
|
+
capabilities: dynamicCapabilities
|
|
3404
3503
|
});
|
|
3405
3504
|
const costSuffix = frame.viewModel.header.totalCostLabel ? ` (total: ${frame.viewModel.header.totalCostLabel})` : "";
|
|
3406
3505
|
const generatedLine = `${frame.viewModel.header.generatedLabel}${costSuffix}`;
|
|
@@ -3521,7 +3620,7 @@ async function selectCandidate(options) {
|
|
|
3521
3620
|
nowMs: Date.now(),
|
|
3522
3621
|
spinnerFrames: SPINNER_FRAMES,
|
|
3523
3622
|
copy: renderCopy,
|
|
3524
|
-
capabilities:
|
|
3623
|
+
capabilities: dynamicCapabilities,
|
|
3525
3624
|
bufferText: buffer.getText()
|
|
3526
3625
|
});
|
|
3527
3626
|
const prompt = frame.prompt;
|
|
@@ -3597,7 +3696,7 @@ async function selectCandidate(options) {
|
|
|
3597
3696
|
nowMs: Date.now(),
|
|
3598
3697
|
spinnerFrames: SPINNER_FRAMES,
|
|
3599
3698
|
copy: renderCopy,
|
|
3600
|
-
capabilities:
|
|
3699
|
+
capabilities: dynamicCapabilities,
|
|
3601
3700
|
bufferText: buffer.getText()
|
|
3602
3701
|
});
|
|
3603
3702
|
const prompt = frame.prompt;
|
|
@@ -3681,6 +3780,8 @@ async function selectCandidate(options) {
|
|
|
3681
3780
|
}
|
|
3682
3781
|
if (key.name === "e" && key.shift) {
|
|
3683
3782
|
if (!hasReadySlot(context.slots)) return;
|
|
3783
|
+
updateDynamicCapabilities();
|
|
3784
|
+
if (!dynamicCapabilities.escalate) return;
|
|
3684
3785
|
applyResult(transitionSelectorFlow(context, { type: "ESCALATE" }));
|
|
3685
3786
|
return;
|
|
3686
3787
|
}
|
|
@@ -4063,7 +4164,7 @@ function createCandidateFactory(diff, models, context, captureRecorder, baseGuid
|
|
|
4063
4164
|
} : void 0
|
|
4064
4165
|
});
|
|
4065
4166
|
}
|
|
4066
|
-
async function runInteractiveDescribe(models, createCandidates, context, guideHint, isEscalation) {
|
|
4167
|
+
async function runInteractiveDescribe(models, createCandidates, context, guideHint, isEscalation, capabilities) {
|
|
4067
4168
|
return selectCandidate({
|
|
4068
4169
|
createCandidates,
|
|
4069
4170
|
maxSlots: models.length,
|
|
@@ -4071,7 +4172,8 @@ async function runInteractiveDescribe(models, createCandidates, context, guideHi
|
|
|
4071
4172
|
models,
|
|
4072
4173
|
inlineEditPrompt: true,
|
|
4073
4174
|
initialGuideHint: guideHint,
|
|
4074
|
-
isEscalation
|
|
4175
|
+
isEscalation,
|
|
4176
|
+
capabilities
|
|
4075
4177
|
});
|
|
4076
4178
|
}
|
|
4077
4179
|
async function describe(args2) {
|
|
@@ -4088,6 +4190,11 @@ async function describe(args2) {
|
|
|
4088
4190
|
apiPath: "/v1/commit-message/stream"
|
|
4089
4191
|
});
|
|
4090
4192
|
try {
|
|
4193
|
+
const existingCredentials = await getCredentials();
|
|
4194
|
+
const authKind = existingCredentials?.authKind ?? "anonymous";
|
|
4195
|
+
const token = await getToken();
|
|
4196
|
+
const api = createApiClient(token);
|
|
4197
|
+
const capabilities = await resolveEntitlementCapability(api, authKind);
|
|
4091
4198
|
const stats = getJjDiffStats(options.revision);
|
|
4092
4199
|
console.log(ui.success(`Found ${formatDiffStats(stats)}`));
|
|
4093
4200
|
let guideHint;
|
|
@@ -4120,7 +4227,8 @@ async function describe(args2) {
|
|
|
4120
4227
|
createCandidates,
|
|
4121
4228
|
context,
|
|
4122
4229
|
guideHint,
|
|
4123
|
-
isEscalation
|
|
4230
|
+
isEscalation,
|
|
4231
|
+
capabilities
|
|
4124
4232
|
);
|
|
4125
4233
|
if (result.action === "abort") {
|
|
4126
4234
|
if (result.error instanceof InvalidModelError) {
|
|
@@ -4329,6 +4437,48 @@ function composeGuidance2(guideHint) {
|
|
|
4329
4437
|
const normalizedGuideHint = guideHint?.trim() ?? "";
|
|
4330
4438
|
return normalizedGuideHint || void 0;
|
|
4331
4439
|
}
|
|
4440
|
+
function decideVcsCommitMessageSelection(state, result, escalationModels) {
|
|
4441
|
+
if (result.action === "escalate") {
|
|
4442
|
+
return {
|
|
4443
|
+
kind: "escalate",
|
|
4444
|
+
state: {
|
|
4445
|
+
...state,
|
|
4446
|
+
models: escalationModels,
|
|
4447
|
+
guideHint: void 0,
|
|
4448
|
+
refineMessage: void 0,
|
|
4449
|
+
isEscalation: true
|
|
4450
|
+
}
|
|
4451
|
+
};
|
|
4452
|
+
}
|
|
4453
|
+
if (result.action === "refine") {
|
|
4454
|
+
return {
|
|
4455
|
+
kind: "refine",
|
|
4456
|
+
state: {
|
|
4457
|
+
...state,
|
|
4458
|
+
guideHint: result.guide,
|
|
4459
|
+
refineMessage: result.selected ?? result.selectedCandidate?.content
|
|
4460
|
+
},
|
|
4461
|
+
guideHint: result.guide,
|
|
4462
|
+
refineMessage: result.selected ?? result.selectedCandidate?.content
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
if (result.action === "confirm" && result.selected) {
|
|
4466
|
+
return {
|
|
4467
|
+
kind: "confirm",
|
|
4468
|
+
state,
|
|
4469
|
+
selected: result.selected,
|
|
4470
|
+
selectedCandidateGenerationId: result.selectedCandidate?.generationId
|
|
4471
|
+
};
|
|
4472
|
+
}
|
|
4473
|
+
if (result.action === "abort") {
|
|
4474
|
+
return {
|
|
4475
|
+
kind: "abort",
|
|
4476
|
+
state,
|
|
4477
|
+
error: result.error
|
|
4478
|
+
};
|
|
4479
|
+
}
|
|
4480
|
+
return { kind: "continue", state };
|
|
4481
|
+
}
|
|
4332
4482
|
async function translate(args2) {
|
|
4333
4483
|
const options = parseArgs(args2);
|
|
4334
4484
|
const input = await stdin();
|
|
@@ -4357,14 +4507,20 @@ async function handleVcsCommitMessage(input, options, args2) {
|
|
|
4357
4507
|
args: args2,
|
|
4358
4508
|
apiPath: "/v1/commit-message/stream"
|
|
4359
4509
|
});
|
|
4360
|
-
const
|
|
4510
|
+
const baseModels = resolveModels(options.cliModels);
|
|
4511
|
+
const escalationModels = resolveEscalationModels();
|
|
4512
|
+
let state = {
|
|
4513
|
+
models: baseModels,
|
|
4514
|
+
isEscalation: false
|
|
4515
|
+
};
|
|
4361
4516
|
try {
|
|
4517
|
+
const existingCredentials = await getCredentials();
|
|
4518
|
+
const authKind = existingCredentials?.authKind ?? "anonymous";
|
|
4362
4519
|
const token = await getToken();
|
|
4363
4520
|
const installationId = await getInstallationId();
|
|
4364
4521
|
const api = createApiClient(token);
|
|
4522
|
+
const capabilities = await resolveEntitlementCapability(api, authKind);
|
|
4365
4523
|
const apiClient = api;
|
|
4366
|
-
let guideHint;
|
|
4367
|
-
let refineMessage;
|
|
4368
4524
|
let commandExecutionRun = 0;
|
|
4369
4525
|
const recordSelection2 = async (generationId) => {
|
|
4370
4526
|
if (!generationId || !apiClient) return;
|
|
@@ -4381,10 +4537,10 @@ async function handleVcsCommitMessage(input, options, args2) {
|
|
|
4381
4537
|
console.error(`Warning: Failed to record selection. ${message}`);
|
|
4382
4538
|
}
|
|
4383
4539
|
};
|
|
4384
|
-
const startCommandExecutionSession = (isRefineAttempt) => {
|
|
4540
|
+
const startCommandExecutionSession = (isRefineAttempt, currentModels) => {
|
|
4385
4541
|
const sessionId = ++commandExecutionRun;
|
|
4386
|
-
const requestGuide = composeGuidance2(guideHint);
|
|
4387
|
-
const apiPath = isRefineAttempt
|
|
4542
|
+
const requestGuide = composeGuidance2(state.guideHint);
|
|
4543
|
+
const apiPath = isRefineAttempt ? "/v1/commit-message/refine" : TARGET_TO_API_PATH[options.target];
|
|
4388
4544
|
const { commandExecutionPromise, abortController, cliSessionId } = startCommandExecution({
|
|
4389
4545
|
api,
|
|
4390
4546
|
installationId,
|
|
@@ -4392,7 +4548,7 @@ async function handleVcsCommitMessage(input, options, args2) {
|
|
|
4392
4548
|
args: args2,
|
|
4393
4549
|
apiPath,
|
|
4394
4550
|
requestPayload: {
|
|
4395
|
-
...
|
|
4551
|
+
...currentModels.length === 1 ? { input, target: "vcs-commit-message", model: currentModels[0] } : { input, target: "vcs-commit-message", models: currentModels },
|
|
4396
4552
|
...requestGuide ? { guide: requestGuide } : {}
|
|
4397
4553
|
}
|
|
4398
4554
|
});
|
|
@@ -4402,7 +4558,7 @@ async function handleVcsCommitMessage(input, options, args2) {
|
|
|
4402
4558
|
}
|
|
4403
4559
|
abortController.abort(abortReasonForError(error));
|
|
4404
4560
|
await handleCommandExecutionError(error, {
|
|
4405
|
-
progress: { ready: 0, total:
|
|
4561
|
+
progress: { ready: 0, total: currentModels.length }
|
|
4406
4562
|
});
|
|
4407
4563
|
});
|
|
4408
4564
|
return {
|
|
@@ -4412,32 +4568,40 @@ async function handleVcsCommitMessage(input, options, args2) {
|
|
|
4412
4568
|
};
|
|
4413
4569
|
};
|
|
4414
4570
|
while (true) {
|
|
4415
|
-
const isRefineAttempt = refineMessage !== void 0;
|
|
4416
|
-
const { commandExecutionSignal, commandExecutionPromise, cliSessionId } = startCommandExecutionSession(isRefineAttempt);
|
|
4571
|
+
const isRefineAttempt = state.refineMessage !== void 0;
|
|
4572
|
+
const { commandExecutionSignal, commandExecutionPromise, cliSessionId } = startCommandExecutionSession(isRefineAttempt, state.models);
|
|
4417
4573
|
const createCandidates = (signal) => generateCommitMessages({
|
|
4418
4574
|
diff: input,
|
|
4419
|
-
models,
|
|
4420
|
-
guide: composeGuidance2(guideHint),
|
|
4575
|
+
models: state.models,
|
|
4576
|
+
guide: composeGuidance2(state.guideHint),
|
|
4421
4577
|
signal: mergeAbortSignals(signal, commandExecutionSignal),
|
|
4422
4578
|
cliSessionId,
|
|
4423
4579
|
commandExecutionPromise,
|
|
4424
4580
|
streamCaptureRecorder: captureRecorder,
|
|
4425
|
-
refine: refineMessage !== void 0 ? {
|
|
4426
|
-
originalMessage: refineMessage,
|
|
4427
|
-
refineInstruction: guideHint
|
|
4581
|
+
refine: state.refineMessage !== void 0 ? {
|
|
4582
|
+
originalMessage: state.refineMessage,
|
|
4583
|
+
refineInstruction: state.guideHint
|
|
4428
4584
|
} : void 0
|
|
4429
4585
|
});
|
|
4430
4586
|
const result = await selectCandidate({
|
|
4431
4587
|
createCandidates,
|
|
4432
|
-
maxSlots: models.length,
|
|
4588
|
+
maxSlots: state.models.length,
|
|
4433
4589
|
abortSignal: commandExecutionSignal,
|
|
4434
|
-
models,
|
|
4590
|
+
models: state.models,
|
|
4435
4591
|
inlineEditPrompt: true,
|
|
4436
|
-
initialGuideHint: guideHint
|
|
4592
|
+
initialGuideHint: state.guideHint,
|
|
4593
|
+
isEscalation: state.isEscalation,
|
|
4594
|
+
capabilities
|
|
4437
4595
|
});
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4596
|
+
const transition = decideVcsCommitMessageSelection(
|
|
4597
|
+
state,
|
|
4598
|
+
result,
|
|
4599
|
+
escalationModels
|
|
4600
|
+
);
|
|
4601
|
+
state = transition.state;
|
|
4602
|
+
if (transition.kind === "abort") {
|
|
4603
|
+
if (transition.error instanceof InvalidModelError) {
|
|
4604
|
+
exitWithInvalidModelError2(transition.error);
|
|
4441
4605
|
}
|
|
4442
4606
|
if (isCommandExecutionAbort(commandExecutionSignal)) {
|
|
4443
4607
|
return;
|
|
@@ -4445,14 +4609,16 @@ async function handleVcsCommitMessage(input, options, args2) {
|
|
|
4445
4609
|
console.error("Aborted.");
|
|
4446
4610
|
process.exit(1);
|
|
4447
4611
|
}
|
|
4448
|
-
if (
|
|
4449
|
-
|
|
4450
|
-
refineMessage = result.selected ?? result.selectedCandidate?.content;
|
|
4612
|
+
if (transition.kind === "escalate") {
|
|
4613
|
+
console.log(ui.hint(" -> Escalate"));
|
|
4451
4614
|
continue;
|
|
4452
4615
|
}
|
|
4453
|
-
if (
|
|
4454
|
-
|
|
4455
|
-
|
|
4616
|
+
if (transition.kind === "refine") {
|
|
4617
|
+
continue;
|
|
4618
|
+
}
|
|
4619
|
+
if (transition.kind === "confirm") {
|
|
4620
|
+
await recordSelection2(transition.selectedCandidateGenerationId);
|
|
4621
|
+
console.log(transition.selected);
|
|
4456
4622
|
return;
|
|
4457
4623
|
}
|
|
4458
4624
|
}
|
|
@@ -4642,7 +4808,7 @@ function parseArgs(args2) {
|
|
|
4642
4808
|
// package.json
|
|
4643
4809
|
var package_default = {
|
|
4644
4810
|
name: "ultrahope",
|
|
4645
|
-
version: "0.1.
|
|
4811
|
+
version: "0.1.12",
|
|
4646
4812
|
description: "LLM-powered development workflow assistant",
|
|
4647
4813
|
type: "module",
|
|
4648
4814
|
license: "MIT",
|
|
@@ -4734,8 +4900,8 @@ Options:
|
|
|
4734
4900
|
--help, -h Show this help message
|
|
4735
4901
|
|
|
4736
4902
|
Plans:
|
|
4737
|
-
|
|
4738
|
-
Pro: login required, paid usage, no
|
|
4903
|
+
Free: 5 requests/day and 40,000 chars/request in the CLI without login
|
|
4904
|
+
Pro: login required, paid usage, no Free plan limits`);
|
|
4739
4905
|
}
|
|
4740
4906
|
main().catch((err) => {
|
|
4741
4907
|
console.error(err);
|