@askviraj/ai-plugins 1.0.1 → 1.1.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/bin/installer.js +219 -7
- package/package.json +20 -1
- package/readme.md +44 -0
package/bin/installer.js
CHANGED
|
@@ -49,6 +49,12 @@ const SUBAGENT_STATUS_LINE = {
|
|
|
49
49
|
const MARKETPLACE_REF = "virajp/ai-plugins";
|
|
50
50
|
const MARKETPLACE_NAME = "virajp-plugins";
|
|
51
51
|
|
|
52
|
+
// Sources for the latest versions (used by --version and --upgrade): the
|
|
53
|
+
// marketplace manifest on the repo's main branch, and the published CLI on npm.
|
|
54
|
+
const REMOTE_MARKETPLACE_URL =
|
|
55
|
+
"https://raw.githubusercontent.com/virajp/ai-plugins/main/.claude-plugin/marketplace.json";
|
|
56
|
+
const NPM_LATEST_URL = "https://registry.npmjs.org/@askviraj/ai-plugins/latest";
|
|
57
|
+
|
|
52
58
|
// All plugins published by the virajp-plugins marketplace.
|
|
53
59
|
const PLUGINS = ["vwf", "typescript-lsp", "dart-lsp", "context7", "mempalace"];
|
|
54
60
|
|
|
@@ -83,22 +89,122 @@ const PLUGIN_EXTRA_DEPS = {
|
|
|
83
89
|
class Installer extends Command {
|
|
84
90
|
async run() {
|
|
85
91
|
const { flags } = await this.parse(Installer);
|
|
92
|
+
|
|
93
|
+
if (flags.version) {
|
|
94
|
+
await this.printVersions();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
86
98
|
const plan = this.resolvePlan(flags);
|
|
99
|
+
const hasInstall = plan.plugins.length
|
|
100
|
+
|| plan.statusLine
|
|
101
|
+
|| plan.subagentStatusLine;
|
|
87
102
|
|
|
88
|
-
if (!
|
|
103
|
+
if (!hasInstall && !flags.upgrade) {
|
|
89
104
|
this.error(
|
|
90
|
-
"Nothing to do. Pass --all, --plugins, --plugin <name>, --statusline,
|
|
105
|
+
"Nothing to do. Pass --all, --plugins, --plugin <name>, --statusline, --subagentstatusline, --upgrade, or --version.",
|
|
91
106
|
);
|
|
92
107
|
}
|
|
93
108
|
|
|
94
|
-
|
|
109
|
+
// Install first — so a fresh machine ends up with the requested plugins…
|
|
110
|
+
if (hasInstall) {
|
|
111
|
+
this.checkDeps(plan);
|
|
112
|
+
if (plan.plugins.length) {
|
|
113
|
+
this.installPlugins(plan.plugins);
|
|
114
|
+
}
|
|
115
|
+
if (plan.statusLine || plan.subagentStatusLine) {
|
|
116
|
+
await this.installStatusline(plan, flags.yes);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
95
119
|
|
|
96
|
-
|
|
97
|
-
|
|
120
|
+
// …then upgrade everything that is installed to the latest versions.
|
|
121
|
+
if (flags.upgrade) {
|
|
122
|
+
await this.upgrade();
|
|
98
123
|
}
|
|
124
|
+
}
|
|
99
125
|
|
|
100
|
-
|
|
101
|
-
|
|
126
|
+
// Print the CLI version (vs npm latest), the bundled statusline version, and
|
|
127
|
+
// each plugin's installed version (from `claude plugin list`) vs the latest in
|
|
128
|
+
// the remote marketplace, flagging updates. Errors out if the network or the
|
|
129
|
+
// claude CLI is unavailable.
|
|
130
|
+
async printVersions() {
|
|
131
|
+
const cli = this.config.version;
|
|
132
|
+
const cliLatest = (await fetchJson(NPM_LATEST_URL)).version;
|
|
133
|
+
const latest = await remoteLatest();
|
|
134
|
+
const installed = installedPlugins();
|
|
135
|
+
|
|
136
|
+
this.log(`${this.config.name} ${cli}${updateNote(cli, cliLatest)}`);
|
|
137
|
+
this.log(` ${"statusline".padEnd(16)} ${cli} (bundled with the CLI)`);
|
|
138
|
+
|
|
139
|
+
this.log(`\nPlugins (${MARKETPLACE_NAME}):`);
|
|
140
|
+
for (const name of PLUGINS) {
|
|
141
|
+
this.log(
|
|
142
|
+
` ${name.padEnd(16)} ${
|
|
143
|
+
pluginVersionLine(installed[name], latest[name])
|
|
144
|
+
}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Upgrade every installed virajp-plugins plugin to its latest version, refresh
|
|
150
|
+
// the statusline (if installed) to this CLI's bundled version, and note a newer
|
|
151
|
+
// CLI. Runs after the install phase, so newly installed plugins are already
|
|
152
|
+
// latest and only pre-existing ones get bumped. Installing missing plugins is
|
|
153
|
+
// the install flags' job — this only upgrades what is present. Errors out if
|
|
154
|
+
// the network or the claude CLI is unavailable.
|
|
155
|
+
async upgrade() {
|
|
156
|
+
if (!hasBin("claude")) {
|
|
157
|
+
this.error("claude CLI not found. Install it first, then re-run.");
|
|
158
|
+
}
|
|
159
|
+
// Fail fast on the remote reads before mutating anything.
|
|
160
|
+
const latest = await remoteLatest();
|
|
161
|
+
const cliLatest = (await fetchJson(NPM_LATEST_URL)).version;
|
|
162
|
+
const installed = installedPlugins();
|
|
163
|
+
const ours = PLUGINS.filter(name => installed[name]);
|
|
164
|
+
|
|
165
|
+
this.log(
|
|
166
|
+
`\nUpgrading installed plugins (marketplace ${MARKETPLACE_NAME})…`,
|
|
167
|
+
);
|
|
168
|
+
this.runClaude(["plugin", "marketplace", "update", MARKETPLACE_NAME]);
|
|
169
|
+
|
|
170
|
+
if (!ours.length) {
|
|
171
|
+
this.log("No virajp-plugins plugins are installed — nothing to upgrade.");
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
let updated = 0;
|
|
175
|
+
for (const name of ours) {
|
|
176
|
+
const have = installed[name];
|
|
177
|
+
const want = latest[name];
|
|
178
|
+
if (want && cmpVer(want, have) > 0) {
|
|
179
|
+
this.log(`\nUpdating ${name} (${have} → ${want})…`);
|
|
180
|
+
this.runClaude(["plugin", "update", `${name}@${MARKETPLACE_NAME}`]);
|
|
181
|
+
updated++;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
this.log(
|
|
185
|
+
`${name} is up to date (${have}${want ? "" : ", external"}).`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (updated) {
|
|
190
|
+
this.log(
|
|
191
|
+
"\nPlugin updates applied — restart Claude Code to load them.",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Refresh the installed statusline script + config (no settings.json change).
|
|
197
|
+
if (existsSync(INSTALLED_SCRIPT)) {
|
|
198
|
+
this.log("\nRefreshing statusline…");
|
|
199
|
+
await this.installScript();
|
|
200
|
+
await this.seedUserConfig();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (cmpVer(cliLatest, this.config.version) > 0) {
|
|
204
|
+
this.log(
|
|
205
|
+
`\nA newer CLI is available: ${this.config.version} → ${cliLatest}`,
|
|
206
|
+
);
|
|
207
|
+
this.log("Re-run with: pnpx @askviraj/ai-plugins@latest --upgrade");
|
|
102
208
|
}
|
|
103
209
|
}
|
|
104
210
|
|
|
@@ -302,9 +408,20 @@ Installer.examples = [
|
|
|
302
408
|
"pnpx @askviraj/ai-plugins --plugins",
|
|
303
409
|
"pnpx @askviraj/ai-plugins --plugin vwf --plugin dart-lsp",
|
|
304
410
|
"pnpx @askviraj/ai-plugins --statusline --subagentstatusline --yes",
|
|
411
|
+
"pnpx @askviraj/ai-plugins --all --upgrade",
|
|
412
|
+
"pnpx @askviraj/ai-plugins --version",
|
|
305
413
|
];
|
|
306
414
|
|
|
307
415
|
Installer.flags = {
|
|
416
|
+
version: Flags.boolean({
|
|
417
|
+
char: "v",
|
|
418
|
+
description:
|
|
419
|
+
"Show CLI, statusline, and plugin versions (installed vs latest)",
|
|
420
|
+
}),
|
|
421
|
+
upgrade: Flags.boolean({
|
|
422
|
+
description:
|
|
423
|
+
"After any install, upgrade installed plugins to latest + refresh the statusline + check for a CLI update. Combine with --all for an idempotent install+upgrade (safe in setup scripts)",
|
|
424
|
+
}),
|
|
308
425
|
all: Flags.boolean({
|
|
309
426
|
description:
|
|
310
427
|
"Install everything: every marketplace plugin plus both statusline keys",
|
|
@@ -332,6 +449,101 @@ Installer.flags = {
|
|
|
332
449
|
}),
|
|
333
450
|
};
|
|
334
451
|
|
|
452
|
+
// Fetch and parse JSON over HTTP with a bounded timeout. Throws on any failure
|
|
453
|
+
// (network down, non-2xx, bad JSON) so callers can hard-error per the offline
|
|
454
|
+
// policy.
|
|
455
|
+
async function fetchJson(url) {
|
|
456
|
+
const ctrl = new AbortController();
|
|
457
|
+
const timer = setTimeout(() => ctrl.abort(), 5000);
|
|
458
|
+
try {
|
|
459
|
+
const res = await fetch(url, {
|
|
460
|
+
signal: ctrl.signal,
|
|
461
|
+
headers: { "user-agent": "askviraj-ai-plugins" },
|
|
462
|
+
});
|
|
463
|
+
if (!res.ok) {
|
|
464
|
+
throw new Error(`HTTP ${res.status} for ${url}`);
|
|
465
|
+
}
|
|
466
|
+
return await res.json();
|
|
467
|
+
}
|
|
468
|
+
finally {
|
|
469
|
+
clearTimeout(timer);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Map of plugin name → latest version from the remote marketplace manifest
|
|
474
|
+
// (null for url-sourced entries like mempalace, which pin no version here).
|
|
475
|
+
async function remoteLatest() {
|
|
476
|
+
const mp = await fetchJson(REMOTE_MARKETPLACE_URL);
|
|
477
|
+
const out = {};
|
|
478
|
+
for (const p of mp.plugins || []) {
|
|
479
|
+
out[p.name] = p.version || null;
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Map of plugin name → installed version, parsed from `claude plugin list`,
|
|
485
|
+
// restricted to the virajp-plugins marketplace. Throws if claude fails.
|
|
486
|
+
function installedPlugins() {
|
|
487
|
+
const res = spawnSync("claude", ["plugin", "list"], { encoding: "utf8" });
|
|
488
|
+
if (res.error || res.status !== 0) {
|
|
489
|
+
throw new Error(
|
|
490
|
+
"`claude plugin list` failed — is the claude CLI installed?",
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
const out = {};
|
|
494
|
+
let current = null;
|
|
495
|
+
for (const line of (res.stdout || "").split("\n")) {
|
|
496
|
+
const name = line.match(/([A-Za-z0-9_-]+)@virajp-plugins\b/);
|
|
497
|
+
if (name) {
|
|
498
|
+
current = name[1];
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (current) {
|
|
502
|
+
const ver = line.match(/Version:\s*(\S+)/);
|
|
503
|
+
if (ver) {
|
|
504
|
+
out[current] = ver[1];
|
|
505
|
+
current = null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return out;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Numeric semver compare of dotted versions: 1 if a>b, -1 if a<b, 0 if equal.
|
|
513
|
+
function cmpVer(a, b) {
|
|
514
|
+
const pa = String(a).split(".").map(n => parseInt(n, 10) || 0);
|
|
515
|
+
const pb = String(b).split(".").map(n => parseInt(n, 10) || 0);
|
|
516
|
+
for (let i = 0; i < 3; i++) {
|
|
517
|
+
if ((pa[i] || 0) > (pb[i] || 0)) {
|
|
518
|
+
return 1;
|
|
519
|
+
}
|
|
520
|
+
if ((pa[i] || 0) < (pb[i] || 0)) {
|
|
521
|
+
return -1;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return 0;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// " → X (update available)" when latest beats current, else " (latest)".
|
|
528
|
+
function updateNote(current, latest) {
|
|
529
|
+
return latest && cmpVer(latest, current) > 0
|
|
530
|
+
? ` → ${latest} (update available)`
|
|
531
|
+
: " (latest)";
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// The version column for one plugin given its installed and latest versions.
|
|
535
|
+
function pluginVersionLine(installed, latest) {
|
|
536
|
+
if (!latest) {
|
|
537
|
+
return installed
|
|
538
|
+
? `${installed} (external; not tracked here)`
|
|
539
|
+
: "not installed (external)";
|
|
540
|
+
}
|
|
541
|
+
if (!installed) {
|
|
542
|
+
return `not installed (latest ${latest})`;
|
|
543
|
+
}
|
|
544
|
+
return `${installed}${updateNote(installed, latest)}`;
|
|
545
|
+
}
|
|
546
|
+
|
|
335
547
|
// True if `bin` is an executable found on PATH.
|
|
336
548
|
function hasBin(bin) {
|
|
337
549
|
return (process.env.PATH || "")
|
package/package.json
CHANGED
|
@@ -7,6 +7,25 @@
|
|
|
7
7
|
"@oclif/core": "latest"
|
|
8
8
|
},
|
|
9
9
|
"description": "CLI to install Viraj Patel's Claude Code toolkit: marketplace plugins (via the claude CLI) and the powerline statusline",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"claude",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"claude-code-plugin",
|
|
14
|
+
"claude-code-plugins",
|
|
15
|
+
"marketplace",
|
|
16
|
+
"statusline",
|
|
17
|
+
"powerline",
|
|
18
|
+
"subagent",
|
|
19
|
+
"cli",
|
|
20
|
+
"mcp",
|
|
21
|
+
"lsp",
|
|
22
|
+
"vwf",
|
|
23
|
+
"context7",
|
|
24
|
+
"mempalace",
|
|
25
|
+
"ai",
|
|
26
|
+
"anthropic",
|
|
27
|
+
"oclif"
|
|
28
|
+
],
|
|
10
29
|
"engines": {
|
|
11
30
|
"node": ">=18"
|
|
12
31
|
},
|
|
@@ -28,5 +47,5 @@
|
|
|
28
47
|
"url": "github:virajp/ai-plugins"
|
|
29
48
|
},
|
|
30
49
|
"type": "commonjs",
|
|
31
|
-
"version": "1.0
|
|
50
|
+
"version": "1.1.0"
|
|
32
51
|
}
|
package/readme.md
CHANGED
|
@@ -52,6 +52,19 @@ Available plugin names: `vwf`, `mempalace`, `context7`, `typescript-lsp`,
|
|
|
52
52
|
> The **statusline** also installs through this CLI — see
|
|
53
53
|
> [Statusline](#statusline) below.
|
|
54
54
|
|
|
55
|
+
**Check versions, and keep everything up to date:**
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
# CLI, statusline, and each plugin's installed vs latest version
|
|
59
|
+
pnpx @askviraj/ai-plugins --version
|
|
60
|
+
|
|
61
|
+
# upgrade installed plugins + refresh the statusline (runs after any install)
|
|
62
|
+
pnpx @askviraj/ai-plugins --upgrade
|
|
63
|
+
|
|
64
|
+
# idempotent install + upgrade — safe to drop in a setup script
|
|
65
|
+
pnpx @askviraj/ai-plugins --all --upgrade
|
|
66
|
+
```
|
|
67
|
+
|
|
55
68
|
## Plugins
|
|
56
69
|
|
|
57
70
|
### vwf
|
|
@@ -161,3 +174,34 @@ Dart language server.
|
|
|
161
174
|
```sh
|
|
162
175
|
pnpx @askviraj/ai-plugins --plugin dart-lsp
|
|
163
176
|
```
|
|
177
|
+
|
|
178
|
+
## Credits & acknowledgements
|
|
179
|
+
|
|
180
|
+
This project is a thin layer over a lot of excellent work. It would not exist —
|
|
181
|
+
or would be far poorer — without these. Thank you to their authors and
|
|
182
|
+
maintainers. 🙏
|
|
183
|
+
|
|
184
|
+
- **[Claude Code](https://claude.ai/code)** by
|
|
185
|
+
[Anthropic](https://anthropic.com) — the host these plugins, hooks, and
|
|
186
|
+
statusline plug into.
|
|
187
|
+
- **[MemPalace](https://github.com/MemPalace/mempalace)** — the AI memory system
|
|
188
|
+
that powers `vwf`'s cross-session recall (re-listed here as a dependency).
|
|
189
|
+
- **[Context7](https://github.com/upstash/context7)** by
|
|
190
|
+
[Upstash](https://upstash.com) — the MCP docs server behind the `context7`
|
|
191
|
+
plugin.
|
|
192
|
+
- **[mise](https://mise.jdx.dev/)** by Jeff Dickey — resolves the toolchain the
|
|
193
|
+
LSP plugins and hooks depend on.
|
|
194
|
+
- **[pnpm](https://pnpm.io/)** — the package manager the `npm→pnpm` hook and
|
|
195
|
+
`context7` rely on.
|
|
196
|
+
- **[typescript-language-server](https://github.com/typescript-language-server/typescript-language-server)**
|
|
197
|
+
and the **[Dart SDK](https://dart.dev/)** language server — the engines behind
|
|
198
|
+
the LSP plugins.
|
|
199
|
+
- **[rtk](https://github.com/rtk-ai/rtk) (Rust Token Killer)** — the
|
|
200
|
+
token-saving proxy `vwf`'s Bash hook shells out to (installed via
|
|
201
|
+
`brew install --formulae rtk`).
|
|
202
|
+
- **[graphify](https://github.com/safishamsi/graphify)** — the knowledge-graph
|
|
203
|
+
tool `vwf` integrates with.
|
|
204
|
+
- **[oclif](https://oclif.io/)** — the framework this installer CLI is built on.
|
|
205
|
+
- **[Nerd Fonts](https://www.nerdfonts.com/)** — the glyphs that make the
|
|
206
|
+
statusline render, and the **[Gruvbox](https://github.com/morhetz/gruvbox)**
|
|
207
|
+
palette it ships by default.
|