thufir 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/dist/analyze.d.ts +28 -0
- package/dist/analyze.js +62 -0
- package/dist/api.d.ts +15 -0
- package/dist/api.js +30 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +183 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -0
- package/dist/prompts.d.ts +22 -0
- package/dist/prompts.js +547 -0
- package/dist/scaffold.d.ts +13 -0
- package/dist/scaffold.js +352 -0
- package/dist/templates/agents/base.md +350 -0
- package/dist/templates/claude-md.md +1 -0
- package/dist/templates/cursorrules.md +1 -0
- package/dist/templates/gitignore.md +54 -0
- package/dist/templates/skill-triggers.md +5 -0
- package/dist/templates/skills/gstack.md +17 -0
- package/dist/templates/skills/superpowers.md +16 -0
- package/dist/templates/traits/compassion.md +13 -0
- package/dist/templates/traits/consultant.md +36 -0
- package/dist/templates/traits/first-principles.md +8 -0
- package/dist/templates/traits/mbb-problem-solving.md +9 -0
- package/dist/templates/traits/mentat-core.md +33 -0
- package/dist/templates/traits/parts.md +13 -0
- package/dist/templates/traits/psychology.md +32 -0
- package/dist/templates/traits/systemic.md +13 -0
- package/dist/templates/traits/thufir-personality.md +9 -0
- package/dist/templates/traits/top-down.md +9 -0
- package/dist/templates/traits/zelazny.md +9 -0
- package/dist/ui/ascii.d.ts +12 -0
- package/dist/ui/ascii.js +205 -0
- package/dist/version-check.d.ts +2 -0
- package/dist/version-check.js +27 -0
- package/package.json +38 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface ThufirSettings {
|
|
2
|
+
version: string;
|
|
3
|
+
initialized: string;
|
|
4
|
+
last_realigned: string;
|
|
5
|
+
colleague_name: string;
|
|
6
|
+
user_name: string;
|
|
7
|
+
project_mode: 'single' | 'multi';
|
|
8
|
+
project_focus: 'code' | 'planning' | 'hybrid';
|
|
9
|
+
project_description: string;
|
|
10
|
+
traits: string[];
|
|
11
|
+
skillPackages?: string[];
|
|
12
|
+
skillTriggers?: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface RepoAnalysis {
|
|
15
|
+
isGitRepo: boolean;
|
|
16
|
+
hasAgentsMd: boolean;
|
|
17
|
+
hasClaudeMd: boolean;
|
|
18
|
+
hasCursorRules: boolean;
|
|
19
|
+
hasDocsDir: boolean;
|
|
20
|
+
hasSkillsDir: boolean;
|
|
21
|
+
hasGitHubRemote: boolean;
|
|
22
|
+
gitHubRemoteUrl: string | null;
|
|
23
|
+
hasGhCli: boolean;
|
|
24
|
+
hasThufirMarker: boolean;
|
|
25
|
+
thufirSettings: ThufirSettings | null;
|
|
26
|
+
savedToken: string | null;
|
|
27
|
+
}
|
|
28
|
+
export declare function analyzeRepo(cwd: string): RepoAnalysis;
|
package/dist/analyze.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
function commandExists(cmd) {
|
|
5
|
+
try {
|
|
6
|
+
execSync(`which ${cmd}`, { stdio: 'ignore' });
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function getGitRemote(cwd) {
|
|
14
|
+
try {
|
|
15
|
+
const output = execSync('git remote get-url origin', { encoding: 'utf-8', cwd, stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
16
|
+
return output || null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function analyzeRepo(cwd) {
|
|
23
|
+
let isGitRepo = false;
|
|
24
|
+
try {
|
|
25
|
+
execSync('git rev-parse --git-dir', { cwd, stdio: 'ignore' });
|
|
26
|
+
isGitRepo = true;
|
|
27
|
+
}
|
|
28
|
+
catch { /* not a git repo */ }
|
|
29
|
+
const remote = isGitRepo ? getGitRemote(cwd) : null;
|
|
30
|
+
const hasThufirMarker = existsSync(join(cwd, '.thufir'));
|
|
31
|
+
let thufirSettings = null;
|
|
32
|
+
if (hasThufirMarker) {
|
|
33
|
+
try {
|
|
34
|
+
thufirSettings = JSON.parse(readFileSync(join(cwd, '.thufir'), 'utf-8'));
|
|
35
|
+
}
|
|
36
|
+
catch { /* corrupt or unreadable */ }
|
|
37
|
+
}
|
|
38
|
+
let savedToken = null;
|
|
39
|
+
const localPath = join(cwd, '.thufir.local');
|
|
40
|
+
if (existsSync(localPath)) {
|
|
41
|
+
try {
|
|
42
|
+
const local = JSON.parse(readFileSync(localPath, 'utf-8'));
|
|
43
|
+
if (local.token)
|
|
44
|
+
savedToken = local.token;
|
|
45
|
+
}
|
|
46
|
+
catch { /* ignore */ }
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
isGitRepo,
|
|
50
|
+
hasAgentsMd: existsSync(join(cwd, 'AGENTS.md')),
|
|
51
|
+
hasClaudeMd: existsSync(join(cwd, 'CLAUDE.md')),
|
|
52
|
+
hasCursorRules: existsSync(join(cwd, '.cursorrules')),
|
|
53
|
+
hasDocsDir: existsSync(join(cwd, 'docs')),
|
|
54
|
+
hasSkillsDir: existsSync(join(cwd, '.claude', 'skills')),
|
|
55
|
+
hasGitHubRemote: remote !== null,
|
|
56
|
+
gitHubRemoteUrl: remote,
|
|
57
|
+
hasGhCli: commandExists('gh'),
|
|
58
|
+
hasThufirMarker,
|
|
59
|
+
thufirSettings,
|
|
60
|
+
savedToken,
|
|
61
|
+
};
|
|
62
|
+
}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface ValidateResponse {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
owner?: string;
|
|
4
|
+
projects_remaining?: number;
|
|
5
|
+
expires?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function validateToken(token: string): Promise<ValidateResponse>;
|
|
9
|
+
export declare function reportUsage(token: string, repoId: string): Promise<void>;
|
|
10
|
+
export declare function checkVersion(currentVersion: string): Promise<{
|
|
11
|
+
updateAvailable: boolean;
|
|
12
|
+
latest: string;
|
|
13
|
+
changelog: string;
|
|
14
|
+
}>;
|
|
15
|
+
export {};
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const API_BASE = 'https://thufir-api.krisztian-596.workers.dev';
|
|
2
|
+
export async function validateToken(token) {
|
|
3
|
+
const res = await fetch(`${API_BASE}/validate`, {
|
|
4
|
+
method: 'POST',
|
|
5
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6
|
+
body: JSON.stringify({ token }),
|
|
7
|
+
});
|
|
8
|
+
return res.json();
|
|
9
|
+
}
|
|
10
|
+
export async function reportUsage(token, repoId) {
|
|
11
|
+
await fetch(`${API_BASE}/use`, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: { 'Content-Type': 'application/json' },
|
|
14
|
+
body: JSON.stringify({ token, repo_id: repoId }),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function checkVersion(currentVersion) {
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch(`${API_BASE}/version`);
|
|
20
|
+
const data = await res.json();
|
|
21
|
+
return {
|
|
22
|
+
updateAvailable: data.latest !== currentVersion,
|
|
23
|
+
latest: data.latest,
|
|
24
|
+
changelog: data.changelog,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { updateAvailable: false, latest: currentVersion, changelog: '' };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runInit(): Promise<void>;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { writeFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { showVersionCheck } from '../version-check.js';
|
|
7
|
+
import { analyzeRepo } from '../analyze.js';
|
|
8
|
+
import { runPrompts, runRealignPrompts, runMergePrompts } from '../prompts.js';
|
|
9
|
+
import { printCompleteScreen, printGetStarted, printCommitScreen } from '../ui/ascii.js';
|
|
10
|
+
import { scaffold, createInitialCommit, setupGitHub, backupAgentFiles, generateMergePrompt } from '../scaffold.js';
|
|
11
|
+
import { reportUsage } from '../api.js';
|
|
12
|
+
function commandExists(cmd) {
|
|
13
|
+
try {
|
|
14
|
+
execSync(`which ${cmd}`, { stdio: 'ignore' });
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function runInit() {
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
// Version check (non-blocking)
|
|
24
|
+
await showVersionCheck();
|
|
25
|
+
// Analyze repo first
|
|
26
|
+
const analysis = analyzeRepo(cwd);
|
|
27
|
+
// --- Mode B: Realign (.thufir exists) ---
|
|
28
|
+
if (analysis.hasThufirMarker && analysis.thufirSettings) {
|
|
29
|
+
const result = await runRealignPrompts(analysis.thufirSettings, analysis.savedToken);
|
|
30
|
+
if (!result) {
|
|
31
|
+
console.log(pc.dim(' Cancelled.'));
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
// Commit screen
|
|
35
|
+
printCommitScreen(result.answers.colleagueName, result.action === 'reconfigure' ? 'reconfigure' : 'realign');
|
|
36
|
+
const commitChoice = await p.select({
|
|
37
|
+
message: pc.yellow(pc.bold('Execute the command?')),
|
|
38
|
+
options: [
|
|
39
|
+
{ value: 'execute', label: '⚔ Execute', hint: 'commit to this action' },
|
|
40
|
+
{ value: 'abort', label: '↩ Abort', hint: 'return without changes' },
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
if (p.isCancel(commitChoice) || commitChoice === 'abort') {
|
|
44
|
+
console.log(pc.dim(' No changes made.'));
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
// Scaffold in realign mode
|
|
48
|
+
const s = p.spinner();
|
|
49
|
+
s.start(`Realigning ${result.answers.colleagueName}...`);
|
|
50
|
+
// Preserve original initialized date
|
|
51
|
+
result.answers._originalInitialized = analysis.thufirSettings.initialized;
|
|
52
|
+
const scaffoldResult = scaffold(cwd, result.answers, analysis, true);
|
|
53
|
+
s.stop(`${result.answers.colleagueName} realigned.`);
|
|
54
|
+
// Report usage (realign doesn't count as new project use)
|
|
55
|
+
const repoId = analysis.gitHubRemoteUrl || cwd;
|
|
56
|
+
await reportUsage(result.answers.token, repoId).catch(() => { });
|
|
57
|
+
// Complete screen + summary
|
|
58
|
+
printCompleteScreen(result.answers.colleagueName);
|
|
59
|
+
for (const file of scaffoldResult.filesCreated) {
|
|
60
|
+
console.log(` ${pc.green('✓')} Updated ${file}`);
|
|
61
|
+
}
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(pc.dim(` Review changes with ${pc.bold('git diff')}, then commit when ready.`));
|
|
64
|
+
console.log();
|
|
65
|
+
printGetStarted(result.answers.colleagueName);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// --- Mode C: Merge (agent files exist but no .thufir) ---
|
|
69
|
+
if ((analysis.hasAgentsMd || analysis.hasClaudeMd || analysis.hasCursorRules) && !analysis.hasThufirMarker) {
|
|
70
|
+
const mergeAnswers = await runMergePrompts(analysis);
|
|
71
|
+
if (!mergeAnswers) {
|
|
72
|
+
console.log(pc.dim(' Cancelled.'));
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
const s = p.spinner();
|
|
76
|
+
// Step 1: Backup
|
|
77
|
+
s.start('Backing up existing agent files...');
|
|
78
|
+
const backedUp = backupAgentFiles(cwd);
|
|
79
|
+
s.stop(`Backed up ${backedUp.length} files to .thufir-backup/`);
|
|
80
|
+
// Step 2: Scaffold (overwrites agent files with Thufir versions)
|
|
81
|
+
s.start(`Setting up ${mergeAnswers.colleagueName}...`);
|
|
82
|
+
const scaffoldResult = scaffold(cwd, mergeAnswers, analysis);
|
|
83
|
+
s.stop('Thufir files written.');
|
|
84
|
+
// Step 3: Generate merge prompt
|
|
85
|
+
s.start('Generating merge plan...');
|
|
86
|
+
const mergePrompt = generateMergePrompt(cwd, mergeAnswers, backedUp);
|
|
87
|
+
const promptPath = join(cwd, '.thufir-backup', 'merge-prompt.md');
|
|
88
|
+
writeFileSync(promptPath, mergePrompt, 'utf-8');
|
|
89
|
+
s.stop('Merge plan ready.');
|
|
90
|
+
// Step 4: Detect and run local agent
|
|
91
|
+
const hasClaude = commandExists('claude');
|
|
92
|
+
const hasCursor = commandExists('cursor');
|
|
93
|
+
// Report usage
|
|
94
|
+
const repoId = analysis.gitHubRemoteUrl || cwd;
|
|
95
|
+
await reportUsage(mergeAnswers.token, repoId).catch(() => { });
|
|
96
|
+
printCompleteScreen(mergeAnswers.colleagueName);
|
|
97
|
+
for (const file of backedUp) {
|
|
98
|
+
console.log(` ${pc.yellow('↗')} Backed up ${file}`);
|
|
99
|
+
}
|
|
100
|
+
for (const file of scaffoldResult.filesCreated) {
|
|
101
|
+
console.log(` ${pc.green('✓')} Created ${file}`);
|
|
102
|
+
}
|
|
103
|
+
console.log(` ${pc.blue('📋')} Merge plan: .thufir-backup/merge-prompt.md`);
|
|
104
|
+
console.log();
|
|
105
|
+
if (hasClaude) {
|
|
106
|
+
const runAgent = await p.confirm({
|
|
107
|
+
message: 'Claude Code CLI detected. Run the merge agent now?',
|
|
108
|
+
});
|
|
109
|
+
if (!p.isCancel(runAgent) && runAgent) {
|
|
110
|
+
p.log.step('Running merge agent via Claude Code...');
|
|
111
|
+
try {
|
|
112
|
+
execSync(`claude -p "$(cat .thufir-backup/merge-prompt.md)"`, {
|
|
113
|
+
cwd,
|
|
114
|
+
stdio: 'inherit',
|
|
115
|
+
timeout: 300000, // 5 min timeout
|
|
116
|
+
});
|
|
117
|
+
p.log.success('Merge agent completed.');
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
p.log.warn('Merge agent exited. You can re-run manually:');
|
|
121
|
+
console.log(pc.dim(` claude -p "$(cat .thufir-backup/merge-prompt.md)"`));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
p.log.info('Run the merge agent manually when ready:');
|
|
126
|
+
console.log(pc.dim(` claude -p "$(cat .thufir-backup/merge-prompt.md)"`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (hasCursor) {
|
|
130
|
+
p.log.info('Open this project in Cursor and paste the contents of:');
|
|
131
|
+
console.log(pc.dim(` .thufir-backup/merge-prompt.md`));
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
p.log.info('Open this project in Claude Code or Cursor and paste the contents of:');
|
|
135
|
+
console.log(pc.dim(` .thufir-backup/merge-prompt.md`));
|
|
136
|
+
}
|
|
137
|
+
printGetStarted(mergeAnswers.colleagueName);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// --- Mode A: Fresh install ---
|
|
141
|
+
const answers = await runPrompts(analysis, analysis.savedToken);
|
|
142
|
+
if (!answers) {
|
|
143
|
+
console.log(pc.dim(' Cancelled.'));
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
// Commit screen
|
|
147
|
+
printCommitScreen(answers.colleagueName, 'install');
|
|
148
|
+
const commitChoice = await p.select({
|
|
149
|
+
message: pc.yellow(pc.bold('Execute the command?')),
|
|
150
|
+
options: [
|
|
151
|
+
{ value: 'execute', label: '⚔ Execute', hint: 'commit to this action' },
|
|
152
|
+
{ value: 'abort', label: '↩ Abort', hint: 'return without changes' },
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
if (p.isCancel(commitChoice) || commitChoice === 'abort') {
|
|
156
|
+
console.log(pc.dim(' No changes made.'));
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
// Scaffold
|
|
160
|
+
const s = p.spinner();
|
|
161
|
+
s.start(`Setting up ${answers.colleagueName}...`);
|
|
162
|
+
const result = scaffold(cwd, answers, analysis);
|
|
163
|
+
createInitialCommit(cwd, result.filesCreated, answers.colleagueName);
|
|
164
|
+
s.stop(`${answers.colleagueName} is ready.`);
|
|
165
|
+
// GitHub setup
|
|
166
|
+
if (answers.setupGitHub !== 'skip' && analysis.hasGhCli) {
|
|
167
|
+
setupGitHub(cwd, answers.setupGitHub, answers.repoName);
|
|
168
|
+
}
|
|
169
|
+
// Report usage to API (after files written — safe)
|
|
170
|
+
const repoId = analysis.gitHubRemoteUrl || cwd;
|
|
171
|
+
await reportUsage(answers.token, repoId).catch(() => { });
|
|
172
|
+
// Summary
|
|
173
|
+
printCompleteScreen(answers.colleagueName);
|
|
174
|
+
if (result.gitInitialized) {
|
|
175
|
+
console.log(` ${pc.green('✓')} Initialized git repository`);
|
|
176
|
+
}
|
|
177
|
+
for (const file of result.filesCreated) {
|
|
178
|
+
console.log(` ${pc.green('✓')} Created ${file}`);
|
|
179
|
+
}
|
|
180
|
+
console.log(` ${pc.green('✓')} Created initial commit`);
|
|
181
|
+
console.log();
|
|
182
|
+
printGetStarted(answers.colleagueName);
|
|
183
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type RepoAnalysis, type ThufirSettings } from './analyze.js';
|
|
2
|
+
export interface InitAnswers {
|
|
3
|
+
token: string;
|
|
4
|
+
userName: string;
|
|
5
|
+
colleagueName: string;
|
|
6
|
+
projectMode: 'single' | 'multi';
|
|
7
|
+
projectDescription: string;
|
|
8
|
+
projectFocus: 'code' | 'planning' | 'hybrid';
|
|
9
|
+
traits: string[];
|
|
10
|
+
skillPackages: string[];
|
|
11
|
+
skillTriggers: string[];
|
|
12
|
+
setupGitHub: 'create' | 'connect' | 'skip';
|
|
13
|
+
repoName: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function runPrompts(analysis: RepoAnalysis, savedToken?: string | null): Promise<InitAnswers | null>;
|
|
16
|
+
export declare function runMergePrompts(analysis: RepoAnalysis): Promise<InitAnswers | null>;
|
|
17
|
+
export type RealignAction = 'realign' | 'reconfigure' | 'cancel';
|
|
18
|
+
export interface RealignResult {
|
|
19
|
+
action: RealignAction;
|
|
20
|
+
answers: InitAnswers;
|
|
21
|
+
}
|
|
22
|
+
export declare function runRealignPrompts(settings: ThufirSettings, savedToken: string | null): Promise<RealignResult | null>;
|