@guiho/runx 0.2.7 → 0.4.1
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/CHANGELOG.md +39 -0
- package/DOCS.md +127 -128
- package/README.md +56 -75
- package/devops/build-binaries.ts +44 -62
- package/devops/devops.xdocs.md +8 -5
- package/devops/install.ps1 +182 -152
- package/devops/install.sh +197 -262
- package/devops/installers.spec.ts +31 -0
- package/devops/verify-release-assets.ts +17 -0
- package/docs/docs.xdocs.md +3 -1
- package/docs/plans/plans.xdocs.md +7 -0
- package/docs/plans/rfc-0034-cli-compliance-migration.md +586 -0
- package/docs/plans/upgrade-reliability-implementation.md +95 -0
- package/docs/reviews/implementation/implementation.xdocs.md +2 -0
- package/docs/reviews/implementation/rfc-0034-cli-compliance-migration-review.md +69 -0
- package/docs/reviews/plans/plans.xdocs.md +6 -0
- package/docs/reviews/plans/rfc-0034-cli-compliance-migration-review.md +80 -0
- package/docs/reviews/plans/upgrade-reliability-implementation-review.md +65 -0
- package/docs/superpowers/specs/2026-07-15-upgrade-reliability-design.md +662 -0
- package/docs/superpowers/specs/specs.xdocs.md +24 -0
- package/docs/superpowers/superpowers.xdocs.md +21 -0
- package/docs/todo/rfc-0034-cli-compliance-migration-implementation.md +77 -0
- package/docs/todo/rfc-0034-cli-compliance-migration.md +152 -0
- package/docs/todo/todo.xdocs.md +6 -2
- package/docs/validation/rfc-0034-cli-compliance-migration.md +83 -0
- package/docs/validation/upgrade-reliability.md +124 -0
- package/docs/validation/validation.xdocs.md +5 -0
- package/library/agents.d.ts +29 -4
- package/library/agents.d.ts.map +1 -1
- package/library/agents.js +143 -41
- package/library/cli.d.ts.map +1 -1
- package/library/cli.js +311 -331
- package/library/configuration.d.ts +57 -0
- package/library/configuration.d.ts.map +1 -0
- package/library/configuration.js +111 -0
- package/library/embedded-resources.d.ts +6 -1
- package/library/embedded-resources.d.ts.map +1 -1
- package/library/embedded-resources.js +10 -4
- package/library/executor.d.ts +5 -1
- package/library/executor.d.ts.map +1 -1
- package/library/executor.js +10 -10
- package/library/help.d.ts +8 -4
- package/library/help.d.ts.map +1 -1
- package/library/help.js +70 -46
- package/library/init.d.ts +8 -27
- package/library/init.d.ts.map +1 -1
- package/library/init.js +37 -159
- package/library/manifest.d.ts +4 -43
- package/library/manifest.d.ts.map +1 -1
- package/library/manifest.js +4 -125
- package/library/path-utils.d.ts +13 -0
- package/library/path-utils.d.ts.map +1 -0
- package/library/path-utils.js +82 -0
- package/library/recovery.d.ts +7 -0
- package/library/recovery.d.ts.map +1 -0
- package/library/recovery.js +23 -0
- package/library/release-catalog.d.ts +32 -0
- package/library/release-catalog.d.ts.map +1 -0
- package/library/release-catalog.js +124 -0
- package/library/self-management.d.ts +24 -4
- package/library/self-management.d.ts.map +1 -1
- package/library/self-management.js +279 -99
- package/library/storage.d.ts +13 -0
- package/library/storage.d.ts.map +1 -0
- package/library/storage.js +38 -0
- package/library/types.d.ts +11 -16
- package/library/types.d.ts.map +1 -1
- package/library/types.js +3 -0
- package/library/update-cache.d.ts +21 -0
- package/library/update-cache.d.ts.map +1 -0
- package/library/update-cache.js +68 -0
- package/library/upgrade-reporting.d.ts +11 -0
- package/library/upgrade-reporting.d.ts.map +1 -0
- package/library/upgrade-reporting.js +67 -0
- package/library/upgrade-types.d.ts +72 -0
- package/library/upgrade-types.d.ts.map +1 -0
- package/library/upgrade-types.js +3 -0
- package/package.json +6 -3
- package/scripts/runx-bin.mjs +49 -0
- package/scripts/runx-bin.spec.ts +62 -0
- package/scripts/scripts.xdocs.md +3 -3
- package/skills/guiho-s-runx/SKILL.md +64 -59
- package/skills/guiho-s-runx/guiho-s-runx.xdocs.md +7 -4
- package/skills/skills.xdocs.md +1 -1
- package/docs/todo/implement-runx-alpha.md +0 -36
- package/scripts/runx-bin.ts +0 -24
package/library/cli.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @copyright Copyright © 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { Type } from '@sinclair/typebox';
|
|
5
|
+
import { Value } from '@sinclair/typebox/value';
|
|
5
6
|
import { defineCommand, renderUsage, runCommand as runCittyCommand } from 'citty';
|
|
6
|
-
import {
|
|
7
|
+
import { applyAgentInstructions, installAgentSkill, listAgentPrompts, listAgentSkills, removeAgentInstructions, showAgentInstructions, showAgentPrompt, showAgentSkill, uninstallAgentSkill, updateAgentInstructions, updateAgentSkill, } from './agents.js';
|
|
7
8
|
import { RunXError } from './errors.js';
|
|
8
9
|
import { runCommand } from './executor.js';
|
|
9
|
-
import { readVersion,
|
|
10
|
+
import { readVersion, renderHelpDocs, renderHelpTree } from './help.js';
|
|
10
11
|
import { initializeRunXManifest } from './init.js';
|
|
11
12
|
import { readManifest, resolveCommand } from './manifest.js';
|
|
13
|
+
import { resolvePath } from './path-utils.js';
|
|
12
14
|
import { renderDescription, renderExecutionPlan, renderJson, renderList } from './render.js';
|
|
13
|
-
import {
|
|
15
|
+
import { fetchReleaseCatalog, resolveUpgradePlatform } from './release-catalog.js';
|
|
16
|
+
import { checkForLatestVersion, uninstallSelf, upgradeSelf } from './self-management.js';
|
|
17
|
+
import { readCachedUpdateNotice, runUpdateWorker, spawnUpdateWorker } from './update-cache.js';
|
|
18
|
+
import { renderReleaseCatalog, renderUpgradeEvent, renderUpgradeHeading, renderUpgradePlan, renderUpgradeResult } from './upgrade-reporting.js';
|
|
14
19
|
export { runCli, runCliWithErrorHandling, runxCommand, };
|
|
15
20
|
class CliHandled extends Error {
|
|
16
21
|
}
|
|
@@ -22,207 +27,220 @@ class CliUsageError extends Error {
|
|
|
22
27
|
this.usage = usage;
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'help-tree': { type: 'boolean', description: 'Show the complete command hierarchy.' },
|
|
31
|
-
'help-docs': { type: 'boolean', description: 'Show manifest and agent guidance.' },
|
|
30
|
+
const formatSchema = Type.Union([Type.Literal('text'), Type.Literal('json')]);
|
|
31
|
+
const archSchema = Type.Union([Type.Literal('x64'), Type.Literal('arm64')]);
|
|
32
|
+
const variantSchema = Type.Union([Type.Literal('baseline'), Type.Literal('default'), Type.Literal('modern')]);
|
|
33
|
+
const positiveIntegerSchema = Type.Integer({ minimum: 1 });
|
|
34
|
+
const helpArgs = {
|
|
32
35
|
help: { type: 'boolean', alias: 'h', description: 'Show command help.' },
|
|
33
|
-
|
|
36
|
+
'help-tree': { type: 'boolean', description: 'Show this command hierarchy.' },
|
|
37
|
+
'help-tree-depth': { type: 'string', valueHint: 'positive-integer', description: 'Limit help-tree recursion depth.' },
|
|
38
|
+
'help-docs': { type: 'boolean', description: 'Emit Markdown documentation for this command.' },
|
|
34
39
|
};
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
const catalogArgs = {
|
|
41
|
+
cwd: { type: 'string', valueHint: 'path', description: 'Use this effective working directory.' },
|
|
42
|
+
config: { type: 'string', valueHint: 'path', description: 'Use this runx.yaml configuration file.' },
|
|
43
|
+
format: { type: 'string', valueHint: 'text|json', default: 'text', description: 'Select output format.' },
|
|
44
|
+
verbose: { type: 'boolean', description: 'Enable diagnostics.' },
|
|
45
|
+
...helpArgs,
|
|
39
46
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'file',
|
|
56
|
-
'format',
|
|
57
|
-
'verbose',
|
|
58
|
-
'dryRun',
|
|
59
|
-
'dry-run',
|
|
60
|
-
'yes',
|
|
61
|
-
'tool',
|
|
62
|
-
'helpTree',
|
|
63
|
-
'help-tree',
|
|
64
|
-
'helpDocs',
|
|
65
|
-
'help-docs',
|
|
66
|
-
'help',
|
|
67
|
-
'h',
|
|
68
|
-
'version',
|
|
69
|
-
'v',
|
|
70
|
-
]);
|
|
71
|
-
const createCommandTree = () => {
|
|
72
|
-
const state = { globalArgs: { _: [] }, usageCommand: {} };
|
|
73
|
-
const listCommand = defineCommand({
|
|
74
|
-
meta: { name: 'runx list', description: 'List commands in a RunX manifest.' },
|
|
75
|
-
args: commonArgs,
|
|
76
|
-
setup: withCommandHelp(state),
|
|
77
|
-
run: async () => listCommands(resolveOptions(state.globalArgs)),
|
|
78
|
-
});
|
|
79
|
-
const describeCommandDefinition = defineCommand({
|
|
80
|
-
meta: { name: 'runx describe', description: 'Describe one manifest command.' },
|
|
81
|
-
args: {
|
|
82
|
-
selector: { type: 'positional', description: 'Command UID, group/id, index, or unambiguous ID.' },
|
|
83
|
-
...commonArgs,
|
|
84
|
-
},
|
|
85
|
-
setup: withCommandHelp(state),
|
|
86
|
-
run: async ({ args }) => {
|
|
87
|
-
if (!args.selector)
|
|
88
|
-
throw await missingSelector(state);
|
|
89
|
-
await describeCommand(args.selector, resolveOptions(state.globalArgs));
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
const runCommandDefinition = createRunCommand(state);
|
|
93
|
-
const checkCommand = defineCommand({
|
|
94
|
-
meta: { name: 'runx check', description: 'Validate a RunX manifest without executing it.' },
|
|
95
|
-
args: commonArgs,
|
|
96
|
-
setup: withCommandHelp(state),
|
|
97
|
-
run: async () => checkManifest(resolveOptions(state.globalArgs)),
|
|
47
|
+
function createCommandTree() {
|
|
48
|
+
const state = { args: { _: [] }, command: {}, commands: new Map() };
|
|
49
|
+
const leaf = (name, description, args, run) => {
|
|
50
|
+
const command = defineCommand({ meta: { name, description }, args: { ...args, ...helpArgs }, setup: helpSetup(state), run });
|
|
51
|
+
state.commands.set(name.replace(/^runx(?:\s+|$)/, ''), command);
|
|
52
|
+
return command;
|
|
53
|
+
};
|
|
54
|
+
const list = leaf('runx list', 'List commands in a RunX configuration.', catalogArgs, async () => listCommands(options(state.args)));
|
|
55
|
+
const describe = leaf('runx describe', 'Describe one catalog command without execution.', {
|
|
56
|
+
selector: { type: 'positional', description: 'UID, group/id, index, or unambiguous ID.' },
|
|
57
|
+
...catalogArgs,
|
|
58
|
+
}, async ({ args }) => {
|
|
59
|
+
if (!args.selector)
|
|
60
|
+
throw await usageError(state, 'Missing required positional argument: SELECTOR');
|
|
61
|
+
await describeCommand(String(args.selector), options(state.args));
|
|
98
62
|
});
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
63
|
+
const run = leaf('runx run', 'Execute one selected catalog command.', {
|
|
64
|
+
selector: { type: 'positional', description: 'UID, group/id, index, or unambiguous ID.' },
|
|
65
|
+
...catalogArgs,
|
|
66
|
+
'dry-run': { type: 'boolean', description: 'Print the execution plan without spawning.' },
|
|
67
|
+
yes: { type: 'boolean', description: 'Approve a confirmation-gated command.' },
|
|
68
|
+
}, async ({ args }) => {
|
|
69
|
+
if (!args.selector)
|
|
70
|
+
throw await usageError(state, 'Missing required positional argument: SELECTOR');
|
|
71
|
+
await runSelected(String(args.selector), options(state.args), state.args);
|
|
104
72
|
});
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
args:
|
|
108
|
-
|
|
109
|
-
...agentInstallArgs,
|
|
110
|
-
},
|
|
111
|
-
setup: withCommandHelp(state),
|
|
112
|
-
run: async ({ args }) => runAgentsInstall(args.scope, resolveOptions(state.globalArgs), state.globalArgs),
|
|
73
|
+
const check = leaf('runx check', 'Validate a RunX configuration without execution.', catalogArgs, async () => checkManifest(options(state.args)));
|
|
74
|
+
const init = leaf('runx init', 'Create a new YAML RunX configuration.', catalogArgs, async () => {
|
|
75
|
+
const result = await initializeRunXManifest({ cwd: options(state.args).cwd, config: options(state.args).config });
|
|
76
|
+
write(state.args.format === 'json' ? renderJson(result) : `created: ${result.path}\n`);
|
|
113
77
|
});
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
78
|
+
const skillInstall = leaf('runx agent skill install', 'Install the bundled skill into both global tool locations.', {
|
|
79
|
+
cwd: catalogArgs.cwd, local: { type: 'boolean', description: 'Use project-local tool directories.' }, format: catalogArgs.format,
|
|
80
|
+
}, async () => agentMutation('installed', installAgentSkill, state.args));
|
|
81
|
+
const skillUninstall = leaf('runx agent skill uninstall', 'Remove the bundled skill from both tool locations.', {
|
|
82
|
+
cwd: catalogArgs.cwd, local: { type: 'boolean', description: 'Use project-local tool directories.' }, format: catalogArgs.format,
|
|
83
|
+
}, async () => agentMutation('removed', uninstallAgentSkill, state.args));
|
|
84
|
+
const skillUpdate = leaf('runx agent skill update', 'Refresh the bundled skill in both tool locations.', {
|
|
85
|
+
cwd: catalogArgs.cwd, local: { type: 'boolean', description: 'Use project-local tool directories.' }, format: catalogArgs.format,
|
|
86
|
+
}, async () => agentMutation('updated', updateAgentSkill, state.args));
|
|
87
|
+
const skillList = leaf('runx agent skill list', 'List bundled RunX skills.', {
|
|
88
|
+
filter: { type: 'string', valueHint: 'keyword', description: 'Filter skill metadata.' }, format: catalogArgs.format,
|
|
89
|
+
}, () => writeFormatted(listAgentSkills(state.args.filter), state.args));
|
|
90
|
+
const skillShow = leaf('runx agent skill show', 'Show metadata for one bundled skill.', {
|
|
91
|
+
id: { type: 'positional', description: 'Bundled skill ID.' }, format: catalogArgs.format,
|
|
92
|
+
}, async ({ args }) => {
|
|
93
|
+
if (!args.id)
|
|
94
|
+
throw await usageError(state, 'Missing required positional argument: ID');
|
|
95
|
+
writeFormatted(await showAgentSkill(String(args.id)), state.args);
|
|
128
96
|
});
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
97
|
+
const skill = group('runx agent skill', 'Manage the bundled RunX skill.', { install: skillInstall, uninstall: skillUninstall, update: skillUpdate, list: skillList, show: skillShow }, state);
|
|
98
|
+
const instructionApply = leaf('runx agent instruction apply', 'Apply the managed instruction block.', { cwd: catalogArgs.cwd, format: catalogArgs.format }, async () => instructionMutation('updated', applyAgentInstructions, state.args));
|
|
99
|
+
const instructionRemove = leaf('runx agent instruction remove', 'Remove the managed instruction block.', { cwd: catalogArgs.cwd, format: catalogArgs.format }, async () => instructionMutation('removed', removeAgentInstructions, state.args));
|
|
100
|
+
const instructionUpdate = leaf('runx agent instruction update', 'Replace stale managed instruction content.', { cwd: catalogArgs.cwd, format: catalogArgs.format }, async () => instructionMutation('updated', updateAgentInstructions, state.args));
|
|
101
|
+
const instructionShow = leaf('runx agent instruction show', 'Print the raw instruction template.', {}, async () => write(await showAgentInstructions()));
|
|
102
|
+
const instruction = group('runx agent instruction', 'Manage RunX instruction blocks.', { apply: instructionApply, remove: instructionRemove, update: instructionUpdate, show: instructionShow }, state);
|
|
103
|
+
const promptList = leaf('runx agent prompt list', 'List bundled RunX prompts.', {
|
|
104
|
+
names: { type: 'boolean', description: 'Print prompt names only.' }, format: catalogArgs.format,
|
|
105
|
+
}, () => {
|
|
106
|
+
if (state.args.names) {
|
|
107
|
+
const names = listAgentPrompts(true);
|
|
108
|
+
write(options(state.args).format === 'json' ? renderJson(names) : `${names.join('\n')}\n`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
writeFormatted(listAgentPrompts(false), state.args);
|
|
134
112
|
});
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
113
|
+
const promptShow = leaf('runx agent prompt show', 'Print one raw bundled prompt.', {
|
|
114
|
+
id: { type: 'positional', description: 'Bundled prompt ID.' },
|
|
115
|
+
}, async ({ args }) => {
|
|
116
|
+
if (!args.id)
|
|
117
|
+
throw await usageError(state, 'Missing required positional argument: ID');
|
|
118
|
+
write(await showAgentPrompt(String(args.id)));
|
|
140
119
|
});
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
120
|
+
const prompt = group('runx agent prompt', 'Inspect bundled agent prompts.', { list: promptList, show: promptShow }, state);
|
|
121
|
+
const agent = group('runx agent', 'Manage RunX agent integration.', { skill, instruction, prompt }, state);
|
|
122
|
+
const upgradeArgs = {
|
|
123
|
+
version: { type: 'string', valueHint: 'version', description: 'Select an exact release version.' },
|
|
124
|
+
arch: { type: 'string', valueHint: 'x64|arm64', description: 'Select target architecture.' },
|
|
125
|
+
variant: { type: 'string', valueHint: 'baseline|default|modern', description: 'Select x64 binary variant.' },
|
|
126
|
+
'dry-run': { type: 'boolean', description: 'Plan without mutation.' },
|
|
127
|
+
format: catalogArgs.format,
|
|
128
|
+
};
|
|
129
|
+
const upgradeCheck = leaf('runx upgrade check', 'Check whether a newer stable release exists.', {
|
|
130
|
+
format: catalogArgs.format,
|
|
131
|
+
}, async () => {
|
|
132
|
+
const result = await checkForLatestVersion();
|
|
133
|
+
writeFormatted(result, state.args);
|
|
146
134
|
});
|
|
147
|
-
const
|
|
135
|
+
const upgradeList = leaf('runx upgrade list', 'List RunX releases newest first.', {
|
|
136
|
+
page: { type: 'string', valueHint: 'positive-integer', description: 'Select result page.' },
|
|
137
|
+
'per-page': { type: 'string', valueHint: 'positive-integer', description: 'Select page size.' },
|
|
138
|
+
'pre-releases': { type: 'boolean', description: 'Include prerelease versions.' },
|
|
139
|
+
arch: { type: 'string', valueHint: 'x64|arm64', description: 'Select target architecture.' },
|
|
140
|
+
variant: { type: 'string', valueHint: 'baseline|default|modern', description: 'Select x64 variant.' },
|
|
141
|
+
format: catalogArgs.format,
|
|
142
|
+
}, async () => runUpgradeList(state.args));
|
|
143
|
+
const upgrade = defineCommand({
|
|
148
144
|
meta: { name: 'runx upgrade', description: 'Inspect or upgrade a native RunX executable.' },
|
|
149
|
-
args:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
check: upgradeCheckCommand,
|
|
154
|
-
list: upgradeListCommand,
|
|
155
|
-
},
|
|
156
|
-
setup: withCommandHelp(state),
|
|
157
|
-
});
|
|
158
|
-
const uninstallCommand = defineCommand({
|
|
159
|
-
meta: { name: 'runx uninstall', description: 'Uninstall a native RunX executable.' },
|
|
160
|
-
args: selfManagementArgs,
|
|
161
|
-
setup: withCommandHelp(state),
|
|
162
|
-
run: async () => runUninstall(resolveOptions(state.globalArgs), state.globalArgs),
|
|
163
|
-
});
|
|
164
|
-
const homeCommand = defineCommand({
|
|
165
|
-
meta: { name: 'runx', description: 'Show the RunX home page.', hidden: true },
|
|
166
|
-
args: rootArgs,
|
|
167
|
-
setup: withCommandHelp(state),
|
|
168
|
-
run: () => write(showHome()),
|
|
145
|
+
args: { ...upgradeArgs, ...helpArgs },
|
|
146
|
+
subCommands: { check: upgradeCheck, list: upgradeList },
|
|
147
|
+
setup: helpSetup(state),
|
|
148
|
+
run: async () => runUpgrade(options(state.args), state.args),
|
|
169
149
|
});
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
init: initCommand,
|
|
177
|
-
agents: agentsCommand,
|
|
178
|
-
upgrade: upgradeCommand,
|
|
179
|
-
uninstall: uninstallCommand,
|
|
150
|
+
state.commands.set('upgrade', upgrade);
|
|
151
|
+
const uninstall = leaf('runx uninstall', 'Uninstall the native RunX executable.', {
|
|
152
|
+
'dry-run': { type: 'boolean', description: 'Print the target without deleting it.' }, format: catalogArgs.format,
|
|
153
|
+
}, async () => {
|
|
154
|
+
const result = await uninstallSelf(Boolean(state.args.dryRun));
|
|
155
|
+
writeFormatted(result, state.args);
|
|
180
156
|
});
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
['r', runCommandDefinition],
|
|
186
|
-
['check', checkCommand],
|
|
187
|
-
['init', initCommand],
|
|
188
|
-
['agents', agentsCommand],
|
|
189
|
-
['agents install', agentsInstallCommand],
|
|
190
|
-
['agents instructions', agentsInstructionsCommand],
|
|
191
|
-
['upgrade', upgradeCommand],
|
|
192
|
-
['upgrade check', upgradeCheckCommand],
|
|
193
|
-
['upgrade list', upgradeListCommand],
|
|
194
|
-
['uninstall', uninstallCommand],
|
|
195
|
-
]);
|
|
196
|
-
const subCommands = createSelectorCompatibleCommands(staticSubCommands, state);
|
|
197
|
-
const command = defineCommand({
|
|
198
|
-
meta: {
|
|
199
|
-
name: 'runx',
|
|
200
|
-
version: readVersion(),
|
|
201
|
-
description: 'A documented, local command catalog for runx.yaml manifests.',
|
|
202
|
-
},
|
|
203
|
-
args: rootArgs,
|
|
204
|
-
default: '_home',
|
|
205
|
-
subCommands,
|
|
157
|
+
const root = defineCommand({
|
|
158
|
+
meta: { name: 'runx', version: readVersion(), description: 'A documented local command catalog for runx.yaml.' },
|
|
159
|
+
args: { version: { type: 'boolean', alias: 'v', description: 'Show the RunX version.' }, ...helpArgs },
|
|
160
|
+
subCommands: { list, describe, run, check, init, agent, upgrade, uninstall },
|
|
206
161
|
setup: async (context) => {
|
|
207
|
-
state.
|
|
208
|
-
state.
|
|
209
|
-
await
|
|
210
|
-
if (
|
|
211
|
-
return
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (
|
|
217
|
-
|
|
162
|
+
state.args = context.args;
|
|
163
|
+
state.command = resolveHelpCommand(state);
|
|
164
|
+
await validateInvocation(state);
|
|
165
|
+
if (context.rawArgs.length === 1 && ['-v', '--version'].includes(context.rawArgs[0] ?? '')) {
|
|
166
|
+
return handled(`${readVersion()}\n`);
|
|
167
|
+
}
|
|
168
|
+
await handleHelp(state);
|
|
169
|
+
},
|
|
170
|
+
run: ({ args }) => {
|
|
171
|
+
if (args._.length === 0)
|
|
172
|
+
write(`Hello Windows - runx v${readVersion()}\n`);
|
|
218
173
|
},
|
|
219
174
|
});
|
|
220
|
-
state.
|
|
221
|
-
|
|
222
|
-
};
|
|
175
|
+
state.commands.set('', root);
|
|
176
|
+
state.command = root;
|
|
177
|
+
return { command: root, state };
|
|
178
|
+
}
|
|
179
|
+
async function validateInvocation(state) {
|
|
180
|
+
const first = state.args._[0];
|
|
181
|
+
const topLevel = new Set(['list', 'describe', 'run', 'check', 'init', 'agent', 'upgrade', 'uninstall']);
|
|
182
|
+
if (first && !topLevel.has(first))
|
|
183
|
+
throw await usageError(state, `Unknown command: ${first}`);
|
|
184
|
+
const allowed = new Set(['_', 'h', 'v']);
|
|
185
|
+
for (const command of state.commands.values()) {
|
|
186
|
+
for (const key of Object.keys(command.args ?? {})) {
|
|
187
|
+
allowed.add(key);
|
|
188
|
+
allowed.add(key.replace(/-([a-z])/g, (_, character) => character.toUpperCase()));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const unknown = Object.keys(state.args).find((key) => !allowed.has(key));
|
|
192
|
+
if (unknown)
|
|
193
|
+
throw await usageError(state, `Unknown option: --${unknown.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)}`);
|
|
194
|
+
}
|
|
195
|
+
function group(name, description, subCommands, state, defaultCommand) {
|
|
196
|
+
const command = defineCommand({ meta: { name, description }, args: helpArgs, subCommands, ...(defaultCommand ? { default: defaultCommand } : {}), setup: helpSetup(state) });
|
|
197
|
+
state.commands.set(name.replace(/^runx(?:\s+|$)/, ''), command);
|
|
198
|
+
return command;
|
|
199
|
+
}
|
|
200
|
+
function helpSetup(state) {
|
|
201
|
+
return async (context) => {
|
|
202
|
+
state.args = context.args;
|
|
203
|
+
state.command = context.cmd;
|
|
204
|
+
await handleHelp(state);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async function handleHelp(state) {
|
|
208
|
+
if (state.args.helpDocs)
|
|
209
|
+
return handled(renderHelpDocs(state.command));
|
|
210
|
+
if (state.args.helpTree || state.args.helpTreeDepth) {
|
|
211
|
+
const depth = state.args.helpTreeDepth ? positiveInteger(state.args.helpTreeDepth, '--help-tree-depth') : Number.POSITIVE_INFINITY;
|
|
212
|
+
return handled(renderHelpTree(state.command, depth));
|
|
213
|
+
}
|
|
214
|
+
if (state.args.help || state.args.h)
|
|
215
|
+
return handled(`${await renderUsage(state.command)}\n`);
|
|
216
|
+
}
|
|
217
|
+
function resolveHelpCommand(state) {
|
|
218
|
+
const tokens = state.args._.filter((token) => !token.startsWith('-'));
|
|
219
|
+
for (let length = tokens.length; length > 0; length -= 1) {
|
|
220
|
+
const command = state.commands.get(tokens.slice(0, length).join(' '));
|
|
221
|
+
if (command)
|
|
222
|
+
return command;
|
|
223
|
+
}
|
|
224
|
+
return state.commands.get('') ?? state.command;
|
|
225
|
+
}
|
|
223
226
|
const { command: runxCommand } = createCommandTree();
|
|
224
227
|
async function runCli(rawArgs = process.argv.slice(2)) {
|
|
228
|
+
if (rawArgs.length === 1 && rawArgs[0] === '--check-updates-worker') {
|
|
229
|
+
await runUpdateWorker();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
225
232
|
const { command, state } = createCommandTree();
|
|
233
|
+
const cleanOutput = rawArgs.some((arg) => ['-h', '--help', '-v', '--version', '--help-tree', '--help-docs'].includes(arg) || arg.startsWith('--help-tree-depth'));
|
|
234
|
+
if (!cleanOutput) {
|
|
235
|
+
const notice = await readCachedUpdateNotice(rawArgs.includes('--verbose'));
|
|
236
|
+
if (notice) {
|
|
237
|
+
if (rawArgs.length === 0)
|
|
238
|
+
write(`${notice}\n`);
|
|
239
|
+
else
|
|
240
|
+
process.stderr.write(`${notice}\n`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
spawnUpdateWorker();
|
|
226
244
|
try {
|
|
227
245
|
await runCittyCommand(command, { rawArgs });
|
|
228
246
|
}
|
|
@@ -231,9 +249,8 @@ async function runCli(rawArgs = process.argv.slice(2)) {
|
|
|
231
249
|
return;
|
|
232
250
|
if (error instanceof CliUsageError)
|
|
233
251
|
throw error;
|
|
234
|
-
if (
|
|
235
|
-
throw new CliUsageError(error.message, await renderUsage(state.
|
|
236
|
-
}
|
|
252
|
+
if (error instanceof Error && error.name === 'CLIError')
|
|
253
|
+
throw new CliUsageError(error.message, await renderUsage(state.command));
|
|
237
254
|
throw error;
|
|
238
255
|
}
|
|
239
256
|
}
|
|
@@ -244,174 +261,137 @@ async function runCliWithErrorHandling(rawArgs) {
|
|
|
244
261
|
catch (error) {
|
|
245
262
|
if (error instanceof CliUsageError) {
|
|
246
263
|
process.stderr.write(`${error.usage}\n\nerror: ${error.message}\n`);
|
|
247
|
-
process.exitCode =
|
|
248
|
-
return;
|
|
264
|
+
process.exitCode = 2;
|
|
249
265
|
}
|
|
250
|
-
if (error instanceof RunXError) {
|
|
266
|
+
else if (error instanceof RunXError) {
|
|
251
267
|
process.stderr.write(`error: ${error.message}\n`);
|
|
252
268
|
process.exitCode = error.exitCode;
|
|
253
|
-
return;
|
|
254
269
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
function createRunCommand(state, selectorDefault) {
|
|
260
|
-
return defineCommand({
|
|
261
|
-
meta: {
|
|
262
|
-
name: selectorDefault ? 'runx <selector>' : 'runx run',
|
|
263
|
-
alias: selectorDefault ? undefined : 'r',
|
|
264
|
-
description: selectorDefault ? 'Run a selector using the root shorthand.' : 'Run one manifest command.',
|
|
265
|
-
},
|
|
266
|
-
args: selectorDefault
|
|
267
|
-
? runArgs
|
|
268
|
-
: {
|
|
269
|
-
selector: { type: 'positional', description: 'Command UID, group/id, index, or unambiguous ID.' },
|
|
270
|
-
...runArgs,
|
|
271
|
-
},
|
|
272
|
-
setup: withCommandHelp(state),
|
|
273
|
-
run: async ({ args }) => {
|
|
274
|
-
const selector = selectorDefault ?? (typeof args.selector === 'string' ? args.selector : undefined);
|
|
275
|
-
if (!selector)
|
|
276
|
-
throw await missingSelector(state);
|
|
277
|
-
await runSelectedCommand(selector, resolveOptions(state.globalArgs), state.globalArgs);
|
|
278
|
-
},
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
function createSelectorCompatibleCommands(commands, state) {
|
|
282
|
-
const aliases = new Set(['r']);
|
|
283
|
-
return new Proxy(commands, {
|
|
284
|
-
has: (target, property) => {
|
|
285
|
-
if (typeof property !== 'string')
|
|
286
|
-
return Reflect.has(target, property);
|
|
287
|
-
if (aliases.has(property))
|
|
288
|
-
return false;
|
|
289
|
-
return true;
|
|
290
|
-
},
|
|
291
|
-
get: (target, property, receiver) => {
|
|
292
|
-
if (typeof property !== 'string' || Reflect.has(target, property))
|
|
293
|
-
return Reflect.get(target, property, receiver);
|
|
294
|
-
return createRunCommand(state, property);
|
|
295
|
-
},
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
function resolveUsageCommand(positionals, commands, state) {
|
|
299
|
-
const name = positionals[0];
|
|
300
|
-
if (!name)
|
|
301
|
-
return state.usageCommand;
|
|
302
|
-
if (!commands.has(name))
|
|
303
|
-
return createRunCommand(state, name);
|
|
304
|
-
for (let length = positionals.length; length > 0; length -= 1) {
|
|
305
|
-
const command = commands.get(positionals.slice(0, length).join(' '));
|
|
306
|
-
if (command)
|
|
307
|
-
return command;
|
|
270
|
+
else {
|
|
271
|
+
process.stderr.write(`error: ${error instanceof Error ? error.message : 'Unexpected failure.'}\n`);
|
|
272
|
+
process.exitCode = 1;
|
|
273
|
+
}
|
|
308
274
|
}
|
|
309
|
-
return commands.get(name);
|
|
310
275
|
}
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
async function validateArguments(context, state) {
|
|
320
|
-
const unknown = Object.keys(context.args).find((key) => !knownArgumentKeys.has(key));
|
|
321
|
-
if (!unknown)
|
|
322
|
-
return;
|
|
323
|
-
throw new CliUsageError(`Unknown option --${unknown}`, await renderUsage(state.usageCommand));
|
|
324
|
-
}
|
|
325
|
-
async function handleUsage(command) {
|
|
326
|
-
return handleOutput(`${await renderUsage(command)}\n`);
|
|
276
|
+
async function load(optionsValue) {
|
|
277
|
+
const loaded = await readManifest(optionsValue.cwd, optionsValue.config);
|
|
278
|
+
const message = `configuration file loaded: ${loaded.path}\n`;
|
|
279
|
+
if (optionsValue.format === 'json')
|
|
280
|
+
process.stderr.write(message);
|
|
281
|
+
else
|
|
282
|
+
write(message);
|
|
283
|
+
return loaded;
|
|
327
284
|
}
|
|
328
|
-
function
|
|
329
|
-
|
|
330
|
-
|
|
285
|
+
async function listCommands(optionsValue) {
|
|
286
|
+
const { manifest, path } = await load(optionsValue);
|
|
287
|
+
write(optionsValue.format === 'json' ? renderJson({ manifestPath: path, manifest }) : renderList(manifest, path));
|
|
331
288
|
}
|
|
332
|
-
async function
|
|
333
|
-
const { manifest, path } = await
|
|
334
|
-
write(options.format === 'json' ? renderJson({ manifestPath: path, manifest }) : renderList(manifest, path));
|
|
335
|
-
}
|
|
336
|
-
async function describeCommand(selector, options) {
|
|
337
|
-
const { manifest, path } = await readManifest(options.cwd, options.file);
|
|
289
|
+
async function describeCommand(selector, optionsValue) {
|
|
290
|
+
const { manifest, path } = await load(optionsValue);
|
|
338
291
|
const command = resolveCommand(manifest, path, selector);
|
|
339
|
-
write(
|
|
292
|
+
write(optionsValue.format === 'json' ? renderJson(command) : renderDescription(command));
|
|
340
293
|
}
|
|
341
|
-
async function checkManifest(
|
|
342
|
-
const { manifest, path } = await
|
|
294
|
+
async function checkManifest(optionsValue) {
|
|
295
|
+
const { manifest, path } = await load(optionsValue);
|
|
343
296
|
const result = { valid: true, manifestPath: path, commandCount: manifest.commands.length, groups: Object.keys(manifest.groups) };
|
|
344
|
-
write(
|
|
297
|
+
write(optionsValue.format === 'json' ? renderJson(result) : `valid: true\nmanifest: ${path}\ncommands: ${result.commandCount}\n`);
|
|
345
298
|
}
|
|
346
|
-
async function
|
|
347
|
-
|
|
348
|
-
throw new RunXError('runx init does not support --file. It always creates runx.yaml in --cwd or the current directory.');
|
|
349
|
-
}
|
|
350
|
-
if (args.format === 'json') {
|
|
351
|
-
throw new RunXError('runx init does not support --format json because initialization is an interactive terminal workflow.');
|
|
352
|
-
}
|
|
353
|
-
await initializeRunXManifest({ cwd: resolve(args.cwd ?? process.cwd()) });
|
|
354
|
-
}
|
|
355
|
-
async function runSelectedCommand(selector, options, args) {
|
|
356
|
-
const { manifest, path } = await readManifest(options.cwd, options.file);
|
|
299
|
+
async function runSelected(selector, optionsValue, args) {
|
|
300
|
+
const { manifest, path } = await load(optionsValue);
|
|
357
301
|
const command = resolveCommand(manifest, path, selector);
|
|
358
302
|
if (args.dryRun) {
|
|
359
|
-
write(
|
|
303
|
+
write(optionsValue.format === 'json' ? renderJson({ dryRun: true, command }) : renderExecutionPlan(command));
|
|
360
304
|
return;
|
|
361
305
|
}
|
|
362
|
-
if (command.confirm === 'always' && !args.yes)
|
|
363
|
-
throw new RunXError(`Command ${command.uid} requires confirmation.
|
|
364
|
-
|
|
365
|
-
if (options.format === 'text')
|
|
306
|
+
if (command.confirm === 'always' && !args.yes)
|
|
307
|
+
throw new RunXError(`Command ${command.uid} requires confirmation. Rerun with --yes only after authorization.`, 2);
|
|
308
|
+
if (optionsValue.format === 'text')
|
|
366
309
|
write(`Running ${command.uid} (${command.selector})\n`);
|
|
367
310
|
process.exitCode = await runCommand(command);
|
|
368
311
|
}
|
|
369
|
-
async function
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
312
|
+
async function agentMutation(label, action, args) {
|
|
313
|
+
try {
|
|
314
|
+
const paths = await action(args.local ? 'local' : 'global', resolvePath(args.cwd ?? process.cwd()));
|
|
315
|
+
writeFormatted({ [label]: paths }, args);
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
if (error instanceof RunXError)
|
|
319
|
+
throw error;
|
|
320
|
+
throw new RunXError(`Agent skill mutation failed: ${error instanceof Error ? error.message : String(error)}`, 5);
|
|
321
|
+
}
|
|
378
322
|
}
|
|
379
|
-
async function
|
|
380
|
-
|
|
381
|
-
|
|
323
|
+
async function instructionMutation(label, action, args) {
|
|
324
|
+
try {
|
|
325
|
+
const paths = await action(resolvePath(args.cwd ?? process.cwd()));
|
|
326
|
+
writeFormatted({ [label]: paths }, args);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
if (error instanceof RunXError)
|
|
330
|
+
throw error;
|
|
331
|
+
throw new RunXError(`Agent instruction mutation failed: ${error instanceof Error ? error.message : String(error)}`, 5);
|
|
332
|
+
}
|
|
382
333
|
}
|
|
383
|
-
async function
|
|
384
|
-
const
|
|
385
|
-
|
|
334
|
+
async function runUpgradeList(args) {
|
|
335
|
+
const requestedArch = args.arch ? decode(archSchema, args.arch, '--arch') : process.arch;
|
|
336
|
+
const platform = resolveUpgradePlatform(process.platform, requestedArch);
|
|
337
|
+
const variant = decode(variantSchema, args.variant ?? 'baseline', '--variant');
|
|
338
|
+
const page = positiveInteger(args.page ?? '1', '--page');
|
|
339
|
+
const perPage = positiveInteger(args.perPage ?? '20', '--per-page');
|
|
340
|
+
const catalog = await fetchReleaseCatalog({ ...platform, variant, currentVersion: readVersion() });
|
|
341
|
+
const filtered = args.preReleases ? catalog.releases : catalog.releases.filter((release) => !release.prerelease);
|
|
342
|
+
const paged = { ...catalog, releases: filtered.slice((page - 1) * perPage, page * perPage) };
|
|
343
|
+
write(options(args).format === 'json' ? renderJson(paged) : renderReleaseCatalog(paged));
|
|
386
344
|
}
|
|
387
|
-
async function
|
|
388
|
-
|
|
389
|
-
|
|
345
|
+
async function runUpgrade(optionsValue, args) {
|
|
346
|
+
if (args.arch)
|
|
347
|
+
decode(archSchema, args.arch, '--arch');
|
|
348
|
+
if (args.variant)
|
|
349
|
+
decode(variantSchema, args.variant, '--variant');
|
|
350
|
+
const text = optionsValue.format === 'text';
|
|
351
|
+
if (text)
|
|
352
|
+
write(renderUpgradeHeading());
|
|
353
|
+
const result = await upgradeSelf({
|
|
354
|
+
dryRun: Boolean(args.dryRun),
|
|
355
|
+
requestedVersion: typeof args.version === 'string' ? args.version : undefined,
|
|
356
|
+
arch: args.arch,
|
|
357
|
+
variant: args.variant,
|
|
358
|
+
onPlan: text ? (plan) => write(renderUpgradePlan(plan)) : undefined,
|
|
359
|
+
onEvent: text ? (event) => write(renderUpgradeEvent(event)) : undefined,
|
|
360
|
+
});
|
|
361
|
+
write(text ? renderUpgradeResult(result) : renderJson(result));
|
|
362
|
+
if (result.outcome === 'failed' || result.outcome === 'rolled-back')
|
|
363
|
+
process.exitCode = result.error?.code.includes('download') ? 4 : 5;
|
|
390
364
|
}
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
return write(renderJson(result));
|
|
395
|
-
const summary = `current: ${result.currentVersion}\ntarget: ${result.latestVersion}\n`;
|
|
396
|
-
write(result.upToDate ? `${summary}Already up to date.\n` : `${summary}path: ${result.executablePath}\n${result.scheduled ? 'scheduled: true\n' : ''}`);
|
|
365
|
+
function options(args) {
|
|
366
|
+
const format = decode(formatSchema, args.format ?? 'text', '--format');
|
|
367
|
+
return { cwd: resolvePath(args.cwd ?? process.cwd()), config: args.config, format: format, verbose: Boolean(args.verbose) };
|
|
397
368
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
369
|
+
function decode(schema, value, flag) {
|
|
370
|
+
try {
|
|
371
|
+
return Value.Decode(schema, value);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
throw new RunXError(`Invalid ${flag} value: ${String(value)}`, 2);
|
|
375
|
+
}
|
|
401
376
|
}
|
|
402
|
-
function
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
377
|
+
function positiveInteger(value, flag) {
|
|
378
|
+
const number = Number(value);
|
|
379
|
+
try {
|
|
380
|
+
return Value.Decode(positiveIntegerSchema, number);
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
throw new RunXError(`${flag} must be a positive integer.`, 2);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function usageError(state, message) {
|
|
387
|
+
return new CliUsageError(message, await renderUsage(state.command));
|
|
412
388
|
}
|
|
413
|
-
function
|
|
414
|
-
|
|
389
|
+
function writeFormatted(value, args) {
|
|
390
|
+
write(options(args).format === 'json' ? renderJson(value) : `${typeof value === 'string' ? value : JSON.stringify(value, null, 2)}\n`);
|
|
391
|
+
}
|
|
392
|
+
function handled(value) {
|
|
393
|
+
write(value.endsWith('\n') ? value : `${value}\n`);
|
|
394
|
+
throw new CliHandled();
|
|
415
395
|
}
|
|
416
396
|
function write(value) {
|
|
417
397
|
process.stdout.write(value);
|