@fanzie/task-cli 0.1.1 → 0.2.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.
Files changed (2) hide show
  1. package/dist/cli.js +374 -220
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,28 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command as e } from "commander";
3
- import * as t from "node:readline";
3
+ import * as t from "@larksuiteoapi/node-sdk";
4
4
  import * as n from "node:fs";
5
5
  import * as r from "node:path";
6
6
  import * as i from "node:os";
7
7
  import * as a from "js-yaml";
8
- import * as o from "@larksuiteoapi/node-sdk";
9
8
  //#region src/config.ts
10
- var s = r.join(i.homedir(), ".config", "fz-task"), c = r.join(s, "config.yaml"), l = r.join(s, ".env");
11
- function u(e) {
9
+ var o = r.join(i.homedir(), ".config", "fz-task"), s = r.join(o, "config.yaml"), c = r.join(o, ".env");
10
+ function l(e) {
12
11
  let t = new URL(e), n = t.pathname.split("/").filter(Boolean), r = n.indexOf("wiki"), i = n.indexOf("base"), a = r === -1 ? i : r;
13
12
  if (a === -1 || a + 1 >= n.length) throw Error(`Invalid bitable URL: cannot extract appToken from ${e}`);
14
- let o = n[a + 1], s = t.searchParams.get("table");
15
- if (!s) throw Error(`Invalid bitable URL: missing table parameter in ${e}`);
16
13
  return {
17
- appToken: o,
18
- tableId: s
14
+ appToken: n[a + 1],
15
+ tableId: t.searchParams.get("table") ?? void 0
19
16
  };
20
17
  }
21
- function d(e, t) {
18
+ function u(e, t) {
22
19
  return e.replace(/\$\{(\w+)\}/g, (e, n) => t[n] ?? e);
23
20
  }
24
- function f(e) {
25
- let t = e ?? l, r = {};
21
+ function d(e) {
22
+ let t = e ?? c, r = {};
26
23
  if (!n.existsSync(t)) return r;
27
24
  let i = n.readFileSync(t, "utf-8");
28
25
  for (let e of i.split("\n")) {
@@ -35,63 +32,195 @@ function f(e) {
35
32
  }
36
33
  return r;
37
34
  }
38
- function p() {
39
- let e = f(r.join(process.cwd(), ".env")), t = f(), i = {
35
+ function f() {
36
+ let e = d(r.join(process.cwd(), ".env")), t = d(), i = {
40
37
  ...process.env,
41
38
  ...t,
42
39
  ...e
43
40
  };
44
- if (!n.existsSync(c)) return null;
45
- let o = n.readFileSync(c, "utf-8"), s = a.load(o);
46
- if (!s?.profiles) return null;
41
+ if (!n.existsSync(s)) return null;
42
+ let o = n.readFileSync(s, "utf-8"), c = a.load(o);
43
+ if (!c?.profiles) return null;
47
44
  let l = {
48
45
  profiles: {},
49
- defaultProfile: s.defaultProfile ?? "default"
46
+ defaultProfile: c.defaultProfile ?? "default"
50
47
  };
51
- for (let [e, t] of Object.entries(s.profiles)) {
48
+ for (let [e, t] of Object.entries(c.profiles)) {
52
49
  let n = t;
53
50
  l.profiles[e] = {
54
51
  auth: {
55
- appId: d(n.auth?.appId ?? "", i),
56
- appSecret: d(n.auth?.appSecret ?? "", i)
52
+ appId: u(n.auth?.appId ?? "", i),
53
+ appSecret: u(n.auth?.appSecret ?? "", i)
57
54
  },
58
55
  bitable: {
59
56
  url: n.bitable?.url ?? "",
60
57
  appToken: n.bitable?.appToken ?? "",
61
- tableId: n.bitable?.tableId ?? ""
58
+ tableId: n.bitable?.tableId || void 0
62
59
  }
63
60
  };
64
61
  }
65
62
  return l;
66
63
  }
67
- function m(e, t) {
64
+ function p(e, t) {
68
65
  let n = t ?? process.env.FZ_TASK_PROFILE ?? e.defaultProfile, r = e.profiles[n];
69
66
  if (!r) throw Error(`Profile "${n}" not found. Available: ${Object.keys(e.profiles).join(", ")}`);
70
67
  return r;
71
68
  }
72
- function h() {
73
- n.mkdirSync(s, { recursive: !0 });
69
+ function m() {
70
+ n.mkdirSync(o, { recursive: !0 });
74
71
  }
75
- function g(e) {
76
- h();
72
+ function h(e) {
73
+ m();
77
74
  let t = a.dump(e);
78
- n.writeFileSync(c, t, "utf-8");
75
+ n.writeFileSync(s, t, "utf-8");
79
76
  }
80
- function _(e, t) {
81
- h();
77
+ function g(e, t) {
78
+ m();
82
79
  let r = `FZ_TASK_APP_ID=${e}\nFZ_TASK_APP_SECRET=${t}\n`;
83
- n.writeFileSync(l, r, "utf-8");
80
+ n.writeFileSync(c, r, "utf-8");
81
+ }
82
+ var _ = ".fz-task.yaml";
83
+ function v(e) {
84
+ let t = e ?? process.cwd(), i = r.join(t, _);
85
+ if (!n.existsSync(i)) return null;
86
+ try {
87
+ let e = n.readFileSync(i, "utf-8"), t = a.load(e);
88
+ return t?.tableId ? {
89
+ tableId: t.tableId,
90
+ appToken: t.appToken || void 0
91
+ } : null;
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+ function y(e, t) {
97
+ let i = t ?? process.cwd(), o = r.join(i, _), s = { tableId: e.tableId };
98
+ e.appToken && (s.appToken = e.appToken);
99
+ let c = a.dump(s);
100
+ n.writeFileSync(o, c, "utf-8");
101
+ }
102
+ function b(e, t) {
103
+ let n = f();
104
+ if (!n) throw Error("No config found. Run `fz-task-cli auth init` to set up.");
105
+ let r = p(n, e), i = r.bitable.appToken, a, o = v(t);
106
+ if (o) a = o.tableId, o.appToken && (i = o.appToken);
107
+ else if (r.bitable.tableId) a = r.bitable.tableId, console.warn("Warning: Using tableId from global config. Run `fz-task init` to create a project config.");
108
+ else throw Error("No project config found. Run `fz-task init` in your project directory.");
109
+ return {
110
+ appId: r.auth.appId,
111
+ appSecret: r.auth.appSecret,
112
+ appToken: i,
113
+ tableId: a
114
+ };
115
+ }
116
+ //#endregion
117
+ //#region src/commands/auth.ts
118
+ function x(e) {
119
+ let n = e.command("auth").description("Authentication management");
120
+ n.command("status").description("Show current global auth config status").action(async () => {
121
+ let t = f();
122
+ t || (console.error("No config found. Run `fz-task-cli auth init` to set up."), process.exit(3));
123
+ try {
124
+ let n = e.opts().profile ?? t.defaultProfile, r = p(t, e.opts().profile);
125
+ console.log(`Profile: ${n}`), console.log(`App ID: ${r.auth.appId.slice(0, 6)}...${r.auth.appId.slice(-4)}`), console.log(`App Token: ${r.bitable.appToken}`), console.log("Status: OK");
126
+ } catch (e) {
127
+ console.error(e.message), process.exit(3);
128
+ }
129
+ }), n.command("init").description("Set up global authentication").requiredOption("--app-id <id>", "Feishu App ID").requiredOption("--app-secret <secret>", "Feishu App Secret").option("--url <url>", "Bitable URL (extracts appToken)").option("--app-token <token>", "Direct appToken (Base ID)").option("--no-verify", "Skip connection verification").action(async (e) => {
130
+ let { appId: n, appSecret: r, url: i, appToken: a, verify: o } = e;
131
+ i && a && (console.error("Cannot specify both --url and --app-token."), process.exit(2)), !i && !a && (console.error("Must specify either --url or --app-token."), process.exit(2));
132
+ let s;
133
+ if (i) try {
134
+ ({appToken: s} = l(i));
135
+ } catch (e) {
136
+ console.error(`URL parsing failed: ${e.message}`), process.exit(2);
137
+ }
138
+ else s = a;
139
+ if (h({
140
+ profiles: { default: {
141
+ auth: {
142
+ appId: "${FZ_TASK_APP_ID}",
143
+ appSecret: "${FZ_TASK_APP_SECRET}"
144
+ },
145
+ bitable: {
146
+ url: i ?? "",
147
+ appToken: s
148
+ }
149
+ } },
150
+ defaultProfile: "default"
151
+ }), g(n, r), console.log("Config written to ~/.config/fz-task/config.yaml"), console.log("Credentials written to ~/.config/fz-task/.env"), console.log(`App Token: ${s}`), o) {
152
+ console.log("Verifying connection...");
153
+ try {
154
+ await new t.Client({
155
+ appId: n,
156
+ appSecret: r,
157
+ appType: t.AppType.SelfBuild,
158
+ domain: t.Domain.Feishu
159
+ }).bitable.appTable.list({ path: { app_token: s } }), console.log("Connection verified successfully!");
160
+ } catch (e) {
161
+ console.error(`Connection failed: ${e.message}`), process.exit(3);
162
+ }
163
+ }
164
+ console.log("Run `fz-task init` in your project directory to initialize a project.");
165
+ });
166
+ }
167
+ //#endregion
168
+ //#region src/types.ts
169
+ var S = [
170
+ "TODO",
171
+ "TEST_SPEC",
172
+ "DOING",
173
+ "TESTING",
174
+ "VERIFIED",
175
+ "DONE"
176
+ ], C = new Map(S.map((e, t) => [e, t]));
177
+ function w(e, t) {
178
+ if (e === "DONE") return !1;
179
+ if (t === "BLOCKED" || e === "BLOCKED") return !0;
180
+ let n = C.get(e), r = C.get(t);
181
+ return n === void 0 || r === void 0 ? !1 : r === n + 1 || r === n - 1;
182
+ }
183
+ function T(e) {
184
+ if (e === "DONE") return [];
185
+ if (e === "BLOCKED") return [...S];
186
+ let t = C.get(e);
187
+ if (t === void 0) return [];
188
+ let n = [];
189
+ return t > 0 && n.push(S[t - 1]), t < S.length - 1 && n.push(S[t + 1]), n.push("BLOCKED"), n;
190
+ }
191
+ //#endregion
192
+ //#region src/formatter.ts
193
+ function E(e, t, n) {
194
+ if (n === "json") return JSON.stringify({
195
+ ...e,
196
+ children: t
197
+ }, null, 2);
198
+ let r = [];
199
+ if (r.push(`ID: ${e.record_id}`), r.push(`Title: ${e.fields.title}`), r.push(`Type: ${e.fields.type}`), r.push(`Status: ${e.fields.status}`), e.fields.priority && r.push(`Priority: ${e.fields.priority}`), e.fields.assignee && r.push(`Assignee: ${e.fields.assignee}`), e.fields.doc_url && r.push(`Doc URL: ${e.fields.doc_url}`), e.fields.description && r.push(`Description: ${e.fields.description}`), t.length > 0) {
200
+ let n = e.fields.type === "Feature" ? "Stories" : "ACs";
201
+ r.push(""), r.push(`Children (${t.length} ${n}):`);
202
+ for (let e of t) r.push(` ${e.record_id} [${e.fields.status}] ${e.fields.title}`);
203
+ }
204
+ return r.join("\n");
205
+ }
206
+ function D(e, t) {
207
+ if (t === "json") return JSON.stringify(e, null, 2);
208
+ if (e.length === 0) return "No records found.";
209
+ let n = [];
210
+ n.push("ID Type Status Priority Title"), n.push("-".repeat(80));
211
+ for (let t of e) n.push(`${t.record_id}\t${t.fields.type}\t${t.fields.status}\t${t.fields.priority ?? "-"}\t${t.fields.title}`);
212
+ return n.join("\n");
84
213
  }
85
214
  //#endregion
86
215
  //#region src/client.ts
87
- function v(e) {
216
+ function O(e) {
88
217
  let t = {};
89
218
  return e.title !== void 0 && (t.Text = e.title), e.type !== void 0 && (t.type = e.type), e.status !== void 0 && (t.status = e.status), e.parent !== void 0 && (t.parent = e.parent), e.doc_url !== void 0 && (t.doc_url = {
90
219
  link: e.doc_url,
91
220
  text: e.doc_url
92
221
  }), e.assignee !== void 0 && (t.assignee = e.assignee), e.description !== void 0 && (t.description = e.description), e.previous_status !== void 0 && (t.previous_status = e.previous_status), e.priority !== void 0 && (t.priority = e.priority), t;
93
222
  }
94
- function y(e) {
223
+ function k(e) {
95
224
  let t = e.fields, n, r = t.parent;
96
225
  if (Array.isArray(r) && r.length > 0) {
97
226
  let e = r[0];
@@ -113,20 +242,20 @@ function y(e) {
113
242
  }
114
243
  };
115
244
  }
116
- var b = class {
245
+ var A = class {
117
246
  larkClient;
118
247
  appToken;
119
248
  tableId;
120
249
  constructor(e) {
121
- this.appToken = e.appToken, this.tableId = e.tableId, this.larkClient = new o.Client({
250
+ this.appToken = e.appToken, this.tableId = e.tableId, this.larkClient = new t.Client({
122
251
  appId: e.appId,
123
252
  appSecret: e.appSecret,
124
- appType: o.AppType.SelfBuild,
125
- domain: o.Domain.Feishu
253
+ appType: t.AppType.SelfBuild,
254
+ domain: t.Domain.Feishu
126
255
  });
127
256
  }
128
257
  async createRecord(e) {
129
- let t = v(e), n = await this.larkClient.bitable.appTableRecord.create({
258
+ let t = O(e), n = await this.larkClient.bitable.appTableRecord.create({
130
259
  path: {
131
260
  app_token: this.appToken,
132
261
  table_id: this.tableId
@@ -136,7 +265,7 @@ var b = class {
136
265
  if (n.code !== 0) throw Error(`createRecord failed: ${n.msg}`);
137
266
  let r = n.data?.record;
138
267
  if (!r) throw Error("createRecord: no record in response");
139
- return y(r);
268
+ return k(r);
140
269
  }
141
270
  async getRecord(e) {
142
271
  let t = await this.larkClient.bitable.appTableRecord.get({ path: {
@@ -147,7 +276,7 @@ var b = class {
147
276
  if (t.code !== 0) throw Error(`getRecord failed: ${t.msg}`);
148
277
  let n = t.data?.record;
149
278
  if (!n) throw Error("getRecord: no record in response");
150
- return y(n);
279
+ return k(n);
151
280
  }
152
281
  async listRecords(e) {
153
282
  let t = [];
@@ -165,13 +294,13 @@ var b = class {
165
294
  });
166
295
  if (t.code !== 0) throw Error(`listRecords failed: ${t.msg}`);
167
296
  let a = t.data?.items ?? [];
168
- for (let e of a) r.push(y(e));
297
+ for (let e of a) r.push(k(e));
169
298
  i = t.data?.has_more ? t.data.page_token : void 0;
170
299
  } while (i);
171
300
  return r;
172
301
  }
173
302
  async updateRecord(e, t) {
174
- let n = v(t), r = await this.larkClient.bitable.appTableRecord.update({
303
+ let n = O(t), r = await this.larkClient.bitable.appTableRecord.update({
175
304
  path: {
176
305
  app_token: this.appToken,
177
306
  table_id: this.tableId,
@@ -182,7 +311,7 @@ var b = class {
182
311
  if (r.code !== 0) throw Error(`updateRecord failed: ${r.msg}`);
183
312
  let i = r.data?.record;
184
313
  if (!i) throw Error("updateRecord: no record in response");
185
- return y(i);
314
+ return k(i);
186
315
  }
187
316
  async deleteRecord(e) {
188
317
  let t = await this.larkClient.bitable.appTableRecord.delete({ path: {
@@ -197,129 +326,23 @@ var b = class {
197
326
  }
198
327
  };
199
328
  //#endregion
200
- //#region src/commands/auth.ts
201
- function x(e) {
202
- let n = t.createInterface({
203
- input: process.stdin,
204
- output: process.stdout
205
- });
206
- return new Promise((t) => {
207
- n.question(e, (e) => {
208
- n.close(), t(e.trim());
209
- });
210
- });
211
- }
212
- function S(e) {
213
- let t = e.command("auth").description("Authentication management");
214
- t.command("status").description("Show current config and connection status").action(async () => {
215
- let t = p();
216
- t || (console.error("No config found. Run `fz-task-cli auth init` to set up."), process.exit(3));
217
- try {
218
- let n = e.opts().profile ?? t.defaultProfile, r = m(t, e.opts().profile);
219
- console.log(`Profile: ${n}`), console.log(`App ID: ${r.auth.appId.slice(0, 6)}...${r.auth.appId.slice(-4)}`), console.log(`App Token: ${r.bitable.appToken}`), console.log(`Table ID: ${r.bitable.tableId}`), console.log("Status: OK");
220
- } catch (e) {
221
- console.error(e.message), process.exit(3);
222
- }
223
- }), t.command("init").description("Interactive setup").action(async () => {
224
- let e = await x("Feishu App ID: "), t = await x("Feishu App Secret: "), n = await x("Bitable URL: "), r, i;
225
- try {
226
- ({appToken: r, tableId: i} = u(n));
227
- } catch (e) {
228
- console.error(`URL parsing failed: ${e.message}`), process.exit(2);
229
- }
230
- g({
231
- profiles: { default: {
232
- auth: {
233
- appId: "${FZ_TASK_APP_ID}",
234
- appSecret: "${FZ_TASK_APP_SECRET}"
235
- },
236
- bitable: {
237
- url: n,
238
- appToken: r,
239
- tableId: i
240
- }
241
- } },
242
- defaultProfile: "default"
243
- }), _(e, t), console.log("Config written to ~/.config/fz-task/config.yaml"), console.log("Credentials written to ~/.config/fz-task/.env"), console.log(`App Token: ${r}`), console.log(`Table ID: ${i}`), console.log("Verifying connection...");
244
- try {
245
- await new b({
246
- appId: e,
247
- appSecret: t,
248
- appToken: r,
249
- tableId: i
250
- }).listRecords({ type: "Feature" }), console.log("Connection verified successfully!");
251
- } catch (e) {
252
- console.error(`Connection failed: ${e.message}`), process.exit(3);
253
- }
254
- });
255
- }
256
- //#endregion
257
- //#region src/types.ts
258
- var C = [
259
- "TODO",
260
- "TEST_SPEC",
261
- "DOING",
262
- "TESTING",
263
- "VERIFIED",
264
- "DONE"
265
- ], w = new Map(C.map((e, t) => [e, t]));
266
- function T(e, t) {
267
- if (e === "DONE") return !1;
268
- if (t === "BLOCKED" || e === "BLOCKED") return !0;
269
- let n = w.get(e), r = w.get(t);
270
- return n === void 0 || r === void 0 ? !1 : r === n + 1 || r === n - 1;
271
- }
272
- function E(e) {
273
- if (e === "DONE") return [];
274
- if (e === "BLOCKED") return [...C];
275
- let t = w.get(e);
276
- if (t === void 0) return [];
277
- let n = [];
278
- return t > 0 && n.push(C[t - 1]), t < C.length - 1 && n.push(C[t + 1]), n.push("BLOCKED"), n;
279
- }
280
- //#endregion
281
- //#region src/formatter.ts
282
- function D(e, t, n) {
283
- if (n === "json") return JSON.stringify({
284
- ...e,
285
- children: t
286
- }, null, 2);
287
- let r = [];
288
- if (r.push(`ID: ${e.record_id}`), r.push(`Title: ${e.fields.title}`), r.push(`Type: ${e.fields.type}`), r.push(`Status: ${e.fields.status}`), e.fields.priority && r.push(`Priority: ${e.fields.priority}`), e.fields.assignee && r.push(`Assignee: ${e.fields.assignee}`), e.fields.doc_url && r.push(`Doc URL: ${e.fields.doc_url}`), e.fields.description && r.push(`Description: ${e.fields.description}`), t.length > 0) {
289
- let n = e.fields.type === "Feature" ? "Stories" : "ACs";
290
- r.push(""), r.push(`Children (${t.length} ${n}):`);
291
- for (let e of t) r.push(` ${e.record_id} [${e.fields.status}] ${e.fields.title}`);
329
+ //#region src/commands/shared.ts
330
+ function j(e) {
331
+ try {
332
+ return new A(b(e.parent?.opts().profile));
333
+ } catch (e) {
334
+ console.error(e.message), process.exit(3);
292
335
  }
293
- return r.join("\n");
294
336
  }
295
- function O(e, t) {
296
- if (t === "json") return JSON.stringify(e, null, 2);
297
- if (e.length === 0) return "No records found.";
298
- let n = [];
299
- n.push("ID Type Status Priority Title"), n.push("-".repeat(80));
300
- for (let t of e) n.push(`${t.record_id}\t${t.fields.type}\t${t.fields.status}\t${t.fields.priority ?? "-"}\t${t.fields.title}`);
301
- return n.join("\n");
337
+ function M(e) {
338
+ return e.parent?.opts().format ?? "table";
302
339
  }
303
340
  //#endregion
304
341
  //#region src/commands/feature.ts
305
- function k(e) {
306
- let t = p();
307
- t || (console.error("No config found. Run `fz-task-cli auth init` to set up."), process.exit(3));
308
- let n = m(t, e.parent?.opts().profile);
309
- return new b({
310
- appId: n.auth.appId,
311
- appSecret: n.auth.appSecret,
312
- appToken: n.bitable.appToken,
313
- tableId: n.bitable.tableId
314
- });
315
- }
316
- function A(e) {
317
- return e.parent?.opts().format ?? "table";
318
- }
319
- function j(e) {
342
+ function N(e) {
320
343
  let t = e.command("feature").description("Feature management commands");
321
344
  t.command("add [title]").description("Create a new Feature").option("--json <json>", "JSON string with fields").action(async (e, n, r) => {
322
- let i = k(t), a = A(t), o = {};
345
+ let i = j(t), a = M(t), o = {};
323
346
  if (n.json) try {
324
347
  o = JSON.parse(n.json);
325
348
  } catch {
@@ -328,20 +351,20 @@ function j(e) {
328
351
  e && (o.title = e), o.title || (console.error("Title is required. Provide as argument or in --json."), process.exit(2)), o.type = "Feature", o.status ||= "TODO";
329
352
  try {
330
353
  let e = await i.createRecord(o);
331
- console.log(D(e, [], a));
354
+ console.log(E(e, [], a));
332
355
  } catch (e) {
333
356
  console.error(`Failed to create feature: ${e.message}`), process.exit(1);
334
357
  }
335
358
  }), t.command("list").description("List all Features").action(async (e, n) => {
336
- let r = k(t), i = A(t);
359
+ let r = j(t), i = M(t);
337
360
  try {
338
361
  let e = await r.listRecords({ type: "Feature" });
339
- console.log(O(e, i));
362
+ console.log(D(e, i));
340
363
  } catch (e) {
341
364
  console.error(`Failed to list features: ${e.message}`), process.exit(1);
342
365
  }
343
366
  }), t.command("get <id>").description("Get a Feature by ID").action(async (e, n, r) => {
344
- let i = k(t), a = A(t), o;
367
+ let i = j(t), a = M(t), o;
345
368
  try {
346
369
  o = await i.getRecord(e);
347
370
  } catch (e) {
@@ -350,12 +373,12 @@ function j(e) {
350
373
  o.fields.type !== "Feature" && (console.error(`Record ${e} is not a Feature (type: ${o.fields.type}).`), process.exit(2));
351
374
  try {
352
375
  let t = await i.getChildren(e);
353
- console.log(D(o, t, a));
376
+ console.log(E(o, t, a));
354
377
  } catch (e) {
355
378
  console.error(`Failed to get children: ${e.message}`), process.exit(1);
356
379
  }
357
380
  }), t.command("update <id>").description("Update a Feature").option("--status <status>", "New status").option("--priority <priority>", "New priority").option("--assignee <assignee>", "New assignee").option("--doc-url <url>", "New doc URL").option("--title <title>", "New title").option("--description <description>", "New description").action(async (e, n, r) => {
358
- let i = k(t), a = A(t), o;
381
+ let i = j(t), a = M(t), o;
359
382
  try {
360
383
  o = await i.getRecord(e);
361
384
  } catch (e) {
@@ -365,8 +388,8 @@ function j(e) {
365
388
  let s = {};
366
389
  if (n.status) {
367
390
  let e = n.status, t = o.fields.status;
368
- if (!T(t, e)) {
369
- let n = E(t);
391
+ if (!w(t, e)) {
392
+ let n = T(t);
370
393
  console.error(`Invalid status transition: ${t} -> ${e}. Allowed: ${n.join(", ")}`), process.exit(2);
371
394
  }
372
395
  s.status = e, e === "BLOCKED" && (s.previous_status = t), t === "BLOCKED" && (s.previous_status = void 0);
@@ -374,12 +397,12 @@ function j(e) {
374
397
  n.priority && (s.priority = n.priority), n.assignee && (s.assignee = n.assignee), n.docUrl && (s.doc_url = n.docUrl), n.title && (s.title = n.title), n.description && (s.description = n.description);
375
398
  try {
376
399
  let t = await i.updateRecord(e, s), n = await i.getChildren(e);
377
- console.log(D(t, n, a));
400
+ console.log(E(t, n, a));
378
401
  } catch (e) {
379
402
  console.error(`Failed to update feature: ${e.message}`), process.exit(1);
380
403
  }
381
404
  }), t.command("delete <id>").description("Delete a Feature").action(async (e, n, r) => {
382
- let i = k(t), a;
405
+ let i = j(t), a;
383
406
  try {
384
407
  a = await i.getRecord(e);
385
408
  } catch (e) {
@@ -397,24 +420,10 @@ function j(e) {
397
420
  }
398
421
  //#endregion
399
422
  //#region src/commands/story.ts
400
- function M(e) {
401
- let t = p();
402
- t || (console.error("No config found. Run `fz-task-cli auth init` to set up."), process.exit(3));
403
- let n = m(t, e.parent?.opts().profile);
404
- return new b({
405
- appId: n.auth.appId,
406
- appSecret: n.auth.appSecret,
407
- appToken: n.bitable.appToken,
408
- tableId: n.bitable.tableId
409
- });
410
- }
411
- function N(e) {
412
- return e.parent?.opts().format ?? "table";
413
- }
414
423
  function P(e) {
415
424
  let t = e.command("story").description("UserStory management commands");
416
425
  t.command("add [title]").description("Create a new UserStory under a Feature").requiredOption("--feature <id>", "Parent Feature record ID").option("--json <json>", "JSON string with fields").action(async (e, n, r) => {
417
- let i = M(t), a = N(t), o;
426
+ let i = j(t), a = M(t), o;
418
427
  try {
419
428
  o = await i.getRecord(n.feature);
420
429
  } catch (e) {
@@ -430,20 +439,20 @@ function P(e) {
430
439
  e && (s.title = e), s.title || (console.error("Title is required. Provide as argument or in --json."), process.exit(2)), s.type = "UserStory", s.parent = [n.feature], s.status ||= "TODO";
431
440
  try {
432
441
  let e = await i.createRecord(s);
433
- console.log(D(e, [], a));
442
+ console.log(E(e, [], a));
434
443
  } catch (e) {
435
444
  console.error(`Failed to create story: ${e.message}`), process.exit(1);
436
445
  }
437
446
  }), t.command("list").description("List UserStories, optionally filtered by Feature").option("--feature <id>", "Parent Feature record ID to filter by").action(async (e, n) => {
438
- let r = M(t), i = N(t);
447
+ let r = j(t), i = M(t);
439
448
  try {
440
449
  let t;
441
- t = e.feature ? (await r.getChildren(e.feature)).filter((e) => e.fields.type === "UserStory") : await r.listRecords({ type: "UserStory" }), console.log(O(t, i));
450
+ t = e.feature ? (await r.getChildren(e.feature)).filter((e) => e.fields.type === "UserStory") : await r.listRecords({ type: "UserStory" }), console.log(D(t, i));
442
451
  } catch (e) {
443
452
  console.error(`Failed to list stories: ${e.message}`), process.exit(1);
444
453
  }
445
454
  }), t.command("get <id>").description("Get a UserStory by ID").action(async (e, n, r) => {
446
- let i = M(t), a = N(t), o;
455
+ let i = j(t), a = M(t), o;
447
456
  try {
448
457
  o = await i.getRecord(e);
449
458
  } catch (e) {
@@ -452,12 +461,12 @@ function P(e) {
452
461
  o.fields.type !== "UserStory" && (console.error(`Record ${e} is not a UserStory (type: ${o.fields.type}).`), process.exit(2));
453
462
  try {
454
463
  let t = await i.getChildren(e);
455
- console.log(D(o, t, a));
464
+ console.log(E(o, t, a));
456
465
  } catch (e) {
457
466
  console.error(`Failed to get children: ${e.message}`), process.exit(1);
458
467
  }
459
468
  }), t.command("update <id>").description("Update a UserStory").option("--status <status>", "New status").option("--priority <priority>", "New priority").option("--assignee <assignee>", "New assignee").option("--doc-url <url>", "New doc URL").option("--title <title>", "New title").option("--description <description>", "New description").action(async (e, n, r) => {
460
- let i = M(t), a = N(t), o;
469
+ let i = j(t), a = M(t), o;
461
470
  try {
462
471
  o = await i.getRecord(e);
463
472
  } catch (e) {
@@ -467,8 +476,8 @@ function P(e) {
467
476
  let s = {};
468
477
  if (n.status) {
469
478
  let e = n.status, t = o.fields.status;
470
- if (!T(t, e)) {
471
- let n = E(t);
479
+ if (!w(t, e)) {
480
+ let n = T(t);
472
481
  console.error(`Invalid status transition: ${t} -> ${e}. Allowed: ${n.join(", ")}`), process.exit(2);
473
482
  }
474
483
  s.status = e, e === "BLOCKED" && (s.previous_status = t), t === "BLOCKED" && (s.previous_status = void 0);
@@ -476,12 +485,12 @@ function P(e) {
476
485
  n.priority && (s.priority = n.priority), n.assignee && (s.assignee = n.assignee), n.docUrl && (s.doc_url = n.docUrl), n.title && (s.title = n.title), n.description && (s.description = n.description);
477
486
  try {
478
487
  let t = await i.updateRecord(e, s), n = await i.getChildren(e);
479
- console.log(D(t, n, a));
488
+ console.log(E(t, n, a));
480
489
  } catch (e) {
481
490
  console.error(`Failed to update story: ${e.message}`), process.exit(1);
482
491
  }
483
492
  }), t.command("delete <id>").description("Delete a UserStory").action(async (e, n, r) => {
484
- let i = M(t), a;
493
+ let i = j(t), a;
485
494
  try {
486
495
  a = await i.getRecord(e);
487
496
  } catch (e) {
@@ -500,23 +509,9 @@ function P(e) {
500
509
  //#endregion
501
510
  //#region src/commands/ac.ts
502
511
  function F(e) {
503
- let t = p();
504
- t || (console.error("No config found. Run `fz-task-cli auth init` to set up."), process.exit(3));
505
- let n = m(t, e.parent?.opts().profile);
506
- return new b({
507
- appId: n.auth.appId,
508
- appSecret: n.auth.appSecret,
509
- appToken: n.bitable.appToken,
510
- tableId: n.bitable.tableId
511
- });
512
- }
513
- function I(e) {
514
- return e.parent?.opts().format ?? "table";
515
- }
516
- function L(e) {
517
512
  let t = e.command("ac").description("AC (Acceptance Criteria) management commands");
518
513
  t.command("add [title]").description("Create a new AC under a UserStory").requiredOption("--story <id>", "Parent UserStory record ID").option("--json <json>", "JSON string with fields").action(async (e, n, r) => {
519
- let i = F(t), a = I(t), o;
514
+ let i = j(t), a = M(t), o;
520
515
  try {
521
516
  o = await i.getRecord(n.story);
522
517
  } catch (e) {
@@ -532,28 +527,28 @@ function L(e) {
532
527
  e && (s.title = e), s.title || (console.error("Title is required. Provide as argument or in --json."), process.exit(2)), s.type = "AC", s.parent = [n.story], s.status ||= "TODO";
533
528
  try {
534
529
  let e = await i.createRecord(s);
535
- console.log(D(e, [], a));
530
+ console.log(E(e, [], a));
536
531
  } catch (e) {
537
532
  console.error(`Failed to create AC: ${e.message}`), process.exit(1);
538
533
  }
539
534
  }), t.command("list").description("List ACs, optionally filtered by UserStory").option("--story <id>", "Parent UserStory record ID to filter by").action(async (e, n) => {
540
- let r = F(t), i = I(t);
535
+ let r = j(t), i = M(t);
541
536
  try {
542
537
  let t;
543
- t = e.story ? (await r.getChildren(e.story)).filter((e) => e.fields.type === "AC") : await r.listRecords({ type: "AC" }), console.log(O(t, i));
538
+ t = e.story ? (await r.getChildren(e.story)).filter((e) => e.fields.type === "AC") : await r.listRecords({ type: "AC" }), console.log(D(t, i));
544
539
  } catch (e) {
545
540
  console.error(`Failed to list ACs: ${e.message}`), process.exit(1);
546
541
  }
547
542
  }), t.command("get <id>").description("Get an AC by ID").action(async (e, n, r) => {
548
- let i = F(t), a = I(t), o;
543
+ let i = j(t), a = M(t), o;
549
544
  try {
550
545
  o = await i.getRecord(e);
551
546
  } catch (e) {
552
547
  console.error(`Record not found: ${e.message}`), process.exit(4);
553
548
  }
554
- o.fields.type !== "AC" && (console.error(`Record ${e} is not an AC (type: ${o.fields.type}).`), process.exit(2)), console.log(D(o, [], a));
549
+ o.fields.type !== "AC" && (console.error(`Record ${e} is not an AC (type: ${o.fields.type}).`), process.exit(2)), console.log(E(o, [], a));
555
550
  }), t.command("update <id>").description("Update an AC").option("--status <status>", "New status").option("--priority <priority>", "New priority").option("--assignee <assignee>", "New assignee").option("--doc-url <url>", "New doc URL").option("--title <title>", "New title").option("--description <description>", "New description").action(async (e, n, r) => {
556
- let i = F(t), a = I(t), o;
551
+ let i = j(t), a = M(t), o;
557
552
  try {
558
553
  o = await i.getRecord(e);
559
554
  } catch (e) {
@@ -563,8 +558,8 @@ function L(e) {
563
558
  let s = {};
564
559
  if (n.status) {
565
560
  let e = n.status, t = o.fields.status;
566
- if (!T(t, e)) {
567
- let n = E(t);
561
+ if (!w(t, e)) {
562
+ let n = T(t);
568
563
  console.error(`Invalid status transition: ${t} -> ${e}. Allowed: ${n.join(", ")}`), process.exit(2);
569
564
  }
570
565
  s.status = e, e === "BLOCKED" && (s.previous_status = t), t === "BLOCKED" && (s.previous_status = void 0);
@@ -572,12 +567,12 @@ function L(e) {
572
567
  n.priority && (s.priority = n.priority), n.assignee && (s.assignee = n.assignee), n.docUrl && (s.doc_url = n.docUrl), n.title && (s.title = n.title), n.description && (s.description = n.description);
573
568
  try {
574
569
  let t = await i.updateRecord(e, s);
575
- console.log(D(t, [], a));
570
+ console.log(E(t, [], a));
576
571
  } catch (e) {
577
572
  console.error(`Failed to update AC: ${e.message}`), process.exit(1);
578
573
  }
579
574
  }), t.command("delete <id>").description("Delete an AC").action(async (e, n, r) => {
580
- let i = F(t), a;
575
+ let i = j(t), a;
581
576
  try {
582
577
  a = await i.getRecord(e);
583
578
  } catch (e) {
@@ -592,7 +587,166 @@ function L(e) {
592
587
  });
593
588
  }
594
589
  //#endregion
590
+ //#region src/commands/init.ts
591
+ var I = 1, L = 3, R = 15, z = 21;
592
+ function B() {
593
+ return [
594
+ {
595
+ field_name: "Text",
596
+ type: I,
597
+ ui_type: "Text"
598
+ },
599
+ {
600
+ field_name: "type",
601
+ type: L,
602
+ ui_type: "SingleSelect",
603
+ property: { options: [
604
+ { name: "Feature" },
605
+ { name: "UserStory" },
606
+ { name: "AC" }
607
+ ] }
608
+ },
609
+ {
610
+ field_name: "status",
611
+ type: L,
612
+ ui_type: "SingleSelect",
613
+ property: { options: [
614
+ { name: "TODO" },
615
+ { name: "TEST_SPEC" },
616
+ { name: "DOING" },
617
+ { name: "TESTING" },
618
+ { name: "VERIFIED" },
619
+ { name: "DONE" },
620
+ { name: "BLOCKED" }
621
+ ] }
622
+ },
623
+ {
624
+ field_name: "priority",
625
+ type: L,
626
+ ui_type: "SingleSelect",
627
+ property: { options: [
628
+ { name: "P0" },
629
+ { name: "P1" },
630
+ { name: "P2" },
631
+ { name: "P3" }
632
+ ] }
633
+ },
634
+ {
635
+ field_name: "assignee",
636
+ type: I,
637
+ ui_type: "Text"
638
+ },
639
+ {
640
+ field_name: "description",
641
+ type: I,
642
+ ui_type: "Text"
643
+ },
644
+ {
645
+ field_name: "doc_url",
646
+ type: R,
647
+ ui_type: "Url"
648
+ },
649
+ {
650
+ field_name: "previous_status",
651
+ type: I,
652
+ ui_type: "Text"
653
+ }
654
+ ];
655
+ }
656
+ function V(e) {
657
+ e.command("init").description("Initialize project — create or link a Bitable table").option("--url <url>", "Link an existing table by URL").option("--force", "Force reinitialize (recreates table)").action(async (n) => {
658
+ let r = e.opts().profile, i = f();
659
+ i || (console.error("No config found. Run `fz-task-cli auth init` to set up."), process.exit(3));
660
+ let a;
661
+ try {
662
+ a = p(i, r);
663
+ } catch (e) {
664
+ console.error(e.message), process.exit(3);
665
+ }
666
+ let o = a.bitable.appToken;
667
+ o || (console.error("No appToken in global config. Run `fz-task-cli auth init` to set up."), process.exit(3));
668
+ let s = new t.Client({
669
+ appId: a.auth.appId,
670
+ appSecret: a.auth.appSecret,
671
+ appType: t.AppType.SelfBuild,
672
+ domain: t.Domain.Feishu
673
+ });
674
+ n.url ? await H(n.url, o, n.force) : await U(s, o, n.force);
675
+ });
676
+ }
677
+ async function H(e, t, n) {
678
+ let r, i;
679
+ try {
680
+ let t = l(e);
681
+ r = t.appToken, i = t.tableId;
682
+ } catch (e) {
683
+ console.error(`URL parsing failed: ${e.message}`), process.exit(2);
684
+ }
685
+ i || (console.error("URL must contain a table parameter (e.g., ?table=tblXXX)."), process.exit(2)), v() && !n && (console.error("Already initialized. Use --force to reinitialize."), process.exit(2));
686
+ let a = { tableId: i };
687
+ r !== t && (a.appToken = r), y(a), console.log(`Project linked to existing table: ${i}`);
688
+ }
689
+ async function U(e, t, n) {
690
+ let i = v();
691
+ if (i && !n && (console.error("Already initialized. Use --force to reinitialize."), process.exit(2)), i && n) {
692
+ console.log(`Deleting old table: ${i.tableId}...`);
693
+ try {
694
+ await e.bitable.appTable.delete({ path: {
695
+ app_token: i.appToken ?? t,
696
+ table_id: i.tableId
697
+ } });
698
+ } catch (e) {
699
+ console.warn(`Warning: Could not delete old table: ${e.message}`);
700
+ }
701
+ }
702
+ let a = `fz-task-${r.basename(process.cwd())}`;
703
+ console.log(`Creating table "${a}"...`);
704
+ let o = await e.bitable.appTable.create({
705
+ path: { app_token: t },
706
+ data: { table: {
707
+ name: a,
708
+ fields: B()
709
+ } }
710
+ });
711
+ o.code !== 0 && (console.error(`Failed to create table: ${o.msg} (code: ${o.code})`), process.exit(3));
712
+ let s = o.data.table_id, c = await e.bitable.appTableField.create({
713
+ path: {
714
+ app_token: t,
715
+ table_id: s
716
+ },
717
+ data: {
718
+ field_name: "parent",
719
+ type: z,
720
+ ui_type: "SingleLink",
721
+ property: { table_id: s }
722
+ }
723
+ });
724
+ c.code !== 0 && console.warn(`Warning: Failed to create parent field: ${c.msg}`), y({ tableId: s }), console.log(`Project initialized! Table ID: ${s}`);
725
+ }
726
+ //#endregion
727
+ //#region src/commands/status.ts
728
+ function W(e) {
729
+ e.command("status").description("Show current config status (global + project)").action(async () => {
730
+ let t = e.opts().profile, n = f();
731
+ if (!n) {
732
+ console.log("Global Auth: Not configured. Run `fz-task auth init`"), console.log("App Token: —"), console.log("Project: —");
733
+ return;
734
+ }
735
+ let r;
736
+ try {
737
+ r = p(n, t);
738
+ } catch (e) {
739
+ console.log(`Global Auth: Error — ${e.message}`), console.log("App Token: —"), console.log("Project: —");
740
+ return;
741
+ }
742
+ let i = r.auth.appId, a = i.length > 10 ? `${i.slice(0, 6)}...${i.slice(-4)}` : i;
743
+ console.log(`Global Auth: OK (App ID: ${a})`);
744
+ let o = v();
745
+ o ? (o.appToken ? console.log(`App Token: ${o.appToken} (project override)`) : console.log(`App Token: ${r.bitable.appToken}`), console.log(`Project: Initialized (Table ID: ${o.tableId})`)) : (console.log(`App Token: ${r.bitable.appToken}`), console.log("Project: Not initialized. Run `fz-task init`"));
746
+ });
747
+ }
748
+ //#endregion
595
749
  //#region src/cli.ts
596
- var R = new e();
597
- R.name("fz-task-cli").description("Feishu Bitable task management CLI").version("0.1.0").option("--profile <name>", "config profile name").option("--format <format>", "output format (json|table)", "table"), S(R), j(R), P(R), L(R), R.parse();
750
+ var G = new e();
751
+ G.name("fz-task-cli").description("Feishu Bitable task management CLI").version("0.1.0").option("--profile <name>", "config profile name").option("--format <format>", "output format (json|table)", "table"), x(G), V(G), W(G), N(G), P(G), F(G), G.parse();
598
752
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanzie/task-cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "CLI tool for managing Feature/UserStory/AC task hierarchies in Feishu Bitable",
5
5
  "type": "module",
6
6
  "main": "index.js",