@upgraide/ui-notes-cli 0.2.0 → 0.2.2
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 +14 -4
- package/api.ts +1 -1
- package/commands/bulk-resolve.ts +2 -2
- package/commands/config.ts +1 -1
- package/commands/context.ts +1 -1
- package/commands/install-skills.ts +58 -0
- package/commands/set.ts +9 -0
- package/commands/visual-diff.ts +2 -2
- package/config.ts +39 -8
- package/index.ts +32 -1
- package/package.json +3 -2
- package/skills/uinotes-batch.md +23 -0
- package/skills/uinotes-changelog.md +22 -0
- package/skills/uinotes-comment.md +19 -0
- package/skills/uinotes-context.md +22 -0
- package/skills/uinotes-explain.md +21 -0
- package/skills/uinotes-resolve.md +26 -0
package/README.md
CHANGED
|
@@ -45,10 +45,12 @@ uinotes config
|
|
|
45
45
|
|
|
46
46
|
# Set a value
|
|
47
47
|
uinotes config set apiUrl https://my-api.example.com
|
|
48
|
-
|
|
48
|
+
|
|
49
|
+
# Bind a project to the current directory
|
|
50
|
+
uinotes set project my-app
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
**Config keys:** `apiUrl`, `apiKey
|
|
53
|
+
**Config keys:** `apiUrl`, `apiKey`
|
|
52
54
|
|
|
53
55
|
### `uinotes projects`
|
|
54
56
|
|
|
@@ -122,11 +124,19 @@ The CLI looks for config in two places (local takes priority):
|
|
|
122
124
|
```json
|
|
123
125
|
{
|
|
124
126
|
"apiUrl": "http://localhost:3000",
|
|
125
|
-
"apiKey": "your-api-key"
|
|
126
|
-
"defaultProject": "my-app"
|
|
127
|
+
"apiKey": "your-api-key"
|
|
127
128
|
}
|
|
128
129
|
```
|
|
129
130
|
|
|
131
|
+
### Project Binding
|
|
132
|
+
|
|
133
|
+
Run `uinotes set project <slug>` to create a `.uinotes` file in the current directory. This binds the project to the directory tree so all commands automatically use it — no `--project` flag needed.
|
|
134
|
+
|
|
135
|
+
The CLI resolves the project in this order:
|
|
136
|
+
1. `--project` flag (explicit override)
|
|
137
|
+
2. `.uinotes` file in current directory or nearest parent
|
|
138
|
+
3. No project (commands that require one will error)
|
|
139
|
+
|
|
130
140
|
API keys are redacted when displayed with `uinotes config`.
|
|
131
141
|
|
|
132
142
|
## Global Options
|
package/api.ts
CHANGED
package/commands/bulk-resolve.ts
CHANGED
|
@@ -31,9 +31,9 @@ async function bulkResolve(
|
|
|
31
31
|
filterValue: string,
|
|
32
32
|
opts: BulkResolveOptions,
|
|
33
33
|
): Promise<void> {
|
|
34
|
-
const project = opts.project || config.
|
|
34
|
+
const project = opts.project || config.project;
|
|
35
35
|
if (!project) {
|
|
36
|
-
console.error('No project specified. Use --project or set
|
|
36
|
+
console.error('No project specified. Use --project or run: uinotes set project <slug>');
|
|
37
37
|
process.exit(1);
|
|
38
38
|
}
|
|
39
39
|
|
package/commands/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getConfig, getConfigPath, setConfigValue } from '../config.js';
|
|
2
2
|
|
|
3
|
-
const VALID_KEYS = ['apiUrl', 'apiKey'
|
|
3
|
+
const VALID_KEYS = ['apiUrl', 'apiKey'];
|
|
4
4
|
|
|
5
5
|
function redactKey(value: string): string {
|
|
6
6
|
if (!value || value.length <= 4) return '****';
|
package/commands/context.ts
CHANGED
|
@@ -55,7 +55,7 @@ export async function context(config: Config, opts: ContextOptions): Promise<voi
|
|
|
55
55
|
const output = formatNotesForAI(notes, {
|
|
56
56
|
format: opts.format || 'markdown',
|
|
57
57
|
componentFilter: opts.component,
|
|
58
|
-
projectName: opts.project || config.
|
|
58
|
+
projectName: opts.project || config.project || 'unknown',
|
|
59
59
|
resolvedNotes: resolvedMap,
|
|
60
60
|
});
|
|
61
61
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Config } from '../config.js';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
interface InstallSkillsOptions {
|
|
6
|
+
dir?: string;
|
|
7
|
+
check?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const DEFAULT_DIR = '.claude/skills';
|
|
11
|
+
|
|
12
|
+
export async function installSkills(_config: Config, opts: InstallSkillsOptions): Promise<void> {
|
|
13
|
+
const targetDir = opts.dir || join(process.cwd(), DEFAULT_DIR);
|
|
14
|
+
const skillsSourceDir = join(import.meta.dir, '..', 'skills');
|
|
15
|
+
|
|
16
|
+
// List available skills
|
|
17
|
+
let skillFiles: string[];
|
|
18
|
+
try {
|
|
19
|
+
skillFiles = readdirSync(skillsSourceDir).filter(f => f.endsWith('.md'));
|
|
20
|
+
} catch {
|
|
21
|
+
console.error('Could not read bundled skill files.');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (skillFiles.length === 0) {
|
|
26
|
+
console.error('No skill files found in package.');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (opts.check) {
|
|
31
|
+
// Check installed vs bundled
|
|
32
|
+
console.log('Skill status:');
|
|
33
|
+
for (const file of skillFiles) {
|
|
34
|
+
const targetPath = join(targetDir, file);
|
|
35
|
+
if (existsSync(targetPath)) {
|
|
36
|
+
console.log(` ${file}: installed`);
|
|
37
|
+
} else {
|
|
38
|
+
console.log(` ${file}: not installed`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Install skills
|
|
45
|
+
mkdirSync(targetDir, { recursive: true });
|
|
46
|
+
|
|
47
|
+
for (const file of skillFiles) {
|
|
48
|
+
const sourcePath = join(skillsSourceDir, file);
|
|
49
|
+
const targetPath = join(targetDir, file);
|
|
50
|
+
const content = readFileSync(sourcePath, 'utf-8');
|
|
51
|
+
writeFileSync(targetPath, content);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`Installed ${skillFiles.length} skills to ${targetDir}/`);
|
|
55
|
+
for (const file of skillFiles) {
|
|
56
|
+
console.log(` ${file}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/commands/set.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
export function setProject(slug: string): void {
|
|
5
|
+
const filePath = join(process.cwd(), '.uinotes');
|
|
6
|
+
writeFileSync(filePath, JSON.stringify({ project: slug }, null, 2) + '\n');
|
|
7
|
+
console.log(`Bound project "${slug}" to ${process.cwd()}`);
|
|
8
|
+
console.log(`Created .uinotes — commit this file so your team and AI agents inherit it.`);
|
|
9
|
+
}
|
package/commands/visual-diff.ts
CHANGED
|
@@ -37,9 +37,9 @@ async function loadPlaywright() {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export async function visualDiff(config: Config, opts: VisualDiffOptions): Promise<void> {
|
|
40
|
-
const project = opts.project || config.
|
|
40
|
+
const project = opts.project || config.project;
|
|
41
41
|
if (!project) {
|
|
42
|
-
console.error('Error: --project is required (or set
|
|
42
|
+
console.error('Error: --project is required (or run: uinotes set project <slug>)');
|
|
43
43
|
process.exit(1);
|
|
44
44
|
}
|
|
45
45
|
|
package/config.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface ResolveConfig {
|
|
|
11
11
|
export interface Config {
|
|
12
12
|
apiUrl: string;
|
|
13
13
|
apiKey: string;
|
|
14
|
-
|
|
14
|
+
project?: string;
|
|
15
15
|
resolve?: ResolveConfig;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -23,11 +23,34 @@ const DEFAULT_CONFIG: Config = {
|
|
|
23
23
|
apiKey: '',
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
function readJsonFile(path: string):
|
|
26
|
+
function readJsonFile(path: string): Record<string, any> | null {
|
|
27
27
|
if (!existsSync(path)) return null;
|
|
28
28
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Walk up the directory tree from cwd looking for a .uinotes file
|
|
33
|
+
* that binds a project slug to this directory tree.
|
|
34
|
+
*/
|
|
35
|
+
function findDirectoryProject(startDir: string): string | undefined {
|
|
36
|
+
let dir = startDir;
|
|
37
|
+
while (true) {
|
|
38
|
+
const filePath = join(dir, '.uinotes');
|
|
39
|
+
if (existsSync(filePath)) {
|
|
40
|
+
try {
|
|
41
|
+
const content = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
42
|
+
if (content.project) return content.project;
|
|
43
|
+
} catch {
|
|
44
|
+
// Invalid JSON — skip
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const parent = dirname(dir);
|
|
48
|
+
if (parent === dir) break; // reached filesystem root
|
|
49
|
+
dir = parent;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
export function getConfigPath(): string {
|
|
32
55
|
if (existsSync(LOCAL_PATH)) return LOCAL_PATH;
|
|
33
56
|
return GLOBAL_PATH;
|
|
@@ -36,14 +59,22 @@ export function getConfigPath(): string {
|
|
|
36
59
|
export function getConfig(): Config {
|
|
37
60
|
// Per-repo config takes priority
|
|
38
61
|
const local = readJsonFile(LOCAL_PATH);
|
|
39
|
-
|
|
62
|
+
const base = local
|
|
63
|
+
? { ...DEFAULT_CONFIG, ...local }
|
|
64
|
+
: (() => {
|
|
65
|
+
const global = readJsonFile(GLOBAL_PATH);
|
|
66
|
+
if (global) return { ...DEFAULT_CONFIG, ...global };
|
|
67
|
+
// Create default global config
|
|
68
|
+
writeFileSync(GLOBAL_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n');
|
|
69
|
+
return { ...DEFAULT_CONFIG };
|
|
70
|
+
})();
|
|
40
71
|
|
|
41
|
-
|
|
42
|
-
if (
|
|
72
|
+
// Resolve project from .uinotes directory binding (if not already set)
|
|
73
|
+
if (!base.project) {
|
|
74
|
+
base.project = findDirectoryProject(process.cwd());
|
|
75
|
+
}
|
|
43
76
|
|
|
44
|
-
|
|
45
|
-
writeFileSync(GLOBAL_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n');
|
|
46
|
-
return DEFAULT_CONFIG;
|
|
77
|
+
return base as Config;
|
|
47
78
|
}
|
|
48
79
|
|
|
49
80
|
export function setConfigValue(key: string, value: string): Config {
|
package/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ Commands:
|
|
|
22
22
|
projects add-url <slug> <pattern> Add URL pattern to project
|
|
23
23
|
projects remove-url <slug> <pattern> Remove URL pattern
|
|
24
24
|
projects delete <slug> Archive a project
|
|
25
|
+
set project <slug> Bind project to current directory
|
|
25
26
|
login Login and create API key
|
|
26
27
|
pull Fetch open notes
|
|
27
28
|
context AI-optimized digest of open notes
|
|
@@ -36,6 +37,7 @@ Commands:
|
|
|
36
37
|
visual-diff Capture & compare page screenshots
|
|
37
38
|
batch <file|-> Execute NDJSON batch operations
|
|
38
39
|
schema List or print API resource schemas
|
|
40
|
+
install-skills Install Claude Code skill files
|
|
39
41
|
config View/edit configuration
|
|
40
42
|
|
|
41
43
|
Options:
|
|
@@ -53,6 +55,8 @@ Options:
|
|
|
53
55
|
--width <px> Viewport width for screenshots (default: 1280)
|
|
54
56
|
--full-page Capture full page height (default: true)
|
|
55
57
|
--since <ref> Git ref or ISO date for changelog start
|
|
58
|
+
--dir <path> Target directory for skill installation
|
|
59
|
+
--check Check installed skill versions
|
|
56
60
|
--resource <name> Resource name for schema command
|
|
57
61
|
--concurrency <n> Concurrent batch operations (default: 5)
|
|
58
62
|
--dry-run Preview batch operations without executing`;
|
|
@@ -124,6 +128,23 @@ async function main() {
|
|
|
124
128
|
return;
|
|
125
129
|
}
|
|
126
130
|
|
|
131
|
+
// set command is local-only, no API key needed
|
|
132
|
+
if (command === 'set') {
|
|
133
|
+
const sub = positional[1];
|
|
134
|
+
if (sub === 'project') {
|
|
135
|
+
const slug = positional[2];
|
|
136
|
+
if (!slug) {
|
|
137
|
+
console.error('Usage: uinotes set project <slug>');
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
const { setProject } = await import('./commands/set.js');
|
|
141
|
+
setProject(slug);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
console.error(`Unknown set subcommand: ${sub}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
|
|
127
148
|
// Schema command is local-only, no API key needed
|
|
128
149
|
if (command === 'schema') {
|
|
129
150
|
const { schema } = await import('./commands/schema.js');
|
|
@@ -131,13 +152,23 @@ async function main() {
|
|
|
131
152
|
return;
|
|
132
153
|
}
|
|
133
154
|
|
|
155
|
+
// install-skills is local-only, no API key needed
|
|
156
|
+
if (command === 'install-skills') {
|
|
157
|
+
const { installSkills } = await import('./commands/install-skills.js');
|
|
158
|
+
await installSkills(getConfig(), {
|
|
159
|
+
dir: flags.dir,
|
|
160
|
+
check: flags.check === 'true',
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
134
165
|
const config = getConfig();
|
|
135
166
|
if (!config.apiKey) {
|
|
136
167
|
console.error('No API key configured. Run: uinotes config set apiKey <your-key>');
|
|
137
168
|
process.exit(1);
|
|
138
169
|
}
|
|
139
170
|
|
|
140
|
-
const project = flags.project;
|
|
171
|
+
const project = flags.project || config.project;
|
|
141
172
|
|
|
142
173
|
switch (command) {
|
|
143
174
|
case 'projects': {
|
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.2",
|
|
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"],
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"commands/*.ts",
|
|
13
13
|
"formatters/*.ts",
|
|
14
14
|
"resolvers/*.ts",
|
|
15
|
-
"schemas/*.json"
|
|
15
|
+
"schemas/*.json",
|
|
16
|
+
"skills/*.md"
|
|
16
17
|
],
|
|
17
18
|
"engines": {
|
|
18
19
|
"bun": ">=1.0.0"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uinotes-batch
|
|
3
|
+
description: Execute multiple note operations in a single call using NDJSON. Use for bulk triage of multiple notes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# uinotes-batch
|
|
7
|
+
|
|
8
|
+
Execute multiple operations (resolve, wontfix, comment, reopen) in one invocation.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
- After triaging multiple notes and deciding actions for each
|
|
12
|
+
- When resolving several notes as part of a single fix
|
|
13
|
+
- For bulk operations that span multiple notes
|
|
14
|
+
|
|
15
|
+
## Command
|
|
16
|
+
```bash
|
|
17
|
+
echo '{"op":"resolve","id":"abc123","message":"Fixed overflow"}
|
|
18
|
+
{"op":"comment","id":"def456","text":"Needs design input"}
|
|
19
|
+
{"op":"wontfix","id":"ghi789","message":"Deprecated component"}' | uinotes batch -
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Operations: `resolve`, `wontfix`, `comment` (requires `text`), `reopen`.
|
|
23
|
+
Add `--dry-run` to preview without executing.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uinotes-changelog
|
|
3
|
+
description: Generate a changelog from resolved UI notes. Use after resolving notes to create release notes or PR descriptions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# uinotes-changelog
|
|
7
|
+
|
|
8
|
+
Generate a changelog from notes resolved since a git ref or date.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
- After resolving notes, to generate a summary for PR descriptions
|
|
12
|
+
- For release notes listing UI fixes
|
|
13
|
+
- To audit what was fixed in a time period
|
|
14
|
+
|
|
15
|
+
## Command
|
|
16
|
+
```bash
|
|
17
|
+
uinotes changelog --since HEAD~5
|
|
18
|
+
uinotes changelog --since v1.2.0 --format conventional
|
|
19
|
+
uinotes changelog --since 2026-02-01 --format json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Formats: `markdown` (default), `conventional` (conventional commits), `json`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uinotes-comment
|
|
3
|
+
description: Add a comment to a UI note. Use to document progress, ask questions, or provide status updates on notes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# uinotes-comment
|
|
7
|
+
|
|
8
|
+
Add comments to notes to document progress or communicate.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
- To document what was tried or what's blocking a fix
|
|
12
|
+
- To ask clarifying questions about a note
|
|
13
|
+
- To provide a status update before resolving
|
|
14
|
+
|
|
15
|
+
## Command
|
|
16
|
+
```bash
|
|
17
|
+
uinotes comment <note-id> "Comment text here"
|
|
18
|
+
uinotes comments <note-id> # List existing comments
|
|
19
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uinotes-context
|
|
3
|
+
description: Fetch AI-optimized context of open UI notes for the current project. Use when asked to check for UI bugs, feedback, or notes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# uinotes-context
|
|
7
|
+
|
|
8
|
+
Run `uinotes context` to get a structured digest of all open UI notes.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
- At the start of a task to understand what UI issues exist
|
|
12
|
+
- Before making UI changes to check for related notes
|
|
13
|
+
- When the user asks about UI bugs, feedback, or design issues
|
|
14
|
+
|
|
15
|
+
## Command
|
|
16
|
+
```bash
|
|
17
|
+
uinotes context --format markdown
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Add `--resolve-files --root-dir .` to map notes to source files.
|
|
21
|
+
Add `--type bug` to filter by type (bug, ux, feature, question).
|
|
22
|
+
Add `--project <slug>` to target a specific project.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uinotes-explain
|
|
3
|
+
description: Get full context for a single note including comments, history, and visual diffs. Use when investigating a specific note.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# uinotes-explain
|
|
7
|
+
|
|
8
|
+
Get fully-hydrated context for a single note in one call.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
- When investigating a specific note before fixing it
|
|
12
|
+
- When you need comments, history, or visual diff context
|
|
13
|
+
- When triaging a single note
|
|
14
|
+
|
|
15
|
+
## Command
|
|
16
|
+
```bash
|
|
17
|
+
uinotes explain <note-id>
|
|
18
|
+
uinotes explain <note-id> --format json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Returns: note body, type, status, component, selector, URL, screenshot URL, all comments, status history, and visual diffs.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uinotes-resolve
|
|
3
|
+
description: Mark a UI note as resolved after fixing it. Use after completing a fix for a UI issue.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# uinotes-resolve
|
|
7
|
+
|
|
8
|
+
Mark notes as resolved after fixing the issue they describe.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
- After fixing a bug, UX issue, or implementing a feature request described in a note
|
|
12
|
+
- After verifying a fix addresses the feedback
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
```bash
|
|
16
|
+
# Resolve with a message explaining the fix
|
|
17
|
+
uinotes resolve <note-id> "Fixed by updating the component"
|
|
18
|
+
|
|
19
|
+
# Mark as won't fix with explanation
|
|
20
|
+
uinotes wontfix <note-id> "Working as intended"
|
|
21
|
+
|
|
22
|
+
# Bulk resolve all notes for a component
|
|
23
|
+
uinotes resolve-by-component <ComponentName> --message "Refactored component"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Always include a resolution message describing what was done.
|