or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

commands.mdconfiguration.mdevents.mdexecution.mdextensions.mdindex.mdstorage.mdui.md

extensions.mddocs/

0

# Extension System

1

2

Plugin architecture for importing command libraries and extending Vorpal functionality.

3

4

## Capabilities

5

6

### Extension Loading

7

8

Imports a library of Vorpal API commands.

9

10

```javascript { .api }

11

/**

12

* Imports a library of Vorpal API commands

13

* @param commands - Extension module (function, string path, array, or object)

14

* @param options - Optional configuration for the extension

15

* @returns Vorpal instance for chaining

16

*/

17

function use(commands: ExtensionInput, options?: ExtensionOptions): Vorpal;

18

19

type ExtensionInput =

20

| ((vorpal: Vorpal) => void) // Function that extends vorpal

21

| string // Module path to require

22

| ExtensionInput[] // Array of extensions

23

| { [key: string]: any }; // Extension object

24

25

interface ExtensionOptions {

26

[key: string]: any; // Extension-specific options

27

}

28

```

29

30

**Usage Examples:**

31

32

```javascript

33

const vorpal = require('vorpal')();

34

35

// Load extension by function

36

function myExtension(vorpal) {

37

vorpal

38

.command('hello', 'Says hello')

39

.action(function(args, callback) {

40

this.log('Hello from extension!');

41

callback();

42

});

43

44

vorpal

45

.command('goodbye', 'Says goodbye')

46

.action(function(args, callback) {

47

this.log('Goodbye from extension!');

48

callback();

49

});

50

}

51

52

vorpal.use(myExtension);

53

54

// Load extension by module path

55

vorpal.use('./my-extension'); // Requires local file

56

vorpal.use('vorpal-grep'); // Requires npm module

57

58

// Load multiple extensions

59

vorpal.use([

60

'./commands/file-commands',

61

'./commands/network-commands',

62

myExtension

63

]);

64

65

// Load extension with options

66

vorpal.use(myExtension, {

67

prefix: 'ext:',

68

verbose: true

69

});

70

```

71

72

## Creating Extensions

73

74

### Function-Based Extensions

75

76

The most common way to create extensions:

77

78

```javascript

79

// my-extension.js

80

function myExtension(vorpal, options) {

81

const prefix = options?.prefix || '';

82

83

vorpal

84

.command(`${prefix}status`, 'Shows application status')

85

.action(function(args, callback) {

86

this.log('Status: Running');

87

this.log('Uptime:', process.uptime());

88

callback();

89

});

90

91

vorpal

92

.command(`${prefix}restart`, 'Restarts the application')

93

.option('-f, --force', 'Force restart without confirmation')

94

.action(function(args, callback) {

95

if (args.options.force) {

96

this.log('Force restarting...');

97

callback();

98

} else {

99

this.prompt({

100

type: 'confirm',

101

name: 'confirm',

102

message: 'Are you sure you want to restart?'

103

}, (result) => {

104

if (result.confirm) {

105

this.log('Restarting...');

106

} else {

107

this.log('Restart cancelled');

108

}

109

callback();

110

});

111

}

112

});

113

}

114

115

module.exports = myExtension;

116

117

// Usage

118

const vorpal = require('vorpal')();

119

vorpal.use(require('./my-extension'), { prefix: 'app:' });

120

```

121

122

### Object-Based Extensions

123

124

Extensions can also be objects with specific structure:

125

126

```javascript

127

// extension-object.js

128

const extensionObject = {

129

// Optional initialization function

130

init: function(vorpal, options) {

131

console.log('Extension initialized with options:', options);

132

},

133

134

// Commands definition

135

commands: [

136

{

137

name: 'list-files [directory]',

138

description: 'Lists files in directory',

139

action: function(args, callback) {

140

const fs = require('fs');

141

const dir = args.directory || process.cwd();

142

143

fs.readdir(dir, (err, files) => {

144

if (err) {

145

this.log('Error:', err.message);

146

} else {

147

this.log('Files in', dir + ':');

148

files.forEach(file => this.log('-', file));

149

}

150

callback();

151

});

152

}

153

},

154

{

155

name: 'current-time',

156

description: 'Shows current time',

157

action: function(args, callback) {

158

this.log('Current time:', new Date().toLocaleString());

159

callback();

160

}

161

}

162

]

163

};

164

165

module.exports = extensionObject;

166

```

167

168

### Class-Based Extensions

169

170

More complex extensions using classes:

171

172

