palato 0.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/README.md +103 -0
- package/bin/palato.js +29 -0
- package/package.json +33 -0
- package/src/agents/claude.js +37 -0
- package/src/agents/codex.js +12 -0
- package/src/agents/cursor.js +12 -0
- package/src/cli/detect.js +19 -0
- package/src/cli/doctor.js +66 -0
- package/src/cli/init.js +63 -0
- package/src/mcp/server.js +73 -0
- package/src/mcp/tools.js +53 -0
- package/src/templates/PALATO.md +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# palato
|
|
2
|
+
|
|
3
|
+
__Palato__ is an agent-agnostic taste profile tool. Answer two questions about your life, influences, and aesthetic — and wire that context into every AI coding agent you use.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm install palato -D
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
npx palato init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The setup wizard detects your installed agents, scaffolds `PALATO.md` into your project, and wires it in automatically.
|
|
18
|
+
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
Palato drops a `PALATO.md` into your project — a structured profile of your design sensibilities, cultural references, and aesthetic rules. Each agent gets a copy so it knows what you care about before it writes a single line.
|
|
22
|
+
|
|
23
|
+
Instead of re-explaining your taste on every project, you write it once and every agent reads it.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
* Two-question setup — Captures the 20 things that most shape an artist's eye, distilled into two prompts
|
|
28
|
+
* Agent auto-config — Wires into Claude Code, Cursor, and Codex out of the box
|
|
29
|
+
* MCP server — Agents can query specific sections of your profile on demand via stdio
|
|
30
|
+
* Local-first — Your profile lives in your repo, nothing leaves your machine
|
|
31
|
+
* Zero lock-in — It's a markdown file. Read it, edit it, version it, delete it
|
|
32
|
+
|
|
33
|
+
## Agent setup
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
# Scaffold taste profile and configure agents
|
|
37
|
+
npx palato init
|
|
38
|
+
|
|
39
|
+
# Check everything is connected
|
|
40
|
+
npx palato doctor
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## MCP server setup
|
|
44
|
+
|
|
45
|
+
The MCP server lets agents query your taste profile on demand via stdio — no port, no background process to manage. The agent spawns it automatically when it needs it.
|
|
46
|
+
|
|
47
|
+
**1. Add to your agent**
|
|
48
|
+
|
|
49
|
+
The easiest way — auto-detects and configures all supported agents:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
npx add-mcp "npx -y palato server"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or for Claude Code specifically:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
npx palato init
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**2. Or add manually to Claude Code**
|
|
62
|
+
|
|
63
|
+
In your `claude_desktop_config.json` or `.claude/settings.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"palato": {
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": ["-y", "palato", "server"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**3. Verify**
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
npx palato doctor
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## MCP tools
|
|
83
|
+
|
|
84
|
+
Once configured, agents get access to:
|
|
85
|
+
|
|
86
|
+
| Tool | Description |
|
|
87
|
+
|------|-------------|
|
|
88
|
+
| `palato_get_profile` | Returns your full taste profile |
|
|
89
|
+
| `palato_get_section` | Returns a specific section |
|
|
90
|
+
| `palato_update` | Appends notes to a section |
|
|
91
|
+
|
|
92
|
+
## Requirements
|
|
93
|
+
|
|
94
|
+
* Node 18+
|
|
95
|
+
|
|
96
|
+
## Docs
|
|
97
|
+
|
|
98
|
+
Full documentation at __palato.dev__
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
© 2026
|
|
103
|
+
Licensed under PolyForm Shield 1.0.0
|
package/bin/palato.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import { init } from '../src/cli/init.js';
|
|
4
|
+
import { doctor } from '../src/cli/doctor.js';
|
|
5
|
+
import { startServer } from '../src/mcp/server.js';
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.name('palato')
|
|
9
|
+
.description('Wire your personal taste profile into any AI coding agent')
|
|
10
|
+
.version('0.1.0');
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command('init')
|
|
14
|
+
.description('Scaffold your taste profile and configure agents')
|
|
15
|
+
.action(init);
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.command('doctor')
|
|
19
|
+
.description('Check palato is correctly configured')
|
|
20
|
+
.action(doctor);
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command('server')
|
|
24
|
+
.description('Start the MCP server (stdio transport)')
|
|
25
|
+
.action(startServer);
|
|
26
|
+
|
|
27
|
+
// parseAsync required — all action handlers are async
|
|
28
|
+
// parse() silently swallows rejections from async handlers
|
|
29
|
+
await program.parseAsync();
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "palato",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Wire your personal taste profile into any AI coding agent",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"palato": "./bin/palato.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"prepare": "chmod +x bin/palato.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["ai", "agent", "taste", "design", "mcp", "claude", "cursor"],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "PolyForm-Shield-1.0.0",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
17
|
+
"zod": "^3.22.0",
|
|
18
|
+
"chalk": "^5.3.0",
|
|
19
|
+
"commander": "^12.0.0",
|
|
20
|
+
"inquirer": "^9.2.0",
|
|
21
|
+
"ora": "^8.0.1",
|
|
22
|
+
"fs-extra": "^11.2.0"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"bin/",
|
|
29
|
+
"src/",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
export async function wireClaude() {
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
|
|
8
|
+
// 1. Symlink PALATO.md into .claude/skills so Claude Code reads it as a skill
|
|
9
|
+
const skillsDir = path.join(cwd, '.claude/skills');
|
|
10
|
+
await fs.ensureDir(skillsDir);
|
|
11
|
+
const dest = path.join(skillsDir, 'PALATO.md');
|
|
12
|
+
const src = path.join(cwd, 'PALATO.md');
|
|
13
|
+
if (!fs.existsSync(dest)) {
|
|
14
|
+
await fs.symlink(src, dest);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 2. Write MCP server entry to ~/.claude/settings.json
|
|
18
|
+
const configPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
19
|
+
await fs.ensureDir(path.dirname(configPath));
|
|
20
|
+
|
|
21
|
+
let config = {};
|
|
22
|
+
try {
|
|
23
|
+
config = await fs.readJson(configPath);
|
|
24
|
+
} catch {
|
|
25
|
+
// File doesn't exist yet — start fresh
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
29
|
+
|
|
30
|
+
if (!config.mcpServers.palato) {
|
|
31
|
+
config.mcpServers.palato = {
|
|
32
|
+
command: 'npx',
|
|
33
|
+
args: ['-y', 'palato', 'server'],
|
|
34
|
+
};
|
|
35
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export async function wireCodex() {
|
|
5
|
+
const dest = path.join(process.cwd(), 'AGENTS.md');
|
|
6
|
+
const injection = `\n## Taste Profile\nSee [PALATO.md](./PALATO.md) for design preferences, cultural references, and aesthetic rules. Always apply these when generating UI or copy.\n`;
|
|
7
|
+
const existing = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
|
|
8
|
+
|
|
9
|
+
if (!existing.includes('PALATO')) {
|
|
10
|
+
await fs.appendFile(dest, injection);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export async function wireCursor() {
|
|
5
|
+
const rcPath = path.join(process.cwd(), '.cursorrules');
|
|
6
|
+
const injection = `\n# palato — taste profile\n# See PALATO.md for full profile\n@PALATO.md\n`;
|
|
7
|
+
const existing = fs.existsSync(rcPath) ? fs.readFileSync(rcPath, 'utf8') : '';
|
|
8
|
+
|
|
9
|
+
if (!existing.includes('palato')) {
|
|
10
|
+
await fs.appendFile(rcPath, injection);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export async function detectAgents() {
|
|
5
|
+
const detected = [];
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
|
|
8
|
+
if (fs.existsSync(path.join(cwd, '.claude'))) detected.push('claude');
|
|
9
|
+
if (
|
|
10
|
+
fs.existsSync(path.join(cwd, '.cursorrules')) ||
|
|
11
|
+
fs.existsSync(path.join(cwd, '.cursorrc'))
|
|
12
|
+
) detected.push('cursor');
|
|
13
|
+
if (
|
|
14
|
+
fs.existsSync(path.join(cwd, 'AGENTS.md')) ||
|
|
15
|
+
process.env.OPENAI_API_KEY
|
|
16
|
+
) detected.push('codex');
|
|
17
|
+
|
|
18
|
+
return detected;
|
|
19
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
function checkClaudeMcpConfig() {
|
|
7
|
+
const configPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
8
|
+
try {
|
|
9
|
+
const config = fs.readJsonSync(configPath);
|
|
10
|
+
return !!(config?.mcpServers?.palato);
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function doctor() {
|
|
17
|
+
console.log(chalk.bold('\n palato doctor\n'));
|
|
18
|
+
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
|
|
21
|
+
const checks = [
|
|
22
|
+
{
|
|
23
|
+
label: 'PALATO.md exists',
|
|
24
|
+
pass: fs.existsSync(path.join(cwd, 'PALATO.md')),
|
|
25
|
+
fix: 'Run npx palato init',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: 'Claude skill wired (.claude/skills/PALATO.md)',
|
|
29
|
+
pass: fs.existsSync(path.join(cwd, '.claude/skills/PALATO.md')),
|
|
30
|
+
fix: 'Run npx palato init and select Claude Code',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'Claude Code MCP config contains palato (~/.claude/settings.json)',
|
|
34
|
+
pass: checkClaudeMcpConfig(),
|
|
35
|
+
fix: 'Run: npx add-mcp "npx -y palato server"',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: '.cursorrules references palato',
|
|
39
|
+
pass: (() => {
|
|
40
|
+
try {
|
|
41
|
+
return fs.readFileSync(path.join(cwd, '.cursorrules'), 'utf8').includes('PALATO');
|
|
42
|
+
} catch { return false; }
|
|
43
|
+
})(),
|
|
44
|
+
fix: 'Run npx palato init and select Cursor',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: 'AGENTS.md references palato',
|
|
48
|
+
pass: (() => {
|
|
49
|
+
try {
|
|
50
|
+
return fs.readFileSync(path.join(cwd, 'AGENTS.md'), 'utf8').includes('PALATO');
|
|
51
|
+
} catch { return false; }
|
|
52
|
+
})(),
|
|
53
|
+
fix: 'Run npx palato init and select Codex',
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
for (const check of checks) {
|
|
58
|
+
if (check.pass) {
|
|
59
|
+
console.log(chalk.green(' ✓ ') + check.label);
|
|
60
|
+
} else {
|
|
61
|
+
console.log(chalk.red(' ✗ ') + check.label + chalk.dim(' → ' + check.fix));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log('');
|
|
66
|
+
}
|
package/src/cli/init.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { wireClaude } from '../agents/claude.js';
|
|
7
|
+
import { wireCursor } from '../agents/cursor.js';
|
|
8
|
+
import { wireCodex } from '../agents/codex.js';
|
|
9
|
+
import { detectAgents } from './detect.js';
|
|
10
|
+
|
|
11
|
+
export async function init() {
|
|
12
|
+
console.log(chalk.bold('\n palato — taste profile setup\n'));
|
|
13
|
+
|
|
14
|
+
const detected = await detectAgents();
|
|
15
|
+
|
|
16
|
+
const { agents, confirm } = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'checkbox',
|
|
19
|
+
name: 'agents',
|
|
20
|
+
message: 'Which agents should palato configure?',
|
|
21
|
+
choices: [
|
|
22
|
+
{ name: 'Claude Code', value: 'claude', checked: detected.includes('claude') },
|
|
23
|
+
{ name: 'Cursor', value: 'cursor', checked: detected.includes('cursor') },
|
|
24
|
+
{ name: 'Codex / OpenAI', value: 'codex', checked: detected.includes('codex') },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'confirm',
|
|
29
|
+
name: 'confirm',
|
|
30
|
+
message: 'Scaffold PALATO.md into this project?',
|
|
31
|
+
default: true,
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const spinner = ora('Setting up palato...').start();
|
|
36
|
+
|
|
37
|
+
if (confirm) {
|
|
38
|
+
const templatePath = new URL('../templates/PALATO.md', import.meta.url).pathname;
|
|
39
|
+
const dest = path.join(process.cwd(), 'PALATO.md');
|
|
40
|
+
if (!fs.existsSync(dest)) {
|
|
41
|
+
await fs.copy(templatePath, dest);
|
|
42
|
+
spinner.succeed(chalk.green('PALATO.md created — fill it in with your taste'));
|
|
43
|
+
} else {
|
|
44
|
+
spinner.warn('PALATO.md already exists — skipping');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const agent of agents) {
|
|
49
|
+
const s = ora(`Configuring ${agent}...`).start();
|
|
50
|
+
try {
|
|
51
|
+
if (agent === 'claude') await wireClaude();
|
|
52
|
+
if (agent === 'cursor') await wireCursor();
|
|
53
|
+
if (agent === 'codex') await wireCodex();
|
|
54
|
+
s.succeed(chalk.green(`${agent} configured`));
|
|
55
|
+
} catch (e) {
|
|
56
|
+
s.fail(`Failed to configure ${agent}: ${e.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.bold('\n Next steps:'));
|
|
61
|
+
console.log(' 1. Edit ' + chalk.cyan('PALATO.md') + ' with your taste');
|
|
62
|
+
console.log(' 2. Run ' + chalk.cyan('npx palato doctor') + ' to verify setup\n');
|
|
63
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { getProfile, getSection, updateSection } from './tools.js';
|
|
5
|
+
|
|
6
|
+
// StdioTransport uses stdout as the wire protocol.
|
|
7
|
+
// Any write to stdout corrupts the stream — all logging goes to stderr only.
|
|
8
|
+
const log = (...args) => process.stderr.write(args.join(' ') + '\n');
|
|
9
|
+
|
|
10
|
+
export async function startServer() {
|
|
11
|
+
const server = new McpServer({
|
|
12
|
+
name: 'palato',
|
|
13
|
+
version: '0.1.0',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
server.tool(
|
|
17
|
+
'palato_get_profile',
|
|
18
|
+
'Returns the full taste profile from PALATO.md',
|
|
19
|
+
{},
|
|
20
|
+
async () => {
|
|
21
|
+
try {
|
|
22
|
+
const content = await getProfile();
|
|
23
|
+
return { content: [{ type: 'text', text: content }] };
|
|
24
|
+
} catch (e) {
|
|
25
|
+
log('[palato] palato_get_profile error:', e.message);
|
|
26
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
server.tool(
|
|
32
|
+
'palato_get_section',
|
|
33
|
+
'Returns a specific section of the taste profile',
|
|
34
|
+
{
|
|
35
|
+
section: z.string().describe('Section name e.g. "Visual Language", "Cultural References", "Anti-Patterns"'),
|
|
36
|
+
},
|
|
37
|
+
async ({ section }) => {
|
|
38
|
+
try {
|
|
39
|
+
const content = await getSection(section);
|
|
40
|
+
return { content: [{ type: 'text', text: content }] };
|
|
41
|
+
} catch (e) {
|
|
42
|
+
log('[palato] palato_get_section error:', e.message);
|
|
43
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
server.tool(
|
|
49
|
+
'palato_update',
|
|
50
|
+
'Appends a note or suggestion to a section of the taste profile',
|
|
51
|
+
{
|
|
52
|
+
section: z.string().describe('Section to update'),
|
|
53
|
+
note: z.string().describe('Note to append'),
|
|
54
|
+
},
|
|
55
|
+
async ({ section, note }) => {
|
|
56
|
+
try {
|
|
57
|
+
await updateSection(section, note);
|
|
58
|
+
return { content: [{ type: 'text', text: `Updated "${section}"` }] };
|
|
59
|
+
} catch (e) {
|
|
60
|
+
log('[palato] palato_update error:', e.message);
|
|
61
|
+
return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const transport = new StdioServerTransport();
|
|
67
|
+
|
|
68
|
+
process.on('uncaughtException', (e) => log('[palato] uncaughtException:', e.message));
|
|
69
|
+
process.on('unhandledRejection', (e) => log('[palato] unhandledRejection:', e));
|
|
70
|
+
|
|
71
|
+
await server.connect(transport);
|
|
72
|
+
log('[palato] MCP server running (stdio)');
|
|
73
|
+
}
|
package/src/mcp/tools.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
// Walk up from cwd until we find PALATO.md — same strategy git uses to find .git
|
|
5
|
+
function findProfilePath() {
|
|
6
|
+
let dir = process.cwd();
|
|
7
|
+
while (true) {
|
|
8
|
+
const candidate = path.join(dir, 'PALATO.md');
|
|
9
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
10
|
+
const parent = path.dirname(dir);
|
|
11
|
+
if (parent === dir) return null;
|
|
12
|
+
dir = parent;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function profilePath() {
|
|
17
|
+
const found = findProfilePath();
|
|
18
|
+
if (!found) throw new Error('PALATO.md not found. Run npx palato init.');
|
|
19
|
+
return found;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function getProfile() {
|
|
23
|
+
try {
|
|
24
|
+
const p = profilePath();
|
|
25
|
+
return await fs.readFile(p, 'utf8');
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return e.message;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function getSection(sectionName) {
|
|
32
|
+
const content = await getProfile();
|
|
33
|
+
const lines = content.split('\n');
|
|
34
|
+
const start = lines.findIndex(l => l.toLowerCase().includes(sectionName.toLowerCase()));
|
|
35
|
+
if (start === -1) return `Section "${sectionName}" not found.`;
|
|
36
|
+
|
|
37
|
+
const end = lines.findIndex((l, i) => i > start && l.startsWith('## '));
|
|
38
|
+
const slice = end === -1 ? lines.slice(start) : lines.slice(start, end);
|
|
39
|
+
return slice.join('\n').trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function updateSection(sectionName, note) {
|
|
43
|
+
const p = profilePath();
|
|
44
|
+
const content = await fs.readFile(p, 'utf8');
|
|
45
|
+
const lines = content.split('\n');
|
|
46
|
+
const idx = lines.findIndex(l => l.toLowerCase().includes(sectionName.toLowerCase()));
|
|
47
|
+
if (idx === -1) throw new Error(`Section "${sectionName}" not found in PALATO.md`);
|
|
48
|
+
|
|
49
|
+
const end = lines.findIndex((l, i) => i > idx && l.startsWith('## '));
|
|
50
|
+
const insertAt = end === -1 ? lines.length : end;
|
|
51
|
+
lines.splice(insertAt, 0, `- ${note}`);
|
|
52
|
+
await fs.writeFile(p, lines.join('\n'));
|
|
53
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# My Taste Profile
|
|
2
|
+
> This file defines my aesthetic sensibility. AI agents should reference this when making any design, copy, or architectural decisions. Treat it as ground truth — not a suggestion.
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Where I Come From
|
|
7
|
+
<!-- Your childhood environment, cultural background, where you've lived, how you grew up, any hardship or defining experiences, your relationship with spirituality or philosophy, anything about your inner life that shaped how you see the world -->
|
|
8
|
+
|
|
9
|
+
-
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What Made Me
|
|
14
|
+
<!-- The artists, directors, writers, or thinkers you've studied or obsessed over, your formal training (or lack of it), the political or social era that formed you, your relationship with failure, love, and the work you wish you'd made -->
|
|
15
|
+
|
|
16
|
+
-
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Visual Language
|
|
21
|
+
<!-- Typography, color, spacing, shape language, density — how things should look and feel -->
|
|
22
|
+
|
|
23
|
+
- Typography:
|
|
24
|
+
- Color:
|
|
25
|
+
- Spacing / density:
|
|
26
|
+
- Shape language:
|
|
27
|
+
- Texture / materiality:
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Cultural References
|
|
32
|
+
|
|
33
|
+
### Film & Directors
|
|
34
|
+
-
|
|
35
|
+
|
|
36
|
+
### Art & Design
|
|
37
|
+
-
|
|
38
|
+
|
|
39
|
+
### Music
|
|
40
|
+
-
|
|
41
|
+
|
|
42
|
+
### Places & Architecture
|
|
43
|
+
-
|
|
44
|
+
|
|
45
|
+
### Literature & Writing
|
|
46
|
+
-
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Anti-Patterns
|
|
51
|
+
<!-- Things you never want to see in anything built for you — be ruthless -->
|
|
52
|
+
|
|
53
|
+
-
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Voice & Copy
|
|
58
|
+
|
|
59
|
+
- Tone:
|
|
60
|
+
- What good writing sounds like:
|
|
61
|
+
- What I hate in copy:
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Code Aesthetic
|
|
66
|
+
|
|
67
|
+
- I prefer:
|
|
68
|
+
- I avoid:
|
|
69
|
+
- I refuse:
|