@orxataguy/tyr 1.0.43 → 1.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/package.json +1 -1
- package/src/core/Kernel.ts +6 -0
- package/src/core/sys/help.ts +1 -0
- package/src/core/sys/modules.ts +195 -10
package/README.md
CHANGED
|
@@ -180,11 +180,14 @@ Third parties can distribute commands as a `manifest.json` — a flat JSON map o
|
|
|
180
180
|
```json
|
|
181
181
|
{
|
|
182
182
|
"deploy": "https://raw.githubusercontent.com/someuser/tyr-modules/main/deploy.tyr.ts",
|
|
183
|
-
"rollback": "https://raw.githubusercontent.com/someuser/tyr-modules/main/rollback.tyr.ts"
|
|
183
|
+
"rollback": "https://raw.githubusercontent.com/someuser/tyr-modules/main/rollback.tyr.ts",
|
|
184
|
+
"$env": "https://raw.githubusercontent.com/someuser/tyr-modules/main/.env.example"
|
|
184
185
|
}
|
|
185
186
|
```
|
|
186
187
|
|
|
187
|
-
|
|
188
|
+
`$env` is optional and reserved (`$` can never appear in a command name, so it never collides with one). If present, it's downloaded as `~/.tyr/<module-name>.env.example` — a template of whatever environment variables that module's commands expect. It follows the exact same install/skip/force-update rules as commands below, and `tyr --del <module-name>` removes it along with everything else that module owns.
|
|
189
|
+
|
|
190
|
+
Register one with `tyr --add`, which validates the manifest, records it in `~/.tyr/imported_modules.yaml`, and immediately downloads whatever commands (and `$env`, if present) are missing:
|
|
188
191
|
|
|
189
192
|
```bash
|
|
190
193
|
tyr --add https://raw.githubusercontent.com/someuser/tyr-modules/main/manifest.json my-module
|
|
@@ -194,8 +197,11 @@ tyr --add https://raw.githubusercontent.com/someuser/tyr-modules/main/manifest.j
|
|
|
194
197
|
tyr --modules # list registered modules
|
|
195
198
|
tyr --modules sync # re-check all registered manifests and install anything missing
|
|
196
199
|
tyr --update # git pull ~/.tyr (if linked) and force-refresh all imported commands
|
|
200
|
+
tyr --del my-module # unregister a module: removes its commands, map.yml entries and lock records
|
|
197
201
|
```
|
|
198
202
|
|
|
203
|
+
`tyr --del <module-name>` is the counterpart to `--add` — it looks up every command the lockfile says that module owns, deletes the command file, drops it (and any alias pointing to it) from `map.yml`, clears its lockfile entry, and finally removes the module from `imported_modules.yaml`. Commands that aren't tracked as belonging to that module (hand-written, or now owned by a different module after a collision) are left untouched.
|
|
204
|
+
|
|
199
205
|
Rules worth knowing:
|
|
200
206
|
|
|
201
207
|
- A command name that already exists locally is never overwritten by a plain sync — only `tyr --update` (or `tyr --modules sync --force`) refreshes commands that were themselves installed by an import.
|
|
@@ -219,6 +225,7 @@ Commit and push it, and anyone can install your commands with `tyr --add <raw-ur
|
|
|
219
225
|
- Requires a GitHub remote named `origin` — other hosts aren't supported, since the URLs only resolve through `raw.githubusercontent.com`.
|
|
220
226
|
- If `~/.tyr` isn't a git repository (or has no `origin` remote), `--manifest` refuses and tells you why instead of generating a broken file.
|
|
221
227
|
- Commands whose file lives outside `~/.tyr` (an absolute path elsewhere on disk) are skipped, since they can't be resolved to a URL in the repo.
|
|
228
|
+
- If `~/.tyr/.env.example` exists, it's automatically included as `$env`, so whoever runs `tyr --add` on your manifest gets it as `<module-name>.env.example`.
|
|
222
229
|
|
|
223
230
|
### `tyr chat [directory] [--port <n>] [--split <0-1>]`
|
|
224
231
|
|
package/package.json
CHANGED
package/src/core/Kernel.ts
CHANGED
|
@@ -85,6 +85,7 @@ export class Kernel {
|
|
|
85
85
|
console.log(' tyr --upgrade Upgrade Tyr to the latest npm version');
|
|
86
86
|
console.log(' tyr --modules List or sync imported modules');
|
|
87
87
|
console.log(' tyr --add <url> Register and sync a manifest.json as an imported module');
|
|
88
|
+
console.log(' tyr --del <name> Unregister an imported module and remove its commands');
|
|
88
89
|
console.log(' tyr --manifest Generate ~/.tyr/manifest.json from your commands (requires a GitHub-linked repo)');
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
@@ -194,6 +195,11 @@ export class Kernel {
|
|
|
194
195
|
return;
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
if (commandName === '--del') {
|
|
199
|
+
await modulesCommand(context)(['del', ...args.slice(1)]);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
197
203
|
if (commandName === '--manifest') {
|
|
198
204
|
await modulesCommand(context)(['manifest']);
|
|
199
205
|
return;
|
package/src/core/sys/help.ts
CHANGED
|
@@ -76,6 +76,7 @@ export default function help({ userRoot }: TyrContext) {
|
|
|
76
76
|
{ name: '--upgrade', description: 'Upgrades the tyr npm package.', usage: 'tyr --upgrade' },
|
|
77
77
|
{ name: '--modules', description: 'Lists or syncs modules imported via manifest.json.', usage: 'tyr --modules [sync|list]' },
|
|
78
78
|
{ name: '--add', description: 'Registers a manifest.json URL as an imported module and syncs it.', usage: 'tyr --add <manifest-url> [name]' },
|
|
79
|
+
{ name: '--del', description: 'Unregisters an imported module and removes the commands it owns.', usage: 'tyr --del <module-name>' },
|
|
79
80
|
{ name: '--manifest', description: 'Generates ~/.tyr/manifest.json from your commands (needs a GitHub-linked repo).', usage: 'tyr --manifest' },
|
|
80
81
|
{ name: 'gen', description: 'Generates a new command from a description using AI.', usage: 'tyr gen <name> "<description>"' },
|
|
81
82
|
{ name: 'doc', description: 'Opens the framework documentation in the browser.', usage: 'tyr doc' },
|
package/src/core/sys/modules.ts
CHANGED
|
@@ -18,8 +18,15 @@ interface ManagedCommand {
|
|
|
18
18
|
source: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
interface ManagedEnvFile {
|
|
22
|
+
manifest: string;
|
|
23
|
+
source: string;
|
|
24
|
+
file: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
interface ModulesLock {
|
|
22
28
|
commands: Record<string, ManagedCommand>;
|
|
29
|
+
env?: Record<string, ManagedEnvFile>;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
export interface SyncSummary {
|
|
@@ -82,12 +89,24 @@ async function saveYaml(fs: any, filePath: string, data: unknown): Promise<void>
|
|
|
82
89
|
await fs.write(filePath, yaml.dump(data, { indent: 2, lineWidth: -1 }));
|
|
83
90
|
}
|
|
84
91
|
|
|
92
|
+
// Reserved manifest key for an optional .env.example reference. '$' can never
|
|
93
|
+
// appear in a valid command name (see isValidCommandName), so it can't collide
|
|
94
|
+
// with a real command.
|
|
95
|
+
const ENV_MANIFEST_KEY = '$env';
|
|
96
|
+
|
|
97
|
+
interface ParsedManifest {
|
|
98
|
+
commands: Record<string, string>;
|
|
99
|
+
envUrl?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
/**
|
|
86
103
|
* Fetches a manifest.json (name -> raw file URL) and validates its shape.
|
|
87
104
|
* Only https URLs and safe command names are accepted, to avoid path
|
|
88
105
|
* traversal and to keep imported code coming from a known-good transport.
|
|
106
|
+
* A manifest may also carry a reserved `$env` key pointing at a raw
|
|
107
|
+
* .env.example file for the module's required environment variables.
|
|
89
108
|
*/
|
|
90
|
-
async function fetchManifest(web: any, manifestUrl: string): Promise<
|
|
109
|
+
async function fetchManifest(web: any, manifestUrl: string): Promise<ParsedManifest> {
|
|
91
110
|
if (!isValidHttpsUrl(manifestUrl)) {
|
|
92
111
|
throw new TyrError(`Manifest URL must use https: ${manifestUrl}`);
|
|
93
112
|
}
|
|
@@ -99,16 +118,28 @@ async function fetchManifest(web: any, manifestUrl: string): Promise<Record<stri
|
|
|
99
118
|
throw new TyrError(`Manifest at ${manifestUrl} is not a valid JSON object.`);
|
|
100
119
|
}
|
|
101
120
|
|
|
102
|
-
|
|
121
|
+
const commands: Record<string, string> = {};
|
|
122
|
+
let envUrl: string | undefined;
|
|
123
|
+
|
|
124
|
+
for (const [name, value] of Object.entries(parsed)) {
|
|
125
|
+
if (name === ENV_MANIFEST_KEY) {
|
|
126
|
+
if (typeof value !== 'string' || !isValidHttpsUrl(value)) {
|
|
127
|
+
throw new TyrError(`Manifest at ${manifestUrl} has an invalid (non-https) URL for '${ENV_MANIFEST_KEY}'.`);
|
|
128
|
+
}
|
|
129
|
+
envUrl = value;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
103
133
|
if (!isValidCommandName(name)) {
|
|
104
134
|
throw new TyrError(`Manifest at ${manifestUrl} has an invalid command name: '${name}'`);
|
|
105
135
|
}
|
|
106
|
-
if (typeof
|
|
136
|
+
if (typeof value !== 'string' || !isValidHttpsUrl(value)) {
|
|
107
137
|
throw new TyrError(`Manifest at ${manifestUrl} has an invalid (non-https) URL for command '${name}'.`);
|
|
108
138
|
}
|
|
139
|
+
commands[name] = value;
|
|
109
140
|
}
|
|
110
141
|
|
|
111
|
-
return
|
|
142
|
+
return { commands, envUrl };
|
|
112
143
|
}
|
|
113
144
|
|
|
114
145
|
/**
|
|
@@ -145,6 +176,7 @@ export async function syncModules(context: TyrContext, options: { force?: boolea
|
|
|
145
176
|
|
|
146
177
|
if (!mapConfig.commands) mapConfig.commands = {};
|
|
147
178
|
if (!lock.commands) lock.commands = {};
|
|
179
|
+
if (!lock.env) lock.env = {};
|
|
148
180
|
|
|
149
181
|
// Snapshot the state as it was *before* this run. Collisions between two
|
|
150
182
|
// modules processed in the same run are resolved by processing order
|
|
@@ -160,9 +192,9 @@ export async function syncModules(context: TyrContext, options: { force?: boolea
|
|
|
160
192
|
for (const [moduleName, manifestUrl] of Object.entries(modulesFile.modules ?? {})) {
|
|
161
193
|
logger.info(`Syncing module '${moduleName}' (${manifestUrl})...`);
|
|
162
194
|
|
|
163
|
-
let
|
|
195
|
+
let parsedManifest: ParsedManifest;
|
|
164
196
|
try {
|
|
165
|
-
|
|
197
|
+
parsedManifest = await fetchManifest(web, manifestUrl);
|
|
166
198
|
} catch (e) {
|
|
167
199
|
const reason = e instanceof Error ? e.message : String(e);
|
|
168
200
|
logger.error(`Could not read manifest for module '${moduleName}': ${reason}`);
|
|
@@ -173,6 +205,8 @@ export async function syncModules(context: TyrContext, options: { force?: boolea
|
|
|
173
205
|
continue;
|
|
174
206
|
}
|
|
175
207
|
|
|
208
|
+
const { commands: manifest, envUrl } = parsedManifest;
|
|
209
|
+
|
|
176
210
|
for (const [commandName, fileUrl] of Object.entries(manifest)) {
|
|
177
211
|
const alreadyInMap = !!mapSnapshot[commandName];
|
|
178
212
|
const managed = lockSnapshot[commandName];
|
|
@@ -221,6 +255,39 @@ export async function syncModules(context: TyrContext, options: { force?: boolea
|
|
|
221
255
|
summary.failed.push({ command: commandName, reason });
|
|
222
256
|
}
|
|
223
257
|
}
|
|
258
|
+
|
|
259
|
+
if (envUrl) {
|
|
260
|
+
const envFileName = `${moduleName}.env.example`;
|
|
261
|
+
const envDestPath = path.join(userRoot, envFileName);
|
|
262
|
+
const envManaged = lock.env![moduleName];
|
|
263
|
+
|
|
264
|
+
const shouldInstallEnv = !fs.exists(envDestPath);
|
|
265
|
+
const shouldForceUpdateEnv = force && !!envManaged;
|
|
266
|
+
|
|
267
|
+
if (!shouldInstallEnv && !shouldForceUpdateEnv) {
|
|
268
|
+
logger.info(
|
|
269
|
+
envManaged
|
|
270
|
+
? `Skipping ${envFileName}: already installed. Use 'tyr --update' to refresh.`
|
|
271
|
+
: `Skipping ${envFileName}: a file with that name already exists and isn't managed by an import.`
|
|
272
|
+
);
|
|
273
|
+
} else {
|
|
274
|
+
try {
|
|
275
|
+
const envContent = await web.get(envUrl);
|
|
276
|
+
const envFileContent = typeof envContent === 'string' ? envContent : JSON.stringify(envContent, null, 2);
|
|
277
|
+
await fs.write(envDestPath, envFileContent);
|
|
278
|
+
|
|
279
|
+
lock.env![moduleName] = { manifest: manifestUrl, source: envUrl, file: envFileName };
|
|
280
|
+
lockChanged = true;
|
|
281
|
+
|
|
282
|
+
logger.success(`${shouldInstallEnv ? 'Installed' : 'Updated'} ${envFileName} from module '${moduleName}'.`);
|
|
283
|
+
} catch (e) {
|
|
284
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
285
|
+
logger.error(`Could not download ${envFileName} for module '${moduleName}': ${reason}`);
|
|
286
|
+
logger.info(`Could not download ${envFileName} for module '${moduleName}': ${reason}`);
|
|
287
|
+
summary.failed.push({ command: envFileName, reason });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
224
291
|
}
|
|
225
292
|
|
|
226
293
|
if (mapChanged) await saveYaml(fs, mapPath, mapConfig);
|
|
@@ -287,6 +354,104 @@ export async function addModule(context: TyrContext, manifestUrl: string, name?:
|
|
|
287
354
|
await syncModules(context);
|
|
288
355
|
}
|
|
289
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Unregisters a module: removes it from imported_modules.yaml, deletes every
|
|
359
|
+
* command file it's tracked as owning in the lockfile, drops those commands
|
|
360
|
+
* (and any aliases pointing at them) from map.yml, deletes its
|
|
361
|
+
* `<module>.env.example` if one was imported, and clears their lockfile
|
|
362
|
+
* entries. The counterpart to `tyr --add` — closes the loop.
|
|
363
|
+
*/
|
|
364
|
+
export async function removeModule(context: TyrContext, moduleName: string): Promise<void> {
|
|
365
|
+
const { logger, fs, userRoot } = context as any;
|
|
366
|
+
|
|
367
|
+
if (!moduleName) {
|
|
368
|
+
logger.error('Missing module name.');
|
|
369
|
+
logger.info('Usage: tyr --del <module-name>');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const modulesPath = path.join(userRoot, IMPORTED_MODULES_FILE);
|
|
374
|
+
const mapPath = path.join(userRoot, 'map.yml');
|
|
375
|
+
const lockPath = path.join(userRoot, LOCK_FILE);
|
|
376
|
+
|
|
377
|
+
const modulesFile = await loadYaml<ImportedModulesFile>(fs, modulesPath, { modules: {} });
|
|
378
|
+
const mapConfig = await loadYaml<TyrConfig>(fs, mapPath, { commands: {} });
|
|
379
|
+
const lock = await loadYaml<ModulesLock>(fs, lockPath, { commands: {} });
|
|
380
|
+
|
|
381
|
+
if (!modulesFile.modules) modulesFile.modules = {};
|
|
382
|
+
if (!mapConfig.commands) mapConfig.commands = {};
|
|
383
|
+
if (!lock.commands) lock.commands = {};
|
|
384
|
+
if (!lock.env) lock.env = {};
|
|
385
|
+
|
|
386
|
+
const isRegistered = !!modulesFile.modules[moduleName];
|
|
387
|
+
const managedCommands = Object.entries(lock.commands)
|
|
388
|
+
.filter(([, info]) => info.module === moduleName)
|
|
389
|
+
.map(([commandName]) => commandName);
|
|
390
|
+
const managedEnv = lock.env[moduleName];
|
|
391
|
+
|
|
392
|
+
if (!isRegistered && managedCommands.length === 0 && !managedEnv) {
|
|
393
|
+
logger.error(`Module '${moduleName}' is not registered and owns no commands. Nothing to remove.`);
|
|
394
|
+
logger.info(`Module '${moduleName}' was not found in ${IMPORTED_MODULES_FILE} or ${LOCK_FILE}.`);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
let mapChanged = false;
|
|
399
|
+
let lockChanged = false;
|
|
400
|
+
|
|
401
|
+
for (const commandName of managedCommands) {
|
|
402
|
+
const scriptPath = mapConfig.commands[commandName];
|
|
403
|
+
if (scriptPath) {
|
|
404
|
+
const absolutePath = path.isAbsolute(scriptPath) ? scriptPath : path.resolve(userRoot, scriptPath);
|
|
405
|
+
if (fs.exists(absolutePath)) {
|
|
406
|
+
await fs.delete(absolutePath);
|
|
407
|
+
}
|
|
408
|
+
delete mapConfig.commands[commandName];
|
|
409
|
+
mapChanged = true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (mapConfig.aliases) {
|
|
413
|
+
for (const [alias, target] of Object.entries(mapConfig.aliases)) {
|
|
414
|
+
if (target === commandName) {
|
|
415
|
+
delete mapConfig.aliases[alias];
|
|
416
|
+
mapChanged = true;
|
|
417
|
+
logger.info(`Alias '${alias}' removed (pointed to '${commandName}').`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
delete lock.commands[commandName];
|
|
423
|
+
lockChanged = true;
|
|
424
|
+
|
|
425
|
+
logger.success(`Removed command '${commandName}' (was managed by module '${moduleName}').`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (managedEnv) {
|
|
429
|
+
const envPath = path.join(userRoot, managedEnv.file);
|
|
430
|
+
if (fs.exists(envPath)) {
|
|
431
|
+
await fs.delete(envPath);
|
|
432
|
+
}
|
|
433
|
+
delete lock.env[moduleName];
|
|
434
|
+
lockChanged = true;
|
|
435
|
+
logger.success(`Removed ${managedEnv.file} (was managed by module '${moduleName}').`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (isRegistered) {
|
|
439
|
+
delete modulesFile.modules[moduleName];
|
|
440
|
+
await saveYaml(fs, modulesPath, modulesFile);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (mapChanged) await saveYaml(fs, mapPath, mapConfig);
|
|
444
|
+
if (lockChanged) await saveYaml(fs, lockPath, lock);
|
|
445
|
+
|
|
446
|
+
const removedItems = [...managedCommands, ...(managedEnv ? [managedEnv.file] : [])];
|
|
447
|
+
const removedList = removedItems.length ? `: ${removedItems.join(', ')}` : '';
|
|
448
|
+
logger.success(
|
|
449
|
+
`Module '${moduleName}' removed` +
|
|
450
|
+
(removedItems.length ? ` — ${removedItems.length} item(s) removed${removedList}.` : ' (it had nothing managed).')
|
|
451
|
+
);
|
|
452
|
+
logger.info(`Module '${moduleName}' unlinked from ${IMPORTED_MODULES_FILE}${removedList}.`);
|
|
453
|
+
}
|
|
454
|
+
|
|
290
455
|
interface GithubRepoInfo {
|
|
291
456
|
owner: string;
|
|
292
457
|
repo: string;
|
|
@@ -318,6 +483,10 @@ function parseGithubRemote(remoteUrl: string): GithubRepoInfo | null {
|
|
|
318
483
|
* the publishing counterpart of `tyr --add`: whatever this produces can be
|
|
319
484
|
* handed to someone else, who registers it with `tyr --add <url>`.
|
|
320
485
|
*
|
|
486
|
+
* If ~/.tyr/.env.example exists, its raw URL is also included under the
|
|
487
|
+
* reserved `$env` key, so `tyr --add` downloads it as `<module>.env.example`
|
|
488
|
+
* for whoever installs this manifest.
|
|
489
|
+
*
|
|
321
490
|
* Requires ~/.tyr to be a git repository linked to a GitHub remote — there's
|
|
322
491
|
* no way to build a raw.githubusercontent.com URL otherwise.
|
|
323
492
|
*/
|
|
@@ -383,16 +552,28 @@ export async function generateManifest(context: TyrContext): Promise<void> {
|
|
|
383
552
|
manifest[name] = `https://raw.githubusercontent.com/${repoInfo.owner}/${repoInfo.repo}/${branch}/${normalized}`;
|
|
384
553
|
}
|
|
385
554
|
|
|
386
|
-
|
|
555
|
+
const commandCount = Object.keys(manifest).length;
|
|
556
|
+
if (commandCount === 0) {
|
|
387
557
|
logger.info('No commands to include in manifest.json.');
|
|
388
558
|
}
|
|
389
559
|
|
|
560
|
+
// If ~/.tyr has a .env.example, publish it too — tyr --add downloads it as
|
|
561
|
+
// <module>.env.example so whoever installs this manifest knows which
|
|
562
|
+
// environment variables these commands expect.
|
|
563
|
+
const envExamplePath = path.join(userRoot, '.env.example');
|
|
564
|
+
let includedEnv = false;
|
|
565
|
+
if (fs.exists(envExamplePath)) {
|
|
566
|
+
manifest[ENV_MANIFEST_KEY] = `https://raw.githubusercontent.com/${repoInfo.owner}/${repoInfo.repo}/${branch}/.env.example`;
|
|
567
|
+
includedEnv = true;
|
|
568
|
+
}
|
|
569
|
+
|
|
390
570
|
const manifestPath = path.join(userRoot, 'manifest.json');
|
|
391
571
|
await fs.write(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
392
572
|
|
|
393
573
|
logger.success(
|
|
394
|
-
`manifest.json generated at ${manifestPath} (${
|
|
395
|
-
`${skipped.length ? `, ${skipped.length} skipped` : ''}
|
|
574
|
+
`manifest.json generated at ${manifestPath} (${commandCount} commands` +
|
|
575
|
+
`${skipped.length ? `, ${skipped.length} skipped` : ''}` +
|
|
576
|
+
`${includedEnv ? ', includes .env.example' : ''}).`
|
|
396
577
|
);
|
|
397
578
|
logger.info("Commit and push it so others can install it with: tyr --add <raw-url-to-manifest.json>");
|
|
398
579
|
}
|
|
@@ -411,6 +592,10 @@ export default function modules(context: TyrContext) {
|
|
|
411
592
|
await addModule(context, args[1], args[2]);
|
|
412
593
|
return;
|
|
413
594
|
}
|
|
595
|
+
case 'del': {
|
|
596
|
+
await removeModule(context, args[1]);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
414
599
|
case 'manifest': {
|
|
415
600
|
await generateManifest(context);
|
|
416
601
|
return;
|
|
@@ -435,7 +620,7 @@ export default function modules(context: TyrContext) {
|
|
|
435
620
|
}
|
|
436
621
|
default:
|
|
437
622
|
logger.error(`Unknown subcommand '${sub}'.`);
|
|
438
|
-
logger.info('Usage: tyr --modules [sync|list|manifest]');
|
|
623
|
+
logger.info('Usage: tyr --modules [sync|list|manifest|del <module-name>]');
|
|
439
624
|
}
|
|
440
625
|
};
|
|
441
626
|
}
|