crewx 0.8.9-rc.2 → 0.8.9-rc.21
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.
- package/dist/assets/{MarketPage-BpajB_Ex.js → MarketPage-Cl9KvLAo.js} +1 -1
- package/dist/assets/PromptTab-gj_BvClc.js +27 -0
- package/dist/assets/{arc-B0fFLEOc.js → arc-DDzVSXZh.js} +1 -1
- package/dist/assets/{architectureDiagram-UYN6MBPD-CrC7MKTM.js → architectureDiagram-UYN6MBPD-p8OOXe5w.js} +1 -1
- package/dist/assets/{blockDiagram-ZHA2E4KO-DJsG-9x2.js → blockDiagram-ZHA2E4KO-BTlfXGnf.js} +7 -7
- package/dist/assets/{c4Diagram-6F5ED5ID-DlkyjvkS.js → c4Diagram-6F5ED5ID-CKbrDPWE.js} +1 -1
- package/dist/assets/channel-Hzz-cXd0.js +1 -0
- package/dist/assets/{chunk-5HRBRIJM-B_Csqjvw.js → chunk-5HRBRIJM-otR_A_1G.js} +1 -1
- package/dist/assets/{chunk-7U56Z5CX-BlNdzxbh.js → chunk-7U56Z5CX-BliJIH4-.js} +1 -1
- package/dist/assets/{chunk-ASOPGD6M-BiFWfa3w.js → chunk-ASOPGD6M-CmzypD4Z.js} +1 -1
- package/dist/assets/{chunk-KFBOBJHC-ocugjY-9.js → chunk-KFBOBJHC-BW4u3lPL.js} +1 -1
- package/dist/assets/{chunk-T2TOU4HS-kUreJ-bv.js → chunk-T2TOU4HS-BAp6Xscz.js} +1 -1
- package/dist/assets/{chunk-TMUBEWPD-Du19Q8X-.js → chunk-TMUBEWPD-Bv_RKIlz.js} +1 -1
- package/dist/assets/classDiagram-LNE6IOMH-CXR-DJKP.js +1 -0
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-CXR-DJKP.js +1 -0
- package/dist/assets/{dagre-4EVJKHTY-8N5qxubq.js → dagre-4EVJKHTY-sYyP6lkA.js} +1 -1
- package/dist/assets/{diagram-QW4FP2JN-CjLH9uYk.js → diagram-QW4FP2JN-XWldv7Gz.js} +1 -1
- package/dist/assets/{erDiagram-6RL3IURR-CXgt8NtN.js → erDiagram-6RL3IURR-Cd8OVAQ0.js} +1 -1
- package/dist/assets/{flowDiagram-7ASYPVHJ-CdFlmdmT.js → flowDiagram-7ASYPVHJ-Cc3onSB6.js} +1 -1
- package/dist/assets/{ganttDiagram-NTVNEXSI-DDjK4yxy.js → ganttDiagram-NTVNEXSI-Q6HxMUkA.js} +1 -1
- package/dist/assets/{gitGraph-YCYPL57B-Bd9jYZ-C.js → gitGraph-YCYPL57B-BUh0zUco.js} +1 -1
- package/dist/assets/{gitGraphDiagram-NRZ2UAAF-pmwetPkN.js → gitGraphDiagram-NRZ2UAAF-CvPqw-ya.js} +1 -1
- package/dist/assets/{infoDiagram-A4XQUW5V-BoDEaOyd.js → infoDiagram-A4XQUW5V-CbKGdGqb.js} +1 -1
- package/dist/assets/{journeyDiagram-G5WM74LC-D1LRuhB3.js → journeyDiagram-G5WM74LC-BXyAvuxS.js} +1 -1
- package/dist/assets/{kanban-definition-QRCXZQQD-B66SGjA2.js → kanban-definition-QRCXZQQD-CxBFX9if.js} +1 -1
- package/dist/assets/{linear-VMCGabuP.js → linear-Bu0P9vdO.js} +1 -1
- package/dist/assets/main-AYmbxFu4.css +10 -0
- package/dist/assets/main-CcrxU2Tf.js +1454 -0
- package/dist/assets/{mindmap-definition-GWI6TPTV--NljwHD9.js → mindmap-definition-GWI6TPTV-BbXI83yG.js} +1 -1
- package/dist/assets/{pieDiagram-YF2LJOPJ-CP4IPp3Q.js → pieDiagram-YF2LJOPJ-DuXspLPW.js} +1 -1
- package/dist/assets/{quadrantDiagram-OS5C2QUG-baFc-DtQ.js → quadrantDiagram-OS5C2QUG-DnngSA8p.js} +1 -1
- package/dist/assets/{requirementDiagram-MIRIMTAZ-BTWwNcnt.js → requirementDiagram-MIRIMTAZ-ChJE8XYc.js} +1 -1
- package/dist/assets/{sankeyDiagram-Y46BX6SQ-CnrACC1i.js → sankeyDiagram-Y46BX6SQ-BlkQKMk0.js} +1 -1
- package/dist/assets/{sequenceDiagram-G6AWOVSC-Ct5WkHJl.js → sequenceDiagram-G6AWOVSC-BvYCRnWL.js} +1 -1
- package/dist/assets/{stateDiagram-MAYHULR4-DeZHZmIy.js → stateDiagram-MAYHULR4-Cb6BiEq_.js} +1 -1
- package/dist/assets/stateDiagram-v2-4JROLMXI-31Hvt1M0.js +1 -0
- package/dist/assets/{timeline-definition-U7ZMHBDA-BZfUE50X.js → timeline-definition-U7ZMHBDA-w1-dGiEP.js} +1 -1
- package/dist/assets/{xychartDiagram-6QU3TZC5-DV6BFyHM.js → xychartDiagram-6QU3TZC5-B85iIKzT.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/__stubs__/ccusage-codex.js +8 -0
- package/dist-server/app.module.js +2 -0
- package/dist-server/common/limits/defaults.js +3 -3
- package/dist-server/domain/agent/agent.service.js +11 -0
- package/dist-server/domain/cli/cli.service.js +3 -3
- package/dist-server/domain/cron/cron.service.js +5 -1
- package/dist-server/domain/doc/doc.controller.js +22 -0
- package/dist-server/domain/doc/doc.service.js +42 -10
- package/dist-server/domain/git/git.controller.js +180 -0
- package/dist-server/domain/git/git.module.js +2 -0
- package/dist-server/domain/git/git.service.js +327 -34
- package/dist-server/domain/message/message.service.js +18 -8
- package/dist-server/domain/onboarding/onboarding.service.js +1 -1
- package/dist-server/domain/onboarding/presets/custom.js +2 -2
- package/dist-server/domain/onboarding/presets/dev-team.js +7 -7
- package/dist-server/domain/onboarding/presets/marketing-team.js +5 -5
- package/dist-server/domain/onboarding/presets/planning-team.js +5 -5
- package/dist-server/domain/onboarding/presets/solo.js +3 -3
- package/dist-server/domain/settings/settings.module.js +2 -0
- package/dist-server/domain/settings/settings.service.js +75 -8
- package/dist-server/domain/task/task.service.js +7 -7
- package/dist-server/domain/thread/thread.service.js +2 -1
- package/dist-server/domain/usage/usage.controller.js +30 -0
- package/dist-server/domain/usage/usage.service.js +212 -0
- package/dist-server/domain/wi/wi.controller.js +43 -0
- package/dist-server/domain/wi/wi.module.js +22 -0
- package/dist-server/domain/wi/wi.service.js +110 -0
- package/dist-server/domain/wi/wi.types.js +2 -0
- package/dist-server/domain/workspace/workspace.service.js +4 -1
- package/dist-server/modules/crewx-pool.service.js +194 -53
- package/dist-server/repository/task.repository.js +5 -1
- package/package.json +16 -14
- package/packages/cli/dist/commands/agent.js +1 -1
- package/packages/cli/dist/commands/init.d.ts +2 -0
- package/packages/cli/dist/commands/init.js +198 -6
- package/packages/cli/package.json +3 -2
- package/dist/assets/PromptTab-HhD4MQcW.js +0 -37
- package/dist/assets/channel-ClmdPiZd.js +0 -1
- package/dist/assets/classDiagram-LNE6IOMH-Q0hRWLPF.js +0 -1
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-Q0hRWLPF.js +0 -1
- package/dist/assets/main-AqROOc4i.css +0 -10
- package/dist/assets/main-DWCTap27.js +0 -1289
- package/dist/assets/stateDiagram-v2-4JROLMXI-DVsMMyDB.js +0 -1
|
@@ -45,18 +45,26 @@ var UsageService_1;
|
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
46
|
exports.UsageService = void 0;
|
|
47
47
|
const common_1 = require("@nestjs/common");
|
|
48
|
+
const node_child_process_1 = require("node:child_process");
|
|
49
|
+
const fs = __importStar(require("node:fs"));
|
|
50
|
+
const os = __importStar(require("node:os"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const node_util_1 = require("node:util");
|
|
53
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
48
54
|
const data_loader_1 = require("ccusage/data-loader");
|
|
49
55
|
const task_repository_js_1 = require("../../repository/task.repository.js");
|
|
50
56
|
const project_repository_js_1 = require("../../repository/project.repository.js");
|
|
51
57
|
const CACHE_TTL = 5 * 60 * 1000; // 5분
|
|
52
58
|
const CACHE_MAX_SIZE = 10;
|
|
53
59
|
const CACHE_CLEANUP_INTERVAL = 5 * 60 * 1000; // 5분
|
|
60
|
+
const CLAUDE_OAUTH_CACHE_TTL = 5 * 60 * 1000; // 5분
|
|
54
61
|
let UsageService = UsageService_1 = class UsageService {
|
|
55
62
|
taskRepository;
|
|
56
63
|
projectRepository;
|
|
57
64
|
logger = new common_1.Logger(UsageService_1.name);
|
|
58
65
|
cache = new Map();
|
|
59
66
|
cleanupInterval;
|
|
67
|
+
claudeOAuthCache = null;
|
|
60
68
|
constructor(taskRepository, projectRepository) {
|
|
61
69
|
this.taskRepository = taskRepository;
|
|
62
70
|
this.projectRepository = projectRepository;
|
|
@@ -189,6 +197,32 @@ let UsageService = UsageService_1 = class UsageService {
|
|
|
189
197
|
}));
|
|
190
198
|
return { from, to, timezone: tz, daily };
|
|
191
199
|
}
|
|
200
|
+
// --- USG.PROVIDER: Provider usage aggregation ---
|
|
201
|
+
getProviderUsage(from, to, workspace) {
|
|
202
|
+
const rows = this.taskRepository.getProviderUsage(from, to, workspace);
|
|
203
|
+
const grandTotalTokens = rows.reduce((sum, r) => sum + r.totalTokens, 0);
|
|
204
|
+
const providers = rows.map((r) => ({
|
|
205
|
+
provider: r.provider,
|
|
206
|
+
totalTokens: r.totalTokens,
|
|
207
|
+
inputTokens: r.inputTokens,
|
|
208
|
+
outputTokens: r.outputTokens,
|
|
209
|
+
cachedInputTokens: r.cachedInputTokens,
|
|
210
|
+
totalCost: r.costUsd,
|
|
211
|
+
taskCount: r.totalTasks,
|
|
212
|
+
activeDurationMs: r.activeDurationMs,
|
|
213
|
+
sharePercent: grandTotalTokens > 0
|
|
214
|
+
? Math.round((r.totalTokens / grandTotalTokens) * 10000) / 100
|
|
215
|
+
: 0,
|
|
216
|
+
lastActiveAt: r.lastActiveAt,
|
|
217
|
+
}));
|
|
218
|
+
const totals = {
|
|
219
|
+
totalTokens: grandTotalTokens,
|
|
220
|
+
totalCost: rows.reduce((sum, r) => sum + r.costUsd, 0),
|
|
221
|
+
totalTasks: rows.reduce((sum, r) => sum + r.totalTasks, 0),
|
|
222
|
+
totalActiveDurationMs: rows.reduce((sum, r) => sum + r.activeDurationMs, 0),
|
|
223
|
+
};
|
|
224
|
+
return { from, to, providers, totals };
|
|
225
|
+
}
|
|
192
226
|
/** Convert UTC ISO string to local date string (YYYY-MM-DD) using timezone. */
|
|
193
227
|
utcToLocalDate(utcIso, tz) {
|
|
194
228
|
try {
|
|
@@ -349,6 +383,184 @@ let UsageService = UsageService_1 = class UsageService {
|
|
|
349
383
|
}
|
|
350
384
|
}
|
|
351
385
|
}
|
|
386
|
+
// --- USG.LIMITS: Subscription limit windows ---
|
|
387
|
+
async getSubscriptionLimits() {
|
|
388
|
+
const [codex, claude] = await Promise.all([
|
|
389
|
+
this.parseCodexLimits(),
|
|
390
|
+
this.parseClaudeLimits(),
|
|
391
|
+
]);
|
|
392
|
+
return { codex, claude };
|
|
393
|
+
}
|
|
394
|
+
async parseCodexLimits() {
|
|
395
|
+
try {
|
|
396
|
+
const sessionsDir = path.join(os.homedir(), '.codex', 'sessions');
|
|
397
|
+
const jsonlFile = this.findLatestCodexJsonl(sessionsDir);
|
|
398
|
+
if (!jsonlFile)
|
|
399
|
+
return null;
|
|
400
|
+
const content = fs.readFileSync(jsonlFile, 'utf-8');
|
|
401
|
+
const lines = content.split('\n');
|
|
402
|
+
// Read from the end; skip partial writes (parse failures)
|
|
403
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
404
|
+
const line = lines[i].trim();
|
|
405
|
+
if (!line)
|
|
406
|
+
continue;
|
|
407
|
+
try {
|
|
408
|
+
const obj = JSON.parse(line);
|
|
409
|
+
const payload = obj['payload'];
|
|
410
|
+
const rateLimits = payload?.['rate_limits'];
|
|
411
|
+
if (!rateLimits)
|
|
412
|
+
continue;
|
|
413
|
+
return {
|
|
414
|
+
fiveHour: this.extractCodexWindow(rateLimits['primary']),
|
|
415
|
+
weekly: this.extractCodexWindow(rateLimits['secondary']),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
// partial write — try next line
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
findLatestCodexJsonl(sessionsDir) {
|
|
429
|
+
try {
|
|
430
|
+
let latest = null;
|
|
431
|
+
const walkDir = (dir) => {
|
|
432
|
+
let entries;
|
|
433
|
+
try {
|
|
434
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
for (const entry of entries) {
|
|
440
|
+
const fullPath = path.join(dir, entry.name);
|
|
441
|
+
if (entry.isDirectory()) {
|
|
442
|
+
walkDir(fullPath);
|
|
443
|
+
}
|
|
444
|
+
else if (entry.isFile() && entry.name.startsWith('rollout-') && entry.name.endsWith('.jsonl')) {
|
|
445
|
+
try {
|
|
446
|
+
const stat = fs.statSync(fullPath);
|
|
447
|
+
if (!latest || stat.mtimeMs > latest.mtime) {
|
|
448
|
+
latest = { file: fullPath, mtime: stat.mtimeMs };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
// skip unreadable files
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
walkDir(sessionsDir);
|
|
458
|
+
return latest?.file ?? null;
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
extractCodexWindow(raw) {
|
|
465
|
+
if (!raw || typeof raw !== 'object')
|
|
466
|
+
return null;
|
|
467
|
+
const w = raw;
|
|
468
|
+
const usedPercent = w['used_percent'];
|
|
469
|
+
if (typeof usedPercent !== 'number')
|
|
470
|
+
return null;
|
|
471
|
+
let resetsAt = null;
|
|
472
|
+
if (typeof w['resets_at'] === 'number') {
|
|
473
|
+
resetsAt = new Date(w['resets_at'] * 1000).toISOString();
|
|
474
|
+
}
|
|
475
|
+
return { usedPercent: Math.round(usedPercent), resetsAt };
|
|
476
|
+
}
|
|
477
|
+
async parseClaudeLimits() {
|
|
478
|
+
const now = Date.now();
|
|
479
|
+
if (this.claudeOAuthCache && now - this.claudeOAuthCache.ts < CLAUDE_OAUTH_CACHE_TTL) {
|
|
480
|
+
return this.claudeOAuthCache.data;
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const token = await this.getClaudeOAuthToken();
|
|
484
|
+
if (!token) {
|
|
485
|
+
this.claudeOAuthCache = { data: null, ts: now };
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
const limits = await this.fetchClaudeOAuthLimits(token);
|
|
489
|
+
this.claudeOAuthCache = { data: limits, ts: now };
|
|
490
|
+
return limits;
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
this.claudeOAuthCache = { data: null, ts: now };
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async getClaudeOAuthToken() {
|
|
498
|
+
const configDir = process.env['CLAUDE_CONFIG_DIR']?.trim() || path.join(os.homedir(), '.claude');
|
|
499
|
+
for (const filename of ['.credentials.json', 'credentials.json']) {
|
|
500
|
+
try {
|
|
501
|
+
const raw = fs.readFileSync(path.join(configDir, filename), 'utf-8');
|
|
502
|
+
const parsed = JSON.parse(raw);
|
|
503
|
+
const oauth = parsed['claudeAiOauth'];
|
|
504
|
+
if (typeof oauth === 'object' && oauth !== null) {
|
|
505
|
+
const token = oauth['accessToken'];
|
|
506
|
+
if (typeof token === 'string' && token.length > 0)
|
|
507
|
+
return token;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
// file absent or unreadable — try next
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (process.platform === 'darwin') {
|
|
515
|
+
try {
|
|
516
|
+
const { stdout } = await execFileAsync('security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], { timeout: 5000 });
|
|
517
|
+
const parsed = JSON.parse(stdout.trim());
|
|
518
|
+
const oauth = parsed['claudeAiOauth'];
|
|
519
|
+
if (typeof oauth === 'object' && oauth !== null) {
|
|
520
|
+
const token = oauth['accessToken'];
|
|
521
|
+
if (typeof token === 'string' && token.length > 0)
|
|
522
|
+
return token;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
// keychain unavailable or denied
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
async fetchClaudeOAuthLimits(token) {
|
|
532
|
+
const resp = await this.fetchWithTimeout('https://api.anthropic.com/api/oauth/usage', {
|
|
533
|
+
headers: {
|
|
534
|
+
Authorization: `Bearer ${token}`,
|
|
535
|
+
'anthropic-beta': 'oauth-2025-04-20',
|
|
536
|
+
},
|
|
537
|
+
}, 8000);
|
|
538
|
+
if (!resp.ok)
|
|
539
|
+
return null;
|
|
540
|
+
const body = (await resp.json());
|
|
541
|
+
const toPercent = (utilization) => {
|
|
542
|
+
if (utilization == null)
|
|
543
|
+
return null;
|
|
544
|
+
return Math.min(100, Math.round(utilization < 1 ? utilization * 100 : utilization));
|
|
545
|
+
};
|
|
546
|
+
const fiveHour = body.five_hour != null
|
|
547
|
+
? { usedPercent: toPercent(body.five_hour.utilization) ?? 0, resetsAt: body.five_hour.resets_at ?? null }
|
|
548
|
+
: null;
|
|
549
|
+
const sevenDay = body.seven_day != null
|
|
550
|
+
? { usedPercent: toPercent(body.seven_day.utilization) ?? 0, resetsAt: body.seven_day.resets_at ?? null }
|
|
551
|
+
: null;
|
|
552
|
+
return { fiveHour, sevenDay };
|
|
553
|
+
}
|
|
554
|
+
async fetchWithTimeout(url, init, ms) {
|
|
555
|
+
const controller = new AbortController();
|
|
556
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
557
|
+
try {
|
|
558
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
559
|
+
}
|
|
560
|
+
finally {
|
|
561
|
+
clearTimeout(timer);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
352
564
|
};
|
|
353
565
|
exports.UsageService = UsageService;
|
|
354
566
|
exports.UsageService = UsageService = UsageService_1 = __decorate([
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.WiController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
18
|
+
const wi_service_js_1 = require("./wi.service.js");
|
|
19
|
+
const workspace_decorator_js_1 = require("../../common/decorators/workspace.decorator.js");
|
|
20
|
+
let WiController = class WiController {
|
|
21
|
+
wiService;
|
|
22
|
+
constructor(wiService) {
|
|
23
|
+
this.wiService = wiService;
|
|
24
|
+
}
|
|
25
|
+
async list(ws) {
|
|
26
|
+
const data = await this.wiService.list(ws.path);
|
|
27
|
+
return { success: true, data };
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
exports.WiController = WiController;
|
|
31
|
+
__decorate([
|
|
32
|
+
(0, swagger_1.ApiOperation)({ operationId: 'WI.LIST', summary: 'List work instructions from docs/wi/*.md' }),
|
|
33
|
+
(0, common_1.Get)(),
|
|
34
|
+
__param(0, (0, workspace_decorator_js_1.Workspace)()),
|
|
35
|
+
__metadata("design:type", Function),
|
|
36
|
+
__metadata("design:paramtypes", [Object]),
|
|
37
|
+
__metadata("design:returntype", Promise)
|
|
38
|
+
], WiController.prototype, "list", null);
|
|
39
|
+
exports.WiController = WiController = __decorate([
|
|
40
|
+
(0, swagger_1.ApiTags)('wi'),
|
|
41
|
+
(0, common_1.Controller)('ws/:slug/wi'),
|
|
42
|
+
__metadata("design:paramtypes", [wi_service_js_1.WiService])
|
|
43
|
+
], WiController);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.WiModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const wi_controller_js_1 = require("./wi.controller.js");
|
|
12
|
+
const wi_service_js_1 = require("./wi.service.js");
|
|
13
|
+
let WiModule = class WiModule {
|
|
14
|
+
};
|
|
15
|
+
exports.WiModule = WiModule;
|
|
16
|
+
exports.WiModule = WiModule = __decorate([
|
|
17
|
+
(0, common_1.Module)({
|
|
18
|
+
controllers: [wi_controller_js_1.WiController],
|
|
19
|
+
providers: [wi_service_js_1.WiService],
|
|
20
|
+
exports: [wi_service_js_1.WiService],
|
|
21
|
+
})
|
|
22
|
+
], WiModule);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.WiService = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const fs_1 = require("fs");
|
|
12
|
+
const path_1 = require("path");
|
|
13
|
+
const js_yaml_1 = require("js-yaml");
|
|
14
|
+
const WI_ID_RE = /^WI-\d{8}-\d+$/;
|
|
15
|
+
const VALID_STATUSES = new Set(['draft', 'approved', 'in-progress', 'done', 'cancelled']);
|
|
16
|
+
function toArray(v) {
|
|
17
|
+
if (v === undefined || v === null)
|
|
18
|
+
return [];
|
|
19
|
+
if (Array.isArray(v))
|
|
20
|
+
return v.map(String);
|
|
21
|
+
return [String(v)];
|
|
22
|
+
}
|
|
23
|
+
function normalizeCost(v) {
|
|
24
|
+
if (v === undefined || v === null)
|
|
25
|
+
return undefined;
|
|
26
|
+
const n = parseFloat(String(v).replace(/[^0-9.]/g, ''));
|
|
27
|
+
return isNaN(n) ? undefined : n;
|
|
28
|
+
}
|
|
29
|
+
function normalizeStatus(v) {
|
|
30
|
+
const s = String(v ?? '');
|
|
31
|
+
return VALID_STATUSES.has(s) ? s : 'draft';
|
|
32
|
+
}
|
|
33
|
+
let WiService = class WiService {
|
|
34
|
+
validatePathBoundary(dir, fullPath) {
|
|
35
|
+
const dirCmp = dir.replace(/\\/g, '/').toLowerCase();
|
|
36
|
+
const pathCmp = fullPath.replace(/\\/g, '/').toLowerCase();
|
|
37
|
+
if (pathCmp !== dirCmp && !pathCmp.startsWith(dirCmp + '/')) {
|
|
38
|
+
throw new common_1.BadRequestException('Path is outside wi directory');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
parseFrontmatter(raw) {
|
|
42
|
+
if (!raw.startsWith('---'))
|
|
43
|
+
return null;
|
|
44
|
+
const end = raw.indexOf('\n---', 3);
|
|
45
|
+
if (end === -1)
|
|
46
|
+
return null;
|
|
47
|
+
const yaml = raw.slice(3, end).trim();
|
|
48
|
+
try {
|
|
49
|
+
const parsed = (0, js_yaml_1.load)(yaml);
|
|
50
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
51
|
+
return null;
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
toSummary(data, filename, mtimeMs) {
|
|
59
|
+
const relPath = `docs/wi/${filename}`;
|
|
60
|
+
return {
|
|
61
|
+
wi_id: String(data['wi_id'] ?? ''),
|
|
62
|
+
title: String(data['title'] ?? ''),
|
|
63
|
+
status: normalizeStatus(data['status']),
|
|
64
|
+
priority: data['priority'] !== undefined ? String(data['priority']) : undefined,
|
|
65
|
+
assignee: data['assignee'] !== undefined ? String(data['assignee']) : undefined,
|
|
66
|
+
reviewer: data['reviewer'] !== undefined ? String(data['reviewer']) : undefined,
|
|
67
|
+
risk_level: data['risk_level'] !== undefined ? String(data['risk_level']) : undefined,
|
|
68
|
+
estimated_files: data['estimated_files'] !== undefined ? Number(data['estimated_files']) : undefined,
|
|
69
|
+
estimated_cost: normalizeCost(data['estimated_cost']),
|
|
70
|
+
depends_on: toArray(data['depends_on']),
|
|
71
|
+
blocks: toArray(data['blocks']),
|
|
72
|
+
date: data['date'] !== undefined ? String(data['date']) : undefined,
|
|
73
|
+
spec_id: data['spec_id'] !== undefined ? String(data['spec_id']) : undefined,
|
|
74
|
+
screens: toArray(data['screens']),
|
|
75
|
+
path: relPath,
|
|
76
|
+
mtime: mtimeMs,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async list(workspacePath) {
|
|
80
|
+
const dir = (0, path_1.join)(workspacePath, 'docs', 'wi');
|
|
81
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
82
|
+
return [];
|
|
83
|
+
const entries = await fs_1.promises.readdir(dir);
|
|
84
|
+
const files = entries.filter((f) => f.endsWith('.md'));
|
|
85
|
+
const out = [];
|
|
86
|
+
for (const f of files) {
|
|
87
|
+
try {
|
|
88
|
+
const full = (0, path_1.resolve)(dir, f);
|
|
89
|
+
this.validatePathBoundary(dir, full);
|
|
90
|
+
const raw = await fs_1.promises.readFile(full, 'utf8');
|
|
91
|
+
const data = this.parseFrontmatter(raw);
|
|
92
|
+
if (!data)
|
|
93
|
+
continue;
|
|
94
|
+
const wiId = String(data['wi_id'] ?? '');
|
|
95
|
+
if (!WI_ID_RE.test(wiId))
|
|
96
|
+
continue;
|
|
97
|
+
const stat = await fs_1.promises.stat(full);
|
|
98
|
+
out.push(this.toSummary(data, f, stat.mtimeMs));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// skip malformed files
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return out.sort((a, b) => b.mtime - a.mtime);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
exports.WiService = WiService;
|
|
108
|
+
exports.WiService = WiService = __decorate([
|
|
109
|
+
(0, common_1.Injectable)()
|
|
110
|
+
], WiService);
|
|
@@ -179,12 +179,15 @@ let WorkspaceService = WorkspaceService_1 = class WorkspaceService {
|
|
|
179
179
|
}
|
|
180
180
|
/** Map a DB row to the API response shape */
|
|
181
181
|
toWorkspaceInfo(row) {
|
|
182
|
+
const workspacePath = row.workspace_path ? (0, sdk_1.normalizeWorkspacePath)(row.workspace_path) : '';
|
|
183
|
+
const isGitRepo = workspacePath ? (0, fs_1.existsSync)(path.join(workspacePath, '.git')) : false;
|
|
182
184
|
return {
|
|
183
185
|
id: row.id,
|
|
184
186
|
slug: row.slug,
|
|
185
187
|
name: row.name,
|
|
186
|
-
workspace_path:
|
|
188
|
+
workspace_path: workspacePath,
|
|
187
189
|
is_current: row.id === this.currentWorkspaceId,
|
|
190
|
+
is_git_repo: isGitRepo,
|
|
188
191
|
created_at: row.created_at,
|
|
189
192
|
updated_at: row.updated_at,
|
|
190
193
|
};
|