@jvittechs/j 1.0.57 → 1.0.58

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.
@@ -0,0 +1,11 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export {
9
+ __require
10
+ };
11
+ //# sourceMappingURL=chunk-DGUM43GV.js.map
@@ -0,0 +1,43 @@
1
+ import {
2
+ SettingsService
3
+ } from "./chunk-Z464RBPB.js";
4
+
5
+ // src/commands/settings/show.ts
6
+ import { Command } from "commander";
7
+ import chalk from "chalk";
8
+ import YAML from "yaml";
9
+ function createSettingsShowCommand() {
10
+ return new Command("show").description("Hi\u1EC3n th\u1ECB all settings").option("-j, --json", "Output JSON").action(async (options) => {
11
+ const service = new SettingsService();
12
+ const settings = service.load();
13
+ if (options.json) {
14
+ console.log(JSON.stringify(settings, null, 2));
15
+ return;
16
+ }
17
+ if (!service.exists()) {
18
+ console.log(chalk.dim("No settings file found. Run `j settings init` to create one."));
19
+ console.log("");
20
+ console.log(chalk.dim("Using defaults:"));
21
+ } else {
22
+ console.log(chalk.bold("\u{1F4CB} Project Settings"));
23
+ console.log(chalk.dim(` Path: ${service.getSettingsPath()}`));
24
+ console.log("");
25
+ }
26
+ console.log(YAML.stringify(settings, { indent: 2, lineWidth: 0 }).trim());
27
+ const repoUrl = service.resolveGitRepoUrl();
28
+ if (repoUrl) {
29
+ console.log("");
30
+ console.log(chalk.dim("Derived:"));
31
+ console.log(` ${chalk.dim("repo_url:")} ${repoUrl}`);
32
+ const projectId = service.getProjectId();
33
+ if (projectId) {
34
+ console.log(` ${chalk.dim("project_id:")} ${projectId}`);
35
+ }
36
+ }
37
+ });
38
+ }
39
+
40
+ export {
41
+ createSettingsShowCommand
42
+ };
43
+ //# sourceMappingURL=chunk-DVBQLA4R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/settings/show.ts"],"sourcesContent":["/**\n * jai1 settings show [-j]\n */\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport YAML from 'yaml';\nimport { SettingsService } from '../../services/settings.service.js';\nimport type { SettingsShowOptions } from '../../types/settings.types.js';\n\nexport function createSettingsShowCommand(): Command {\n return new Command('show')\n .description('Hiển thị all settings')\n .option('-j, --json', 'Output JSON')\n .action(async (options: SettingsShowOptions) => {\n const service = new SettingsService();\n const settings = service.load();\n\n if (options.json) {\n console.log(JSON.stringify(settings, null, 2));\n return;\n }\n\n if (!service.exists()) {\n console.log(chalk.dim('No settings file found. Run `j settings init` to create one.'));\n console.log('');\n console.log(chalk.dim('Using defaults:'));\n } else {\n console.log(chalk.bold('📋 Project Settings'));\n console.log(chalk.dim(` Path: ${service.getSettingsPath()}`));\n console.log('');\n }\n\n console.log(YAML.stringify(settings, { indent: 2, lineWidth: 0 }).trim());\n\n // Show derived info\n const repoUrl = service.resolveGitRepoUrl();\n if (repoUrl) {\n console.log('');\n console.log(chalk.dim('Derived:'));\n console.log(` ${chalk.dim('repo_url:')} ${repoUrl}`);\n const projectId = service.getProjectId();\n if (projectId) {\n console.log(` ${chalk.dim('project_id:')} ${projectId}`);\n }\n }\n });\n}\n"],"mappings":";;;;;AAGA,SAAS,eAAe;AACxB,OAAO,WAAW;AAClB,OAAO,UAAU;AAIV,SAAS,4BAAqC;AACjD,SAAO,IAAI,QAAQ,MAAM,EACpB,YAAY,iCAAuB,EACnC,OAAO,cAAc,aAAa,EAClC,OAAO,OAAO,YAAiC;AAC5C,UAAM,UAAU,IAAI,gBAAgB;AACpC,UAAM,WAAW,QAAQ,KAAK;AAE9B,QAAI,QAAQ,MAAM;AACd,cAAQ,IAAI,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAC7C;AAAA,IACJ;AAEA,QAAI,CAAC,QAAQ,OAAO,GAAG;AACnB,cAAQ,IAAI,MAAM,IAAI,8DAA8D,CAAC;AACrF,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,MAAM,IAAI,iBAAiB,CAAC;AAAA,IAC5C,OAAO;AACH,cAAQ,IAAI,MAAM,KAAK,4BAAqB,CAAC;AAC7C,cAAQ,IAAI,MAAM,IAAI,YAAY,QAAQ,gBAAgB,CAAC,EAAE,CAAC;AAC9D,cAAQ,IAAI,EAAE;AAAA,IAClB;AAEA,YAAQ,IAAI,KAAK,UAAU,UAAU,EAAE,QAAQ,GAAG,WAAW,EAAE,CAAC,EAAE,KAAK,CAAC;AAGxE,UAAM,UAAU,QAAQ,kBAAkB;AAC1C,QAAI,SAAS;AACT,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,MAAM,IAAI,UAAU,CAAC;AACjC,cAAQ,IAAI,KAAK,MAAM,IAAI,WAAW,CAAC,MAAM,OAAO,EAAE;AACtD,YAAM,YAAY,QAAQ,aAAa;AACvC,UAAI,WAAW;AACX,gBAAQ,IAAI,KAAK,MAAM,IAAI,aAAa,CAAC,IAAI,SAAS,EAAE;AAAA,MAC5D;AAAA,IACJ;AAAA,EACJ,CAAC;AACT;","names":[]}
@@ -1,11 +1,15 @@
1
+ import {
2
+ SettingsService
3
+ } from "./chunk-Z464RBPB.js";
4
+
1
5
  // src/commands/tasks/summary.ts
