@takuhon/cli 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/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Takuhon contributors
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/NOTICE ADDED
@@ -0,0 +1,10 @@
1
+ @takuhon/cli
2
+ Copyright 2026 Takuhon contributors
3
+
4
+ This product includes software developed by the Takuhon project
5
+ (https://github.com/takuhon-dev/takuhon).
6
+
7
+ Licensed under the Apache License, Version 2.0 (see LICENSE).
8
+
9
+ Third-party dependencies retain their own licenses; see this package's
10
+ node_modules listing for details.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @takuhon/cli
2
+
3
+ The Takuhon CLI, including `create-takuhon` scaffolding and the `takuhon` command (dev/validate/sync/export/migrate/restore).
4
+
5
+ > Phase 1 skeleton. Implementations land in subsequent commits.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ var VERSION = "0.1.0";
5
+ var HELP = `takuhon ${VERSION}
6
+
7
+ Takuhon \u2014 open-source portable profile API server.
8
+
9
+ Usage:
10
+ takuhon --version Show the installed CLI version
11
+ takuhon --help Show this help
12
+
13
+ Scaffolding a new profile project:
14
+ npx create-takuhon my-profile
15
+ npx create-takuhon my-profile --license CC-BY-4.0
16
+
17
+ Subcommands (dev / validate / sync / export / migrate / restore) are planned
18
+ for a future release. Track progress at:
19
+
20
+ https://github.com/takuhon-dev/takuhon
21
+ `;
22
+ function main(argv) {
23
+ const first = argv[0];
24
+ if (first === "--version" || first === "-v") {
25
+ process.stdout.write(`${VERSION}
26
+ `);
27
+ return 0;
28
+ }
29
+ if (first === void 0 || first === "--help" || first === "-h") {
30
+ process.stdout.write(HELP);
31
+ return 0;
32
+ }
33
+ process.stderr.write(
34
+ `takuhon: unknown command '${first}'
35
+ Run \`takuhon --help\` for usage. For scaffolding a new project, use \`create-takuhon\`.
36
+ `
37
+ );
38
+ return 2;
39
+ }
40
+ process.exit(main(process.argv.slice(2)));
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * `@takuhon/cli` entry point — the `takuhon` command.\n *\n * For v0.1.0 this exposes only `--version` / `--help` plus a pointer to\n * `create-takuhon` for scaffolding. The dev / validate / sync / export /\n * migrate / restore subcommands land in subsequent releases. The bare-name\n * `takuhon` npm package (`packages/takuhon/`) redirects here via a 4-line\n * shim, so `npm i -g takuhon && takuhon --help` and `npm i -g @takuhon/cli\n * && takuhon --help` give the same output.\n */\n\nconst VERSION = '0.1.0';\n\nconst HELP = `takuhon ${VERSION}\n\nTakuhon — open-source portable profile API server.\n\nUsage:\n takuhon --version Show the installed CLI version\n takuhon --help Show this help\n\nScaffolding a new profile project:\n npx create-takuhon my-profile\n npx create-takuhon my-profile --license CC-BY-4.0\n\nSubcommands (dev / validate / sync / export / migrate / restore) are planned\nfor a future release. Track progress at:\n\n https://github.com/takuhon-dev/takuhon\n`;\n\nfunction main(argv: readonly string[]): number {\n const first = argv[0];\n\n if (first === '--version' || first === '-v') {\n process.stdout.write(`${VERSION}\\n`);\n return 0;\n }\n\n if (first === undefined || first === '--help' || first === '-h') {\n process.stdout.write(HELP);\n return 0;\n }\n\n process.stderr.write(\n `takuhon: unknown command '${first}'\\n` +\n `Run \\`takuhon --help\\` for usage. For scaffolding a new project, use \\`create-takuhon\\`.\\n`,\n );\n return 2;\n}\n\nprocess.exit(main(process.argv.slice(2)));\n"],"mappings":";;;AAaA,IAAM,UAAU;AAEhB,IAAM,OAAO,WAAW,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkB/B,SAAS,KAAK,MAAiC;AAC7C,QAAM,QAAQ,KAAK,CAAC;AAEpB,MAAI,UAAU,eAAe,UAAU,MAAM;AAC3C,YAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AACnC,WAAO;AAAA,EACT;AAEA,MAAI,UAAU,UAAa,UAAU,YAAY,UAAU,MAAM;AAC/D,YAAQ,OAAO,MAAM,IAAI;AACzB,WAAO;AAAA,EACT;AAEA,UAAQ,OAAO;AAAA,IACb,6BAA6B,KAAK;AAAA;AAAA;AAAA,EAEpC;AACA,SAAO;AACT;AAEA,QAAQ,KAAK,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC;","names":[]}
package/dist/init.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/init.js ADDED
@@ -0,0 +1,583 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/init.ts
4
+ import { basename, resolve } from "path";
5
+ import { parseArgs } from "util";
6
+ import { cancel as cancel2, intro, outro } from "@clack/prompts";
7
+
8
+ // src/licenses.ts
9
+ var LICENSE_OPTIONS = [
10
+ {
11
+ label: "CC BY 4.0",
12
+ hint: "Allow reuse with attribution",
13
+ spdxId: "CC-BY-4.0",
14
+ url: "https://creativecommons.org/licenses/by/4.0/"
15
+ },
16
+ {
17
+ label: "CC BY-NC 4.0",
18
+ hint: "Non-commercial reuse with attribution",
19
+ spdxId: "CC-BY-NC-4.0",
20
+ url: "https://creativecommons.org/licenses/by-nc/4.0/"
21
+ },
22
+ {
23
+ label: "CC0",
24
+ hint: "Public domain",
25
+ spdxId: "CC0-1.0",
26
+ url: "https://creativecommons.org/publicdomain/zero/1.0/"
27
+ },
28
+ {
29
+ label: "Proprietary",
30
+ hint: "All rights reserved",
31
+ spdxId: "Proprietary"
32
+ }
33
+ ];
34
+ var KNOWN_URL_BY_SPDX = {
35
+ "CC0-1.0": "https://creativecommons.org/publicdomain/zero/1.0/",
36
+ "CC-BY-4.0": "https://creativecommons.org/licenses/by/4.0/",
37
+ "CC-BY-SA-4.0": "https://creativecommons.org/licenses/by-sa/4.0/",
38
+ "CC-BY-ND-4.0": "https://creativecommons.org/licenses/by-nd/4.0/",
39
+ "CC-BY-NC-4.0": "https://creativecommons.org/licenses/by-nc/4.0/",
40
+ "CC-BY-NC-SA-4.0": "https://creativecommons.org/licenses/by-nc-sa/4.0/",
41
+ "CC-BY-NC-ND-4.0": "https://creativecommons.org/licenses/by-nc-nd/4.0/",
42
+ MIT: "https://spdx.org/licenses/MIT.html"
43
+ };
44
+ function buildContentLicense(spdxId) {
45
+ if (spdxId === "Proprietary") {
46
+ return {
47
+ spdxId: "Proprietary",
48
+ rights: "All rights reserved. Contact owner for usage permission."
49
+ };
50
+ }
51
+ const url = KNOWN_URL_BY_SPDX[spdxId];
52
+ if (url !== void 0) {
53
+ return { spdxId, url };
54
+ }
55
+ return { spdxId };
56
+ }
57
+ function isValidSpdxInput(input) {
58
+ const trimmed = input.trim();
59
+ if (trimmed === "") return false;
60
+ return /^[A-Za-z0-9.\-+() ]+$/.test(trimmed);
61
+ }
62
+
63
+ // src/prompts.ts
64
+ import { cancel, isCancel, select, text } from "@clack/prompts";
65
+ var CUSTOM_SENTINEL = "__custom__";
66
+ async function promptLicense() {
67
+ const choice = await select({
68
+ message: "Choose a license for your profile content:",
69
+ options: [
70
+ ...LICENSE_OPTIONS.map((opt) => ({
71
+ value: opt.spdxId,
72
+ label: opt.label,
73
+ hint: opt.hint
74
+ })),
75
+ { value: CUSTOM_SENTINEL, label: "Custom", hint: "enter SPDX identifier" }
76
+ ]
77
+ });
78
+ if (isCancel(choice)) {
79
+ cancel("Aborted.");
80
+ return { cancelled: true };
81
+ }
82
+ if (choice !== CUSTOM_SENTINEL) {
83
+ return { spdxId: choice };
84
+ }
85
+ const customRaw = await text({
86
+ message: "Enter the SPDX identifier:",
87
+ placeholder: "e.g. MIT, Apache-2.0, CC-BY-SA-4.0",
88
+ validate(value) {
89
+ if (!isValidSpdxInput(value)) {
90
+ return "Must be a non-empty SPDX-like expression (alphanumerics, ., -, +, parentheses, spaces).";
91
+ }
92
+ return void 0;
93
+ }
94
+ });
95
+ if (isCancel(customRaw)) {
96
+ cancel("Aborted.");
97
+ return { cancelled: true };
98
+ }
99
+ return { spdxId: customRaw.trim() };
100
+ }
101
+
102
+ // src/scaffold/index.ts
103
+ import { mkdir, writeFile } from "fs/promises";
104
+ import { dirname, join } from "path";
105
+
106
+ // src/scaffold/env-example.ts
107
+ function renderEnvExample() {
108
+ return `# ----------------------------------------------------------------
109
+ # Local development variables for this Takuhon deployment.
110
+ #
111
+ # Production secrets (admin token) are provisioned via:
112
+ # wrangler secret put TAKUHON_ADMIN_TOKEN
113
+ # This file is a hint for local tooling, not a runtime source.
114
+ # ----------------------------------------------------------------
115
+
116
+ # Admin bearer token for /api/admin/* endpoints.
117
+ # Generate with: openssl rand -base64 32
118
+ # Then provision with:
119
+ # echo "$TAKUHON_ADMIN_TOKEN" | wrangler secret put TAKUHON_ADMIN_TOKEN
120
+ TAKUHON_ADMIN_TOKEN=
121
+
122
+ # Comma-separated Origin allowlist for browser-originating admin requests.
123
+ # Set in wrangler.toml [vars] for production. Local example:
124
+ # TAKUHON_ADMIN_ORIGIN=https://admin.example.com,https://localhost:8787
125
+ TAKUHON_ADMIN_ORIGIN=
126
+
127
+ # Cloudflare account id (look up via \`wrangler whoami\`). Only needed if you
128
+ # script Cloudflare API calls outside Wrangler itself.
129
+ CLOUDFLARE_ACCOUNT_ID=
130
+ `;
131
+ }
132
+
133
+ // src/scaffold/gitignore.ts
134
+ function renderGitignore() {
135
+ return `# Dependencies
136
+ node_modules/
137
+
138
+ # Wrangler local cache (do not commit; contains bundled output and tmp files)
139
+ .wrangler/
140
+
141
+ # Environment variables (commit \`.env.example\` instead)
142
+ .env
143
+ .env.local
144
+ .env.*.local
145
+
146
+ # Logs
147
+ *.log
148
+ npm-debug.log*
149
+ pnpm-debug.log*
150
+ yarn-debug.log*
151
+ yarn-error.log*
152
+
153
+ # macOS
154
+ .DS_Store
155
+ `;
156
+ }
157
+
158
+ // src/scaffold/package-json.ts
159
+ function buildPackageJson(opts) {
160
+ return {
161
+ name: opts.projectName,
162
+ version: "0.0.0",
163
+ private: true,
164
+ type: "module",
165
+ description: "Takuhon profile deployment.",
166
+ scripts: {
167
+ dev: "wrangler dev",
168
+ deploy: "wrangler deploy"
169
+ },
170
+ dependencies: {
171
+ "@takuhon/api": "^0.1.0",
172
+ "@takuhon/cloudflare": "^0.1.0",
173
+ "@takuhon/core": "^0.1.0",
174
+ hono: "^4.0.0"
175
+ },
176
+ devDependencies: {
177
+ wrangler: "^4.0.0"
178
+ },
179
+ engines: {
180
+ node: ">=22.0.0"
181
+ }
182
+ };
183
+ }
184
+ function renderPackageJson(opts) {
185
+ return `${JSON.stringify(buildPackageJson(opts), null, 2)}
186
+ `;
187
+ }
188
+
189
+ // src/scaffold/readme.ts
190
+ function formatLicenseLine(license) {
191
+ if (license.spdxId === "Proprietary") {
192
+ return "**Proprietary** \u2014 all rights reserved.";
193
+ }
194
+ if (license.url !== void 0) {
195
+ return `[\`${license.spdxId}\`](${license.url})`;
196
+ }
197
+ return `\`${license.spdxId}\``;
198
+ }
199
+ function renderReadme(opts) {
200
+ const { projectName, license } = opts;
201
+ const licenseLine = formatLicenseLine(license);
202
+ return `# ${projectName}
203
+
204
+ A [Takuhon](https://github.com/takuhon-dev/takuhon) profile deployment, running on Cloudflare Workers.
205
+
206
+ > **Status**: pre-deploy. Edit \`takuhon.json\`, provision Cloudflare KV, then \`pnpm deploy\`.
207
+
208
+ > **Heads-up \u2014 Takuhon is in a pre-publish phase.**
209
+ >
210
+ > The \`@takuhon/api\`, \`@takuhon/core\`, and \`@takuhon/cloudflare\` packages
211
+ > referenced in \`package.json\` are not yet on the npm registry.
212
+ > \`pnpm install\` will fail in this directory until they ship. See *Develop*
213
+ > below for the workspace-link recipe in the meantime.
214
+
215
+ ## What is Takuhon?
216
+
217
+ Takuhon lets you own your profile as a portable JSON document and publish it as a mobile-first profile page plus a public API (JSON-LD for AI agents and search engines included).
218
+
219
+ ## Setup
220
+
221
+ 1. **Edit your profile.** Open \`takuhon.json\` and replace the sample fields (\`profile.displayName\`, \`links\`, \`careers\`, \`projects\`, \`skills\`) with your own.
222
+
223
+ 2. **Create the Cloudflare KV namespaces** and copy the returned ids into \`wrangler.toml\`:
224
+
225
+ \`\`\`sh
226
+ wrangler kv namespace create TAKUHON_KV
227
+ wrangler kv namespace create TAKUHON_KV --preview
228
+ \`\`\`
229
+
230
+ 3. **Provision the admin token** as a Wrangler secret (used by \`/api/admin/*\`):
231
+
232
+ \`\`\`sh
233
+ openssl rand -base64 32 | wrangler secret put TAKUHON_ADMIN_TOKEN
234
+ \`\`\`
235
+
236
+ Leaving the secret unset disables admin writes entirely (every \`PUT\` / \`DELETE\` returns 401).
237
+
238
+ ## Develop
239
+
240
+ The Worker entry at \`src/index.ts\` composes the takuhon adapter via
241
+ \`createTakuhonWorker\` and serves your \`takuhon.json\` as the fallback when
242
+ KV has no stored profile yet. Until the \`@takuhon/*\` packages are on npm,
243
+ clone the upstream repo and link them into this directory (substitute
244
+ your own absolute path for \`<takuhon-repo-path>\`):
245
+
246
+ \`\`\`sh
247
+ git clone https://github.com/takuhon-dev/takuhon <takuhon-repo-path>
248
+ ( cd <takuhon-repo-path> && pnpm install && pnpm build )
249
+ pnpm link <takuhon-repo-path>/packages/api <takuhon-repo-path>/packages/core <takuhon-repo-path>/adapters/cloudflare
250
+ \`\`\`
251
+
252
+ Then:
253
+
254
+ \`\`\`sh
255
+ pnpm dev # runs \`wrangler dev\` locally
256
+ \`\`\`
257
+
258
+ The full route map (public + admin) is documented in the [\`@takuhon/cloudflare\` README](https://github.com/takuhon-dev/takuhon/tree/main/adapters/cloudflare#readme).
259
+
260
+ ## Deploy
261
+
262
+ \`\`\`sh
263
+ pnpm deploy # runs \`wrangler deploy\`
264
+ \`\`\`
265
+
266
+ ## License
267
+
268
+ Profile content (\`takuhon.json\`) is licensed under ${licenseLine}
269
+
270
+ The deployment code is your own; pick a license appropriate for it.
271
+ `;
272
+ }
273
+
274
+ // src/scaffold/takuhon-json.ts
275
+ function buildTakuhonJson(license) {
276
+ return {
277
+ schemaVersion: "0.1.0",
278
+ profile: {
279
+ displayName: {
280
+ en: "Sam Lee"
281
+ }
282
+ },
283
+ links: [
284
+ {
285
+ id: "github",
286
+ type: "github",
287
+ url: "https://example.com/github/sam-lee",
288
+ featured: true
289
+ }
290
+ ],
291
+ careers: [
292
+ {
293
+ id: "first-job",
294
+ organization: {
295
+ en: "Example Co."
296
+ },
297
+ role: {
298
+ en: "Junior Software Engineer"
299
+ },
300
+ startDate: "2026-04",
301
+ endDate: null,
302
+ isCurrent: true
303
+ }
304
+ ],
305
+ projects: [
306
+ {
307
+ id: "personal-homepage",
308
+ title: {
309
+ en: "Personal homepage"
310
+ }
311
+ }
312
+ ],
313
+ skills: [
314
+ { id: "html", label: "HTML" },
315
+ { id: "css", label: "CSS" },
316
+ { id: "javascript", label: "JavaScript" }
317
+ ],
318
+ contact: {},
319
+ settings: {
320
+ defaultLocale: "en",
321
+ availableLocales: ["en"]
322
+ },
323
+ meta: {
324
+ contentLicense: { ...license }
325
+ }
326
+ };
327
+ }
328
+ function renderTakuhonJson(license) {
329
+ return `${JSON.stringify(buildTakuhonJson(license), null, 2)}
330
+ `;
331
+ }
332
+
333
+ // src/scaffold/tsconfig-json.ts
334
+ function renderTsconfigJson() {
335
+ const config = {
336
+ compilerOptions: {
337
+ target: "ES2022",
338
+ lib: ["ES2022"],
339
+ module: "ESNext",
340
+ moduleResolution: "Bundler",
341
+ esModuleInterop: true,
342
+ resolveJsonModule: true,
343
+ isolatedModules: true,
344
+ verbatimModuleSyntax: true,
345
+ strict: true,
346
+ noUncheckedIndexedAccess: true,
347
+ noImplicitOverride: true,
348
+ noFallthroughCasesInSwitch: true,
349
+ skipLibCheck: true,
350
+ noEmit: true
351
+ },
352
+ include: ["src/**/*"],
353
+ exclude: ["node_modules"]
354
+ };
355
+ return `${JSON.stringify(config, null, 2)}
356
+ `;
357
+ }
358
+
359
+ // src/scaffold/worker-index-ts.ts
360
+ function renderWorkerIndexTs() {
361
+ return `import { createTakuhonWorker } from '@takuhon/cloudflare';
362
+ import { validate } from '@takuhon/core';
363
+
364
+ // Use the project's own takuhon.json as the fallback served when KV has no
365
+ // stored profile yet. Validated once at module load so a malformed profile
366
+ // fails fast rather than at first request.
367
+ import takuhonJson from '../takuhon.json' with { type: 'json' };
368
+
369
+ const fallback = validate(takuhonJson);
370
+ if (!fallback.ok) {
371
+ throw new Error(
372
+ 'Project takuhon.json failed validation: ' +
373
+ fallback.errors.map((e) => \`\${e.pointer}: \${e.message}\`).join('; '),
374
+ );
375
+ }
376
+
377
+ const fallbackTakuhon = fallback.data;
378
+
379
+ export default createTakuhonWorker({
380
+ fallback: () => fallbackTakuhon,
381
+ });
382
+ `;
383
+ }
384
+
385
+ // src/scaffold/wrangler-toml.ts
386
+ function isValidWorkerName(name) {
387
+ return /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name);
388
+ }
389
+ function renderWranglerToml(projectName) {
390
+ if (!isValidWorkerName(projectName)) {
391
+ throw new Error(
392
+ `Invalid Cloudflare Worker name: "${projectName}". Names must be lowercase, start with a letter or digit, and contain only letters, digits, and hyphens (max 63 chars).`
393
+ );
394
+ }
395
+ return `name = "${projectName}"
396
+ main = "src/index.ts"
397
+ compatibility_date = "2026-05-01"
398
+ compatibility_flags = ["nodejs_compat"]
399
+
400
+ # Replace the placeholder ids after running:
401
+ # wrangler kv namespace create TAKUHON_KV
402
+ # wrangler kv namespace create TAKUHON_KV --preview
403
+ [[kv_namespaces]]
404
+ binding = "TAKUHON_KV"
405
+ id = "REPLACE_WITH_PRODUCTION_NAMESPACE_ID"
406
+ preview_id = "REPLACE_WITH_PREVIEW_NAMESPACE_ID"
407
+
408
+ # Admin Origin allowlist (comma-separated). Empty value disables the check.
409
+ # Example for production: TAKUHON_ADMIN_ORIGIN = "https://admin.example.com"
410
+ #
411
+ # The admin bearer token MUST be provisioned as a Wrangler secret, never in
412
+ # this file:
413
+ # wrangler secret put TAKUHON_ADMIN_TOKEN
414
+ # Use 32+ bytes of entropy, e.g. \`openssl rand -base64 32\`.
415
+ [vars]
416
+ TAKUHON_ADMIN_ORIGIN = ""
417
+ `;
418
+ }
419
+
420
+ // src/scaffold/index.ts
421
+ var TargetDirectoryExistsError = class extends Error {
422
+ constructor(targetDir) {
423
+ super("Target directory already exists.");
424
+ this.targetDir = targetDir;
425
+ }
426
+ targetDir;
427
+ name = "TargetDirectoryExistsError";
428
+ code = "TARGET_EXISTS";
429
+ };
430
+ async function writeProject(opts) {
431
+ const { targetDir, projectName, license } = opts;
432
+ try {
433
+ await mkdir(targetDir, { recursive: false });
434
+ } catch (err) {
435
+ if (isNodeErrnoException(err) && err.code === "EEXIST") {
436
+ throw new TargetDirectoryExistsError(targetDir);
437
+ }
438
+ throw err;
439
+ }
440
+ const files = [
441
+ { path: "takuhon.json", content: renderTakuhonJson(license) },
442
+ { path: "wrangler.toml", content: renderWranglerToml(projectName) },
443
+ { path: "package.json", content: renderPackageJson({ projectName }) },
444
+ { path: "README.md", content: renderReadme({ projectName, license }) },
445
+ { path: ".gitignore", content: renderGitignore() },
446
+ { path: ".env.example", content: renderEnvExample() },
447
+ { path: "tsconfig.json", content: renderTsconfigJson() },
448
+ { path: "src/index.ts", content: renderWorkerIndexTs() }
449
+ ];
450
+ for (const { path, content } of files) {
451
+ const fullPath = join(targetDir, path);
452
+ const parent = dirname(fullPath);
453
+ if (parent !== targetDir) {
454
+ await mkdir(parent, { recursive: true });
455
+ }
456
+ await writeFile(fullPath, content, "utf8");
457
+ }
458
+ return { files: files.map((f) => f.path) };
459
+ }
460
+ function isNodeErrnoException(err) {
461
+ return err instanceof Error && typeof err.code === "string";
462
+ }
463
+
464
+ // src/init.ts
465
+ function parseCliArgs(argv) {
466
+ const { values, positionals } = parseArgs({
467
+ args: [...argv],
468
+ allowPositionals: true,
469
+ strict: true,
470
+ options: {
471
+ license: { type: "string" },
472
+ help: { type: "boolean", short: "h" }
473
+ }
474
+ });
475
+ return {
476
+ targetArg: positionals[0],
477
+ extraPositionals: positionals.slice(1),
478
+ license: values.license,
479
+ help: values.help === true
480
+ };
481
+ }
482
+ function printHelp() {
483
+ process.stdout.write(
484
+ `Usage: create-takuhon <target-dir> [--license <spdxId>]
485
+
486
+ Scaffolds a Takuhon profile deployment in <target-dir>.
487
+
488
+ Options:
489
+ --license <spdxId> Skip the interactive license prompt and use the
490
+ given SPDX identifier (e.g. CC-BY-4.0, MIT,
491
+ Proprietary). Useful for CI / automation.
492
+ -h, --help Show this help.
493
+ `
494
+ );
495
+ }
496
+ async function main(argv) {
497
+ let parsed;
498
+ try {
499
+ parsed = parseCliArgs(argv);
500
+ } catch (err) {
501
+ process.stderr.write(`${err.message}
502
+
503
+ `);
504
+ printHelp();
505
+ return 2;
506
+ }
507
+ if (parsed.help) {
508
+ printHelp();
509
+ return 0;
510
+ }
511
+ if (parsed.targetArg === void 0) {
512
+ process.stderr.write(`Error: missing target directory argument.
513
+
514
+ `);
515
+ printHelp();
516
+ return 2;
517
+ }
518
+ if (parsed.extraPositionals.length > 0) {
519
+ process.stderr.write(
520
+ `Error: unexpected extra arguments: ${parsed.extraPositionals.join(" ")}
521
+
522
+ `
523
+ );
524
+ printHelp();
525
+ return 2;
526
+ }
527
+ const targetDir = resolve(process.cwd(), parsed.targetArg);
528
+ const projectName = basename(targetDir);
529
+ if (!isValidWorkerName(projectName)) {
530
+ process.stderr.write(
531
+ `Error: target directory basename "${projectName}" is not a valid Cloudflare Worker name.
532
+ Names must be lowercase, start and end with a letter or digit, and contain only letters, digits, and hyphens (max 63 chars).
533
+ `
534
+ );
535
+ return 2;
536
+ }
537
+ intro("create-takuhon");
538
+ let spdxId;
539
+ if (parsed.license !== void 0) {
540
+ const raw = parsed.license.trim();
541
+ if (!isValidSpdxInput(raw)) {
542
+ cancel2(`Invalid --license value: "${parsed.license}"`);
543
+ return 2;
544
+ }
545
+ spdxId = raw;
546
+ } else {
547
+ const result = await promptLicense();
548
+ if ("cancelled" in result) {
549
+ return 130;
550
+ }
551
+ spdxId = result.spdxId;
552
+ }
553
+ const license = buildContentLicense(spdxId);
554
+ try {
555
+ await writeProject({ targetDir, projectName, license });
556
+ } catch (err) {
557
+ if (err instanceof TargetDirectoryExistsError) {
558
+ cancel2(`Target directory already exists: ${parsed.targetArg}`);
559
+ return 1;
560
+ }
561
+ throw err;
562
+ }
563
+ outro(
564
+ `Created ${projectName} (license: ${spdxId}).
565
+
566
+ Next steps:
567
+ cd ${parsed.targetArg}
568
+ # 1. Edit takuhon.json with your profile data
569
+ # 2. Provision Cloudflare KV: wrangler kv namespace create TAKUHON_KV
570
+ # 3. Set admin token: openssl rand -base64 32 | wrangler secret put TAKUHON_ADMIN_TOKEN
571
+ pnpm install
572
+ pnpm dev`
573
+ );
574
+ return 0;
575
+ }
576
+ void main(process.argv.slice(2)).then((code) => {
577
+ process.exit(code);
578
+ }).catch((err) => {
579
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}
580
+ `);
581
+ process.exit(1);
582
+ });
583
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/init.ts","../src/licenses.ts","../src/prompts.ts","../src/scaffold/index.ts","../src/scaffold/env-example.ts","../src/scaffold/gitignore.ts","../src/scaffold/package-json.ts","../src/scaffold/readme.ts","../src/scaffold/takuhon-json.ts","../src/scaffold/tsconfig-json.ts","../src/scaffold/worker-index-ts.ts","../src/scaffold/wrangler-toml.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Entry point for `npx create-takuhon` (and `pnpm create takuhon`).\n *\n * Usage:\n * create-takuhon <target-dir> [--license <spdxId>]\n *\n * The positional target directory is required; it must not already exist (we\n * refuse to overwrite). `--license` skips the interactive picker for CI /\n * automation use.\n */\n\nimport { basename, resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\n\nimport { cancel, intro, outro } from '@clack/prompts';\n\nimport { buildContentLicense, isValidSpdxInput } from './licenses.js';\nimport { promptLicense } from './prompts.js';\nimport { TargetDirectoryExistsError, writeProject } from './scaffold/index.js';\nimport { isValidWorkerName } from './scaffold/wrangler-toml.js';\n\ninterface CliArgs {\n readonly targetArg: string | undefined;\n readonly extraPositionals: readonly string[];\n readonly license: string | undefined;\n readonly help: boolean;\n}\n\nfunction parseCliArgs(argv: readonly string[]): CliArgs {\n const { values, positionals } = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: {\n license: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n return {\n targetArg: positionals[0],\n extraPositionals: positionals.slice(1),\n license: values.license,\n help: values.help === true,\n };\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n `Usage: create-takuhon <target-dir> [--license <spdxId>]\\n` +\n `\\n` +\n `Scaffolds a Takuhon profile deployment in <target-dir>.\\n` +\n `\\n` +\n `Options:\\n` +\n ` --license <spdxId> Skip the interactive license prompt and use the\\n` +\n ` given SPDX identifier (e.g. CC-BY-4.0, MIT,\\n` +\n ` Proprietary). Useful for CI / automation.\\n` +\n ` -h, --help Show this help.\\n`,\n );\n}\n\nasync function main(argv: readonly string[]): Promise<number> {\n let parsed: CliArgs;\n try {\n parsed = parseCliArgs(argv);\n } catch (err) {\n process.stderr.write(`${(err as Error).message}\\n\\n`);\n printHelp();\n return 2;\n }\n\n if (parsed.help) {\n printHelp();\n return 0;\n }\n\n if (parsed.targetArg === undefined) {\n process.stderr.write(`Error: missing target directory argument.\\n\\n`);\n printHelp();\n return 2;\n }\n\n if (parsed.extraPositionals.length > 0) {\n process.stderr.write(\n `Error: unexpected extra arguments: ${parsed.extraPositionals.join(' ')}\\n\\n`,\n );\n printHelp();\n return 2;\n }\n\n const targetDir = resolve(process.cwd(), parsed.targetArg);\n const projectName = basename(targetDir);\n\n if (!isValidWorkerName(projectName)) {\n process.stderr.write(\n `Error: target directory basename \"${projectName}\" is not a valid Cloudflare Worker name.\\n` +\n `Names must be lowercase, start and end with a letter or digit, and contain only ` +\n `letters, digits, and hyphens (max 63 chars).\\n`,\n );\n return 2;\n }\n\n intro('create-takuhon');\n\n let spdxId: string;\n if (parsed.license !== undefined) {\n const raw = parsed.license.trim();\n if (!isValidSpdxInput(raw)) {\n cancel(`Invalid --license value: \"${parsed.license}\"`);\n return 2;\n }\n spdxId = raw;\n } else {\n const result = await promptLicense();\n if ('cancelled' in result) {\n return 130;\n }\n spdxId = result.spdxId;\n }\n\n const license = buildContentLicense(spdxId);\n\n try {\n await writeProject({ targetDir, projectName, license });\n } catch (err) {\n if (err instanceof TargetDirectoryExistsError) {\n cancel(`Target directory already exists: ${parsed.targetArg}`);\n return 1;\n }\n throw err;\n }\n\n outro(\n `Created ${projectName} (license: ${spdxId}).\\n` +\n `\\n` +\n `Next steps:\\n` +\n ` cd ${parsed.targetArg}\\n` +\n ` # 1. Edit takuhon.json with your profile data\\n` +\n ` # 2. Provision Cloudflare KV: wrangler kv namespace create TAKUHON_KV\\n` +\n ` # 3. Set admin token: openssl rand -base64 32 | wrangler secret put TAKUHON_ADMIN_TOKEN\\n` +\n ` pnpm install\\n` +\n ` pnpm dev`,\n );\n\n return 0;\n}\n\nvoid main(process.argv.slice(2))\n .then((code) => {\n process.exit(code);\n })\n .catch((err: unknown) => {\n // Render only the message — full stack traces can leak host-machine\n // absolute paths and monorepo-internal layout into a published\n // binary's stderr output.\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n });\n","/**\n * Content-license metadata and helpers for the `create-takuhon` scaffolding\n * flow.\n *\n * The four entries in `LICENSE_OPTIONS` are the curated choices the\n * interactive picker offers. \"Custom\" is handled separately by the prompt\n * flow as a free-form SPDX text input. `buildContentLicense` shapes the\n * chosen identifier into the `meta.contentLicense` fragment that lands in\n * the generated `takuhon.json`. The Takuhon profile JSON Schema accepts any\n * SPDX expression in `meta.contentLicense.spdxId`; unknown identifiers fall\n * through with `spdxId` only, and downstream UI rendering is best-effort.\n */\n\n/** A single row in the interactive license selector. */\nexport interface LicenseOption {\n /** Short label shown as the selectable label in the prompt. */\n readonly label: string;\n /** Longer hint shown alongside the label in the prompt UI. */\n readonly hint: string;\n /** SPDX identifier written to `takuhon.json` `meta.contentLicense.spdxId`. */\n readonly spdxId: string;\n /** Canonical license URL. Omitted for `Proprietary`. */\n readonly url?: string;\n}\n\n/**\n * The four curated license choices shown by the interactive picker. \"Custom\"\n * is not in this list because it triggers a free-form text prompt rather than\n * mapping to a fixed SPDX identifier here.\n */\nexport const LICENSE_OPTIONS: readonly LicenseOption[] = [\n {\n label: 'CC BY 4.0',\n hint: 'Allow reuse with attribution',\n spdxId: 'CC-BY-4.0',\n url: 'https://creativecommons.org/licenses/by/4.0/',\n },\n {\n label: 'CC BY-NC 4.0',\n hint: 'Non-commercial reuse with attribution',\n spdxId: 'CC-BY-NC-4.0',\n url: 'https://creativecommons.org/licenses/by-nc/4.0/',\n },\n {\n label: 'CC0',\n hint: 'Public domain',\n spdxId: 'CC0-1.0',\n url: 'https://creativecommons.org/publicdomain/zero/1.0/',\n },\n {\n label: 'Proprietary',\n hint: 'All rights reserved',\n spdxId: 'Proprietary',\n },\n];\n\n/**\n * Canonical URL lookup for SPDX identifiers we recognise but don't list in\n * the interactive picker. Used by `buildContentLicense` so that a user who\n * passes `--license CC-BY-SA-4.0` (or selects it via `Custom`) still gets a\n * usable `url` field in the generated `takuhon.json`.\n *\n * URLs for non-CC SPDX identifiers point at the canonical SPDX license page\n * (`spdx.org/licenses/<id>.html`). Creative Commons identifiers use the\n * `creativecommons.org` deed URL because that is the page humans expect to\n * land on (the SPDX page is metadata-only).\n */\nconst KNOWN_URL_BY_SPDX: Readonly<Record<string, string>> = {\n 'CC0-1.0': 'https://creativecommons.org/publicdomain/zero/1.0/',\n 'CC-BY-4.0': 'https://creativecommons.org/licenses/by/4.0/',\n 'CC-BY-SA-4.0': 'https://creativecommons.org/licenses/by-sa/4.0/',\n 'CC-BY-ND-4.0': 'https://creativecommons.org/licenses/by-nd/4.0/',\n 'CC-BY-NC-4.0': 'https://creativecommons.org/licenses/by-nc/4.0/',\n 'CC-BY-NC-SA-4.0': 'https://creativecommons.org/licenses/by-nc-sa/4.0/',\n 'CC-BY-NC-ND-4.0': 'https://creativecommons.org/licenses/by-nc-nd/4.0/',\n MIT: 'https://spdx.org/licenses/MIT.html',\n};\n\n/** Shape written to `takuhon.json` under `meta.contentLicense`. */\nexport interface ContentLicenseFragment {\n readonly spdxId: string;\n readonly url?: string;\n readonly rights?: string;\n}\n\n/**\n * Translate a chosen SPDX identifier into the `meta.contentLicense` fragment\n * for `takuhon.json`.\n *\n * - `Proprietary` is given a `rights` sentinel and no `url`.\n * - Known SPDX identifiers get a canonical `url`.\n * - Anything else is written as `{ spdxId }` only — the schema accepts\n * arbitrary SPDX expressions, and UI rendering is best-effort.\n */\nexport function buildContentLicense(spdxId: string): ContentLicenseFragment {\n if (spdxId === 'Proprietary') {\n return {\n spdxId: 'Proprietary',\n rights: 'All rights reserved. Contact owner for usage permission.',\n };\n }\n const url = KNOWN_URL_BY_SPDX[spdxId];\n if (url !== undefined) {\n return { spdxId, url };\n }\n return { spdxId };\n}\n\n/**\n * Light syntactic validation for a user-entered SPDX expression (the\n * `Custom` prompt branch and the `--license` flag).\n *\n * We accept any non-empty string composed of the characters that appear in\n * canonical SPDX identifiers and boolean expressions (alphanumerics, `.`,\n * `-`, `+`, parentheses, and literal spaces). Tabs and newlines are rejected\n * because real SPDX expressions never contain them. Full SPDX expression\n * parsing (`MIT OR (Apache-2.0 AND CC-BY-4.0)`) is intentionally out of\n * scope — we trust the user and let the JSON Schema's downstream validation\n * flag obvious problems.\n */\nexport function isValidSpdxInput(input: string): boolean {\n const trimmed = input.trim();\n if (trimmed === '') return false;\n return /^[A-Za-z0-9.\\-+() ]+$/.test(trimmed);\n}\n","/**\n * Interactive prompt flow for `create-takuhon`, built on `@clack/prompts`.\n *\n * A single-choice picker offers the curated `LICENSE_OPTIONS` plus a\n * `Custom` row that opens a free-form SPDX text input. Cancellation\n * (Ctrl+C or pressing Esc on a clack prompt) returns a sentinel result\n * instead of throwing so the caller can exit cleanly with a specific status\n * code.\n */\n\nimport { cancel, isCancel, select, text } from '@clack/prompts';\n\nimport { LICENSE_OPTIONS, isValidSpdxInput } from './licenses.js';\n\n/** Returned by {@link promptLicense} on successful completion. */\nexport interface PromptLicenseResult {\n /** SPDX identifier to write into `meta.contentLicense.spdxId`. */\n readonly spdxId: string;\n}\n\n/** Returned when the user cancels (Ctrl+C / Esc) instead of completing. */\nexport interface PromptCancelled {\n readonly cancelled: true;\n}\n\nconst CUSTOM_SENTINEL = '__custom__';\n\n/**\n * Run the license picker. Resolves to either the selected SPDX identifier or\n * a cancellation sentinel; never throws on user cancel.\n */\nexport async function promptLicense(): Promise<PromptLicenseResult | PromptCancelled> {\n const choice = await select<string>({\n message: 'Choose a license for your profile content:',\n options: [\n ...LICENSE_OPTIONS.map((opt) => ({\n value: opt.spdxId,\n label: opt.label,\n hint: opt.hint,\n })),\n { value: CUSTOM_SENTINEL, label: 'Custom', hint: 'enter SPDX identifier' },\n ],\n });\n\n if (isCancel(choice)) {\n cancel('Aborted.');\n return { cancelled: true };\n }\n\n if (choice !== CUSTOM_SENTINEL) {\n return { spdxId: choice };\n }\n\n const customRaw = await text({\n message: 'Enter the SPDX identifier:',\n placeholder: 'e.g. MIT, Apache-2.0, CC-BY-SA-4.0',\n validate(value): string | undefined {\n if (!isValidSpdxInput(value)) {\n return 'Must be a non-empty SPDX-like expression (alphanumerics, ., -, +, parentheses, spaces).';\n }\n return undefined;\n },\n });\n\n if (isCancel(customRaw)) {\n cancel('Aborted.');\n return { cancelled: true };\n }\n\n return { spdxId: customRaw.trim() };\n}\n","/**\n * Top-level scaffolding orchestrator for `create-takuhon`.\n *\n * `writeProject` is the single entry point used by `init.ts`. It creates the\n * target directory (must not already exist), then writes the eight files\n * that make up the scaffold: `takuhon.json`, `wrangler.toml`, `package.json`,\n * `README.md`, `.gitignore`, `.env.example`, `tsconfig.json`, and\n * `src/index.ts` (the Cloudflare Worker entry composed via\n * `createTakuhonWorker` from `@takuhon/cloudflare`).\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\nimport type { ContentLicenseFragment } from '../licenses.js';\n\nimport { renderEnvExample } from './env-example.js';\nimport { renderGitignore } from './gitignore.js';\nimport { renderPackageJson } from './package-json.js';\nimport { renderReadme } from './readme.js';\nimport { renderTakuhonJson } from './takuhon-json.js';\nimport { renderTsconfigJson } from './tsconfig-json.js';\nimport { renderWorkerIndexTs } from './worker-index-ts.js';\nimport { renderWranglerToml } from './wrangler-toml.js';\n\nexport interface WriteProjectOptions {\n /** Absolute path of the directory to create. Must not exist. */\n readonly targetDir: string;\n /** Used for the npm `name`, Worker name, and README header. */\n readonly projectName: string;\n /** Chosen content license (already mapped via `buildContentLicense`). */\n readonly license: ContentLicenseFragment;\n}\n\nexport interface WriteProjectResult {\n /** Relative paths of files that were written (in the order written). */\n readonly files: readonly string[];\n}\n\n/**\n * Error thrown when the target directory already exists. The caller is\n * expected to render a friendly message that references the user-supplied\n * path; the `code` is stable for tests. The absolute `targetDir` is exposed\n * as a field rather than embedded in the message so that bubbling the error\n * up through generic logging does not leak filesystem layout.\n */\nexport class TargetDirectoryExistsError extends Error {\n override readonly name = 'TargetDirectoryExistsError';\n readonly code = 'TARGET_EXISTS' as const;\n constructor(readonly targetDir: string) {\n super('Target directory already exists.');\n }\n}\n\n/**\n * Create the project directory and write the scaffolded files. Order is\n * deterministic so callers (and tests) can rely on it.\n */\nexport async function writeProject(opts: WriteProjectOptions): Promise<WriteProjectResult> {\n const { targetDir, projectName, license } = opts;\n\n // mkdir with recursive: false fails if the directory exists; we surface a\n // typed error so the CLI entry can render a friendly message.\n try {\n await mkdir(targetDir, { recursive: false });\n } catch (err) {\n if (isNodeErrnoException(err) && err.code === 'EEXIST') {\n throw new TargetDirectoryExistsError(targetDir);\n }\n throw err;\n }\n\n const files: { readonly path: string; readonly content: string }[] = [\n { path: 'takuhon.json', content: renderTakuhonJson(license) },\n { path: 'wrangler.toml', content: renderWranglerToml(projectName) },\n { path: 'package.json', content: renderPackageJson({ projectName }) },\n { path: 'README.md', content: renderReadme({ projectName, license }) },\n { path: '.gitignore', content: renderGitignore() },\n { path: '.env.example', content: renderEnvExample() },\n { path: 'tsconfig.json', content: renderTsconfigJson() },\n { path: 'src/index.ts', content: renderWorkerIndexTs() },\n ];\n\n for (const { path, content } of files) {\n const fullPath = join(targetDir, path);\n const parent = dirname(fullPath);\n if (parent !== targetDir) {\n await mkdir(parent, { recursive: true });\n }\n await writeFile(fullPath, content, 'utf8');\n }\n\n return { files: files.map((f) => f.path) };\n}\n\nfunction isNodeErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","/**\n * Generator for the `.env.example` file in a freshly scaffolded project.\n *\n * The Wrangler-managed secrets (admin bearer token) are *not* read from `.env`\n * at deploy time — `wrangler secret put` is the source of truth. The\n * `.env.example` is therefore documentation: it lists the variables a developer\n * may want locally (e.g. for scripting or for the wrangler CLI itself) and\n * links each to its production provisioning path.\n */\n\nexport function renderEnvExample(): string {\n return `# ----------------------------------------------------------------\n# Local development variables for this Takuhon deployment.\n#\n# Production secrets (admin token) are provisioned via:\n# wrangler secret put TAKUHON_ADMIN_TOKEN\n# This file is a hint for local tooling, not a runtime source.\n# ----------------------------------------------------------------\n\n# Admin bearer token for /api/admin/* endpoints.\n# Generate with: openssl rand -base64 32\n# Then provision with:\n# echo \"$TAKUHON_ADMIN_TOKEN\" | wrangler secret put TAKUHON_ADMIN_TOKEN\nTAKUHON_ADMIN_TOKEN=\n\n# Comma-separated Origin allowlist for browser-originating admin requests.\n# Set in wrangler.toml [vars] for production. Local example:\n# TAKUHON_ADMIN_ORIGIN=https://admin.example.com,https://localhost:8787\nTAKUHON_ADMIN_ORIGIN=\n\n# Cloudflare account id (look up via \\`wrangler whoami\\`). Only needed if you\n# script Cloudflare API calls outside Wrangler itself.\nCLOUDFLARE_ACCOUNT_ID=\n`;\n}\n","/**\n * Generator for the scaffolded project's `.gitignore`.\n *\n * Covers Node, Wrangler local cache, and environment files. Kept conservative\n * — no opinions about lockfile preferences (the user picks their package\n * manager) or editor folders (per-editor patterns belong in a global ignore).\n */\n\nexport function renderGitignore(): string {\n return `# Dependencies\nnode_modules/\n\n# Wrangler local cache (do not commit; contains bundled output and tmp files)\n.wrangler/\n\n# Environment variables (commit \\`.env.example\\` instead)\n.env\n.env.local\n.env.*.local\n\n# Logs\n*.log\nnpm-debug.log*\npnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# macOS\n.DS_Store\n`;\n}\n","/**\n * Generator for the scaffolded project's `package.json`.\n *\n * Pinned dependency versions are caret ranges matched to the `0.1.x` line of\n * the published `@takuhon/*` packages (this is a Phase 3.5 MVP — once those\n * are on the registry, the user can run `pnpm install` and get a working\n * Cloudflare worker setup). `hono` is declared as a direct dependency because\n * the future scaffolded `src/index.ts` (added in a follow-up phase) imports\n * from it directly; `wrangler` is a devDependency since it's the deploy /\n * dev-server tool.\n */\n\nexport interface PackageJsonOptions {\n /** Used for the npm `name` field. */\n readonly projectName: string;\n}\n\nexport function buildPackageJson(opts: PackageJsonOptions): Record<string, unknown> {\n return {\n name: opts.projectName,\n version: '0.0.0',\n private: true,\n type: 'module',\n description: 'Takuhon profile deployment.',\n scripts: {\n dev: 'wrangler dev',\n deploy: 'wrangler deploy',\n },\n dependencies: {\n '@takuhon/api': '^0.1.0',\n '@takuhon/cloudflare': '^0.1.0',\n '@takuhon/core': '^0.1.0',\n hono: '^4.0.0',\n },\n devDependencies: {\n wrangler: '^4.0.0',\n },\n engines: {\n node: '>=22.0.0',\n },\n };\n}\n\nexport function renderPackageJson(opts: PackageJsonOptions): string {\n return `${JSON.stringify(buildPackageJson(opts), null, 2)}\\n`;\n}\n","/**\n * Generator for the README.md placed in a freshly scaffolded project.\n *\n * Aims to give a first-time user enough to (1) fill in their profile data,\n * (2) provision Cloudflare KV + admin secret, and (3) `pnpm dev` / `pnpm\n * deploy` once the upstream `@takuhon/*` packages are reachable. Cross-\n * references the published `@takuhon/cloudflare` adapter README as the\n * next-step reference for the route map.\n */\n\nimport type { ContentLicenseFragment } from '../licenses.js';\n\nexport interface ReadmeOptions {\n readonly projectName: string;\n readonly license: ContentLicenseFragment;\n}\n\n/**\n * Human-readable license attribution line for the README footer. Uses the\n * canonical URL when available; falls back to plain SPDX (or\n * \"All rights reserved\" for Proprietary).\n */\nfunction formatLicenseLine(license: ContentLicenseFragment): string {\n if (license.spdxId === 'Proprietary') {\n return '**Proprietary** — all rights reserved.';\n }\n if (license.url !== undefined) {\n return `[\\`${license.spdxId}\\`](${license.url})`;\n }\n return `\\`${license.spdxId}\\``;\n}\n\nexport function renderReadme(opts: ReadmeOptions): string {\n const { projectName, license } = opts;\n const licenseLine = formatLicenseLine(license);\n return `# ${projectName}\n\nA [Takuhon](https://github.com/takuhon-dev/takuhon) profile deployment, running on Cloudflare Workers.\n\n> **Status**: pre-deploy. Edit \\`takuhon.json\\`, provision Cloudflare KV, then \\`pnpm deploy\\`.\n\n> **Heads-up — Takuhon is in a pre-publish phase.**\n>\n> The \\`@takuhon/api\\`, \\`@takuhon/core\\`, and \\`@takuhon/cloudflare\\` packages\n> referenced in \\`package.json\\` are not yet on the npm registry.\n> \\`pnpm install\\` will fail in this directory until they ship. See *Develop*\n> below for the workspace-link recipe in the meantime.\n\n## What is Takuhon?\n\nTakuhon lets you own your profile as a portable JSON document and publish it as a mobile-first profile page plus a public API (JSON-LD for AI agents and search engines included).\n\n## Setup\n\n1. **Edit your profile.** Open \\`takuhon.json\\` and replace the sample fields (\\`profile.displayName\\`, \\`links\\`, \\`careers\\`, \\`projects\\`, \\`skills\\`) with your own.\n\n2. **Create the Cloudflare KV namespaces** and copy the returned ids into \\`wrangler.toml\\`:\n\n \\`\\`\\`sh\n wrangler kv namespace create TAKUHON_KV\n wrangler kv namespace create TAKUHON_KV --preview\n \\`\\`\\`\n\n3. **Provision the admin token** as a Wrangler secret (used by \\`/api/admin/*\\`):\n\n \\`\\`\\`sh\n openssl rand -base64 32 | wrangler secret put TAKUHON_ADMIN_TOKEN\n \\`\\`\\`\n\n Leaving the secret unset disables admin writes entirely (every \\`PUT\\` / \\`DELETE\\` returns 401).\n\n## Develop\n\nThe Worker entry at \\`src/index.ts\\` composes the takuhon adapter via\n\\`createTakuhonWorker\\` and serves your \\`takuhon.json\\` as the fallback when\nKV has no stored profile yet. Until the \\`@takuhon/*\\` packages are on npm,\nclone the upstream repo and link them into this directory (substitute\nyour own absolute path for \\`<takuhon-repo-path>\\`):\n\n\\`\\`\\`sh\ngit clone https://github.com/takuhon-dev/takuhon <takuhon-repo-path>\n( cd <takuhon-repo-path> && pnpm install && pnpm build )\npnpm link <takuhon-repo-path>/packages/api <takuhon-repo-path>/packages/core <takuhon-repo-path>/adapters/cloudflare\n\\`\\`\\`\n\nThen:\n\n\\`\\`\\`sh\npnpm dev # runs \\`wrangler dev\\` locally\n\\`\\`\\`\n\nThe full route map (public + admin) is documented in the [\\`@takuhon/cloudflare\\` README](https://github.com/takuhon-dev/takuhon/tree/main/adapters/cloudflare#readme).\n\n## Deploy\n\n\\`\\`\\`sh\npnpm deploy # runs \\`wrangler deploy\\`\n\\`\\`\\`\n\n## License\n\nProfile content (\\`takuhon.json\\`) is licensed under ${licenseLine}\n\nThe deployment code is your own; pick a license appropriate for it.\n`;\n}\n","/**\n * Generator for the `takuhon.json` file written into a freshly scaffolded\n * project.\n *\n * The template is a copy of `examples/minimal-profile/takuhon.json` from the\n * monorepo. It is inlined here as a TypeScript constant rather than imported\n * from `examples/` so the published `@takuhon/cli` npm package does not need\n * to ship the examples directory.\n *\n * Only `meta.contentLicense` is rewritten per the user's choice; everything\n * else is the canonical minimal profile (one career, one project, three\n * skills, `en` locale only) that downstream `@takuhon/core` validation\n * already accepts (see `packages/core/src/__tests__/examples-fixtures.test.ts`).\n */\n\nimport type { ContentLicenseFragment } from '../licenses.js';\n\n/**\n * Build the `takuhon.json` payload as a deterministic, schema-valid object.\n * `meta.contentLicense` is filled from the supplied fragment so each\n * generated project carries the user's chosen SPDX identifier (and `url` /\n * `rights` where applicable).\n */\nexport function buildTakuhonJson(license: ContentLicenseFragment): unknown {\n return {\n schemaVersion: '0.1.0',\n profile: {\n displayName: {\n en: 'Sam Lee',\n },\n },\n links: [\n {\n id: 'github',\n type: 'github',\n url: 'https://example.com/github/sam-lee',\n featured: true,\n },\n ],\n careers: [\n {\n id: 'first-job',\n organization: {\n en: 'Example Co.',\n },\n role: {\n en: 'Junior Software Engineer',\n },\n startDate: '2026-04',\n endDate: null,\n isCurrent: true,\n },\n ],\n projects: [\n {\n id: 'personal-homepage',\n title: {\n en: 'Personal homepage',\n },\n },\n ],\n skills: [\n { id: 'html', label: 'HTML' },\n { id: 'css', label: 'CSS' },\n { id: 'javascript', label: 'JavaScript' },\n ],\n contact: {},\n settings: {\n defaultLocale: 'en',\n availableLocales: ['en'],\n },\n meta: {\n contentLicense: { ...license },\n },\n };\n}\n\n/** Serialise to a UTF-8 string with trailing newline (POSIX-friendly). */\nexport function renderTakuhonJson(license: ContentLicenseFragment): string {\n return `${JSON.stringify(buildTakuhonJson(license), null, 2)}\\n`;\n}\n","/**\n * Generator for the scaffolded project's `tsconfig.json`.\n *\n * Minimal configuration that makes the generated `src/index.ts` typecheck\n * with `tsc --noEmit` in a freshly scaffolded directory. The settings\n * mirror the upstream takuhon monorepo defaults (ES2022 target, ESNext\n * modules, strict mode, `noUncheckedIndexedAccess`) and explicitly enable\n * the two flags the Worker entry depends on:\n *\n * - `resolveJsonModule` — so `import takuhonJson from '../takuhon.json'`\n * resolves at type-check time.\n * - `moduleResolution: \"Bundler\"` — so the Wrangler / esbuild-style\n * resolution (no need for explicit file extensions) is the source of\n * truth that matches what `wrangler dev` actually runs.\n *\n * `noEmit: true` keeps `tsc` purely a type-checker; Wrangler's bundler\n * produces the deployable artifact.\n */\n\nexport function renderTsconfigJson(): string {\n const config = {\n compilerOptions: {\n target: 'ES2022',\n lib: ['ES2022'],\n module: 'ESNext',\n moduleResolution: 'Bundler',\n esModuleInterop: true,\n resolveJsonModule: true,\n isolatedModules: true,\n verbatimModuleSyntax: true,\n strict: true,\n noUncheckedIndexedAccess: true,\n noImplicitOverride: true,\n noFallthroughCasesInSwitch: true,\n skipLibCheck: true,\n noEmit: true,\n },\n include: ['src/**/*'],\n exclude: ['node_modules'],\n };\n return `${JSON.stringify(config, null, 2)}\\n`;\n}\n","/**\n * Generator for `src/index.ts` — the Cloudflare Worker entry file written\n * into a freshly scaffolded project.\n *\n * The generated file uses `createTakuhonWorker()` from `@takuhon/cloudflare`\n * so the scaffolded project does not need to know the internal wiring\n * (Hono router, KV-backed storage, edge cache purger, console audit logger).\n * The user's own `takuhon.json` is loaded via an ES module JSON import,\n * validated once at module load, and the resulting `Takuhon` value is\n * served as the fallback when KV has no stored profile yet.\n *\n * `wrangler.toml`'s `main` field already points at `src/index.ts`, and\n * `nodejs_compat` is enabled, so the file works under `wrangler dev` /\n * `wrangler deploy` without further configuration.\n */\n\nexport function renderWorkerIndexTs(): string {\n return `import { createTakuhonWorker } from '@takuhon/cloudflare';\nimport { validate } from '@takuhon/core';\n\n// Use the project's own takuhon.json as the fallback served when KV has no\n// stored profile yet. Validated once at module load so a malformed profile\n// fails fast rather than at first request.\nimport takuhonJson from '../takuhon.json' with { type: 'json' };\n\nconst fallback = validate(takuhonJson);\nif (!fallback.ok) {\n throw new Error(\n 'Project takuhon.json failed validation: ' +\n fallback.errors.map((e) => \\`\\${e.pointer}: \\${e.message}\\`).join('; '),\n );\n}\n\nconst fallbackTakuhon = fallback.data;\n\nexport default createTakuhonWorker({\n fallback: () => fallbackTakuhon,\n});\n`;\n}\n","/**\n * Generator for `wrangler.toml` in a freshly scaffolded project.\n *\n * The layout mirrors the reference Cloudflare adapter shipped in this\n * monorepo: a single KV namespace `TAKUHON_KV`, placeholder ids the user\n * fills in after running `wrangler kv namespace create`, and the\n * `TAKUHON_ADMIN_ORIGIN` var defaulting to \"\" (disabled). The admin bearer\n * token is provisioned as a Wrangler secret and is therefore not in this file.\n */\n\n/**\n * Validate a Cloudflare Worker name.\n *\n * Cloudflare itself accepts mixed-case DNS labels (1–63 chars, must not\n * start or end with a hyphen). This validator additionally enforces a\n * lowercase convention chosen for the scaffolder so that the resulting\n * `workers.dev` subdomain is predictable and matches the npm `name` field.\n */\nexport function isValidWorkerName(name: string): boolean {\n return /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name);\n}\n\n/**\n * Render `wrangler.toml` for the given project name.\n *\n * Throws if `projectName` is not a valid Cloudflare Worker name. Callers are\n * responsible for sanitising/validating earlier in the prompt flow.\n */\nexport function renderWranglerToml(projectName: string): string {\n if (!isValidWorkerName(projectName)) {\n throw new Error(\n `Invalid Cloudflare Worker name: \"${projectName}\". Names must be lowercase, ` +\n `start with a letter or digit, and contain only letters, digits, and hyphens (max 63 chars).`,\n );\n }\n return `name = \"${projectName}\"\nmain = \"src/index.ts\"\ncompatibility_date = \"2026-05-01\"\ncompatibility_flags = [\"nodejs_compat\"]\n\n# Replace the placeholder ids after running:\n# wrangler kv namespace create TAKUHON_KV\n# wrangler kv namespace create TAKUHON_KV --preview\n[[kv_namespaces]]\nbinding = \"TAKUHON_KV\"\nid = \"REPLACE_WITH_PRODUCTION_NAMESPACE_ID\"\npreview_id = \"REPLACE_WITH_PREVIEW_NAMESPACE_ID\"\n\n# Admin Origin allowlist (comma-separated). Empty value disables the check.\n# Example for production: TAKUHON_ADMIN_ORIGIN = \"https://admin.example.com\"\n#\n# The admin bearer token MUST be provisioned as a Wrangler secret, never in\n# this file:\n# wrangler secret put TAKUHON_ADMIN_TOKEN\n# Use 32+ bytes of entropy, e.g. \\`openssl rand -base64 32\\`.\n[vars]\nTAKUHON_ADMIN_ORIGIN = \"\"\n`;\n}\n"],"mappings":"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","names":["cancel","cancel"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@takuhon/cli",
3
+ "version": "0.1.0",
4
+ "description": "create-takuhon scaffolding plus dev/validate/sync/export/migrate/restore commands",
5
+ "license": "Apache-2.0",
6
+ "author": "Takuhon contributors",
7
+ "homepage": "https://github.com/takuhon-dev/takuhon/tree/main/packages/cli#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/takuhon-dev/takuhon.git",
11
+ "directory": "packages/cli"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/takuhon-dev/takuhon/issues"
15
+ },
16
+ "keywords": [
17
+ "takuhon",
18
+ "profile",
19
+ "cli",
20
+ "create-takuhon",
21
+ "scaffolding"
22
+ ],
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ }
31
+ },
32
+ "bin": {
33
+ "takuhon": "./dist/index.js",
34
+ "create-takuhon": "./dist/init.js"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "README.md",
39
+ "LICENSE",
40
+ "NOTICE"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "engines": {
46
+ "node": ">=22.0.0"
47
+ },
48
+ "dependencies": {
49
+ "@clack/prompts": "^0.11.0",
50
+ "@takuhon/core": "0.1.0"
51
+ },
52
+ "scripts": {
53
+ "typecheck": "tsc",
54
+ "build": "tsup src/index.ts src/init.ts --format esm --dts --clean --sourcemap"
55
+ }
56
+ }