@upgraide/ui-notes-cli 0.2.3 → 0.2.5
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/commands/doctor.ts +106 -0
- package/commands/update.ts +81 -0
- package/index.ts +16 -0
- package/package.json +2 -2
- package/skills/ui-notes/SKILL.md +160 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { getConfig, getConfigPath } from '../config.js';
|
|
5
|
+
|
|
6
|
+
const PACKAGE_NAME = '@upgraide/ui-notes-cli';
|
|
7
|
+
|
|
8
|
+
const PASS = '\x1b[32m✓\x1b[0m';
|
|
9
|
+
const FAIL = '\x1b[31m✗\x1b[0m';
|
|
10
|
+
const WARN = '\x1b[33m!\x1b[0m';
|
|
11
|
+
|
|
12
|
+
function check(ok: boolean, label: string, detail?: string): boolean {
|
|
13
|
+
const icon = ok ? PASS : FAIL;
|
|
14
|
+
console.log(` ${icon} ${label}${detail ? ` ${detail}` : ''}`);
|
|
15
|
+
return ok;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function warn(label: string, detail?: string): void {
|
|
19
|
+
console.log(` ${WARN} ${label}${detail ? ` ${detail}` : ''}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function doctor(): Promise<void> {
|
|
23
|
+
console.log('uinotes doctor\n');
|
|
24
|
+
let issues = 0;
|
|
25
|
+
|
|
26
|
+
// 1. Config file
|
|
27
|
+
const globalPath = join(homedir(), '.uinotes.json');
|
|
28
|
+
const localPath = join(process.cwd(), '.uinotes.json');
|
|
29
|
+
const hasGlobal = existsSync(globalPath);
|
|
30
|
+
const hasLocal = existsSync(localPath);
|
|
31
|
+
const configExists = hasGlobal || hasLocal;
|
|
32
|
+
if (!check(configExists, 'Config file', configExists ? `(${getConfigPath()})` : 'not found')) {
|
|
33
|
+
issues++;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. API URL
|
|
37
|
+
const config = getConfig();
|
|
38
|
+
const hasUrl = !!config.apiUrl;
|
|
39
|
+
if (!check(hasUrl, 'API URL', hasUrl ? config.apiUrl : 'not set')) {
|
|
40
|
+
issues++;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 3. API key
|
|
44
|
+
const hasKey = !!config.apiKey;
|
|
45
|
+
if (!check(hasKey, 'API key', hasKey ? 'configured' : 'not set — run: uinotes config set apiKey <key>')) {
|
|
46
|
+
issues++;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 4. API reachability
|
|
50
|
+
if (hasUrl && hasKey) {
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(`${config.apiUrl}/api/projects`, {
|
|
53
|
+
headers: { 'x-api-key': config.apiKey },
|
|
54
|
+
});
|
|
55
|
+
if (!check(res.ok, 'API reachable', `HTTP ${res.status}`)) {
|
|
56
|
+
issues++;
|
|
57
|
+
}
|
|
58
|
+
} catch (err: any) {
|
|
59
|
+
check(false, 'API reachable', err.message);
|
|
60
|
+
issues++;
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
warn('API reachable', 'skipped (no API key)');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 5. Project binding
|
|
67
|
+
const hasProject = !!config.project;
|
|
68
|
+
if (!check(hasProject, 'Project bound', hasProject ? config.project : 'none — run: uinotes set project <slug>')) {
|
|
69
|
+
issues++;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 6. Skills installed
|
|
73
|
+
const skillPath = join(process.cwd(), '.claude', 'skills', 'ui-notes', 'SKILL.md');
|
|
74
|
+
const skillsInstalled = existsSync(skillPath);
|
|
75
|
+
if (!check(skillsInstalled, 'Skills installed', skillsInstalled ? skillPath : 'not found — run: uinotes install-skills')) {
|
|
76
|
+
issues++;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 7. CLI version
|
|
80
|
+
const pkgPath = join(import.meta.dir, '..', 'package.json');
|
|
81
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
82
|
+
const currentVersion = pkg.version as string;
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
|
|
85
|
+
if (res.ok) {
|
|
86
|
+
const data = await res.json() as { version: string };
|
|
87
|
+
const latest = data.version;
|
|
88
|
+
const upToDate = currentVersion === latest;
|
|
89
|
+
if (!check(upToDate, 'CLI version', upToDate ? `${currentVersion} (latest)` : `${currentVersion} → ${latest} available — run: uinotes update`)) {
|
|
90
|
+
issues++;
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
warn('CLI version', `${currentVersion} (could not check registry)`);
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
warn('CLI version', `${currentVersion} (could not reach registry)`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Summary
|
|
100
|
+
console.log('');
|
|
101
|
+
if (issues === 0) {
|
|
102
|
+
console.log('All checks passed.');
|
|
103
|
+
} else {
|
|
104
|
+
console.log(`${issues} issue${issues > 1 ? 's' : ''} found.`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
const PACKAGE_NAME = '@upgraide/ui-notes-cli';
|
|
5
|
+
|
|
6
|
+
function parseVersion(v: string): number[] {
|
|
7
|
+
return v.replace(/^v/, '').split('.').map(Number);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isNewer(latest: number[], current: number[]): boolean {
|
|
11
|
+
for (let i = 0; i < Math.max(latest.length, current.length); i++) {
|
|
12
|
+
const a = latest[i] ?? 0;
|
|
13
|
+
const b = current[i] ?? 0;
|
|
14
|
+
if (a > b) return true;
|
|
15
|
+
if (a < b) return false;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function detectPackageManager(): Promise<'bun' | 'npm'> {
|
|
21
|
+
try {
|
|
22
|
+
const proc = Bun.spawn(['bun', '--version'], { stdout: 'pipe', stderr: 'pipe' });
|
|
23
|
+
await proc.exited;
|
|
24
|
+
if (proc.exitCode === 0) return 'bun';
|
|
25
|
+
} catch {}
|
|
26
|
+
return 'npm';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function update(): Promise<void> {
|
|
30
|
+
// Read current version
|
|
31
|
+
const pkgPath = join(import.meta.dir, '..', 'package.json');
|
|
32
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
33
|
+
const currentVersion = pkg.version as string;
|
|
34
|
+
|
|
35
|
+
console.log(`Current version: ${currentVersion}`);
|
|
36
|
+
console.log('Checking for updates...');
|
|
37
|
+
|
|
38
|
+
// Fetch latest from npm registry
|
|
39
|
+
let latestVersion: string;
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
console.error(`Failed to check registry (HTTP ${res.status})`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
const data = await res.json() as { version: string };
|
|
47
|
+
latestVersion = data.version;
|
|
48
|
+
} catch (err: any) {
|
|
49
|
+
console.error(`Failed to reach npm registry: ${err.message}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const current = parseVersion(currentVersion);
|
|
54
|
+
const latest = parseVersion(latestVersion);
|
|
55
|
+
|
|
56
|
+
if (!isNewer(latest, current)) {
|
|
57
|
+
console.log(`Already up to date (${currentVersion})`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(`New version available: ${latestVersion}`);
|
|
62
|
+
|
|
63
|
+
const pm = await detectPackageManager();
|
|
64
|
+
const cmd = pm === 'bun'
|
|
65
|
+
? ['bun', 'add', '-g', PACKAGE_NAME]
|
|
66
|
+
: ['npm', 'install', '-g', PACKAGE_NAME];
|
|
67
|
+
|
|
68
|
+
console.log(`Installing with ${pm}...`);
|
|
69
|
+
const proc = Bun.spawn(cmd, {
|
|
70
|
+
stdout: 'inherit',
|
|
71
|
+
stderr: 'inherit',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const exitCode = await proc.exited;
|
|
75
|
+
if (exitCode !== 0) {
|
|
76
|
+
console.error('Update failed.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(`Updated to ${latestVersion}`);
|
|
81
|
+
}
|
package/index.ts
CHANGED
|
@@ -36,6 +36,8 @@ Commands:
|
|
|
36
36
|
batch <file|-> Execute NDJSON batch operations
|
|
37
37
|
schema List or print API resource schemas
|
|
38
38
|
install-skills Install Claude Code skill files
|
|
39
|
+
doctor Check CLI config, connectivity & version
|
|
40
|
+
update Check for and install CLI updates
|
|
39
41
|
config View/edit configuration
|
|
40
42
|
|
|
41
43
|
Options:
|
|
@@ -143,6 +145,20 @@ async function main() {
|
|
|
143
145
|
return;
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
// doctor is local-only, no API key needed
|
|
149
|
+
if (command === 'doctor') {
|
|
150
|
+
const { doctor } = await import('./commands/doctor.js');
|
|
151
|
+
await doctor();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// update is local-only, no API key needed
|
|
156
|
+
if (command === 'update') {
|
|
157
|
+
const { update } = await import('./commands/update.js');
|
|
158
|
+
await update();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
146
162
|
// install-skills is local-only, no API key needed
|
|
147
163
|
if (command === 'install-skills') {
|
|
148
164
|
const { installSkills } = await import('./commands/install-skills.js');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upgraide/ui-notes-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "CLI for UI Notes - pull and manage UI feedback notes",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": ["cli", "ui", "notes", "feedback", "ui-notes"],
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"formatters/*.ts",
|
|
14
14
|
"resolvers/*.ts",
|
|
15
15
|
"schemas/*.json",
|
|
16
|
-
"skills
|
|
16
|
+
"skills/**/*.md"
|
|
17
17
|
],
|
|
18
18
|
"engines": {
|
|
19
19
|
"bun": ">=1.0.0"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-notes
|
|
3
|
+
description: CLI commands for managing UI feedback notes — pull context, resolve issues, add comments, generate changelogs, and batch triage.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# UI Notes CLI
|
|
7
|
+
|
|
8
|
+
`uinotes` is the CLI for UI Notes. Use these commands to triage, resolve, and manage UI feedback notes from your terminal.
|
|
9
|
+
|
|
10
|
+
## Configuration
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uinotes config # Show current config
|
|
14
|
+
uinotes config set apiUrl <url> # Set API URL
|
|
15
|
+
uinotes config set apiKey <key> # Set API key
|
|
16
|
+
uinotes set project <slug> # Bind project to current directory
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Context
|
|
20
|
+
|
|
21
|
+
Fetch an AI-optimized digest of open UI notes.
|
|
22
|
+
|
|
23
|
+
### When to use
|
|
24
|
+
- At the start of a task to understand what UI issues exist
|
|
25
|
+
- Before making UI changes to check for related notes
|
|
26
|
+
- When the user asks about UI bugs, feedback, or design issues
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uinotes context --format markdown
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Add `--resolve-files --root-dir .` to map notes to source files.
|
|
33
|
+
Add `--type bug` to filter by type (bug, ux, feature, question).
|
|
34
|
+
Add `--project <slug>` to target a specific project.
|
|
35
|
+
|
|
36
|
+
## Explain
|
|
37
|
+
|
|
38
|
+
Get full context for a single note including comments, history, and visual diffs.
|
|
39
|
+
|
|
40
|
+
### When to use
|
|
41
|
+
- When investigating a specific note before fixing it
|
|
42
|
+
- When you need comments, history, or visual diff context
|
|
43
|
+
- When triaging a single note
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uinotes explain <note-id>
|
|
47
|
+
uinotes explain <note-id> --format json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Returns: note body, type, status, component, selector, URL, screenshot URL, all comments, status history, and visual diffs.
|
|
51
|
+
|
|
52
|
+
## Resolve
|
|
53
|
+
|
|
54
|
+
Mark notes as resolved after fixing the issue they describe.
|
|
55
|
+
|
|
56
|
+
### When to use
|
|
57
|
+
- After fixing a bug, UX issue, or implementing a feature request described in a note
|
|
58
|
+
- After verifying a fix addresses the feedback
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Resolve with a message explaining the fix
|
|
62
|
+
uinotes resolve <note-id> "Fixed by updating the component"
|
|
63
|
+
|
|
64
|
+
# Mark as won't fix with explanation
|
|
65
|
+
uinotes wontfix <note-id> "Working as intended"
|
|
66
|
+
|
|
67
|
+
# Bulk resolve all notes for a component
|
|
68
|
+
uinotes resolve-by-component <ComponentName> --message "Refactored component"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Always include a resolution message describing what was done.
|
|
72
|
+
|
|
73
|
+
## Comment
|
|
74
|
+
|
|
75
|
+
Add comments to notes to document progress or communicate.
|
|
76
|
+
|
|
77
|
+
### When to use
|
|
78
|
+
- To document what was tried or what's blocking a fix
|
|
79
|
+
- To ask clarifying questions about a note
|
|
80
|
+
- To provide a status update before resolving
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uinotes comment <note-id> "Comment text here"
|
|
84
|
+
uinotes comments <note-id> # List existing comments
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Changelog
|
|
88
|
+
|
|
89
|
+
Generate a changelog from notes resolved since a git ref or date.
|
|
90
|
+
|
|
91
|
+
### When to use
|
|
92
|
+
- After resolving notes, to generate a summary for PR descriptions
|
|
93
|
+
- For release notes listing UI fixes
|
|
94
|
+
- To audit what was fixed in a time period
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
uinotes changelog --since HEAD~5
|
|
98
|
+
uinotes changelog --since v1.2.0 --format conventional
|
|
99
|
+
uinotes changelog --since 2026-02-01 --format json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Formats: `markdown` (default), `conventional` (conventional commits), `json`.
|
|
103
|
+
|
|
104
|
+
## Batch
|
|
105
|
+
|
|
106
|
+
Execute multiple operations (resolve, wontfix, comment, reopen) in one invocation.
|
|
107
|
+
|
|
108
|
+
### When to use
|
|
109
|
+
- After triaging multiple notes and deciding actions for each
|
|
110
|
+
- When resolving several notes as part of a single fix
|
|
111
|
+
- For bulk operations that span multiple notes
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
echo '{"op":"resolve","id":"abc123","message":"Fixed overflow"}
|
|
115
|
+
{"op":"comment","id":"def456","text":"Needs design input"}
|
|
116
|
+
{"op":"wontfix","id":"ghi789","message":"Deprecated component"}' | uinotes batch -
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Operations: `resolve`, `wontfix`, `comment` (requires `text`), `reopen`.
|
|
120
|
+
Add `--dry-run` to preview without executing.
|
|
121
|
+
|
|
122
|
+
## Pull
|
|
123
|
+
|
|
124
|
+
Fetch raw note data from the API.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uinotes pull
|
|
128
|
+
uinotes pull --project my-app --status open --type bug --limit 50
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| Option | Description |
|
|
132
|
+
|--------|-------------|
|
|
133
|
+
| `--project <name>` | Filter by project |
|
|
134
|
+
| `--status <status>` | Filter by status (`open`, `resolved`, `wontfix`) |
|
|
135
|
+
| `--type <type>` | Filter by type (`bug`, `ux`, `feature`, `question`) |
|
|
136
|
+
| `--limit <n>` | Max number of results |
|
|
137
|
+
| `--resolve-files` | Map notes to source files |
|
|
138
|
+
|
|
139
|
+
## Projects
|
|
140
|
+
|
|
141
|
+
Manage projects.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
uinotes projects # List all projects
|
|
145
|
+
uinotes projects create <slug> <name> # Create a project
|
|
146
|
+
uinotes projects get <slug> # Get project details
|
|
147
|
+
uinotes projects rename <slug> <name> # Rename a project
|
|
148
|
+
uinotes projects add-url <slug> <pattern> # Add URL pattern
|
|
149
|
+
uinotes projects remove-url <slug> <pattern> # Remove URL pattern
|
|
150
|
+
uinotes projects delete <slug> # Archive a project
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Visual Diff
|
|
154
|
+
|
|
155
|
+
Capture and compare page screenshots.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
uinotes visual-diff --url https://myapp.com --project my-app
|
|
159
|
+
uinotes visual-diff --compare # Compare against previous screenshots
|
|
160
|
+
```
|