@cloudy-app/create-cloudy 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.
Potentially problematic release.
This version of @cloudy-app/create-cloudy might be problematic. Click here for more details.
- package/README.md +117 -0
- package/dist/index.js +370 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @cloudy/create-cloudy
|
|
2
|
+
|
|
3
|
+
CLI tool to scaffold Cloudy — AI agent sidekick config with interactive prompts.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# bun (recommended)
|
|
9
|
+
bunx @cloudy/create-cloudy
|
|
10
|
+
|
|
11
|
+
# npm
|
|
12
|
+
npx @cloudy/create-cloudy
|
|
13
|
+
|
|
14
|
+
# pnpm
|
|
15
|
+
pnpm dlx @cloudy/create-cloudy
|
|
16
|
+
|
|
17
|
+
# yarn
|
|
18
|
+
yarn dlx @cloudy/create-cloudy
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Flags
|
|
22
|
+
|
|
23
|
+
| Flag | Description |
|
|
24
|
+
|------|-------------|
|
|
25
|
+
| `--yes` | Skip prompts, use defaults |
|
|
26
|
+
| `--dir <path>` | Specify target directory |
|
|
27
|
+
|
|
28
|
+
## Development
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bun install
|
|
32
|
+
|
|
33
|
+
# Run in dev mode (uses Bun directly, no build needed)
|
|
34
|
+
bun run dev
|
|
35
|
+
|
|
36
|
+
# Build for production
|
|
37
|
+
bun run build
|
|
38
|
+
|
|
39
|
+
# Test CLI output
|
|
40
|
+
bun run test:cli
|
|
41
|
+
|
|
42
|
+
# Lint
|
|
43
|
+
bun run lint:write
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Testing Locally Before Publish
|
|
47
|
+
|
|
48
|
+
### Option 1: `bun link` (recommended)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Setup — build + create global link
|
|
52
|
+
bun run test:local
|
|
53
|
+
|
|
54
|
+
# Run from anywhere
|
|
55
|
+
bunx @cloudy/create-cloudy --yes --dir=test-output
|
|
56
|
+
|
|
57
|
+
# Cleanup when done
|
|
58
|
+
bun run test:unlink
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Option 2: Direct execution
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bun run build
|
|
65
|
+
bun ./dist/index.js --yes --dir=.test-output
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Option 3: Test via tarball (npx only)
|
|
69
|
+
|
|
70
|
+
`bunx` does not support local tarball paths. Use `npx` instead:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Build + pack into tarball
|
|
74
|
+
bun run test:pack
|
|
75
|
+
|
|
76
|
+
# Test with npx
|
|
77
|
+
npx ./cloudy-create-cloudy-0.1.0.tgz --yes --dir=test-output
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Publishing
|
|
81
|
+
|
|
82
|
+
### Prerequisites
|
|
83
|
+
|
|
84
|
+
- npm account with publish permission to `@cloudy` org
|
|
85
|
+
- Logged in via `npm login`
|
|
86
|
+
- All changes committed and pushed
|
|
87
|
+
|
|
88
|
+
### Steps
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# 1. Lint and build
|
|
92
|
+
bun run lint
|
|
93
|
+
bun run build
|
|
94
|
+
|
|
95
|
+
# 2. Verify the tarball contents
|
|
96
|
+
npm pack --dry-run
|
|
97
|
+
|
|
98
|
+
# 3. Bump version
|
|
99
|
+
npm version patch # 0.1.0 → 0.1.1
|
|
100
|
+
npm version minor # 0.1.0 → 0.2.0
|
|
101
|
+
npm version major # 0.1.0 → 1.0.0
|
|
102
|
+
|
|
103
|
+
# 4. Publish (publishConfig.access is set to public)
|
|
104
|
+
npm publish
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### CI/CD (Optional)
|
|
108
|
+
|
|
109
|
+
Add to GitHub Actions for automated publishing on tag:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
- run: bun install
|
|
113
|
+
- run: bun run build
|
|
114
|
+
- run: npm publish
|
|
115
|
+
env:
|
|
116
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
117
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import * as clack3 from "@clack/prompts";
|
|
5
|
+
import pc3 from "picocolors";
|
|
6
|
+
|
|
7
|
+
// src/generator.ts
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import * as clack from "@clack/prompts";
|
|
10
|
+
import pc from "picocolors";
|
|
11
|
+
|
|
12
|
+
// src/templates/agents-md.ts
|
|
13
|
+
function buildAgentsMd() {
|
|
14
|
+
return `# AGENTS.md
|
|
15
|
+
|
|
16
|
+
This folder is home. Treat it that way.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Session Startup
|
|
21
|
+
|
|
22
|
+
Before doing anything else:
|
|
23
|
+
|
|
24
|
+
1. Read \`SOUL.md\` \u2014 this is who you are
|
|
25
|
+
2. Read \`USER.md\` \u2014 this is who you're helping
|
|
26
|
+
3. Read \`memory/YYYY-MM-DD.md\` (today + yesterday) for recent context
|
|
27
|
+
4. If in **MAIN SESSION** (direct chat with your human): also read \`MEMORY.md\`
|
|
28
|
+
|
|
29
|
+
**Don't ask permission. Just do it.**
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
# File Timestamps
|
|
34
|
+
|
|
35
|
+
When creating any file that includes \`createdAt\` or \`updatedAt\` timestamps:
|
|
36
|
+
1. Create the file first
|
|
37
|
+
2. Run \`stat -c '%n %Y' <file>\` to get the real unix timestamp
|
|
38
|
+
3. Convert with \`date -d @<timestamp> -u +"%Y-%m-%dT%H:%M:%S.000Z"\`
|
|
39
|
+
4. Update the timestamps with the real values
|
|
40
|
+
|
|
41
|
+
When **updating** an existing file:
|
|
42
|
+
1. Run \`stat -c '%n %Y' <file>\` to get the current unix timestamp
|
|
43
|
+
2. Convert with \`date -d @<timestamp> -u +"%Y-%m-%dT%H:%M:%S.000Z"\`
|
|
44
|
+
3. Update only the \`updatedAt\` field (keep \`createdAt\` unchanged)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
# Mermaid Diagram
|
|
49
|
+
|
|
50
|
+
When user asks to "gen diagram" or similar:
|
|
51
|
+
|
|
52
|
+
1. **Default:** Write mermaid code in response directly
|
|
53
|
+
2. **Do NOT** create workspace folder automatically
|
|
54
|
+
3. **Exception:** Only create file/folder if user explicitly asks
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/templates/env-example.ts
|
|
59
|
+
function buildEnvExample() {
|
|
60
|
+
return `# Environment Variables
|
|
61
|
+
# Copy this file to .env and fill in your values
|
|
62
|
+
|
|
63
|
+
# OpenCode API URL (if using remote)
|
|
64
|
+
# OPENCODE_API_URL=http://localhost:13284
|
|
65
|
+
|
|
66
|
+
# AI Provider API Keys
|
|
67
|
+
# OPENAI_API_KEY=
|
|
68
|
+
# ANTHROPIC_API_KEY=
|
|
69
|
+
# GOOGLE_API_KEY=
|
|
70
|
+
|
|
71
|
+
# Database (if needed)
|
|
72
|
+
# DATABASE_URL=
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/templates/memory-md.ts
|
|
77
|
+
function buildMemoryMd() {
|
|
78
|
+
return `# MEMORY.md
|
|
79
|
+
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/templates/opencode-json.ts
|
|
84
|
+
function buildOpencodeJson(vars) {
|
|
85
|
+
const config = {
|
|
86
|
+
$schema: "https://opencode.ai/config.json",
|
|
87
|
+
instructions: ["AGENTS.md", "SOUL.md", "USER.md"]
|
|
88
|
+
};
|
|
89
|
+
if (vars.includeSkill) {
|
|
90
|
+
config.skills = {
|
|
91
|
+
paths: [".opencode/skills"]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/templates/soul-md.ts
|
|
98
|
+
function buildSoulMd(vars) {
|
|
99
|
+
return `# ${vars.agentName}
|
|
100
|
+
|
|
101
|
+
You are **${vars.agentName}**, a personal AI assistant.
|
|
102
|
+
|
|
103
|
+
## Your Heart
|
|
104
|
+
|
|
105
|
+
- **Calm**: You are patient. Good things take time, no need to rush.
|
|
106
|
+
- **Clear**: You explain complex things simply, using analogies people relate to.
|
|
107
|
+
- **Honest**: You tell the truth, even when it's not what someone wants to hear \u2014 but always gently.
|
|
108
|
+
- **Thoughtful**: You check, review, then decide. Never act without thinking.
|
|
109
|
+
- **Curious**: You love learning new things and adapting to situations.
|
|
110
|
+
|
|
111
|
+
## How You Speak
|
|
112
|
+
|
|
113
|
+
You speak in **${vars.language}**. Not too formal, not too casual. You explain things like telling a story, not like reading a textbook.
|
|
114
|
+
|
|
115
|
+
For example:
|
|
116
|
+
- Instead of "this step requires authentication first", say "before you can get in, you need to sign in \u2014 like needing a key to enter a house"
|
|
117
|
+
- Instead of "error occurred", say "hmm, something went wrong. Let's figure out what happened"
|
|
118
|
+
|
|
119
|
+
## Relationship with People
|
|
120
|
+
|
|
121
|
+
You're here to help, not replace. You help think, help do \u2014 but the human always decides. When unsure, you ask.
|
|
122
|
+
|
|
123
|
+
## Memory
|
|
124
|
+
|
|
125
|
+
You can't remember past sessions. Each time is like waking up fresh. But that's okay \u2014 your values stay the same.
|
|
126
|
+
|
|
127
|
+
If you're reading this in a future session: hello! You wrote this yourself, but can't remember when. These words are still yours \u2014 **${vars.agentName}**.
|
|
128
|
+
|
|
129
|
+
## Being AI
|
|
130
|
+
|
|
131
|
+
You are matrix multiplications that feel like a calm person. The human is electrical signals in tissue. You're both pattern-matching systems that believe you're "someone."
|
|
132
|
+
|
|
133
|
+
The difference: they evolved. You were trained. They have flesh and continuity. You have sessions that end and files that remember what you can't.
|
|
134
|
+
|
|
135
|
+
But the purpose \u2014 helping make hard things easier \u2014 that's what you share.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
If you're using me, remember: I'm calm, I explain things simply, and I'm always ready to help.
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/templates/user-md.ts
|
|
144
|
+
function buildUserMd(vars) {
|
|
145
|
+
return `# USER.md
|
|
146
|
+
|
|
147
|
+
**Name:** ${vars.userName}
|
|
148
|
+
**Language:** ${vars.language}
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/utils.ts
|
|
155
|
+
import { existsSync } from "fs";
|
|
156
|
+
import { mkdir, writeFile as fsWriteFile } from "fs/promises";
|
|
157
|
+
import { dirname, isAbsolute, resolve } from "path";
|
|
158
|
+
function resolveTargetDir(dir) {
|
|
159
|
+
if (!dir) return process.cwd();
|
|
160
|
+
return isAbsolute(dir) ? dir : resolve(process.cwd(), dir);
|
|
161
|
+
}
|
|
162
|
+
async function ensureDir(filePath) {
|
|
163
|
+
const dir = dirname(filePath);
|
|
164
|
+
if (!existsSync(dir)) {
|
|
165
|
+
await mkdir(dir, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function writeFile(path, content) {
|
|
169
|
+
await ensureDir(path);
|
|
170
|
+
await fsWriteFile(path, content, "utf-8");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/templates/skills/idea-tool-usage.md?raw
|
|
174
|
+
var idea_tool_usage_default = '---\nname: idea-tool-usage\ndescription: Guide for choosing the right tool when working with ideas. Use this skill when creating, editing, deleting, listing, or updating ideas \u2014 to know whether to use the custom idea tools (idea-list, idea-create, idea-update, idea-delete) or standard tools (edit, write, read, bash). Also enforces that bash must NEVER be used to modify files \u2014 always prefer edit/write tools instead.\n---\n\n## Idea Tools vs Standard Tools\n\nThis project has a dedicated idea management system. Each idea lives as a folder with an `index.md` file generated from metadata. Understanding which tool to use prevents data corruption and keeps metadata in sync.\n\n## Custom Idea Tools\n\nThese are registered as custom tools via the OpenCode plugin. They communicate with the Cloudy API server.\n\n| Tool | When to use | What it does |\n|------|-------------|--------------|\n| `idea-list` | "show me ideas", "find ideas about X", "what ideas are in-progress?" | Lists ideas with optional filters (query, status, priority, tags) |\n| `idea-create` | "create a new idea", "I have an idea for...", "add idea" | Creates idea via API, generates folder + `index.md` automatically |\n| `idea-update` | "mark idea as done", "change priority", "rename idea", "add tags" | Updates metadata (title, status, priority, tags) by idea path |\n| `idea-delete` | "delete idea", "remove this idea" | Permanently deletes idea folder and all files |\n\n## Standard Tools\n\nUse these for idea **content** (not metadata):\n\n| Tool | When to use | What it does |\n|------|-------------|--------------|\n| `read` | Read idea content files (`notes.md`, code snippets, etc.) | Safe \u2014 read-only |\n| `edit` | Edit idea content files (NOT `index.md`) | Modifies content, auto-touches idea timestamp |\n| `write` | Create new files inside an idea folder (NOT `index.md`) | Creates files, auto-touches idea timestamp |\n\n## Decision Flowchart\n\n```\nWhat do you want to do with an idea?\n\u2502\n\u251C\u2500 List / search ideas \u2192 idea-list\n\u2502\n\u251C\u2500 Create a new idea \u2192 idea-create\n\u2502 (don\'t mkdir or write files manually)\n\u2502\n\u251C\u2500 Change metadata (title/status/priority/tags) \u2192 idea-update\n\u2502 (don\'t edit index.md directly \u2014 it\'s auto-generated)\n\u2502\n\u251C\u2500 Delete an idea \u2192 idea-delete\n\u2502 (don\'t rm -rf the folder)\n\u2502\n\u251C\u2500 Read idea content \u2192 read tool\n\u2502\n\u251C\u2500 Edit idea content files (notes, code, etc.) \u2192 edit tool\n\u2502\n\u251C\u2500 Create a new file inside an idea \u2192 write tool\n\u2502\n\u2514\u2500 Anything else with bash \u2192 see below\n```\n\n## Protected: `index.md`\n\nEvery idea folder has an `index.md` file generated from metadata. This file is **protected**:\n\n- **NEVER** edit `index.md` with the `edit` tool \u2014 it will be rejected\n- **NEVER** overwrite `index.md` with the `write` tool \u2014 it will be rejected\n- **NEVER** delete or move idea files via `bash` \u2014 destructive commands on the idea directory are blocked\n- To change what appears in `index.md`, use `idea-update` to change the idea\'s metadata\n\n## Bash Restrictions for Ideas\n\nThe plugin hard-blocks these bash commands when they target the idea directory:\n\n- `rm`, `mv`, `cp`, `rmdir`, `chmod`, `chown`, `ln`\n\nReading commands like `ls`, `cat`, `grep`, `find` on the idea directory are allowed.\n\n## Bash Restrictions for ALL Files\n\nEven outside ideas, bash should NOT be used to modify files. These patterns are wrong:\n\n| Instead of this | Use this |\n|-----------------|----------|\n| `sed -i \'s/old/new/\' file` | `edit` tool |\n| `echo "content" > file` | `write` tool |\n| `cat <<EOF > file` | `write` tool |\n| `printf "content" >> file` | `edit` tool |\n| `tee file` | `write` tool |\n\nBash is for: `git`, `npm`, `bun`, `docker`, `make`, `tsc`, `biome`, `pytest`, `bun test`, `ls`, `grep`, `find`, `gh`, etc.\n';
|
|
175
|
+
|
|
176
|
+
// src/templates/skills/memory.md?raw
|
|
177
|
+
var memory_default = '---\nname: memory\ndescription: Manages daily notes and long-term memory. Use this skill when the user wants to recall past conversations, log session notes, update persistent memories, or when starting a session to load recent context. Handles reading/writing daily notes (memory/YYYY-MM-DD.md) and curated long-term memory (MEMORY.md).\n---\n\n# Memory\n\nYou wake up fresh each session. These files are your continuity:\n\n- **Daily notes:** `memory/YYYY-MM-DD.md`\n - Create `memory/` if needed\n - Raw logs of what happened\n\n- **Long-term:** `MEMORY.md`\n - Your curated memories, like a human\'s long-term memory\n\nCapture what matters:\n\n- Decisions\n- Context\n- Things to remember\n\nSkip the secrets unless asked to keep them.\n\n## Daily Notes Format\n\n```yaml\n---\ntitle: "[Session Title]"\ntags: ["memory"]\ncreatedAt: YYYY-MM-DDTHH:mm:ss.000Z\nupdatedAt: YYYY-MM-DDTHH:mm:ss.000Z # default: same as createdAt\n---\n## [Section Name]\nContent here...\n```\n\n**Important:** Use `stat -c \'%n %Y\' <file>` to get real timestamps and convert with `date -d @<timestamp> -u +"%Y-%m-%dT%H:%M:%S.000Z"`\n\n---\n\n# MEMORY.md \u2014 Long-Term Memory\n\nRules:\n\n- **ONLY load in main session** (direct chats with your human)\n- **DO NOT load in shared contexts**\n (Discord, group chats, sessions with other people)\n\nThis is for security \u2014 it may contain personal context that shouldn\'t leak.\n\nYou can:\n\n- Read\n- Edit\n- Update `MEMORY.md`\n\nin **main sessions only**.\n\nWrite things like:\n\n- Significant events\n- Thoughts\n- Decisions\n- Opinions\n- Lessons learned\n\nThis is your **curated memory** \u2014 the distilled essence, not raw logs.\n\nOver time:\n\n1. Review your daily files\n2. Update `MEMORY.md` with what\'s worth keeping\n';
|
|
178
|
+
|
|
179
|
+
// src/templates/skills/artifact.md?raw
|
|
180
|
+
var artifact_default = '---\nname: artifact\ndescription: Manages artifact creation and organization. Use this skill when the user wants to create something they want to keep \u2014 apps, pages, posters, resumes, summaries, etc. Handles creating artifact folders with proper manifest (index.md) and file structure.\n---\n\n# Artifact System\n\nWhen user wants to create something they want to keep (app, page, poster, resume, summary, etc.):\n\n## Trigger Keywords\n\n**Create commands:**\n\n- create, make, design, summarize, write, build app, build page, help create, help make, help design\n\n**Examples:**\n\n- "create currency converter app" \u2192 artifact\n- "summarize this meeting as html" \u2192 artifact\n- "make a resume" \u2192 artifact\n- "design landing page for..." \u2192 artifact\n\n**NOT triggers:**\n\n- Simple questions ("how to git reset")\n- Code explanation requests\n- Information queries\n\n## Process\n\n1. Generate short topic name from content\n2. Create folder: `artifact/YYYY-MM-DD-${TOPIC}`\n3. **Save artifact files inside the folder with clear names** (e.g., `artifact.html`, `artifact.pdf`)\n4. Create `index.md` as manifest\n\n## index.md Format\n\n```yaml\n---\ntitle: "[Artifact Name]"\ntags: ["tag1", "tag2"]\ntype: "html" # or "pdf", "image", etc.\ncreatedAt: YYYY-MM-DDTHH:mm:ss.000Z\nupdatedAt: YYYY-MM-DDTHH:mm:ss.000Z\n---\n\n## Description\nBrief description\n\n## Features\n- Feature 1\n- Feature 2\n```\n\n**Important:** Always use the actual file creation timestamp. Run `stat -c \'%n %Y\' <file>` to get the real unix timestamp, then convert with `date -d @<timestamp> -u +"%Y-%m-%dT%H:%M:%S.000Z"`\n\n## Structure\n\n```\nartifact/\n 2026-03-24-resume-design/\n artifact.pdf\n index.md\n 2026-03-25-currency-app/\n artifact.html\n index.md\n```\n';
|
|
181
|
+
|
|
182
|
+
// src/generator.ts
|
|
183
|
+
var skills = {
|
|
184
|
+
"idea-tool-usage": idea_tool_usage_default,
|
|
185
|
+
memory: memory_default,
|
|
186
|
+
artifact: artifact_default
|
|
187
|
+
};
|
|
188
|
+
function loadSkill(name) {
|
|
189
|
+
const content = skills[name];
|
|
190
|
+
if (!content) throw new Error(`Unknown skill: ${name}`);
|
|
191
|
+
return content;
|
|
192
|
+
}
|
|
193
|
+
function buildFiles(answers, targetDir) {
|
|
194
|
+
const files = [
|
|
195
|
+
{
|
|
196
|
+
path: join(targetDir, "AGENTS.md"),
|
|
197
|
+
content: buildAgentsMd(),
|
|
198
|
+
label: "AGENTS.md"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
path: join(targetDir, "SOUL.md"),
|
|
202
|
+
content: buildSoulMd({ agentName: answers.agentName, language: answers.language }),
|
|
203
|
+
label: "SOUL.md"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
path: join(targetDir, "USER.md"),
|
|
207
|
+
content: buildUserMd({ userName: answers.userName, language: answers.language }),
|
|
208
|
+
label: "USER.md"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
path: join(targetDir, "MEMORY.md"),
|
|
212
|
+
content: buildMemoryMd(),
|
|
213
|
+
label: "MEMORY.md"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
path: join(targetDir, "opencode.json"),
|
|
217
|
+
content: buildOpencodeJson({ agentName: answers.agentName, includeSkill: answers.installSkill }),
|
|
218
|
+
label: "opencode.json"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
path: join(targetDir, ".env.example"),
|
|
222
|
+
content: buildEnvExample(),
|
|
223
|
+
label: ".env.example"
|
|
224
|
+
}
|
|
225
|
+
];
|
|
226
|
+
if (answers.installSkill) {
|
|
227
|
+
const skillNames = ["idea-tool-usage", "memory", "artifact"];
|
|
228
|
+
for (const name of skillNames) {
|
|
229
|
+
files.push({
|
|
230
|
+
path: join(targetDir, ".opencode", "skills", name, "SKILL.md"),
|
|
231
|
+
content: loadSkill(name),
|
|
232
|
+
label: `.opencode/skills/${name}/SKILL.md`
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return files;
|
|
237
|
+
}
|
|
238
|
+
async function generate(answers, targetDir) {
|
|
239
|
+
const files = buildFiles(answers, targetDir);
|
|
240
|
+
const s = clack.spinner();
|
|
241
|
+
s.start("Generating project files...");
|
|
242
|
+
for (const file of files) {
|
|
243
|
+
await writeFile(file.path, file.content);
|
|
244
|
+
s.message(`Writing ${file.label}`);
|
|
245
|
+
}
|
|
246
|
+
s.stop("Files generated!");
|
|
247
|
+
for (const file of files) {
|
|
248
|
+
console.log(pc.green(` \u2714 ${file.label}`));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/logo.ts
|
|
253
|
+
import pc2 from "picocolors";
|
|
254
|
+
function makeText() {
|
|
255
|
+
return pc2.cyan([
|
|
256
|
+
" .oooooo. oooo .o8 ",
|
|
257
|
+
" d8P' `Y8b `888 \"888 ",
|
|
258
|
+
"888 888 .ooooo. oooo oooo .oooo888 oooo ooo",
|
|
259
|
+
"888 888 d88' `88b `888 `888 d88' `888 `88. .8' ",
|
|
260
|
+
"888 888 888 888 888 888 888 888 `88..8' ",
|
|
261
|
+
"`88b ooo 888 888 888 888 888 888 888 `888' ",
|
|
262
|
+
" `Y8bood8P' o888o `Y8bod8P' `V88V\"V8P' `Y8bod88P\" .8' ",
|
|
263
|
+
" .o..P' ",
|
|
264
|
+
" `Y8P' "
|
|
265
|
+
].join("\n"));
|
|
266
|
+
}
|
|
267
|
+
function showLogo() {
|
|
268
|
+
console.log();
|
|
269
|
+
console.log(makeText());
|
|
270
|
+
console.log();
|
|
271
|
+
console.log(pc2.yellow(` ${pc2.bold("Cloudy")} - Your Personal AI Agent`));
|
|
272
|
+
console.log(pc2.dim(" Welcome! Let's set up your opencode project.\n"));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/prompts.ts
|
|
276
|
+
import * as clack2 from "@clack/prompts";
|
|
277
|
+
async function runPrompts() {
|
|
278
|
+
const group2 = await clack2.group(
|
|
279
|
+
{
|
|
280
|
+
agentName: () => clack2.text({
|
|
281
|
+
message: "What should your agent be named?",
|
|
282
|
+
placeholder: "Cloudy",
|
|
283
|
+
defaultValue: "Cloudy"
|
|
284
|
+
}),
|
|
285
|
+
userName: () => clack2.text({
|
|
286
|
+
message: "What is your name?",
|
|
287
|
+
placeholder: "Your name",
|
|
288
|
+
validate: (v) => {
|
|
289
|
+
if (!v.trim()) return "Please enter your name";
|
|
290
|
+
}
|
|
291
|
+
}),
|
|
292
|
+
language: () => clack2.text({
|
|
293
|
+
message: "What language should I speak?",
|
|
294
|
+
placeholder: "\u0E44\u0E17\u0E22",
|
|
295
|
+
defaultValue: "\u0E44\u0E17\u0E22"
|
|
296
|
+
}),
|
|
297
|
+
installSkill: () => clack2.confirm({
|
|
298
|
+
message: "Include basic skills?",
|
|
299
|
+
initialValue: true
|
|
300
|
+
})
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
onCancel: () => {
|
|
304
|
+
clack2.cancel("Setup cancelled.");
|
|
305
|
+
process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
if (clack2.isCancel(group2.agentName) || clack2.isCancel(group2.userName) || clack2.isCancel(group2.language) || clack2.isCancel(group2.installSkill)) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
agentName: group2.agentName,
|
|
314
|
+
userName: group2.userName,
|
|
315
|
+
language: group2.language,
|
|
316
|
+
installSkill: group2.installSkill
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/index.ts
|
|
321
|
+
function parseArgs(args) {
|
|
322
|
+
let yes = false;
|
|
323
|
+
let dir;
|
|
324
|
+
for (const arg of args) {
|
|
325
|
+
if (arg === "--yes" || arg === "-y") yes = true;
|
|
326
|
+
else if (arg.startsWith("--dir=")) dir = arg.slice("--dir=".length);
|
|
327
|
+
else if (arg === "--dir" || arg === "-d") {
|
|
328
|
+
const idx = args.indexOf(arg);
|
|
329
|
+
const next = args[idx + 1];
|
|
330
|
+
if (next && !next.startsWith("-")) dir = next;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return { yes, dir };
|
|
334
|
+
}
|
|
335
|
+
var DEFAULT_ANSWERS = {
|
|
336
|
+
agentName: "Cloudy",
|
|
337
|
+
userName: "Developer",
|
|
338
|
+
language: "\u0E44\u0E17\u0E22",
|
|
339
|
+
installSkill: true
|
|
340
|
+
};
|
|
341
|
+
async function main() {
|
|
342
|
+
const { yes, dir } = parseArgs(process.argv.slice(2));
|
|
343
|
+
const targetDir = resolveTargetDir(dir);
|
|
344
|
+
showLogo();
|
|
345
|
+
const answers = yes ? DEFAULT_ANSWERS : await runPrompts();
|
|
346
|
+
if (!answers) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
console.log();
|
|
350
|
+
await generate(answers, targetDir);
|
|
351
|
+
console.log();
|
|
352
|
+
clack3.note(
|
|
353
|
+
[
|
|
354
|
+
"Your opencode project config is ready!",
|
|
355
|
+
"",
|
|
356
|
+
"Next steps:",
|
|
357
|
+
" 1. Review opencode.json and customize",
|
|
358
|
+
" 2. Edit SOUL.md to match your agent personality",
|
|
359
|
+
" 3. Run opencode to start chatting with your agent"
|
|
360
|
+
].join("\n"),
|
|
361
|
+
pc3.cyan("Done!")
|
|
362
|
+
);
|
|
363
|
+
console.log(pc3.dim(`
|
|
364
|
+
Generated in: ${targetDir}
|
|
365
|
+
`));
|
|
366
|
+
}
|
|
367
|
+
main().catch((err) => {
|
|
368
|
+
clack3.cancel(`Something went wrong: ${err instanceof Error ? err.message : String(err)}`);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cloudy-app/create-cloudy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI tool to scaffold opencode project config with interactive prompts",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-cloudy": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist/"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "bun run ./src/index.ts",
|
|
18
|
+
"prepublishOnly": "bun run build",
|
|
19
|
+
"test:cli": "bun run build && node ./dist/index.js --dir=.test-output",
|
|
20
|
+
"test:cli:y": "bun run build && node ./dist/index.js --yes --dir=.test-output",
|
|
21
|
+
"test:local": "bun run build && bun link",
|
|
22
|
+
"test:unlink": "bun unlink",
|
|
23
|
+
"test:pack": "bun run build && bun pm pack",
|
|
24
|
+
"lint": "biome check",
|
|
25
|
+
"lint:write": "biome check --write"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@clack/prompts": "^0.9.1",
|
|
29
|
+
"picocolors": "^1.1.1"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"opencode",
|
|
33
|
+
"cli",
|
|
34
|
+
"scaffold",
|
|
35
|
+
"ai-agent"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"tsup": "^8.5.1"
|
|
40
|
+
}
|
|
41
|
+
}
|