2
6
  import { Command } from "commander";
3
7
  import chalk2 from "chalk";
4
8
  import boxen from "boxen";
5
9
 
6
10
  // src/services/task.service.ts
7
- import { promises as fs, existsSync } from "fs";
8
- import { join, dirname } from "path";
11
+ import { promises as fs2, existsSync } from "fs";
12
+ import { join as join2, dirname } from "path";
9
13
  import { execSync } from "child_process";
10
14
  import chalk from "chalk";
11
15
 
@@ -13,6 +17,7 @@ import chalk from "chalk";
13
17
  import { z } from "zod";
14
18
  var TaskSchema = z.object({
15
19
  id: z.string().regex(/^T-\d+$/),
20
+ type: z.enum(["feature", "bug", "plan", "task", "prd", "prompt"]).default("task"),
16
21
  parent: z.string().default(""),
17
22
  title: z.string().min(1).max(500),
18
23
  status: z.enum(["todo", "in_progress", "done", "cancelled"]),
@@ -46,20 +51,346 @@ var PRIORITY_LABELS = {
46
51
  3: "Low"
47
52
  };
48
53
 
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() {
69
+ try {
70
+ await fs.access(this.configPath);
71
+ return true;
72
+ } 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
+ );
108
+ }
109
+ }
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
125
+ var CloudTaskProvider = class {
126
+ apiUrl;
127
+ accessKey;
128
+ projectId;
129
+ constructor(config, projectId) {
130
+ this.apiUrl = config.apiUrl.replace(/\/$/, "");
131
+ this.accessKey = config.accessKey;
132
+ this.projectId = projectId;
133
+ }
134
+ // ============================================
135
+ // HELPERS
136
+ // ============================================
137
+ headers() {
138
+ return {
139
+ "Content-Type": "application/json",
140
+ "JAI1-Access-Key": this.accessKey
141
+ };
142
+ }
143
+ async request(path, options = {}) {
144
+ const url = `${this.apiUrl}${path}`;
145
+ const res = await fetch(url, {
146
+ ...options,
147
+ headers: { ...this.headers(), ...options.headers || {} }
148
+ });
149
+ if (!res.ok) {
150
+ let errMsg = `API error ${res.status}`;
151
+ try {
152
+ const body = await res.json();
153
+ if (body.error) errMsg = body.error;
154
+ } catch {
155
+ }
156
+ throw new Error(errMsg);
157
+ }
158
+ const data = await res.json();
159
+ if (!data.success) throw new Error(data.error || "API returned failure");
160
+ return data.data;
161
+ }
162
+ // ============================================
163
+ // ENSURE PROJECT REGISTERED
164
+ // ============================================
165
+ async ensureProjectRegistered(repoUrl) {
166
+ const result = await this.request(
167
+ "/api/projects/register",
168
+ {
169
+ method: "POST",
170
+ body: JSON.stringify({ repo_url: repoUrl })
171
+ }
172
+ );
173
+ return result.project_id;
174
+ }
175
+ // ============================================
176
+ // CRUD
177
+ // ============================================
178
+ async readAll() {
179
+ return this.request(`/api/tasks?projectId=${encodeURIComponent(this.projectId)}`);
180
+ }
181
+ async add(input) {
182
+ const all = await this.readAll().catch(() => []);
183
+ const maxNum = all.reduce((max, t) => {
184
+ const n = parseInt(t.id.replace("T-", ""), 10);
185
+ return n > max ? n : max;
186
+ }, 0);
187
+ const taskId = `T-${String(maxNum + 1).padStart(3, "0")}`;
188
+ return this.request("/api/tasks", {
189
+ method: "POST",
190
+ body: JSON.stringify({
191
+ task_id: taskId,
192
+ project_id: this.projectId,
193
+ title: input.title,
194
+ type: input.type || "task",
195
+ parent: input.parent || "",
196
+ priority: input.priority ?? 2,
197
+ depends_on: input.depends_on || [],
198
+ tags: input.tags || [],
199
+ branch: input.branch || "",
200
+ notes: input.notes || ""
201
+ })
202
+ });
203
+ }
204
+ async update(id, updates) {
205
+ return this.request(
206
+ `/api/tasks/${encodeURIComponent(id)}?projectId=${encodeURIComponent(this.projectId)}`,
207
+ {
208
+ method: "PATCH",
209
+ body: JSON.stringify(updates)
210
+ }
211
+ );
212
+ }
213
+ async deleteTask(id) {
214
+ await this.request(
215
+ `/api/tasks/${encodeURIComponent(id)}?projectId=${encodeURIComponent(this.projectId)}`,
216
+ { method: "DELETE" }
217
+ );
218
+ }
219
+ async deleteGroup(parent) {
220
+ await this.request(
221
+ `/api/tasks/group/${encodeURIComponent(parent)}?projectId=${encodeURIComponent(this.projectId)}`,
222
+ { method: "DELETE" }
223
+ );
224
+ }
225
+ // ============================================
226
+ // QUERY
227
+ // ============================================
228
+ async list(options) {
229
+ const params = new URLSearchParams({ projectId: this.projectId });
230
+ if (options?.status) params.set("status", options.status);
231
+ if (options?.type) params.set("type", options.type);
232
+ if (options?.parent !== void 0) params.set("parent", options.parent);
233
+ return this.request(`/api/tasks?${params}`);
234
+ }
235
+ async getById(id) {
236
+ try {
237
+ return await this.request(
238
+ `/api/tasks/${encodeURIComponent(id)}?projectId=${encodeURIComponent(this.projectId)}`
239
+ );
240
+ } catch {
241
+ return null;
242
+ }
243
+ }
244
+ async getParents() {
245
+ const parents = await this.request(
246
+ `/api/tasks/parents?projectId=${encodeURIComponent(this.projectId)}`
247
+ );
248
+ const result = [];
249
+ for (const parent of parents) {
250
+ const tasks = await this.list({ parent });
251
+ const info = {
252
+ name: parent,
253
+ total: tasks.length,
254
+ todo: tasks.filter((t) => t.status === "todo").length,
255
+ in_progress: tasks.filter((t) => t.status === "in_progress").length,
256
+ done: tasks.filter((t) => t.status === "done").length,
257
+ cancelled: tasks.filter((t) => t.status === "cancelled").length,
258
+ blocked: 0,
259
+ ready: 0,
260
+ status: "todo"
261
+ };
262
+ if (info.done + info.cancelled === info.total) info.status = "done";
263
+ else if (info.in_progress > 0) info.status = "in_progress";
264
+ else if (info.todo > 0) info.status = "ready";
265
+ result.push(info);
266
+ }
267
+ return result;
268
+ }
269
+ async getStats() {
270
+ const raw = await this.request(
271
+ `/api/tasks/stats?projectId=${encodeURIComponent(this.projectId)}`
272
+ );
273
+ const s = raw.by_status;
274
+ const total = Object.values(s).reduce((a, b) => a + b, 0);
275
+ return {
276
+ total,
277
+ todo: s["todo"] || 0,
278
+ in_progress: s["in_progress"] || 0,
279
+ done: s["done"] || 0,
280
+ cancelled: s["cancelled"] || 0
281
+ };
282
+ }
283
+ // ============================================
284
+ // WORKFLOW
285
+ // ============================================
286
+ async getReady() {
287
+ return this.request(`/api/tasks/ready?projectId=${encodeURIComponent(this.projectId)}`);
288
+ }
289
+ 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
+ });
297
+ }
298
+ async markDone(id) {
299
+ return this.request(`/api/tasks/${encodeURIComponent(id)}/done`, {
300
+ method: "POST",
301
+ body: JSON.stringify({ project_id: this.projectId })
302
+ });
303
+ }
304
+ async cancel(id) {
305
+ await this.request(`/api/tasks/${encodeURIComponent(id)}/cancel`, {
306
+ method: "POST",
307
+ body: JSON.stringify({ project_id: this.projectId })
308
+ });
309
+ const task = await this.getById(id);
310
+ return task;
311
+ }
312
+ // ============================================
313
+ // SYNC
314
+ // ============================================
315
+ async pull() {
316
+ return this.readAll();
317
+ }
318
+ async push(tasks) {
319
+ for (const task of tasks) {
320
+ try {
321
+ await this.request(
322
+ `/api/tasks/${encodeURIComponent(task.id)}?projectId=${encodeURIComponent(this.projectId)}`,
323
+ { method: "PATCH", body: JSON.stringify(task) }
324
+ );
325
+ } catch {
326
+ try {
327
+ await this.request("/api/tasks", {
328
+ method: "POST",
329
+ body: JSON.stringify({
330
+ task_id: task.id,
331
+ project_id: this.projectId,
332
+ ...task
333
+ })
334
+ });
335
+ } catch {
336
+ }
337
+ }
338
+ }
339
+ }
340
+ };
341
+
49
342
  // src/services/task.service.ts
