Remove ESBuild experiment. Add --install option for bash completions. Move shell scripts to separate files for auditability. Fix template inspect command autocomplete and output formatting

This commit is contained in:
2026-04-20 11:12:26 +00:00
parent ff2fe126c6
commit 32c42cdc2d
11 changed files with 706 additions and 1049 deletions

View File

@@ -8,12 +8,13 @@
* Usage: xo-complete <context> [args...]
*
* Contexts:
* mnemonics - List mnemonic file names
* templates - List template names/IDs
* actions <template> - List actions for a template
* invitations - List invitation IDs
* resources - List UTXO outpoints (txhash:vout)
* subcommands <command> - List subcommands for a top-level command
* mnemonics - List mnemonic file names
* templates - List template names/IDs
* actions <template> - List actions for a template
* fields <category> <template> - List fields for a template category
* invitations - List invitation IDs
* resources - List UTXO outpoints (txhash:vout)
* subcommands <command> - List subcommands for a top-level command
*
* Output: One completion suggestion per line, suitable for shell completion.
*
@@ -150,6 +151,33 @@ async function listTemplates(prefix?: string): Promise<void> {
}
}
/**
* Resolves a template by name or ID.
*/
async function resolveTemplate(
engine: Awaited<ReturnType<Awaited<ReturnType<typeof getOfflineEngineModule>>["tryCreateOfflineEngine"]>>,
templateQuery: string,
) {
if (!engine) return null;
const { generateTemplateIdentifier } = await getEngineModule();
const templates = await engine.listImportedTemplates();
// Try exact match on name or ID
let template = templates.find(
(t) => t.name === templateQuery || generateTemplateIdentifier(t) === templateQuery,
);
// Try partial match on name
if (!template) {
template = templates.find((t) =>
t.name?.toLowerCase().includes(templateQuery.toLowerCase()),
);
}
return template ?? null;
}
/**
* Lists actions for a specific template.
*/
@@ -158,7 +186,6 @@ async function listActions(templateQuery: string, prefix?: string): Promise<void
if (!mnemonic) return;
const { tryCreateOfflineEngine } = await getOfflineEngineModule();
const { generateTemplateIdentifier } = await getEngineModule();
const engine = await tryCreateOfflineEngine(mnemonic, {
databasePath: getDataDir(),
@@ -168,18 +195,7 @@ async function listActions(templateQuery: string, prefix?: string): Promise<void
if (!engine) return;
try {
// Try to resolve the template by name or ID
const templates = await engine.listImportedTemplates();
let template = templates.find(
(t) => t.name === templateQuery || generateTemplateIdentifier(t) === templateQuery,
);
if (!template) {
// Try partial match on name
template = templates.find((t) =>
t.name?.toLowerCase().includes(templateQuery.toLowerCase()),
);
}
const template = await resolveTemplate(engine, templateQuery);
if (template && template.actions) {
const actions = Object.keys(template.actions);
@@ -190,6 +206,53 @@ async function listActions(templateQuery: string, prefix?: string): Promise<void
}
}
/**
* Lists fields (actions, transactions, outputs, etc.) for a specific template category.
* Used for completing the 3rd argument of `template inspect <category> <template> <field>`.
*/
async function listFields(category: string, templateQuery: string, prefix?: string): Promise<void> {
const mnemonic = getCurrentMnemonic();
if (!mnemonic) return;
const { tryCreateOfflineEngine } = await getOfflineEngineModule();
const engine = await tryCreateOfflineEngine(mnemonic, {
databasePath: getDataDir(),
databaseFilename: "xo-wallet.db",
});
if (!engine) return;
try {
const template = await resolveTemplate(engine, templateQuery);
if (!template) return;
let fields: string[] = [];
switch (category) {
case "action":
fields = Object.keys(template.actions || {});
break;
case "transaction":
fields = Object.keys(template.transactions || {});
break;
case "output":
fields = Object.keys(template.outputs || {});
break;
case "lockingscript":
fields = Object.keys(template.lockingScripts || {});
break;
case "variable":
fields = Object.keys(template.variables || {});
break;
}
outputCompletions(fields, prefix);
} finally {
await engine.stop();
}
}
/**
* Lists invitation IDs from the invitation storage.
*/
@@ -278,6 +341,13 @@ async function main(): Promise<void> {
}
break;
case "fields":
// fields <category> <template> [prefix]
if (arg1 && arg2) {
await listFields(arg1, arg2, process.argv[5]);
}
break;
case "invitations":
await listInvitations(arg1);
break;