@cat-factory/cli 0.2.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/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/args.d.ts +40 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +153 -0
- package/dist/args.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +42 -0
- package/dist/bin.js.map +1 -0
- package/dist/bootstrap.d.ts +23 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +171 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/env.d.ts +38 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +150 -0
- package/dist/env.js.map +1 -0
- package/dist/gitignore.d.ts +15 -0
- package/dist/gitignore.d.ts.map +1 -0
- package/dist/gitignore.js +46 -0
- package/dist/gitignore.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +25 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +74 -0
- package/dist/io.js.map +1 -0
- package/dist/plan.d.ts +29 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +144 -0
- package/dist/plan.js.map +1 -0
- package/dist/secrets.d.ts +25 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +9 -0
- package/dist/secrets.js.map +1 -0
- package/dist/slug.d.ts +3 -0
- package/dist/slug.d.ts.map +1 -0
- package/dist/slug.js +19 -0
- package/dist/slug.js.map +1 -0
- package/dist/templates.d.ts +29 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +189 -0
- package/dist/templates.js.map +1 -0
- package/dist/vcs.d.ts +20 -0
- package/dist/vcs.d.ts.map +1 -0
- package/dist/vcs.js +52 -0
- package/dist/vcs.js.map +1 -0
- package/package.json +51 -0
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ContainerRuntime } from './templates.js';
|
|
2
|
+
import { type VcsProvider } from './vcs.js';
|
|
3
|
+
/** One line of an `.env` file: a key/value pair with an optional leading comment block. */
|
|
4
|
+
export interface EnvEntry {
|
|
5
|
+
key: string;
|
|
6
|
+
value: string;
|
|
7
|
+
/** Comment lines rendered (each prefixed with `# `) immediately above the entry. */
|
|
8
|
+
comment?: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Render a list of {@link EnvEntry} into `.env` file text. Comments are emitted above their
|
|
12
|
+
* entry; entries are separated by a blank line for readability. The output always ends with a
|
|
13
|
+
* trailing newline.
|
|
14
|
+
*/
|
|
15
|
+
export declare function renderEnvFile(entries: EnvEntry[]): string;
|
|
16
|
+
/** Inputs that flow into the local-backend `.env`. */
|
|
17
|
+
export interface LocalEnvInput {
|
|
18
|
+
databaseUrl: string;
|
|
19
|
+
authSessionSecret: string;
|
|
20
|
+
encryptionKey: string;
|
|
21
|
+
harnessImage: string;
|
|
22
|
+
port: number;
|
|
23
|
+
corsAllowedOrigins: string;
|
|
24
|
+
provider: VcsProvider;
|
|
25
|
+
/** Container runtime that spawns agent jobs (`LOCAL_CONTAINER_RUNTIME`). */
|
|
26
|
+
containerRuntime: ContainerRuntime;
|
|
27
|
+
/** The pasted PAT, or empty/undefined to leave the var present-but-blank. */
|
|
28
|
+
token?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Build the local-mode backend `.env` (`@cat-factory/local-server`). */
|
|
31
|
+
export declare function buildLocalEnv(input: LocalEnvInput): string;
|
|
32
|
+
/** Inputs that flow into the frontend `.env`. */
|
|
33
|
+
export interface FrontendEnvInput {
|
|
34
|
+
apiBase: string;
|
|
35
|
+
}
|
|
36
|
+
/** Build the frontend SPA `.env` (`@cat-factory/app` consumer). */
|
|
37
|
+
export declare function buildFrontendEnv(input: FrontendEnvInput): string;
|
|
38
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,EAA4B,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AAErE,2FAA2F;AAC3F,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAMzD;AAED,sDAAsD;AACtD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,kBAAkB,EAAE,MAAM,CAAA;IAC1B,QAAQ,EAAE,WAAW,CAAA;IACrB,4EAA4E;IAC5E,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CA8E1D;AA6CD,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAWhE"}
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { CONTAINER_RUNTIMES } from './templates.js';
|
|
2
|
+
import { patEnvVar, providerLabel } from './vcs.js';
|
|
3
|
+
/**
|
|
4
|
+
* Render a list of {@link EnvEntry} into `.env` file text. Comments are emitted above their
|
|
5
|
+
* entry; entries are separated by a blank line for readability. The output always ends with a
|
|
6
|
+
* trailing newline.
|
|
7
|
+
*/
|
|
8
|
+
export function renderEnvFile(entries) {
|
|
9
|
+
const blocks = entries.map((entry) => {
|
|
10
|
+
const comment = (entry.comment ?? []).map((line) => `# ${line}`);
|
|
11
|
+
return [...comment, `${entry.key}=${entry.value}`].join('\n');
|
|
12
|
+
});
|
|
13
|
+
return `${blocks.join('\n\n')}\n`;
|
|
14
|
+
}
|
|
15
|
+
/** Build the local-mode backend `.env` (`@cat-factory/local-server`). */
|
|
16
|
+
export function buildLocalEnv(input) {
|
|
17
|
+
const tokenVar = patEnvVar(input.provider);
|
|
18
|
+
const label = providerLabel(input.provider);
|
|
19
|
+
const entries = [
|
|
20
|
+
{
|
|
21
|
+
comment: [
|
|
22
|
+
'Local-mode backend (@cat-factory/local-server). Generated by `cat-factory init`.',
|
|
23
|
+
'This file is gitignored — it holds secrets. Keep the generated values STABLE.',
|
|
24
|
+
'Postgres connection string (matches the bundled docker-compose.yml).',
|
|
25
|
+
],
|
|
26
|
+
key: 'DATABASE_URL',
|
|
27
|
+
value: input.databaseUrl,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
comment: ['HTTP listen port for the backend API.'],
|
|
31
|
+
key: 'PORT',
|
|
32
|
+
value: String(input.port),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
comment: ["Allowed CORS origin(s) — the frontend SPA's dev origin."],
|
|
36
|
+
key: 'CORS_ALLOWED_ORIGINS',
|
|
37
|
+
value: input.corsAllowedOrigins,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
comment: [
|
|
41
|
+
'Signs the session JWT (>= 32 chars). Regenerating forces a re-login — keep it stable.',
|
|
42
|
+
],
|
|
43
|
+
key: 'AUTH_SESSION_SECRET',
|
|
44
|
+
value: input.authSessionSecret,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
comment: [
|
|
48
|
+
'Master key sealing every credential at rest (base64, >= 32 decoded bytes).',
|
|
49
|
+
'Regenerating ORPHANS every encrypted credential — keep it stable.',
|
|
50
|
+
],
|
|
51
|
+
key: 'ENCRYPTION_KEY',
|
|
52
|
+
value: input.encryptionKey,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
comment: [
|
|
56
|
+
`${label} personal access token. Agent containers clone/push/open PRs with it.`,
|
|
57
|
+
'Minted by the CLI with the right scopes pre-selected; paste-replace to rotate.',
|
|
58
|
+
],
|
|
59
|
+
key: tokenVar,
|
|
60
|
+
value: input.token ?? '',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
comment: [
|
|
64
|
+
'The executor-harness image agent jobs run as per-run containers.',
|
|
65
|
+
'Pull it first: docker pull ghcr.io/kibertoad/cat-factory-executor:latest',
|
|
66
|
+
],
|
|
67
|
+
key: 'LOCAL_HARNESS_IMAGE',
|
|
68
|
+
value: input.harnessImage,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
comment: [`Container runtime used to spawn agent jobs: ${CONTAINER_RUNTIMES.join(' | ')}.`],
|
|
72
|
+
key: 'LOCAL_CONTAINER_RUNTIME',
|
|
73
|
+
value: input.containerRuntime,
|
|
74
|
+
},
|
|
75
|
+
...reachabilityEntries(input.containerRuntime),
|
|
76
|
+
{
|
|
77
|
+
comment: [
|
|
78
|
+
'At least one model provider must be configured or no model is selectable.',
|
|
79
|
+
'Cloudflare Workers AI over REST is the simplest: set both, then uncomment.',
|
|
80
|
+
'See the README for minting a token + finding your account id.',
|
|
81
|
+
],
|
|
82
|
+
key: '# CLOUDFLARE_ACCOUNT_ID',
|
|
83
|
+
value: '',
|
|
84
|
+
},
|
|
85
|
+
{ key: '# CLOUDFLARE_API_TOKEN', value: '' },
|
|
86
|
+
{
|
|
87
|
+
comment: ['Or a direct vendor key (combinable with the above):'],
|
|
88
|
+
key: '# ANTHROPIC_API_KEY',
|
|
89
|
+
value: '',
|
|
90
|
+
},
|
|
91
|
+
{ key: '# OPENAI_API_KEY', value: '' },
|
|
92
|
+
];
|
|
93
|
+
return renderEnvFile(entries);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Container→host reachability + security hints, commented out (sensible defaults apply). Agent
|
|
97
|
+
* job containers reach this service's LLM proxy over the network, and how they address the host
|
|
98
|
+
* differs per runtime — surfacing the knobs here (rather than only in the upstream docs) means a
|
|
99
|
+
* developer who hits "proxy unreachable from the container" has the fix in the file they already
|
|
100
|
+
* have open. Mirrors the relevant slice of `deploy/local/.env.example`.
|
|
101
|
+
*/
|
|
102
|
+
function reachabilityEntries(runtime) {
|
|
103
|
+
const entries = [
|
|
104
|
+
{
|
|
105
|
+
comment: [
|
|
106
|
+
'Container -> host reachability (defaults apply; uncomment only if the agent container',
|
|
107
|
+
"can't reach this service's LLM proxy). The harness reaches the host at a per-runtime",
|
|
108
|
+
'alias: host.docker.internal (docker/podman/orbstack), host.lima.internal (colima), the',
|
|
109
|
+
'vmnet gateway (apple). On Colima/Apple this is often unreachable — set it to your LAN IP.',
|
|
110
|
+
],
|
|
111
|
+
key: '# LOCAL_HARNESS_HOST_ALIAS',
|
|
112
|
+
value: '',
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
if (runtime === 'docker' || runtime === 'podman') {
|
|
116
|
+
entries.push({
|
|
117
|
+
comment: [
|
|
118
|
+
'Native Linux Docker only: add --add-host=<alias>:host-gateway so the container can',
|
|
119
|
+
'resolve the host (needed on Linux; harmless on Docker Desktop/OrbStack).',
|
|
120
|
+
],
|
|
121
|
+
key: '# LOCAL_DOCKER_ADD_HOST_GATEWAY',
|
|
122
|
+
value: 'true',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
entries.push({
|
|
126
|
+
comment: [
|
|
127
|
+
'SECURITY: with the dev gate open the server binds all interfaces so job containers can',
|
|
128
|
+
'reach the proxy. To lock it down, bind to loopback (works on Docker Desktop; NOT native',
|
|
129
|
+
'Linux Docker, where containers need the bridge gateway) or close the gate.',
|
|
130
|
+
'HOST=127.0.0.1',
|
|
131
|
+
],
|
|
132
|
+
key: '# AUTH_DEV_OPEN',
|
|
133
|
+
value: 'false',
|
|
134
|
+
});
|
|
135
|
+
return entries;
|
|
136
|
+
}
|
|
137
|
+
/** Build the frontend SPA `.env` (`@cat-factory/app` consumer). */
|
|
138
|
+
export function buildFrontendEnv(input) {
|
|
139
|
+
return renderEnvFile([
|
|
140
|
+
{
|
|
141
|
+
comment: [
|
|
142
|
+
'Frontend SPA (extends @cat-factory/app). Generated by `cat-factory init`.',
|
|
143
|
+
'Base URL of the backend API. Baked in at build time (the SPA is ssr: false).',
|
|
144
|
+
],
|
|
145
|
+
key: 'NUXT_PUBLIC_API_BASE',
|
|
146
|
+
value: input.apiBase,
|
|
147
|
+
},
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=env.js.map
|
package/dist/env.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The ignore rules every scaffolded project needs. `.env` secrets first — that's the point. */
|
|
2
|
+
export declare const REQUIRED_GITIGNORE_RULES: readonly string[];
|
|
3
|
+
/**
|
|
4
|
+
* Produce the `.gitignore` content for a freshly scaffolded project. A leading header documents
|
|
5
|
+
* the source; rules follow one per line.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildGitignore(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Merge the {@link REQUIRED_GITIGNORE_RULES} into an EXISTING `.gitignore`, appending only the
|
|
10
|
+
* rules that aren't already present (compared after trimming, ignoring blank/comment lines). The
|
|
11
|
+
* existing content is preserved verbatim; a documented block of the missing rules is appended.
|
|
12
|
+
* Returns the original unchanged when nothing is missing.
|
|
13
|
+
*/
|
|
14
|
+
export declare function mergeGitignore(existing: string): string;
|
|
15
|
+
//# sourceMappingURL=gitignore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../src/gitignore.ts"],"names":[],"mappings":"AAKA,gGAAgG;AAChG,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAcrD,CAAA;AAID;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAYvD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// `.gitignore` management for the scaffolded project. The whole point of writing secrets into
|
|
2
|
+
// `.env` is that they must NEVER be committed, so the CLI guarantees the ignore rules are in
|
|
3
|
+
// place — creating a `.gitignore` when there is none, or merging the required rules into an
|
|
4
|
+
// existing one (e.g. when scaffolding into a directory that is already a git repo).
|
|
5
|
+
/** The ignore rules every scaffolded project needs. `.env` secrets first — that's the point. */
|
|
6
|
+
export const REQUIRED_GITIGNORE_RULES = [
|
|
7
|
+
// Secrets — the reason this file exists.
|
|
8
|
+
'.env',
|
|
9
|
+
'.env.*',
|
|
10
|
+
'!.env.example',
|
|
11
|
+
// Dependencies + build output.
|
|
12
|
+
'node_modules/',
|
|
13
|
+
'dist/',
|
|
14
|
+
'*.tsbuildinfo',
|
|
15
|
+
// Nuxt build / generate output.
|
|
16
|
+
'.nuxt',
|
|
17
|
+
'.output',
|
|
18
|
+
// Local Postgres data / captured artifacts.
|
|
19
|
+
'.file-storage/',
|
|
20
|
+
];
|
|
21
|
+
const HEADER = '# cat-factory (added by `cat-factory init`)';
|
|
22
|
+
/**
|
|
23
|
+
* Produce the `.gitignore` content for a freshly scaffolded project. A leading header documents
|
|
24
|
+
* the source; rules follow one per line.
|
|
25
|
+
*/
|
|
26
|
+
export function buildGitignore() {
|
|
27
|
+
return `${HEADER}\n${REQUIRED_GITIGNORE_RULES.join('\n')}\n`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Merge the {@link REQUIRED_GITIGNORE_RULES} into an EXISTING `.gitignore`, appending only the
|
|
31
|
+
* rules that aren't already present (compared after trimming, ignoring blank/comment lines). The
|
|
32
|
+
* existing content is preserved verbatim; a documented block of the missing rules is appended.
|
|
33
|
+
* Returns the original unchanged when nothing is missing.
|
|
34
|
+
*/
|
|
35
|
+
export function mergeGitignore(existing) {
|
|
36
|
+
const present = new Set(existing
|
|
37
|
+
.split('\n')
|
|
38
|
+
.map((line) => line.trim())
|
|
39
|
+
.filter((line) => line.length > 0 && !line.startsWith('#')));
|
|
40
|
+
const missing = REQUIRED_GITIGNORE_RULES.filter((rule) => !present.has(rule));
|
|
41
|
+
if (missing.length === 0)
|
|
42
|
+
return existing;
|
|
43
|
+
const prefix = existing.endsWith('\n') || existing.length === 0 ? existing : `${existing}\n`;
|
|
44
|
+
return `${prefix}\n${HEADER}\n${missing.join('\n')}\n`;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=gitignore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../src/gitignore.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,6FAA6F;AAC7F,4FAA4F;AAC5F,oFAAoF;AAEpF,gGAAgG;AAChG,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,yCAAyC;IACzC,MAAM;IACN,QAAQ;IACR,eAAe;IACf,+BAA+B;IAC/B,eAAe;IACf,OAAO;IACP,eAAe;IACf,gCAAgC;IAChC,OAAO;IACP,SAAS;IACT,4CAA4C;IAC5C,gBAAgB;CACjB,CAAA;AAED,MAAM,MAAM,GAAG,6CAA6C,CAAA;AAE5D;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,GAAG,MAAM,KAAK,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,QAAQ;SACL,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAC9D,CAAA;IACD,MAAM,OAAO,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;IAC7E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAA;IAEzC,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,CAAA;IAC5F,OAAO,GAAG,MAAM,KAAK,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA;AACxD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { type CliOptions, HELP_TEXT, OPTION_DEFAULTS, parseArgs, ArgError } from './args.js';
|
|
2
|
+
export { bootstrap, type BootstrapDeps, type FileSystem, BootstrapError } from './bootstrap.js';
|
|
3
|
+
export { buildFrontendEnv, buildLocalEnv, type EnvEntry, type FrontendEnvInput, type LocalEnvInput, renderEnvFile, } from './env.js';
|
|
4
|
+
export { buildGitignore, mergeGitignore, REQUIRED_GITIGNORE_RULES } from './gitignore.js';
|
|
5
|
+
export { type Io, createConsoleIo } from './io.js';
|
|
6
|
+
export { type BootstrapInput, buildPlan, type PlannedFile } from './plan.js';
|
|
7
|
+
export { generateSecrets, type GeneratedSecrets, type RandomBytes } from './secrets.js';
|
|
8
|
+
export { slugifyProjectName } from './slug.js';
|
|
9
|
+
export { CONTAINER_RUNTIMES, type ContainerRuntime, DEFAULT_HARNESS_IMAGE } from './templates.js';
|
|
10
|
+
export { githubPatCreationUrl, gitlabPatCreationUrl, patCreationUrl, patEnvVar, providerLabel, VCS_PROVIDERS, type VcsProvider, } from './vcs.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC5F,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/F,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,aAAa,GACd,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzF,OAAO,EAAE,KAAK,EAAE,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,KAAK,cAAc,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAC5E,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACjG,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,aAAa,EACb,aAAa,EACb,KAAK,WAAW,GACjB,MAAM,UAAU,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Public, programmatic API of @cat-factory/cli. The `cat-factory` command (bin.ts) is a thin
|
|
2
|
+
// shell over these; they are also exported so the bootstrap can be embedded in other tooling.
|
|
3
|
+
export { HELP_TEXT, OPTION_DEFAULTS, parseArgs, ArgError } from './args.js';
|
|
4
|
+
export { bootstrap, BootstrapError } from './bootstrap.js';
|
|
5
|
+
export { buildFrontendEnv, buildLocalEnv, renderEnvFile, } from './env.js';
|
|
6
|
+
export { buildGitignore, mergeGitignore, REQUIRED_GITIGNORE_RULES } from './gitignore.js';
|
|
7
|
+
export { createConsoleIo } from './io.js';
|
|
8
|
+
export { buildPlan } from './plan.js';
|
|
9
|
+
export { generateSecrets } from './secrets.js';
|
|
10
|
+
export { slugifyProjectName } from './slug.js';
|
|
11
|
+
export { CONTAINER_RUNTIMES, DEFAULT_HARNESS_IMAGE } from './templates.js';
|
|
12
|
+
export { githubPatCreationUrl, gitlabPatCreationUrl, patCreationUrl, patEnvVar, providerLabel, VCS_PROVIDERS, } from './vcs.js';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,8FAA8F;AAC9F,OAAO,EAAmB,SAAS,EAAE,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC5F,OAAO,EAAE,SAAS,EAAuC,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/F,OAAO,EACL,gBAAgB,EAChB,aAAa,EAIb,aAAa,GACd,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzF,OAAO,EAAW,eAAe,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAuB,SAAS,EAAoB,MAAM,WAAW,CAAA;AAC5E,OAAO,EAAE,eAAe,EAA2C,MAAM,cAAc,CAAA;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,kBAAkB,EAAyB,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACjG,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,aAAa,EACb,aAAa,GAEd,MAAM,UAAU,CAAA"}
|
package/dist/io.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal I/O seam. The orchestrator depends on this interface (never on `@clack/prompts` or
|
|
3
|
+
* `process` directly), so the whole interactive flow can be driven by a fake in tests. The real
|
|
4
|
+
* implementation ({@link createConsoleIo}) is clack-backed; tests inject their own.
|
|
5
|
+
*/
|
|
6
|
+
export interface Io {
|
|
7
|
+
info(message: string): void;
|
|
8
|
+
warn(message: string): void;
|
|
9
|
+
/** Free-text prompt with an optional default (used when the reply is empty). */
|
|
10
|
+
question(prompt: string, defaultValue?: string): Promise<string>;
|
|
11
|
+
/** A single-choice menu; returns the chosen option's value. */
|
|
12
|
+
select<T extends string>(prompt: string, options: readonly {
|
|
13
|
+
value: T;
|
|
14
|
+
label: string;
|
|
15
|
+
}[], defaultValue: T): Promise<T>;
|
|
16
|
+
/** Like {@link question} but does not echo the typed characters (for secrets/tokens). */
|
|
17
|
+
secret(prompt: string): Promise<string>;
|
|
18
|
+
/** Yes/no prompt. */
|
|
19
|
+
confirm(prompt: string, defaultValue: boolean): Promise<boolean>;
|
|
20
|
+
/** Open a URL in the user's default browser (best-effort; resolves even if it can't). */
|
|
21
|
+
openBrowser(url: string): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/** The real, console-backed {@link Io}, implemented with `@clack/prompts`. */
|
|
24
|
+
export declare function createConsoleIo(): Io;
|
|
25
|
+
//# sourceMappingURL=io.d.ts.map
|
package/dist/io.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.d.ts","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,MAAM,WAAW,EAAE;IACjB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChE,+DAA+D;IAC/D,MAAM,CAAC,CAAC,SAAS,MAAM,EACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS;QAAE,KAAK,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,EAC/C,YAAY,EAAE,CAAC,GACd,OAAO,CAAC,CAAC,CAAC,CAAA;IACb,yFAAyF;IACzF,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvC,qBAAqB;IACrB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAChE,yFAAyF;IACzF,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxC;AAwBD,8EAA8E;AAC9E,wBAAgB,eAAe,IAAI,EAAE,CAwDpC"}
|
package/dist/io.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { cancel, confirm as clackConfirm, isCancel, log, password, select as clackSelect, text, } from '@clack/prompts';
|
|
3
|
+
/** The OS-appropriate command for opening a URL in the default browser. */
|
|
4
|
+
function openCommand(url) {
|
|
5
|
+
switch (process.platform) {
|
|
6
|
+
case 'darwin':
|
|
7
|
+
return { cmd: 'open', args: [url] };
|
|
8
|
+
case 'win32':
|
|
9
|
+
// `start` is a cmd builtin; the empty "" is the window title arg it requires.
|
|
10
|
+
return { cmd: 'cmd', args: ['/c', 'start', '', url] };
|
|
11
|
+
default:
|
|
12
|
+
return { cmd: 'xdg-open', args: [url] };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** A clack prompt resolved to a cancel symbol (Ctrl-C / Esc): print a notice and exit cleanly. */
|
|
16
|
+
function bailIfCancelled(value) {
|
|
17
|
+
if (isCancel(value)) {
|
|
18
|
+
cancel('Cancelled.');
|
|
19
|
+
process.exit(130);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
/** The real, console-backed {@link Io}, implemented with `@clack/prompts`. */
|
|
24
|
+
export function createConsoleIo() {
|
|
25
|
+
return {
|
|
26
|
+
info(message) {
|
|
27
|
+
// Callers pad messages with leading/trailing newlines for plain-console spacing; clack adds
|
|
28
|
+
// its own, so strip the padding to avoid empty bar lines.
|
|
29
|
+
log.message(message.replace(/^\n+|\n+$/g, ''));
|
|
30
|
+
},
|
|
31
|
+
warn(message) {
|
|
32
|
+
log.warn(message.replace(/^\n+|\n+$/g, ''));
|
|
33
|
+
},
|
|
34
|
+
async question(prompt, defaultValue) {
|
|
35
|
+
const value = bailIfCancelled(await text({ message: prompt, placeholder: defaultValue, defaultValue }));
|
|
36
|
+
const trimmed = (value ?? '').trim();
|
|
37
|
+
return trimmed.length > 0 ? trimmed : (defaultValue ?? '');
|
|
38
|
+
},
|
|
39
|
+
async select(prompt, options, defaultValue) {
|
|
40
|
+
// clack's `Option<Value>` is a conditional type TS can't match against our concrete
|
|
41
|
+
// `{ value, label }` while `Value` is still the generic `T`; the shapes are identical for
|
|
42
|
+
// string values, so cast the option list to satisfy it.
|
|
43
|
+
const value = bailIfCancelled(await clackSelect({
|
|
44
|
+
message: prompt,
|
|
45
|
+
options: [...options],
|
|
46
|
+
initialValue: defaultValue,
|
|
47
|
+
}));
|
|
48
|
+
return value;
|
|
49
|
+
},
|
|
50
|
+
async secret(prompt) {
|
|
51
|
+
// clack's password input masks the typed characters — no readline poking needed.
|
|
52
|
+
const value = bailIfCancelled(await password({ message: prompt }));
|
|
53
|
+
return (value ?? '').trim();
|
|
54
|
+
},
|
|
55
|
+
async confirm(prompt, defaultValue) {
|
|
56
|
+
return bailIfCancelled(await clackConfirm({ message: prompt, initialValue: defaultValue }));
|
|
57
|
+
},
|
|
58
|
+
openBrowser(url) {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
try {
|
|
61
|
+
const { cmd, args } = openCommand(url);
|
|
62
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
63
|
+
child.on('error', () => resolve());
|
|
64
|
+
child.unref();
|
|
65
|
+
resolve();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
resolve();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=io.js.map
|
package/dist/io.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"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"}
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ContainerRuntime } from './templates.js';
|
|
2
|
+
import { type VcsProvider } from './vcs.js';
|
|
3
|
+
/** A single file the CLI will write, as a path relative to the project root + its content. */
|
|
4
|
+
export interface PlannedFile {
|
|
5
|
+
path: string;
|
|
6
|
+
content: string;
|
|
7
|
+
/** True when the file holds secrets (it must always be gitignored, never overwritten blindly). */
|
|
8
|
+
secret?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Everything the orchestrator needs to render the full project. Fully resolved (no prompting). */
|
|
11
|
+
export interface BootstrapInput {
|
|
12
|
+
projectName: string;
|
|
13
|
+
appTitle: string;
|
|
14
|
+
provider: VcsProvider;
|
|
15
|
+
token: string;
|
|
16
|
+
databaseUrl: string;
|
|
17
|
+
apiBase: string;
|
|
18
|
+
port: number;
|
|
19
|
+
corsAllowedOrigins: string;
|
|
20
|
+
harnessImage: string;
|
|
21
|
+
containerRuntime: ContainerRuntime;
|
|
22
|
+
authSessionSecret: string;
|
|
23
|
+
encryptionKey: string;
|
|
24
|
+
/** Existing root `.gitignore` content, if the target dir already has one (rules are merged in). */
|
|
25
|
+
existingGitignore?: string;
|
|
26
|
+
}
|
|
27
|
+
/** The complete, ordered set of files for a scaffolded project. Pure — fully testable. */
|
|
28
|
+
export declare function buildPlan(input: BootstrapInput): PlannedFile[];
|
|
29
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAA4C,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AAErF,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,kGAAkG;IAClG,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,mGAAmG;AACnG,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,WAAW,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,kBAAkB,EAAE,MAAM,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,mGAAmG;IACnG,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,0FAA0F;AAC1F,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,EAAE,CA8C9D"}
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { buildFrontendEnv, buildLocalEnv } from './env.js';
|
|
2
|
+
import { buildGitignore, mergeGitignore } from './gitignore.js';
|
|
3
|
+
import { dockerCompose, frontendEnvExample, frontendNuxtConfig, frontendPackageJson, frontendWranglerToml, localEnvExample, localMainTs, localPackageJson, tsconfigJson, } from './templates.js';
|
|
4
|
+
import { patCreationUrl, patEnvVar, providerLabel } from './vcs.js';
|
|
5
|
+
/** The complete, ordered set of files for a scaffolded project. Pure — fully testable. */
|
|
6
|
+
export function buildPlan(input) {
|
|
7
|
+
const localEnv = buildLocalEnv({
|
|
8
|
+
databaseUrl: input.databaseUrl,
|
|
9
|
+
authSessionSecret: input.authSessionSecret,
|
|
10
|
+
encryptionKey: input.encryptionKey,
|
|
11
|
+
harnessImage: input.harnessImage,
|
|
12
|
+
port: input.port,
|
|
13
|
+
corsAllowedOrigins: input.corsAllowedOrigins,
|
|
14
|
+
provider: input.provider,
|
|
15
|
+
containerRuntime: input.containerRuntime,
|
|
16
|
+
token: input.token,
|
|
17
|
+
});
|
|
18
|
+
const frontendEnv = buildFrontendEnv({ apiBase: input.apiBase });
|
|
19
|
+
const gitignore = input.existingGitignore !== undefined
|
|
20
|
+
? mergeGitignore(input.existingGitignore)
|
|
21
|
+
: buildGitignore();
|
|
22
|
+
return [
|
|
23
|
+
{ path: '.gitignore', content: gitignore },
|
|
24
|
+
{ path: 'README.md', content: projectReadme(input) },
|
|
25
|
+
// Local-mode backend.
|
|
26
|
+
{ path: 'local/package.json', content: localPackageJson(input.projectName) },
|
|
27
|
+
{ path: 'local/src/main.ts', content: localMainTs },
|
|
28
|
+
{ path: 'local/docker-compose.yml', content: dockerCompose(input.databaseUrl) },
|
|
29
|
+
{ path: 'local/tsconfig.json', content: tsconfigJson },
|
|
30
|
+
{
|
|
31
|
+
path: 'local/.env.example',
|
|
32
|
+
content: localEnvExample({
|
|
33
|
+
provider: input.provider,
|
|
34
|
+
containerRuntime: input.containerRuntime,
|
|
35
|
+
databaseUrl: input.databaseUrl,
|
|
36
|
+
port: input.port,
|
|
37
|
+
harnessImage: input.harnessImage,
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
{ path: 'local/.env', content: localEnv, secret: true },
|
|
41
|
+
// Frontend SPA.
|
|
42
|
+
{ path: 'frontend/package.json', content: frontendPackageJson(input.projectName) },
|
|
43
|
+
{ path: 'frontend/nuxt.config.ts', content: frontendNuxtConfig(input.appTitle) },
|
|
44
|
+
{ path: 'frontend/wrangler.toml', content: frontendWranglerToml(input.projectName) },
|
|
45
|
+
{ path: 'frontend/.env.example', content: frontendEnvExample(input.apiBase) },
|
|
46
|
+
{ path: 'frontend/.env', content: frontendEnv, secret: true },
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The bundled `npm run db:up` shells `docker compose` (matching `deploy/local`). Docker, OrbStack
|
|
51
|
+
* and Colima all provide a `docker` CLI so it works as-is; Podman and Apple `container` don't, so
|
|
52
|
+
* warn and point at the alternative for the chosen runtime.
|
|
53
|
+
*/
|
|
54
|
+
function dbUpRuntimeNote(runtime) {
|
|
55
|
+
if (runtime === 'podman') {
|
|
56
|
+
return `
|
|
57
|
+
> **Podman note:** \`npm run db:up\` runs \`docker compose\`. If you don't have the Docker CLI,
|
|
58
|
+
> bring Postgres up with \`podman compose up -d postgres\` (or \`podman-compose\`) instead, or
|
|
59
|
+
> point a \`docker\`-compatible shim at the Podman socket.
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
if (runtime === 'apple') {
|
|
63
|
+
return `
|
|
64
|
+
> **Apple \`container\` note:** there is no \`docker compose\`, so \`npm run db:up\` won't work.
|
|
65
|
+
> Run Postgres another way (e.g. Docker Desktop/OrbStack just for the DB, or a native Postgres)
|
|
66
|
+
> and point \`DATABASE_URL\` at it. Only the agent job containers use Apple \`container\`.
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
function projectReadme(input) {
|
|
72
|
+
const label = providerLabel(input.provider);
|
|
73
|
+
const tokenVar = patEnvVar(input.provider);
|
|
74
|
+
return `# ${input.appTitle}
|
|
75
|
+
|
|
76
|
+
A local-mode deployment of [cat-factory](https://github.com/kibertoad/cat-factory), scaffolded
|
|
77
|
+
with \`cat-factory init\`. It has two parts:
|
|
78
|
+
|
|
79
|
+
- **\`local/\`** — the backend (\`@cat-factory/local-server\`): the shared Hono app with
|
|
80
|
+
Drizzle/Postgres + pg-boss, agent jobs run as per-run local containers, and ${label} reached
|
|
81
|
+
via a personal access token.
|
|
82
|
+
- **\`frontend/\`** — the board SPA (extends the \`@cat-factory/app\` Nuxt layer).
|
|
83
|
+
|
|
84
|
+
> The \`.env\` files hold generated secrets (\`AUTH_SESSION_SECRET\`, \`ENCRYPTION_KEY\`) and your
|
|
85
|
+
> ${label} token (\`${tokenVar}\`). They are gitignored — **keep the values stable and never
|
|
86
|
+
> commit them.** Regenerating \`AUTH_SESSION_SECRET\` forces a re-login; regenerating
|
|
87
|
+
> \`ENCRYPTION_KEY\` orphans every encrypted credential.
|
|
88
|
+
|
|
89
|
+
## Prerequisites
|
|
90
|
+
|
|
91
|
+
- Node.js 24+ (the backend entry runs TypeScript via type stripping).
|
|
92
|
+
- A container runtime (Docker/Podman/OrbStack/Colima) for Postgres and the agent containers.
|
|
93
|
+
- The executor-harness image pulled locally:
|
|
94
|
+
\`\`\`sh
|
|
95
|
+
docker pull ${input.harnessImage}
|
|
96
|
+
\`\`\`
|
|
97
|
+
|
|
98
|
+
## Install
|
|
99
|
+
|
|
100
|
+
\`\`\`sh
|
|
101
|
+
cd local && npm install && cd ../frontend && npm install && cd ..
|
|
102
|
+
\`\`\`
|
|
103
|
+
|
|
104
|
+
## Run the backend
|
|
105
|
+
|
|
106
|
+
\`\`\`sh
|
|
107
|
+
cd local
|
|
108
|
+
npm run db:up # start local Postgres (docker compose)
|
|
109
|
+
npm start # migrate + serve the API on :${input.port}
|
|
110
|
+
\`\`\`
|
|
111
|
+
${dbUpRuntimeNote(input.containerRuntime)}
|
|
112
|
+
At least one **model provider** must be configured or no model is selectable. The simplest is
|
|
113
|
+
Cloudflare Workers AI over REST — set \`CLOUDFLARE_ACCOUNT_ID\` + \`CLOUDFLARE_API_TOKEN\` in
|
|
114
|
+
\`local/.env\` (or a direct vendor key like \`ANTHROPIC_API_KEY\`), then restart.
|
|
115
|
+
|
|
116
|
+
## Run the frontend
|
|
117
|
+
|
|
118
|
+
\`\`\`sh
|
|
119
|
+
cd frontend
|
|
120
|
+
npm run dev # Nuxt dev server on http://localhost:3000
|
|
121
|
+
\`\`\`
|
|
122
|
+
|
|
123
|
+
Open http://localhost:3000. \`NUXT_PUBLIC_API_BASE\` (in \`frontend/.env\`) points at
|
|
124
|
+
\`${input.apiBase}\`.
|
|
125
|
+
|
|
126
|
+
## Rotating the ${label} token
|
|
127
|
+
|
|
128
|
+
The token in \`local/.env\` (\`${tokenVar}\`) was minted at this URL with the right scopes
|
|
129
|
+
pre-selected:
|
|
130
|
+
|
|
131
|
+
${patCreationUrl(input.provider)}
|
|
132
|
+
|
|
133
|
+
To rotate it, create a new token there and paste-replace the value, then restart the backend.
|
|
134
|
+
|
|
135
|
+
## Linking a target repo
|
|
136
|
+
|
|
137
|
+
Agent steps resolve which repo to operate on from a projection seeded from the PAT. With
|
|
138
|
+
\`${tokenVar}\` set, the board's "Add from existing repo" button lists the repos the token can
|
|
139
|
+
access — pick one to create a service frame linked to that repo. See the
|
|
140
|
+
[local-mode docs](https://github.com/kibertoad/cat-factory/blob/main/deploy/local/README.md)
|
|
141
|
+
for the CLI \`link:repo\` alternative and the full container-runtime matrix.
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=plan.js.map
|
package/dist/plan.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two mandatory crypto secrets a cat-factory deployment needs, in the EXACT formats
|
|
3
|
+
* the server's config loader expects:
|
|
4
|
+
*
|
|
5
|
+
* - `authSessionSecret` — signs the session JWT. The loader requires `>= 32` characters;
|
|
6
|
+
* we emit 32 random bytes as **hex** (64 chars), matching `deploy/local`'s
|
|
7
|
+
* `generate-secrets.mjs`.
|
|
8
|
+
* - `encryptionKey` — the master key that seals every integration credential at rest.
|
|
9
|
+
* The cipher requires valid **base64 of at least 32 decoded bytes** (a non-base64 value
|
|
10
|
+
* like `dummy` fails the cipher at boot with `InvalidCharacterError`); we emit 32 random
|
|
11
|
+
* bytes as base64.
|
|
12
|
+
*
|
|
13
|
+
* Both MUST stay STABLE once chosen: regenerating `authSessionSecret` forces a re-login and
|
|
14
|
+
* regenerating `encryptionKey` orphans every encrypted-at-rest credential. The CLI generates
|
|
15
|
+
* them once at scaffold time and writes them into `.env` so the developer keeps the values.
|
|
16
|
+
*/
|
|
17
|
+
export interface GeneratedSecrets {
|
|
18
|
+
authSessionSecret: string;
|
|
19
|
+
encryptionKey: string;
|
|
20
|
+
}
|
|
21
|
+
/** Injectable RNG seam so the generator is deterministic in tests. */
|
|
22
|
+
export type RandomBytes = (size: number) => Buffer;
|
|
23
|
+
/** Generate a fresh {@link GeneratedSecrets} pair in the server's required formats. */
|
|
24
|
+
export declare function generateSecrets(randomBytes?: RandomBytes): GeneratedSecrets;
|
|
25
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,sEAAsE;AACtE,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;AAElD,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,WAAW,GAAE,WAA6B,GAAG,gBAAgB,CAK5F"}
|