feishu-docs-cli 0.1.0-beta.10

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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +403 -0
  3. package/README.zh.md +402 -0
  4. package/bin/feishu-docs.js +8 -0
  5. package/dist/auth.d.ts +76 -0
  6. package/dist/auth.js +512 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/cli.d.ts +5 -0
  9. package/dist/cli.js +197 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/client.d.ts +28 -0
  12. package/dist/client.js +256 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/commands/authorize.d.ts +12 -0
  15. package/dist/commands/authorize.js +73 -0
  16. package/dist/commands/authorize.js.map +1 -0
  17. package/dist/commands/cat.d.ts +6 -0
  18. package/dist/commands/cat.js +159 -0
  19. package/dist/commands/cat.js.map +1 -0
  20. package/dist/commands/cp.d.ts +9 -0
  21. package/dist/commands/cp.js +70 -0
  22. package/dist/commands/cp.js.map +1 -0
  23. package/dist/commands/create.d.ts +6 -0
  24. package/dist/commands/create.js +120 -0
  25. package/dist/commands/create.js.map +1 -0
  26. package/dist/commands/delete.d.ts +6 -0
  27. package/dist/commands/delete.js +91 -0
  28. package/dist/commands/delete.js.map +1 -0
  29. package/dist/commands/info.d.ts +6 -0
  30. package/dist/commands/info.js +69 -0
  31. package/dist/commands/info.js.map +1 -0
  32. package/dist/commands/install-skill.d.ts +5 -0
  33. package/dist/commands/install-skill.js +28 -0
  34. package/dist/commands/install-skill.js.map +1 -0
  35. package/dist/commands/login.d.ts +10 -0
  36. package/dist/commands/login.js +93 -0
  37. package/dist/commands/login.js.map +1 -0
  38. package/dist/commands/ls.d.ts +6 -0
  39. package/dist/commands/ls.js +79 -0
  40. package/dist/commands/ls.js.map +1 -0
  41. package/dist/commands/mkdir.d.ts +6 -0
  42. package/dist/commands/mkdir.js +49 -0
  43. package/dist/commands/mkdir.js.map +1 -0
  44. package/dist/commands/mv.d.ts +9 -0
  45. package/dist/commands/mv.js +72 -0
  46. package/dist/commands/mv.js.map +1 -0
  47. package/dist/commands/read.d.ts +6 -0
  48. package/dist/commands/read.js +439 -0
  49. package/dist/commands/read.js.map +1 -0
  50. package/dist/commands/search.d.ts +7 -0
  51. package/dist/commands/search.js +92 -0
  52. package/dist/commands/search.js.map +1 -0
  53. package/dist/commands/share.d.ts +13 -0
  54. package/dist/commands/share.js +266 -0
  55. package/dist/commands/share.js.map +1 -0
  56. package/dist/commands/spaces.d.ts +6 -0
  57. package/dist/commands/spaces.js +43 -0
  58. package/dist/commands/spaces.js.map +1 -0
  59. package/dist/commands/tree.d.ts +6 -0
  60. package/dist/commands/tree.js +101 -0
  61. package/dist/commands/tree.js.map +1 -0
  62. package/dist/commands/update.d.ts +9 -0
  63. package/dist/commands/update.js +217 -0
  64. package/dist/commands/update.js.map +1 -0
  65. package/dist/commands/wiki.d.ts +6 -0
  66. package/dist/commands/wiki.js +286 -0
  67. package/dist/commands/wiki.js.map +1 -0
  68. package/dist/parser/block-types.d.ts +141 -0
  69. package/dist/parser/block-types.js +167 -0
  70. package/dist/parser/block-types.js.map +1 -0
  71. package/dist/parser/blocks-to-md.d.ts +26 -0
  72. package/dist/parser/blocks-to-md.js +666 -0
  73. package/dist/parser/blocks-to-md.js.map +1 -0
  74. package/dist/parser/text-elements.d.ts +13 -0
  75. package/dist/parser/text-elements.js +91 -0
  76. package/dist/parser/text-elements.js.map +1 -0
  77. package/dist/scopes.d.ts +21 -0
  78. package/dist/scopes.js +48 -0
  79. package/dist/scopes.js.map +1 -0
  80. package/dist/services/block-writer.d.ts +29 -0
  81. package/dist/services/block-writer.js +131 -0
  82. package/dist/services/block-writer.js.map +1 -0
  83. package/dist/services/doc-blocks.d.ts +8 -0
  84. package/dist/services/doc-blocks.js +26 -0
  85. package/dist/services/doc-blocks.js.map +1 -0
  86. package/dist/services/markdown-convert.d.ts +68 -0
  87. package/dist/services/markdown-convert.js +217 -0
  88. package/dist/services/markdown-convert.js.map +1 -0
  89. package/dist/services/wiki-nodes.d.ts +20 -0
  90. package/dist/services/wiki-nodes.js +46 -0
  91. package/dist/services/wiki-nodes.js.map +1 -0
  92. package/dist/types/index.d.ts +236 -0
  93. package/dist/types/index.js +5 -0
  94. package/dist/types/index.js.map +1 -0
  95. package/dist/utils/document-resolver.d.ts +26 -0
  96. package/dist/utils/document-resolver.js +46 -0
  97. package/dist/utils/document-resolver.js.map +1 -0
  98. package/dist/utils/drive-types.d.ts +4 -0
  99. package/dist/utils/drive-types.js +16 -0
  100. package/dist/utils/drive-types.js.map +1 -0
  101. package/dist/utils/errors.d.ts +23 -0
  102. package/dist/utils/errors.js +114 -0
  103. package/dist/utils/errors.js.map +1 -0
  104. package/dist/utils/member.d.ts +11 -0
  105. package/dist/utils/member.js +30 -0
  106. package/dist/utils/member.js.map +1 -0
  107. package/dist/utils/scope-prompt.d.ts +39 -0
  108. package/dist/utils/scope-prompt.js +134 -0
  109. package/dist/utils/scope-prompt.js.map +1 -0
  110. package/dist/utils/url-parser.d.ts +5 -0
  111. package/dist/utils/url-parser.js +55 -0
  112. package/dist/utils/url-parser.js.map +1 -0
  113. package/dist/utils/validate.d.ts +8 -0
  114. package/dist/utils/validate.js +15 -0
  115. package/dist/utils/validate.js.map +1 -0
  116. package/dist/utils/version.d.ts +12 -0
  117. package/dist/utils/version.js +128 -0
  118. package/dist/utils/version.js.map +1 -0
  119. package/package.json +53 -0
  120. package/skills/feishu-docs/SKILL.md +194 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Unified error handling for feishu-docs CLI.
