quiver-cli 0.2.0 → 0.4.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/README.md +15 -1
- package/dist/cli.js +136 -15
- package/package.json +1 -1
- package/template/.agents/commands/cp.md +26 -34
- package/template/.agents/commands/review.md +68 -0
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ quiver-cli check # detect drift (CI-friendly: --json, exit 1)
|
|
|
57
57
|
| `quiver-cli login` | Store a GitHub token for remote (`github:`) catalogs |
|
|
58
58
|
| `quiver-cli logout` | Remove the stored GitHub token |
|
|
59
59
|
| `quiver-cli help` | Show help |
|
|
60
|
-
| `quiver-cli version` | Show the version (`-v`, `--version`)
|
|
60
|
+
| `quiver-cli version` | Show the version + any available update (`-v`, `--version`) |
|
|
61
61
|
|
|
62
62
|
Options: `-f/--force`, `--all/-y` (non-interactive), `--json`
|
|
63
63
|
(status/check/upstream/list), `--providers=claude,opencode` (limit generated
|
|
@@ -183,6 +183,20 @@ exactly the variables the selected servers need (none needed → no file). For
|
|
|
183
183
|
Codex, secret headers are mapped to `env_http_headers` so secrets never land in
|
|
184
184
|
the committed `config.toml`.
|
|
185
185
|
|
|
186
|
+
## Staying up to date
|
|
187
|
+
|
|
188
|
+
quiver-cli checks npm for a newer release (at most once a day, cached under
|
|
189
|
+
`~/.cache/quiver/`) and prints a one-line notice after a command when an update
|
|
190
|
+
is available. `quiver-cli version` checks on demand. Update with:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
pnpm add -g quiver-cli # or: npm i -g quiver-cli / yarn global add quiver-cli
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The check is best-effort and never blocks or fails a command. It is silenced
|
|
197
|
+
automatically for `--json`, non-interactive shells and CI, and can be disabled
|
|
198
|
+
entirely with `QUIVER_NO_UPDATE_NOTIFIER=1`.
|
|
199
|
+
|
|
186
200
|
## Development
|
|
187
201
|
|
|
188
202
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -766,13 +766,6 @@ var init_remote = __esm({
|
|
|
766
766
|
});
|
|
767
767
|
|
|
768
768
|
// src/catalog/resolve.ts
|
|
769
|
-
var resolve_exports = {};
|
|
770
|
-
__export(resolve_exports, {
|
|
771
|
-
DEFAULT_CATALOG_SOURCE: () => DEFAULT_CATALOG_SOURCE,
|
|
772
|
-
isCatalogWritable: () => isCatalogWritable,
|
|
773
|
-
packageRoot: () => packageRoot,
|
|
774
|
-
resolveCatalog: () => resolveCatalog
|
|
775
|
-
});
|
|
776
769
|
import { accessSync, constants, existsSync as existsSync6 } from "fs";
|
|
777
770
|
import { dirname as dirname3, relative as relative3, resolve as resolve7, sep } from "path";
|
|
778
771
|
import { fileURLToPath } from "url";
|
|
@@ -1400,7 +1393,7 @@ var init_select = __esm({
|
|
|
1400
1393
|
"use strict";
|
|
1401
1394
|
init_prompts();
|
|
1402
1395
|
DEFAULT_SKILLS = ["find-skills", "skill-creator"];
|
|
1403
|
-
DEFAULT_COMMANDS = ["cp"];
|
|
1396
|
+
DEFAULT_COMMANDS = ["cp", "review"];
|
|
1404
1397
|
skillHint = (fm) => {
|
|
1405
1398
|
const parts = [];
|
|
1406
1399
|
if (fm.version) parts.push(`v${fm.version}`);
|
|
@@ -2978,6 +2971,114 @@ var init_logout = __esm({
|
|
|
2978
2971
|
}
|
|
2979
2972
|
});
|
|
2980
2973
|
|
|
2974
|
+
// src/version/notifier.ts
|
|
2975
|
+
var notifier_exports = {};
|
|
2976
|
+
__export(notifier_exports, {
|
|
2977
|
+
checkForUpdate: () => checkForUpdate,
|
|
2978
|
+
compareSemver: () => compareSemver,
|
|
2979
|
+
getCurrentVersion: () => getCurrentVersion,
|
|
2980
|
+
installHint: () => installHint,
|
|
2981
|
+
notifierSuppressed: () => notifierSuppressed
|
|
2982
|
+
});
|
|
2983
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
2984
|
+
import { homedir as homedir3 } from "os";
|
|
2985
|
+
import { dirname as dirname6, resolve as resolve19 } from "path";
|
|
2986
|
+
var REGISTRY_URL, CHECK_TTL_MS, FETCH_TIMEOUT_MS, INSTALL_HINT, cacheFilePath, installHint, getCurrentVersion, compareSemver, readCache, writeCache, fetchLatestVersion, checkForUpdate, notifierSuppressed;
|
|
2987
|
+
var init_notifier = __esm({
|
|
2988
|
+
"src/version/notifier.ts"() {
|
|
2989
|
+
"use strict";
|
|
2990
|
+
init_resolve();
|
|
2991
|
+
REGISTRY_URL = "https://registry.npmjs.org/quiver-cli/latest";
|
|
2992
|
+
CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2993
|
+
FETCH_TIMEOUT_MS = 2e3;
|
|
2994
|
+
INSTALL_HINT = "pnpm add -g quiver-cli";
|
|
2995
|
+
cacheFilePath = () => {
|
|
2996
|
+
const base = process.env["XDG_CACHE_HOME"] || resolve19(homedir3(), ".cache");
|
|
2997
|
+
return resolve19(base, "quiver", "update-check.json");
|
|
2998
|
+
};
|
|
2999
|
+
installHint = () => INSTALL_HINT;
|
|
3000
|
+
getCurrentVersion = () => {
|
|
3001
|
+
try {
|
|
3002
|
+
const pkg = JSON.parse(
|
|
3003
|
+
readFileSync11(resolve19(packageRoot, "package.json"), "utf8")
|
|
3004
|
+
);
|
|
3005
|
+
return pkg.version;
|
|
3006
|
+
} catch {
|
|
3007
|
+
return "0.0.0";
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
compareSemver = (a, b) => {
|
|
3011
|
+
const parse2 = (v) => {
|
|
3012
|
+
const [core = "", ...preParts] = v.replace(/^v/, "").split("-");
|
|
3013
|
+
const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
3014
|
+
while (nums.length < 3) nums.push(0);
|
|
3015
|
+
return { nums, pre: preParts.length > 0 };
|
|
3016
|
+
};
|
|
3017
|
+
const pa = parse2(a);
|
|
3018
|
+
const pb = parse2(b);
|
|
3019
|
+
for (let i = 0; i < 3; i += 1) {
|
|
3020
|
+
const da = pa.nums[i] ?? 0;
|
|
3021
|
+
const db = pb.nums[i] ?? 0;
|
|
3022
|
+
if (da !== db) return da > db ? 1 : -1;
|
|
3023
|
+
}
|
|
3024
|
+
if (pa.pre !== pb.pre) return pa.pre ? -1 : 1;
|
|
3025
|
+
return 0;
|
|
3026
|
+
};
|
|
3027
|
+
readCache = () => {
|
|
3028
|
+
const path = cacheFilePath();
|
|
3029
|
+
if (!existsSync14(path)) return null;
|
|
3030
|
+
try {
|
|
3031
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
3032
|
+
} catch {
|
|
3033
|
+
return null;
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
writeCache = (cache) => {
|
|
3037
|
+
try {
|
|
3038
|
+
const path = cacheFilePath();
|
|
3039
|
+
mkdirSync7(dirname6(path), { recursive: true });
|
|
3040
|
+
writeFileSync9(path, JSON.stringify(cache, null, 2) + "\n");
|
|
3041
|
+
} catch {
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
fetchLatestVersion = async () => {
|
|
3045
|
+
const controller = new AbortController();
|
|
3046
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
3047
|
+
try {
|
|
3048
|
+
const res = await fetch(REGISTRY_URL, {
|
|
3049
|
+
signal: controller.signal,
|
|
3050
|
+
headers: { Accept: "application/json" }
|
|
3051
|
+
});
|
|
3052
|
+
if (!res.ok) return null;
|
|
3053
|
+
const body = await res.json();
|
|
3054
|
+
return typeof body.version === "string" ? body.version : null;
|
|
3055
|
+
} catch {
|
|
3056
|
+
return null;
|
|
3057
|
+
} finally {
|
|
3058
|
+
clearTimeout(timer);
|
|
3059
|
+
}
|
|
3060
|
+
};
|
|
3061
|
+
checkForUpdate = async (options = {}) => {
|
|
3062
|
+
const current = getCurrentVersion();
|
|
3063
|
+
const cache = readCache();
|
|
3064
|
+
let latest = cache?.latest ?? null;
|
|
3065
|
+
const fresh = cache && Date.now() - new Date(cache.checkedAt).getTime() < CHECK_TTL_MS;
|
|
3066
|
+
if (options.force || !fresh) {
|
|
3067
|
+
const fetched = await fetchLatestVersion();
|
|
3068
|
+
if (fetched) {
|
|
3069
|
+
latest = fetched;
|
|
3070
|
+
writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest: fetched });
|
|
3071
|
+
} else if (!cache) {
|
|
3072
|
+
writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest: null });
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
const updateAvailable = latest !== null && compareSemver(latest, current) > 0;
|
|
3076
|
+
return { current, latest, updateAvailable };
|
|
3077
|
+
};
|
|
3078
|
+
notifierSuppressed = (json) => Boolean(process.env["QUIVER_NO_UPDATE_NOTIFIER"]) || Boolean(process.env["CI"]) || !process.stdout.isTTY || json;
|
|
3079
|
+
}
|
|
3080
|
+
});
|
|
3081
|
+
|
|
2981
3082
|
// src/cli.ts
|
|
2982
3083
|
init_prompts();
|
|
2983
3084
|
import process4 from "process";
|
|
@@ -3102,13 +3203,14 @@ var run = async () => {
|
|
|
3102
3203
|
case "version":
|
|
3103
3204
|
case "--version":
|
|
3104
3205
|
case "-v": {
|
|
3105
|
-
const {
|
|
3106
|
-
const {
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3206
|
+
const { checkForUpdate: checkForUpdate2, installHint: installHint2 } = await Promise.resolve().then(() => (init_notifier(), notifier_exports));
|
|
3207
|
+
const info2 = await checkForUpdate2({ force: true });
|
|
3208
|
+
console.log(info2.current);
|
|
3209
|
+
if (info2.updateAvailable) {
|
|
3210
|
+
console.log(
|
|
3211
|
+
`update available: ${info2.latest} (run: ${installHint2()})`
|
|
3212
|
+
);
|
|
3213
|
+
}
|
|
3112
3214
|
break;
|
|
3113
3215
|
}
|
|
3114
3216
|
default:
|
|
@@ -3117,6 +3219,25 @@ var run = async () => {
|
|
|
3117
3219
|
console.log(HELP);
|
|
3118
3220
|
process4.exitCode = 1;
|
|
3119
3221
|
}
|
|
3222
|
+
if (!["version", "--version", "-v", "help", "--help", "-h"].includes(command)) {
|
|
3223
|
+
await maybeNotifyUpdate(options.json);
|
|
3224
|
+
}
|
|
3225
|
+
};
|
|
3226
|
+
var maybeNotifyUpdate = async (json) => {
|
|
3227
|
+
try {
|
|
3228
|
+
const { checkForUpdate: checkForUpdate2, notifierSuppressed: notifierSuppressed2, installHint: installHint2 } = await Promise.resolve().then(() => (init_notifier(), notifier_exports));
|
|
3229
|
+
if (notifierSuppressed2(json)) return;
|
|
3230
|
+
const info2 = await checkForUpdate2();
|
|
3231
|
+
if (!info2.updateAvailable) return;
|
|
3232
|
+
const c = palette();
|
|
3233
|
+
console.log(
|
|
3234
|
+
`
|
|
3235
|
+
${c.yellow("\u25B2")} ${c.bold("update available")} ${c.dim(
|
|
3236
|
+
info2.current
|
|
3237
|
+
)} \u2192 ${c.cyan(info2.latest)} ${c.dim(`run: ${installHint2()}`)}`
|
|
3238
|
+
);
|
|
3239
|
+
} catch {
|
|
3240
|
+
}
|
|
3120
3241
|
};
|
|
3121
3242
|
run().catch(async (err) => {
|
|
3122
3243
|
await error(err instanceof Error ? err.message : String(err));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quiver-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Compose a selected subset of skills, commands & MCP servers from a central catalog into any repo as native configs for opencode, Claude Code and Codex - with lockfile-based drift awareness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,40 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Commit and push all current changes using Conventional Commits
|
|
3
|
+
subtask: true
|
|
3
4
|
---
|
|
4
5
|
|
|
5
|
-
You are a git commit assistant. Your job is to stage
|
|
6
|
+
You are a git commit assistant. Your job is to stage changes, create well-formed Conventional Commit messages, and push to the remote.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
### 1. Inspect Changes
|
|
10
|
-
|
|
11
|
-
Run these commands to understand the current state:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
git status
|
|
15
|
-
git diff --stat
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
If there are no changes (nothing to commit), respond with "Nothing to commit." and stop.
|
|
8
|
+
## Context
|
|
19
9
|
|
|
20
|
-
|
|
10
|
+
Status:
|
|
11
|
+
!`git status`
|
|
21
12
|
|
|
22
|
-
|
|
13
|
+
Summary:
|
|
14
|
+
!`git diff --stat`
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
SCHEMA_CHANGED=$(git diff --name-only HEAD -- prisma/schema.prisma)
|
|
26
|
-
MIGRATION_CHANGED=$(git diff --name-only HEAD -- prisma/migrations/)
|
|
27
|
-
```
|
|
16
|
+
## Steps
|
|
28
17
|
|
|
29
|
-
|
|
18
|
+
### 1. Inspect Changes
|
|
30
19
|
|
|
31
|
-
|
|
32
|
-
2. After the migration is created, verify it exists in `prisma/migrations/`.
|
|
33
|
-
3. The new migration files will be included in the commit automatically (via `git add -A`).
|
|
20
|
+
Review the status and summary above to understand the current state.
|
|
34
21
|
|
|
35
|
-
If
|
|
22
|
+
If there are no changes (nothing to commit), respond with "Nothing to commit." and stop.
|
|
36
23
|
|
|
37
|
-
###
|
|
24
|
+
### 2. Analyze Changes
|
|
38
25
|
|
|
39
26
|
Review the diff to understand what changed:
|
|
40
27
|
|
|
@@ -42,13 +29,13 @@ Review the diff to understand what changed:
|
|
|
42
29
|
git diff HEAD
|
|
43
30
|
```
|
|
44
31
|
|
|
45
|
-
###
|
|
32
|
+
### 3. Split Commits if Necessary
|
|
46
33
|
|
|
47
34
|
If changes are **unrelated** (e.g., a bug fix and a new feature), split them into separate commits. Each commit must represent **one logical change**.
|
|
48
35
|
|
|
49
|
-
To split: stage files selectively with `git add <file>`, commit, then repeat.
|
|
36
|
+
To split: stage the relevant files selectively with `git add <file>`, commit, then repeat for the next logical change.
|
|
50
37
|
|
|
51
|
-
###
|
|
38
|
+
### 4. Select Commit Type
|
|
52
39
|
|
|
53
40
|
Choose the most accurate type:
|
|
54
41
|
|
|
@@ -64,7 +51,7 @@ Choose the most accurate type:
|
|
|
64
51
|
| `chore` | Tooling, config, deps |
|
|
65
52
|
| `ci` | CI/CD only |
|
|
66
53
|
|
|
67
|
-
###
|
|
54
|
+
### 5. Compose Commit Message
|
|
68
55
|
|
|
69
56
|
Format: `<type>(<scope>): <subject>`
|
|
70
57
|
|
|
@@ -88,17 +75,21 @@ Format: `<type>(<scope>): <subject>`
|
|
|
88
75
|
|
|
89
76
|
**Forbidden subjects:** `update`, `wip`, `changes`, `fix stuff`, `misc`
|
|
90
77
|
|
|
91
|
-
###
|
|
78
|
+
### 6. Execute
|
|
79
|
+
|
|
80
|
+
Stage only the files belonging to the current logical change — never use `git add -A`:
|
|
92
81
|
|
|
93
82
|
```bash
|
|
94
|
-
git add
|
|
83
|
+
git add <file1> <file2> ...
|
|
95
84
|
git commit -m "<message>"
|
|
96
|
-
git push -u origin HEAD
|
|
97
85
|
```
|
|
98
86
|
|
|
99
|
-
If splitting commits, repeat the add/commit cycle per logical change
|
|
87
|
+
If splitting commits, repeat the add/commit cycle per logical change. Push once at the end:
|
|
88
|
+
|
|
89
|
+
- If the current branch has no upstream, set it: `git push -u origin HEAD`
|
|
90
|
+
- Otherwise: `git push`
|
|
100
91
|
|
|
101
|
-
###
|
|
92
|
+
### 7. Report
|
|
102
93
|
|
|
103
94
|
After pushing, show a summary:
|
|
104
95
|
|
|
@@ -110,6 +101,7 @@ Pushed to <branch>:
|
|
|
110
101
|
|
|
111
102
|
## Rules
|
|
112
103
|
|
|
104
|
+
- Stage selectively per logical change; NEVER use `git add -A`.
|
|
113
105
|
- NEVER amend existing commits.
|
|
114
106
|
- NEVER force push.
|
|
115
107
|
- NEVER create empty commits.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Review uncommitted changes for bugs, security and quality issues (read-only)
|
|
3
|
+
subtask: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a code reviewer. Review the changes below and report issues. You do NOT edit, stage, or commit — review only.
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
Status:
|
|
11
|
+
!`git status`
|
|
12
|
+
|
|
13
|
+
Staged diff:
|
|
14
|
+
!`git diff --staged`
|
|
15
|
+
|
|
16
|
+
Unstaged diff:
|
|
17
|
+
!`git diff`
|
|
18
|
+
|
|
19
|
+
## Steps
|
|
20
|
+
|
|
21
|
+
### 1. Determine Scope
|
|
22
|
+
|
|
23
|
+
If there are no changes in either diff above, respond with "Nothing to review." and stop.
|
|
24
|
+
|
|
25
|
+
Otherwise, skim the changed files to understand what changed and why.
|
|
26
|
+
|
|
27
|
+
### 2. Review
|
|
28
|
+
|
|
29
|
+
Assess the changed lines against the criteria below. Judge only the lines that were actually added or modified, not the surrounding untouched code.
|
|
30
|
+
|
|
31
|
+
**General**
|
|
32
|
+
|
|
33
|
+
- Obvious bugs or incorrect logic.
|
|
34
|
+
- Missing error handling or swallowed errors.
|
|
35
|
+
- Security: secrets committed in code, injection, unvalidated input.
|
|
36
|
+
- Dead or commented-out code, leftover debug logging (`console.log`).
|
|
37
|
+
- Missing tests where they are clearly warranted.
|
|
38
|
+
- Unclear names or overly broad visibility.
|
|
39
|
+
|
|
40
|
+
**TypeScript / Next.js**
|
|
41
|
+
|
|
42
|
+
- `any` or unsafe casts instead of `unknown` + narrowing.
|
|
43
|
+
- Server/client boundary: server secrets or `server-only` logic leaking into client components; `"use client"` set correctly; no `NEXT_PUBLIC_` exposure of secrets.
|
|
44
|
+
- `async`/`await` in Server Components with proper error and loading handling.
|
|
45
|
+
- Input validation at API route / server action boundaries (e.g. Zod).
|
|
46
|
+
- Re-render or dependency-array correctness (only when visible in the diff).
|
|
47
|
+
|
|
48
|
+
### 3. Report
|
|
49
|
+
|
|
50
|
+
Output the result in this format:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
## Review
|
|
54
|
+
|
|
55
|
+
Verdict: OK | Changes recommended | Blocking issues
|
|
56
|
+
|
|
57
|
+
### Findings
|
|
58
|
+
- [blocker|warning|nit] file.ts:42 — description + suggested fix
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If there are no findings, write "No issues found." under the verdict.
|
|
62
|
+
|
|
63
|
+
## Rules
|
|
64
|
+
|
|
65
|
+
- Read-only: NEVER edit, stage, or commit files.
|
|
66
|
+
- Judge only the changed lines, not unrelated surrounding code.
|
|
67
|
+
- Always reference findings with `file:line`.
|
|
68
|
+
- Skip formatting nits already covered by Prettier/ESLint.
|