-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.ts
More file actions
116 lines (108 loc) · 4.25 KB
/
Copy pathinstall.ts
File metadata and controls
116 lines (108 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { confirm, isCancel, log, outro, spinner } from '@clack/prompts';
import { createRequire } from 'node:module';
import pc from 'picocolors';
import { cancelAndExit } from '../lib/confirm.js';
import { DOCS_URL, FIRST_FEATURE_COMMAND, REPO_URL } from '../lib/constants.js';
import { fetchAndInstall } from '../lib/installer.js';
import {
configPath,
readPharnConfig,
toInstalledModules,
writePharnConfig,
} from '../lib/pharn-config.js';
import type { PharnConfig, WizardConfig } from '../types.js';
const require = createRequire(import.meta.url);
const pkg = require('../../package.json') as { version: string };
const PHARN_VERSION = pkg.version;
export async function runInstall(config: WizardConfig): Promise<void> {
const startedAt = Date.now();
const cwd = process.cwd();
const claudeDir = resolve(cwd, '.claude');
if (existsSync(configPath(cwd))) {
const existing = readPharnConfig(cwd);
if (existing) {
log.info(
`Existing pharn.config.json found (skillsVersion ${existing.skillsVersion ?? 'unknown'}).`,
);
}
if (!(await confirmOverwrite('Overwrite existing pharn.config.json?'))) {
cancelAndExit();
}
}
// The optional modules plus the stack pack; pharn-core and transitive deps
// are added during resolution.
const selected = [
...config.modules,
...(config.stackPack ? [config.stackPack] : []),
];
const s = spinner();
s.start(`Fetching skills from ${REPO_URL}`);
let skillsVersion: string;
let commit: string | null;
let resolved: { name: string; version: string }[];
try {
const result = await fetchAndInstall({
claudeDir,
selected,
constitution: config.constitution,
wizardSkills: config.installedSkills,
});
skillsVersion = result.skillsVersion;
commit = result.commit;
resolved = result.resolved;
s.stop(`Skills installed from ${REPO_URL}`);
} catch (err) {
s.stop('Failed to install skills');
const message = err instanceof Error ? err.message : String(err);
log.error(`⚠ ${message}`);
if (process.env.PHARN_DEBUG) console.error(err);
else log.info('Re-run with PHARN_DEBUG=1 for full error output.');
process.exit(1);
}
const configFile: PharnConfig = {
pharnVersion: PHARN_VERSION,
skillsVersion,
repo: REPO_URL.replace(/^github\.com\//, ''),
commit,
constitution: config.constitution,
modules: toInstalledModules(resolved),
installedAt: new Date().toISOString(),
// schemaVersion 2: persist the wizard answers + selected skills so add and
// update can re-resolve without re-asking. Omitted entirely on legacy installs.
...(config.stackAnswers ? { stackAnswers: config.stackAnswers } : {}),
...(config.installedSkills && config.installedSkills.length > 0
? { installedSkills: config.installedSkills }
: {}),
...(config.vendorSkills && config.vendorSkills.length > 0
? { vendorSkills: config.vendorSkills }
: {}),
};
await writePharnConfig(cwd, configFile);
const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
const check = pc.green('✔');
outro(
[
`${check} ${resolved.length} module${resolved.length === 1 ? '' : 's'} installed → ${pc.dim('.claude/')}`,
...(config.installedSkills && config.installedSkills.length > 0
? [
`${check} ${config.installedSkills.length} skill${config.installedSkills.length === 1 ? '' : 's'} installed → ${pc.dim('.claude/skills/')} ${pc.dim(`(${config.installedSkills.map((s) => s.skill).join(', ')})`)}`,
]
: []),
`${check} CONSTITUTION.md + memory-bank written`,
`${check} pharn.config.json written ${pc.dim(`(skills v${skillsVersion})`)}`,
`${pc.dim(`Done in ${elapsed}s`)}`,
'',
pc.bold('Next steps'),
` ${pc.cyan('1.')} ${pc.bold('claude')} ${pc.dim('open Claude Code')}`,
` ${pc.cyan('2.')} ${pc.bold(FIRST_FEATURE_COMMAND)} ${pc.dim('plan your first feature')}`,
'',
`${pc.bold('Docs')} ${pc.cyan(DOCS_URL)}`,
].join('\n'),
);
}
async function confirmOverwrite(message: string): Promise<boolean> {
const ok = await confirm({ message, initialValue: false });
return !isCancel(ok) && ok === true;
}