```javascript

173

// database-extension.js

174

class DatabaseExtension {

175

constructor(options = {}) {

176

this.connectionString = options.connectionString;

177

this.timeout = options.timeout || 5000;

178

}

179

180

install(vorpal) {

181

const self = this;

182

183

vorpal

184

.command('db:connect', 'Connect to database')

185

.action(function(args, callback) {

186

self.connect(this, callback);

187

});

188

189

vorpal

190

.command('db:query <sql>', 'Execute SQL query')

191

.action(function(args, callback) {

192

self.query(this, args.sql, callback);

193

});

194

195

vorpal

196

.command('db:status', 'Show database status')

197

.action(function(args, callback) {

198

self.status(this, callback);

199

});

200

}

201

202

connect(context, callback) {

203

context.log('Connecting to database...');

204

// Database connection logic

205

setTimeout(() => {

206

context.log('Connected successfully');

207

callback();

208

}, 1000);

209

}

210

211

query(context, sql, callback) {

212

context.log(`Executing: ${sql}`);

213

// Query execution logic

214

setTimeout(() => {

215

context.log('Query completed');

216

callback();

217

}, 500);

218

}

219

220

status(context, callback) {

221

context.log('Database Status: Connected');

222

context.log('Connection:', this.connectionString);

223

callback();

224

}

225

}

226

227

// Export function that creates and installs the extension

228

module.exports = function(options) {

229

return function(vorpal) {

230

const extension = new DatabaseExtension(options);

231

extension.install(vorpal);

232

};

233

};

234

235

// Usage

236

const vorpal = require('vorpal')();

237

const dbExtension = require('./database-extension');

238

239

vorpal.use(dbExtension({

240

connectionString: 'mongodb://localhost:27017/mydb',

241

timeout: 10000

242

}));

243

```

244

245

## Built-in Extensions

246

247

### Loading Common Extensions

248

249

Many community extensions are available:

250

251

```javascript

252

const vorpal = require('vorpal')();

253

254

// File system commands

255

vorpal.use('vorpal-less'); // less command for viewing files

256

vorpal.use('vorpal-grep'); // grep command for searching

257

258

// Network utilities

259

vorpal.use('vorpal-tour'); // Interactive tours/tutorials

260

261

// Development tools

262

vorpal.use('vorpal-watch'); // File watching commands

263

```

264

265

### Creating Modular Extensions

266

267

Split complex functionality across multiple extension files:

268

269

```javascript

270

// commands/index.js - Main extension loader

271

const fileCommands = require('./file-commands');

272

const networkCommands = require('./network-commands');

273

const systemCommands = require('./system-commands');

274

275

module.exports = function(options = {}) {

276

return function(vorpal) {

277

if (options.includeFiles !== false) {

278

vorpal.use(fileCommands);

279

}

280

281

if (options.includeNetwork !== false) {

282

vorpal.use(networkCommands);

283

}

284

285

if (options.includeSystem !== false) {

286

vorpal.use(systemCommands);

287

}

288

};

289

};

290

291

// Usage

292

const vorpal = require('vorpal')();

293

const myCommands = require('./commands');

294

295

vorpal.use(myCommands({

296

includeNetwork: false // Skip network commands

297

}));

298

```

299

300

### Extension with Shared State

301

302

Extensions can maintain shared state:

303

304

```javascript

305

// stateful-extension.js

306

function createStatefulExtension() {

307

// Shared state across commands

308

const state = {

309

users: [],

310

currentUser: null,

311

settings: {}

312

};

313

314

return function(vorpal) {

315

vorpal

316

.command('user:add <name>', 'Add a user')

317

.action(function(args, callback) {

318

state.users.push({

319

name: args.name,

320

id: Date.now(),

321

created: new Date()

322

});

323

this.log(`User ${args.name} added`);

324

callback();

325

});

326

327

vorpal

328

.command('user:list', 'List all users')

329

.action(function(args, callback) {

330

if (state.users.length === 0) {

331

this.log('No users found');

332

} else {

333

this.log('Users:');

334

state.users.forEach(user => {

335

this.log(`- ${user.name} (ID: ${user.id})`);

336

});

337

}

338

callback();

339

});

340

341

vorpal

342

.command('user:select <name>', 'Select current user')

343

.action(function(args, callback) {

344

const user = state.users.find(u => u.name === args.name);

345

if (user) {

346

state.currentUser = user;

347

this.log(`Selected user: ${user.name}`);

348

} else {

349

this.log(`User ${args.name} not found`);

350

}

351

callback();

352

});

353

354

vorpal

355

.command('user:current', 'Show current user')

356

.action(function(args, callback) {

357

if (state.currentUser) {

358

this.log('Current user:', state.currentUser.name);

359

} else {

360

this.log('No user selected');

361

}

362

callback();

363

});

364

};

365

}

366

367

module.exports = createStatefulExtension;

368

369

// Usage

370

const vorpal = require('vorpal')();

371

const statefulExtension = require('./stateful-extension');

372

373

vorpal.use(statefulExtension());

374

```

