@jvittechs/j 1.0.58 → 1.0.60

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.
@@ -1,6 +1,7 @@
1
1
  import {
2
+ ConfigService,
2
3
  SettingsService
3
- } from "./chunk-Z464RBPB.js";
4
+ } from "./chunk-OJYJGHQX.js";
4
5
 
5
6
  // src/commands/tasks/summary.ts
6
7
  import { Command } from "commander";
@@ -8,8 +9,8 @@ import chalk2 from "chalk";
8
9
  import boxen from "boxen";
9
10
 
10
11
  // src/services/task.service.ts
11
- import { promises as fs2, existsSync } from "fs";
12
- import { join as join2, dirname } from "path";
12
+ import { promises as fs, existsSync } from "fs";
13
+ import { join, dirname } from "path";
13
14
  import { execSync } from "child_process";
14
15
  import chalk from "chalk";
15
16
 
@@ -51,77 +52,52 @@ var PRIORITY_LABELS = {
51
52
  3: "Low"
52
53
  };
53
54
 
54
- // src/services/config.service.ts
55
- import { promises as fs } from "fs";
56
- import { join } from "path";
57
- import { homedir } from "os";
58
- var ConfigService = class {
59
- configDir;
60
- configPath;
61
- constructor() {
62
- this.configDir = join(homedir(), ".jai1");
63
- this.configPath = join(this.configDir, "config.json");
64
- }
65
- /**
66
- * Check if config file exists
67
- */
68
- async exists() {
55
+ // src/services/cloud-task-provider.ts
56
+ function mapCloudToLocal(ct) {
57
+ return {
58
+ id: ct.task_id,
59
+ type: ct.type,
60
+ parent: ct.parent || "",
61
+ title: ct.title,
62
+ status: ct.status,
63
+ assigned_to: ct.assigned_to || "",
64
+ claimed_at: ct.claimed_at || "",
65
+ priority: ct.priority ?? 2,
66
+ depends_on: Array.isArray(ct.depends_on) ? ct.depends_on : parseJsonArray(ct.depends_on),
67
+ tags: Array.isArray(ct.tags) ? ct.tags : parseJsonArray(ct.tags),
68
+ branch: ct.branch || "",
69
+ notes: ct.notes || "",
70
+ created: ct.created_at?.split("T")[0] || ct.created_at || "",
71
+ updated: ct.updated_at?.split("T")[0] || ct.updated_at || ""
72
+ };
73
+ }
74
+ function parseJsonArray(val) {
75
+ if (Array.isArray(val)) return val;
76
+ if (typeof val === "string") {
69
77
  try {
70
- await fs.access(this.configPath);
71
- return true;
78
+ return JSON.parse(val);
72
79
  } catch {
73
- return false;
74
- }
75
- }
76
- /**
77
- * Load configuration from file
78
- * @returns Config object or null if not found
79
- */
80
- async load() {
81
- if (!await this.exists()) {
82
- return null;
83
- }
84
- try {
85
- const content = await fs.readFile(this.configPath, "utf-8");
86
- return JSON.parse(content);
87
- } catch (error) {
88
- throw new Error(
89
- `Failed to load config: ${error instanceof Error ? error.message : String(error)}`
90
- );
91
- }
92
- }
93
- /**
94
- * Save configuration to file
95
- * Creates directory if it doesn't exist
96
- * Sets proper file permissions (600)
97
- */
98
- async save(config) {
99
- try {
100
- await fs.mkdir(this.configDir, { recursive: true, mode: 448 });
101
- await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), {
102
- mode: 384
103
- });
104
- } catch (error) {
105
- throw new Error(
106
- `Failed to save config: ${error instanceof Error ? error.message : String(error)}`
107
- );
80
+ return [];
108
81
  }
109
82
  }
110
- /**
111
- * Get config file path
112
- */
113
- getConfigPath() {
114
- return this.configPath;
115
- }
116
- /**
117
- * Get config directory path
118
- */
119
- getConfigDir() {
120
- return this.configDir;
121
- }
122
- };
123
-
124
- // src/services/cloud-task-provider.ts
83
+ return [];
84
+ }
85
+ function mapLocalToCloud(task) {
86
+ return {
87
+ task_id: task.id,
88
+ title: task.title,
89
+ type: task.type || "task",
90
+ parent: task.parent || "",
91
+ status: task.status,
92
+ assigned_to: task.assigned_to || "",
93
+ claimed_at: task.claimed_at || "",
94
+ priority: task.priority ?? 2,
95
+ depends_on: task.depends_on || [],
96
+ tags: task.tags || [],
97
+ branch: task.branch || "",
98
+ notes: task.notes || ""
99
+ };
100
+ }
125
101
  var CloudTaskProvider = class {
126
102
  apiUrl;
127
103
  accessKey;
@@ -176,16 +152,19 @@ var CloudTaskProvider = class {
176
152
  // CRUD
177
153
  // ============================================
178
154
  async readAll() {
179
- return this.request(`/api/tasks?projectId=${encodeURIComponent(this.projectId)}`);
155
+ const rows = await this.request(
156
+ `/api/tasks?projectId=${encodeURIComponent(this.projectId)}`
157
+ );
158
+ return rows.map(mapCloudToLocal);
180
159
  }
181
160
  async add(input) {
182
161
  const all = await this.readAll().catch(() => []);
183
162
  const maxNum = all.reduce((max, t) => {
184
163
  const n = parseInt(t.id.replace("T-", ""), 10);
185
- return n > max ? n : max;
164
+ return isNaN(n) ? max : Math.max(max, n);
186
165
  }, 0);
187
166
  const taskId = `T-${String(maxNum + 1).padStart(3, "0")}`;
188
- return this.request("/api/tasks", {
167
+ const row = await this.request("/api/tasks", {
189
168
  method: "POST",
190
169
  body: JSON.stringify({
191
170
  task_id: taskId,
@@ -200,15 +179,17 @@ var CloudTaskProvider = class {
200
179
  notes: input.notes || ""
201
180
  })
202
181
  });
182
+ return mapCloudToLocal(row);
203
183
  }
204
184
  async update(id, updates) {
205
- return this.request(
185
+ const row = await this.request(
206
186
  `/api/tasks/${encodeURIComponent(id)}?projectId=${encodeURIComponent(this.projectId)}`,
207
187
  {
208
188
  method: "PATCH",
209
189
  body: JSON.stringify(updates)
210
190
  }
211
191
  );
192
+ return mapCloudToLocal(row);
212
193
  }
213
194
  async deleteTask(id) {
214
195
  await this.request(
@@ -230,13 +211,15 @@ var CloudTaskProvider = class {
230
211
  if (options?.status) params.set("status", options.status);
231
212
  if (options?.type) params.set("type", options.type);
232
213
  if (options?.parent !== void 0) params.set("parent", options.parent);
233
- return this.request(`/api/tasks?${params}`);
214
+ const rows = await this.request(`/api/tasks?${params}`);
215
+ return rows.map(mapCloudToLocal);
234
216
  }
235
217
  async getById(id) {
236
218
  try {
237
- return await this.request(
219
+ const row = await this.request(
238
220
  `/api/tasks/${encodeURIComponent(id)}?projectId=${encodeURIComponent(this.projectId)}`
239
221
  );
222
+ return mapCloudToLocal(row);
240
223
  } catch {
241
224
  return null;
242
225
  }
@@ -284,22 +267,33 @@ var CloudTaskProvider = class {
284
267
  // WORKFLOW
285
268
  // ============================================
286
269
  async getReady() {
287
- return this.request(`/api/tasks/ready?projectId=${encodeURIComponent(this.projectId)}`);
270
+ const rows = await this.request(
271
+ `/api/tasks/ready?projectId=${encodeURIComponent(this.projectId)}`
272
+ );
273
+ return rows.map(mapCloudToLocal);
288
274
  }
289
275
  async pick(id, agentId) {
290
- return this.request(`/api/tasks/${encodeURIComponent(id)}/pick`, {
291
- method: "POST",
292
- body: JSON.stringify({
293
- project_id: this.projectId,
294
- agent_id: agentId || "cli"
295
- })
296
- });
276
+ const row = await this.request(
277
+ `/api/tasks/${encodeURIComponent(id)}/pick`,
278
+ {
279
+ method: "POST",
280
+ body: JSON.stringify({
281
+ project_id: this.projectId,
282
+ agent_id: agentId || "cli"
283
+ })
284
+ }
285
+ );
286
+ return mapCloudToLocal(row);
297
287
  }
298
288
  async markDone(id) {
299
- return this.request(`/api/tasks/${encodeURIComponent(id)}/done`, {
300
- method: "POST",
301
- body: JSON.stringify({ project_id: this.projectId })
302
- });
289
+ const row = await this.request(
290
+ `/api/tasks/${encodeURIComponent(id)}/done`,
291
+ {
292
+ method: "POST",
293
+ body: JSON.stringify({ project_id: this.projectId })
294
+ }
295
+ );
296
+ return mapCloudToLocal(row);
303
297
  }
304
298
  async cancel(id) {
305
299
  await this.request(`/api/tasks/${encodeURIComponent(id)}/cancel`, {
@@ -317,19 +311,19 @@ var CloudTaskProvider = class {
317
311
  }
318
312
  async push(tasks) {
319
313
  for (const task of tasks) {
314
+ const cloudFields = mapLocalToCloud(task);
320
315
  try {
321
316
  await this.request(
322
317
  `/api/tasks/${encodeURIComponent(task.id)}?projectId=${encodeURIComponent(this.projectId)}`,
323
- { method: "PATCH", body: JSON.stringify(task) }
318
+ { method: "PATCH", body: JSON.stringify(cloudFields) }
324
319
  );
325
320
  } catch {
326
321
  try {
327
322
  await this.request("/api/tasks", {
328
323
  method: "POST",
329
324
  body: JSON.stringify({
330
- task_id: task.id,
331
- project_id: this.projectId,
332
- ...task
325
+ ...cloudFields,
326
+ project_id: this.projectId
333
327
  })
334
328
  });
335
329
  } catch {
@@ -349,27 +343,45 @@ var TaskService = class {
349
343
  _providerReady;
350
344
  constructor(cwd) {
351
345
  this.cwd = cwd || process.cwd();
352
- this.tasksPath = join2(this.cwd, TASKS_FILE);
346
+ this.tasksPath = join(this.cwd, TASKS_FILE);
353
347
  this._providerReady = this._initCloudProvider();
354
348
  }
355
349
  /**
356
- * Initialise cloud provider if settings.yaml has tasks.cloud = true
350
+ * Initialise cloud provider if settings.yaml has tasks.cloud = true.
351
+ * Auto-registers the project on the server on first use.
357
352
  */
358
353
  async _initCloudProvider() {
359
354
  try {
360
355
  const settings = new SettingsService(this.cwd);
361
356
  if (!settings.isTaskCloudEnabled()) return;
362
- const projectId = settings.getProjectId();
363
- if (!projectId) return;
364
357
  const config = await new ConfigService().load();
365
358
  if (!config?.apiUrl || !config?.accessKey) return;
366
- this._cloudProvider = new CloudTaskProvider(config, projectId);
359
+ const repoUrl = settings.resolveGitRepoUrl();
360
+ if (!repoUrl) return;
361
+ let projectId = settings.getProjectId();
362
+ if (!projectId) return;
363
+ const provider = new CloudTaskProvider(config, projectId);
364
+ try {
365
+ const registeredId = await provider.ensureProjectRegistered(repoUrl);
366
+ if (registeredId && registeredId !== projectId) {
367
+ projectId = registeredId;
368
+ await settings.set("tasks.projectId", registeredId);
369
+ this._cloudProvider = new CloudTaskProvider(config, registeredId);
370
+ return;
371
+ }
372
+ } catch {
373
+ }
374
+ this._cloudProvider = provider;
367
375
  } catch {
368
376
  }
369
377
  }
370
378
  /**
371
379
  * Wait for provider initialisation (call before any cloud operation)
372
380
  */
381
+ async waitForInit() {
382
+ await this._providerReady;
383
+ }
384
+ /** @internal alias kept for backward compat */
373
385
  async ready() {
374
386
  await this._providerReady;
375
387
  }
@@ -390,7 +402,7 @@ var TaskService = class {
390
402
  * If not, print a helpful message and exit.
391
403
  */
392
404
  static ensureJai1Dir(cwd) {
393
- const dir = join2(cwd || process.cwd(), ".jai1");
405
+ const dir = join(cwd || process.cwd(), ".jai1");
394
406
  if (!existsSync(dir)) {
395
407
  console.error(chalk.red("\u274C Th\u01B0 m\u1EE5c .jai1 kh\xF4ng t\u1ED3n t\u1EA1i trong project n\xE0y."));
396
408
  console.error("");
@@ -407,12 +419,16 @@ var TaskService = class {
407
419
  // READ
408
420
  // ============================================
409
421
  /**
410
- * Read all tasks from JSONL file
422
+ * Read all tasks from JSONL file (or cloud API if cloud mode)
411
423
  */
412
424
  async readAll() {
425
+ await this.ready();
426
+ if (this._cloudProvider) {
427
+ return this._cloudProvider.readAll();
428
+ }
413
429
  try {
414
430
  await this.ensureTasksFileNotDirectory();
415
- const content = await fs2.readFile(this.tasksPath, "utf-8");
431
+ const content = await fs.readFile(this.tasksPath, "utf-8");
416
432
  const lines = content.trim().split("\n").filter(Boolean);
417
433
  return lines.map((line) => TaskSchema.parse(JSON.parse(line)));
418
434
  } catch (error) {
@@ -426,6 +442,10 @@ var TaskService = class {
426
442
  * Find task by ID
427
443
  */
428
444
  async findById(id) {
445
+ await this.ready();
446
+ if (this._cloudProvider) {
447
+ return this._cloudProvider.getById(id);
448
+ }
429
449
  const tasks = await this.readAll();
430
450
  return tasks.find((t) => t.id === id) || null;
431
451
  }
@@ -433,6 +453,17 @@ var TaskService = class {
433
453
  * Filter tasks by criteria
434
454
  */
435
455
  async filter(criteria) {
456
+ await this.ready();
457
+ if (this._cloudProvider) {
458
+ const listInput = {};
459
+ if (criteria.status) listInput.status = criteria.status;
460
+ if (criteria.parent) listInput.parent = criteria.parent;
461
+ const tasks2 = await this._cloudProvider.list(listInput);
462
+ if (criteria.assignee) {
463
+ return tasks2.filter((t) => t.assigned_to === criteria.assignee);
464
+ }
465
+ return tasks2;
466
+ }
436
467
  const tasks = await this.readAll();
437
468
  return tasks.filter((t) => {
438
469
  if (criteria.status && t.status !== criteria.status) return false;
@@ -446,6 +477,12 @@ var TaskService = class {
446
477
  * status=todo, all depends_on done or cancelled, assigned_to empty
447
478
  */
448
479
  async getReady(parent) {
480
+ await this.ready();
481
+ if (this._cloudProvider) {
482
+ const ready = await this._cloudProvider.getReady();
483
+ if (parent) return ready.filter((t) => t.parent === parent);
484
+ return ready;
485
+ }
449
486
  const tasks = await this.readAll();
450
487
  const resolvedIds = new Set(
451
488
  tasks.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
@@ -478,6 +515,11 @@ var TaskService = class {
478
515
  * Get stats by status
479
516
  */
480
517
  async getStats() {
518
+ await this.ready();
519
+ if (this._cloudProvider) {
520
+ const stats = await this._cloudProvider.getStats();
521
+ return { ...stats, blocked: 0 };
522
+ }
481
523
  const tasks = await this.readAll();
482
524
  const resolvedIds = new Set(
483
525
  tasks.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
@@ -507,6 +549,12 @@ var TaskService = class {
507
549
  * todo = otherwise (blocked or waiting)
508
550
  */
509
551
  async getParents(statusFilter) {
552
+ await this.ready();
553
+ if (this._cloudProvider) {
554
+ const parents2 = await this._cloudProvider.getParents();
555
+ if (statusFilter) return parents2.filter((p) => p.status === statusFilter);
556
+ return parents2;
557
+ }
510
558
  const tasks = await this.readAll();
511
559
  const resolvedIds = new Set(
512
560
  tasks.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id)
@@ -621,6 +669,10 @@ var TaskService = class {
621
669
  * Update a task by ID
622
670
  */
623
671
  async update(id, changes) {
672
+ await this.ready();
673
+ if (this._cloudProvider) {
674
+ return this._cloudProvider.update(id, changes);
675
+ }
624
676
  const tasks = await this.readAll();
625
677
  const index = tasks.findIndex((t) => t.id === id);
626
678
  if (index === -1) {
@@ -636,6 +688,7 @@ var TaskService = class {
636
688
  * Add dependency: child depends on parent
637
689
  */
638
690
  async addDependency(childId, parentId) {
691
+ await this.ready();
639
692
  const tasks = await this.readAll();
640
693
  const child = tasks.find((t) => t.id === childId);
641
694
  const parent = tasks.find((t) => t.id === parentId);
@@ -656,6 +709,10 @@ var TaskService = class {
656
709
  * Mark task as done
657
710
  */
658
711
  async markDone(id) {
712
+ await this.ready();
713
+ if (this._cloudProvider) {
714
+ return this._cloudProvider.markDone(id);
715
+ }
659
716
  return this.update(id, { status: "done" });
660
717
  }
661
718
  /**
@@ -669,6 +726,10 @@ var TaskService = class {
669
726
  * Cancel a task: set status=cancelled, clear assignment
670
727
  */
671
728
  async cancel(id) {
729
+ await this.ready();
730
+ if (this._cloudProvider) {
731
+ return this._cloudProvider.cancel(id);
732
+ }
672
733
  return this.update(id, {
673
734
  status: "cancelled",
674
735
  assigned_to: "",
@@ -679,6 +740,10 @@ var TaskService = class {
679
740
  * Delete a task completely and clean up depends_on references
680
741
  */
681
742
  async deleteTask(id) {
743
+ await this.ready();
744
+ if (this._cloudProvider) {
745
+ return this._cloudProvider.deleteTask(id);
746
+ }
682
747
  return this.deleteTasks([id]);
683
748
  }
684
749
  /**
@@ -686,6 +751,13 @@ var TaskService = class {
686
751
  * Validates all IDs exist before deleting any.
687
752
  */
688
753
  async deleteTasks(ids) {
754
+ await this.ready();
755
+ if (this._cloudProvider) {
756
+ for (const id of ids) {
757
+ await this._cloudProvider.deleteTask(id);
758
+ }
759
+ return;
760
+ }
689
761
  const tasks = await this.readAll();
690
762
  const deleteSet = new Set(ids);
691
763
  const notFound = ids.filter((id) => !tasks.find((t) => t.id === id));
@@ -708,6 +780,15 @@ var TaskService = class {
708
780
  * Returns the list of deleted tasks for display purposes.
709
781
  */
710
782
  async deleteGroup(parentName) {
783
+ await this.ready();
784
+ if (this._cloudProvider) {
785
+ const groupTasks2 = await this._cloudProvider.list({ parent: parentName });
786
+ if (groupTasks2.length === 0) {
787
+ throw new Error(`No tasks found in group: ${parentName}`);
788
+ }
789
+ await this._cloudProvider.deleteGroup(parentName);
790
+ return groupTasks2;
791
+ }
711
792
  const tasks = await this.readAll();
712
793
  const groupTasks = tasks.filter((t) => t.parent === parentName);
713
794
  if (groupTasks.length === 0) {
@@ -721,6 +802,10 @@ var TaskService = class {
721
802
  * Pick next task: claim for current user
722
803
  */
723
804
  async pick(taskId) {
805
+ await this.ready();
806
+ if (this._cloudProvider) {
807
+ return this._cloudProvider.pick(taskId, this.getCurrentUser());
808
+ }
724
809
  const username = this.getCurrentUser();
725
810
  const now = (/* @__PURE__ */ new Date()).toISOString();
726
811
  return this.update(taskId, {
@@ -814,9 +899,9 @@ var TaskService = class {
814
899
  * - Dirty working tree → stash/unstash automatically
815
900
  */
816
901
  async syncPush() {
817
- const cwd = process.cwd();
902
+ const cwd = this.cwd;
818
903
  const branch = SYNC_BRANCH;
819
- const tasksFullPath = join2(cwd, TASKS_FILE);
904
+ const tasksFullPath = join(cwd, TASKS_FILE);
820
905
  if (!existsSync(tasksFullPath)) {
821
906
  return;
822
907
  }
@@ -880,7 +965,7 @@ var TaskService = class {
880
965
  * - tasks.jsonl doesn't exist on remote branch → returns empty
881
966
  */
882
967
  async syncPull() {
883
- const cwd = process.cwd();
968
+ const cwd = this.cwd;
884
969
  const branch = SYNC_BRANCH;
885
970
  let merged = 0;
886
971
  let conflicts = 0;
@@ -926,16 +1011,16 @@ var TaskService = class {
926
1011
  async appendTask(task) {
927
1012
  await this.ensureTasksFileNotDirectory();
928
1013
  const dir = dirname(this.tasksPath);
929
- await fs2.mkdir(dir, { recursive: true });
1014
+ await fs.mkdir(dir, { recursive: true });
930
1015
  const line = JSON.stringify(task) + "\n";
931
- await fs2.appendFile(this.tasksPath, line, "utf-8");
1016
+ await fs.appendFile(this.tasksPath, line, "utf-8");
932
1017
  }
933
1018
  async writeAll(tasks) {
934
1019
  await this.ensureTasksFileNotDirectory();
935
1020
  const dir = dirname(this.tasksPath);
936
- await fs2.mkdir(dir, { recursive: true });
1021
+ await fs.mkdir(dir, { recursive: true });
937
1022
  const content = tasks.map((t) => JSON.stringify(t)).join("\n") + "\n";
938
- await fs2.writeFile(this.tasksPath, content, "utf-8");
1023
+ await fs.writeFile(this.tasksPath, content, "utf-8");
939
1024
  }
940
1025
  getCurrentUser() {
941
1026
  try {
@@ -963,7 +1048,7 @@ var TaskService = class {
963
1048
  }
964
1049
  async ensureTasksFileNotDirectory() {
965
1050
  try {
966
- const stat = await fs2.stat(this.tasksPath);
1051
+ const stat = await fs.stat(this.tasksPath);
967
1052
  if (stat.isDirectory()) {
968
1053
  throw new Error(
969
1054
  `Invalid tasks path: expected file at ${TASKS_FILE} but found a directory. Please remove the directory and re-run the command.`
@@ -1030,7 +1115,6 @@ function createTaskSummaryCommand() {
1030
1115
  }
1031
1116
 
1032
1117
  export {
1033
- ConfigService,
1034
1118
  STATUS_ICONS,
1035
1119
  BLOCKED_ICON,
1036
1120
  PRIORITY_ICONS,
@@ -1039,4 +1123,4 @@ export {
1039
1123
  handleTaskSummary,
1040
1124
  createTaskSummaryCommand
1041
1125
  };
1042
- //# sourceMappingURL=chunk-S7QUPWSM.js.map
1126
+ //# sourceMappingURL=chunk-22FXNN76.js.map