Skip to content

Commit 0445e2b

Browse files
committed
Initial commit
0 parents  commit 0445e2b

17 files changed

Lines changed: 1365 additions & 0 deletions

File tree

.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build output
5+
dist/
6+
7+
# Bun
8+
bun.lockb
9+
*.bun-build
10+
11+
# Logs
12+
*.log
13+
14+
# OS
15+
.DS_Store
16+
Thumbs.db
17+
18+
# Editor
19+
.idea/
20+
.vscode/
21+
*.swp
22+
*.swo
23+
24+
# Environment
25+
.env
26+
.env.local

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Handcraft Byte
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

bin/temper.ts

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#!/usr/bin/env bun
2+
3+
import { parseArgs } from "util";
4+
import { run } from "../src/commands/run";
5+
import { search, list } from "../src/commands/search";
6+
import { info } from "../src/commands/info";
7+
import { edit } from "../src/commands/edit";
8+
import { open } from "../src/commands/open";
9+
import { SnippetCache } from "../src/lib/cache";
10+
11+
const VERSION = "0.1.0";
12+
13+
const HELP = `
14+
temper - Homebrew for code snippets
15+
16+
USAGE
17+
temper <command> [options]
18+
19+
COMMANDS
20+
run <slug> Execute a JavaScript snippet
21+
search <query> Search for snippets
22+
list List all available snippets
23+
info <slug> Show detailed snippet information
24+
edit <slug> Download snippet and open in editor
25+
open <slug> Open snippet in browser
26+
cache Manage local cache
27+
28+
OPTIONS
29+
-l, --language <lang> Language for info/edit/open (default: javascript)
30+
-h, --help Show help
31+
-v, --version Show version
32+
33+
EXAMPLES
34+
temper run generate-uuid
35+
temper run fibonacci --n=10
36+
echo "hello world" | temper run title-case
37+
temper search "sort array"
38+
temper info quick-sort
39+
temper open generate-uuid
40+
temper edit quick-sort -l python
41+
42+
ENVIRONMENT
43+
EDITOR Editor for 'temper edit' (default: vim)
44+
TEMPER_API_URL API base URL (default: https://tempercode.dev)
45+
TEMPER_CACHE_DIR Cache directory (default: ~/.temper/cache)
46+
47+
Learn more: https://tempercode.dev
48+
`;
49+
50+
async function readStdin(): Promise<string | undefined> {
51+
// Check if stdin is a TTY (interactive terminal)
52+
// If it's not a TTY, data is being piped in
53+
if (process.stdin.isTTY) {
54+
return undefined;
55+
}
56+
57+
// Use Bun's native file API for stdin
58+
const text = await Bun.stdin.text();
59+
return text.trim() || undefined;
60+
}
61+
62+
function parseParams(args: string[]): Record<string, string> {
63+
const params: Record<string, string> = {};
64+
65+
for (const arg of args) {
66+
if (arg.startsWith("--") && arg.includes("=")) {
67+
const [key, ...valueParts] = arg.slice(2).split("=");
68+
params[key] = valueParts.join("=");
69+
}
70+
}
71+
72+
return params;
73+
}
74+
75+
async function main() {
76+
const args = process.argv.slice(2);
77+
78+
// Handle empty args
79+
if (args.length === 0) {
80+
console.log(HELP);
81+
process.exit(0);
82+
}
83+
84+
// Parse args
85+
const { values, positionals } = parseArgs({
86+
args,
87+
options: {
88+
language: { type: "string", short: "l" },
89+
help: { type: "boolean", short: "h" },
90+
version: { type: "boolean", short: "v" },
91+
editor: { type: "string", short: "e" },
92+
type: { type: "string", short: "t" },
93+
limit: { type: "string" },
94+
clear: { type: "boolean" },
95+
},
96+
allowPositionals: true,
97+
strict: false, // Allow unknown options (for snippet params like --n=10)
98+
});
99+
100+
// Handle global flags
101+
if (values.help) {
102+
console.log(HELP);
103+
process.exit(0);
104+
}
105+
106+
if (values.version) {
107+
console.log(`temper ${VERSION}`);
108+
process.exit(0);
109+
}
110+
111+
const [command, ...rest] = positionals;
112+
const slug = rest[0];
113+
114+
// Extract custom params (--key=value) from remaining args
115+
const customParams = parseParams(args);
116+
117+
switch (command) {
118+
case "run": {
119+
if (!slug) {
120+
console.error("Usage: temper run <slug> [--param=value...]");
121+
console.error("Example: temper run fibonacci --n=10");
122+
process.exit(1);
123+
}
124+
125+
// Read stdin if available
126+
const stdin = await readStdin();
127+
128+
await run(slug, {
129+
params: customParams,
130+
stdin,
131+
});
132+
break;
133+
}
134+
135+
case "search": {
136+
const query = rest.join(" ");
137+
if (!query) {
138+
console.error("Usage: temper search <query>");
139+
console.error("Example: temper search 'sort array'");
140+
process.exit(1);
141+
}
142+
143+
await search(query, {
144+
language: values.language,
145+
type: values.type,
146+
limit: values.limit ? parseInt(values.limit, 10) : undefined,
147+
});
148+
break;
149+
}
150+
151+
case "list": {
152+
await list({
153+
language: values.language,
154+
type: values.type,
155+
limit: values.limit ? parseInt(values.limit, 10) : undefined,
156+
});
157+
break;
158+
}
159+
160+
case "info": {
161+
if (!slug) {
162+
console.error("Usage: temper info <slug>");
163+
console.error("Example: temper info fibonacci");
164+
process.exit(1);
165+
}
166+
167+
await info(slug, {
168+
language: values.language,
169+
});
170+
break;
171+
}
172+
173+
case "edit": {
174+
if (!slug) {
175+
console.error("Usage: temper edit <slug> [-l language]");
176+
console.error("Example: temper edit sort-array -l ruby");
177+
process.exit(1);
178+
}
179+
180+
await edit(slug, {
181+
language: values.language,
182+
editor: values.editor,
183+
});
184+
break;
185+
}
186+
187+
case "open": {
188+
if (!slug) {
189+
console.error("Usage: temper open <slug> [-l language]");
190+
console.error("Example: temper open generate-uuid");
191+
process.exit(1);
192+
}
193+
194+
await open(slug, { language: values.language });
195+
break;
196+
}
197+
198+
case "cache": {
199+
const cache = new SnippetCache();
200+
const subcommand = rest[0];
201+
202+
if (subcommand === "clear" || values.clear) {
203+
await cache.clear();
204+
console.log("Cache cleared.");
205+
} else if (subcommand === "list") {
206+
const cached = await cache.listCached();
207+
if (cached.length === 0) {
208+
console.log("Cache is empty.");
209+
} else {
210+
console.log(`Cached snippets (${cached.length}):`);
211+
cached.forEach(s => console.log(` ${s}`));
212+
}
213+
} else {
214+
console.log("Usage: temper cache <list|clear>");
215+
}
216+
break;
217+
}
218+
219+
default:
220+
console.error(`Unknown command: ${command}`);
221+
console.log(HELP);
222+
process.exit(1);
223+
}
224+
}
225+
226+
main().catch((err) => {
227+
console.error(`Error: ${err.message}`);
228+
process.exit(1);
229+
});

bun.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)