3
+ *
4
+ * Exit codes:
5
+ * 0 - success
6
+ * 1 - invalid args / file not found
7
+ * 2 - auth failure
8
+ * 3 - API error
9
+ */
10
+ const ERROR_MAP = {
11
+ INVALID_ARGS: { exit: 1, type: "INVALID_ARGS" },
12
+ FILE_NOT_FOUND: { exit: 1, type: "FILE_NOT_FOUND" },
13
+ AUTH_REQUIRED: { exit: 2, type: "AUTH_REQUIRED" },
14
+ TOKEN_EXPIRED: { exit: 2, type: "TOKEN_EXPIRED" },
15
+ PERMISSION_DENIED: { exit: 2, type: "PERMISSION_DENIED" },
16
+ SCOPE_MISSING: { exit: 2, type: "SCOPE_MISSING" },
17
+ NOT_FOUND: { exit: 3, type: "NOT_FOUND" },
18
+ NOT_SUPPORTED: { exit: 3, type: "NOT_SUPPORTED" },
19
+ RATE_LIMITED: { exit: 3, type: "RATE_LIMITED" },
20
+ API_ERROR: { exit: 3, type: "API_ERROR" },
21
+ };
22
+ export class CliError extends Error {
23
+ exitCode;
24
+ errorType;
25
+ apiCode;
26
+ retryable;
27
+ recovery;
28
+ /** Scope names from API permission_violations (only for SCOPE_MISSING). */
29
+ missingScopes;
30
+ constructor(type, message, { apiCode, retryable = false, recovery, missingScopes, } = {}) {
31
+ super(message);
32
+ this.name = "CliError";
33
+ const info = ERROR_MAP[type] || ERROR_MAP.API_ERROR;
34
+ this.exitCode = info.exit;
35
+ this.errorType = info.type;
36
+ this.apiCode = apiCode;
37
+ this.retryable = retryable;
38
+ this.recovery = recovery;
39
+ this.missingScopes = missingScopes;
40
+ }
41
+ }
42
+ export function formatError(err, json = false) {
43
+ if (err instanceof CliError) {
44
+ if (json) {
45
+ return JSON.stringify({
46
+ success: false,
47
+ error: {
48
+ type: err.errorType,
49
+ message: err.message,
50
+ api_code: err.apiCode,
51
+ retryable: err.retryable,
52
+ recovery: err.recovery,
53
+ ...(err.missingScopes &&
54
+ err.missingScopes.length > 0 && {
55
+ missing_scopes: err.missingScopes,
56
+ }),
57
+ },
58
+ });
59
+ }
60
+ const code = err.apiCode ? ` (code: ${err.apiCode})` : "";
61
+ return `feishu-docs: error: ${err.message}${code}`;
62
+ }
63
+ const safeMessage = err.message || "未知错误";
64
+ if (json) {
65
+ return JSON.stringify({
66
+ success: false,
67
+ error: {
68
+ type: "UNKNOWN",
69
+ message: safeMessage,
70
+ retryable: false,
71
+ },
72
+ });
73
+ }
74
+ return `feishu-docs: error: ${safeMessage}`;
75
+ }
76
+ export function handleError(err, json = false) {
77
+ process.stderr.write(formatError(err, json) + "\n");
78
+ const exitCode = err instanceof CliError ? err.exitCode : 1;
79
+ process.exit(exitCode);
80
+ }
81
+ export function mapApiError(err) {
82
+ const e = err;
83
+ const code = e?.code ?? e?.response?.data?.code ?? e?.response?.code;
84
+ const msg = e?.msg ?? e?.response?.data?.msg ?? e?.response?.msg ?? e?.message;
85
+ if (code === 131006) {
86
+ return new CliError("PERMISSION_DENIED", `权限不足,请确认文档已对当前用户开放访问权限`, {
87
+ apiCode: code,
88
+ recovery: "请求文档拥有者授予访问权限,或使用 --auth user 切换身份",
89
+ });
90
+ }
91
+ if (code === 131008) {
92
+ // 131008 is context-dependent: "permission denied" for node ops, "already exist" for member ops.
93
+ // Preserve apiCode so callers can distinguish at the call site.
94
+ return new CliError("PERMISSION_DENIED", msg || "权限不足或资源已存在", {
95
+ apiCode: code,
96
+ recovery: "请求文档拥有者授予访问权限,或使用 --auth user 切换身份",
97
+ });
98
+ }
99
+ if (code === 131001 || code === 131002) {
100
+ return new CliError("NOT_FOUND", `文档不存在或已被删除`, {
101
+ apiCode: code,
102
+ });
103
+ }
104
+ if (code === 99991400 || code === 99991663) {
105
+ return new CliError("TOKEN_EXPIRED", `认证已过期,请重新运行 feishu-docs login`, {
106
+ apiCode: code,
107
+ recovery: "运行 feishu-docs login 重新认证",
108
+ });
109
+ }
110
+ // 99991672 / 99991679 are scope errors — handled directly in fetchWithAuth
111
+ // before mapApiError is called. If they reach here, treat as generic API error.
112
+ return new CliError("API_ERROR", msg || "未知 API 错误", { apiCode: code });
113
+ }
114
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared member ID utilities for wiki and share commands.
3
+ */
4
+ /**
5
+ * Validate member_id format to prevent path injection.
6
+ */
7
+ export declare function validateMemberId(memberId: string): void;
8
+ /**
9
+ * Auto-detect member_type from member identifier.
10
+ */
11
+ export declare function detectMemberType(memberId: string): string;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared member ID utilities for wiki and share commands.
3
+ */
4
+ import { CliError } from "./errors.js";
5
+ const MEMBER_ID_RE = /^[A-Za-z0-9@._\-+]{1,200}$/;
6
+ /**
7
+ * Validate member_id format to prevent path injection.
8
+ */
9
+ export function validateMemberId(memberId) {
10
+ if (!MEMBER_ID_RE.test(memberId)) {
11
+ throw new CliError("INVALID_ARGS", `无效的 member_id 格式: ${memberId}`);
12
+ }
13
+ }
14
+ /**
15
+ * Auto-detect member_type from member identifier.
16
+ */
17
+ export function detectMemberType(memberId) {
18
+ if (memberId.includes("@"))
19
+ return "email";
20
+ if (memberId.startsWith("ou_"))
21
+ return "openid";
22
+ if (memberId.startsWith("on_"))
23
+ return "unionid";
24
+ if (memberId.startsWith("oc_"))
25
+ return "openchat";
26
+ if (memberId.startsWith("od_"))
27
+ return "opendepartmentid";
28
+ return "userid";
29
+ }
30
+ //# sourceMappingURL=member.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"member.js","sourceRoot":"","sources":["../../src/utils/member.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAElD;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,qBAAqB,QAAQ,EAAE,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IAC3C,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChD,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACjD,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,UAAU,CAAC;IAClD,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,kBAAkB,CAAC;IAC1D,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Reactive scope authorization: prompt and recovery utilities.
3
+ *
4
+ * Instead of pre-flight scope checks, this module provides:
5
+ *
6
+ * - `promptScopeAuth()` — Interactive OAuth prompt for missing scopes.
7
+ * - `withScopeRecovery()` — Wrapper that catches SCOPE_MISSING errors,
8
+ * prompts the user (if interactive), and retries the operation once.
9
+ *
10
+ * The Feishu API is the source of truth for required scopes. When an API
11
+ * call fails with 99991672 (app scope) or 99991679 (user scope), `fetchWithAuth`
12
+ * throws `CliError("SCOPE_MISSING", { missingScopes })`. This module handles
13
+ * the recovery.
14
+ */
15
+ import type { GlobalOpts } from "../types/index.js";
16
+ /**
17
+ * Prompt the user to authorize missing scopes interactively.
18
+ *
19
+ * 1. Tells the user which scopes are missing.
20
+ * 2. Reminds them to enable the scopes in the Feishu developer console.
21
+ * 3. Asks whether to run the OAuth flow now.
22
+ *
23
+ * Returns `true` if authorization succeeded, `false` otherwise.
24
+ * Always returns `false` in non-interactive contexts (JSON mode, non-TTY).
25
+ */
26
+ export declare function promptScopeAuth(missingScopes: string[], globalOpts: GlobalOpts): Promise<boolean>;
27
+ /**
28
+ * Wrap an async operation with reactive scope error recovery.
29
+ *
30
+ * When the operation throws `CliError("SCOPE_MISSING")`:
31
+ * - If interactive (TTY + non-JSON) and scopes are known: prompt → retry once.
32
+ * - Otherwise: re-throw with a clear recovery hint.
33
+ *
34
+ * @param fallbackScopes Scope names to use when the API doesn't include
35
+ * `permission_violations` (older APIs like search). When the error has
36
+ * empty `missingScopes` but `fallbackScopes` is provided, these are used
37
+ * for the authorization prompt.
38
+ */
39
+ export declare function withScopeRecovery<T>(fn: () => Promise<T>, globalOpts: GlobalOpts, fallbackScopes?: string[]): Promise<T>;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Reactive scope authorization: prompt and recovery utilities.
3
+ *
4
+ * Instead of pre-flight scope checks, this module provides:
5
+ *
6
+ * - `promptScopeAuth()` — Interactive OAuth prompt for missing scopes.
7
+ * - `withScopeRecovery()` — Wrapper that catches SCOPE_MISSING errors,
8
+ * prompts the user (if interactive), and retries the operation once.
9
+ *
10
+ * The Feishu API is the source of truth for required scopes. When an API
11
+ * call fails with 99991672 (app scope) or 99991679 (user scope), `fetchWithAuth`
12
+ * throws `CliError("SCOPE_MISSING", { missingScopes })`. This module handles
13
+ * the recovery.
14
+ */
15
+ import * as readline from "node:readline";
16
+ import { oauthLogin, loadTokens } from "../auth.js";
17
+ import { CliError } from "./errors.js";
18
+ import { BASE_SCOPES, mergeScopes, buildScopeHint } from "../scopes.js";
19
+ /**
20
+ * Ask a yes/no question on stderr, read from stdin.
21
+ * Returns true only if user answers "y" or "yes".
22
+ */
23
+ function askYesNo(question) {
24
+ return new Promise((resolve) => {
25
+ const rl = readline.createInterface({
26
+ input: process.stdin,
27
+ output: process.stderr,
28
+ });
29
+ rl.question(`${question} (y/N) `, (answer) => {
30
+ rl.close();
31
+ resolve(["y", "yes"].includes(answer.trim().toLowerCase()));
32
+ });
33
+ });
34
+ }
35
+ /**
36
+ * Prompt the user to authorize missing scopes interactively.
37
+ *
38
+ * 1. Tells the user which scopes are missing.
39
+ * 2. Reminds them to enable the scopes in the Feishu developer console.
40
+ * 3. Asks whether to run the OAuth flow now.
41
+ *
42
+ * Returns `true` if authorization succeeded, `false` otherwise.
43
+ * Always returns `false` in non-interactive contexts (JSON mode, non-TTY).
44
+ */
45
+ export async function promptScopeAuth(missingScopes, globalOpts) {
46
+ // Non-interactive: skip prompt
47
+ if (globalOpts.json || !process.stdin.isTTY) {
48
+ return false;
49
+ }
50
+ const appId = process.env.FEISHU_APP_ID;
51
+ const appSecret = process.env.FEISHU_APP_SECRET;
52
+ if (!appId || !appSecret)
53
+ return false;
54
+ // Skip interactive OAuth when using env-var token — re-auth would save to file
55
+ // but createClient still prefers the env token, so retry would fail again.
56
+ if (process.env.FEISHU_USER_TOKEN)
57
+ return false;
58
+ const scopeList = missingScopes.join(", ");
59
+ const scopeStr = missingScopes.join(" ");
60
+ process.stderr.write(`\nfeishu-docs: 缺少以下权限: ${scopeList}\n` +
61
+ `请先确认已在飞书开发者后台 (https://open.feishu.cn/app) 为应用开通了对应的 API 权限。\n` +
62
+ `开通后,需要通过 OAuth 授权将权限授予当前用户(用户已授予的权限会累积保留)。\n` +
63
+ `手动授权命令: feishu-docs authorize --scope "${scopeStr}"\n\n`);
64
+ const yes = await askYesNo("是否现在申请授予权限?");
65
+ if (!yes)
66
+ return false;
67
+ // Merge missing scopes with current grants and re-run OAuth
68
+ const stored = await loadTokens();
69
+ const currentScopes = stored?.tokens?.scope
70
+ ? stored.tokens.scope.split(/\s+/).filter(Boolean)
71
+ : [...BASE_SCOPES];
72
+ const merged = mergeScopes(currentScopes, missingScopes);
73
+ try {
74
+ await oauthLogin(appId, {
75
+ scope: merged.join(" "),
76
+ appSecret,
77
+ useLark: globalOpts.lark,
78
+ });
79
+ process.stderr.write("feishu-docs: 授权成功!\n\n");
80
+ return true;
81
+ }
82
+ catch (err) {
83
+ const detail = err instanceof Error ? err.message : String(err);
84
+ process.stderr.write(`feishu-docs: 授权失败: ${detail}\n\n`);
85
+ return false;
86
+ }
87
+ }
88
+ /**
89
+ * Wrap an async operation with reactive scope error recovery.
90
+ *
91
+ * When the operation throws `CliError("SCOPE_MISSING")`:
92
+ * - If interactive (TTY + non-JSON) and scopes are known: prompt → retry once.
93
+ * - Otherwise: re-throw with a clear recovery hint.
94
+ *
95
+ * @param fallbackScopes Scope names to use when the API doesn't include
96
+ * `permission_violations` (older APIs like search). When the error has
97
+ * empty `missingScopes` but `fallbackScopes` is provided, these are used
98
+ * for the authorization prompt.
99
+ */
100
+ export async function withScopeRecovery(fn, globalOpts, fallbackScopes) {
101
+ try {
102
+ return await fn();
103
+ }
104
+ catch (err) {
105
+ if (!(err instanceof CliError) || err.errorType !== "SCOPE_MISSING") {
106
+ throw err;
107
+ }
108
+ const apiScopes = err.missingScopes ?? [];
109
+ const scopes = apiScopes.length > 0 ? apiScopes : (fallbackScopes ?? []);
110
+ // No scopes from API or fallback → can't prompt meaningfully, just throw
111
+ if (scopes.length === 0) {
112
+ throw err;
113
+ }
114
+ // Tenant mode: OAuth produces a user token which tenant auth ignores,
115
+ // so interactive recovery would be pointless — just throw with a hint.
116
+ if (globalOpts.auth === "tenant") {
117
+ throw new CliError("AUTH_REQUIRED", buildScopeHint(scopes), {
118
+ apiCode: err.apiCode,
119
+ missingScopes: scopes,
120
+ });
121
+ }
122
+ // Try interactive recovery
123
+ const authorized = await promptScopeAuth(scopes, globalOpts);
124
+ if (!authorized) {
125
+ throw new CliError("AUTH_REQUIRED", buildScopeHint(scopes), {
126
+ apiCode: err.apiCode,
127
+ missingScopes: scopes,
128
+ });
129
+ }
130
+ // Retry once — if it fails again, let the error propagate
131
+ return await fn();
132
+ }
133
+ }
134
+ //# sourceMappingURL=scope-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-prompt.js","sourceRoot":"","sources":["../../src/utils/scope-prompt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Parse Feishu/Lark URL or raw token into { type, token }.
3
+ */
4
+ import { ParsedDoc } from "../types/index.js";
5
+ export declare function parseDocUrl(input: unknown): ParsedDoc;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Parse Feishu/Lark URL or raw token into { type, token }.
3
+ */
4
+ import { CliError } from "./errors.js";
5
+ const FEISHU_DOMAINS = /(?:^|\.)(feishu\.cn|larksuite\.com|larkoffice\.com)$/;
6
+ const PATH_PATTERNS = [
7
+ { pattern: /^\/wiki\/([A-Za-z0-9]+)/, type: "wiki" },
8
+ { pattern: /^\/docx\/([A-Za-z0-9]+)/, type: "docx" },
9
+ { pattern: /^\/doc\/([A-Za-z0-9]+)/, type: "doc" },
10
+ { pattern: /^\/sheets\/([A-Za-z0-9]+)/, type: "sheet" },
11
+ { pattern: /^\/base\/([A-Za-z0-9]+)/, type: "bitable" },
12
+ ];
13
+ const RAW_TOKEN_RE = /^[A-Za-z][A-Za-z0-9]{19,}$/;
14
+ export function parseDocUrl(input) {
15
+ if (!input) {
16
+ throw new CliError("INVALID_ARGS", "缺少文档 URL 或 token", {
17
+ recovery: "请提供飞书文档 URL 或 token",
18
+ });
19
+ }
20
+ const trimmed = input.trim();
21
+ // Try as URL
22
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
23
+ let url;
24
+ try {
25
+ url = new URL(trimmed);
26
+ }
27
+ catch {
28
+ throw new CliError("INVALID_ARGS", `无效的 URL: ${trimmed}`, {
29
+ recovery: "请提供有效的飞书文档 URL",
30
+ });
31
+ }
32
+ if (!FEISHU_DOMAINS.test(url.hostname)) {
33
+ throw new CliError("INVALID_ARGS", "不支持的域名,请使用飞书/Lark URL", {
34
+ recovery: "支持的域名: feishu.cn, larksuite.com, larkoffice.com",
35
+ });
36
+ }
37
+ for (const { pattern, type } of PATH_PATTERNS) {
38
+ const match = url.pathname.match(pattern);
39
+ if (match) {
40
+ const token = match[1];
41
+ if (!token) {
42
+ throw new CliError("INVALID_ARGS", "URL 中缺少文档 token");
43
+ }
44
+ return { type, token };
45
+ }
46
+ }
47
+ throw new CliError("INVALID_ARGS", `无法识别的 URL 路径: ${url.pathname}`, { recovery: "支持的路径: /wiki/, /docx/, /doc/, /sheets/, /base/" });
48
+ }
49
+ // Try as raw token
50
+ if (RAW_TOKEN_RE.test(trimmed)) {
51
+ return { type: "unknown", token: trimmed };
52
+ }
53
+ throw new CliError("INVALID_ARGS", `无法识别的输入: ${trimmed}。请输入飞书 URL 或文档 token`, { recovery: "请提供飞书文档 URL 或至少 20 位的文档 token" });
54
+ }
55
+ //# sourceMappingURL=url-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-parser.js","sourceRoot":"","sources":["../../src/utils/url-parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,cAAc,GAAG,sDAAsD,CAAC;AAE9E,MAAM,aAAa,GAA8C;IAC/D,EAAE,OAAO,EAAE,yBAAyB,EAAE,IAAI,EAAE,MAAM,EAAE;IACpD,EAAE,OAAO,EAAE,yBAAyB,EAAE,IAAI,EAAE,MAAM,EAAE;IACpD,EAAE,OAAO,EAAE,wBAAwB,EAAE,IAAI,EAAE,KAAK,EAAE;IAClD,EAAE,OAAO,EAAE,2BAA2B,EAAE,IAAI,EAAE,OAAO,EAAE;IACvD,EAAE,OAAO,EAAE,yBAAyB,EAAE,IAAI,EAAE,SAAS,EAAE;CACxD,CAAC;AAEF,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAElD,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,kBAAkB,EAAE;YACrD,QAAQ,EAAE,qBAAqB;SAChC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAI,KAAgB,CAAC,IAAI,EAAE,CAAC;IAEzC,aAAa;IACb,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpE,IAAI,GAAQ,CAAC;QACb,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,YAAY,OAAO,EAAE,EAAE;gBACxD,QAAQ,EAAE,gBAAgB;aAC3B,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,uBAAuB,EAAE;gBAC1D,QAAQ,EAAE,iDAAiD;aAC5D,CAAC,CAAC;QACL,CAAC;QAED,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,aAAa,EAAE,CAAC;YAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC1C,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACvB,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;gBACxD,CAAC;gBACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YACzB,CAAC;QACH,CAAC;QAED,MAAM,IAAI,QAAQ,CAChB,cAAc,EACd,iBAAiB,GAAG,CAAC,QAAQ,EAAE,EAC/B,EAAE,QAAQ,EAAE,gDAAgD,EAAE,CAC/D,CAAC;IACJ,CAAC;IAED,mBAAmB;IACnB,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,IAAI,QAAQ,CAChB,cAAc,EACd,YAAY,OAAO,sBAAsB,EACzC,EAAE,QAAQ,EAAE,+BAA+B,EAAE,CAC9C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Input validation utilities for API path parameters.
3
+ */
4
+ /**
5
+ * Validate a space_id or token before interpolating into URL paths.
6
+ * Prevents path traversal via malformed IDs.
7
+ */
8
+ export declare function validateToken(value: unknown, label?: string): void;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Input validation utilities for API path parameters.
3
+ */
4
+ import { CliError } from "./errors.js";
5
+ const TOKEN_RE = /^[A-Za-z0-9_\-]{1,100}$/;
6
+ /**
7
+ * Validate a space_id or token before interpolating into URL paths.
8
+ * Prevents path traversal via malformed IDs.
9
+ */
10
+ export function validateToken(value, label = "token") {
11
+ if (!value || !TOKEN_RE.test(value)) {
12
+ throw new CliError("INVALID_ARGS", `无效的 ${label} 格式: ${value}`);
13
+ }
14
+ }
15
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,QAAQ,GAAG,yBAAyB,CAAC;AAE3C;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,QAAgB,OAAO;IACnE,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAe,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,OAAO,KAAK,QAAQ,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Version display and update check utilities.
3
+ */
4
+ /**
5
+ * Read the local package version from package.json.
6
+ */
7
+ export declare function getLocalVersion(): string;
8
+ /**
9
+ * Check for updates and print a notice if a newer version is available.
10
+ * This is non-blocking and never throws — update check failures are silent.
11
+ */
12
+ export declare function checkForUpdates(): Promise<void>;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Version display and update check utilities.
3
+ */
4
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
7
+ import { createRequire } from "node:module";
8
+ const PACKAGE_NAME = "feishu-docs-cli";
9
+ const CONFIG_DIR = join(homedir(), ".feishu-docs");
10
+ const UPDATE_CHECK_FILE = join(CONFIG_DIR, ".update-check.json");
11
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
12
+ /**
13
+ * Read the local package version from package.json.
14
+ */
15
+ export function getLocalVersion() {
16
+ const require = createRequire(import.meta.url);
17
+ const pkg = require("../../package.json");
18
+ return pkg.version;
19
+ }
20
+ /**
21
+ * Fetch the latest version from npm registry.
22
+ */
23
+ async function fetchLatestVersion() {
24
+ const controller = new AbortController();
25
+ const timeoutId = setTimeout(() => controller.abort(), 5_000);
26
+ try {
27
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { signal: controller.signal });
28
+ if (!res.ok)
29
+ return null;
30
+ const data = (await res.json());
31
+ return data.version || null;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ finally {
37
+ clearTimeout(timeoutId);
38
+ }
39
+ }
40
+ /**
41
+ * Load the update check state from disk.
42
+ */
43
+ async function loadCheckState() {
44
+ try {
45
+ const raw = await readFile(UPDATE_CHECK_FILE, "utf-8");
46
+ return JSON.parse(raw);
47
+ }
48
+ catch {
49
+ return { lastCheck: 0 };
50
+ }
51
+ }
52
+ /**
53
+ * Save the update check state to disk.
54
+ */
55
+ async function saveCheckState(state) {
56
+ await mkdir(CONFIG_DIR, { recursive: true });
57
+ await writeFile(UPDATE_CHECK_FILE, JSON.stringify(state));
58
+ }
59
+ /**
60
+ * Compare two semver strings. Returns:
61
+ * 1 if a > b, -1 if a < b, 0 if equal.
62
+ */
63
+ function compareSemver(a, b) {
64
+ const pa = a.replace(/^v/, "").split(/[-.]/).map((s) => {
65
+ const n = Number(s);
66
+ return isNaN(n) ? s : n;
67
+ });
68
+ const pb = b.replace(/^v/, "").split(/[-.]/).map((s) => {
69
+ const n = Number(s);
70
+ return isNaN(n) ? s : n;
71
+ });
72
+ const len = Math.max(pa.length, pb.length);
73
+ for (let i = 0; i < len; i++) {
74
+ const va = pa[i] ?? 0;
75
+ const vb = pb[i] ?? 0;
76
+ // A pre-release tag (string) is less than a release (number absent)
77
+ if (typeof va === "string" && typeof vb === "number")
78
+ return -1;
79
+ if (typeof va === "number" && typeof vb === "string")
80
+ return 1;
81
+ if (va < vb)
82
+ return -1;
83
+ if (va > vb)
84
+ return 1;
85
+ }
86
+ return 0;
87
+ }
88
+ /**
89
+ * Check for updates and print a notice if a newer version is available.
90
+ * This is non-blocking and never throws — update check failures are silent.
91
+ */
92
+ export async function checkForUpdates() {
93
+ try {
94
+ const state = await loadCheckState();
95
+ const now = Date.now();
96
+ // Skip if checked recently
97
+ if (now - state.lastCheck < CHECK_INTERVAL_MS) {
98
+ // Still show cached notice if a newer version was found last time
99
+ if (state.latestVersion) {
100
+ const local = getLocalVersion();
101
+ if (compareSemver(state.latestVersion, local) > 0) {
102
+ printUpdateNotice(local, state.latestVersion);
103
+ }
104
+ }
105
+ return;
106
+ }
107
+ // Fetch latest version from npm (with timeout)
108
+ const latest = await fetchLatestVersion();
109
+ const newState = {
110
+ lastCheck: now,
111
+ latestVersion: latest || undefined,
112
+ };
113
+ await saveCheckState(newState);
114
+ if (latest) {
115
+ const local = getLocalVersion();
116
+ if (compareSemver(latest, local) > 0) {
117
+ printUpdateNotice(local, latest);
118
+ }
119
+ }
120
+ }
121
+ catch {
122
+ // Never block CLI usage due to update check failure
123
+ }
124
+ }
125
+ function printUpdateNotice(current, latest) {
126
+ process.stderr.write(`feishu-docs: 发现新版本 ${latest}(当前 ${current}),运行 npm update -g ${PACKAGE_NAME} 更新\n`);
127
+ }
128
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,YAAY,GAAG,iBAAiB,CAAC;AACvC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;AACnD,MAAM,iBAAiB,GAAG,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC;AACjE,MAAM,iBAAiB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW;AAO1D;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAAwB,CAAC;IACjE,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB;IAC/B,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CACrB,8BAA8B,YAAY,SAAS,EACnD,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAC9B,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyB,CAAC;QACxD,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,SAAS,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,cAAc;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqB,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IAC1B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,cAAc,CAAC,KAAuB;IACnD,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,MAAM,SAAS,CAAC,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,CAAS,EAAE,CAAS;IACzC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACrD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACrD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,oEAAoE;QACpE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC,CAAC;QAChE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC;QAC/D,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC,CAAC;QACvB,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,cAAc,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,2BAA2B;QAC3B,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,GAAG,iBAAiB,EAAE,CAAC;YAC9C,kEAAkE;YAClE,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;gBACxB,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;gBAChC,IAAI,aAAa,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;oBAClD,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;gBAChD,CAAC;YACH,CAAC;YACD,OAAO;QACT,CAAC;QAED,+CAA+C;QAC/C,MAAM,MAAM,GAAG,MAAM,kBAAkB,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAqB;YACjC,SAAS,EAAE,GAAG;YACd,aAAa,EAAE,MAAM,IAAI,SAAS;SACnC,CAAC;QACF,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;QAE/B,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;YAChC,IAAI,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrC,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAe,EAAE,MAAc;IACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,sBAAsB,MAAM,OAAO,OAAO,sBAAsB,YAAY,OAAO,CACpF,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "feishu-docs-cli",
3
+ "version": "0.1.0-beta.10",
4
+ "description": "CLI tool for AI Agents to read/write Feishu docs via shell commands",
5
+ "type": "module",
6
+ "bin": {
7
+ "feishu-docs": "./bin/feishu-docs.js"
8
+ },
9
+ "main": "./dist/cli.js",
10
+ "types": "./dist/cli.d.ts",
11
+ "files": [
12
+ "bin/",
13
+ "dist/",
14
+ "skills/",
15
+ "README.md",
16
+ "README.zh.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "build:check": "tsc --noEmit",
22
+ "pretest": "npm run build:check",
23
+ "test": "tsx --test test/*.test.ts",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/cliff-byte/feishu-docs-cli.git"
29
+ },
30
+ "keywords": [
31
+ "feishu",
32
+ "lark",
33
+ "cli",
34
+ "docs",
35
+ "ai-agent",
36
+ "claude",
37
+ "codex"
38
+ ],
39
+ "author": "cliff-byte",
40
+ "license": "MIT",
41
+ "bugs": {
42
+ "url": "https://github.com/cliff-byte/feishu-docs-cli/issues"
43
+ },
44
+ "homepage": "https://github.com/cliff-byte/feishu-docs-cli#readme",
45
+ "engines": {
46
+ "node": ">=18.3.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^25.5.0",
50
+ "tsx": "^4.21.0",
51
+ "typescript": "^5.9.3"
52
+ }
53
+ }