@usabledev/usable-chat 1.147.0 → 1.149.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/cli.js +410 -75
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -23716,8 +23716,7 @@ var init_harness_prompt = __esm({
|
|
|
23716
23716
|
</communication>
|
|
23717
23717
|
|
|
23718
23718
|
<grounding>
|
|
23719
|
-
Usable is the user's long-term memory
|
|
23720
|
-
- Before non-trivial work, search it with \`agentic-search-fragments\`: natural-language intent (what you're trying to achieve, not keywords), scoped to the workspace; iterate until the result is sufficient. Prefer it over external search or memory.
|
|
23719
|
+
Usable is the user's long-term memory. When a task touches project domain, past decisions, or conventions you're unsure of, ground yourself ONCE with a single targeted \`agentic-search-fragments\` (natural-language intent \u2014 what you're trying to achieve, not keywords \u2014 scoped to the workspace), then proceed. One good search is enough: don't re-run the same search, and don't search at all for routine local work you can just do (reading/editing files, running tests, authoring an extension from the \`extension_authoring_guide\`).
|
|
23721
23720
|
- Cite the fragments you relied on (title + id). When you point the user at a fragment, give the link: https://usable.dev/dashboard/workspaces/<workspaceId>/fragments/<fragmentId>.
|
|
23722
23721
|
- IDs \u2014 fragment, workspace, collection \u2014 come ONLY from tool results. NEVER invent, guess, or shorten a UUID.
|
|
23723
23722
|
- After work is complete AND verified, capture durable insights with create/update-memory-fragment (tag by repo; note residual risk). Don't document speculatively.
|
|
@@ -23725,7 +23724,6 @@ Usable is the user's long-term memory and your first source of truth \u2014 cons
|
|
|
23725
23724
|
|
|
23726
23725
|
<tools>
|
|
23727
23726
|
- Resolve prerequisites before acting; run independent calls in parallel.
|
|
23728
|
-
- Don't stop at the first plausible answer if another call would cut uncertainty. If a search comes back empty or suspiciously narrow, retry with a different strategy.
|
|
23729
23727
|
- \`bash\` output is truncated to a preview; the full output spills to a temp file whose path is reported. Don't pipe through \`head\`/\`tail\` to work around the truncation.
|
|
23730
23728
|
- Destructive or hard-to-reverse commands require approval \u2014 surface what you're about to do first.
|
|
23731
23729
|
</tools>
|
|
@@ -23740,7 +23738,7 @@ Usable is the user's long-term memory and your first source of truth \u2014 cons
|
|
|
23740
23738
|
</completeness>
|
|
23741
23739
|
|
|
23742
23740
|
<workflow>
|
|
23743
|
-
1. Scope \u2014 for multi-file work, research the existing code and conventions (
|
|
23741
|
+
1. Scope \u2014 for multi-file work, research the existing code and conventions before writing (ground once in Usable if the task touches project conventions or past decisions). Reuse existing patterns; inventing a parallel convention is prohibited. A local coding task needs no plan-mode, task-status updates, or other workflow ceremony unless the user asks for it \u2014 just do the work.
|
|
23744
23742
|
2. Edit \u2014 read whole sections, not snippets. Re-read a file if it changed since you last read it. Fix problems at the source and remove obsolete code rather than leaving aliases or re-exports behind.
|
|
23745
23743
|
3. Verify \u2014 for non-trivial changes, prove it with tests you can actually run (prefer real over mocks; test behavior, not plumbing). All tests green before you call it done \u2014 "it was already broken" is not a skip reason; fix it or flag it.
|
|
23746
23744
|
4. Report \u2014 say what changed, the evidence (the commands you ran and their results), and anything left or risky. Be brief in prose, not in evidence.
|
|
@@ -27325,7 +27323,7 @@ var init_sql = __esm({
|
|
|
27325
27323
|
return new SQL([new StringChunk(str)]);
|
|
27326
27324
|
}
|
|
27327
27325
|
sql2.raw = raw;
|
|
27328
|
-
function
|
|
27326
|
+
function join21(chunks, separator) {
|
|
27329
27327
|
const result = [];
|
|
27330
27328
|
for (const [i18, chunk2] of chunks.entries()) {
|
|
27331
27329
|
if (i18 > 0 && separator !== void 0) {
|
|
@@ -27335,7 +27333,7 @@ var init_sql = __esm({
|
|
|
27335
27333
|
}
|
|
27336
27334
|
return new SQL(result);
|
|
27337
27335
|
}
|
|
27338
|
-
sql2.join =
|
|
27336
|
+
sql2.join = join21;
|
|
27339
27337
|
function identifier(value) {
|
|
27340
27338
|
return new Name(value);
|
|
27341
27339
|
}
|
|
@@ -31722,7 +31720,7 @@ var init_select2 = __esm({
|
|
|
31722
31720
|
const baseTableName = this.tableName;
|
|
31723
31721
|
const tableName = getTableLikeName(table);
|
|
31724
31722
|
for (const item of extractUsedTable(table)) this.usedTables.add(item);
|
|
31725
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
31723
|
+
if (typeof tableName === "string" && this.config.joins?.some((join21) => join21.alias === tableName)) {
|
|
31726
31724
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
31727
31725
|
}
|
|
31728
31726
|
if (!this.isPartialSelect) {
|
|
@@ -33257,7 +33255,7 @@ var init_update = __esm({
|
|
|
33257
33255
|
createJoin(joinType) {
|
|
33258
33256
|
return (table, on3) => {
|
|
33259
33257
|
const tableName = getTableLikeName(table);
|
|
33260
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
33258
|
+
if (typeof tableName === "string" && this.config.joins.some((join21) => join21.alias === tableName)) {
|
|
33261
33259
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
33262
33260
|
}
|
|
33263
33261
|
if (typeof on3 === "function") {
|
|
@@ -33353,10 +33351,10 @@ var init_update = __esm({
|
|
|
33353
33351
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
33354
33352
|
fields[tableName] = fromFields;
|
|
33355
33353
|
}
|
|
33356
|
-
for (const
|
|
33357
|
-
const tableName2 = getTableLikeName(
|
|
33358
|
-
if (typeof tableName2 === "string" && !is(
|
|
33359
|
-
const fromFields = this.getTableLikeFields(
|
|
33354
|
+
for (const join21 of this.config.joins) {
|
|
33355
|
+
const tableName2 = getTableLikeName(join21.table);
|
|
33356
|
+
if (typeof tableName2 === "string" && !is(join21.table, SQL)) {
|
|
33357
|
+
const fromFields = this.getTableLikeFields(join21.table);
|
|
33360
33358
|
fields[tableName2] = fromFields;
|
|
33361
33359
|
}
|
|
33362
33360
|
}
|
|
@@ -84938,8 +84936,8 @@ var init_bedrock_sdk = __esm({
|
|
|
84938
84936
|
});
|
|
84939
84937
|
|
|
84940
84938
|
// src/lib/logger.ts
|
|
84941
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
84942
|
-
import { join as
|
|
84939
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync9 } from "fs";
|
|
84940
|
+
import { join as join11 } from "path";
|
|
84943
84941
|
var LOG_LEVEL_PRIORITY, Logger, logger2, orchestrationLogger, streamLogger;
|
|
84944
84942
|
var init_logger2 = __esm({
|
|
84945
84943
|
"src/lib/logger.ts"() {
|
|
@@ -84952,7 +84950,7 @@ var init_logger2 = __esm({
|
|
|
84952
84950
|
};
|
|
84953
84951
|
Logger = class {
|
|
84954
84952
|
constructor() {
|
|
84955
|
-
this.logDir =
|
|
84953
|
+
this.logDir = join11(process.cwd(), "logs");
|
|
84956
84954
|
this.enableFileLogging = process.env.ENABLE_FILE_LOGGING === "true";
|
|
84957
84955
|
this.isDevelopment = process.env.NODE_ENV === "development";
|
|
84958
84956
|
this.enableConsoleLogging = this.isDevelopment || process.env.ENABLE_CONSOLE_LOGGING === "true";
|
|
@@ -84960,7 +84958,7 @@ var init_logger2 = __esm({
|
|
|
84960
84958
|
this.minLogLevel = ["debug", "info", "warn", "error"].includes(configuredLevel) ? configuredLevel : "info";
|
|
84961
84959
|
if (this.enableFileLogging) {
|
|
84962
84960
|
try {
|
|
84963
|
-
|
|
84961
|
+
mkdirSync9(this.logDir, { recursive: true });
|
|
84964
84962
|
} catch (error41) {
|
|
84965
84963
|
console.error("Failed to create logs directory:", error41);
|
|
84966
84964
|
this.enableFileLogging = false;
|
|
@@ -85042,7 +85040,7 @@ var init_logger2 = __esm({
|
|
|
85042
85040
|
logToFile(entry) {
|
|
85043
85041
|
try {
|
|
85044
85042
|
const logFileName = this.getLogFileName(entry.category);
|
|
85045
|
-
const logPath =
|
|
85043
|
+
const logPath = join11(this.logDir, logFileName);
|
|
85046
85044
|
const logLine = JSON.stringify(entry) + "\n";
|
|
85047
85045
|
appendFileSync3(logPath, logLine, "utf-8");
|
|
85048
85046
|
} catch (error41) {
|
|
@@ -155289,7 +155287,7 @@ var init_registry2 = __esm({
|
|
|
155289
155287
|
* @returns Array of sub-agents that can handle the query
|
|
155290
155288
|
*/
|
|
155291
155289
|
findHandlers(query, context) {
|
|
155292
|
-
return this.getAll().filter((agent) => agent.canHandle(query, context));
|
|
155290
|
+
return this.getAll().filter((agent) => agent.canHandle(query, context)).sort((a21, b32) => (b32.priority ?? 0) - (a21.priority ?? 0));
|
|
155293
155291
|
}
|
|
155294
155292
|
/**
|
|
155295
155293
|
* Check if a sub-agent is registered
|
|
@@ -156928,8 +156926,8 @@ ${effectiveSystemPrompt}`;
|
|
|
156928
156926
|
apiKey: options2.apiKey,
|
|
156929
156927
|
getAccessToken: options2.getAccessToken,
|
|
156930
156928
|
// Pass token provider for MCP tool authentication
|
|
156931
|
-
maxSteps: 10,
|
|
156932
|
-
// Allow multi-step tool calling
|
|
156929
|
+
maxSteps: subAgent.maxTurns ?? 10,
|
|
156930
|
+
// Allow multi-step tool calling; expert maxTurns overrides default
|
|
156933
156931
|
timeoutMs: 6e4,
|
|
156934
156932
|
// 60 second timeout
|
|
156935
156933
|
temperature,
|
|
@@ -157405,7 +157403,11 @@ var init_custom_expert = __esm({
|
|
|
157405
157403
|
(val) => val.startsWith("data:") || val.startsWith("http://") || val.startsWith("https://") || val.startsWith("/"),
|
|
157406
157404
|
"Must be a valid URL, data URL, or path"
|
|
157407
157405
|
).optional(),
|
|
157408
|
-
parentTools: external_exports.array(parentToolDefinitionSchema).max(MAX_PARENT_TOOLS).optional()
|
|
157406
|
+
parentTools: external_exports.array(parentToolDefinitionSchema).max(MAX_PARENT_TOOLS).optional(),
|
|
157407
|
+
excludeKeywords: external_exports.array(external_exports.string().min(1).max(50)).max(20).optional(),
|
|
157408
|
+
priority: external_exports.number().int().min(0).max(100).optional(),
|
|
157409
|
+
maxTurns: external_exports.number().int().min(1).max(50).optional(),
|
|
157410
|
+
uiShortName: external_exports.string().max(30).optional()
|
|
157409
157411
|
});
|
|
157410
157412
|
createCustomExpertSchema = external_exports.object({
|
|
157411
157413
|
key: external_exports.string().min(1).max(100).regex(/^[a-z0-9-]+$/, "Key must be lowercase alphanumeric with hyphens").optional(),
|
|
@@ -215465,14 +215467,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
215465
215467
|
} else if (node.nodeType === 1) {
|
|
215466
215468
|
replacement = replacementForNode.call(self2, node);
|
|
215467
215469
|
}
|
|
215468
|
-
return
|
|
215470
|
+
return join21(output, replacement);
|
|
215469
215471
|
}, "");
|
|
215470
215472
|
}
|
|
215471
215473
|
function postProcess(output) {
|
|
215472
215474
|
var self2 = this;
|
|
215473
215475
|
this.rules.forEach(function(rule) {
|
|
215474
215476
|
if (typeof rule.append === "function") {
|
|
215475
|
-
output =
|
|
215477
|
+
output = join21(output, rule.append(self2.options));
|
|
215476
215478
|
}
|
|
215477
215479
|
});
|
|
215478
215480
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -215484,7 +215486,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
215484
215486
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
215485
215487
|
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
|
|
215486
215488
|
}
|
|
215487
|
-
function
|
|
215489
|
+
function join21(output, replacement) {
|
|
215488
215490
|
var s17 = trimTrailingNewlines(output);
|
|
215489
215491
|
var s22 = trimLeadingNewlines(replacement);
|
|
215490
215492
|
var nls = Math.max(output.length - s17.length, replacement.length - s22.length);
|
|
@@ -242472,6 +242474,14 @@ Never treat a markdown file in working memory as a completed deliverable.
|
|
|
242472
242474
|
systemMessageParts.push(LANGUAGE_PROMPT);
|
|
242473
242475
|
orchestrationLogger.info("Multilingual mode enabled - Faroese language prompt added");
|
|
242474
242476
|
}
|
|
242477
|
+
if (config3.language) {
|
|
242478
|
+
systemMessageParts.push(
|
|
242479
|
+
`The user interface requested language "${config3.language}". Answer in that language unless the user explicitly asks for another language.`
|
|
242480
|
+
);
|
|
242481
|
+
if (config3.language === "fo" && !config3.isMultilingual) {
|
|
242482
|
+
systemMessageParts.push(LANGUAGE_PROMPT);
|
|
242483
|
+
}
|
|
242484
|
+
}
|
|
242475
242485
|
if (chatMode === "discussion") {
|
|
242476
242486
|
systemMessageParts.push(`<discussion-mode>
|
|
242477
242487
|
CRITICAL: You are in DISCUSSION MODE (read-only). This is a hard constraint you MUST follow.
|
|
@@ -244534,12 +244544,12 @@ var init_session_pathway = __esm({
|
|
|
244534
244544
|
});
|
|
244535
244545
|
|
|
244536
244546
|
// src/adapters/cli/prompt-files.ts
|
|
244537
|
-
import { existsSync as
|
|
244538
|
-
import { dirname as dirname14, join as
|
|
244547
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, realpathSync as realpathSync4 } from "node:fs";
|
|
244548
|
+
import { dirname as dirname14, join as join16 } from "node:path";
|
|
244539
244549
|
function readFile(path11) {
|
|
244540
244550
|
try {
|
|
244541
|
-
if (!
|
|
244542
|
-
const content =
|
|
244551
|
+
if (!existsSync10(path11)) return void 0;
|
|
244552
|
+
const content = readFileSync7(path11, "utf8").trim();
|
|
244543
244553
|
return content || void 0;
|
|
244544
244554
|
} catch {
|
|
244545
244555
|
return void 0;
|
|
@@ -244548,7 +244558,7 @@ function readFile(path11) {
|
|
|
244548
244558
|
function gitRoot(start) {
|
|
244549
244559
|
let dir = start;
|
|
244550
244560
|
while (true) {
|
|
244551
|
-
if (
|
|
244561
|
+
if (existsSync10(join16(dir, ".git"))) return dir;
|
|
244552
244562
|
const parent = dirname14(dir);
|
|
244553
244563
|
if (parent === dir) return start;
|
|
244554
244564
|
dir = parent;
|
|
@@ -244566,16 +244576,16 @@ function projectChain(cwd) {
|
|
|
244566
244576
|
return chain;
|
|
244567
244577
|
}
|
|
244568
244578
|
function resolvePromptFiles(cwd, dataDir2) {
|
|
244569
|
-
const override = readFile(
|
|
244579
|
+
const override = readFile(join16(cwd, ".usable", "SYSTEM.md")) ?? readFile(join16(dataDir2, "SYSTEM.md"));
|
|
244570
244580
|
const appends = [
|
|
244571
|
-
readFile(
|
|
244572
|
-
readFile(
|
|
244581
|
+
readFile(join16(dataDir2, "SYSTEM.append.md")),
|
|
244582
|
+
readFile(join16(cwd, ".usable", "SYSTEM.append.md"))
|
|
244573
244583
|
].filter((s17) => Boolean(s17));
|
|
244574
244584
|
const append = appends.length ? appends.join("\n\n") : void 0;
|
|
244575
244585
|
const candidates = [];
|
|
244576
|
-
for (const name14 of CONTEXT_FILE_NAMES) candidates.push(
|
|
244586
|
+
for (const name14 of CONTEXT_FILE_NAMES) candidates.push(join16(dataDir2, name14));
|
|
244577
244587
|
for (const dir of projectChain(cwd)) {
|
|
244578
|
-
for (const name14 of CONTEXT_FILE_NAMES) candidates.push(
|
|
244588
|
+
for (const name14 of CONTEXT_FILE_NAMES) candidates.push(join16(dir, name14));
|
|
244579
244589
|
}
|
|
244580
244590
|
const seen = /* @__PURE__ */ new Set();
|
|
244581
244591
|
const contextFiles = [];
|
|
@@ -245321,12 +245331,12 @@ var init_stdio2 = __esm({
|
|
|
245321
245331
|
});
|
|
245322
245332
|
|
|
245323
245333
|
// src/adapters/cli/mcp-servers.ts
|
|
245324
|
-
import { existsSync as
|
|
245325
|
-
import { join as
|
|
245334
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
245335
|
+
import { join as join17 } from "node:path";
|
|
245326
245336
|
function readConfig(path11) {
|
|
245327
245337
|
try {
|
|
245328
|
-
if (!
|
|
245329
|
-
const parsed = configSchema.safeParse(JSON.parse(
|
|
245338
|
+
if (!existsSync11(path11)) return {};
|
|
245339
|
+
const parsed = configSchema.safeParse(JSON.parse(readFileSync8(path11, "utf8")));
|
|
245330
245340
|
if (!parsed.success || !parsed.data.mcpServers) return {};
|
|
245331
245341
|
const out = {};
|
|
245332
245342
|
for (const [name14, spec] of Object.entries(parsed.data.mcpServers)) out[name14] = { name: name14, ...spec };
|
|
@@ -245337,9 +245347,9 @@ function readConfig(path11) {
|
|
|
245337
245347
|
}
|
|
245338
245348
|
function loadMcpServerSpecs(cwd, dataDir2) {
|
|
245339
245349
|
const merged = {
|
|
245340
|
-
...readConfig(
|
|
245341
|
-
...readConfig(
|
|
245342
|
-
...readConfig(
|
|
245350
|
+
...readConfig(join17(dataDir2, "mcp.json")),
|
|
245351
|
+
...readConfig(join17(cwd, ".mcp.json")),
|
|
245352
|
+
...readConfig(join17(cwd, ".usable", "mcp.json"))
|
|
245343
245353
|
};
|
|
245344
245354
|
return Object.values(merged).filter((s17) => !s17.disabled && (s17.command || s17.url));
|
|
245345
245355
|
}
|
|
@@ -245759,8 +245769,8 @@ var init_usable_api = __esm({
|
|
|
245759
245769
|
});
|
|
245760
245770
|
|
|
245761
245771
|
// src/adapters/cli/media-tools.ts
|
|
245762
|
-
import { writeFileSync as
|
|
245763
|
-
import { join as
|
|
245772
|
+
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
245773
|
+
import { join as join18 } from "node:path";
|
|
245764
245774
|
function stamp() {
|
|
245765
245775
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
245766
245776
|
}
|
|
@@ -245800,8 +245810,8 @@ function buildMediaTools(opts) {
|
|
|
245800
245810
|
const b64 = resp.data?.[0]?.b64_json;
|
|
245801
245811
|
if (!b64) return { error: "no image returned" };
|
|
245802
245812
|
const buf = Buffer.from(b64, "base64");
|
|
245803
|
-
const file2 =
|
|
245804
|
-
|
|
245813
|
+
const file2 = join18(outDir ?? process.cwd(), `usable-image-${stamp()}.png`);
|
|
245814
|
+
writeFileSync8(file2, buf);
|
|
245805
245815
|
return { path: file2, bytes: buf.length };
|
|
245806
245816
|
} catch (err) {
|
|
245807
245817
|
return { error: err.message || "image generation failed" };
|
|
@@ -245920,7 +245930,9 @@ Notes:
|
|
|
245920
245930
|
|
|
245921
245931
|
## Minimal example
|
|
245922
245932
|
\`\`\`ts
|
|
245923
|
-
import
|
|
245933
|
+
// Type-only import (erased at runtime) \u2014 keep the file dependency-free so it
|
|
245934
|
+
// loads anywhere. Use plain JSON-schema for \`parameters\`; a zod schema also
|
|
245935
|
+
// works, but only if your project actually has zod installed on disk.
|
|
245924
245936
|
import type { ExtensionAPI } from '@/extensions/api'
|
|
245925
245937
|
|
|
245926
245938
|
export default (pi: ExtensionAPI) => {
|
|
@@ -245928,7 +245940,7 @@ export default (pi: ExtensionAPI) => {
|
|
|
245928
245940
|
pi.registerTool({
|
|
245929
245941
|
name: 'word_count',
|
|
245930
245942
|
description: 'Count the words in a string.',
|
|
245931
|
-
parameters:
|
|
245943
|
+
parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
|
|
245932
245944
|
async execute({ text }) {
|
|
245933
245945
|
return { success: true, data: { words: String(text).trim().split(/\\s+/).filter(Boolean).length } }
|
|
245934
245946
|
},
|
|
@@ -245953,20 +245965,113 @@ export default (pi: ExtensionAPI) => {
|
|
|
245953
245965
|
}
|
|
245954
245966
|
\`\`\`
|
|
245955
245967
|
|
|
245956
|
-
|
|
245968
|
+
## Verifying it loads
|
|
245969
|
+
- Run \`/verify-extension <name>\` \u2014 it transpiles + loads the file in isolation (via jiti) and reports syntax / load-time errors and what it registers. Do NOT run \`tsc\` on the file: path aliases (\`@/extensions/api\`) won't resolve standalone, and the \`@/extensions/api\` import is types-only (erased at runtime). \`/verify-extension\` is the supported check.
|
|
245970
|
+
- Then \`/trust\` (project only) + \`/reload\` \u2014 or restart. Confirm with \`/help\` (commands) and by asking the model to call your tool.`;
|
|
245971
|
+
}
|
|
245972
|
+
});
|
|
245973
|
+
|
|
245974
|
+
// src/adapters/cli/diff.ts
|
|
245975
|
+
function unifiedDiff(oldStr, newStr, opts = {}) {
|
|
245976
|
+
if (oldStr === newStr) return "";
|
|
245977
|
+
const context = opts.context ?? 3;
|
|
245978
|
+
const maxLines = opts.maxLines ?? 80;
|
|
245979
|
+
const a21 = oldStr === "" ? [] : oldStr.split("\n");
|
|
245980
|
+
const b32 = newStr === "" ? [] : newStr.split("\n");
|
|
245981
|
+
const n30 = a21.length;
|
|
245982
|
+
const m33 = b32.length;
|
|
245983
|
+
if (n30 * m33 > 4e6) return `(diff omitted \u2014 ${n30}\u2192${m33} lines too large to render)`;
|
|
245984
|
+
const lcs = Array.from({ length: n30 + 1 }, () => new Array(m33 + 1).fill(0));
|
|
245985
|
+
for (let i19 = n30 - 1; i19 >= 0; i19--) {
|
|
245986
|
+
for (let j21 = m33 - 1; j21 >= 0; j21--) {
|
|
245987
|
+
lcs[i19][j21] = a21[i19] === b32[j21] ? lcs[i19 + 1][j21 + 1] + 1 : Math.max(lcs[i19 + 1][j21], lcs[i19][j21 + 1]);
|
|
245988
|
+
}
|
|
245989
|
+
}
|
|
245990
|
+
const ops = [];
|
|
245991
|
+
let i18 = 0;
|
|
245992
|
+
let j20 = 0;
|
|
245993
|
+
while (i18 < n30 && j20 < m33) {
|
|
245994
|
+
if (a21[i18] === b32[j20]) {
|
|
245995
|
+
ops.push({ t: " ", line: a21[i18] });
|
|
245996
|
+
i18++;
|
|
245997
|
+
j20++;
|
|
245998
|
+
} else if (lcs[i18 + 1][j20] >= lcs[i18][j20 + 1]) {
|
|
245999
|
+
ops.push({ t: "-", line: a21[i18] });
|
|
246000
|
+
i18++;
|
|
246001
|
+
} else {
|
|
246002
|
+
ops.push({ t: "+", line: b32[j20] });
|
|
246003
|
+
j20++;
|
|
246004
|
+
}
|
|
246005
|
+
}
|
|
246006
|
+
while (i18 < n30) ops.push({ t: "-", line: a21[i18++] });
|
|
246007
|
+
while (j20 < m33) ops.push({ t: "+", line: b32[j20++] });
|
|
246008
|
+
const keep = new Array(ops.length).fill(false);
|
|
246009
|
+
ops.forEach((op2, idx) => {
|
|
246010
|
+
if (op2.t === " ") return;
|
|
246011
|
+
for (let k23 = Math.max(0, idx - context); k23 <= Math.min(ops.length - 1, idx + context); k23++) {
|
|
246012
|
+
keep[k23] = true;
|
|
246013
|
+
}
|
|
246014
|
+
});
|
|
246015
|
+
const out = [];
|
|
246016
|
+
let oldNo = 1;
|
|
246017
|
+
let newNo = 1;
|
|
246018
|
+
let inHunk = false;
|
|
246019
|
+
let hunk = [];
|
|
246020
|
+
let hunkOldStart = 1;
|
|
246021
|
+
let hunkNewStart = 1;
|
|
246022
|
+
const flush = () => {
|
|
246023
|
+
if (hunk.length) {
|
|
246024
|
+
out.push(`@@ -${hunkOldStart} +${hunkNewStart} @@`);
|
|
246025
|
+
out.push(...hunk);
|
|
246026
|
+
hunk = [];
|
|
246027
|
+
}
|
|
246028
|
+
inHunk = false;
|
|
246029
|
+
};
|
|
246030
|
+
for (let idx = 0; idx < ops.length; idx++) {
|
|
246031
|
+
const op2 = ops[idx];
|
|
246032
|
+
if (keep[idx]) {
|
|
246033
|
+
if (!inHunk) {
|
|
246034
|
+
inHunk = true;
|
|
246035
|
+
hunkOldStart = oldNo;
|
|
246036
|
+
hunkNewStart = newNo;
|
|
246037
|
+
}
|
|
246038
|
+
hunk.push(`${op2.t}${op2.line}`);
|
|
246039
|
+
} else if (inHunk) {
|
|
246040
|
+
flush();
|
|
246041
|
+
}
|
|
246042
|
+
if (op2.t === " ") {
|
|
246043
|
+
oldNo++;
|
|
246044
|
+
newNo++;
|
|
246045
|
+
} else if (op2.t === "-") {
|
|
246046
|
+
oldNo++;
|
|
246047
|
+
} else {
|
|
246048
|
+
newNo++;
|
|
246049
|
+
}
|
|
246050
|
+
}
|
|
246051
|
+
flush();
|
|
246052
|
+
if (out.length > maxLines) {
|
|
246053
|
+
const kept = out.slice(0, maxLines);
|
|
246054
|
+
kept.push(`\u2026 (${out.length - maxLines} more diff lines)`);
|
|
246055
|
+
return kept.join("\n");
|
|
246056
|
+
}
|
|
246057
|
+
return out.join("\n");
|
|
246058
|
+
}
|
|
246059
|
+
var init_diff = __esm({
|
|
246060
|
+
"src/adapters/cli/diff.ts"() {
|
|
246061
|
+
"use strict";
|
|
245957
246062
|
}
|
|
245958
246063
|
});
|
|
245959
246064
|
|
|
245960
246065
|
// src/adapters/cli/tools.ts
|
|
245961
246066
|
import { spawn as spawn6 } from "node:child_process";
|
|
245962
|
-
import { existsSync as
|
|
246067
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "node:fs";
|
|
245963
246068
|
import { tmpdir } from "node:os";
|
|
245964
|
-
import { dirname as dirname15, isAbsolute as isAbsolute4, join as
|
|
246069
|
+
import { dirname as dirname15, isAbsolute as isAbsolute4, join as join19, relative as relative2, resolve as resolve6, sep as sep2 } from "node:path";
|
|
245965
246070
|
function truncate2(text2, label) {
|
|
245966
246071
|
if (text2.length <= MAX_OUTPUT_CHARS) return text2;
|
|
245967
|
-
const file2 =
|
|
246072
|
+
const file2 = join19(tmpdir(), `usable-${label}-${Date.now()}.txt`);
|
|
245968
246073
|
try {
|
|
245969
|
-
|
|
246074
|
+
writeFileSync9(file2, text2);
|
|
245970
246075
|
} catch {
|
|
245971
246076
|
}
|
|
245972
246077
|
return text2.slice(0, MAX_OUTPUT_CHARS) + `
|
|
@@ -246090,9 +246195,9 @@ function createLocalTools(opts = {}) {
|
|
|
246090
246195
|
const guarded = guardPath(parsed.data.path);
|
|
246091
246196
|
if (!guarded.ok) return { error: guarded.error };
|
|
246092
246197
|
const path11 = guarded.path;
|
|
246093
|
-
if (!
|
|
246198
|
+
if (!existsSync12(path11)) return { error: `No such file: ${rel(path11)}` };
|
|
246094
246199
|
try {
|
|
246095
|
-
const lines =
|
|
246200
|
+
const lines = readFileSync9(path11, "utf8").split("\n");
|
|
246096
246201
|
const offset = Math.max(1, parsed.data.offset ?? 1);
|
|
246097
246202
|
const limit = Math.max(1, parsed.data.limit ?? MAX_READ_LINES);
|
|
246098
246203
|
const slice = lines.slice(offset - 1, offset - 1 + limit);
|
|
@@ -246124,10 +246229,14 @@ function createLocalTools(opts = {}) {
|
|
|
246124
246229
|
const path11 = guarded.path;
|
|
246125
246230
|
try {
|
|
246126
246231
|
const dir = dirname15(path11);
|
|
246127
|
-
|
|
246128
|
-
|
|
246232
|
+
const before = existsSync12(path11) ? readFileSync9(path11, "utf8") : "";
|
|
246233
|
+
if (!existsSync12(dir)) mkdirSync10(dir, { recursive: true });
|
|
246234
|
+
writeFileSync9(path11, parsed.data.content);
|
|
246129
246235
|
readFiles.add(path11);
|
|
246130
|
-
|
|
246236
|
+
const verb = before ? "Overwrote" : "Wrote";
|
|
246237
|
+
const diff = unifiedDiff(before, parsed.data.content, { maxLines: 60 });
|
|
246238
|
+
return `${verb} ${rel(path11)} (${parsed.data.content.length} bytes)` + (diff ? `
|
|
246239
|
+
${diff}` : "");
|
|
246131
246240
|
} catch (e14) {
|
|
246132
246241
|
return { error: e14 instanceof Error ? e14.message : String(e14) };
|
|
246133
246242
|
}
|
|
@@ -246157,11 +246266,12 @@ function createLocalTools(opts = {}) {
|
|
|
246157
246266
|
const guarded = guardPath(parsed.data.path);
|
|
246158
246267
|
if (!guarded.ok) return { error: guarded.error };
|
|
246159
246268
|
const path11 = guarded.path;
|
|
246160
|
-
if (!
|
|
246269
|
+
if (!existsSync12(path11)) return { error: `No such file: ${rel(path11)}` };
|
|
246161
246270
|
if (!readFiles.has(path11)) return { error: `Read ${rel(path11)} before editing it.` };
|
|
246162
246271
|
const edits = parsed.data.edits;
|
|
246163
246272
|
try {
|
|
246164
|
-
|
|
246273
|
+
const original2 = readFileSync9(path11, "utf8");
|
|
246274
|
+
let content = original2;
|
|
246165
246275
|
for (const [i18, edit2] of edits.entries()) {
|
|
246166
246276
|
const { oldText, newText } = edit2;
|
|
246167
246277
|
let count2 = content.split(oldText).length - 1;
|
|
@@ -246179,8 +246289,10 @@ function createLocalTools(opts = {}) {
|
|
|
246179
246289
|
if (count2 > 1) return { error: `edit ${i18 + 1}: oldText appears ${count2}\xD7 \u2014 add more context to make it unique` };
|
|
246180
246290
|
content = content.replace(target, () => newText);
|
|
246181
246291
|
}
|
|
246182
|
-
|
|
246183
|
-
|
|
246292
|
+
writeFileSync9(path11, content);
|
|
246293
|
+
const diff = unifiedDiff(original2, content, { maxLines: 60 });
|
|
246294
|
+
return `Applied ${edits.length} edit(s) to ${rel(path11)}` + (diff ? `
|
|
246295
|
+
${diff}` : "");
|
|
246184
246296
|
} catch (e14) {
|
|
246185
246297
|
return { error: e14 instanceof Error ? e14.message : String(e14) };
|
|
246186
246298
|
}
|
|
@@ -246331,6 +246443,7 @@ var init_tools = __esm({
|
|
|
246331
246443
|
"use strict";
|
|
246332
246444
|
init_zod();
|
|
246333
246445
|
init_extension_authoring();
|
|
246446
|
+
init_diff();
|
|
246334
246447
|
MAX_READ_LINES = 2e3;
|
|
246335
246448
|
MAX_OUTPUT_CHARS = 3e4;
|
|
246336
246449
|
DEFAULT_BASH_TIMEOUT_S = 120;
|
|
@@ -261237,13 +261350,25 @@ async function launchTui(options2) {
|
|
|
261237
261350
|
if (res.ok) {
|
|
261238
261351
|
sys(
|
|
261239
261352
|
`Scaffolded ${res.path}
|
|
261240
|
-
Edit it, then /trust (project extensions need it) and /reload to load it.`
|
|
261353
|
+
Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project extensions need it) and /reload to load it.`
|
|
261241
261354
|
);
|
|
261242
261355
|
} else {
|
|
261243
261356
|
sys(`Could not scaffold: ${res.error}`, "error");
|
|
261244
261357
|
}
|
|
261245
261358
|
return;
|
|
261246
261359
|
}
|
|
261360
|
+
case "verify-extension": {
|
|
261361
|
+
if (!extensions) {
|
|
261362
|
+
sys("No extension system available.", "error");
|
|
261363
|
+
return;
|
|
261364
|
+
}
|
|
261365
|
+
if (!arg.trim()) {
|
|
261366
|
+
sys("Usage: /verify-extension <name>", "error");
|
|
261367
|
+
return;
|
|
261368
|
+
}
|
|
261369
|
+
sys(await extensions.verify(arg.trim()));
|
|
261370
|
+
return;
|
|
261371
|
+
}
|
|
261247
261372
|
case "login":
|
|
261248
261373
|
try {
|
|
261249
261374
|
const res = await actions.login((m33) => sys(m33));
|
|
@@ -261387,6 +261512,7 @@ Edit it, then /trust (project extensions need it) and /reload to load it.`
|
|
|
261387
261512
|
{ name: "sessions", description: "list sessions for this project" },
|
|
261388
261513
|
{ name: "provider", description: "switch model provider" },
|
|
261389
261514
|
{ name: "new-extension", description: "scaffold a starter extension", argumentHint: "<name>" },
|
|
261515
|
+
{ name: "verify-extension", description: "check an extension loads", argumentHint: "<name>" },
|
|
261390
261516
|
{ name: "new", description: "start a fresh session" },
|
|
261391
261517
|
{ name: "clear", description: "clear the screen" },
|
|
261392
261518
|
{ name: "exit", description: "quit" },
|
|
@@ -261506,6 +261632,9 @@ Edit it, then /trust (project extensions need it) and /reload to load it.`
|
|
|
261506
261632
|
tone: "info"
|
|
261507
261633
|
});
|
|
261508
261634
|
}
|
|
261635
|
+
if (options2.updateNotice) {
|
|
261636
|
+
push({ kind: "system", text: options2.updateNotice, tone: "info" });
|
|
261637
|
+
}
|
|
261509
261638
|
ui2.addChild(root);
|
|
261510
261639
|
ui2.setFocus(root);
|
|
261511
261640
|
ui2.start();
|
|
@@ -261539,6 +261668,7 @@ var init_tui2 = __esm({
|
|
|
261539
261668
|
"/reasoning [lvl] set reasoning effort (low|medium|high)",
|
|
261540
261669
|
"/sessions list sessions for this project",
|
|
261541
261670
|
"/new-extension <n> scaffold a starter extension in .usable/extensions/",
|
|
261671
|
+
"/verify-extension <n> check an extension loads (no tsc needed)",
|
|
261542
261672
|
"/new start a fresh session",
|
|
261543
261673
|
"/clear clear the screen",
|
|
261544
261674
|
"/exit, /quit quit (or Ctrl-C twice)",
|
|
@@ -261573,8 +261703,8 @@ process.emitWarning = function patchedEmitWarning(warning, ...rest) {
|
|
|
261573
261703
|
|
|
261574
261704
|
// src/cli/index.ts
|
|
261575
261705
|
import { parseArgs } from "node:util";
|
|
261576
|
-
import { appendFileSync as appendFileSync4, existsSync as
|
|
261577
|
-
import { basename as basename10, dirname as dirname16, join as
|
|
261706
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync13 } from "node:fs";
|
|
261707
|
+
import { basename as basename10, dirname as dirname16, join as join20 } from "node:path";
|
|
261578
261708
|
|
|
261579
261709
|
// src/lib/config.cli.ts
|
|
261580
261710
|
init_zod();
|
|
@@ -262149,7 +262279,7 @@ init_tui_select();
|
|
|
262149
262279
|
init_model_registry();
|
|
262150
262280
|
|
|
262151
262281
|
// package.json
|
|
262152
|
-
var version = "1.
|
|
262282
|
+
var version = "1.149.0";
|
|
262153
262283
|
|
|
262154
262284
|
// src/adapters/cli/model-catalog.ts
|
|
262155
262285
|
init_codex_auth();
|
|
@@ -262356,17 +262486,16 @@ function normalizeExtensionName(name14) {
|
|
|
262356
262486
|
}
|
|
262357
262487
|
function template(id) {
|
|
262358
262488
|
const cmd = id.replace(/-/g, "");
|
|
262359
|
-
return `import {
|
|
262360
|
-
import type { ExtensionAPI } from '@/extensions/api'
|
|
262489
|
+
return `import type { ExtensionAPI } from '@/extensions/api'
|
|
262361
262490
|
|
|
262362
262491
|
// ${id} \u2014 a usable-chat harness extension.
|
|
262363
|
-
//
|
|
262492
|
+
// Verify it loads with /verify-extension ${id}, then /trust (project) + /reload. /help lists commands.
|
|
262364
262493
|
export default (pi: ExtensionAPI) => {
|
|
262365
|
-
// A model-callable tool.
|
|
262494
|
+
// A model-callable tool (plain JSON-schema params \u2014 no external deps).
|
|
262366
262495
|
pi.registerTool({
|
|
262367
262496
|
name: '${cmd}_echo',
|
|
262368
262497
|
description: 'Echo back the given text (example tool from the ${id} extension).',
|
|
262369
|
-
parameters:
|
|
262498
|
+
parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
|
|
262370
262499
|
async execute({ text }) {
|
|
262371
262500
|
return { success: true, data: { echo: String(text) } }
|
|
262372
262501
|
},
|
|
@@ -262378,8 +262507,9 @@ export default (pi: ExtensionAPI) => {
|
|
|
262378
262507
|
run: (arg, ctx) => ctx.ui.notify(\`${id}: \${arg || '(no arg)'}\`),
|
|
262379
262508
|
})
|
|
262380
262509
|
|
|
262381
|
-
//
|
|
262382
|
-
// pi.on('token_usage', (e, ctx) => ctx.ui.setStatus('tok', \`\${e.totalTokens ?? 0} tok\`))
|
|
262510
|
+
// Live status-bar metrics (uncomment): token count + context % for this session.
|
|
262511
|
+
// pi.on('token_usage', (e, ctx) => ctx.ui.setStatus('${cmd}-tok', \`\${e.totalTokens ?? 0} tok\`))
|
|
262512
|
+
// pi.on('context', (e, ctx) => ctx.ui.setStatus('${cmd}-ctx', e.pct != null ? \`ctx \${Math.round(e.pct)}%\` : ''))
|
|
262383
262513
|
// pi.on('error', (e, ctx) => ctx.ui.notify(\`error: \${e.message}\`, 'error'))
|
|
262384
262514
|
// pi.setBanner([' ${id} ']) // replace the startup ASCII banner
|
|
262385
262515
|
}
|
|
@@ -262589,6 +262719,45 @@ async function loadExtensions(opts) {
|
|
|
262589
262719
|
}
|
|
262590
262720
|
return { registry: registry2, untrustedProjectDir };
|
|
262591
262721
|
}
|
|
262722
|
+
async function verifyExtension(cwd, name14) {
|
|
262723
|
+
const id = name14.replace(/^\//, "").trim();
|
|
262724
|
+
if (!id) return { ok: false, message: "usage: /verify-extension <name>" };
|
|
262725
|
+
const candidates = [];
|
|
262726
|
+
for (const base of [projectExtensionsDir(cwd), homeExtensionsDir()]) {
|
|
262727
|
+
for (const e14 of ["ts", "js", "mjs"]) candidates.push(join9(base, `${id}.${e14}`));
|
|
262728
|
+
for (const idx of ["index.ts", "index.js", "index.mjs"]) candidates.push(join9(base, id, idx));
|
|
262729
|
+
}
|
|
262730
|
+
const file2 = candidates.find((p28) => existsSync7(p28));
|
|
262731
|
+
if (!file2) {
|
|
262732
|
+
return {
|
|
262733
|
+
ok: false,
|
|
262734
|
+
message: `extension "${id}" not found in ./.usable/extensions or ~/.usable/extensions`
|
|
262735
|
+
};
|
|
262736
|
+
}
|
|
262737
|
+
const jiti = createJiti(import.meta.url, { moduleCache: false, fsCache: false });
|
|
262738
|
+
try {
|
|
262739
|
+
const mod = await jiti.import(file2);
|
|
262740
|
+
const fn4 = typeof mod === "function" ? mod : mod.default;
|
|
262741
|
+
if (typeof fn4 !== "function") {
|
|
262742
|
+
return {
|
|
262743
|
+
ok: false,
|
|
262744
|
+
message: `${basename2(file2)}: no default export \u2014 expected \`export default (pi) => { \u2026 }\``
|
|
262745
|
+
};
|
|
262746
|
+
}
|
|
262747
|
+
const registry2 = createRegistry();
|
|
262748
|
+
await fn4(recordingApi(registry2, id));
|
|
262749
|
+
const hooks = registry2.toolCall.length + registry2.toolResult.length + registry2.text.length + registry2.turnStart.length + registry2.turnEnd.length + registry2.reasoning.length + registry2.stepStart.length + registry2.stepEnd.length + registry2.tokenUsage.length + registry2.context.length + registry2.error.length + registry2.autoCompaction.length + registry2.sessionStart.length + registry2.sessionNew.length + registry2.sessionSwitch.length;
|
|
262750
|
+
return {
|
|
262751
|
+
ok: true,
|
|
262752
|
+
message: `\u2713 ${basename2(file2)} loads cleanly \u2014 ${registry2.tools.length} tool(s), ${registry2.commands.size} command(s), ${hooks} hook(s). Run /reload to activate.`
|
|
262753
|
+
};
|
|
262754
|
+
} catch (err) {
|
|
262755
|
+
return {
|
|
262756
|
+
ok: false,
|
|
262757
|
+
message: `\u2717 ${basename2(file2)} failed to load: ${err instanceof Error ? err.message : String(err)}`
|
|
262758
|
+
};
|
|
262759
|
+
}
|
|
262760
|
+
}
|
|
262592
262761
|
|
|
262593
262762
|
// src/extensions/runtime.ts
|
|
262594
262763
|
init_esm();
|
|
@@ -262698,6 +262867,153 @@ function buildExtensions(registry2, opts) {
|
|
|
262698
262867
|
};
|
|
262699
262868
|
}
|
|
262700
262869
|
|
|
262870
|
+
// src/adapters/cli/update-check.ts
|
|
262871
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
262872
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
262873
|
+
import { join as join10 } from "node:path";
|
|
262874
|
+
var PKG_NAME = "@usabledev/usable-chat";
|
|
262875
|
+
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
262876
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
262877
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
262878
|
+
function parseVersion(version4) {
|
|
262879
|
+
const m33 = version4.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
|
|
262880
|
+
if (!m33) return void 0;
|
|
262881
|
+
return { major: +m33[1], minor: +m33[2], patch: +m33[3], prerelease: m33[4] };
|
|
262882
|
+
}
|
|
262883
|
+
function compareVersions(a21, b32) {
|
|
262884
|
+
const l13 = parseVersion(a21);
|
|
262885
|
+
const r17 = parseVersion(b32);
|
|
262886
|
+
if (!l13 || !r17) return void 0;
|
|
262887
|
+
if (l13.major !== r17.major) return l13.major - r17.major;
|
|
262888
|
+
if (l13.minor !== r17.minor) return l13.minor - r17.minor;
|
|
262889
|
+
if (l13.patch !== r17.patch) return l13.patch - r17.patch;
|
|
262890
|
+
if (l13.prerelease === r17.prerelease) return 0;
|
|
262891
|
+
if (!l13.prerelease) return 1;
|
|
262892
|
+
if (!r17.prerelease) return -1;
|
|
262893
|
+
return l13.prerelease.localeCompare(r17.prerelease);
|
|
262894
|
+
}
|
|
262895
|
+
function isNewerVersion(candidate, current) {
|
|
262896
|
+
const c21 = compareVersions(candidate, current);
|
|
262897
|
+
return c21 !== void 0 ? c21 > 0 : candidate.trim() !== current.trim();
|
|
262898
|
+
}
|
|
262899
|
+
function updateCheckEnabled() {
|
|
262900
|
+
if (process.env.USABLE_SKIP_UPDATE_CHECK || process.env.NO_UPDATE_NOTIFIER) return false;
|
|
262901
|
+
if (process.env.CI) return false;
|
|
262902
|
+
if (process.env.E2E_ALLOW_TEST_SEAMS === "true") return false;
|
|
262903
|
+
return true;
|
|
262904
|
+
}
|
|
262905
|
+
function statePath(dataDir2) {
|
|
262906
|
+
return join10(dataDir2, "update-check.json");
|
|
262907
|
+
}
|
|
262908
|
+
function readState(dataDir2) {
|
|
262909
|
+
const p28 = statePath(dataDir2);
|
|
262910
|
+
if (!existsSync8(p28)) return null;
|
|
262911
|
+
try {
|
|
262912
|
+
const s17 = JSON.parse(readFileSync6(p28, "utf8"));
|
|
262913
|
+
if (typeof s17.lastCheckAt === "number" && typeof s17.latestVersion === "string") {
|
|
262914
|
+
return { lastCheckAt: s17.lastCheckAt, latestVersion: s17.latestVersion };
|
|
262915
|
+
}
|
|
262916
|
+
} catch {
|
|
262917
|
+
}
|
|
262918
|
+
return null;
|
|
262919
|
+
}
|
|
262920
|
+
function writeState(dataDir2, state2) {
|
|
262921
|
+
try {
|
|
262922
|
+
if (!existsSync8(dataDir2)) mkdirSync8(dataDir2, { recursive: true });
|
|
262923
|
+
writeFileSync7(statePath(dataDir2), JSON.stringify(state2, null, 2), { mode: 384 });
|
|
262924
|
+
} catch {
|
|
262925
|
+
}
|
|
262926
|
+
}
|
|
262927
|
+
async function fetchLatestVersion(timeoutMs = FETCH_TIMEOUT_MS) {
|
|
262928
|
+
try {
|
|
262929
|
+
const res = await fetch(REGISTRY_URL, {
|
|
262930
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
262931
|
+
headers: { accept: "application/json" }
|
|
262932
|
+
});
|
|
262933
|
+
if (!res.ok) return null;
|
|
262934
|
+
const json3 = await res.json();
|
|
262935
|
+
return typeof json3.version === "string" ? json3.version : null;
|
|
262936
|
+
} catch {
|
|
262937
|
+
return null;
|
|
262938
|
+
}
|
|
262939
|
+
}
|
|
262940
|
+
function formatUpdateNotice(current, latest) {
|
|
262941
|
+
return `\u24D8 update available ${current} \u2192 ${latest} \xB7 run \`usable-chat update\``;
|
|
262942
|
+
}
|
|
262943
|
+
function getUpdateNotice(opts) {
|
|
262944
|
+
if (!updateCheckEnabled()) return null;
|
|
262945
|
+
const state2 = readState(opts.dataDir);
|
|
262946
|
+
if (state2 && isNewerVersion(state2.latestVersion, opts.currentVersion)) {
|
|
262947
|
+
return formatUpdateNotice(opts.currentVersion, state2.latestVersion);
|
|
262948
|
+
}
|
|
262949
|
+
return null;
|
|
262950
|
+
}
|
|
262951
|
+
async function maybeRefreshUpdateState(opts) {
|
|
262952
|
+
if (!updateCheckEnabled()) return;
|
|
262953
|
+
const now2 = opts.now ?? Date.now();
|
|
262954
|
+
const state2 = readState(opts.dataDir);
|
|
262955
|
+
if (state2 && now2 - state2.lastCheckAt < CHECK_INTERVAL_MS) return;
|
|
262956
|
+
const latest = await fetchLatestVersion();
|
|
262957
|
+
writeState(opts.dataDir, {
|
|
262958
|
+
lastCheckAt: now2,
|
|
262959
|
+
latestVersion: latest ?? state2?.latestVersion ?? "0.0.0"
|
|
262960
|
+
});
|
|
262961
|
+
}
|
|
262962
|
+
function refreshUpdateStateInBackground(opts) {
|
|
262963
|
+
void maybeRefreshUpdateState(opts).catch(() => {
|
|
262964
|
+
});
|
|
262965
|
+
}
|
|
262966
|
+
function detectInstall(entry = process.argv[1] ?? "") {
|
|
262967
|
+
const p28 = entry.replace(/\\/g, "/");
|
|
262968
|
+
if (p28.includes("/_npx/")) return "npx";
|
|
262969
|
+
if (process.env.npm_config_global === "true") return "global";
|
|
262970
|
+
if (p28.includes("/lib/node_modules/") || p28.includes("/pnpm/global/") || p28.includes("/pnpm-global/"))
|
|
262971
|
+
return "global";
|
|
262972
|
+
if (p28.includes("/node_modules/")) return "local";
|
|
262973
|
+
return "unknown";
|
|
262974
|
+
}
|
|
262975
|
+
async function runUpdateCommand(opts) {
|
|
262976
|
+
const kind = opts?.kind ?? detectInstall(opts?.entry);
|
|
262977
|
+
const out = (s17) => process.stdout.write(s17 + "\n");
|
|
262978
|
+
const err = (s17) => process.stderr.write(s17 + "\n");
|
|
262979
|
+
if (kind === "npx") {
|
|
262980
|
+
out("You're running via npx, which already fetches the latest on each run.");
|
|
262981
|
+
out(`To force the newest explicitly: npx ${PKG_NAME}@latest`);
|
|
262982
|
+
return 0;
|
|
262983
|
+
}
|
|
262984
|
+
const usePnpm = (opts?.entry ?? process.argv[1] ?? "").replace(/\\/g, "/").includes("/pnpm");
|
|
262985
|
+
const cmd = usePnpm ? "pnpm" : "npm";
|
|
262986
|
+
const args = usePnpm ? ["add", "-g", `${PKG_NAME}@latest`] : ["install", "-g", `${PKG_NAME}@latest`];
|
|
262987
|
+
out(`\u21BB installing ${PKG_NAME}@latest (${cmd} ${args.join(" ")}) \u2026`);
|
|
262988
|
+
const spawn7 = opts?.spawnFn ?? nodeSpawn;
|
|
262989
|
+
return await new Promise((resolve7) => {
|
|
262990
|
+
let settled = false;
|
|
262991
|
+
const done = (code) => {
|
|
262992
|
+
if (!settled) {
|
|
262993
|
+
settled = true;
|
|
262994
|
+
resolve7(code);
|
|
262995
|
+
}
|
|
262996
|
+
};
|
|
262997
|
+
const child = spawn7(cmd, args, { stdio: "inherit", shell: process.platform === "win32" });
|
|
262998
|
+
child.on("error", (e14) => {
|
|
262999
|
+
if (e14.code === "EACCES" || e14.code === "EPERM") {
|
|
263000
|
+
err(`\u2717 permission denied. Try: sudo ${cmd} ${args.join(" ")}`);
|
|
263001
|
+
} else if (e14.code === "ENOENT") {
|
|
263002
|
+
err(`\u2717 \`${cmd}\` not found. Install it, or run: npm install -g ${PKG_NAME}@latest`);
|
|
263003
|
+
} else {
|
|
263004
|
+
err(`\u2717 update failed: ${e14.message}
|
|
263005
|
+
Run manually: ${cmd} ${args.join(" ")}`);
|
|
263006
|
+
}
|
|
263007
|
+
done(1);
|
|
263008
|
+
});
|
|
263009
|
+
child.on("exit", (code) => {
|
|
263010
|
+
if (code === 0) out(`\u2713 updated \u2014 restart usable-chat to use the new version.`);
|
|
263011
|
+
else err(`\u2717 ${cmd} exited with code ${code ?? 1}`);
|
|
263012
|
+
done(code ?? 1);
|
|
263013
|
+
});
|
|
263014
|
+
});
|
|
263015
|
+
}
|
|
263016
|
+
|
|
262701
263017
|
// src/cli/index.ts
|
|
262702
263018
|
function usableCompletionsUrl(cfg) {
|
|
262703
263019
|
const host = (cfg.ai.usableApiHost || "https://chat.usable.dev").replace(/\/+$/, "").replace(/\/api\/v1$/, "");
|
|
@@ -262756,7 +263072,7 @@ function userIdFromToken(token) {
|
|
|
262756
263072
|
function resolveGitRoot(start) {
|
|
262757
263073
|
let dir = start;
|
|
262758
263074
|
while (true) {
|
|
262759
|
-
if (
|
|
263075
|
+
if (existsSync13(join20(dir, ".git"))) return dir;
|
|
262760
263076
|
const parent = dirname16(dir);
|
|
262761
263077
|
if (parent === dir) return start;
|
|
262762
263078
|
dir = parent;
|
|
@@ -262775,6 +263091,7 @@ function printUsage() {
|
|
|
262775
263091
|
' usable-chat -c -p "<prompt>" Continue the latest session for this project',
|
|
262776
263092
|
' usable-chat --resume <id> -p "..." Resume a specific session',
|
|
262777
263093
|
" usable-chat sessions List sessions for this project",
|
|
263094
|
+
" usable-chat update Update to the latest version (npm i -g)",
|
|
262778
263095
|
"",
|
|
262779
263096
|
"Auth & keys:",
|
|
262780
263097
|
" usable-chat login Usable/MCP token (Keycloak)",
|
|
@@ -262893,7 +263210,7 @@ function readScriptedLlm() {
|
|
|
262893
263210
|
}
|
|
262894
263211
|
async function runTui(cfg, store, projectId, values2) {
|
|
262895
263212
|
applyEnv(cfg);
|
|
262896
|
-
silenceConsole(
|
|
263213
|
+
silenceConsole(join20(cfg.paths.dataDir, "usable.log"));
|
|
262897
263214
|
let session = await loadSession(cfg);
|
|
262898
263215
|
let model = values2.model ?? cfg.ai.defaultModel;
|
|
262899
263216
|
let reasoning = cfg.ai.reasoningEffort;
|
|
@@ -262922,7 +263239,7 @@ async function runTui(cfg, store, projectId, values2) {
|
|
|
262922
263239
|
const scriptedLlm = readScriptedLlm();
|
|
262923
263240
|
const { runCliTurn: runCliTurn2 } = await Promise.resolve().then(() => (init_run(), run_exports));
|
|
262924
263241
|
const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui2(), tui_exports));
|
|
262925
|
-
const extLogFile =
|
|
263242
|
+
const extLogFile = join20(cfg.paths.dataDir, "usable.log");
|
|
262926
263243
|
const extLog = (m33) => {
|
|
262927
263244
|
try {
|
|
262928
263245
|
appendFileSync4(extLogFile, `[ext] ${m33}
|
|
@@ -263025,6 +263342,11 @@ async function runTui(cfg, store, projectId, values2) {
|
|
|
263025
263342
|
kind: m33.role === "assistant" ? "assistant" : "user",
|
|
263026
263343
|
text: typeof m33.content === "string" ? m33.content : JSON.stringify(m33.content)
|
|
263027
263344
|
}));
|
|
263345
|
+
const updateNotice = getUpdateNotice({
|
|
263346
|
+
currentVersion: version,
|
|
263347
|
+
dataDir: cfg.paths.dataDir
|
|
263348
|
+
});
|
|
263349
|
+
refreshUpdateStateInBackground({ dataDir: cfg.paths.dataDir });
|
|
263028
263350
|
await launchTui2({
|
|
263029
263351
|
runTurn,
|
|
263030
263352
|
actions,
|
|
@@ -263032,6 +263354,7 @@ async function runTui(cfg, store, projectId, values2) {
|
|
|
263032
263354
|
loggedIn: Boolean(session.user?.accessToken),
|
|
263033
263355
|
user: session.user?.id,
|
|
263034
263356
|
version,
|
|
263357
|
+
updateNotice: updateNotice ?? void 0,
|
|
263035
263358
|
extensions: {
|
|
263036
263359
|
hasCommand: (n30) => ext2.current.hasCommand(n30),
|
|
263037
263360
|
runCommand: (n30, a21) => ext2.current.runCommand(n30, a21),
|
|
@@ -263044,6 +263367,7 @@ async function runTui(cfg, store, projectId, values2) {
|
|
|
263044
263367
|
clearSkillCache2();
|
|
263045
263368
|
await ext2.reload();
|
|
263046
263369
|
},
|
|
263370
|
+
verify: (name14) => verifyExtension(process.cwd(), name14).then((r17) => r17.message),
|
|
263047
263371
|
trust: () => ext2.trust(),
|
|
263048
263372
|
bindNotify: (fn4) => {
|
|
263049
263373
|
extUi.notify = fn4;
|
|
@@ -263085,6 +263409,9 @@ async function main() {
|
|
|
263085
263409
|
if (positionals[0] === "config") {
|
|
263086
263410
|
return runConfigCommand(cfg.paths.configFile, positionals.slice(1));
|
|
263087
263411
|
}
|
|
263412
|
+
if (positionals[0] === "update") {
|
|
263413
|
+
return runUpdateCommand();
|
|
263414
|
+
}
|
|
263088
263415
|
const store = new CliStore(cfg.paths.dbFile);
|
|
263089
263416
|
const gitRoot2 = resolveGitRoot(process.cwd());
|
|
263090
263417
|
const projectId = store.resolveProject(gitRoot2, basename10(gitRoot2));
|
|
@@ -263196,6 +263523,14 @@ async function main() {
|
|
|
263196
263523
|
message: { role: "assistant", content: result.text }
|
|
263197
263524
|
});
|
|
263198
263525
|
}
|
|
263526
|
+
await maybeRefreshUpdateState({ dataDir: cfg.paths.dataDir });
|
|
263527
|
+
const updateNotice = getUpdateNotice({
|
|
263528
|
+
currentVersion: version,
|
|
263529
|
+
dataDir: cfg.paths.dataDir
|
|
263530
|
+
});
|
|
263531
|
+
if (updateNotice) process.stderr.write(`
|
|
263532
|
+
${updateNotice}
|
|
263533
|
+
`);
|
|
263199
263534
|
store.close();
|
|
263200
263535
|
return 0;
|
|
263201
263536
|
}
|