@upgraide/ui-notes-cli 0.2.4 → 0.2.6

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 CHANGED
@@ -31,44 +31,32 @@ uinotes resolve 42 "Fixed in latest deploy"
31
31
  View or update configuration.
32
32
 
33
33
  ```bash
34
- # Show current config
35
- uinotes config
36
-
37
- # Set a value
34
+ uinotes config # Show current config
38
35
  uinotes config set apiUrl https://my-api.example.com
36
+ uinotes config set apiKey <your-key>
37
+ ```
38
+
39
+ ### `uinotes set project`
40
+
41
+ Bind a project to the current directory (creates `.uinotes` file).
39
42
 
40
- # Bind a project to the current directory
43
+ ```bash
41
44
  uinotes set project my-app
42
45
  ```
43
46
 
44
- **Config keys:** `apiUrl`, `apiKey`
45
-
46
47
  ### `uinotes projects`
47
48
 
48
49
  Manage projects.
49
50
 
50
51
  ```bash
51
- # List all projects
52
- uinotes projects
53
-
54
- # Include archived projects
55
- uinotes projects --archived
56
-
57
- # Create a project with URL patterns
58
- uinotes projects create my-app "My Application" --url "https://myapp.com/*"
59
-
60
- # Get project details
52
+ uinotes projects # List all projects
53
+ uinotes projects --archived # Include archived
54
+ uinotes projects create my-app "My App" --url "https://myapp.com/*"
61
55
  uinotes projects get my-app
62
-
63
- # Rename a project
64
56
  uinotes projects rename my-app "New Name"
65
-
66
- # Add/remove URL patterns
67
57
  uinotes projects add-url my-app "https://staging.myapp.com/*"
68
58
  uinotes projects remove-url my-app "https://old.myapp.com/*"
69
-
70
- # Archive a project
71
- uinotes projects delete my-app
59
+ uinotes projects delete my-app # Archive
72
60
  ```
73
61
 
74
62
  ### `uinotes pull`
@@ -78,31 +66,117 @@ Fetch notes from the API.
78
66
  ```bash
79
67
  uinotes pull
80
68
  uinotes pull --project my-app --status open --type bug --limit 50
69
+ uinotes pull --resolve-files --root-dir . # Map notes to source files
81
70
  ```
82
71
 
83
- | Option | Description |
84
- |--------|-------------|
85
- | `--project <name>` | Filter by project (overrides default) |
86
- | `--status <status>` | Filter by status (`open`, `resolved`, `wontfix`) |
87
- | `--type <type>` | Filter by type (`bug`, `ux`, `feature`, `question`) |
88
- | `--limit <n>` | Max number of results |
72
+ ### `uinotes context`
73
+
74
+ AI-optimized digest of open notes.
75
+
76
+ ```bash
77
+ uinotes context --project my-app
78
+ uinotes context --format xml --component Header
79
+ uinotes context --resolve-files --root-dir .
80
+ ```
89
81
 
90
- ### `uinotes resolve`
82
+ ### `uinotes explain`
91
83
 
92
- Mark a note as resolved.
84
+ Full context for a single note (body, comments, history, visual diffs).
93
85
 
94
86
  ```bash
95
- uinotes resolve <id> [message]
96
- uinotes resolve 42 "Fixed in v2.1" --project my-app
87
+ uinotes explain <note-id>
88
+ uinotes explain <note-id> --format json
97
89
  ```
98
90
 
99
- ### `uinotes wontfix`
91
+ ### `uinotes resolve` / `uinotes wontfix`
100
92
 
101
- Mark a note as won't fix.
93
+ Mark a note as resolved or won't fix.
102
94
 
103
95
  ```bash
96
+ uinotes resolve <id> [message]
104
97
  uinotes wontfix <id> [message]
105
- uinotes wontfix 99 "Out of scope for this release"
98
+ ```
99
+
100
+ ### `uinotes comment` / `uinotes comments`
101
+
102
+ Add or list comments on a note.
103
+
104
+ ```bash
105
+ uinotes comment <id> "Comment text"
106
+ uinotes comments <id>
107
+ ```
108
+
109
+ ### `uinotes resolve-by-component` / `uinotes resolve-by-selector`
110
+
111
+ Bulk resolve notes matching a component or selector.
112
+
113
+ ```bash
114
+ uinotes resolve-by-component OldWidget "Replaced with NewWidget"
115
+ uinotes resolve-by-selector ".deprecated" "Removed" --dry-run
116
+ ```
117
+
118
+ ### `uinotes changelog`
119
+
120
+ Generate a changelog from resolved notes.
121
+
122
+ ```bash
123
+ uinotes changelog --since HEAD~5
124
+ uinotes changelog --since v1.2.0 --format conventional
125
+ ```
126
+
127
+ ### `uinotes visual-diff`
128
+
129
+ Capture and compare page screenshots.
130
+
131
+ ```bash
132
+ uinotes visual-diff --project my-app
133
+ uinotes visual-diff --compare --width 1440
134
+ ```
135
+
136
+ ### `uinotes batch`
137
+
138
+ Execute NDJSON batch operations.
139
+
140
+ ```bash
141
+ echo '{"op":"resolve","id":"abc","message":"Fixed"}' | uinotes batch -
142
+ uinotes batch ops.ndjson --dry-run --concurrency 10
143
+ ```
144
+
145
+ ### `uinotes schema`
146
+
147
+ List or print API resource schemas.
148
+
149
+ ```bash
150
+ uinotes schema
151
+ uinotes schema --resource notes
152
+ ```
153
+
154
+ ### `uinotes install-skills`
155
+
156
+ Install Claude Code skill files into `.claude/skills/`.
157
+
158
+ ```bash
159
+ uinotes install-skills
160
+ uinotes install-skills --dir /path/to/project/.claude/skills
161
+ uinotes install-skills --check
162
+ ```
163
+
164
+ ### `uinotes doctor`
165
+
166
+ Check CLI configuration, API connectivity, and version.
167
+
168
+ ```bash
169
+ uinotes doctor
170
+ ```
171
+
172
+ Checks: config file, API URL, API key, API reachability, project binding, skills installed, CLI version.
173
+
174
+ ### `uinotes update`
175
+
176
+ Check for and install CLI updates.
177
+
178
+ ```bash
179
+ uinotes update
106
180
  ```
107
181
 
108
182
  ## Configuration
@@ -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.4",
3
+ "version": "0.2.6",
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"],