50
343
  var TASKS_FILE = ".jai1/tasks.jsonl";
51
344
  var SYNC_BRANCH = "jai1";
52
345
  var TaskService = class {
53
346
  tasksPath;
347
+ cwd;
348
+ _cloudProvider = null;
349
+ _providerReady;
54
350
  constructor(cwd) {
55
- this.tasksPath = join(cwd || process.cwd(), TASKS_FILE);
351
+ this.cwd = cwd || process.cwd();
352
+ this.tasksPath = join2(this.cwd, TASKS_FILE);
353
+ this._providerReady = this._initCloudProvider();
354
+ }
355
+ /**
356
+ * Initialise cloud provider if settings.yaml has tasks.cloud = true
357
+ */
358
+ async _initCloudProvider() {
359
+ try {
360
+ const settings = new SettingsService(this.cwd);
361
+ if (!settings.isTaskCloudEnabled()) return;
362
+ const projectId = settings.getProjectId();
363
+ if (!projectId) return;
364
+ const config = await new ConfigService().load();
365
+ if (!config?.apiUrl || !config?.accessKey) return;
366
+ this._cloudProvider = new CloudTaskProvider(config, projectId);
367
+ } catch {
368
+ }
369
+ }
370
+ /**
371
+ * Wait for provider initialisation (call before any cloud operation)
372
+ */
373
+ async ready() {
374
+ await this._providerReady;
375
+ }
376
+ /**
377
+ * Returns true when cloud mode is active
378
+ */
379
+ get isCloud() {
380
+ return this._cloudProvider !== null;
381
+ }
382
+ /**
383
+ * Returns cloud provider if active
384
+ */
385
+ get cloud() {
386
+ return this._cloudProvider;
56
387
  }
57
388
  /**
58
389
  * Check if .jai1 directory exists in CWD.
59
390
  * If not, print a helpful message and exit.
60
391
  */
61
392
  static ensureJai1Dir(cwd) {
62
- const dir = join(cwd || process.cwd(), ".jai1");
393
+ const dir = join2(cwd || process.cwd(), ".jai1");
63
394
  if (!existsSync(dir)) {
64
395
  console.error(chalk.red("\u274C Th\u01B0 m\u1EE5c .jai1 kh\xF4ng t\u1ED3n t\u1EA1i trong project n\xE0y."));
65
396
  console.error("");
@@ -81,7 +412,7 @@ var TaskService = class {
81
412
  async readAll() {
82
413
  try {
83
414
  await this.ensureTasksFileNotDirectory();
84
- const content = await fs.readFile(this.tasksPath, "utf-8");
415
+ const content = await fs2.readFile(this.tasksPath, "utf-8");
85
416
  const lines = content.trim().split("\n").filter(Boolean);
86
417
  return lines.map((line) => TaskSchema.parse(JSON.parse(line)));
87
418
  } catch (error) {
@@ -252,10 +583,24 @@ var TaskService = class {
252
583
  * Add a new task
253
584
  */
254
585
  async add(data) {
586
+ await this.ready();
587
+ if (this._cloudProvider) {
588
+ const input = {
589
+ title: data.title,
590
+ ...data.type !== void 0 && { type: data.type },
591
+ ...data.parent !== void 0 && { parent: data.parent },
592
+ ...data.priority !== void 0 && { priority: data.priority },
593
+ ...data.tags !== void 0 && { tags: data.tags },
594
+ ...data.notes !== void 0 && { notes: data.notes },
595
+ ...data.branch !== void 0 && { branch: data.branch }
596
+ };
597
+ return this._cloudProvider.add(input);
598
+ }
255
599
  const id = await this.nextId();
256
600
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
257
601
  const task = TaskSchema.parse({
258
602
  id,
603
+ type: data.type || "task",
259
604
  parent: data.parent || "",
260
605
  title: data.title,
261
606
  status: "todo",
@@ -266,8 +611,8 @@ var TaskService = class {
266
611
  created: now,
267
612
  updated: now,
268
613
  tags: data.tags || [],
269
- branch: "",
270
- notes: ""
614
+ branch: data.branch || "",
615
+ notes: data.notes || ""
271
616
  });
272
617
  await this.appendTask(task);
273
618
  return task;
@@ -471,7 +816,7 @@ var TaskService = class {
471
816
  async syncPush() {
472
817
  const cwd = process.cwd();
473
818
  const branch = SYNC_BRANCH;
474
- const tasksFullPath = join(cwd, TASKS_FILE);
819
+ const tasksFullPath = join2(cwd, TASKS_FILE);
475
820
  if (!existsSync(tasksFullPath)) {
476
821
  return;
477
822
  }
@@ -581,16 +926,16 @@ var TaskService = class {
581
926
  async appendTask(task) {
582
927
  await this.ensureTasksFileNotDirectory();
583
928
  const dir = dirname(this.tasksPath);
584
- await fs.mkdir(dir, { recursive: true });
929
+ await fs2.mkdir(dir, { recursive: true });
585
930
  const line = JSON.stringify(task) + "\n";
586
- await fs.appendFile(this.tasksPath, line, "utf-8");
931
+ await fs2.appendFile(this.tasksPath, line, "utf-8");
587
932
  }
588
933
  async writeAll(tasks) {
589
934
  await this.ensureTasksFileNotDirectory();
590
935
  const dir = dirname(this.tasksPath);
591
- await fs.mkdir(dir, { recursive: true });
936
+ await fs2.mkdir(dir, { recursive: true });
592
937
  const content = tasks.map((t) => JSON.stringify(t)).join("\n") + "\n";
593
- await fs.writeFile(this.tasksPath, content, "utf-8");
938
+ await fs2.writeFile(this.tasksPath, content, "utf-8");
594
939
  }
595
940
  getCurrentUser() {
596
941
  try {
@@ -618,7 +963,7 @@ var TaskService = class {
618
963
  }
619
964
  async ensureTasksFileNotDirectory() {
620
965
  try {
621
- const stat = await fs.stat(this.tasksPath);
966
+ const stat = await fs2.stat(this.tasksPath);
622
967
  if (stat.isDirectory()) {
623
968
  throw new Error(
624
969
  `Invalid tasks path: expected file at ${TASKS_FILE} but found a directory. Please remove the directory and re-run the command.`
@@ -685,6 +1030,7 @@ function createTaskSummaryCommand() {
685
1030
  }
686
1031
 
687
1032
  export {
1033
+ ConfigService,
688
1034
  STATUS_ICONS,
689
1035
  BLOCKED_ICON,
690
1036
  PRIORITY_ICONS,
@@ -693,4 +1039,4 @@ export {
693
1039
  handleTaskSummary,
694
1040
  createTaskSummaryCommand
695
1041
  };
696
- //# sourceMappingURL=chunk-FZBVI5AX.js.map
1042
+ //# sourceMappingURL=chunk-S7QUPWSM.js.map