@take-out/cli 0.7.0-1784497775575 → 0.7.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/README.md CHANGED
@@ -63,8 +63,7 @@ takeout check
63
63
 
64
64
  #### `takeout docs`
65
65
 
66
- Read documentation from the current repository's `skills/` or `docs/`
67
- directory:
66
+ Manage and retrieve documentation files:
68
67
 
69
68
  ```bash
70
69
  # list available docs
@@ -76,6 +75,9 @@ takeout docs get zero tamagui
76
75
  # get path to docs directory
77
76
  takeout docs path
78
77
 
78
+ # eject docs to local ./docs folder
79
+ takeout docs eject
80
+
79
81
  # generate Claude Code skills from docs
80
82
  takeout skills generate
81
83
  takeout skills generate --clean # clean and regenerate
@@ -85,7 +87,7 @@ takeout skills generate --clean # clean and regenerate
85
87
 
86
88
  The `skills generate` command generates Claude Code skills from your documentation:
87
89
 
88
- 1. scans docs from `./skills/` when present, otherwise `./docs/`
90
+ 1. scans docs from both `./docs/` (local) and package docs
89
91
  2. only docs with YAML frontmatter containing `name` and `description` become skills
90
92
  3. those docs are symlinked into `.claude/skills/`
91
93
 
@@ -37,39 +37,45 @@ __export(docs_exports, {
37
37
  });
38
38
  module.exports = __toCommonJS(docs_exports);
39
39
  var import_node_fs = require("node:fs");
40
+ var import_node_module = require("node:module");
40
41
  var import_node_path = require("node:path");
41
42
  var import_citty = require("citty");
42
43
  var import_picocolors = __toESM(require("picocolors"), 1);
43
- function resolveDocsDir(cwd = process.cwd()) {
44
- const skillsDir = (0, import_node_path.join)(cwd, "skills");
45
- if ((0, import_node_fs.existsSync)(skillsDir)) return skillsDir;
46
- const docsDir = (0, import_node_path.join)(cwd, "docs");
47
- if ((0, import_node_fs.existsSync)(docsDir)) return docsDir;
48
- throw new Error(`No docs or skills directory found in ${cwd}`);
49
- }
44
+ var import_sync = require("../utils/sync.cjs");
45
+ const import_meta = {};
46
+ const require2 = (0, import_node_module.createRequire)(import_meta.url);
47
+ const DOCS_DIR = (0, import_node_path.dirname)(require2.resolve("@take-out/docs/package.json"));
50
48
  const listCommand = (0, import_citty.defineCommand)({
51
49
  meta: {
52
50
  name: "list",
53
- description: "List repository documentation files"
51
+ description: "List all available documentation files"
54
52
  },
55
53
  async run() {
56
- let docsDir;
57
- try {
58
- docsDir = resolveDocsDir();
59
- } catch (error) {
60
- console.error(import_picocolors.default.red(`\u2717 ${error instanceof Error ? error.message : String(error)}`));
54
+ if (!(0, import_node_fs.existsSync)(DOCS_DIR)) {
55
+ console.error(import_picocolors.default.red("\u2717 Docs directory not found"));
61
56
  process.exit(1);
62
57
  }
63
- const files = (0, import_node_fs.readdirSync)(docsDir).filter(file => file.endsWith(".md")).sort();
58
+ const files = (0, import_node_fs.readdirSync)(DOCS_DIR).filter(f => f.endsWith(".md")).sort();
64
59
  console.info();
65
60
  console.info(import_picocolors.default.bold(import_picocolors.default.cyan("\u{1F4DA} Available Documentation")));
66
61
  console.info();
67
62
  for (const file of files) {
68
63
  const name = file.replace(/\.md$/, "");
69
- const content = (0, import_node_fs.readFileSync)((0, import_node_path.join)(docsDir, file), "utf-8");
70
- const heading = content.match(/^#\s+(.+)$/m)?.[1];
71
- console.info(` ${import_picocolors.default.green(name)}`);
72
- if (heading) console.info(` ${import_picocolors.default.dim(heading)}`);
64
+ const path = (0, import_node_path.join)(DOCS_DIR, file);
65
+ let description = "";
66
+ try {
67
+ const content = (0, import_node_fs.readFileSync)(path, "utf-8");
68
+ const match = content.match(/^#\s+(.+)$/m);
69
+ if (match?.[1]) {
70
+ description = match[1];
71
+ }
72
+ } catch {}
73
+ if (description) {
74
+ console.info(` ${import_picocolors.default.green(name)}`);
75
+ console.info(` ${import_picocolors.default.dim(description)}`);
76
+ } else {
77
+ console.info(` ${import_picocolors.default.green(name)}`);
78
+ }
73
79
  }
74
80
  console.info();
75
81
  console.info(import_picocolors.default.dim(`Use 'takeout docs get <name>' to view a document`));
@@ -79,7 +85,7 @@ const listCommand = (0, import_citty.defineCommand)({
79
85
  const getCommand = (0, import_citty.defineCommand)({
80
86
  meta: {
81
87
  name: "get",
82
- description: "Get the content of one or more repository documentation files"
88
+ description: "Get the content of one or more documentation files"
83
89
  },
84
90
  args: {
85
91
  name: {
@@ -92,38 +98,65 @@ const getCommand = (0, import_citty.defineCommand)({
92
98
  async run({
93
99
  args
94
100
  }) {
95
- let docsDir;
96
- try {
97
- docsDir = resolveDocsDir();
98
- } catch (error) {
99
- console.error(import_picocolors.default.red(`\u2717 ${error instanceof Error ? error.message : String(error)}`));
100
- process.exit(1);
101
- }
102
101
  const names = args._.length > 0 ? args._ : [args.name];
103
102
  const results = [];
103
+ const errors = [];
104
+ const cwd = process.cwd();
105
+ const localSkillsDir = (0, import_node_path.join)(cwd, "skills");
106
+ const localDocsDir = (0, import_node_path.join)(cwd, "docs");
104
107
  for (const name of names) {
105
108
  const fileName = name.endsWith(".md") ? name : `${name}.md`;
106
- const filePath = (0, import_node_path.join)(docsDir, fileName);
107
- if (!(0, import_node_fs.existsSync)(filePath)) {
108
- console.error(import_picocolors.default.red(`\u2717 Doc file not found: ${name}`));
109
+ const localSkillsPath = (0, import_node_path.join)(localSkillsDir, fileName);
110
+ const localDocsPath = (0, import_node_path.join)(localDocsDir, fileName);
111
+ const packageFilePath = (0, import_node_path.join)(DOCS_DIR, fileName);
112
+ let filePath = null;
113
+ if ((0, import_node_fs.existsSync)(localSkillsPath)) {
114
+ filePath = localSkillsPath;
115
+ } else if ((0, import_node_fs.existsSync)(localDocsPath)) {
116
+ filePath = localDocsPath;
117
+ } else if ((0, import_node_fs.existsSync)(packageFilePath)) {
118
+ filePath = packageFilePath;
119
+ }
120
+ if (!filePath) {
121
+ errors.push({
122
+ name,
123
+ error: "File not found"
124
+ });
109
125
  continue;
110
126
  }
111
- results.push({
112
- name,
113
- content: (0, import_node_fs.readFileSync)(filePath, "utf-8")
114
- });
127
+ try {
128
+ const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
129
+ results.push({
130
+ name,
131
+ content
132
+ });
133
+ } catch (err) {
134
+ errors.push({
135
+ name,
136
+ error: String(err)
137
+ });
138
+ }
115
139
  }
116
- if (results.length === 0) {
140
+ if (errors.length > 0) {
141
+ for (const {
142
+ name
143
+ } of errors) {
144
+ console.error(import_picocolors.default.red(`\u2717 Doc file not found: ${name}`));
145
+ }
117
146
  console.info();
118
147
  console.info(import_picocolors.default.dim(`Use 'takeout docs list' to see available docs`));
119
- process.exit(1);
148
+ if (results.length === 0) {
149
+ process.exit(1);
150
+ }
151
+ console.info();
120
152
  }
121
- for (let index = 0; index < results.length; index++) {
122
- const result = results[index];
153
+ for (let i = 0; i < results.length; i++) {
154
+ const result = results[i];
155
+ if (!result) continue;
123
156
  console.info(`# ${result.name}`);
