feishu-docs-cli 0.1.0-beta.2

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 (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +333 -0
  3. package/README.zh.md +332 -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 +166 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/client.d.ts +42 -0
  12. package/dist/client.js +269 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/commands/cat.d.ts +6 -0
  15. package/dist/commands/cat.js +151 -0
  16. package/dist/commands/cat.js.map +1 -0
  17. package/dist/commands/create.d.ts +6 -0
  18. package/dist/commands/create.js +120 -0
  19. package/dist/commands/create.js.map +1 -0
  20. package/dist/commands/delete.d.ts +6 -0
  21. package/dist/commands/delete.js +89 -0
  22. package/dist/commands/delete.js.map +1 -0
  23. package/dist/commands/info.d.ts +6 -0
  24. package/dist/commands/info.js +69 -0
  25. package/dist/commands/info.js.map +1 -0
  26. package/dist/commands/login.d.ts +10 -0
  27. package/dist/commands/login.js +90 -0
  28. package/dist/commands/login.js.map +1 -0
  29. package/dist/commands/ls.d.ts +6 -0
  30. package/dist/commands/ls.js +76 -0
  31. package/dist/commands/ls.js.map +1 -0
  32. package/dist/commands/read.d.ts +6 -0
  33. package/dist/commands/read.js +404 -0
  34. package/dist/commands/read.js.map +1 -0
  35. package/dist/commands/search.d.ts +7 -0
  36. package/dist/commands/search.js +87 -0
  37. package/dist/commands/search.js.map +1 -0
  38. package/dist/commands/share.d.ts +13 -0
  39. package/dist/commands/share.js +210 -0
  40. package/dist/commands/share.js.map +1 -0
  41. package/dist/commands/spaces.d.ts +6 -0
  42. package/dist/commands/spaces.js +43 -0
  43. package/dist/commands/spaces.js.map +1 -0
  44. package/dist/commands/tree.d.ts +6 -0
  45. package/dist/commands/tree.js +101 -0
  46. package/dist/commands/tree.js.map +1 -0
  47. package/dist/commands/update.d.ts +9 -0
  48. package/dist/commands/update.js +211 -0
  49. package/dist/commands/update.js.map +1 -0
  50. package/dist/commands/wiki.d.ts +6 -0
  51. package/dist/commands/wiki.js +284 -0
  52. package/dist/commands/wiki.js.map +1 -0
  53. package/dist/parser/block-types.d.ts +141 -0
  54. package/dist/parser/block-types.js +167 -0
  55. package/dist/parser/block-types.js.map +1 -0
  56. package/dist/parser/blocks-to-md.d.ts +26 -0
  57. package/dist/parser/blocks-to-md.js +576 -0
  58. package/dist/parser/blocks-to-md.js.map +1 -0
  59. package/dist/parser/text-elements.d.ts +13 -0
  60. package/dist/parser/text-elements.js +91 -0
  61. package/dist/parser/text-elements.js.map +1 -0
  62. package/dist/services/block-writer.d.ts +30 -0
  63. package/dist/services/block-writer.js +131 -0
  64. package/dist/services/block-writer.js.map +1 -0
  65. package/dist/services/doc-blocks.d.ts +9 -0
  66. package/dist/services/doc-blocks.js +30 -0
  67. package/dist/services/doc-blocks.js.map +1 -0
  68. package/dist/services/markdown-convert.d.ts +51 -0
  69. package/dist/services/markdown-convert.js +109 -0
  70. package/dist/services/markdown-convert.js.map +1 -0
  71. package/dist/services/wiki-nodes.d.ts +21 -0
  72. package/dist/services/wiki-nodes.js +47 -0
  73. package/dist/services/wiki-nodes.js.map +1 -0
  74. package/dist/types/index.d.ts +234 -0
  75. package/dist/types/index.js +5 -0
  76. package/dist/types/index.js.map +1 -0
  77. package/dist/utils/document-resolver.d.ts +28 -0
  78. package/dist/utils/document-resolver.js +47 -0
  79. package/dist/utils/document-resolver.js.map +1 -0
  80. package/dist/utils/drive-types.d.ts +4 -0
  81. package/dist/utils/drive-types.js +16 -0
  82. package/dist/utils/drive-types.js.map +1 -0
  83. package/dist/utils/errors.d.ts +21 -0
  84. package/dist/utils/errors.js +110 -0
  85. package/dist/utils/errors.js.map +1 -0
  86. package/dist/utils/member.d.ts +11 -0
  87. package/dist/utils/member.js +30 -0
  88. package/dist/utils/member.js.map +1 -0
  89. package/dist/utils/url-parser.d.ts +5 -0
  90. package/dist/utils/url-parser.js +55 -0
  91. package/dist/utils/url-parser.js.map +1 -0
  92. package/dist/utils/validate.d.ts +8 -0
  93. package/dist/utils/validate.js +15 -0
  94. package/dist/utils/validate.js.map +1 -0
  95. package/package.json +54 -0
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Shared type definitions for feishu-docs CLI.
3
+ */
4
+ export type ErrorType = "INVALID_ARGS" | "FILE_NOT_FOUND" | "AUTH_REQUIRED" | "TOKEN_EXPIRED" | "PERMISSION_DENIED" | "NOT_FOUND" | "NOT_SUPPORTED" | "RATE_LIMITED" | "API_ERROR";
5
+ export interface CliErrorOptions {
6
+ apiCode?: number;
7
+ retryable?: boolean;
8
+ recovery?: string;
9
+ }
10
+ export type AuthMode = "user" | "tenant" | "auto";
11
+ export interface AuthInfo {
12
+ mode: AuthMode;
13
+ appId?: string;
14
+ appSecret?: string;
15
+ userToken?: string;
16
+ tenantToken?: string;
17
+ expiresAt?: number;
18
+ refreshToken?: string;
19
+ useLark: boolean;
20
+ }
21
+ export interface TokenData {
22
+ user_access_token: string;
23
+ refresh_token: string;
24
+ expires_at: number;
25
+ token_type?: string;
26
+ }
27
+ export interface GlobalOpts {
28
+ auth: string;
29
+ json: boolean;
30
+ lark: boolean;
31
+ }
32
+ export interface CommandArgs {
33
+ positionals?: string[];
34
+ [key: string]: unknown;
35
+ }
36
+ export type CommandHandler = (args: CommandArgs, globalOpts: GlobalOpts) => Promise<void>;
37
+ export interface OptionDef {
38
+ type: "string" | "boolean";
39
+ default?: string | boolean;
40
+ }
41
+ export interface CommandMeta {
42
+ options: Record<string, OptionDef>;
43
+ positionals?: boolean;
44
+ handler: CommandHandler;
45
+ }
46
+ export interface SubcommandMeta {
47
+ subcommands: Record<string, CommandMeta>;
48
+ }
49
+ export type DocType = "wiki" | "docx" | "doc" | "sheet" | "bitable" | "unknown";
50
+ export interface ParsedDoc {
51
+ type: DocType;
52
+ token: string;
53
+ }
54
+ export interface DocumentInfo {
55
+ documentId: string;
56
+ objToken: string;
57
+ objType: string;
58
+ title: string;
59
+ url?: string;
60
+ revisionId?: number;
61
+ nodeToken?: string;
62
+ spaceId?: string;
63
+ }
64
+ export interface ApiResponse<T = unknown> {
65
+ code?: number;
66
+ msg?: string;
67
+ data?: T;
68
+ }
69
+ export interface FetchOptions {
70
+ method?: string;
71
+ params?: Record<string, string | number | string[] | undefined>;
72
+ body?: unknown;
73
+ headers?: Record<string, string>;
74
+ }
75
+ export interface TextElement {
76
+ text_run?: {
77
+ content: string;
78
+ text_element_style?: TextElementStyle;
79
+ };
80
+ mention_user?: {
81
+ user_id: string;
82
+ text_element_style?: TextElementStyle;
83
+ };
84
+ mention_doc?: {
85
+ token: string;
86
+ obj_type: number;
87
+ url: string;
88
+ title?: string;
89
+ text_element_style?: TextElementStyle;
90
+ };
91
+ equation?: {
92
+ content: string;
93
+ text_element_style?: TextElementStyle;
94
+ };
95
+ file?: {
96
+ file_token: string;
97
+ text_element_style?: TextElementStyle;
98
+ };
99
+ reminder?: {
100
+ timestamp: string;
101
+ };
102
+ undefined?: Record<string, unknown>;
103
+ }
104
+ export interface TextElementStyle {
105
+ bold?: boolean;
106
+ italic?: boolean;
107
+ strikethrough?: boolean;
108
+ underline?: boolean;
109
+ inline_code?: boolean;
110
+ link?: {
111
+ url: string;
112
+ };
113
+ text_color?: number;
114
+ background_color?: number;
115
+ }
116
+ export interface BlockText {
117
+ elements: TextElement[];
118
+ style?: {
119
+ align?: number;
120
+ done?: boolean;
121
+ language?: number;
122
+ wrap?: boolean;
123
+ folded?: boolean;
124
+ };
125
+ }
126
+ export interface Block {
127
+ block_id: string;
128
+ block_type: number;
129
+ parent_id?: string;
130
+ children?: string[];
131
+ text?: BlockText;
132
+ heading1?: BlockText;
133
+ heading2?: BlockText;
134
+ heading3?: BlockText;
135
+ heading4?: BlockText;
136
+ heading5?: BlockText;
137
+ heading6?: BlockText;
138
+ heading7?: BlockText;
139
+ heading8?: BlockText;
140
+ heading9?: BlockText;
141
+ bullet?: BlockText;
142
+ ordered?: BlockText;
143
+ code?: BlockText;
144
+ quote?: BlockText;
145
+ equation?: BlockText;
146
+ todo?: BlockText;
147
+ callout?: {
148
+ background_color?: number;
149
+ border_color?: number;
150
+ emoji_id?: string;
151
+ body?: BlockText;
152
+ elements?: TextElement[];
153
+ };
154
+ divider?: Record<string, never>;
155
+ image?: {
156
+ token: string;
157
+ width?: number;
158
+ height?: number;
159
+ align?: number;
160
+ };
161
+ file?: {
162
+ token: string;
163
+ name?: string;
164
+ view_type?: number;
165
+ };
166
+ table?: {
167
+ cells?: string[];
168
+ property?: {
169
+ row_size: number;
170
+ column_size: number;
171
+ column_width?: number[];
172
+ header_row?: boolean;
173
+ merge_info?: unknown[];
174
+ };
175
+ };
176
+ table_cell?: {
177
+ elements?: TextElement[];
178
+ };
179
+ grid?: {
180
+ column_size: number;
181
+ };
182
+ grid_column?: {
183
+ width_ratio?: number;
184
+ };
185
+ iframe?: {
186
+ component?: {
187
+ iframe_type?: number;
188
+ url?: string;
189
+ };
190
+ };
191
+ quote_container?: Record<string, never>;
192
+ view?: {
193
+ view_type?: number;
194
+ };
195
+ bitable?: {
196
+ token?: string;
197
+ };
198
+ sheet?: {
199
+ token?: string;
200
+ };
201
+ chat_card?: {
202
+ chat_id?: string;
203
+ };
204
+ diagram?: {
205
+ diagram_type?: number;
206
+ };
207
+ task?: {
208
+ task_id?: string;
209
+ };
210
+ [key: string]: unknown;
211
+ }
212
+ export interface WikiNode {
213
+ space_id: string;
214
+ node_token: string;
215
+ obj_token: string;
216
+ obj_type: string;
217
+ parent_node_token?: string;
218
+ title: string;
219
+ has_child: boolean;
220
+ node_type?: string;
221
+ origin_node_token?: string;
222
+ origin_space_id?: string;
223
+ }
224
+ export interface WikiSpace {
225
+ space_id: string;
226
+ name: string;
227
+ description?: string;
228
+ visibility?: string;
229
+ }
230
+ export interface ConvertedBlocks {
231
+ blocks: Block[];
232
+ firstLevelBlockIds: string[];
233
+ blockIdToImageUrls: Record<string, string>;
234
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared type definitions for feishu-docs CLI.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Unified URL/token → document resolution.
3
+ * Extracts the repeated resolve logic from read.js, write.js, delete.js.
4
+ */
5
+ import * as lark from "@larksuiteoapi/node-sdk";
6
+ import { AuthInfo, ParsedDoc } from "../types/index.js";
7
+ export interface ResolvedDocument {
8
+ objToken: string;
9
+ objType: string;
10
+ title: string | undefined;
11
+ nodeToken: string | undefined;
12
+ spaceId: string | undefined;
13
+ hasChild: boolean;
14
+ parsed: ParsedDoc;
15
+ }
16
+ /**
17
+ * Resolve a URL or raw token to a fully-qualified document descriptor.
18
+ *
19
+ * @param {object} client - Lark SDK client
20
+ * @param {object} authInfo - Auth credentials
21
+ * @param {string} input - URL or raw token
22
+ * @param {object} options
23
+ * @param {boolean} options.allowFallback - If true, unknown types silently fall back to docx (default: true)
24
+ * @returns {{ objToken, objType, title, nodeToken, spaceId, hasChild, parsed }}
25
+ */
26
+ export declare function resolveDocument(client: lark.Client, authInfo: AuthInfo, input: string, options?: {
27
+ allowFallback?: boolean;
28
+ }): Promise<ResolvedDocument>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Unified URL/token → document resolution.
3
+ * Extracts the repeated resolve logic from read.js, write.js, delete.js.
4
+ */
5
+ import { parseDocUrl } from "./url-parser.js";
6
+ import { resolveWikiToken } from "../services/wiki-nodes.js";
7
+ /**
8
+ * Resolve a URL or raw token to a fully-qualified document descriptor.
9
+ *
10
+ * @param {object} client - Lark SDK client
11
+ * @param {object} authInfo - Auth credentials
12
+ * @param {string} input - URL or raw token
13
+ * @param {object} options
14
+ * @param {boolean} options.allowFallback - If true, unknown types silently fall back to docx (default: true)
15
+ * @returns {{ objToken, objType, title, nodeToken, spaceId, hasChild, parsed }}
16
+ */
17
+ export async function resolveDocument(client, authInfo, input, options = {}) {
18
+ const { allowFallback = true } = options;
19
+ const parsed = parseDocUrl(input);
20
+ let objToken = parsed.token;
21
+ let objType = parsed.type === "unknown" ? "docx" : parsed.type;
22
+ let title;
23
+ let nodeToken;
24
+ let spaceId;
25
+ let hasChild = false;
26
+ if (parsed.type === "wiki" || parsed.type === "unknown") {
27
+ try {
28
+ const wiki = await resolveWikiToken(client, authInfo, parsed.token);
29
+ objToken = wiki.objToken;
30
+ objType = wiki.objType;
31
+ title = wiki.title;
32
+ nodeToken = wiki.nodeToken;
33
+ spaceId = wiki.spaceId;
34
+ hasChild = wiki.hasChild;
35
+ }
36
+ catch (err) {
37
+ if (parsed.type === "unknown" && allowFallback) {
38
+ objType = "docx";
39
+ }
40
+ else {
41
+ throw err;
42
+ }
43
+ }
44
+ }
45
+ return { objToken, objType, title, nodeToken, spaceId, hasChild, parsed };
46
+ }
47
+ //# sourceMappingURL=document-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-resolver.js","sourceRoot":"","sources":["../../src/utils/document-resolver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAa7D;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAmB,EACnB,QAAkB,EAClB,KAAa,EACb,UAAuC,EAAE;IAEzC,MAAM,EAAE,aAAa,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACzC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,IAAI,OAAO,GAAW,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;IACvE,IAAI,KAAyB,CAAC;IAC9B,IAAI,SAA6B,CAAC;IAClC,IAAI,OAA2B,CAAC;IAChC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACpE,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;YACzB,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YACvB,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;YACnB,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;YAC3B,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YACvB,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,aAAa,EAAE,CAAC;gBAC/C,OAAO,GAAG,MAAM,CAAC;YACnB,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Map document object type to Feishu Drive API type parameter.
3
+ */
4
+ export declare function mapToDriveType(objType: string): string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Map document object type to Feishu Drive API type parameter.
3
+ */
4
+ const DRIVE_TYPE_MAP = {
5
+ docx: "docx",
6
+ doc: "doc",
7
+ sheet: "sheet",
8
+ bitable: "bitable",
9
+ mindnote: "mindnote",
10
+ board: "board",
11
+ wiki: "wiki",
12
+ };
13
+ export function mapToDriveType(objType) {
14
+ return DRIVE_TYPE_MAP[objType] || "docx";
15
+ }
16
+ //# sourceMappingURL=drive-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drive-types.js","sourceRoot":"","sources":["../../src/utils/drive-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,cAAc,GAA2B;IAC7C,IAAI,EAAE,MAAM;IACZ,GAAG,EAAE,KAAK;IACV,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,cAAc,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,21 @@
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
+ import { ErrorType, CliErrorOptions } from "../types/index.js";
11
+ export declare class CliError extends Error {
12
+ exitCode: number;
13
+ errorType: string;
14
+ apiCode?: number;
15
+ retryable: boolean;
16
+ recovery?: string;
17
+ constructor(type: ErrorType, message: string, { apiCode, retryable, recovery }?: CliErrorOptions);
18
+ }
19
+ export declare function formatError(err: unknown, json?: boolean): string;
20
+ export declare function handleError(err: unknown, json?: boolean): never;
21
+ export declare function mapApiError(err: unknown): CliError;
@@ -0,0 +1,110 @@
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
+ NOT_FOUND: { exit: 3, type: "NOT_FOUND" },
17
+ NOT_SUPPORTED: { exit: 3, type: "NOT_SUPPORTED" },
18
+ RATE_LIMITED: { exit: 3, type: "RATE_LIMITED" },
19
+ API_ERROR: { exit: 3, type: "API_ERROR" },
20
+ };
21
+ export class CliError extends Error {
22
+ exitCode;
23
+ errorType;
24
+ apiCode;
25
+ retryable;
26
+ recovery;
27
+ constructor(type, message, { apiCode, retryable = false, recovery } = {}) {
28
+ super(message);
29
+ this.name = "CliError";
30
+ const info = ERROR_MAP[type] || ERROR_MAP.API_ERROR;
31
+ this.exitCode = info.exit;
32
+ this.errorType = info.type;
33
+ this.apiCode = apiCode;
34
+ this.retryable = retryable;
35
+ this.recovery = recovery;
36
+ }
37
+ }
38
+ export function formatError(err, json = false) {
39
+ if (err instanceof CliError) {
40
+ if (json) {
41
+ return JSON.stringify({
42
+ success: false,
43
+ error: {
44
+ type: err.errorType,
45
+ message: err.message,
46
+ api_code: err.apiCode,
47
+ retryable: err.retryable,
48
+ recovery: err.recovery,
49
+ },
50
+ });
51
+ }
52
+ const code = err.apiCode ? ` (code: ${err.apiCode})` : "";
53
+ return `feishu-docs: error: ${err.message}${code}`;
54
+ }
55
+ const safeMessage = err.message || "未知错误";
56
+ if (json) {
57
+ return JSON.stringify({
58
+ success: false,
59
+ error: {
60
+ type: "UNKNOWN",
61
+ message: safeMessage,
62
+ retryable: false,
63
+ },
64
+ });
65
+ }
66
+ return `feishu-docs: error: ${safeMessage}`;
67
+ }
68
+ export function handleError(err, json = false) {
69
+ process.stderr.write(formatError(err, json) + "\n");
70
+ const exitCode = err instanceof CliError ? err.exitCode : 1;
71
+ process.exit(exitCode);
72
+ }
73
+ export function mapApiError(err) {
74
+ const e = err;
75
+ const code = e?.code ?? e?.response?.data?.code ?? e?.response?.code;
76
+ const msg = e?.msg ?? e?.response?.data?.msg ?? e?.response?.msg ?? e?.message;
77
+ if (code === 131006) {
78
+ return new CliError("PERMISSION_DENIED", `权限不足,请确认文档已对当前用户开放访问权限`, {
79
+ apiCode: code,
80
+ recovery: "请求文档拥有者授予访问权限,或使用 --auth user 切换身份",
81
+ });
82
+ }
83
+ if (code === 131008) {
84
+ // 131008 is context-dependent: "permission denied" for node ops, "already exist" for member ops.
85
+ // Preserve apiCode so callers can distinguish at the call site.
86
+ return new CliError("PERMISSION_DENIED", msg || "权限不足或资源已存在", {
87
+ apiCode: code,
88
+ recovery: "请求文档拥有者授予访问权限,或使用 --auth user 切换身份",
89
+ });
90
+ }
91
+ if (code === 131001 || code === 131002) {
92
+ return new CliError("NOT_FOUND", `文档不存在或已被删除`, {
93
+ apiCode: code,
94
+ });
95
+ }
96
+ if (code === 99991400 || code === 99991663) {
97
+ return new CliError("TOKEN_EXPIRED", `认证已过期,请重新运行 feishu-docs login`, {
98
+ apiCode: code,
99
+ recovery: "运行 feishu-docs login 重新认证",
100
+ });
101
+ }
102
+ if (code === 99991672) {
103
+ return new CliError("RATE_LIMITED", `API 请求频率超限,请稍后重试`, {
104
+ apiCode: code,
105
+ retryable: true,
106
+ });
107
+ }
108
+ return new CliError("API_ERROR", msg || "未知 API 错误", { apiCode: code });
109
+ }
110
+ //# 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,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"}