firth 0.0.1
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 +76 -0
- package/dist/cli.js +183 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# firth-cli
|
|
2
|
+
|
|
3
|
+
> The runtime CLI for [Firth](https://github.com/firthdev/firth) — the cloud platform SDK for AI coding agents.
|
|
4
|
+
|
|
5
|
+
**Status:** Pre-alpha. v0.0.1 ships only `firth init` (project scaffolding).
|
|
6
|
+
|
|
7
|
+
This repo is the **L2 / CLI layer** of the Firth project. The companion repo [`firth`](https://github.com/firthdev/firth) holds the L1 / Knowledge layer (Skills, templates, runbooks, ARCHITECTURE.md).
|
|
8
|
+
|
|
9
|
+
For the project's overall design and rationale, see [`firth/ARCHITECTURE.md`](https://github.com/firthdev/firth/blob/main/ARCHITECTURE.md). This README is just for the CLI itself.
|
|
10
|
+
|
|
11
|
+
## Local development
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# from this directory
|
|
15
|
+
npm install
|
|
16
|
+
|
|
17
|
+
# run the CLI in dev (no build step)
|
|
18
|
+
npm run dev -- init
|
|
19
|
+
|
|
20
|
+
# typecheck
|
|
21
|
+
npm run typecheck
|
|
22
|
+
|
|
23
|
+
# tests
|
|
24
|
+
npm test
|
|
25
|
+
|
|
26
|
+
# build a distributable
|
|
27
|
+
npm run build
|
|
28
|
+
|
|
29
|
+
# link into your shell so `firth` works globally during dev
|
|
30
|
+
npm link
|
|
31
|
+
firth init my-test-app
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Commands (current)
|
|
35
|
+
|
|
36
|
+
### `firth init [name]`
|
|
37
|
+
|
|
38
|
+
Scaffold a Firth project. Generates `firth.config.ts` and `firth.lock.json`.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# interactive
|
|
42
|
+
firth init my-app
|
|
43
|
+
|
|
44
|
+
# in current directory
|
|
45
|
+
firth init .
|
|
46
|
+
|
|
47
|
+
# non-interactive (agent-friendly): use defaults
|
|
48
|
+
firth init my-app --yes
|
|
49
|
+
|
|
50
|
+
# non-interactive with explicit overrides
|
|
51
|
+
firth init my-app --frontend=nextjs --backend=hono --db=neon \
|
|
52
|
+
--frontend-host=vercel --backend-host=railway --yes
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Defaults (when `--yes` is passed): Next.js + Hono + Neon Postgres + Vercel + Railway.
|
|
56
|
+
|
|
57
|
+
## Commands (planned)
|
|
58
|
+
|
|
59
|
+
- `firth deploy` — provision resources and push code across the stack.
|
|
60
|
+
- `firth secrets set/get/list` — sync secrets across providers.
|
|
61
|
+
- `firth logs [--service]` — tail logs.
|
|
62
|
+
- `firth status` — current deployment + resource state.
|
|
63
|
+
- `firth handoff` — generate a context dump for a fresh agent session.
|
|
64
|
+
- `firth db migrate / db reset` — database lifecycle.
|
|
65
|
+
|
|
66
|
+
## Design notes
|
|
67
|
+
|
|
68
|
+
- **Thin orchestrator, not a wrapper.** Every command shells out to the official provider CLI/API; we never re-implement provider features.
|
|
69
|
+
- **Agent-friendly errors.** Failures emit `ERROR / LIKELY CAUSE / SUGGESTED ACTIONS` so an agent loop can recover.
|
|
70
|
+
- **Local state lives in the project.** `firth.config.ts` (declarative, hand-edited) + `firth.lock.json` (generated, holds resource IDs) — both committed.
|
|
71
|
+
|
|
72
|
+
See [`firth/ARCHITECTURE.md`](https://github.com/firthdev/firth/blob/main/ARCHITECTURE.md) for the full rationale.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT (planned).
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { defineCommand as defineCommand2, runMain } from "citty";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { defineCommand } from "citty";
|
|
8
|
+
import * as p from "@clack/prompts";
|
|
9
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
10
|
+
import { existsSync } from "fs";
|
|
11
|
+
import { resolve, basename } from "path";
|
|
12
|
+
var initCommand = defineCommand({
|
|
13
|
+
meta: {
|
|
14
|
+
name: "init",
|
|
15
|
+
description: "Scaffold Firth project files (firth.config.ts + firth.lock.json) in the target directory."
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
name: {
|
|
19
|
+
type: "positional",
|
|
20
|
+
description: "Project directory (use '.' for current directory)",
|
|
21
|
+
required: false,
|
|
22
|
+
default: "."
|
|
23
|
+
},
|
|
24
|
+
yes: {
|
|
25
|
+
type: "boolean",
|
|
26
|
+
alias: "y",
|
|
27
|
+
description: "Skip all prompts; use defaults (Next.js + Hono + Neon + Vercel + Railway). Safe for non-interactive agent runs.",
|
|
28
|
+
default: false
|
|
29
|
+
},
|
|
30
|
+
frontend: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Frontend framework override (nextjs)"
|
|
33
|
+
},
|
|
34
|
+
backend: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Backend framework override (hono | express | none)"
|
|
37
|
+
},
|
|
38
|
+
db: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Database provider override (neon | none)"
|
|
41
|
+
},
|
|
42
|
+
"frontend-host": {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Frontend host override (vercel | none)"
|
|
45
|
+
},
|
|
46
|
+
"backend-host": {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Backend host override (railway | none)"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
async run({ args }) {
|
|
52
|
+
const rawName = String(args.name);
|
|
53
|
+
const targetDir = resolve(process.cwd(), rawName);
|
|
54
|
+
const projectName = rawName === "." ? basename(targetDir) : basename(targetDir);
|
|
55
|
+
p.intro("firth init");
|
|
56
|
+
const configPath = resolve(targetDir, "firth.config.ts");
|
|
57
|
+
if (existsSync(configPath)) {
|
|
58
|
+
p.cancel(
|
|
59
|
+
[
|
|
60
|
+
"ERROR: firth.config.ts already exists.",
|
|
61
|
+
`LOCATION: ${configPath}`,
|
|
62
|
+
"SUGGESTED ACTIONS:",
|
|
63
|
+
" 1. Edit firth.config.ts by hand to change the stack.",
|
|
64
|
+
" 2. Or delete firth.config.ts and re-run `firth init`."
|
|
65
|
+
].join("\n")
|
|
66
|
+
);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const stack = args.yes ? defaultStack() : await promptStack(args);
|
|
70
|
+
if (!stack) {
|
|
71
|
+
p.cancel("Cancelled.");
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
const config = { project: projectName, stack };
|
|
75
|
+
const lock = { version: 1, resources: {} };
|
|
76
|
+
if (!existsSync(targetDir)) {
|
|
77
|
+
await mkdir(targetDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
await writeFile(configPath, renderConfig(config), "utf8");
|
|
80
|
+
await writeFile(
|
|
81
|
+
resolve(targetDir, "firth.lock.json"),
|
|
82
|
+
JSON.stringify(lock, null, 2) + "\n",
|
|
83
|
+
"utf8"
|
|
84
|
+
);
|
|
85
|
+
p.outro(
|
|
86
|
+
[
|
|
87
|
+
`OK: wrote firth.config.ts and firth.lock.json to ${targetDir}`,
|
|
88
|
+
"",
|
|
89
|
+
"NEXT STEPS:",
|
|
90
|
+
" 1. Review firth.config.ts and adjust the stack if needed.",
|
|
91
|
+
" 2. Run `firth deploy` to provision and ship (coming soon)."
|
|
92
|
+
].join("\n")
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
function defaultStack() {
|
|
97
|
+
return {
|
|
98
|
+
frontend: "nextjs",
|
|
99
|
+
backend: "hono",
|
|
100
|
+
db: "neon",
|
|
101
|
+
frontendHost: "vercel",
|
|
102
|
+
backendHost: "railway"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function promptStack(args) {
|
|
106
|
+
const frontend = args.frontend ?? await p.select({
|
|
107
|
+
message: "Frontend framework?",
|
|
108
|
+
options: [{ value: "nextjs", label: "Next.js" }],
|
|
109
|
+
initialValue: "nextjs"
|
|
110
|
+
});
|
|
111
|
+
if (p.isCancel(frontend)) return null;
|
|
112
|
+
const backend = args.backend ?? await p.select({
|
|
113
|
+
message: "Backend framework?",
|
|
114
|
+
options: [
|
|
115
|
+
{ value: "hono", label: "Hono (recommended)" },
|
|
116
|
+
{ value: "express", label: "Express" },
|
|
117
|
+
{ value: "none", label: "None (frontend-only)" }
|
|
118
|
+
],
|
|
119
|
+
initialValue: "hono"
|
|
120
|
+
});
|
|
121
|
+
if (p.isCancel(backend)) return null;
|
|
122
|
+
const db = args.db ?? await p.select({
|
|
123
|
+
message: "Database?",
|
|
124
|
+
options: [
|
|
125
|
+
{ value: "neon", label: "Neon Postgres" },
|
|
126
|
+
{ value: "none", label: "None" }
|
|
127
|
+
],
|
|
128
|
+
initialValue: "neon"
|
|
129
|
+
});
|
|
130
|
+
if (p.isCancel(db)) return null;
|
|
131
|
+
const frontendHost = args["frontend-host"] ?? await p.select({
|
|
132
|
+
message: "Frontend hosting?",
|
|
133
|
+
options: [
|
|
134
|
+
{ value: "vercel", label: "Vercel" },
|
|
135
|
+
{ value: "none", label: "Not yet" }
|
|
136
|
+
],
|
|
137
|
+
initialValue: "vercel"
|
|
138
|
+
});
|
|
139
|
+
if (p.isCancel(frontendHost)) return null;
|
|
140
|
+
let backendHost;
|
|
141
|
+
if (backend === "none") {
|
|
142
|
+
backendHost = "none";
|
|
143
|
+
} else {
|
|
144
|
+
const result = args["backend-host"] ?? await p.select({
|
|
145
|
+
message: "Backend hosting?",
|
|
146
|
+
options: [
|
|
147
|
+
{ value: "railway", label: "Railway" },
|
|
148
|
+
{ value: "none", label: "Not yet" }
|
|
149
|
+
],
|
|
150
|
+
initialValue: "railway"
|
|
151
|
+
});
|
|
152
|
+
if (p.isCancel(result)) return null;
|
|
153
|
+
backendHost = String(result);
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
frontend: String(frontend),
|
|
157
|
+
backend: String(backend),
|
|
158
|
+
db: String(db),
|
|
159
|
+
frontendHost: String(frontendHost),
|
|
160
|
+
backendHost
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function renderConfig(config) {
|
|
164
|
+
return `// firth.config.ts
|
|
165
|
+
// Generated by \`firth init\`. Source-of-truth for this project's stack.
|
|
166
|
+
// Hand-edit, then run \`firth deploy\` to apply changes.
|
|
167
|
+
|
|
168
|
+
export default ${JSON.stringify(config, null, 2)};
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/cli.ts
|
|
173
|
+
var main = defineCommand2({
|
|
174
|
+
meta: {
|
|
175
|
+
name: "firth",
|
|
176
|
+
version: "0.0.1",
|
|
177
|
+
description: "Cloud platform SDK for AI coding agents."
|
|
178
|
+
},
|
|
179
|
+
subCommands: {
|
|
180
|
+
init: initCommand
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
runMain(main);
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "firth",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Cloud platform SDK for AI coding agents — scaffold, deploy, and operate cloud stacks alongside your AI coding agent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"firth": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "tsx src/cli.ts",
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"cli",
|
|
22
|
+
"ai-agent",
|
|
23
|
+
"deploy",
|
|
24
|
+
"scaffold",
|
|
25
|
+
"vercel",
|
|
26
|
+
"railway",
|
|
27
|
+
"neon",
|
|
28
|
+
"firth"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@clack/prompts": "^0.7.0",
|
|
36
|
+
"citty": "^0.1.6"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.11.0",
|
|
40
|
+
"tsup": "^8.0.2",
|
|
41
|
+
"tsx": "^4.7.1",
|
|
42
|
+
"typescript": "^5.4.0",
|
|
43
|
+
"vitest": "^1.4.0"
|
|
44
|
+
}
|
|
45
|
+
}
|