375

376

## Extension Best Practices

377

378

### Namespace Commands

379

380

Prefix commands to avoid conflicts:

381

382

```javascript

383

function myExtension(vorpal, options) {

384

const prefix = options?.prefix || 'ext:';

385

386

vorpal

387

.command(`${prefix}command1`, 'Description')

388

.action(function(args, callback) {

389

// Command logic

390

callback();

391

});

392

}

393

```

394

395

### Provide Configuration Options

396

397

Make extensions configurable:

398

399

```javascript

400

function configurableExtension(vorpal, options = {}) {

401

const config = {

402

timeout: options.timeout || 5000,

403

verbose: options.verbose || false,

404

prefix: options.prefix || '',

405

...options

406

};

407

408

// Use config throughout the extension

409

}

410

```

411

412

### Error Handling

413

414

Handle errors gracefully in extensions:

415

416

```javascript

417

function robustExtension(vorpal) {

418

vorpal

419

.command('risky-command', 'Might fail')

420

.action(function(args, callback) {

421

try {

422

// Risky operation

423

this.log('Success!');

424

callback();

425

} catch (error) {

426

this.log('Error:', error.message);

427

callback(error);

428

}

429

});

430

}

431

```

432

433

## Complete Extension Example

434

435

```javascript

436

// file-manager-extension.js

437

const fs = require('fs');

438

const path = require('path');

439

440

function fileManagerExtension(vorpal, options = {}) {

441

const config = {

442

showHidden: options.showHidden || false,

443

defaultPath: options.defaultPath || process.cwd(),

444

...options

445

};

446

447

// Shared state

448

let currentPath = config.defaultPath;

449

450

vorpal

451

.command('fm:pwd', 'Show current directory')

452

.action(function(args, callback) {

453

this.log('Current directory:', currentPath);

454

callback();

455

});

456

457

vorpal

458

.command('fm:cd [directory]', 'Change directory')

459

.action(function(args, callback) {

460

const newPath = args.directory

461

? path.resolve(currentPath, args.directory)

462

: config.defaultPath;

463

464

fs.access(newPath, fs.constants.F_OK, (err) => {

465

if (err) {

466

this.log('Directory not found:', newPath);

467

} else {

468

currentPath = newPath;

469

this.log('Changed to:', currentPath);

470

}

471

callback();

472

});

473

});

474

475

vorpal

476

.command('fm:ls [pattern]', 'List files')

477

.option('-l, --long', 'Long format')

478

.option('-a, --all', 'Show hidden files')

479

.action(function(args, callback) {

480

const showHidden = args.options.all || config.showHidden;

481

const longFormat = args.options.long;

482

483

fs.readdir(currentPath, (err, files) => {

484

if (err) {

485

this.log('Error reading directory:', err.message);

486

callback();

487

return;

488

}

489

490

let filteredFiles = files;

491

492

if (!showHidden) {

493

filteredFiles = files.filter(file => !file.startsWith('.'));

494

}

495

496

if (args.pattern) {

497

const regex = new RegExp(args.pattern, 'i');

498

filteredFiles = filteredFiles.filter(file => regex.test(file));

499

}

500

501

if (longFormat) {

502

filteredFiles.forEach(file => {

503

const fullPath = path.join(currentPath, file);

504

fs.stat(fullPath, (err, stats) => {

505

if (!err) {

506

const size = stats.size;

507

const modified = stats.mtime.toLocaleDateString();

508

const type = stats.isDirectory() ? 'DIR' : 'FILE';

509

this.log(`${type.padEnd(4)} ${size.toString().padStart(8)} ${modified} ${file}`);

510

}

511

});

512

});

513

} else {

514

this.log(filteredFiles.join(' '));

515

}

516

517

callback();

518

});

519

});

520

}

521

522

module.exports = fileManagerExtension;

523

524

// Usage in main application

525

const vorpal = require('vorpal')();

526

const fileManager = require('./file-manager-extension');

527

528

vorpal.use(fileManager, {

529

showHidden: true,

530

defaultPath: '/home/user'

531

});

532

533

vorpal

534

.delimiter('app$')

535

.show();

536

```