124
157
  console.info();
125
158
  console.info(result.content);
126
- if (index < results.length - 1) {
159
+ if (i < results.length - 1) {
127
160
  console.info();
128
161
  console.info("---");
129
162
  console.info();
@@ -134,7 +167,7 @@ const getCommand = (0, import_citty.defineCommand)({
134
167
  const pathCommand = (0, import_citty.defineCommand)({
135
168
  meta: {
136
169
  name: "path",
137
- description: "Get the absolute path to repository documentation"
170
+ description: "Get the absolute path to a documentation file"
138
171
  },
139
172
  args: {
140
173
  name: {
@@ -146,19 +179,12 @@ const pathCommand = (0, import_citty.defineCommand)({
146
179
  async run({
147
180
  args
148
181
  }) {
149
- let docsDir;
150
- try {
151
- docsDir = resolveDocsDir();
152
- } catch (error) {
153
- console.error(import_picocolors.default.red(`\u2717 ${error instanceof Error ? error.message : String(error)}`));
154
- process.exit(1);
155
- }
156
182
  if (!args.name) {
157
- console.info(docsDir);
183
+ console.info(DOCS_DIR);
158
184
  return;
159
185
  }
160
186
  const fileName = args.name.endsWith(".md") ? args.name : `${args.name}.md`;
161
- const filePath = (0, import_node_path.join)(docsDir, fileName);
187
+ const filePath = (0, import_node_path.join)(DOCS_DIR, fileName);
162
188
  if (!(0, import_node_fs.existsSync)(filePath)) {
163
189
  console.error(import_picocolors.default.red(`\u2717 Doc file not found: ${args.name}`));
164
190
  process.exit(1);
@@ -166,14 +192,114 @@ const pathCommand = (0, import_citty.defineCommand)({
166
192
  console.info(filePath);
167
193
  }
168
194
  });
195
+ const ejectCommand = (0, import_citty.defineCommand)({
196
+ meta: {
197
+ name: "eject",
198
+ description: "Eject Takeout documentation files into your project"
199
+ },
200
+ args: {
201
+ yes: {
202
+ type: "boolean",
203
+ description: "Skip confirmations and eject all files",
204
+ default: false
205
+ }
206
+ },
207
+ async run({
208
+ args
209
+ }) {
210
+ const cwd = process.cwd();
211
+ const targetDocsDir = (0, import_node_path.join)(cwd, "docs");
212
+ const sourceDocsDir = DOCS_DIR;
213
+ console.info();
214
+ console.info(import_picocolors.default.bold(import_picocolors.default.cyan("\u{1F4DA} Eject Docs")));
215
+ console.info();
216
+ console.info(import_picocolors.default.dim(`Source: ${sourceDocsDir}`));
217
+ console.info(import_picocolors.default.dim(`Target: ${targetDocsDir}`));
218
+ console.info();
219
+ if (!(0, import_node_fs.existsSync)(sourceDocsDir)) {
220
+ console.error(import_picocolors.default.red("\u2717 Source docs directory not found in Takeout package"));
221
+ process.exit(1);
222
+ }
223
+ if (!(0, import_node_fs.existsSync)(targetDocsDir)) {
224
+ console.info(import_picocolors.default.yellow("\u26A0 Target docs directory does not exist, will create it"));
225
+ (0, import_node_fs.mkdirSync)(targetDocsDir, {
226
+ recursive: true
227
+ });
228
+ }
229
+ const sourceFiles = (0, import_node_fs.readdirSync)(sourceDocsDir).filter(f => f.endsWith(".md"));
230
+ if (sourceFiles.length === 0) {
231
+ console.info(import_picocolors.default.yellow("No markdown files found in Takeout docs"));
232
+ return;
233
+ }
234
+ const filesToSync = [];
235
+ const stats = {
236
+ new: 0,
237
+ modified: 0,
238
+ identical: 0
239
+ };
240
+ for (const file of sourceFiles) {
241
+ const sourcePath = (0, import_node_path.join)(sourceDocsDir, file);
242
+ const targetPath = (0, import_node_path.join)(targetDocsDir, file);
243
+ const status = (0, import_sync.compareFiles)(sourcePath, targetPath);
244
+ stats[status]++;
245
+ filesToSync.push({
246
+ name: file,
247
+ sourcePath,
248
+ targetPath,
249
+ status,
250
+ sourceSize: (0, import_sync.getFileSize)(sourcePath),
251
+ targetSize: (0, import_sync.getFileSize)(targetPath)
252
+ });
253
+ }
254
+ console.info(import_picocolors.default.bold("Summary:"));
255
+ console.info(` ${import_picocolors.default.green(`${stats.new} new`)}`);
256
+ console.info(` ${import_picocolors.default.yellow(`${stats.modified} modified`)}`);
257
+ console.info(` ${import_picocolors.default.dim(`${stats.identical} identical`)}`);
258
+ console.info();
259
+ if (stats.new === 0 && stats.modified === 0) {
260
+ console.info(import_picocolors.default.green("\u2713 All docs are already up to date!"));
261
+ return;
262
+ }
263
+ const sortOrder = {
264
+ new: 0,
265
+ modified: 1,
266
+ identical: 2
267
+ };
268
+ filesToSync.sort((a, b) => sortOrder[a.status] - sortOrder[b.status]);
269
+ let syncedCount = 0;
270
+ for (const file of filesToSync) {
271
+ if (args.yes && file.status !== "identical") {
272
+ const targetDir = (0, import_node_path.join)(targetDocsDir);
273
+ if (!(0, import_node_fs.existsSync)(targetDir)) {
274
+ (0, import_node_fs.mkdirSync)(targetDir, {
275
+ recursive: true
276
+ });
277
+ }
278
+ const content = (0, import_node_fs.readFileSync)(file.sourcePath);
279
+ (0, import_node_fs.writeFileSync)(file.targetPath, content);
280
+ console.info(import_picocolors.default.green(` \u2713 ${file.name}`));
281
+ syncedCount++;
282
+ } else {
283
+ const wasSynced = await (0, import_sync.syncFileWithConfirmation)(file);
284
+ if (wasSynced) {
285
+ syncedCount++;
286
+ }
287
+ }
288
+ }
289
+ console.info();
290
+ console.info(import_picocolors.default.bold(import_picocolors.default.green(`\u2713 Complete: ${syncedCount} file(s) ejected`)));
291
+ console.info();
292
+ }
293
+ });
169
294
  const docsCommand = (0, import_citty.defineCommand)({
170
295
  meta: {
171
296
  name: "docs",
172
- description: "List and retrieve repository documentation"
297
+ description: "List and retrieve Takeout documentation"
173
298
  },
174
299
  subCommands: {
175
300
  list: listCommand,
176
301
  get: getCommand,
177
- path: pathCommand
302
+ path: pathCommand,
303
+ eject: ejectCommand
178
304
  }
179
305
  });
@@ -285,7 +285,7 @@ const onboardCommand = (0, import_citty.defineCommand)({
285
285
  console.info();
286
286
  console.info(" bun ios # build iOS dev app");
287
287
  console.info(" bun android # build Android dev app");
288
- console.info(" bun tko docs list # view repository docs");
288
+ console.info(" bun tko docs list # view Takeout docs");
289
289
  console.info();
290
290
  }
291
291
  if (startStep !== "production") {
@@ -690,7 +690,7 @@ async function setupUncloudDeployment(cwd) {
690
690
  console.info(import_picocolors.default.gray(" uc machine add --name server-2 root@IP"));
691
691
  console.info(import_picocolors.default.gray(" uc scale web 3 # run 3 instances"));
692
692
  console.info();
693
- console.info(import_picocolors.default.gray("view detailed guide: bun tko docs get deploy-uncloud"));
693
+ console.info(import_picocolors.default.gray("view detailed guide: bun tko docs read deployment-uncloud"));
694
694
  console.info(import_picocolors.default.gray("or see: docs/deployment-uncloud.md"));
695
695
  }
696
696
  async function setupSSTDeployment(cwd) {
@@ -37,9 +37,18 @@ __export(skills_exports, {
37
37
  });
38
38
  module.exports = __toCommonJS(skills_exports);
39
39
  var import_node_fs = require("node:fs");
40
+ var import_node_module = require("node:module");
40
41
  var import_node_path = require("node:path");
41
42
  var import_citty = require("citty");
42
43
  var import_picocolors = __toESM(require("picocolors"), 1);
44
+ const import_meta = {};
45
+ const require2 = (0, import_node_module.createRequire)(import_meta.url);
46
+ let DOCS_DIR;
47
+ try {
48
+ DOCS_DIR = (0, import_node_path.dirname)(require2.resolve("@take-out/docs/package.json"));
49
+ } catch {
50
+ DOCS_DIR = "";
51
+ }
43
52
  const SKILL_PREFIX = "takeout-";
44
53
  function hasSkillFrontmatter(content) {
45
54
  if (!content.startsWith("---")) return false;
@@ -62,6 +71,7 @@ function resolveLocalDocsDir(cwd) {
62
71
  }
63
72
  function collectAllDocs(cwd) {
64
73
  const docs = [];
74
+ const seen = /* @__PURE__ */new Set();
65
75
  const localDocsDir = resolveLocalDocsDir(cwd);
66
76
  if ((0, import_node_fs.existsSync)(localDocsDir)) {
67
77
  const files = (0, import_node_fs.readdirSync)(localDocsDir).filter(f => f.endsWith(".md"));
@@ -69,8 +79,23 @@ function collectAllDocs(cwd) {
69
79
  const name = file.replace(/\.md$/, "");
70
80
  docs.push({
71
81
  name,
72
- path: (0, import_node_path.join)(localDocsDir, file)
82
+ path: (0, import_node_path.join)(localDocsDir, file),
83
+ source: "local"
73
84
  });
85
+ seen.add(name);
86
+ }
87
+ }
88
+ if (DOCS_DIR && (0, import_node_fs.existsSync)(DOCS_DIR)) {
89
+ const files = (0, import_node_fs.readdirSync)(DOCS_DIR).filter(f => f.endsWith(".md"));
90
+ for (const file of files) {
91
+ const name = file.replace(/\.md$/, "");
92
+ if (!seen.has(name)) {
93
+ docs.push({
94
+ name,
95
+ path: (0, import_node_path.join)(DOCS_DIR, file),
96
+ source: "package"
97
+ });
98
+ }
74
99
  }
75
100
  }
76
101
  return docs;
@@ -140,7 +165,8 @@ async function generateDocSkills(cwd, clean) {
140
165
  if (!shouldCreate) continue;
141
166
  (0, import_node_fs.symlinkSync)(relativePath, skillFile);
142
167
  symlinked++;
143
- console.info(` ${import_picocolors.default.green("\u27F7")} ${skillName} ${import_picocolors.default.dim("(symlink)")}`);
168
+ const sourceLabel = doc.source === "local" ? import_picocolors.default.blue("local") : import_picocolors.default.dim("package");
169
+ console.info(` ${import_picocolors.default.green("\u27F7")} ${skillName} ${sourceLabel} ${import_picocolors.default.dim("(symlink)")}`);
144
170
  } else {
145
171
  if (!hasFrontmatter) {
146
172
  skipped++;
@@ -168,7 +194,7 @@ async function generateDocSkills(cwd, clean) {
168
194
  if (stat.isSymbolicLink()) {
169
195
  const linkTarget = (0, import_node_fs.readlinkSync)(skillFile);
170
196
  const resolvedTarget = (0, import_node_path.resolve)(skillDir, linkTarget);
171
- shouldUnlink = resolvedTarget.startsWith(`${localDocsDir}/`);
197
+ shouldUnlink = resolvedTarget.startsWith(`${localDocsDir}/`) || !!DOCS_DIR && resolvedTarget.startsWith(`${DOCS_DIR}/`);
172
198
  }
173
199
  } catch {}
174
200
  if (!shouldUnlink) continue;
@@ -40,18 +40,29 @@ var import_node_child_process = require("node:child_process");
40
40
  var import_node_fs = require("node:fs");
41
41
  var import_node_os = require("node:os");
42
42
  var import_node_path = require("node:path");
43
+ var import_node_url = require("node:url");
43
44
  var import_citty = require("citty");
44
45
  var import_picocolors = __toESM(require("picocolors"), 1);
45
46
  var import_prompts = require("../utils/prompts.cjs");
47
+ const import_meta = {};
46
48
  const UPSTREAM_REPO = "tamagui/takeout2";
47
49
  const UPSTREAM_REMOTE = "takeout-upstream";
48
50
  const TAKEOUT_FILE = ".takeout";
49
51
  function getSyncPrompt() {
50
- const promptPath = (0, import_node_path.join)(process.cwd(), "docs", "upgrade.md");
51
- if (!(0, import_node_fs.existsSync)(promptPath)) {
52
- throw new Error("Missing docs/upgrade.md. Copy it from https://github.com/tamagui/takeout2/blob/main/docs/upgrade.md and rerun `bun tko sync`.");
52
+ try {
53
+ const currentDir = (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
54
+ let monorepoRoot = currentDir;
55
+ while (monorepoRoot !== (0, import_node_path.parse)(monorepoRoot).root) {
56
+ const promptPath = (0, import_node_path.join)(monorepoRoot, "packages", "docs", "sync-prompt.md");
57
+ if ((0, import_node_fs.existsSync)(promptPath)) {
58
+ return (0, import_node_fs.readFileSync)(promptPath, "utf-8");
59
+ }
60
+ monorepoRoot = (0, import_node_path.dirname)(monorepoRoot);
61
+ }
62
+ throw new Error("Could not find sync-prompt.md in packages/docs");
63
+ } catch (error) {
64
+ throw new Error(`Could not load sync prompt: ${error instanceof Error ? error.message : "Unknown error"}`);
53
65
  }
54
- return (0, import_node_fs.readFileSync)(promptPath, "utf-8");
55
66
  }
56
67
  function checkToolAvailable(command) {
57
68
  try {