analyzthis_design 2.0.0 → 2.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/HOW-TO-USE.md +436 -0
- package/README.md +29 -13
- package/agents/cards/evolve-check.md +38 -0
- package/agents/manifests/evolve-check.json +16 -0
- package/dist/HOW-TO-USE.md +15 -3
- package/dist/README.md +29 -13
- package/dist/agents/cards/evolve-check.md +38 -0
- package/dist/agents/manifests/evolve-check.json +16 -0
- package/dist/bin/cli.js +1225 -1
- package/dist/lib/cache.js +111 -1
- package/dist/lib/chunk-executor.js +219 -1
- package/dist/lib/chunk-models.js +228 -1
- package/dist/lib/chunk-planner.js +328 -1
- package/dist/lib/chunk-router.js +66 -1
- package/dist/lib/chunk-run.js +199 -1
- package/dist/lib/chunk-synthesis.js +176 -1
- package/dist/lib/chunk-telemetry.js +88 -1
- package/dist/lib/collect.js +858 -1
- package/dist/lib/cost.js +119 -1
- package/dist/lib/dedup.js +167 -1
- package/dist/lib/deliberation.js +721 -1
- package/dist/lib/design-spec.js +236 -1
- package/dist/lib/evolution-metrics.js +197 -0
- package/dist/lib/evolve.js +361 -1
- package/dist/lib/export.js +77 -1
- package/dist/lib/feedback-submit.js +324 -1
- package/dist/lib/feedback.js +182 -1
- package/dist/lib/host-llm.js +251 -1
- package/dist/lib/install.js +301 -1
- package/dist/lib/knowledge.js +384 -1
- package/dist/lib/lessons.js +217 -1
- package/dist/lib/moodboard.js +563 -1
- package/dist/lib/orchestrator/run.js +935 -1
- package/dist/lib/outcome.js +193 -1
- package/dist/lib/platforms.js +166 -1
- package/dist/lib/provider.js +57 -1
- package/dist/lib/query-expander.js +83 -1
- package/dist/lib/ranker.js +105 -1
- package/dist/lib/reference-pack.js +221 -0
- package/dist/lib/research.js +143 -1
- package/dist/lib/retrieve.js +131 -1
- package/dist/lib/session.js +185 -1
- package/dist/lib/source-discovery.js +486 -1
- package/dist/lib/synthesis.js +155 -1
- package/dist/lib/token-gate.js +46 -1
- package/dist/skills/design-reference/google-fonts.csv +1924 -1924
- package/dist/skills/design-reference/products.csv +162 -162
- package/dist/skills/design-reference/schema.json +159 -0
- package/dist/skills/design-reference/stacks/angular.csv +1 -1
- package/dist/skills/design-reference/stacks/astro.csv +1 -1
- package/dist/skills/design-reference/stacks/laravel.csv +2 -2
- package/dist/skills/design-reference/stacks/threejs.csv +54 -54
- package/dist/skills/design-reference/styles.csv +85 -85
- package/dist/skills/design-reference/typography.csv +75 -74
- package/dist/skills/design-reference/ui-reasoning.csv +1 -1
- package/dist/skills/evolve-check/SKILL.md +106 -0
- package/package.json +8 -8
- package/scripts/validate-csvs.js +197 -0
- package/skills/design-reference/google-fonts.csv +1924 -1924
- package/skills/design-reference/products.csv +162 -162
- package/skills/design-reference/schema.json +159 -0
- package/skills/design-reference/stacks/angular.csv +1 -1
- package/skills/design-reference/stacks/astro.csv +1 -1
- package/skills/design-reference/stacks/laravel.csv +2 -2
- package/skills/design-reference/stacks/threejs.csv +54 -54
- package/skills/design-reference/styles.csv +85 -85
- package/skills/design-reference/typography.csv +75 -74
- package/skills/design-reference/ui-reasoning.csv +1 -1
- package/skills/evolve-check/SKILL.md +106 -0
package/dist/lib/host-llm.js
CHANGED
|
@@ -1 +1,251 @@
|
|
|
1
|
-
'use strict';
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Host LLM bridge — orchestrator writes persona prompts; Devi (host IDE agent) writes responses.
|
|
5
|
+
* No external API keys required.
|
|
6
|
+
*
|
|
7
|
+
* Run dir: ~/.analyzthis_design/runs/{projectId}/{runId}/
|
|
8
|
+
* pending/001-arjun.json
|
|
9
|
+
* responses/001-arjun.md
|
|
10
|
+
* manifest.json
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
const { enforceOutputCap } = require('./token-gate');
|
|
18
|
+
|
|
19
|
+
const RUNS_ROOT = path.join(os.homedir(), '.analyzthis_design', 'runs');
|
|
20
|
+
|
|
21
|
+
class HostLlmPendingError extends Error {
|
|
22
|
+
constructor({ runDir, runId, stepId, personaId, pendingPath, completedSteps, totalSteps }) {
|
|
23
|
+
super(`Host LLM pending: ${personaId} (${stepId})`);
|
|
24
|
+
this.name = 'HostLlmPendingError';
|
|
25
|
+
this.runDir = runDir;
|
|
26
|
+
this.runId = runId;
|
|
27
|
+
this.stepId = stepId;
|
|
28
|
+
this.personaId = personaId;
|
|
29
|
+
this.pendingPath = pendingPath;
|
|
30
|
+
this.completedSteps = completedSteps;
|
|
31
|
+
this.totalSteps = totalSteps;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ensureDir(p) {
|
|
36
|
+
fs.mkdirSync(p, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createRun({ projectId, task }) {
|
|
40
|
+
const runId = `${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
41
|
+
const runDir = path.join(RUNS_ROOT, projectId, runId);
|
|
42
|
+
ensureDir(path.join(runDir, 'pending'));
|
|
43
|
+
ensureDir(path.join(runDir, 'responses'));
|
|
44
|
+
const manifest = {
|
|
45
|
+
run_id: runId,
|
|
46
|
+
project_id: projectId,
|
|
47
|
+
task: task || '',
|
|
48
|
+
created_at: new Date().toISOString(),
|
|
49
|
+
step_counter: 0,
|
|
50
|
+
completed_steps: [],
|
|
51
|
+
};
|
|
52
|
+
fs.writeFileSync(path.join(runDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
53
|
+
return { runId, runDir, manifest };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function loadManifest(runDir) {
|
|
57
|
+
const p = path.join(runDir, 'manifest.json');
|
|
58
|
+
if (!fs.existsSync(p)) return null;
|
|
59
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function saveManifest(runDir, manifest) {
|
|
63
|
+
fs.writeFileSync(path.join(runDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function nextStepId(manifest) {
|
|
67
|
+
manifest.step_counter = (manifest.step_counter || 0) + 1;
|
|
68
|
+
return String(manifest.step_counter).padStart(3, '0') + `-${manifest.pending_persona || 'step'}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function pendingPath(runDir, stepId) {
|
|
72
|
+
return path.join(runDir, 'pending', `${stepId}.json`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function responsePath(runDir, stepId) {
|
|
76
|
+
return path.join(runDir, 'responses', `${stepId}.md`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writePending(runDir, manifest, { personaId, system, user, meta = {} }) {
|
|
80
|
+
manifest.pending_persona = personaId;
|
|
81
|
+
const stepId = `${String(manifest.step_counter + 1).padStart(3, '0')}-${personaId}`;
|
|
82
|
+
const payload = {
|
|
83
|
+
step_id: stepId,
|
|
84
|
+
persona_id: personaId,
|
|
85
|
+
system,
|
|
86
|
+
user,
|
|
87
|
+
max_tokens: meta.max_tokens ?? meta.maxTokens ?? null,
|
|
88
|
+
output_char_cap: meta.output_char_cap ?? null,
|
|
89
|
+
created_at: new Date().toISOString(),
|
|
90
|
+
...meta,
|
|
91
|
+
};
|
|
92
|
+
fs.writeFileSync(pendingPath(runDir, stepId), JSON.stringify(payload, null, 2));
|
|
93
|
+
return stepId;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readResponse(runDir, stepId) {
|
|
97
|
+
const p = responsePath(runDir, stepId);
|
|
98
|
+
if (!fs.existsSync(p)) return null;
|
|
99
|
+
return fs.readFileSync(p, 'utf8');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function submitResponse(runDir, stepId, text, { maxTokens } = {}) {
|
|
103
|
+
ensureDir(path.join(runDir, 'responses'));
|
|
104
|
+
let capped = text;
|
|
105
|
+
const pendingFile = pendingPath(runDir, stepId);
|
|
106
|
+
if (fs.existsSync(pendingFile)) {
|
|
107
|
+
try {
|
|
108
|
+
const pending = JSON.parse(fs.readFileSync(pendingFile, 'utf8'));
|
|
109
|
+
const cap = maxTokens ?? pending.max_tokens;
|
|
110
|
+
if (cap) capped = enforceOutputCap(text, cap);
|
|
111
|
+
} catch { /* use raw text */ }
|
|
112
|
+
} else if (maxTokens) {
|
|
113
|
+
capped = enforceOutputCap(text, maxTokens);
|
|
114
|
+
}
|
|
115
|
+
fs.writeFileSync(responsePath(runDir, stepId), capped);
|
|
116
|
+
const manifest = loadManifest(runDir);
|
|
117
|
+
if (manifest && !manifest.completed_steps.includes(stepId)) {
|
|
118
|
+
manifest.completed_steps.push(stepId);
|
|
119
|
+
saveManifest(runDir, manifest);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function listPending(runDir) {
|
|
124
|
+
const pendingDir = path.join(runDir, 'pending');
|
|
125
|
+
if (!fs.existsSync(pendingDir)) return [];
|
|
126
|
+
return fs.readdirSync(pendingDir)
|
|
127
|
+
.filter((f) => f.endsWith('.json'))
|
|
128
|
+
.map((f) => {
|
|
129
|
+
const stepId = f.replace(/\.json$/, '');
|
|
130
|
+
const data = JSON.parse(fs.readFileSync(path.join(pendingDir, f), 'utf8'));
|
|
131
|
+
return {
|
|
132
|
+
stepId,
|
|
133
|
+
personaId: data.persona_id,
|
|
134
|
+
hasResponse: fs.existsSync(responsePath(runDir, stepId)),
|
|
135
|
+
pendingPath: path.join(pendingDir, f),
|
|
136
|
+
};
|
|
137
|
+
})
|
|
138
|
+
.sort((a, b) => a.stepId.localeCompare(b.stepId));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function findLatestRun(projectId) {
|
|
142
|
+
const dir = path.join(RUNS_ROOT, projectId);
|
|
143
|
+
if (!fs.existsSync(dir)) return null;
|
|
144
|
+
const runs = fs.readdirSync(dir).sort().reverse();
|
|
145
|
+
for (const runId of runs) {
|
|
146
|
+
const runDir = path.join(dir, runId);
|
|
147
|
+
if (fs.existsSync(path.join(runDir, 'manifest.json'))) return { runId, runDir };
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Host provider call — write pending, return response if exists, else throw HostLlmPendingError.
|
|
154
|
+
* @param {object} opts
|
|
155
|
+
* @param {object} opts.runContext — { runDir, manifest, hostResponder }
|
|
156
|
+
*/
|
|
157
|
+
async function callHostLlm({ personaId, system, user, maxTokens, runContext }) {
|
|
158
|
+
const { runDir, manifest, hostResponder } = runContext;
|
|
159
|
+
if (!runDir || !manifest) throw new Error('host run context missing — call createRun first');
|
|
160
|
+
|
|
161
|
+
const stepId = writePending(runDir, manifest, {
|
|
162
|
+
personaId,
|
|
163
|
+
system,
|
|
164
|
+
user,
|
|
165
|
+
meta: { provider: 'host', agent: 'devi', max_tokens: maxTokens },
|
|
166
|
+
});
|
|
167
|
+
manifest.step_counter += 1;
|
|
168
|
+
saveManifest(runDir, manifest);
|
|
169
|
+
|
|
170
|
+
const applyCap = (raw) => (maxTokens ? enforceOutputCap(raw, maxTokens) : raw);
|
|
171
|
+
|
|
172
|
+
// Inline responder (tests / scripted fixtures)
|
|
173
|
+
if (typeof hostResponder === 'function') {
|
|
174
|
+
const text = await hostResponder({ personaId, system, user, stepId, runDir, maxTokens });
|
|
175
|
+
if (text) {
|
|
176
|
+
const capped = applyCap(text);
|
|
177
|
+
submitResponse(runDir, stepId, capped, { maxTokens });
|
|
178
|
+
return capped;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Pre-written response file
|
|
183
|
+
let text = readResponse(runDir, stepId);
|
|
184
|
+
if (text) {
|
|
185
|
+
text = applyCap(text);
|
|
186
|
+
manifest.completed_steps = manifest.completed_steps || [];
|
|
187
|
+
if (!manifest.completed_steps.includes(stepId)) {
|
|
188
|
+
manifest.completed_steps.push(stepId);
|
|
189
|
+
saveManifest(runDir, manifest);
|
|
190
|
+
}
|
|
191
|
+
return text;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Optional short poll (HOST_LLM_POLL_MS)
|
|
195
|
+
const pollMs = Number(process.env.HOST_LLM_POLL_MS || 0);
|
|
196
|
+
if (pollMs > 0) {
|
|
197
|
+
const deadline = Date.now() + pollMs;
|
|
198
|
+
while (Date.now() < deadline) {
|
|
199
|
+
text = readResponse(runDir, stepId);
|
|
200
|
+
if (text) {
|
|
201
|
+
const capped = applyCap(text);
|
|
202
|
+
submitResponse(runDir, stepId, capped, { maxTokens });
|
|
203
|
+
return capped;
|
|
204
|
+
}
|
|
205
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const pending = listPending(runDir);
|
|
210
|
+
throw new HostLlmPendingError({
|
|
211
|
+
runDir,
|
|
212
|
+
runId: manifest.run_id,
|
|
213
|
+
stepId,
|
|
214
|
+
personaId,
|
|
215
|
+
pendingPath: pendingPath(runDir, stepId),
|
|
216
|
+
completedSteps: manifest.completed_steps?.length || 0,
|
|
217
|
+
totalSteps: pending.length,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function printDeviInstructions(err) {
|
|
222
|
+
console.log('\n╔══════════════════════════════════════════════════════════════════════╗');
|
|
223
|
+
console.log('║ DEVI — Host LLM pending ║');
|
|
224
|
+
console.log('╚══════════════════════════════════════════════════════════════════════╝\n');
|
|
225
|
+
console.log(` Persona waiting: ${err.personaId}`);
|
|
226
|
+
console.log(` Step: ${err.stepId}`);
|
|
227
|
+
console.log(` Prompt file: ${err.pendingPath}`);
|
|
228
|
+
console.log(` Run directory: ${err.runDir}`);
|
|
229
|
+
console.log('\n In Cursor, invoke: /devi');
|
|
230
|
+
console.log(' Or submit response:');
|
|
231
|
+
console.log(` npx analyzthis_design devi respond --run ${err.runDir} --step ${err.stepId} --file response.md`);
|
|
232
|
+
console.log(' Then continue:');
|
|
233
|
+
console.log(` npx analyzthis_design run --continue --task "..." --project ${path.basename(path.dirname(err.runDir))}\n`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = {
|
|
237
|
+
HostLlmPendingError,
|
|
238
|
+
RUNS_ROOT,
|
|
239
|
+
createRun,
|
|
240
|
+
loadManifest,
|
|
241
|
+
saveManifest,
|
|
242
|
+
writePending,
|
|
243
|
+
readResponse,
|
|
244
|
+
submitResponse,
|
|
245
|
+
listPending,
|
|
246
|
+
findLatestRun,
|
|
247
|
+
callHostLlm,
|
|
248
|
+
printDeviInstructions,
|
|
249
|
+
pendingPath,
|
|
250
|
+
responsePath,
|
|
251
|
+
};
|
package/dist/lib/install.js
CHANGED
|
@@ -1,2 +1,302 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const {
|
|
8
|
+
TARGETS,
|
|
9
|
+
TARGET_DIRS,
|
|
10
|
+
ALL_TARGET_IDS,
|
|
11
|
+
resolveTargets,
|
|
12
|
+
resolvePackageRoot,
|
|
13
|
+
listInstalledTargets,
|
|
14
|
+
} = require('./platforms');
|
|
15
|
+
|
|
16
|
+
const PACKAGE_ROOT = resolvePackageRoot(__dirname);
|
|
17
|
+
const PACKAGE_SKILLS_DIR = path.join(PACKAGE_ROOT, 'skills');
|
|
18
|
+
const WELCOME_MARKER = path.join(os.homedir(), '.analyzthis_design', '.welcome-shown');
|
|
19
|
+
|
|
20
|
+
const SKILLS = [
|
|
21
|
+
'getting-started',
|
|
22
|
+
'arjun',
|
|
23
|
+
'meera',
|
|
24
|
+
'priya',
|
|
25
|
+
'zara',
|
|
26
|
+
'noor',
|
|
27
|
+
'anuj',
|
|
28
|
+
'raj',
|
|
29
|
+
'kavi',
|
|
30
|
+
'collect-knowledge',
|
|
31
|
+
'design-critic',
|
|
32
|
+
'ux-ideator',
|
|
33
|
+
'ux-story-gate',
|
|
34
|
+
'persona-orchestrator',
|
|
35
|
+
'deliberation-protocol',
|
|
36
|
+
'chunk-planner',
|
|
37
|
+
'run-unchunked',
|
|
38
|
+
'evolve-check',
|
|
39
|
+
'devi',
|
|
40
|
+
'design-director',
|
|
41
|
+
'design-spec',
|
|
42
|
+
'design-personas',
|
|
43
|
+
'design-reference',
|
|
44
|
+
'knowledge-bank',
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
function getPackageVersion() {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')).version;
|
|
50
|
+
} catch {
|
|
51
|
+
return 'unknown';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function shouldShowWelcome() {
|
|
56
|
+
try {
|
|
57
|
+
return !fs.existsSync(WELCOME_MARKER);
|
|
58
|
+
} catch {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function markWelcomeShown() {
|
|
64
|
+
fs.mkdirSync(path.dirname(WELCOME_MARKER), { recursive: true });
|
|
65
|
+
fs.writeFileSync(WELCOME_MARKER, new Date().toISOString());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function printWelcomeBanner(targetId, log = console.log) {
|
|
69
|
+
const t = TARGETS[targetId];
|
|
70
|
+
if (!t) return;
|
|
71
|
+
const p = t.invokePrefix || '/';
|
|
72
|
+
const version = getPackageVersion();
|
|
73
|
+
const gs = `${p}getting-started`;
|
|
74
|
+
|
|
75
|
+
log(`\n✅ Analyzthis Design installed (v${version})`);
|
|
76
|
+
log('');
|
|
77
|
+
log(' Structured UX critiques, ideation, and task-grounded screen reviews — in your AI chat.');
|
|
78
|
+
log(' No external LLM API keys required for CLI runs: ' +
|
|
79
|
+
`${p}devi voices each persona from your IDE.`);
|
|
80
|
+
log('');
|
|
81
|
+
log(`Start here in ${t.label}:`);
|
|
82
|
+
if (targetId === 'windsurf') {
|
|
83
|
+
log(` @getting-started ← read this first`);
|
|
84
|
+
} else if (targetId === 'codex') {
|
|
85
|
+
log(' Add to AGENTS.md: getting-started, kavi, devi, ux-ideator, persona-orchestrator');
|
|
86
|
+
log(' Skills live in ~/.codex/skills/<name>/SKILL.md');
|
|
87
|
+
} else {
|
|
88
|
+
log(` ${gs} ← read this first`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
log('');
|
|
92
|
+
log(' Design — wireframes (new screens):');
|
|
93
|
+
log(` ${p}ux-ideator two competing text wireframes + deliberation`);
|
|
94
|
+
log(` ${p}design-director wireframe → DesignSpec → spec gates → build`);
|
|
95
|
+
log(` ${p}noor quick minimalist wireframe (Concept A)`);
|
|
96
|
+
log(` ${p}anuj power-user wireframe (Concept B)`);
|
|
97
|
+
|
|
98
|
+
log('');
|
|
99
|
+
log(' Evaluate — critique (existing designs):');
|
|
100
|
+
log(` ${p}kavi scan repo → knowledge bank (once per project)`);
|
|
101
|
+
log(` ${p}persona-orchestrator MoE router + gates → SHIP / REVISE / BLOCK`);
|
|
102
|
+
log(` ${p}design-critic 4-persona critique + composite score`);
|
|
103
|
+
log(` ${p}deliberation-protocol adversarial review rules (objections, Raj)`);
|
|
104
|
+
log(` ${p}devi host LLM: voice personas when CLI has no API keys`);
|
|
105
|
+
|
|
106
|
+
log('');
|
|
107
|
+
log(' CLI (host mode — no API keys):');
|
|
108
|
+
log(' npx analyzthis_design collect');
|
|
109
|
+
log(' npx analyzthis_design run --task "Review screen" --full');
|
|
110
|
+
log(' npx analyzthis_design devi status');
|
|
111
|
+
log(' npx analyzthis_design run --continue --task "..." --full');
|
|
112
|
+
|
|
113
|
+
log('');
|
|
114
|
+
log(` Re-print anytime: npx analyzthis_design welcome --target ${targetId}`);
|
|
115
|
+
log(' Docs: https://www.npmjs.com/package/analyzthis_design');
|
|
116
|
+
log(' Repo: https://github.com/joshirishi/analyzthis_design\n');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function copyDir(src, dest) {
|
|
120
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
121
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
122
|
+
const srcPath = path.join(src, entry.name);
|
|
123
|
+
const destPath = path.join(dest, entry.name);
|
|
124
|
+
if (entry.isDirectory()) copyDir(srcPath, destPath);
|
|
125
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function installFlat(skill, skillsDir, force) {
|
|
130
|
+
const src = path.join(PACKAGE_SKILLS_DIR, skill, 'SKILL.md');
|
|
131
|
+
const dest = path.join(skillsDir, `${skill}.md`);
|
|
132
|
+
if (!fs.existsSync(src)) return 'missing';
|
|
133
|
+
if (fs.existsSync(dest) && !force) return 'skipped';
|
|
134
|
+
try {
|
|
135
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
136
|
+
fs.copyFileSync(src, dest);
|
|
137
|
+
return 'installed';
|
|
138
|
+
} catch {
|
|
139
|
+
return 'error';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function installDir(skill, skillsDir, force) {
|
|
144
|
+
const src = path.join(PACKAGE_SKILLS_DIR, skill);
|
|
145
|
+
const dest = path.join(skillsDir, skill);
|
|
146
|
+
if (!fs.existsSync(src)) return 'missing';
|
|
147
|
+
if (fs.existsSync(dest) && !force) return 'skipped';
|
|
148
|
+
try {
|
|
149
|
+
copyDir(src, dest);
|
|
150
|
+
return 'installed';
|
|
151
|
+
} catch {
|
|
152
|
+
return 'error';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function skillInstalledAt(root, layout, skill) {
|
|
157
|
+
if (layout === 'dir') {
|
|
158
|
+
return fs.existsSync(path.join(root, skill, 'SKILL.md')) || fs.existsSync(path.join(root, skill));
|
|
159
|
+
}
|
|
160
|
+
return fs.existsSync(path.join(root, `${skill}.md`));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function installOne(skill, root, layout, force) {
|
|
164
|
+
if (skill === 'design-reference' || layout === 'dir') {
|
|
165
|
+
return installDir(skill, root, force);
|
|
166
|
+
}
|
|
167
|
+
return installFlat(skill, root, force);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Install skills missing from any destination (safe upgrade path without --force). */
|
|
171
|
+
function installMissing(skill, destinations, force) {
|
|
172
|
+
let status = 'skipped';
|
|
173
|
+
for (const dest of destinations) {
|
|
174
|
+
if (!force && skillInstalledAt(dest.root, dest.layout, skill)) continue;
|
|
175
|
+
const result = installOne(skill, dest.root, dest.layout, force);
|
|
176
|
+
if (result === 'installed') status = 'installed';
|
|
177
|
+
else if (result === 'error') return 'error';
|
|
178
|
+
else if (result === 'missing') return 'missing';
|
|
179
|
+
}
|
|
180
|
+
return status;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function install({ silent = false, force = false, target = 'cursor', showBanner = true } = {}) {
|
|
184
|
+
const log = silent ? () => {} : console.log;
|
|
185
|
+
const warn = silent ? () => {} : console.warn;
|
|
186
|
+
|
|
187
|
+
const targets = resolveTargets(target);
|
|
188
|
+
if (!targets) {
|
|
189
|
+
warn(` ⚠ Unknown target "${target}". Choose: ${ALL_TARGET_IDS.join(', ')}, all`);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for (const tId of targets) {
|
|
194
|
+
const t = TARGETS[tId];
|
|
195
|
+
const destinations = [{ root: t.root, layout: t.layout }];
|
|
196
|
+
if (t.also) destinations.push({ root: t.also.root, layout: t.also.layout });
|
|
197
|
+
|
|
198
|
+
const installed = [], skipped = [], errors = [];
|
|
199
|
+
|
|
200
|
+
for (const skill of SKILLS) {
|
|
201
|
+
const result = installMissing(skill, destinations, force);
|
|
202
|
+
if (result === 'installed') {
|
|
203
|
+
installed.push(skill);
|
|
204
|
+
} else if (result === 'skipped') {
|
|
205
|
+
skipped.push(skill);
|
|
206
|
+
} else if (result === 'missing') {
|
|
207
|
+
warn(` ⚠ Skill source not found: ${skill}`);
|
|
208
|
+
errors.push(skill);
|
|
209
|
+
} else {
|
|
210
|
+
warn(` ✗ Failed to install ${skill}`);
|
|
211
|
+
errors.push(skill);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (installed.length > 0) {
|
|
216
|
+
log(`\n✅ [${t.label}] Installed ${installed.length} skill(s) → ${t.root}`);
|
|
217
|
+
if (t.also) log(` (+ legacy copy → ${t.also.root})`);
|
|
218
|
+
for (const s of installed) log(` • ${s}`);
|
|
219
|
+
}
|
|
220
|
+
if (skipped.length > 0) {
|
|
221
|
+
log(`\n⏭ [${t.label}] Skipped ${skipped.length} existing skill(s) (use --force to overwrite):`);
|
|
222
|
+
for (const s of skipped) log(` • ${s}`);
|
|
223
|
+
}
|
|
224
|
+
if (errors.length > 0) {
|
|
225
|
+
log(`\n✗ [${t.label}] ${errors.length} skill(s) failed.`);
|
|
226
|
+
}
|
|
227
|
+
if (showBanner && !silent && (installed.length > 0 || skipped.length > 0)) {
|
|
228
|
+
printWelcomeBanner(tId, log);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function remove({ silent = false, target = 'cursor' } = {}) {
|
|
234
|
+
const log = silent ? () => {} : console.log;
|
|
235
|
+
|
|
236
|
+
const targets = resolveTargets(target);
|
|
237
|
+
if (!targets) return;
|
|
238
|
+
|
|
239
|
+
for (const tId of targets) {
|
|
240
|
+
const t = TARGETS[tId];
|
|
241
|
+
const roots = [t.root];
|
|
242
|
+
if (t.also) roots.push(t.also.root);
|
|
243
|
+
|
|
244
|
+
const removed = [], missing = [];
|
|
245
|
+
|
|
246
|
+
for (const skill of SKILLS) {
|
|
247
|
+
let found = false;
|
|
248
|
+
for (const root of roots) {
|
|
249
|
+
const asDir = path.join(root, skill);
|
|
250
|
+
const asFile = path.join(root, `${skill}.md`);
|
|
251
|
+
if (fs.existsSync(asDir)) {
|
|
252
|
+
fs.rmSync(asDir, { recursive: true, force: true });
|
|
253
|
+
found = true;
|
|
254
|
+
}
|
|
255
|
+
if (fs.existsSync(asFile)) {
|
|
256
|
+
fs.rmSync(asFile, { force: true });
|
|
257
|
+
found = true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (found) removed.push(skill);
|
|
261
|
+
else missing.push(skill);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (removed.length > 0) {
|
|
265
|
+
log(`\n🗑 [${t.label}] Removed ${removed.length} skill(s):`);
|
|
266
|
+
for (const s of removed) log(` • ${s}`);
|
|
267
|
+
}
|
|
268
|
+
if (missing.length > 0) {
|
|
269
|
+
log(`\n [${t.label}] ${missing.length} skill(s) were not installed.`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
log('');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = {
|
|
276
|
+
install,
|
|
277
|
+
remove,
|
|
278
|
+
SKILLS,
|
|
279
|
+
TARGET_DIRS,
|
|
280
|
+
TARGETS,
|
|
281
|
+
ALL_TARGET_IDS,
|
|
282
|
+
printWelcomeBanner,
|
|
283
|
+
markWelcomeShown,
|
|
284
|
+
shouldShowWelcome,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
if (require.main === module) {
|
|
288
|
+
const silent = process.argv.includes('--silent');
|
|
289
|
+
const forceWelcome = process.argv.includes('--welcome');
|
|
290
|
+
const postTargets = listInstalledTargets();
|
|
291
|
+
const targets = postTargets.length ? postTargets : ['cursor'];
|
|
292
|
+
for (const tId of targets) {
|
|
293
|
+
install({ silent, target: tId, showBanner: !silent && tId === 'cursor' });
|
|
294
|
+
}
|
|
295
|
+
if (forceWelcome || shouldShowWelcome()) {
|
|
296
|
+
if (silent) {
|
|
297
|
+
printWelcomeBanner('cursor');
|
|
298
|
+
printWelcomeBanner('claude');
|
|
299
|
+
}
|
|
300
|
+
markWelcomeShown();
|
|
301
|
+
}
|
|
302
|
+
}
|