@usabledev/usable-chat 1.147.0 → 1.148.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 +394 -71
- 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) {
|
|
@@ -215465,14 +215463,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
215465
215463
|
} else if (node.nodeType === 1) {
|
|
215466
215464
|
replacement = replacementForNode.call(self2, node);
|
|
215467
215465
|
}
|
|
215468
|
-
return
|
|
215466
|
+
return join21(output, replacement);
|
|
215469
215467
|
}, "");
|
|
215470
215468
|
}
|
|
215471
215469
|
function postProcess(output) {
|
|
215472
215470
|
var self2 = this;
|
|
215473
215471
|
this.rules.forEach(function(rule) {
|
|
215474
215472
|
if (typeof rule.append === "function") {
|
|
215475
|
-
output =
|
|
215473
|
+
output = join21(output, rule.append(self2.options));
|
|
215476
215474
|
}
|
|
215477
215475
|
});
|
|
215478
215476
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -215484,7 +215482,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
215484
215482
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
215485
215483
|
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
|
|
215486
215484
|
}
|
|
215487
|
-
function
|
|
215485
|
+
function join21(output, replacement) {
|
|
215488
215486
|
var s17 = trimTrailingNewlines(output);
|
|
215489
215487
|
var s22 = trimLeadingNewlines(replacement);
|
|
215490
215488
|
var nls = Math.max(output.length - s17.length, replacement.length - s22.length);
|
|
@@ -244534,12 +244532,12 @@ var init_session_pathway = __esm({
|
|
|
244534
244532
|
});
|
|
244535
244533
|
|
|
244536
244534
|
// src/adapters/cli/prompt-files.ts
|
|
244537
|
-
import { existsSync as
|
|
244538
|
-
import { dirname as dirname14, join as
|
|
244535
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, realpathSync as realpathSync4 } from "node:fs";
|
|
244536
|
+
import { dirname as dirname14, join as join16 } from "node:path";
|
|
244539
244537
|
function readFile(path11) {
|
|
244540
244538
|
try {
|
|
244541
|
-
if (!
|
|
244542
|
-
const content =
|
|
244539
|
+
if (!existsSync10(path11)) return void 0;
|
|
244540
|
+
const content = readFileSync7(path11, "utf8").trim();
|
|
244543
244541
|
return content || void 0;
|
|
244544
244542
|
} catch {
|
|
244545
244543
|
return void 0;
|
|
@@ -244548,7 +244546,7 @@ function readFile(path11) {
|
|
|
244548
244546
|
function gitRoot(start) {
|
|
244549
244547
|
let dir = start;
|
|
244550
244548
|
while (true) {
|
|
244551
|
-
if (
|
|
244549
|
+
if (existsSync10(join16(dir, ".git"))) return dir;
|
|
244552
244550
|
const parent = dirname14(dir);
|
|
244553
244551
|
if (parent === dir) return start;
|
|
244554
244552
|
dir = parent;
|
|
@@ -244566,16 +244564,16 @@ function projectChain(cwd) {
|
|
|
244566
244564
|
return chain;
|
|
244567
244565
|
}
|
|
244568
244566
|
function resolvePromptFiles(cwd, dataDir2) {
|
|
244569
|
-
const override = readFile(
|
|
244567
|
+
const override = readFile(join16(cwd, ".usable", "SYSTEM.md")) ?? readFile(join16(dataDir2, "SYSTEM.md"));
|
|
244570
244568
|
const appends = [
|
|
244571
|
-
readFile(
|
|
244572
|
-
readFile(
|
|
244569
|
+
readFile(join16(dataDir2, "SYSTEM.append.md")),
|
|
244570
|
+
readFile(join16(cwd, ".usable", "SYSTEM.append.md"))
|
|
244573
244571
|
].filter((s17) => Boolean(s17));
|
|
244574
244572
|
const append = appends.length ? appends.join("\n\n") : void 0;
|
|
244575
244573
|
const candidates = [];
|
|
244576
|
-
for (const name14 of CONTEXT_FILE_NAMES) candidates.push(
|
|
244574
|
+
for (const name14 of CONTEXT_FILE_NAMES) candidates.push(join16(dataDir2, name14));
|
|
244577
244575
|
for (const dir of projectChain(cwd)) {
|
|
244578
|
-
for (const name14 of CONTEXT_FILE_NAMES) candidates.push(
|
|
244576
|
+
for (const name14 of CONTEXT_FILE_NAMES) candidates.push(join16(dir, name14));
|
|
244579
244577
|
}
|
|
244580
244578
|
const seen = /* @__PURE__ */ new Set();
|
|
244581
244579
|
const contextFiles = [];
|
|
@@ -245321,12 +245319,12 @@ var init_stdio2 = __esm({
|
|
|
245321
245319
|
});
|
|
245322
245320
|
|
|
245323
245321
|
// src/adapters/cli/mcp-servers.ts
|
|
245324
|
-
import { existsSync as
|
|
245325
|
-
import { join as
|
|
245322
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
245323
|
+
import { join as join17 } from "node:path";
|
|
245326
245324
|
function readConfig(path11) {
|
|
245327
245325
|
try {
|
|
245328
|
-
if (!
|
|
245329
|
-
const parsed = configSchema.safeParse(JSON.parse(
|
|
245326
|
+
if (!existsSync11(path11)) return {};
|
|
245327
|
+
const parsed = configSchema.safeParse(JSON.parse(readFileSync8(path11, "utf8")));
|
|
245330
245328
|
if (!parsed.success || !parsed.data.mcpServers) return {};
|
|
245331
245329
|
const out = {};
|
|
245332
245330
|
for (const [name14, spec] of Object.entries(parsed.data.mcpServers)) out[name14] = { name: name14, ...spec };
|
|
@@ -245337,9 +245335,9 @@ function readConfig(path11) {
|
|
|
245337
245335
|
}
|
|
245338
245336
|
function loadMcpServerSpecs(cwd, dataDir2) {
|
|
245339
245337
|
const merged = {
|
|
245340
|
-
...readConfig(
|
|
245341
|
-
...readConfig(
|
|
245342
|
-
...readConfig(
|
|
245338
|
+
...readConfig(join17(dataDir2, "mcp.json")),
|
|
245339
|
+
...readConfig(join17(cwd, ".mcp.json")),
|
|
245340
|
+
...readConfig(join17(cwd, ".usable", "mcp.json"))
|
|
245343
245341
|
};
|
|
245344
245342
|
return Object.values(merged).filter((s17) => !s17.disabled && (s17.command || s17.url));
|
|
245345
245343
|
}
|
|
@@ -245759,8 +245757,8 @@ var init_usable_api = __esm({
|
|
|
245759
245757
|
});
|
|
245760
245758
|
|
|
245761
245759
|
// src/adapters/cli/media-tools.ts
|
|
245762
|
-
import { writeFileSync as
|
|
245763
|
-
import { join as
|
|
245760
|
+
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
245761
|
+
import { join as join18 } from "node:path";
|
|
245764
245762
|
function stamp() {
|
|
245765
245763
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
245766
245764
|
}
|
|
@@ -245800,8 +245798,8 @@ function buildMediaTools(opts) {
|
|
|
245800
245798
|
const b64 = resp.data?.[0]?.b64_json;
|
|
245801
245799
|
if (!b64) return { error: "no image returned" };
|
|
245802
245800
|
const buf = Buffer.from(b64, "base64");
|
|
245803
|
-
const file2 =
|
|
245804
|
-
|
|
245801
|
+
const file2 = join18(outDir ?? process.cwd(), `usable-image-${stamp()}.png`);
|
|
245802
|
+
writeFileSync8(file2, buf);
|
|
245805
245803
|
return { path: file2, bytes: buf.length };
|
|
245806
245804
|
} catch (err) {
|
|
245807
245805
|
return { error: err.message || "image generation failed" };
|
|
@@ -245920,7 +245918,9 @@ Notes:
|
|
|
245920
245918
|
|
|
245921
245919
|
## Minimal example
|
|
245922
245920
|
\`\`\`ts
|
|
245923
|
-
import
|
|
245921
|
+
// Type-only import (erased at runtime) \u2014 keep the file dependency-free so it
|
|
245922
|
+
// loads anywhere. Use plain JSON-schema for \`parameters\`; a zod schema also
|
|
245923
|
+
// works, but only if your project actually has zod installed on disk.
|
|
245924
245924
|
import type { ExtensionAPI } from '@/extensions/api'
|
|
245925
245925
|
|
|
245926
245926
|
export default (pi: ExtensionAPI) => {
|
|
@@ -245928,7 +245928,7 @@ export default (pi: ExtensionAPI) => {
|
|
|
245928
245928
|
pi.registerTool({
|
|
245929
245929
|
name: 'word_count',
|
|
245930
245930
|
description: 'Count the words in a string.',
|
|
245931
|
-
parameters:
|
|
245931
|
+
parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
|
|
245932
245932
|
async execute({ text }) {
|
|
245933
245933
|
return { success: true, data: { words: String(text).trim().split(/\\s+/).filter(Boolean).length } }
|
|
245934
245934
|
},
|
|
@@ -245953,20 +245953,113 @@ export default (pi: ExtensionAPI) => {
|
|
|
245953
245953
|
}
|
|
245954
245954
|
\`\`\`
|
|
245955
245955
|
|
|
245956
|
-
|
|
245956
|
+
## Verifying it loads
|
|
245957
|
+
- 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.
|
|
245958
|
+
- Then \`/trust\` (project only) + \`/reload\` \u2014 or restart. Confirm with \`/help\` (commands) and by asking the model to call your tool.`;
|
|
245959
|
+
}
|
|
245960
|
+
});
|
|
245961
|
+
|
|
245962
|
+
// src/adapters/cli/diff.ts
|
|
245963
|
+
function unifiedDiff(oldStr, newStr, opts = {}) {
|
|
245964
|
+
if (oldStr === newStr) return "";
|
|
245965
|
+
const context = opts.context ?? 3;
|
|
245966
|
+
const maxLines = opts.maxLines ?? 80;
|
|
245967
|
+
const a21 = oldStr === "" ? [] : oldStr.split("\n");
|
|
245968
|
+
const b32 = newStr === "" ? [] : newStr.split("\n");
|
|
245969
|
+
const n30 = a21.length;
|
|
245970
|
+
const m33 = b32.length;
|
|
245971
|
+
if (n30 * m33 > 4e6) return `(diff omitted \u2014 ${n30}\u2192${m33} lines too large to render)`;
|
|
245972
|
+
const lcs = Array.from({ length: n30 + 1 }, () => new Array(m33 + 1).fill(0));
|
|
245973
|
+
for (let i19 = n30 - 1; i19 >= 0; i19--) {
|
|
245974
|
+
for (let j21 = m33 - 1; j21 >= 0; j21--) {
|
|
245975
|
+
lcs[i19][j21] = a21[i19] === b32[j21] ? lcs[i19 + 1][j21 + 1] + 1 : Math.max(lcs[i19 + 1][j21], lcs[i19][j21 + 1]);
|
|
245976
|
+
}
|
|
245977
|
+
}
|
|
245978
|
+
const ops = [];
|
|
245979
|
+
let i18 = 0;
|
|
245980
|
+
let j20 = 0;
|
|
245981
|
+
while (i18 < n30 && j20 < m33) {
|
|
245982
|
+
if (a21[i18] === b32[j20]) {
|
|
245983
|
+
ops.push({ t: " ", line: a21[i18] });
|
|
245984
|
+
i18++;
|
|
245985
|
+
j20++;
|
|
245986
|
+
} else if (lcs[i18 + 1][j20] >= lcs[i18][j20 + 1]) {
|
|
245987
|
+
ops.push({ t: "-", line: a21[i18] });
|
|
245988
|
+
i18++;
|
|
245989
|
+
} else {
|
|
245990
|
+
ops.push({ t: "+", line: b32[j20] });
|
|
245991
|
+
j20++;
|
|
245992
|
+
}
|
|
245993
|
+
}
|
|
245994
|
+
while (i18 < n30) ops.push({ t: "-", line: a21[i18++] });
|
|
245995
|
+
while (j20 < m33) ops.push({ t: "+", line: b32[j20++] });
|
|
245996
|
+
const keep = new Array(ops.length).fill(false);
|
|
245997
|
+
ops.forEach((op2, idx) => {
|
|
245998
|
+
if (op2.t === " ") return;
|
|
245999
|
+
for (let k23 = Math.max(0, idx - context); k23 <= Math.min(ops.length - 1, idx + context); k23++) {
|
|
246000
|
+
keep[k23] = true;
|
|
246001
|
+
}
|
|
246002
|
+
});
|
|
246003
|
+
const out = [];
|
|
246004
|
+
let oldNo = 1;
|
|
246005
|
+
let newNo = 1;
|
|
246006
|
+
let inHunk = false;
|
|
246007
|
+
let hunk = [];
|
|
246008
|
+
let hunkOldStart = 1;
|
|
246009
|
+
let hunkNewStart = 1;
|
|
246010
|
+
const flush = () => {
|
|
246011
|
+
if (hunk.length) {
|
|
246012
|
+
out.push(`@@ -${hunkOldStart} +${hunkNewStart} @@`);
|
|
246013
|
+
out.push(...hunk);
|
|
246014
|
+
hunk = [];
|
|
246015
|
+
}
|
|
246016
|
+
inHunk = false;
|
|
246017
|
+
};
|
|
246018
|
+
for (let idx = 0; idx < ops.length; idx++) {
|
|
246019
|
+
const op2 = ops[idx];
|
|
246020
|
+
if (keep[idx]) {
|
|
246021
|
+
if (!inHunk) {
|
|
246022
|
+
inHunk = true;
|
|
246023
|
+
hunkOldStart = oldNo;
|
|
246024
|
+
hunkNewStart = newNo;
|
|
246025
|
+
}
|
|
246026
|
+
hunk.push(`${op2.t}${op2.line}`);
|
|
246027
|
+
} else if (inHunk) {
|
|
246028
|
+
flush();
|
|
246029
|
+
}
|
|
246030
|
+
if (op2.t === " ") {
|
|
246031
|
+
oldNo++;
|
|
246032
|
+
newNo++;
|
|
246033
|
+
} else if (op2.t === "-") {
|
|
246034
|
+
oldNo++;
|
|
246035
|
+
} else {
|
|
246036
|
+
newNo++;
|
|
246037
|
+
}
|
|
246038
|
+
}
|
|
246039
|
+
flush();
|
|
246040
|
+
if (out.length > maxLines) {
|
|
246041
|
+
const kept = out.slice(0, maxLines);
|
|
246042
|
+
kept.push(`\u2026 (${out.length - maxLines} more diff lines)`);
|
|
246043
|
+
return kept.join("\n");
|
|
246044
|
+
}
|
|
246045
|
+
return out.join("\n");
|
|
246046
|
+
}
|
|
246047
|
+
var init_diff = __esm({
|
|
246048
|
+
"src/adapters/cli/diff.ts"() {
|
|
246049
|
+
"use strict";
|
|
245957
246050
|
}
|
|
245958
246051
|
});
|
|
245959
246052
|
|
|
245960
246053
|
// src/adapters/cli/tools.ts
|
|
245961
246054
|
import { spawn as spawn6 } from "node:child_process";
|
|
245962
|
-
import { existsSync as
|
|
246055
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "node:fs";
|
|
245963
246056
|
import { tmpdir } from "node:os";
|
|
245964
|
-
import { dirname as dirname15, isAbsolute as isAbsolute4, join as
|
|
246057
|
+
import { dirname as dirname15, isAbsolute as isAbsolute4, join as join19, relative as relative2, resolve as resolve6, sep as sep2 } from "node:path";
|
|
245965
246058
|
function truncate2(text2, label) {
|
|
245966
246059
|
if (text2.length <= MAX_OUTPUT_CHARS) return text2;
|
|
245967
|
-
const file2 =
|
|
246060
|
+
const file2 = join19(tmpdir(), `usable-${label}-${Date.now()}.txt`);
|
|
245968
246061
|
try {
|
|
245969
|
-
|
|
246062
|
+
writeFileSync9(file2, text2);
|
|
245970
246063
|
} catch {
|
|
245971
246064
|
}
|
|
245972
246065
|
return text2.slice(0, MAX_OUTPUT_CHARS) + `
|
|
@@ -246090,9 +246183,9 @@ function createLocalTools(opts = {}) {
|
|
|
246090
246183
|
const guarded = guardPath(parsed.data.path);
|
|
246091
246184
|
if (!guarded.ok) return { error: guarded.error };
|
|
246092
246185
|
const path11 = guarded.path;
|
|
246093
|
-
if (!
|
|
246186
|
+
if (!existsSync12(path11)) return { error: `No such file: ${rel(path11)}` };
|
|
246094
246187
|
try {
|
|
246095
|
-
const lines =
|
|
246188
|
+
const lines = readFileSync9(path11, "utf8").split("\n");
|
|
246096
246189
|
const offset = Math.max(1, parsed.data.offset ?? 1);
|
|
246097
246190
|
const limit = Math.max(1, parsed.data.limit ?? MAX_READ_LINES);
|
|
246098
246191
|
const slice = lines.slice(offset - 1, offset - 1 + limit);
|
|
@@ -246124,10 +246217,14 @@ function createLocalTools(opts = {}) {
|
|
|
246124
246217
|
const path11 = guarded.path;
|
|
246125
246218
|
try {
|
|
246126
246219
|
const dir = dirname15(path11);
|
|
246127
|
-
|
|
246128
|
-
|
|
246220
|
+
const before = existsSync12(path11) ? readFileSync9(path11, "utf8") : "";
|
|
246221
|
+
if (!existsSync12(dir)) mkdirSync10(dir, { recursive: true });
|
|
246222
|
+
writeFileSync9(path11, parsed.data.content);
|
|
246129
246223
|
readFiles.add(path11);
|
|
246130
|
-
|
|
246224
|
+
const verb = before ? "Overwrote" : "Wrote";
|
|
246225
|
+
const diff = unifiedDiff(before, parsed.data.content, { maxLines: 60 });
|
|
246226
|
+
return `${verb} ${rel(path11)} (${parsed.data.content.length} bytes)` + (diff ? `
|
|
246227
|
+
${diff}` : "");
|
|
246131
246228
|
} catch (e14) {
|
|
246132
246229
|
return { error: e14 instanceof Error ? e14.message : String(e14) };
|
|
246133
246230
|
}
|
|
@@ -246157,11 +246254,12 @@ function createLocalTools(opts = {}) {
|
|
|
246157
246254
|
const guarded = guardPath(parsed.data.path);
|
|
246158
246255
|
if (!guarded.ok) return { error: guarded.error };
|
|
246159
246256
|
const path11 = guarded.path;
|
|
246160
|
-
if (!
|
|
246257
|
+
if (!existsSync12(path11)) return { error: `No such file: ${rel(path11)}` };
|
|
246161
246258
|
if (!readFiles.has(path11)) return { error: `Read ${rel(path11)} before editing it.` };
|
|
246162
246259
|
const edits = parsed.data.edits;
|
|
246163
246260
|
try {
|
|
246164
|
-
|
|
246261
|
+
const original2 = readFileSync9(path11, "utf8");
|
|
246262
|
+
let content = original2;
|
|
246165
246263
|
for (const [i18, edit2] of edits.entries()) {
|
|
246166
246264
|
const { oldText, newText } = edit2;
|
|
246167
246265
|
let count2 = content.split(oldText).length - 1;
|
|
@@ -246179,8 +246277,10 @@ function createLocalTools(opts = {}) {
|
|
|
246179
246277
|
if (count2 > 1) return { error: `edit ${i18 + 1}: oldText appears ${count2}\xD7 \u2014 add more context to make it unique` };
|
|
246180
246278
|
content = content.replace(target, () => newText);
|
|
246181
246279
|
}
|
|
246182
|
-
|
|
246183
|
-
|
|
246280
|
+
writeFileSync9(path11, content);
|
|
246281
|
+
const diff = unifiedDiff(original2, content, { maxLines: 60 });
|
|
246282
|
+
return `Applied ${edits.length} edit(s) to ${rel(path11)}` + (diff ? `
|
|
246283
|
+
${diff}` : "");
|
|
246184
246284
|
} catch (e14) {
|
|
246185
246285
|
return { error: e14 instanceof Error ? e14.message : String(e14) };
|
|
246186
246286
|
}
|
|
@@ -246331,6 +246431,7 @@ var init_tools = __esm({
|
|
|
246331
246431
|
"use strict";
|
|
246332
246432
|
init_zod();
|
|
246333
246433
|
init_extension_authoring();
|
|
246434
|
+
init_diff();
|
|
246334
246435
|
MAX_READ_LINES = 2e3;
|
|
246335
246436
|
MAX_OUTPUT_CHARS = 3e4;
|
|
246336
246437
|
DEFAULT_BASH_TIMEOUT_S = 120;
|
|
@@ -261237,13 +261338,25 @@ async function launchTui(options2) {
|
|
|
261237
261338
|
if (res.ok) {
|
|
261238
261339
|
sys(
|
|
261239
261340
|
`Scaffolded ${res.path}
|
|
261240
|
-
Edit it, then /trust (project extensions need it) and /reload to load it.`
|
|
261341
|
+
Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project extensions need it) and /reload to load it.`
|
|
261241
261342
|
);
|
|
261242
261343
|
} else {
|
|
261243
261344
|
sys(`Could not scaffold: ${res.error}`, "error");
|
|
261244
261345
|
}
|
|
261245
261346
|
return;
|
|
261246
261347
|
}
|
|
261348
|
+
case "verify-extension": {
|
|
261349
|
+
if (!extensions) {
|
|
261350
|
+
sys("No extension system available.", "error");
|
|
261351
|
+
return;
|
|
261352
|
+
}
|
|
261353
|
+
if (!arg.trim()) {
|
|
261354
|
+
sys("Usage: /verify-extension <name>", "error");
|
|
261355
|
+
return;
|
|
261356
|
+
}
|
|
261357
|
+
sys(await extensions.verify(arg.trim()));
|
|
261358
|
+
return;
|
|
261359
|
+
}
|
|
261247
261360
|
case "login":
|
|
261248
261361
|
try {
|
|
261249
261362
|
const res = await actions.login((m33) => sys(m33));
|
|
@@ -261387,6 +261500,7 @@ Edit it, then /trust (project extensions need it) and /reload to load it.`
|
|
|
261387
261500
|
{ name: "sessions", description: "list sessions for this project" },
|
|
261388
261501
|
{ name: "provider", description: "switch model provider" },
|
|
261389
261502
|
{ name: "new-extension", description: "scaffold a starter extension", argumentHint: "<name>" },
|
|
261503
|
+
{ name: "verify-extension", description: "check an extension loads", argumentHint: "<name>" },
|
|
261390
261504
|
{ name: "new", description: "start a fresh session" },
|
|
261391
261505
|
{ name: "clear", description: "clear the screen" },
|
|
261392
261506
|
{ name: "exit", description: "quit" },
|
|
@@ -261506,6 +261620,9 @@ Edit it, then /trust (project extensions need it) and /reload to load it.`
|
|
|
261506
261620
|
tone: "info"
|
|
261507
261621
|
});
|
|
261508
261622
|
}
|
|
261623
|
+
if (options2.updateNotice) {
|
|
261624
|
+
push({ kind: "system", text: options2.updateNotice, tone: "info" });
|
|
261625
|
+
}
|
|
261509
261626
|
ui2.addChild(root);
|
|
261510
261627
|
ui2.setFocus(root);
|
|
261511
261628
|
ui2.start();
|
|
@@ -261539,6 +261656,7 @@ var init_tui2 = __esm({
|
|
|
261539
261656
|
"/reasoning [lvl] set reasoning effort (low|medium|high)",
|
|
261540
261657
|
"/sessions list sessions for this project",
|
|
261541
261658
|
"/new-extension <n> scaffold a starter extension in .usable/extensions/",
|
|
261659
|
+
"/verify-extension <n> check an extension loads (no tsc needed)",
|
|
261542
261660
|
"/new start a fresh session",
|
|
261543
261661
|
"/clear clear the screen",
|
|
261544
261662
|
"/exit, /quit quit (or Ctrl-C twice)",
|
|
@@ -261573,8 +261691,8 @@ process.emitWarning = function patchedEmitWarning(warning, ...rest) {
|
|
|
261573
261691
|
|
|
261574
261692
|
// src/cli/index.ts
|
|
261575
261693
|
import { parseArgs } from "node:util";
|
|
261576
|
-
import { appendFileSync as appendFileSync4, existsSync as
|
|
261577
|
-
import { basename as basename10, dirname as dirname16, join as
|
|
261694
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync13 } from "node:fs";
|
|
261695
|
+
import { basename as basename10, dirname as dirname16, join as join20 } from "node:path";
|
|
261578
261696
|
|
|
261579
261697
|
// src/lib/config.cli.ts
|
|
261580
261698
|
init_zod();
|
|
@@ -262149,7 +262267,7 @@ init_tui_select();
|
|
|
262149
262267
|
init_model_registry();
|
|
262150
262268
|
|
|
262151
262269
|
// package.json
|
|
262152
|
-
var version = "1.
|
|
262270
|
+
var version = "1.148.0";
|
|
262153
262271
|
|
|
262154
262272
|
// src/adapters/cli/model-catalog.ts
|
|
262155
262273
|
init_codex_auth();
|
|
@@ -262356,17 +262474,16 @@ function normalizeExtensionName(name14) {
|
|
|
262356
262474
|
}
|
|
262357
262475
|
function template(id) {
|
|
262358
262476
|
const cmd = id.replace(/-/g, "");
|
|
262359
|
-
return `import {
|
|
262360
|
-
import type { ExtensionAPI } from '@/extensions/api'
|
|
262477
|
+
return `import type { ExtensionAPI } from '@/extensions/api'
|
|
262361
262478
|
|
|
262362
262479
|
// ${id} \u2014 a usable-chat harness extension.
|
|
262363
|
-
//
|
|
262480
|
+
// Verify it loads with /verify-extension ${id}, then /trust (project) + /reload. /help lists commands.
|
|
262364
262481
|
export default (pi: ExtensionAPI) => {
|
|
262365
|
-
// A model-callable tool.
|
|
262482
|
+
// A model-callable tool (plain JSON-schema params \u2014 no external deps).
|
|
262366
262483
|
pi.registerTool({
|
|
262367
262484
|
name: '${cmd}_echo',
|
|
262368
262485
|
description: 'Echo back the given text (example tool from the ${id} extension).',
|
|
262369
|
-
parameters:
|
|
262486
|
+
parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
|
|
262370
262487
|
async execute({ text }) {
|
|
262371
262488
|
return { success: true, data: { echo: String(text) } }
|
|
262372
262489
|
},
|
|
@@ -262378,8 +262495,9 @@ export default (pi: ExtensionAPI) => {
|
|
|
262378
262495
|
run: (arg, ctx) => ctx.ui.notify(\`${id}: \${arg || '(no arg)'}\`),
|
|
262379
262496
|
})
|
|
262380
262497
|
|
|
262381
|
-
//
|
|
262382
|
-
// pi.on('token_usage', (e, ctx) => ctx.ui.setStatus('tok', \`\${e.totalTokens ?? 0} tok\`))
|
|
262498
|
+
// Live status-bar metrics (uncomment): token count + context % for this session.
|
|
262499
|
+
// pi.on('token_usage', (e, ctx) => ctx.ui.setStatus('${cmd}-tok', \`\${e.totalTokens ?? 0} tok\`))
|
|
262500
|
+
// pi.on('context', (e, ctx) => ctx.ui.setStatus('${cmd}-ctx', e.pct != null ? \`ctx \${Math.round(e.pct)}%\` : ''))
|
|
262383
262501
|
// pi.on('error', (e, ctx) => ctx.ui.notify(\`error: \${e.message}\`, 'error'))
|
|
262384
262502
|
// pi.setBanner([' ${id} ']) // replace the startup ASCII banner
|
|
262385
262503
|
}
|
|
@@ -262589,6 +262707,45 @@ async function loadExtensions(opts) {
|
|
|
262589
262707
|
}
|
|
262590
262708
|
return { registry: registry2, untrustedProjectDir };
|
|
262591
262709
|
}
|
|
262710
|
+
async function verifyExtension(cwd, name14) {
|
|
262711
|
+
const id = name14.replace(/^\//, "").trim();
|
|
262712
|
+
if (!id) return { ok: false, message: "usage: /verify-extension <name>" };
|
|
262713
|
+
const candidates = [];
|
|
262714
|
+
for (const base of [projectExtensionsDir(cwd), homeExtensionsDir()]) {
|
|
262715
|
+
for (const e14 of ["ts", "js", "mjs"]) candidates.push(join9(base, `${id}.${e14}`));
|
|
262716
|
+
for (const idx of ["index.ts", "index.js", "index.mjs"]) candidates.push(join9(base, id, idx));
|
|
262717
|
+
}
|
|
262718
|
+
const file2 = candidates.find((p28) => existsSync7(p28));
|
|
262719
|
+
if (!file2) {
|
|
262720
|
+
return {
|
|
262721
|
+
ok: false,
|
|
262722
|
+
message: `extension "${id}" not found in ./.usable/extensions or ~/.usable/extensions`
|
|
262723
|
+
};
|
|
262724
|
+
}
|
|
262725
|
+
const jiti = createJiti(import.meta.url, { moduleCache: false, fsCache: false });
|
|
262726
|
+
try {
|
|
262727
|
+
const mod = await jiti.import(file2);
|
|
262728
|
+
const fn4 = typeof mod === "function" ? mod : mod.default;
|
|
262729
|
+
if (typeof fn4 !== "function") {
|
|
262730
|
+
return {
|
|
262731
|
+
ok: false,
|
|
262732
|
+
message: `${basename2(file2)}: no default export \u2014 expected \`export default (pi) => { \u2026 }\``
|
|
262733
|
+
};
|
|
262734
|
+
}
|
|
262735
|
+
const registry2 = createRegistry();
|
|
262736
|
+
await fn4(recordingApi(registry2, id));
|
|
262737
|
+
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;
|
|
262738
|
+
return {
|
|
262739
|
+
ok: true,
|
|
262740
|
+
message: `\u2713 ${basename2(file2)} loads cleanly \u2014 ${registry2.tools.length} tool(s), ${registry2.commands.size} command(s), ${hooks} hook(s). Run /reload to activate.`
|
|
262741
|
+
};
|
|
262742
|
+
} catch (err) {
|
|
262743
|
+
return {
|
|
262744
|
+
ok: false,
|
|
262745
|
+
message: `\u2717 ${basename2(file2)} failed to load: ${err instanceof Error ? err.message : String(err)}`
|
|
262746
|
+
};
|
|
262747
|
+
}
|
|
262748
|
+
}
|
|
262592
262749
|
|
|
262593
262750
|
// src/extensions/runtime.ts
|
|
262594
262751
|
init_esm();
|
|
@@ -262698,6 +262855,153 @@ function buildExtensions(registry2, opts) {
|
|
|
262698
262855
|
};
|
|
262699
262856
|
}
|
|
262700
262857
|
|
|
262858
|
+
// src/adapters/cli/update-check.ts
|
|
262859
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
262860
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
262861
|
+
import { join as join10 } from "node:path";
|
|
262862
|
+
var PKG_NAME = "@usabledev/usable-chat";
|
|
262863
|
+
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
262864
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
262865
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
262866
|
+
function parseVersion(version4) {
|
|
262867
|
+
const m33 = version4.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/);
|
|
262868
|
+
if (!m33) return void 0;
|
|
262869
|
+
return { major: +m33[1], minor: +m33[2], patch: +m33[3], prerelease: m33[4] };
|
|
262870
|
+
}
|
|
262871
|
+
function compareVersions(a21, b32) {
|
|
262872
|
+
const l13 = parseVersion(a21);
|
|
262873
|
+
const r17 = parseVersion(b32);
|
|
262874
|
+
if (!l13 || !r17) return void 0;
|
|
262875
|
+
if (l13.major !== r17.major) return l13.major - r17.major;
|
|
262876
|
+
if (l13.minor !== r17.minor) return l13.minor - r17.minor;
|
|
262877
|
+
if (l13.patch !== r17.patch) return l13.patch - r17.patch;
|
|
262878
|
+
if (l13.prerelease === r17.prerelease) return 0;
|
|
262879
|
+
if (!l13.prerelease) return 1;
|
|
262880
|
+
if (!r17.prerelease) return -1;
|
|
262881
|
+
return l13.prerelease.localeCompare(r17.prerelease);
|
|
262882
|
+
}
|
|
262883
|
+
function isNewerVersion(candidate, current) {
|
|
262884
|
+
const c21 = compareVersions(candidate, current);
|
|
262885
|
+
return c21 !== void 0 ? c21 > 0 : candidate.trim() !== current.trim();
|
|
262886
|
+
}
|
|
262887
|
+
function updateCheckEnabled() {
|
|
262888
|
+
if (process.env.USABLE_SKIP_UPDATE_CHECK || process.env.NO_UPDATE_NOTIFIER) return false;
|
|
262889
|
+
if (process.env.CI) return false;
|
|
262890
|
+
if (process.env.E2E_ALLOW_TEST_SEAMS === "true") return false;
|
|
262891
|
+
return true;
|
|
262892
|
+
}
|
|
262893
|
+
function statePath(dataDir2) {
|
|
262894
|
+
return join10(dataDir2, "update-check.json");
|
|
262895
|
+
}
|
|
262896
|
+
function readState(dataDir2) {
|
|
262897
|
+
const p28 = statePath(dataDir2);
|
|
262898
|
+
if (!existsSync8(p28)) return null;
|
|
262899
|
+
try {
|
|
262900
|
+
const s17 = JSON.parse(readFileSync6(p28, "utf8"));
|
|
262901
|
+
if (typeof s17.lastCheckAt === "number" && typeof s17.latestVersion === "string") {
|
|
262902
|
+
return { lastCheckAt: s17.lastCheckAt, latestVersion: s17.latestVersion };
|
|
262903
|
+
}
|
|
262904
|
+
} catch {
|
|
262905
|
+
}
|
|
262906
|
+
return null;
|
|
262907
|
+
}
|
|
262908
|
+
function writeState(dataDir2, state2) {
|
|
262909
|
+
try {
|
|
262910
|
+
if (!existsSync8(dataDir2)) mkdirSync8(dataDir2, { recursive: true });
|
|
262911
|
+
writeFileSync7(statePath(dataDir2), JSON.stringify(state2, null, 2), { mode: 384 });
|
|
262912
|
+
} catch {
|
|
262913
|
+
}
|
|
262914
|
+
}
|
|
262915
|
+
async function fetchLatestVersion(timeoutMs = FETCH_TIMEOUT_MS) {
|
|
262916
|
+
try {
|
|
262917
|
+
const res = await fetch(REGISTRY_URL, {
|
|
262918
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
262919
|
+
headers: { accept: "application/json" }
|
|
262920
|
+
});
|
|
262921
|
+
if (!res.ok) return null;
|
|
262922
|
+
const json3 = await res.json();
|
|
262923
|
+
return typeof json3.version === "string" ? json3.version : null;
|
|
262924
|
+
} catch {
|
|
262925
|
+
return null;
|
|
262926
|
+
}
|
|
262927
|
+
}
|
|
262928
|
+
function formatUpdateNotice(current, latest) {
|
|
262929
|
+
return `\u24D8 update available ${current} \u2192 ${latest} \xB7 run \`usable-chat update\``;
|
|
262930
|
+
}
|
|
262931
|
+
function getUpdateNotice(opts) {
|
|
262932
|
+
if (!updateCheckEnabled()) return null;
|
|
262933
|
+
const state2 = readState(opts.dataDir);
|
|
262934
|
+
if (state2 && isNewerVersion(state2.latestVersion, opts.currentVersion)) {
|
|
262935
|
+
return formatUpdateNotice(opts.currentVersion, state2.latestVersion);
|
|
262936
|
+
}
|
|
262937
|
+
return null;
|
|
262938
|
+
}
|
|
262939
|
+
async function maybeRefreshUpdateState(opts) {
|
|
262940
|
+
if (!updateCheckEnabled()) return;
|
|
262941
|
+
const now2 = opts.now ?? Date.now();
|
|
262942
|
+
const state2 = readState(opts.dataDir);
|
|
262943
|
+
if (state2 && now2 - state2.lastCheckAt < CHECK_INTERVAL_MS) return;
|
|
262944
|
+
const latest = await fetchLatestVersion();
|
|
262945
|
+
writeState(opts.dataDir, {
|
|
262946
|
+
lastCheckAt: now2,
|
|
262947
|
+
latestVersion: latest ?? state2?.latestVersion ?? "0.0.0"
|
|
262948
|
+
});
|
|
262949
|
+
}
|
|
262950
|
+
function refreshUpdateStateInBackground(opts) {
|
|
262951
|
+
void maybeRefreshUpdateState(opts).catch(() => {
|
|
262952
|
+
});
|
|
262953
|
+
}
|
|
262954
|
+
function detectInstall(entry = process.argv[1] ?? "") {
|
|
262955
|
+
const p28 = entry.replace(/\\/g, "/");
|
|
262956
|
+
if (p28.includes("/_npx/")) return "npx";
|
|
262957
|
+
if (process.env.npm_config_global === "true") return "global";
|
|
262958
|
+
if (p28.includes("/lib/node_modules/") || p28.includes("/pnpm/global/") || p28.includes("/pnpm-global/"))
|
|
262959
|
+
return "global";
|
|
262960
|
+
if (p28.includes("/node_modules/")) return "local";
|
|
262961
|
+
return "unknown";
|
|
262962
|
+
}
|
|
262963
|
+
async function runUpdateCommand(opts) {
|
|
262964
|
+
const kind = opts?.kind ?? detectInstall(opts?.entry);
|
|
262965
|
+
const out = (s17) => process.stdout.write(s17 + "\n");
|
|
262966
|
+
const err = (s17) => process.stderr.write(s17 + "\n");
|
|
262967
|
+
if (kind === "npx") {
|
|
262968
|
+
out("You're running via npx, which already fetches the latest on each run.");
|
|
262969
|
+
out(`To force the newest explicitly: npx ${PKG_NAME}@latest`);
|
|
262970
|
+
return 0;
|
|
262971
|
+
}
|
|
262972
|
+
const usePnpm = (opts?.entry ?? process.argv[1] ?? "").replace(/\\/g, "/").includes("/pnpm");
|
|
262973
|
+
const cmd = usePnpm ? "pnpm" : "npm";
|
|
262974
|
+
const args = usePnpm ? ["add", "-g", `${PKG_NAME}@latest`] : ["install", "-g", `${PKG_NAME}@latest`];
|
|
262975
|
+
out(`\u21BB installing ${PKG_NAME}@latest (${cmd} ${args.join(" ")}) \u2026`);
|
|
262976
|
+
const spawn7 = opts?.spawnFn ?? nodeSpawn;
|
|
262977
|
+
return await new Promise((resolve7) => {
|
|
262978
|
+
let settled = false;
|
|
262979
|
+
const done = (code) => {
|
|
262980
|
+
if (!settled) {
|
|
262981
|
+
settled = true;
|
|
262982
|
+
resolve7(code);
|
|
262983
|
+
}
|
|
262984
|
+
};
|
|
262985
|
+
const child = spawn7(cmd, args, { stdio: "inherit", shell: process.platform === "win32" });
|
|
262986
|
+
child.on("error", (e14) => {
|
|
262987
|
+
if (e14.code === "EACCES" || e14.code === "EPERM") {
|
|
262988
|
+
err(`\u2717 permission denied. Try: sudo ${cmd} ${args.join(" ")}`);
|
|
262989
|
+
} else if (e14.code === "ENOENT") {
|
|
262990
|
+
err(`\u2717 \`${cmd}\` not found. Install it, or run: npm install -g ${PKG_NAME}@latest`);
|
|
262991
|
+
} else {
|
|
262992
|
+
err(`\u2717 update failed: ${e14.message}
|
|
262993
|
+
Run manually: ${cmd} ${args.join(" ")}`);
|
|
262994
|
+
}
|
|
262995
|
+
done(1);
|
|
262996
|
+
});
|
|
262997
|
+
child.on("exit", (code) => {
|
|
262998
|
+
if (code === 0) out(`\u2713 updated \u2014 restart usable-chat to use the new version.`);
|
|
262999
|
+
else err(`\u2717 ${cmd} exited with code ${code ?? 1}`);
|
|
263000
|
+
done(code ?? 1);
|
|
263001
|
+
});
|
|
263002
|
+
});
|
|
263003
|
+
}
|
|
263004
|
+
|
|
262701
263005
|
// src/cli/index.ts
|
|
262702
263006
|
function usableCompletionsUrl(cfg) {
|
|
262703
263007
|
const host = (cfg.ai.usableApiHost || "https://chat.usable.dev").replace(/\/+$/, "").replace(/\/api\/v1$/, "");
|
|
@@ -262756,7 +263060,7 @@ function userIdFromToken(token) {
|
|
|
262756
263060
|
function resolveGitRoot(start) {
|
|
262757
263061
|
let dir = start;
|
|
262758
263062
|
while (true) {
|
|
262759
|
-
if (
|
|
263063
|
+
if (existsSync13(join20(dir, ".git"))) return dir;
|
|
262760
263064
|
const parent = dirname16(dir);
|
|
262761
263065
|
if (parent === dir) return start;
|
|
262762
263066
|
dir = parent;
|
|
@@ -262775,6 +263079,7 @@ function printUsage() {
|
|
|
262775
263079
|
' usable-chat -c -p "<prompt>" Continue the latest session for this project',
|
|
262776
263080
|
' usable-chat --resume <id> -p "..." Resume a specific session',
|
|
262777
263081
|
" usable-chat sessions List sessions for this project",
|
|
263082
|
+
" usable-chat update Update to the latest version (npm i -g)",
|
|
262778
263083
|
"",
|
|
262779
263084
|
"Auth & keys:",
|
|
262780
263085
|
" usable-chat login Usable/MCP token (Keycloak)",
|
|
@@ -262893,7 +263198,7 @@ function readScriptedLlm() {
|
|
|
262893
263198
|
}
|
|
262894
263199
|
async function runTui(cfg, store, projectId, values2) {
|
|
262895
263200
|
applyEnv(cfg);
|
|
262896
|
-
silenceConsole(
|
|
263201
|
+
silenceConsole(join20(cfg.paths.dataDir, "usable.log"));
|
|
262897
263202
|
let session = await loadSession(cfg);
|
|
262898
263203
|
let model = values2.model ?? cfg.ai.defaultModel;
|
|
262899
263204
|
let reasoning = cfg.ai.reasoningEffort;
|
|
@@ -262922,7 +263227,7 @@ async function runTui(cfg, store, projectId, values2) {
|
|
|
262922
263227
|
const scriptedLlm = readScriptedLlm();
|
|
262923
263228
|
const { runCliTurn: runCliTurn2 } = await Promise.resolve().then(() => (init_run(), run_exports));
|
|
262924
263229
|
const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui2(), tui_exports));
|
|
262925
|
-
const extLogFile =
|
|
263230
|
+
const extLogFile = join20(cfg.paths.dataDir, "usable.log");
|
|
262926
263231
|
const extLog = (m33) => {
|
|
262927
263232
|
try {
|
|
262928
263233
|
appendFileSync4(extLogFile, `[ext] ${m33}
|
|
@@ -263025,6 +263330,11 @@ async function runTui(cfg, store, projectId, values2) {
|
|
|
263025
263330
|
kind: m33.role === "assistant" ? "assistant" : "user",
|
|
263026
263331
|
text: typeof m33.content === "string" ? m33.content : JSON.stringify(m33.content)
|
|
263027
263332
|
}));
|
|
263333
|
+
const updateNotice = getUpdateNotice({
|
|
263334
|
+
currentVersion: version,
|
|
263335
|
+
dataDir: cfg.paths.dataDir
|
|
263336
|
+
});
|
|
263337
|
+
refreshUpdateStateInBackground({ dataDir: cfg.paths.dataDir });
|
|
263028
263338
|
await launchTui2({
|
|
263029
263339
|
runTurn,
|
|
263030
263340
|
actions,
|
|
@@ -263032,6 +263342,7 @@ async function runTui(cfg, store, projectId, values2) {
|
|
|
263032
263342
|
loggedIn: Boolean(session.user?.accessToken),
|
|
263033
263343
|
user: session.user?.id,
|
|
263034
263344
|
version,
|
|
263345
|
+
updateNotice: updateNotice ?? void 0,
|
|
263035
263346
|
extensions: {
|
|
263036
263347
|
hasCommand: (n30) => ext2.current.hasCommand(n30),
|
|
263037
263348
|
runCommand: (n30, a21) => ext2.current.runCommand(n30, a21),
|
|
@@ -263044,6 +263355,7 @@ async function runTui(cfg, store, projectId, values2) {
|
|
|
263044
263355
|
clearSkillCache2();
|
|
263045
263356
|
await ext2.reload();
|
|
263046
263357
|
},
|
|
263358
|
+
verify: (name14) => verifyExtension(process.cwd(), name14).then((r17) => r17.message),
|
|
263047
263359
|
trust: () => ext2.trust(),
|
|
263048
263360
|
bindNotify: (fn4) => {
|
|
263049
263361
|
extUi.notify = fn4;
|
|
@@ -263085,6 +263397,9 @@ async function main() {
|
|
|
263085
263397
|
if (positionals[0] === "config") {
|
|
263086
263398
|
return runConfigCommand(cfg.paths.configFile, positionals.slice(1));
|
|
263087
263399
|
}
|
|
263400
|
+
if (positionals[0] === "update") {
|
|
263401
|
+
return runUpdateCommand();
|
|
263402
|
+
}
|
|
263088
263403
|
const store = new CliStore(cfg.paths.dbFile);
|
|
263089
263404
|
const gitRoot2 = resolveGitRoot(process.cwd());
|
|
263090
263405
|
const projectId = store.resolveProject(gitRoot2, basename10(gitRoot2));
|
|
@@ -263196,6 +263511,14 @@ async function main() {
|
|
|
263196
263511
|
message: { role: "assistant", content: result.text }
|
|
263197
263512
|
});
|
|
263198
263513
|
}
|
|
263514
|
+
await maybeRefreshUpdateState({ dataDir: cfg.paths.dataDir });
|
|
263515
|
+
const updateNotice = getUpdateNotice({
|
|
263516
|
+
currentVersion: version,
|
|
263517
|
+
dataDir: cfg.paths.dataDir
|
|
263518
|
+
});
|
|
263519
|
+
if (updateNotice) process.stderr.write(`
|
|
263520
|
+
${updateNotice}
|
|
263521
|
+
`);
|
|
263199
263522
|
store.close();
|
|
263200
263523
|
return 0;
|
|
263201
263524
|
}
|