knowns 0.2.0 → 0.3.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.
- package/CHANGELOG.md +87 -0
- package/CLAUDE.md +64 -52
- package/README.md +3 -3
- package/dist/index.js +1027 -431
- package/dist/mcp/server.js +77 -2
- package/dist/ui/assets/index-BB-qpEBt.css +1 -0
- package/dist/ui/assets/index-DHWZIcYt.js +171 -0
- package/dist/ui/index.html +2 -2
- package/package.json +18 -4
- package/dist/ui/assets/index-BdDGlLyh.js +0 -152
- package/dist/ui/assets/index-BgbLT-Et.css +0 -1
package/dist/index.js
CHANGED
|
@@ -3313,10 +3313,10 @@ var require_stringify = __commonJS({
|
|
|
3313
3313
|
data = Object.assign({}, file3.data, data);
|
|
3314
3314
|
const open = opts.delimiters[0];
|
|
3315
3315
|
const close = opts.delimiters[1];
|
|
3316
|
-
const
|
|
3316
|
+
const matter6 = engine.stringify(data, options2).trim();
|
|
3317
3317
|
let buf = "";
|
|
3318
|
-
if (
|
|
3319
|
-
buf = newline(open) + newline(
|
|
3318
|
+
if (matter6 !== "{}") {
|
|
3319
|
+
buf = newline(open) + newline(matter6) + newline(close);
|
|
3320
3320
|
}
|
|
3321
3321
|
if (typeof file3.excerpt === "string" && file3.excerpt !== "") {
|
|
3322
3322
|
if (str2.indexOf(file3.excerpt.trim()) === -1) {
|
|
@@ -3422,19 +3422,19 @@ var require_gray_matter = __commonJS({
|
|
|
3422
3422
|
var toFile = require_to_file();
|
|
3423
3423
|
var parse4 = require_parse();
|
|
3424
3424
|
var utils = require_utils();
|
|
3425
|
-
function
|
|
3425
|
+
function matter6(input, options2) {
|
|
3426
3426
|
if (input === "") {
|
|
3427
3427
|
return { data: {}, content: input, excerpt: "", orig: input };
|
|
3428
3428
|
}
|
|
3429
3429
|
let file3 = toFile(input);
|
|
3430
|
-
const cached2 =
|
|
3430
|
+
const cached2 = matter6.cache[file3.content];
|
|
3431
3431
|
if (!options2) {
|
|
3432
3432
|
if (cached2) {
|
|
3433
3433
|
file3 = Object.assign({}, cached2);
|
|
3434
3434
|
file3.orig = cached2.orig;
|
|
3435
3435
|
return file3;
|
|
3436
3436
|
}
|
|
3437
|
-
|
|
3437
|
+
matter6.cache[file3.content] = file3;
|
|
3438
3438
|
}
|
|
3439
3439
|
return parseMatter(file3, options2);
|
|
3440
3440
|
}
|
|
@@ -3456,7 +3456,7 @@ var require_gray_matter = __commonJS({
|
|
|
3456
3456
|
}
|
|
3457
3457
|
str2 = str2.slice(openLen);
|
|
3458
3458
|
const len = str2.length;
|
|
3459
|
-
const language =
|
|
3459
|
+
const language = matter6.language(str2, opts);
|
|
3460
3460
|
if (language.name) {
|
|
3461
3461
|
file3.language = language.name;
|
|
3462
3462
|
str2 = str2.slice(language.raw.length);
|
|
@@ -3491,24 +3491,24 @@ var require_gray_matter = __commonJS({
|
|
|
3491
3491
|
}
|
|
3492
3492
|
return file3;
|
|
3493
3493
|
}
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
if (typeof file3 === "string") file3 =
|
|
3494
|
+
matter6.engines = engines2;
|
|
3495
|
+
matter6.stringify = function(file3, data, options2) {
|
|
3496
|
+
if (typeof file3 === "string") file3 = matter6(file3, options2);
|
|
3497
3497
|
return stringify(file3, data, options2);
|
|
3498
3498
|
};
|
|
3499
|
-
|
|
3499
|
+
matter6.read = function(filepath, options2) {
|
|
3500
3500
|
const str2 = fs.readFileSync(filepath, "utf8");
|
|
3501
|
-
const file3 =
|
|
3501
|
+
const file3 = matter6(str2, options2);
|
|
3502
3502
|
file3.path = filepath;
|
|
3503
3503
|
return file3;
|
|
3504
3504
|
};
|
|
3505
|
-
|
|
3505
|
+
matter6.test = function(str2, options2) {
|
|
3506
3506
|
return utils.startsWith(str2, defaults(options2).delimiters[0]);
|
|
3507
3507
|
};
|
|
3508
|
-
|
|
3508
|
+
matter6.language = function(str2, options2) {
|
|
3509
3509
|
const opts = defaults(options2);
|
|
3510
3510
|
const open = opts.delimiters[0];
|
|
3511
|
-
if (
|
|
3511
|
+
if (matter6.test(str2)) {
|
|
3512
3512
|
str2 = str2.slice(open.length);
|
|
3513
3513
|
}
|
|
3514
3514
|
const language = str2.slice(0, str2.search(/\r?\n/));
|
|
@@ -3517,11 +3517,11 @@ var require_gray_matter = __commonJS({
|
|
|
3517
3517
|
name: language ? language.trim() : ""
|
|
3518
3518
|
};
|
|
3519
3519
|
};
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3520
|
+
matter6.cache = {};
|
|
3521
|
+
matter6.clearCache = function() {
|
|
3522
|
+
matter6.cache = {};
|
|
3523
3523
|
};
|
|
3524
|
-
module2.exports =
|
|
3524
|
+
module2.exports = matter6;
|
|
3525
3525
|
}
|
|
3526
3526
|
});
|
|
3527
3527
|
|
|
@@ -30381,7 +30381,7 @@ var require_view = __commonJS({
|
|
|
30381
30381
|
var dirname4 = path.dirname;
|
|
30382
30382
|
var basename3 = path.basename;
|
|
30383
30383
|
var extname = path.extname;
|
|
30384
|
-
var
|
|
30384
|
+
var join19 = path.join;
|
|
30385
30385
|
var resolve = path.resolve;
|
|
30386
30386
|
module2.exports = View;
|
|
30387
30387
|
function View(name, options2) {
|
|
@@ -30443,12 +30443,12 @@ var require_view = __commonJS({
|
|
|
30443
30443
|
};
|
|
30444
30444
|
View.prototype.resolve = function resolve2(dir, file3) {
|
|
30445
30445
|
var ext = this.ext;
|
|
30446
|
-
var path2 =
|
|
30446
|
+
var path2 = join19(dir, file3);
|
|
30447
30447
|
var stat3 = tryStat(path2);
|
|
30448
30448
|
if (stat3 && stat3.isFile()) {
|
|
30449
30449
|
return path2;
|
|
30450
30450
|
}
|
|
30451
|
-
path2 =
|
|
30451
|
+
path2 = join19(dir, basename3(file3, ext), "index" + ext);
|
|
30452
30452
|
stat3 = tryStat(path2);
|
|
30453
30453
|
if (stat3 && stat3.isFile()) {
|
|
30454
30454
|
return path2;
|
|
@@ -32186,11 +32186,11 @@ var require_router = __commonJS({
|
|
|
32186
32186
|
var slice = Array.prototype.slice;
|
|
32187
32187
|
var flatten = Array.prototype.flat;
|
|
32188
32188
|
var methods = METHODS.map((method) => method.toLowerCase());
|
|
32189
|
-
module2.exports =
|
|
32189
|
+
module2.exports = Router9;
|
|
32190
32190
|
module2.exports.Route = Route;
|
|
32191
|
-
function
|
|
32192
|
-
if (!(this instanceof
|
|
32193
|
-
return new
|
|
32191
|
+
function Router9(options2) {
|
|
32192
|
+
if (!(this instanceof Router9)) {
|
|
32193
|
+
return new Router9(options2);
|
|
32194
32194
|
}
|
|
32195
32195
|
const opts = options2 || {};
|
|
32196
32196
|
function router(req, res, next) {
|
|
@@ -32204,9 +32204,9 @@ var require_router = __commonJS({
|
|
|
32204
32204
|
router.stack = [];
|
|
32205
32205
|
return router;
|
|
32206
32206
|
}
|
|
32207
|
-
|
|
32207
|
+
Router9.prototype = function() {
|
|
32208
32208
|
};
|
|
32209
|
-
|
|
32209
|
+
Router9.prototype.param = function param(name, fn) {
|
|
32210
32210
|
if (!name) {
|
|
32211
32211
|
throw new TypeError("argument name is required");
|
|
32212
32212
|
}
|
|
@@ -32226,7 +32226,7 @@ var require_router = __commonJS({
|
|
|
32226
32226
|
params.push(fn);
|
|
32227
32227
|
return this;
|
|
32228
32228
|
};
|
|
32229
|
-
|
|
32229
|
+
Router9.prototype.handle = function handle(req, res, callback) {
|
|
32230
32230
|
if (!callback) {
|
|
32231
32231
|
throw new TypeError("argument callback is required");
|
|
32232
32232
|
}
|
|
@@ -32353,7 +32353,7 @@ var require_router = __commonJS({
|
|
|
32353
32353
|
}
|
|
32354
32354
|
}
|
|
32355
32355
|
};
|
|
32356
|
-
|
|
32356
|
+
Router9.prototype.use = function use(handler) {
|
|
32357
32357
|
let offset = 0;
|
|
32358
32358
|
let path = "/";
|
|
32359
32359
|
if (typeof handler !== "function") {
|
|
@@ -32386,7 +32386,7 @@ var require_router = __commonJS({
|
|
|
32386
32386
|
}
|
|
32387
32387
|
return this;
|
|
32388
32388
|
};
|
|
32389
|
-
|
|
32389
|
+
Router9.prototype.route = function route(path) {
|
|
32390
32390
|
const route2 = new Route(path);
|
|
32391
32391
|
const layer = new Layer(path, {
|
|
32392
32392
|
sensitive: this.caseSensitive,
|
|
@@ -32401,7 +32401,7 @@ var require_router = __commonJS({
|
|
|
32401
32401
|
return route2;
|
|
32402
32402
|
};
|
|
32403
32403
|
methods.concat("all").forEach(function(method) {
|
|
32404
|
-
|
|
32404
|
+
Router9.prototype[method] = function(path) {
|
|
32405
32405
|
const route = this.route(path);
|
|
32406
32406
|
route[method].apply(route, slice.call(arguments, 1));
|
|
32407
32407
|
return this;
|
|
@@ -32584,7 +32584,7 @@ var require_application = __commonJS({
|
|
|
32584
32584
|
var compileTrust = require_utils4().compileTrust;
|
|
32585
32585
|
var resolve = __require("node:path").resolve;
|
|
32586
32586
|
var once = require_once();
|
|
32587
|
-
var
|
|
32587
|
+
var Router9 = require_router();
|
|
32588
32588
|
var slice = Array.prototype.slice;
|
|
32589
32589
|
var flatten = Array.prototype.flat;
|
|
32590
32590
|
var app = exports2 = module2.exports = {};
|
|
@@ -32600,7 +32600,7 @@ var require_application = __commonJS({
|
|
|
32600
32600
|
enumerable: true,
|
|
32601
32601
|
get: function getrouter() {
|
|
32602
32602
|
if (router === null) {
|
|
32603
|
-
router = new
|
|
32603
|
+
router = new Router9({
|
|
32604
32604
|
caseSensitive: this.enabled("case sensitive routing"),
|
|
32605
32605
|
strict: this.enabled("strict routing")
|
|
32606
32606
|
});
|
|
@@ -34093,7 +34093,7 @@ var require_send = __commonJS({
|
|
|
34093
34093
|
var Stream = __require("stream");
|
|
34094
34094
|
var util = __require("util");
|
|
34095
34095
|
var extname = path.extname;
|
|
34096
|
-
var
|
|
34096
|
+
var join19 = path.join;
|
|
34097
34097
|
var normalize = path.normalize;
|
|
34098
34098
|
var resolve = path.resolve;
|
|
34099
34099
|
var sep = path.sep;
|
|
@@ -34265,7 +34265,7 @@ var require_send = __commonJS({
|
|
|
34265
34265
|
return res;
|
|
34266
34266
|
}
|
|
34267
34267
|
parts = path2.split(sep);
|
|
34268
|
-
path2 = normalize(
|
|
34268
|
+
path2 = normalize(join19(root, path2));
|
|
34269
34269
|
} else {
|
|
34270
34270
|
if (UP_PATH_REGEXP.test(path2)) {
|
|
34271
34271
|
debug('malicious path "%s"', path2);
|
|
@@ -34398,7 +34398,7 @@ var require_send = __commonJS({
|
|
|
34398
34398
|
if (err) return self.onStatError(err);
|
|
34399
34399
|
return self.error(404);
|
|
34400
34400
|
}
|
|
34401
|
-
var p =
|
|
34401
|
+
var p = join19(path2, self._index[i]);
|
|
34402
34402
|
debug('stat "%s"', p);
|
|
34403
34403
|
fs.stat(p, function(err2, stat3) {
|
|
34404
34404
|
if (err2) return next(err2);
|
|
@@ -35138,7 +35138,7 @@ var require_express = __commonJS({
|
|
|
35138
35138
|
var EventEmitter = __require("node:events").EventEmitter;
|
|
35139
35139
|
var mixin = require_merge_descriptors();
|
|
35140
35140
|
var proto2 = require_application();
|
|
35141
|
-
var
|
|
35141
|
+
var Router9 = require_router();
|
|
35142
35142
|
var req = require_request();
|
|
35143
35143
|
var res = require_response();
|
|
35144
35144
|
exports2 = module2.exports = createApplication;
|
|
@@ -35160,8 +35160,8 @@ var require_express = __commonJS({
|
|
|
35160
35160
|
exports2.application = proto2;
|
|
35161
35161
|
exports2.request = req;
|
|
35162
35162
|
exports2.response = res;
|
|
35163
|
-
exports2.Route =
|
|
35164
|
-
exports2.Router =
|
|
35163
|
+
exports2.Route = Router9.Route;
|
|
35164
|
+
exports2.Router = Router9;
|
|
35165
35165
|
exports2.json = bodyParser.json;
|
|
35166
35166
|
exports2.raw = bodyParser.raw;
|
|
35167
35167
|
exports2.static = require_serve_static();
|
|
@@ -38791,7 +38791,7 @@ import { existsSync as existsSync2 } from "node:fs";
|
|
|
38791
38791
|
import { basename, join as join4 } from "node:path";
|
|
38792
38792
|
|
|
38793
38793
|
// src/storage/file-store.ts
|
|
38794
|
-
import { mkdir as mkdir2, readdir, unlink as unlink2 } from "node:fs/promises";
|
|
38794
|
+
import { mkdir as mkdir2, readdir, rename, unlink as unlink2 } from "node:fs/promises";
|
|
38795
38795
|
import { join as join2 } from "node:path";
|
|
38796
38796
|
|
|
38797
38797
|
// src/models/task.ts
|
|
@@ -39279,6 +39279,8 @@ var FileStore = class {
|
|
|
39279
39279
|
// .knowns/
|
|
39280
39280
|
tasksPath;
|
|
39281
39281
|
// .knowns/tasks/
|
|
39282
|
+
archivePath;
|
|
39283
|
+
// .knowns/archive/
|
|
39282
39284
|
projectPath;
|
|
39283
39285
|
// .knowns/config.json
|
|
39284
39286
|
timeEntriesPath;
|
|
@@ -39288,6 +39290,7 @@ var FileStore = class {
|
|
|
39288
39290
|
this.projectRoot = projectRoot;
|
|
39289
39291
|
this.basePath = join2(projectRoot, ".knowns");
|
|
39290
39292
|
this.tasksPath = join2(this.basePath, "tasks");
|
|
39293
|
+
this.archivePath = join2(this.basePath, "archive");
|
|
39291
39294
|
this.projectPath = join2(this.basePath, "config.json");
|
|
39292
39295
|
this.timeEntriesPath = join2(this.basePath, "time-entries.json");
|
|
39293
39296
|
this.versionStore = new VersionStore(projectRoot);
|
|
@@ -39462,6 +39465,7 @@ var FileStore = class {
|
|
|
39462
39465
|
await this.addSubtask(newParent, id);
|
|
39463
39466
|
}
|
|
39464
39467
|
}
|
|
39468
|
+
const oldFileName = updates.title && updates.title !== task.title ? await this.findTaskFile(id) : null;
|
|
39465
39469
|
const updatedTask = {
|
|
39466
39470
|
...task,
|
|
39467
39471
|
...updates,
|
|
@@ -39470,6 +39474,10 @@ var FileStore = class {
|
|
|
39470
39474
|
updatedAt: /* @__PURE__ */ new Date()
|
|
39471
39475
|
};
|
|
39472
39476
|
await this.versionStore.recordVersion(id, task, updatedTask, author);
|
|
39477
|
+
if (oldFileName) {
|
|
39478
|
+
const oldFilePath = join2(this.tasksPath, oldFileName);
|
|
39479
|
+
await unlink2(oldFilePath);
|
|
39480
|
+
}
|
|
39473
39481
|
await this.saveTask(updatedTask);
|
|
39474
39482
|
if (updates.timeEntries !== void 0) {
|
|
39475
39483
|
const allTimeEntries = await this.loadTimeEntries();
|
|
@@ -39489,6 +39497,66 @@ var FileStore = class {
|
|
|
39489
39497
|
const filePath = join2(this.tasksPath, fileName);
|
|
39490
39498
|
await unlink2(filePath);
|
|
39491
39499
|
}
|
|
39500
|
+
/**
|
|
39501
|
+
* Archive task - move to archive folder
|
|
39502
|
+
*/
|
|
39503
|
+
async archiveTask(id) {
|
|
39504
|
+
const task = await this.getTask(id);
|
|
39505
|
+
if (!task) {
|
|
39506
|
+
throw new Error(`Task ${id} not found`);
|
|
39507
|
+
}
|
|
39508
|
+
const fileName = await this.findTaskFile(id);
|
|
39509
|
+
if (!fileName) {
|
|
39510
|
+
throw new Error(`Task file for ${id} not found`);
|
|
39511
|
+
}
|
|
39512
|
+
await mkdir2(this.archivePath, { recursive: true });
|
|
39513
|
+
const oldPath = join2(this.tasksPath, fileName);
|
|
39514
|
+
const newPath = join2(this.archivePath, fileName);
|
|
39515
|
+
await rename(oldPath, newPath);
|
|
39516
|
+
return task;
|
|
39517
|
+
}
|
|
39518
|
+
/**
|
|
39519
|
+
* Unarchive task - restore from archive folder
|
|
39520
|
+
*/
|
|
39521
|
+
async unarchiveTask(id) {
|
|
39522
|
+
try {
|
|
39523
|
+
const files = await readdir(this.archivePath);
|
|
39524
|
+
const taskFile = files.find((f) => f.startsWith(`task-${id} -`));
|
|
39525
|
+
if (!taskFile) {
|
|
39526
|
+
throw new Error(`Archived task ${id} not found`);
|
|
39527
|
+
}
|
|
39528
|
+
const archiveFilePath = join2(this.archivePath, taskFile);
|
|
39529
|
+
const tasksFilePath = join2(this.tasksPath, taskFile);
|
|
39530
|
+
await rename(archiveFilePath, tasksFilePath);
|
|
39531
|
+
return await this.getTask(id);
|
|
39532
|
+
} catch (error46) {
|
|
39533
|
+
console.error(`Failed to unarchive task ${id}:`, error46);
|
|
39534
|
+
throw error46;
|
|
39535
|
+
}
|
|
39536
|
+
}
|
|
39537
|
+
/**
|
|
39538
|
+
* Batch archive tasks - archive all done tasks older than specified duration
|
|
39539
|
+
* @param olderThanMs - Duration in milliseconds (tasks done before now - olderThanMs will be archived)
|
|
39540
|
+
* @returns Array of archived tasks
|
|
39541
|
+
*/
|
|
39542
|
+
async batchArchiveTasks(olderThanMs) {
|
|
39543
|
+
const allTasks = await this.getAllTasks();
|
|
39544
|
+
const now = Date.now();
|
|
39545
|
+
const cutoffTime = now - olderThanMs;
|
|
39546
|
+
const tasksToArchive = allTasks.filter(
|
|
39547
|
+
(task) => task.status === "done" && new Date(task.updatedAt).getTime() < cutoffTime
|
|
39548
|
+
);
|
|
39549
|
+
const archivedTasks = [];
|
|
39550
|
+
for (const task of tasksToArchive) {
|
|
39551
|
+
try {
|
|
39552
|
+
await this.archiveTask(task.id);
|
|
39553
|
+
archivedTasks.push(task);
|
|
39554
|
+
} catch (error46) {
|
|
39555
|
+
console.error(`Failed to archive task ${task.id}:`, error46);
|
|
39556
|
+
}
|
|
39557
|
+
}
|
|
39558
|
+
return archivedTasks;
|
|
39559
|
+
}
|
|
39492
39560
|
/**
|
|
39493
39561
|
* Save task to file
|
|
39494
39562
|
*/
|
|
@@ -39507,11 +39575,18 @@ var FileStore = class {
|
|
|
39507
39575
|
}
|
|
39508
39576
|
/**
|
|
39509
39577
|
* Find task file by ID
|
|
39578
|
+
* Warns if multiple files with same ID are found (indicates a bug)
|
|
39510
39579
|
*/
|
|
39511
39580
|
async findTaskFile(id) {
|
|
39512
39581
|
try {
|
|
39513
39582
|
const files = await readdir(this.tasksPath);
|
|
39514
|
-
|
|
39583
|
+
const matchingFiles = files.filter((f) => f.startsWith(`task-${id} -`));
|
|
39584
|
+
if (matchingFiles.length > 1) {
|
|
39585
|
+
console.warn(
|
|
39586
|
+
`Warning: Found ${matchingFiles.length} files for task-${id}. This may cause data inconsistency. Files: ${matchingFiles.join(", ")}`
|
|
39587
|
+
);
|
|
39588
|
+
}
|
|
39589
|
+
return matchingFiles[0] || null;
|
|
39515
39590
|
} catch (_error) {
|
|
39516
39591
|
return null;
|
|
39517
39592
|
}
|
|
@@ -40148,10 +40223,10 @@ When starting a new session or working on an unfamiliar project:
|
|
|
40148
40223
|
knowns doc list --plain
|
|
40149
40224
|
|
|
40150
40225
|
# 2. Read essential project docs (prioritize these)
|
|
40151
|
-
knowns doc
|
|
40152
|
-
knowns doc
|
|
40153
|
-
knowns doc
|
|
40154
|
-
knowns doc
|
|
40226
|
+
knowns doc "README" --plain # Project overview
|
|
40227
|
+
knowns doc "ARCHITECTURE" --plain # System design
|
|
40228
|
+
knowns doc "CONVENTIONS" --plain # Coding standards
|
|
40229
|
+
knowns doc "API" --plain # API specifications
|
|
40155
40230
|
|
|
40156
40231
|
# 3. Review current task backlog
|
|
40157
40232
|
knowns task list --plain
|
|
@@ -40162,17 +40237,17 @@ knowns task list --status in-progress --plain
|
|
|
40162
40237
|
|
|
40163
40238
|
\`\`\`bash
|
|
40164
40239
|
# 1. View the task details
|
|
40165
|
-
knowns task
|
|
40240
|
+
knowns task <id> --plain
|
|
40166
40241
|
|
|
40167
40242
|
# 2. Follow ALL refs in the task (see Reference System section)
|
|
40168
|
-
# @.knowns/tasks/task-44 - ... \u2192 knowns task
|
|
40169
|
-
# @.knowns/docs/patterns/module.md \u2192 knowns doc
|
|
40243
|
+
# @.knowns/tasks/task-44 - ... \u2192 knowns task 44 --plain
|
|
40244
|
+
# @.knowns/docs/patterns/module.md \u2192 knowns doc "patterns/module" --plain
|
|
40170
40245
|
|
|
40171
40246
|
# 3. Search for additional related documentation
|
|
40172
40247
|
knowns search "<keywords from task>" --type doc --plain
|
|
40173
40248
|
|
|
40174
40249
|
# 4. Read ALL related docs before planning
|
|
40175
|
-
knowns doc
|
|
40250
|
+
knowns doc "<related-doc>" --plain
|
|
40176
40251
|
|
|
40177
40252
|
# 5. Check for similar completed tasks (learn from history)
|
|
40178
40253
|
knowns search "<keywords>" --type task --status done --plain
|
|
@@ -40210,10 +40285,15 @@ Tasks and docs can contain **references** to other tasks/docs. AI agents MUST un
|
|
|
40210
40285
|
|
|
40211
40286
|
### Reference Formats
|
|
40212
40287
|
|
|
40213
|
-
| Type |
|
|
40214
|
-
|
|
40215
|
-
| **Task ref** | \`@task-<id>\` | \`@.knowns/tasks/task-<id> - <title>.md\` | \`knowns task
|
|
40216
|
-
| **Doc ref** | \`@doc/<path>\` | \`@.knowns/docs/<path>.md\` | \`knowns doc
|
|
40288
|
+
| Type | When Writing (Input) | When Reading (Output) | CLI Command |
|
|
40289
|
+
|------|---------------------|----------------------|-------------|
|
|
40290
|
+
| **Task ref** | \`@task-<id>\` | \`@.knowns/tasks/task-<id> - <title>.md\` | \`knowns task <id> --plain\` |
|
|
40291
|
+
| **Doc ref** | \`@doc/<path>\` | \`@.knowns/docs/<path>.md\` | \`knowns doc <path> --plain\` |
|
|
40292
|
+
|
|
40293
|
+
> **CRITICAL for AI Agents**:
|
|
40294
|
+
> - When **WRITING** refs (in descriptions, plans, notes): Use \`@task-<id>\` and \`@doc/<path>\`
|
|
40295
|
+
> - When **READING** output from \`--plain\`: You'll see \`@.knowns/tasks/...\` and \`@.knowns/docs/...\`
|
|
40296
|
+
> - **NEVER write** the output format (\`@.knowns/...\`) - always use input format (\`@task-<id>\`, \`@doc/<path>\`)
|
|
40217
40297
|
|
|
40218
40298
|
### How to Follow Refs
|
|
40219
40299
|
|
|
@@ -40225,16 +40305,16 @@ When you read a task and see refs in system output format, follow them:
|
|
|
40225
40305
|
# @.knowns/docs/patterns/module.md
|
|
40226
40306
|
|
|
40227
40307
|
# Follow task ref (extract ID from task-<id>)
|
|
40228
|
-
knowns task
|
|
40308
|
+
knowns task 44 --plain
|
|
40229
40309
|
|
|
40230
40310
|
# Follow doc ref (extract path, remove .md)
|
|
40231
|
-
knowns doc
|
|
40311
|
+
knowns doc "patterns/module" --plain
|
|
40232
40312
|
\`\`\`
|
|
40233
40313
|
|
|
40234
40314
|
### Parsing Rules
|
|
40235
40315
|
|
|
40236
|
-
1. **Task refs**: \`@.knowns/tasks/task-<id> - ...\` \u2192 extract \`<id>\` \u2192 \`knowns task
|
|
40237
|
-
2. **Doc refs**: \`@.knowns/docs/<path>.md\` \u2192 extract \`<path>\` \u2192 \`knowns doc
|
|
40316
|
+
1. **Task refs**: \`@.knowns/tasks/task-<id> - ...\` \u2192 extract \`<id>\` \u2192 \`knowns task <id> --plain\`
|
|
40317
|
+
2. **Doc refs**: \`@.knowns/docs/<path>.md\` \u2192 extract \`<path>\` \u2192 \`knowns doc "<path>" --plain\`
|
|
40238
40318
|
|
|
40239
40319
|
### Recursive Following
|
|
40240
40320
|
|
|
@@ -40260,20 +40340,20 @@ Task 42
|
|
|
40260
40340
|
|
|
40261
40341
|
\`\`\`bash
|
|
40262
40342
|
# 1. Read the task
|
|
40263
|
-
$ knowns task
|
|
40343
|
+
$ knowns task 42 --plain
|
|
40264
40344
|
|
|
40265
40345
|
# Output contains:
|
|
40266
40346
|
# @.knowns/tasks/task-44 - CLI Task Create Command.md
|
|
40267
40347
|
# @.knowns/docs/README.md
|
|
40268
40348
|
|
|
40269
40349
|
# 2. Follow task ref
|
|
40270
|
-
$ knowns task
|
|
40350
|
+
$ knowns task 44 --plain
|
|
40271
40351
|
|
|
40272
40352
|
# 3. Follow doc ref
|
|
40273
|
-
$ knowns doc
|
|
40353
|
+
$ knowns doc "README" --plain
|
|
40274
40354
|
|
|
40275
40355
|
# 4. If README contains more refs, follow them too
|
|
40276
|
-
# @.knowns/docs/patterns/module.md \u2192 knowns doc
|
|
40356
|
+
# @.knowns/docs/patterns/module.md \u2192 knowns doc "patterns/module" --plain
|
|
40277
40357
|
|
|
40278
40358
|
# Now you have complete context
|
|
40279
40359
|
\`\`\`
|
|
@@ -40292,7 +40372,12 @@ knowns init [name]
|
|
|
40292
40372
|
knowns task create "Title" -d "Description" --ac "Criterion 1" --ac "Criterion 2"
|
|
40293
40373
|
|
|
40294
40374
|
# View task (ALWAYS use --plain for AI)
|
|
40295
|
-
knowns task
|
|
40375
|
+
knowns task <id> --plain # Shorthand
|
|
40376
|
+
knowns task view <id> --plain # Full command
|
|
40377
|
+
|
|
40378
|
+
# View doc (ALWAYS use --plain for AI)
|
|
40379
|
+
knowns doc <path> --plain # Shorthand
|
|
40380
|
+
knowns doc view "<path>" --plain # Full command
|
|
40296
40381
|
|
|
40297
40382
|
# List tasks
|
|
40298
40383
|
knowns task list --plain
|
|
@@ -40322,9 +40407,9 @@ $ knowns doc list --plain
|
|
|
40322
40407
|
# DOC: email-templates.md
|
|
40323
40408
|
|
|
40324
40409
|
# 0b. Read essential project docs
|
|
40325
|
-
$ knowns doc
|
|
40326
|
-
$ knowns doc
|
|
40327
|
-
$ knowns doc
|
|
40410
|
+
$ knowns doc "README" --plain
|
|
40411
|
+
$ knowns doc "ARCHITECTURE" --plain
|
|
40412
|
+
$ knowns doc "CONVENTIONS" --plain
|
|
40328
40413
|
|
|
40329
40414
|
# Now the agent understands project context and conventions
|
|
40330
40415
|
|
|
@@ -40356,7 +40441,7 @@ $ knowns search "password security" --type doc --plain
|
|
|
40356
40441
|
# DOC: email-templates.md (score: 0.78)
|
|
40357
40442
|
|
|
40358
40443
|
# 4. Read the documentation
|
|
40359
|
-
$ knowns doc
|
|
40444
|
+
$ knowns doc "security-patterns" --plain
|
|
40360
40445
|
|
|
40361
40446
|
# 5. Create implementation plan (SHARE WITH USER, WAIT FOR APPROVAL)
|
|
40362
40447
|
$ knowns task edit AUTH-042 --plan $'1. Review security patterns (see @doc/security-patterns)
|
|
@@ -40436,8 +40521,8 @@ knowns time start <id>
|
|
|
40436
40521
|
knowns search "authentication" --type doc --plain
|
|
40437
40522
|
|
|
40438
40523
|
# View relevant documents
|
|
40439
|
-
knowns doc
|
|
40440
|
-
knowns doc
|
|
40524
|
+
knowns doc "API Guidelines" --plain
|
|
40525
|
+
knowns doc "Security Patterns" --plain
|
|
40441
40526
|
|
|
40442
40527
|
# Also check for similar completed tasks
|
|
40443
40528
|
knowns search "auth" --type task --status done --plain
|
|
@@ -40524,7 +40609,8 @@ knowns task edit <id> --notes "Implementation summary"
|
|
|
40524
40609
|
knowns task edit <id> --append-notes "Progress update"
|
|
40525
40610
|
|
|
40526
40611
|
# View & List
|
|
40527
|
-
knowns task
|
|
40612
|
+
knowns task <id> --plain # Shorthand (ALWAYS use --plain)
|
|
40613
|
+
knowns task view <id> --plain # Full command
|
|
40528
40614
|
knowns task list --plain
|
|
40529
40615
|
knowns task list --status in-progress --plain
|
|
40530
40616
|
knowns task list --assignee @me --plain
|
|
@@ -40555,7 +40641,8 @@ knowns time report --by-label --csv > report.csv
|
|
|
40555
40641
|
# List & View
|
|
40556
40642
|
knowns doc list --plain
|
|
40557
40643
|
knowns doc list --tag architecture --plain
|
|
40558
|
-
knowns doc
|
|
40644
|
+
knowns doc <path> --plain # Shorthand (ALWAYS use --plain)
|
|
40645
|
+
knowns doc view "<path>" --plain # Full command
|
|
40559
40646
|
|
|
40560
40647
|
# Create (with optional folder)
|
|
40561
40648
|
knowns doc create "Title" -d "Description" -t "tags"
|
|
@@ -40662,7 +40749,7 @@ Implemented JWT auth using jsonwebtoken library.
|
|
|
40662
40749
|
| \`Error: No active timer\` | Calling \`time stop\` without active timer | Start timer first: \`knowns time start <id>\` |
|
|
40663
40750
|
| \`Error: Timer already running\` | Starting timer when one is active | Stop current: \`knowns time stop\` |
|
|
40664
40751
|
| \`Error: Invalid status\` | Wrong status format | Use lowercase with hyphens: \`in-progress\`, not \`In Progress\` |
|
|
40665
|
-
| \`Error: AC index out of range\` | Checking non-existent criterion | View task first: \`knowns task
|
|
40752
|
+
| \`Error: AC index out of range\` | Checking non-existent criterion | View task first: \`knowns task <id> --plain\` |
|
|
40666
40753
|
| \`Error: Document not found\` | Wrong document name | Run \`knowns doc list --plain\` to find correct name |
|
|
40667
40754
|
| \`Error: Not initialized\` | Running commands without init | Run \`knowns init\` first |
|
|
40668
40755
|
|
|
@@ -40676,7 +40763,7 @@ knowns --version
|
|
|
40676
40763
|
knowns status
|
|
40677
40764
|
|
|
40678
40765
|
# View raw task data (for debugging)
|
|
40679
|
-
knowns task
|
|
40766
|
+
knowns task <id> --json
|
|
40680
40767
|
|
|
40681
40768
|
# Check timer status
|
|
40682
40769
|
knowns time status
|
|
@@ -40740,6 +40827,7 @@ Use **lowercase with hyphens**:
|
|
|
40740
40827
|
| Plan without checking docs | Read docs before planning |
|
|
40741
40828
|
| Ignore similar completed tasks | Search done tasks for patterns |
|
|
40742
40829
|
| Missing doc links in description/plan | Link docs using \`@doc/<path>\` |
|
|
40830
|
+
| Write refs as \`@.knowns/docs/...\` or \`@.knowns/tasks/...\` | Use input format: \`@doc/<path>\`, \`@task-<id>\` |
|
|
40743
40831
|
| Forget \`--plain\` flag | Always use \`--plain\` for AI |
|
|
40744
40832
|
| Code before plan approval | Share plan, WAIT for approval |
|
|
40745
40833
|
| Mark done without all criteria | Check ALL criteria first |
|
|
@@ -40747,8 +40835,8 @@ Use **lowercase with hyphens**:
|
|
|
40747
40835
|
| Use \`"In Progress"\` or \`"Done"\` | Use \`in-progress\`, \`done\` |
|
|
40748
40836
|
| Use \`@yourself\` | Use \`@me\` or specific username |
|
|
40749
40837
|
| Ignore refs in task description | Follow ALL refs (\`@.knowns/...\`) before planning |
|
|
40750
|
-
| See \`@.knowns/docs/...\` but don't read | Use \`knowns doc
|
|
40751
|
-
| See \`@.knowns/tasks/task-X\` but don't check | Use \`knowns task
|
|
40838
|
+
| See \`@.knowns/docs/...\` but don't read | Use \`knowns doc "<path>" --plain\` |
|
|
40839
|
+
| See \`@.knowns/tasks/task-X\` but don't check | Use \`knowns task X --plain\` for context |
|
|
40752
40840
|
| Follow only first-level refs | Recursively follow nested refs until complete |
|
|
40753
40841
|
|
|
40754
40842
|
---
|
|
@@ -40806,7 +40894,7 @@ EOF
|
|
|
40806
40894
|
- [ ] ALL refs in task followed (\`@.knowns/...\`)
|
|
40807
40895
|
- [ ] Nested refs recursively followed until complete context gathered
|
|
40808
40896
|
- [ ] Related docs searched: \`knowns search "keyword" --type doc --plain\`
|
|
40809
|
-
- [ ] ALL relevant docs read: \`knowns doc
|
|
40897
|
+
- [ ] ALL relevant docs read: \`knowns doc "Doc Name" --plain\`
|
|
40810
40898
|
- [ ] Similar done tasks reviewed for patterns
|
|
40811
40899
|
- [ ] Task assigned to self: \`-a @me\`
|
|
40812
40900
|
- [ ] Status set to in-progress: \`-s in-progress\`
|
|
@@ -40835,16 +40923,16 @@ EOF
|
|
|
40835
40923
|
\`\`\`bash
|
|
40836
40924
|
# === AGENT INITIALIZATION (Once per session) ===
|
|
40837
40925
|
knowns doc list --plain
|
|
40838
|
-
knowns doc
|
|
40839
|
-
knowns doc
|
|
40840
|
-
knowns doc
|
|
40926
|
+
knowns doc "README" --plain
|
|
40927
|
+
knowns doc "ARCHITECTURE" --plain
|
|
40928
|
+
knowns doc "CONVENTIONS" --plain
|
|
40841
40929
|
|
|
40842
40930
|
# === FULL WORKFLOW ===
|
|
40843
40931
|
knowns task create "Title" -d "Description" --ac "Criterion"
|
|
40844
40932
|
knowns task edit <id> -s in-progress -a @me
|
|
40845
40933
|
knowns time start <id>
|
|
40846
40934
|
knowns search "keyword" --type doc --plain
|
|
40847
|
-
knowns doc
|
|
40935
|
+
knowns doc "Doc Name" --plain
|
|
40848
40936
|
knowns search "keyword" --type task --status done --plain # Learn from history
|
|
40849
40937
|
knowns task edit <id> --plan $'1. Step (see @doc/file)\\n2. Step'
|
|
40850
40938
|
# ... wait for approval, then implement ...
|
|
@@ -40854,7 +40942,7 @@ knowns time stop
|
|
|
40854
40942
|
knowns task edit <id> -s done
|
|
40855
40943
|
|
|
40856
40944
|
# === VIEW & SEARCH ===
|
|
40857
|
-
knowns task
|
|
40945
|
+
knowns task <id> --plain # Shorthand for view
|
|
40858
40946
|
knowns task list --plain
|
|
40859
40947
|
knowns task list --status in-progress --assignee @me --plain
|
|
40860
40948
|
knowns search "query" --plain
|
|
@@ -40868,7 +40956,7 @@ knowns time report --from "2025-12-01" --to "2025-12-31"
|
|
|
40868
40956
|
|
|
40869
40957
|
# === DOCUMENTATION ===
|
|
40870
40958
|
knowns doc list --plain
|
|
40871
|
-
knowns doc
|
|
40959
|
+
knowns doc "path/doc-name" --plain # Shorthand for view
|
|
40872
40960
|
knowns doc create "Title" -d "Description" -t "tags" -f "folder"
|
|
40873
40961
|
knowns doc edit "doc-name" -c "New content"
|
|
40874
40962
|
knowns doc edit "doc-name" -a "Appended content"
|
|
@@ -41219,6 +41307,8 @@ function findProjectRoot(startPath = process.cwd()) {
|
|
|
41219
41307
|
// src/utils/mention-refs.ts
|
|
41220
41308
|
var TASK_MENTION_REGEX = /@task-(\d+)/g;
|
|
41221
41309
|
var DOC_MENTION_REGEX = /@docs?\/([^\s|)]+)/g;
|
|
41310
|
+
var OUTPUT_TASK_REGEX = /@\.knowns\/tasks\/task-(\d+)(?:\s*-\s*[^@\n]+?\.md|\.md)/g;
|
|
41311
|
+
var OUTPUT_DOC_REGEX = /@\.knowns\/docs\/([^\s@]+?)\.md(?:\s*-\s*[^@\n]+)?/g;
|
|
41222
41312
|
function sanitizeTitle(title) {
|
|
41223
41313
|
return title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").trim().slice(0, 50);
|
|
41224
41314
|
}
|
|
@@ -41251,6 +41341,12 @@ function buildTaskMap(tasks) {
|
|
|
41251
41341
|
}
|
|
41252
41342
|
return map2;
|
|
41253
41343
|
}
|
|
41344
|
+
function normalizeRefs(text) {
|
|
41345
|
+
let result = text;
|
|
41346
|
+
result = result.replace(new RegExp(OUTPUT_TASK_REGEX.source, "g"), (match, taskId) => `@task-${taskId}`);
|
|
41347
|
+
result = result.replace(new RegExp(OUTPUT_DOC_REGEX.source, "g"), (match, docPath) => `@doc/${docPath}`);
|
|
41348
|
+
return result;
|
|
41349
|
+
}
|
|
41254
41350
|
|
|
41255
41351
|
// src/utils/notify-server.ts
|
|
41256
41352
|
import { existsSync as existsSync5, readFileSync } from "node:fs";
|
|
@@ -41285,6 +41381,18 @@ async function notifyTaskUpdate(taskId) {
|
|
|
41285
41381
|
} catch {
|
|
41286
41382
|
}
|
|
41287
41383
|
}
|
|
41384
|
+
async function notifyRefresh() {
|
|
41385
|
+
try {
|
|
41386
|
+
const port = getServerPort();
|
|
41387
|
+
await fetch(`http://localhost:${port}/api/notify`, {
|
|
41388
|
+
method: "POST",
|
|
41389
|
+
headers: { "Content-Type": "application/json" },
|
|
41390
|
+
body: JSON.stringify({ type: "tasks:refresh" }),
|
|
41391
|
+
signal: AbortSignal.timeout(1e3)
|
|
41392
|
+
});
|
|
41393
|
+
} catch {
|
|
41394
|
+
}
|
|
41395
|
+
}
|
|
41288
41396
|
async function notifyDocUpdate(docPath) {
|
|
41289
41397
|
try {
|
|
41290
41398
|
const port = getServerPort();
|
|
@@ -41297,6 +41405,18 @@ async function notifyDocUpdate(docPath) {
|
|
|
41297
41405
|
} catch {
|
|
41298
41406
|
}
|
|
41299
41407
|
}
|
|
41408
|
+
async function notifyTimeUpdate(active) {
|
|
41409
|
+
try {
|
|
41410
|
+
const port = getServerPort();
|
|
41411
|
+
await fetch(`http://localhost:${port}/api/notify`, {
|
|
41412
|
+
method: "POST",
|
|
41413
|
+
headers: { "Content-Type": "application/json" },
|
|
41414
|
+
body: JSON.stringify({ type: "time:updated", active }),
|
|
41415
|
+
signal: AbortSignal.timeout(1e3)
|
|
41416
|
+
});
|
|
41417
|
+
} catch {
|
|
41418
|
+
}
|
|
41419
|
+
}
|
|
41300
41420
|
|
|
41301
41421
|
// src/commands/task.ts
|
|
41302
41422
|
function getFileStore() {
|
|
@@ -41675,7 +41795,7 @@ var createCommand2 = new Command("create").description("Create a new task").argu
|
|
|
41675
41795
|
}));
|
|
41676
41796
|
const task = await fileStore.createTask({
|
|
41677
41797
|
title,
|
|
41678
|
-
description: options2.description,
|
|
41798
|
+
description: options2.description ? normalizeRefs(options2.description) : void 0,
|
|
41679
41799
|
status: options2.status,
|
|
41680
41800
|
priority: options2.priority,
|
|
41681
41801
|
assignee: options2.assignee,
|
|
@@ -41780,7 +41900,7 @@ var editCommand = new Command("edit").description("Edit task properties").argume
|
|
|
41780
41900
|
updates.title = options2.title;
|
|
41781
41901
|
}
|
|
41782
41902
|
if (options2.description) {
|
|
41783
|
-
updates.description = options2.description;
|
|
41903
|
+
updates.description = normalizeRefs(options2.description);
|
|
41784
41904
|
}
|
|
41785
41905
|
if (options2.status) {
|
|
41786
41906
|
if (!isValidTaskStatus(options2.status, allowedStatuses)) {
|
|
@@ -41857,15 +41977,15 @@ var editCommand = new Command("edit").description("Edit task properties").argume
|
|
|
41857
41977
|
updates.acceptanceCriteria = criteria;
|
|
41858
41978
|
}
|
|
41859
41979
|
if (options2.plan) {
|
|
41860
|
-
updates.implementationPlan = options2.plan;
|
|
41980
|
+
updates.implementationPlan = normalizeRefs(options2.plan);
|
|
41861
41981
|
}
|
|
41862
41982
|
if (options2.notes) {
|
|
41863
|
-
updates.implementationNotes = options2.notes;
|
|
41983
|
+
updates.implementationNotes = normalizeRefs(options2.notes);
|
|
41864
41984
|
}
|
|
41865
41985
|
if (options2.appendNotes) {
|
|
41866
41986
|
const existingNotes = task.implementationNotes || "";
|
|
41867
41987
|
const separator = existingNotes ? "\n\n" : "";
|
|
41868
|
-
updates.implementationNotes = existingNotes + separator + options2.appendNotes;
|
|
41988
|
+
updates.implementationNotes = existingNotes + separator + normalizeRefs(options2.appendNotes);
|
|
41869
41989
|
}
|
|
41870
41990
|
await fileStore.updateTask(id, updates);
|
|
41871
41991
|
await notifyTaskUpdate(id);
|
|
@@ -41929,6 +42049,7 @@ var archiveCommand = new Command("archive").description("Archive a task").argume
|
|
|
41929
42049
|
const content = await file(oldPath).text();
|
|
41930
42050
|
await write(newPath, content);
|
|
41931
42051
|
await unlink3(oldPath);
|
|
42052
|
+
await notifyRefresh();
|
|
41932
42053
|
console.log(source_default.green(`\u2713 Archived task-${id}: ${task.title}`));
|
|
41933
42054
|
} catch (error46) {
|
|
41934
42055
|
console.error(source_default.red("\u2717 Failed to archive task"));
|
|
@@ -41959,6 +42080,7 @@ var unarchiveCommand = new Command("unarchive").description("Restore archived ta
|
|
|
41959
42080
|
const content = await file(archivePath).text();
|
|
41960
42081
|
await write(tasksFilePath, content);
|
|
41961
42082
|
await unlink3(archivePath);
|
|
42083
|
+
await notifyRefresh();
|
|
41962
42084
|
console.log(source_default.green(`\u2713 Restored task-${id}`));
|
|
41963
42085
|
} catch (error46) {
|
|
41964
42086
|
console.error(source_default.red("\u2717 Failed to restore task"));
|
|
@@ -42363,7 +42485,27 @@ var restoreCommand = new Command("restore").description("Restore task to a previ
|
|
|
42363
42485
|
process.exit(1);
|
|
42364
42486
|
}
|
|
42365
42487
|
});
|
|
42366
|
-
var taskCommand = new Command("task").description("Manage tasks")
|
|
42488
|
+
var taskCommand = new Command("task").description("Manage tasks").argument("[id]", "Task ID (shorthand for 'task view <id>')").option("--plain", "Plain text output for AI").action(async (id, options2) => {
|
|
42489
|
+
if (!id) {
|
|
42490
|
+
taskCommand.help();
|
|
42491
|
+
return;
|
|
42492
|
+
}
|
|
42493
|
+
try {
|
|
42494
|
+
const fileStore = getFileStore();
|
|
42495
|
+
const task = await fileStore.getTask(id);
|
|
42496
|
+
if (!task) {
|
|
42497
|
+
console.error(source_default.red(`\u2717 Task ${id} not found`));
|
|
42498
|
+
process.exit(1);
|
|
42499
|
+
}
|
|
42500
|
+
console.log(await formatTask(task, fileStore, options2.plain));
|
|
42501
|
+
} catch (error46) {
|
|
42502
|
+
console.error(source_default.red("\u2717 Failed to view task"));
|
|
42503
|
+
if (error46 instanceof Error) {
|
|
42504
|
+
console.error(source_default.red(` ${error46.message}`));
|
|
42505
|
+
}
|
|
42506
|
+
process.exit(1);
|
|
42507
|
+
}
|
|
42508
|
+
});
|
|
42367
42509
|
taskCommand.addCommand(createCommand2);
|
|
42368
42510
|
taskCommand.addCommand(listCommand);
|
|
42369
42511
|
taskCommand.addCommand(viewCommand);
|
|
@@ -42818,6 +42960,13 @@ var startCommand = new Command("start").description("Start timer for a task").ar
|
|
|
42818
42960
|
totalPausedMs: 0
|
|
42819
42961
|
};
|
|
42820
42962
|
await saveTimeData(projectRoot, data);
|
|
42963
|
+
await notifyTimeUpdate({
|
|
42964
|
+
taskId,
|
|
42965
|
+
taskTitle: task.title,
|
|
42966
|
+
startedAt: data.active.startedAt,
|
|
42967
|
+
pausedAt: null,
|
|
42968
|
+
totalPausedMs: 0
|
|
42969
|
+
});
|
|
42821
42970
|
console.log(source_default.green(`\u23F1 Started timer for #${taskId}: ${task.title}`));
|
|
42822
42971
|
} catch (error46) {
|
|
42823
42972
|
console.error(source_default.red("\u2717 Failed to start timer"));
|
|
@@ -42858,6 +43007,7 @@ var stopCommand = new Command("stop").description("Stop current timer and save t
|
|
|
42858
43007
|
}
|
|
42859
43008
|
data.active = null;
|
|
42860
43009
|
await saveTimeData(projectRoot, data);
|
|
43010
|
+
await notifyTimeUpdate(null);
|
|
42861
43011
|
console.log(source_default.green(`\u23F9 Stopped timer for #${taskId}`));
|
|
42862
43012
|
console.log(source_default.gray(` Duration: ${formatDuration(seconds)}`));
|
|
42863
43013
|
} catch (error46) {
|
|
@@ -42871,6 +43021,7 @@ var stopCommand = new Command("stop").description("Stop current timer and save t
|
|
|
42871
43021
|
var pauseCommand = new Command("pause").description("Pause current timer").action(async () => {
|
|
42872
43022
|
try {
|
|
42873
43023
|
const projectRoot = getProjectRoot();
|
|
43024
|
+
const fileStore = getFileStore4();
|
|
42874
43025
|
const data = await loadTimeData(projectRoot);
|
|
42875
43026
|
if (!data.active) {
|
|
42876
43027
|
console.log(source_default.yellow("No active timer"));
|
|
@@ -42882,6 +43033,14 @@ var pauseCommand = new Command("pause").description("Pause current timer").actio
|
|
|
42882
43033
|
}
|
|
42883
43034
|
data.active.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
42884
43035
|
await saveTimeData(projectRoot, data);
|
|
43036
|
+
const task = await fileStore.getTask(data.active.taskId);
|
|
43037
|
+
await notifyTimeUpdate({
|
|
43038
|
+
taskId: data.active.taskId,
|
|
43039
|
+
taskTitle: task?.title || "",
|
|
43040
|
+
startedAt: data.active.startedAt,
|
|
43041
|
+
pausedAt: data.active.pausedAt,
|
|
43042
|
+
totalPausedMs: data.active.totalPausedMs
|
|
43043
|
+
});
|
|
42885
43044
|
console.log(source_default.yellow(`\u23F8 Paused timer for #${data.active.taskId}`));
|
|
42886
43045
|
} catch (error46) {
|
|
42887
43046
|
console.error(source_default.red("\u2717 Failed to pause timer"));
|
|
@@ -42894,6 +43053,7 @@ var pauseCommand = new Command("pause").description("Pause current timer").actio
|
|
|
42894
43053
|
var resumeCommand = new Command("resume").description("Resume paused timer").action(async () => {
|
|
42895
43054
|
try {
|
|
42896
43055
|
const projectRoot = getProjectRoot();
|
|
43056
|
+
const fileStore = getFileStore4();
|
|
42897
43057
|
const data = await loadTimeData(projectRoot);
|
|
42898
43058
|
if (!data.active) {
|
|
42899
43059
|
console.log(source_default.yellow("No active timer"));
|
|
@@ -42907,6 +43067,14 @@ var resumeCommand = new Command("resume").description("Resume paused timer").act
|
|
|
42907
43067
|
data.active.totalPausedMs += pausedDuration;
|
|
42908
43068
|
data.active.pausedAt = null;
|
|
42909
43069
|
await saveTimeData(projectRoot, data);
|
|
43070
|
+
const task = await fileStore.getTask(data.active.taskId);
|
|
43071
|
+
await notifyTimeUpdate({
|
|
43072
|
+
taskId: data.active.taskId,
|
|
43073
|
+
taskTitle: task?.title || "",
|
|
43074
|
+
startedAt: data.active.startedAt,
|
|
43075
|
+
pausedAt: null,
|
|
43076
|
+
totalPausedMs: data.active.totalPausedMs
|
|
43077
|
+
});
|
|
42910
43078
|
console.log(source_default.green(`\u25B6 Resumed timer for #${data.active.taskId}`));
|
|
42911
43079
|
} catch (error46) {
|
|
42912
43080
|
console.error(source_default.red("\u2717 Failed to resume timer"));
|
|
@@ -43175,20 +43343,18 @@ timeCommand.addCommand(addCommand);
|
|
|
43175
43343
|
timeCommand.addCommand(reportCommand);
|
|
43176
43344
|
|
|
43177
43345
|
// src/commands/browser.ts
|
|
43178
|
-
import { existsSync as
|
|
43179
|
-
import { mkdir as mkdir6, readFile as
|
|
43180
|
-
import { join as
|
|
43346
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
43347
|
+
import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile6 } from "node:fs/promises";
|
|
43348
|
+
import { join as join16 } from "node:path";
|
|
43181
43349
|
|
|
43182
43350
|
// src/server/index.ts
|
|
43183
43351
|
import { spawn } from "node:child_process";
|
|
43184
|
-
import { existsSync as
|
|
43185
|
-
import { mkdir as mkdir5, readFile as readFile4, readdir as readdir4, writeFile as writeFile3 } from "node:fs/promises";
|
|
43352
|
+
import { existsSync as existsSync11, realpathSync } from "node:fs";
|
|
43186
43353
|
import { createServer } from "node:http";
|
|
43187
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
43354
|
+
import { basename as basename2, dirname as dirname3, join as join15 } from "node:path";
|
|
43188
43355
|
import { fileURLToPath } from "node:url";
|
|
43189
43356
|
var import_cors = __toESM(require_lib2(), 1);
|
|
43190
|
-
var
|
|
43191
|
-
var import_gray_matter3 = __toESM(require_gray_matter(), 1);
|
|
43357
|
+
var import_express9 = __toESM(require_express2(), 1);
|
|
43192
43358
|
|
|
43193
43359
|
// node_modules/ws/wrapper.mjs
|
|
43194
43360
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -43197,151 +43363,161 @@ var import_sender = __toESM(require_sender(), 1);
|
|
|
43197
43363
|
var import_websocket = __toESM(require_websocket(), 1);
|
|
43198
43364
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
43199
43365
|
|
|
43200
|
-
// src/server/
|
|
43201
|
-
|
|
43202
|
-
|
|
43203
|
-
|
|
43204
|
-
|
|
43205
|
-
|
|
43206
|
-
|
|
43207
|
-
|
|
43208
|
-
|
|
43209
|
-
|
|
43210
|
-
|
|
43211
|
-
|
|
43212
|
-
|
|
43213
|
-
};
|
|
43214
|
-
|
|
43215
|
-
const currentDir = dirname3(currentFile);
|
|
43216
|
-
let packageRoot = currentDir;
|
|
43217
|
-
const normalizedDir = currentDir.replace(/[/\\]+$/, "");
|
|
43218
|
-
const dirName = basename2(normalizedDir);
|
|
43219
|
-
if (dirName === "dist") {
|
|
43220
|
-
packageRoot = join10(currentDir, "..");
|
|
43221
|
-
} else if (normalizedDir.includes("/src/server") || normalizedDir.includes("\\src\\server")) {
|
|
43222
|
-
packageRoot = join10(currentDir, "..", "..");
|
|
43223
|
-
}
|
|
43224
|
-
const uiDistPath = join10(packageRoot, "dist", "ui");
|
|
43225
|
-
if (!existsSync7(join10(uiDistPath, "index.html"))) {
|
|
43226
|
-
throw new Error(`UI build not found at ${uiDistPath}. Run 'bun run build:ui' first.`);
|
|
43227
|
-
}
|
|
43228
|
-
const app = (0, import_express.default)();
|
|
43229
|
-
app.use((0, import_cors.default)());
|
|
43230
|
-
app.use(import_express.default.json());
|
|
43231
|
-
const httpServer = createServer(app);
|
|
43232
|
-
const wss = new import_websocket_server.default({ server: httpServer, path: "/ws" });
|
|
43233
|
-
wss.on("connection", (ws) => {
|
|
43234
|
-
clients.add(ws);
|
|
43235
|
-
ws.on("close", () => {
|
|
43236
|
-
clients.delete(ws);
|
|
43237
|
-
});
|
|
43238
|
-
ws.on("error", (error46) => {
|
|
43239
|
-
console.error("WebSocket error:", error46);
|
|
43240
|
-
clients.delete(ws);
|
|
43241
|
-
});
|
|
43242
|
-
});
|
|
43243
|
-
app.use(
|
|
43244
|
-
"/assets",
|
|
43245
|
-
import_express.default.static(join10(uiDistPath, "assets"), {
|
|
43246
|
-
maxAge: "1y",
|
|
43247
|
-
immutable: true
|
|
43248
|
-
})
|
|
43249
|
-
);
|
|
43250
|
-
app.get("/api/tasks", async (_req, res) => {
|
|
43366
|
+
// src/server/middleware/error-handler.ts
|
|
43367
|
+
function errorHandler(err, _req, res, _next) {
|
|
43368
|
+
console.error("Server error:", err);
|
|
43369
|
+
res.status(500).json({ error: err.message });
|
|
43370
|
+
}
|
|
43371
|
+
|
|
43372
|
+
// src/server/routes/index.ts
|
|
43373
|
+
var import_express8 = __toESM(require_express2(), 1);
|
|
43374
|
+
|
|
43375
|
+
// src/server/routes/activities.ts
|
|
43376
|
+
var import_express = __toESM(require_express2(), 1);
|
|
43377
|
+
function createActivityRoutes(ctx) {
|
|
43378
|
+
const router = (0, import_express.Router)();
|
|
43379
|
+
const { store } = ctx;
|
|
43380
|
+
router.get("/", async (req, res) => {
|
|
43251
43381
|
try {
|
|
43382
|
+
const limit = Number.parseInt(req.query.limit || "50", 10);
|
|
43383
|
+
const type = req.query.type;
|
|
43252
43384
|
const tasks = await store.getAllTasks();
|
|
43253
|
-
|
|
43254
|
-
|
|
43255
|
-
|
|
43256
|
-
|
|
43257
|
-
|
|
43258
|
-
|
|
43259
|
-
|
|
43260
|
-
|
|
43261
|
-
|
|
43262
|
-
|
|
43263
|
-
|
|
43385
|
+
const allActivities = [];
|
|
43386
|
+
for (const task of tasks) {
|
|
43387
|
+
const versions = await store.getTaskVersionHistory(task.id);
|
|
43388
|
+
for (const version2 of versions) {
|
|
43389
|
+
if (type) {
|
|
43390
|
+
const hasMatchingChange = version2.changes.some((c) => c.field === type);
|
|
43391
|
+
if (!hasMatchingChange) continue;
|
|
43392
|
+
}
|
|
43393
|
+
allActivities.push({
|
|
43394
|
+
taskId: task.id,
|
|
43395
|
+
taskTitle: task.title,
|
|
43396
|
+
version: version2.version,
|
|
43397
|
+
timestamp: version2.timestamp,
|
|
43398
|
+
author: version2.author,
|
|
43399
|
+
changes: version2.changes
|
|
43400
|
+
});
|
|
43401
|
+
}
|
|
43402
|
+
}
|
|
43403
|
+
allActivities.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
43404
|
+
const limited = allActivities.slice(0, limit);
|
|
43405
|
+
res.json({ activities: limited });
|
|
43264
43406
|
} catch (error46) {
|
|
43265
|
-
console.error("Error getting
|
|
43407
|
+
console.error("Error getting activities:", error46);
|
|
43266
43408
|
res.status(500).json({ error: String(error46) });
|
|
43267
43409
|
}
|
|
43268
43410
|
});
|
|
43269
|
-
|
|
43411
|
+
return router;
|
|
43412
|
+
}
|
|
43413
|
+
|
|
43414
|
+
// src/server/routes/config.ts
|
|
43415
|
+
var import_express2 = __toESM(require_express2(), 1);
|
|
43416
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
43417
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
43418
|
+
import { join as join10 } from "node:path";
|
|
43419
|
+
function createConfigRoutes(ctx) {
|
|
43420
|
+
const router = (0, import_express2.Router)();
|
|
43421
|
+
const { store } = ctx;
|
|
43422
|
+
const configPath = join10(store.projectRoot, ".knowns", "config.json");
|
|
43423
|
+
router.get("/", async (_req, res) => {
|
|
43270
43424
|
try {
|
|
43271
|
-
|
|
43272
|
-
|
|
43273
|
-
|
|
43425
|
+
if (!existsSync7(configPath)) {
|
|
43426
|
+
res.json({
|
|
43427
|
+
config: {
|
|
43428
|
+
name: "Knowns",
|
|
43429
|
+
defaultPriority: "medium",
|
|
43430
|
+
defaultLabels: [],
|
|
43431
|
+
timeFormat: "24h",
|
|
43432
|
+
visibleColumns: ["todo", "in-progress", "done"]
|
|
43433
|
+
}
|
|
43434
|
+
});
|
|
43274
43435
|
return;
|
|
43275
43436
|
}
|
|
43276
|
-
|
|
43277
|
-
|
|
43278
|
-
|
|
43279
|
-
|
|
43280
|
-
|
|
43281
|
-
|
|
43282
|
-
|
|
43283
|
-
|
|
43284
|
-
|
|
43285
|
-
|
|
43286
|
-
|
|
43287
|
-
|
|
43288
|
-
|
|
43289
|
-
console.error("Error updating task:", error46);
|
|
43290
|
-
res.status(500).json({ error: String(error46) });
|
|
43291
|
-
}
|
|
43292
|
-
});
|
|
43293
|
-
app.post("/api/tasks", async (req, res) => {
|
|
43294
|
-
try {
|
|
43295
|
-
const data = req.body;
|
|
43296
|
-
const task = await store.createTask(data);
|
|
43297
|
-
broadcast({ type: "tasks:updated", task });
|
|
43298
|
-
res.status(201).json(task);
|
|
43437
|
+
const content = await readFile4(configPath, "utf-8");
|
|
43438
|
+
const data = JSON.parse(content);
|
|
43439
|
+
const settings = data.settings || {};
|
|
43440
|
+
const config2 = {
|
|
43441
|
+
name: data.name,
|
|
43442
|
+
id: data.id,
|
|
43443
|
+
createdAt: data.createdAt,
|
|
43444
|
+
...settings
|
|
43445
|
+
};
|
|
43446
|
+
if (!config2.visibleColumns) {
|
|
43447
|
+
config2.visibleColumns = ["todo", "in-progress", "done"];
|
|
43448
|
+
}
|
|
43449
|
+
res.json({ config: config2 });
|
|
43299
43450
|
} catch (error46) {
|
|
43300
|
-
console.error("Error
|
|
43451
|
+
console.error("Error getting config:", error46);
|
|
43301
43452
|
res.status(500).json({ error: String(error46) });
|
|
43302
43453
|
}
|
|
43303
43454
|
});
|
|
43304
|
-
|
|
43455
|
+
router.post("/", async (req, res) => {
|
|
43305
43456
|
try {
|
|
43306
|
-
const
|
|
43307
|
-
|
|
43308
|
-
|
|
43309
|
-
|
|
43310
|
-
|
|
43311
|
-
res.json({ success: true });
|
|
43312
|
-
return;
|
|
43313
|
-
}
|
|
43314
|
-
} else if (type === "tasks:refresh") {
|
|
43315
|
-
broadcast({ type: "tasks:refresh" });
|
|
43316
|
-
res.json({ success: true });
|
|
43317
|
-
return;
|
|
43318
|
-
} else if (type === "docs:updated" && docPath) {
|
|
43319
|
-
broadcast({ type: "docs:updated", docPath });
|
|
43320
|
-
res.json({ success: true });
|
|
43321
|
-
return;
|
|
43322
|
-
} else if (type === "docs:refresh") {
|
|
43323
|
-
broadcast({ type: "docs:refresh" });
|
|
43324
|
-
res.json({ success: true });
|
|
43325
|
-
return;
|
|
43457
|
+
const config2 = req.body;
|
|
43458
|
+
let existingData = {};
|
|
43459
|
+
if (existsSync7(configPath)) {
|
|
43460
|
+
const content = await readFile4(configPath, "utf-8");
|
|
43461
|
+
existingData = JSON.parse(content);
|
|
43326
43462
|
}
|
|
43327
|
-
|
|
43463
|
+
const { name, ...settings } = config2;
|
|
43464
|
+
const merged = {
|
|
43465
|
+
...existingData,
|
|
43466
|
+
name: name || existingData.name,
|
|
43467
|
+
settings
|
|
43468
|
+
};
|
|
43469
|
+
await writeFile3(configPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
43470
|
+
res.json({ success: true });
|
|
43328
43471
|
} catch (error46) {
|
|
43329
|
-
console.error("
|
|
43472
|
+
console.error("Error saving config:", error46);
|
|
43330
43473
|
res.status(500).json({ error: String(error46) });
|
|
43331
43474
|
}
|
|
43332
43475
|
});
|
|
43333
|
-
|
|
43476
|
+
return router;
|
|
43477
|
+
}
|
|
43478
|
+
|
|
43479
|
+
// src/server/routes/docs.ts
|
|
43480
|
+
var import_express3 = __toESM(require_express2(), 1);
|
|
43481
|
+
var import_gray_matter3 = __toESM(require_gray_matter(), 1);
|
|
43482
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
43483
|
+
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
43484
|
+
import { join as join12 } from "node:path";
|
|
43485
|
+
|
|
43486
|
+
// src/server/utils/markdown.ts
|
|
43487
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
43488
|
+
import { join as join11, relative } from "node:path";
|
|
43489
|
+
async function findMarkdownFiles(dir, baseDir) {
|
|
43490
|
+
const files = [];
|
|
43491
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
43492
|
+
for (const entry of entries) {
|
|
43493
|
+
const fullPath = join11(dir, entry.name);
|
|
43494
|
+
if (entry.isDirectory()) {
|
|
43495
|
+
const subFiles = await findMarkdownFiles(fullPath, baseDir);
|
|
43496
|
+
files.push(...subFiles);
|
|
43497
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
43498
|
+
const relativePath = relative(baseDir, fullPath);
|
|
43499
|
+
files.push(relativePath);
|
|
43500
|
+
}
|
|
43501
|
+
}
|
|
43502
|
+
return files;
|
|
43503
|
+
}
|
|
43504
|
+
|
|
43505
|
+
// src/server/routes/docs.ts
|
|
43506
|
+
function createDocRoutes(ctx) {
|
|
43507
|
+
const router = (0, import_express3.Router)();
|
|
43508
|
+
const { store, broadcast } = ctx;
|
|
43509
|
+
const docsDir = join12(store.projectRoot, ".knowns", "docs");
|
|
43510
|
+
router.get("/", async (_req, res) => {
|
|
43334
43511
|
try {
|
|
43335
|
-
|
|
43336
|
-
if (!existsSync7(docsDir)) {
|
|
43512
|
+
if (!existsSync8(docsDir)) {
|
|
43337
43513
|
res.json({ docs: [] });
|
|
43338
43514
|
return;
|
|
43339
43515
|
}
|
|
43340
43516
|
const mdFiles = await findMarkdownFiles(docsDir, docsDir);
|
|
43341
43517
|
const docs = await Promise.all(
|
|
43342
43518
|
mdFiles.map(async (relativePath) => {
|
|
43343
|
-
const fullPath =
|
|
43344
|
-
const content = await
|
|
43519
|
+
const fullPath = join12(docsDir, relativePath);
|
|
43520
|
+
const content = await readFile5(fullPath, "utf-8");
|
|
43345
43521
|
const { data, content: docContent } = (0, import_gray_matter3.default)(content);
|
|
43346
43522
|
const pathParts = relativePath.split("/");
|
|
43347
43523
|
const filename = pathParts[pathParts.length - 1];
|
|
@@ -43361,20 +43537,19 @@ async function startServer(options2) {
|
|
|
43361
43537
|
res.status(500).json({ error: String(error46) });
|
|
43362
43538
|
}
|
|
43363
43539
|
});
|
|
43364
|
-
|
|
43540
|
+
router.get("/{*path}", async (req, res) => {
|
|
43365
43541
|
try {
|
|
43366
43542
|
const docPath = Array.isArray(req.params.path) ? req.params.path.join("/") : req.params.path;
|
|
43367
|
-
const
|
|
43368
|
-
const fullPath = join10(docsDir, docPath);
|
|
43543
|
+
const fullPath = join12(docsDir, docPath);
|
|
43369
43544
|
if (!fullPath.startsWith(docsDir)) {
|
|
43370
43545
|
res.status(400).json({ error: "Invalid path" });
|
|
43371
43546
|
return;
|
|
43372
43547
|
}
|
|
43373
|
-
if (!
|
|
43548
|
+
if (!existsSync8(fullPath)) {
|
|
43374
43549
|
res.status(404).json({ error: "Document not found" });
|
|
43375
43550
|
return;
|
|
43376
43551
|
}
|
|
43377
|
-
const content = await
|
|
43552
|
+
const content = await readFile5(fullPath, "utf-8");
|
|
43378
43553
|
const { data, content: docContent } = (0, import_gray_matter3.default)(content);
|
|
43379
43554
|
const pathParts = docPath.split("/");
|
|
43380
43555
|
const filename = pathParts[pathParts.length - 1];
|
|
@@ -43395,57 +43570,9 @@ async function startServer(options2) {
|
|
|
43395
43570
|
res.status(500).json({ error: String(error46) });
|
|
43396
43571
|
}
|
|
43397
43572
|
});
|
|
43398
|
-
|
|
43573
|
+
router.post("/", async (req, res) => {
|
|
43399
43574
|
try {
|
|
43400
|
-
|
|
43401
|
-
const docsDir = join10(store.projectRoot, ".knowns", "docs");
|
|
43402
|
-
const fullPath = join10(docsDir, docPath);
|
|
43403
|
-
if (!fullPath.startsWith(docsDir)) {
|
|
43404
|
-
res.status(400).json({ error: "Invalid path" });
|
|
43405
|
-
return;
|
|
43406
|
-
}
|
|
43407
|
-
if (!existsSync7(fullPath)) {
|
|
43408
|
-
res.status(404).json({ error: "Document not found" });
|
|
43409
|
-
return;
|
|
43410
|
-
}
|
|
43411
|
-
const data = req.body;
|
|
43412
|
-
const { content, title, description, tags } = data;
|
|
43413
|
-
const existingContent = await readFile4(fullPath, "utf-8");
|
|
43414
|
-
const { data: existingData } = (0, import_gray_matter3.default)(existingContent);
|
|
43415
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
43416
|
-
const updatedFrontmatter = {
|
|
43417
|
-
...existingData,
|
|
43418
|
-
title: title ?? existingData.title,
|
|
43419
|
-
description: description ?? existingData.description,
|
|
43420
|
-
tags: tags ?? existingData.tags,
|
|
43421
|
-
updatedAt: now
|
|
43422
|
-
};
|
|
43423
|
-
const newFileContent = import_gray_matter3.default.stringify(content ?? "", updatedFrontmatter);
|
|
43424
|
-
await writeFile3(fullPath, newFileContent, "utf-8");
|
|
43425
|
-
const pathParts = docPath.split("/");
|
|
43426
|
-
const filename = pathParts[pathParts.length - 1];
|
|
43427
|
-
const folder = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
43428
|
-
const updatedDoc = {
|
|
43429
|
-
filename,
|
|
43430
|
-
path: docPath,
|
|
43431
|
-
folder,
|
|
43432
|
-
title: updatedFrontmatter.title || filename.replace(/\.md$/, ""),
|
|
43433
|
-
description: updatedFrontmatter.description || "",
|
|
43434
|
-
tags: updatedFrontmatter.tags || [],
|
|
43435
|
-
metadata: updatedFrontmatter,
|
|
43436
|
-
content: content ?? ""
|
|
43437
|
-
};
|
|
43438
|
-
broadcast({ type: "docs:updated", doc: updatedDoc });
|
|
43439
|
-
res.json(updatedDoc);
|
|
43440
|
-
} catch (error46) {
|
|
43441
|
-
console.error("Error updating doc:", error46);
|
|
43442
|
-
res.status(500).json({ error: String(error46) });
|
|
43443
|
-
}
|
|
43444
|
-
});
|
|
43445
|
-
app.post("/api/docs", async (req, res) => {
|
|
43446
|
-
try {
|
|
43447
|
-
const docsDir = join10(store.projectRoot, ".knowns", "docs");
|
|
43448
|
-
if (!existsSync7(docsDir)) {
|
|
43575
|
+
if (!existsSync8(docsDir)) {
|
|
43449
43576
|
await mkdir5(docsDir, { recursive: true });
|
|
43450
43577
|
}
|
|
43451
43578
|
const data = req.body;
|
|
@@ -43459,16 +43586,16 @@ async function startServer(options2) {
|
|
|
43459
43586
|
let targetDir;
|
|
43460
43587
|
if (folder?.trim()) {
|
|
43461
43588
|
const cleanFolder = folder.trim().replace(/^\/+|\/+$/g, "");
|
|
43462
|
-
targetDir =
|
|
43463
|
-
filepath =
|
|
43589
|
+
targetDir = join12(docsDir, cleanFolder);
|
|
43590
|
+
filepath = join12(targetDir, filename);
|
|
43464
43591
|
} else {
|
|
43465
43592
|
targetDir = docsDir;
|
|
43466
|
-
filepath =
|
|
43593
|
+
filepath = join12(docsDir, filename);
|
|
43467
43594
|
}
|
|
43468
|
-
if (!
|
|
43595
|
+
if (!existsSync8(targetDir)) {
|
|
43469
43596
|
await mkdir5(targetDir, { recursive: true });
|
|
43470
43597
|
}
|
|
43471
|
-
if (
|
|
43598
|
+
if (existsSync8(filepath)) {
|
|
43472
43599
|
res.status(409).json({ error: "Document with this title already exists in this folder" });
|
|
43473
43600
|
return;
|
|
43474
43601
|
}
|
|
@@ -43481,7 +43608,7 @@ async function startServer(options2) {
|
|
|
43481
43608
|
tags: tags || []
|
|
43482
43609
|
};
|
|
43483
43610
|
const markdown = import_gray_matter3.default.stringify(content || "", frontmatter);
|
|
43484
|
-
await
|
|
43611
|
+
await writeFile4(filepath, markdown, "utf-8");
|
|
43485
43612
|
res.status(201).json({
|
|
43486
43613
|
success: true,
|
|
43487
43614
|
filename,
|
|
@@ -43494,93 +43621,107 @@ async function startServer(options2) {
|
|
|
43494
43621
|
res.status(500).json({ error: String(error46) });
|
|
43495
43622
|
}
|
|
43496
43623
|
});
|
|
43497
|
-
|
|
43624
|
+
router.put("/{*path}", async (req, res) => {
|
|
43498
43625
|
try {
|
|
43499
|
-
const
|
|
43500
|
-
|
|
43501
|
-
|
|
43502
|
-
|
|
43503
|
-
name: "Knowns",
|
|
43504
|
-
defaultPriority: "medium",
|
|
43505
|
-
defaultLabels: [],
|
|
43506
|
-
timeFormat: "24h",
|
|
43507
|
-
visibleColumns: ["todo", "in-progress", "done"]
|
|
43508
|
-
}
|
|
43509
|
-
});
|
|
43626
|
+
const docPath = Array.isArray(req.params.path) ? req.params.path.join("/") : req.params.path;
|
|
43627
|
+
const fullPath = join12(docsDir, docPath);
|
|
43628
|
+
if (!fullPath.startsWith(docsDir)) {
|
|
43629
|
+
res.status(400).json({ error: "Invalid path" });
|
|
43510
43630
|
return;
|
|
43511
43631
|
}
|
|
43512
|
-
|
|
43513
|
-
|
|
43514
|
-
|
|
43515
|
-
const config2 = {
|
|
43516
|
-
name: data.name,
|
|
43517
|
-
id: data.id,
|
|
43518
|
-
createdAt: data.createdAt,
|
|
43519
|
-
...settings
|
|
43520
|
-
};
|
|
43521
|
-
if (!config2.visibleColumns) {
|
|
43522
|
-
config2.visibleColumns = ["todo", "in-progress", "done"];
|
|
43523
|
-
}
|
|
43524
|
-
res.json({ config: config2 });
|
|
43525
|
-
} catch (error46) {
|
|
43526
|
-
console.error("Error getting config:", error46);
|
|
43527
|
-
res.status(500).json({ error: String(error46) });
|
|
43528
|
-
}
|
|
43529
|
-
});
|
|
43530
|
-
app.post("/api/config", async (req, res) => {
|
|
43531
|
-
try {
|
|
43532
|
-
const config2 = req.body;
|
|
43533
|
-
const configPath = join10(store.projectRoot, ".knowns", "config.json");
|
|
43534
|
-
let existingData = {};
|
|
43535
|
-
if (existsSync7(configPath)) {
|
|
43536
|
-
const content = await readFile4(configPath, "utf-8");
|
|
43537
|
-
existingData = JSON.parse(content);
|
|
43632
|
+
if (!existsSync8(fullPath)) {
|
|
43633
|
+
res.status(404).json({ error: "Document not found" });
|
|
43634
|
+
return;
|
|
43538
43635
|
}
|
|
43539
|
-
const
|
|
43540
|
-
const
|
|
43636
|
+
const data = req.body;
|
|
43637
|
+
const { content, title, description, tags } = data;
|
|
43638
|
+
const existingContent = await readFile5(fullPath, "utf-8");
|
|
43639
|
+
const { data: existingData } = (0, import_gray_matter3.default)(existingContent);
|
|
43640
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
43641
|
+
const updatedFrontmatter = {
|
|
43541
43642
|
...existingData,
|
|
43542
|
-
|
|
43543
|
-
|
|
43643
|
+
title: title ?? existingData.title,
|
|
43644
|
+
description: description ?? existingData.description,
|
|
43645
|
+
tags: tags ?? existingData.tags,
|
|
43646
|
+
updatedAt: now
|
|
43544
43647
|
};
|
|
43545
|
-
|
|
43546
|
-
|
|
43648
|
+
const newFileContent = import_gray_matter3.default.stringify(content ?? "", updatedFrontmatter);
|
|
43649
|
+
await writeFile4(fullPath, newFileContent, "utf-8");
|
|
43650
|
+
const pathParts = docPath.split("/");
|
|
43651
|
+
const filename = pathParts[pathParts.length - 1];
|
|
43652
|
+
const folder = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
|
|
43653
|
+
const updatedDoc = {
|
|
43654
|
+
filename,
|
|
43655
|
+
path: docPath,
|
|
43656
|
+
folder,
|
|
43657
|
+
title: updatedFrontmatter.title || filename.replace(/\.md$/, ""),
|
|
43658
|
+
description: updatedFrontmatter.description || "",
|
|
43659
|
+
tags: updatedFrontmatter.tags || [],
|
|
43660
|
+
metadata: updatedFrontmatter,
|
|
43661
|
+
content: content ?? ""
|
|
43662
|
+
};
|
|
43663
|
+
broadcast({ type: "docs:updated", doc: updatedDoc });
|
|
43664
|
+
res.json(updatedDoc);
|
|
43547
43665
|
} catch (error46) {
|
|
43548
|
-
console.error("Error
|
|
43666
|
+
console.error("Error updating doc:", error46);
|
|
43549
43667
|
res.status(500).json({ error: String(error46) });
|
|
43550
43668
|
}
|
|
43551
43669
|
});
|
|
43552
|
-
|
|
43670
|
+
return router;
|
|
43671
|
+
}
|
|
43672
|
+
|
|
43673
|
+
// src/server/routes/notify.ts
|
|
43674
|
+
var import_express4 = __toESM(require_express2(), 1);
|
|
43675
|
+
function createNotifyRoutes(ctx) {
|
|
43676
|
+
const router = (0, import_express4.Router)();
|
|
43677
|
+
const { store, broadcast } = ctx;
|
|
43678
|
+
router.post("/", async (req, res) => {
|
|
43553
43679
|
try {
|
|
43554
|
-
const
|
|
43555
|
-
|
|
43556
|
-
|
|
43557
|
-
|
|
43558
|
-
|
|
43559
|
-
|
|
43560
|
-
|
|
43561
|
-
if (type) {
|
|
43562
|
-
const hasMatchingChange = version2.changes.some((c) => c.field === type);
|
|
43563
|
-
if (!hasMatchingChange) continue;
|
|
43564
|
-
}
|
|
43565
|
-
allActivities.push({
|
|
43566
|
-
taskId: task.id,
|
|
43567
|
-
taskTitle: task.title,
|
|
43568
|
-
version: version2.version,
|
|
43569
|
-
timestamp: version2.timestamp,
|
|
43570
|
-
author: version2.author,
|
|
43571
|
-
changes: version2.changes
|
|
43572
|
-
});
|
|
43680
|
+
const { taskId, type, docPath } = req.body;
|
|
43681
|
+
if (taskId) {
|
|
43682
|
+
const task = await store.getTask(taskId);
|
|
43683
|
+
if (task) {
|
|
43684
|
+
broadcast({ type: "tasks:updated", task });
|
|
43685
|
+
res.json({ success: true });
|
|
43686
|
+
return;
|
|
43573
43687
|
}
|
|
43688
|
+
} else if (type === "tasks:refresh") {
|
|
43689
|
+
broadcast({ type: "tasks:refresh" });
|
|
43690
|
+
res.json({ success: true });
|
|
43691
|
+
return;
|
|
43692
|
+
} else if (type === "docs:updated" && docPath) {
|
|
43693
|
+
broadcast({ type: "docs:updated", docPath });
|
|
43694
|
+
res.json({ success: true });
|
|
43695
|
+
return;
|
|
43696
|
+
} else if (type === "docs:refresh") {
|
|
43697
|
+
broadcast({ type: "docs:refresh" });
|
|
43698
|
+
res.json({ success: true });
|
|
43699
|
+
return;
|
|
43700
|
+
} else if (type === "time:updated") {
|
|
43701
|
+
const { active } = req.body;
|
|
43702
|
+
broadcast({ type: "time:updated", active });
|
|
43703
|
+
res.json({ success: true });
|
|
43704
|
+
return;
|
|
43574
43705
|
}
|
|
43575
|
-
|
|
43576
|
-
const limited = allActivities.slice(0, limit);
|
|
43577
|
-
res.json({ activities: limited });
|
|
43706
|
+
res.status(400).json({ success: false, error: "Invalid notify request" });
|
|
43578
43707
|
} catch (error46) {
|
|
43579
|
-
console.error("
|
|
43708
|
+
console.error("[Server] Notify error:", error46);
|
|
43580
43709
|
res.status(500).json({ error: String(error46) });
|
|
43581
43710
|
}
|
|
43582
43711
|
});
|
|
43583
|
-
|
|
43712
|
+
return router;
|
|
43713
|
+
}
|
|
43714
|
+
|
|
43715
|
+
// src/server/routes/search.ts
|
|
43716
|
+
var import_express5 = __toESM(require_express2(), 1);
|
|
43717
|
+
var import_gray_matter4 = __toESM(require_gray_matter(), 1);
|
|
43718
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
43719
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
43720
|
+
import { join as join13 } from "node:path";
|
|
43721
|
+
function createSearchRoutes(ctx) {
|
|
43722
|
+
const router = (0, import_express5.Router)();
|
|
43723
|
+
const { store } = ctx;
|
|
43724
|
+
router.get("/", async (req, res) => {
|
|
43584
43725
|
try {
|
|
43585
43726
|
const query = req.query.q;
|
|
43586
43727
|
if (!query) {
|
|
@@ -43601,14 +43742,14 @@ async function startServer(options2) {
|
|
|
43601
43742
|
].join(" ").toLowerCase();
|
|
43602
43743
|
return searchText.includes(q);
|
|
43603
43744
|
});
|
|
43604
|
-
const docsDir =
|
|
43745
|
+
const docsDir = join13(store.projectRoot, ".knowns", "docs");
|
|
43605
43746
|
const docResults = [];
|
|
43606
|
-
if (
|
|
43747
|
+
if (existsSync9(docsDir)) {
|
|
43607
43748
|
const mdFiles = await findMarkdownFiles(docsDir, docsDir);
|
|
43608
43749
|
for (const relativePath of mdFiles) {
|
|
43609
|
-
const fullPath =
|
|
43610
|
-
const content = await
|
|
43611
|
-
const { data, content: docContent } = (0,
|
|
43750
|
+
const fullPath = join13(docsDir, relativePath);
|
|
43751
|
+
const content = await readFile6(fullPath, "utf-8");
|
|
43752
|
+
const { data, content: docContent } = (0, import_gray_matter4.default)(content);
|
|
43612
43753
|
const searchText = `${data.title || ""} ${data.description || ""} ${data.tags?.join(" ") || ""} ${docContent}`.toLowerCase();
|
|
43613
43754
|
if (searchText.includes(q)) {
|
|
43614
43755
|
const pathParts = relativePath.split("/");
|
|
@@ -43630,10 +43771,343 @@ async function startServer(options2) {
|
|
|
43630
43771
|
res.status(500).json({ error: String(error46) });
|
|
43631
43772
|
}
|
|
43632
43773
|
});
|
|
43774
|
+
return router;
|
|
43775
|
+
}
|
|
43776
|
+
|
|
43777
|
+
// src/server/routes/tasks.ts
|
|
43778
|
+
var import_express6 = __toESM(require_express2(), 1);
|
|
43779
|
+
function createTaskRoutes(ctx) {
|
|
43780
|
+
const router = (0, import_express6.Router)();
|
|
43781
|
+
const { store, broadcast } = ctx;
|
|
43782
|
+
router.get("/", async (_req, res) => {
|
|
43783
|
+
try {
|
|
43784
|
+
const tasks = await store.getAllTasks();
|
|
43785
|
+
res.json(tasks);
|
|
43786
|
+
} catch (error46) {
|
|
43787
|
+
console.error("Error getting tasks:", error46);
|
|
43788
|
+
res.status(500).json({ error: String(error46) });
|
|
43789
|
+
}
|
|
43790
|
+
});
|
|
43791
|
+
router.get("/:id/history", async (req, res) => {
|
|
43792
|
+
try {
|
|
43793
|
+
const taskId = req.params.id;
|
|
43794
|
+
const history = await store.getTaskVersionHistory(taskId);
|
|
43795
|
+
res.json({ versions: history });
|
|
43796
|
+
} catch (error46) {
|
|
43797
|
+
console.error("Error getting task history:", error46);
|
|
43798
|
+
res.status(500).json({ error: String(error46) });
|
|
43799
|
+
}
|
|
43800
|
+
});
|
|
43801
|
+
router.get("/:id", async (req, res) => {
|
|
43802
|
+
try {
|
|
43803
|
+
const task = await store.getTask(req.params.id);
|
|
43804
|
+
if (!task) {
|
|
43805
|
+
res.status(404).json({ error: "Task not found" });
|
|
43806
|
+
return;
|
|
43807
|
+
}
|
|
43808
|
+
res.json(task);
|
|
43809
|
+
} catch (error46) {
|
|
43810
|
+
console.error("Error getting task:", error46);
|
|
43811
|
+
res.status(500).json({ error: String(error46) });
|
|
43812
|
+
}
|
|
43813
|
+
});
|
|
43814
|
+
router.post("/", async (req, res) => {
|
|
43815
|
+
try {
|
|
43816
|
+
const data = req.body;
|
|
43817
|
+
const task = await store.createTask(data);
|
|
43818
|
+
broadcast({ type: "tasks:updated", task });
|
|
43819
|
+
res.status(201).json(task);
|
|
43820
|
+
} catch (error46) {
|
|
43821
|
+
console.error("Error creating task:", error46);
|
|
43822
|
+
res.status(500).json({ error: String(error46) });
|
|
43823
|
+
}
|
|
43824
|
+
});
|
|
43825
|
+
router.post("/batch-archive", async (req, res) => {
|
|
43826
|
+
try {
|
|
43827
|
+
const { olderThanMs } = req.body;
|
|
43828
|
+
if (typeof olderThanMs !== "number" || olderThanMs < 0) {
|
|
43829
|
+
res.status(400).json({ error: "olderThanMs must be a positive number" });
|
|
43830
|
+
return;
|
|
43831
|
+
}
|
|
43832
|
+
const archivedTasks = await store.batchArchiveTasks(olderThanMs);
|
|
43833
|
+
broadcast({ type: "tasks:batch-archived", tasks: archivedTasks });
|
|
43834
|
+
res.json({ success: true, count: archivedTasks.length, tasks: archivedTasks });
|
|
43835
|
+
} catch (error46) {
|
|
43836
|
+
console.error("Error batch archiving tasks:", error46);
|
|
43837
|
+
res.status(500).json({ error: String(error46) });
|
|
43838
|
+
}
|
|
43839
|
+
});
|
|
43840
|
+
router.put("/:id", async (req, res) => {
|
|
43841
|
+
try {
|
|
43842
|
+
const updates = req.body;
|
|
43843
|
+
const task = await store.updateTask(req.params.id, updates);
|
|
43844
|
+
broadcast({ type: "tasks:updated", task });
|
|
43845
|
+
res.json(task);
|
|
43846
|
+
} catch (error46) {
|
|
43847
|
+
console.error("Error updating task:", error46);
|
|
43848
|
+
res.status(500).json({ error: String(error46) });
|
|
43849
|
+
}
|
|
43850
|
+
});
|
|
43851
|
+
router.post("/:id/archive", async (req, res) => {
|
|
43852
|
+
try {
|
|
43853
|
+
const task = await store.archiveTask(req.params.id);
|
|
43854
|
+
broadcast({ type: "tasks:archived", task });
|
|
43855
|
+
res.json({ success: true, task });
|
|
43856
|
+
} catch (error46) {
|
|
43857
|
+
console.error("Error archiving task:", error46);
|
|
43858
|
+
res.status(500).json({ error: String(error46) });
|
|
43859
|
+
}
|
|
43860
|
+
});
|
|
43861
|
+
router.post("/:id/unarchive", async (req, res) => {
|
|
43862
|
+
try {
|
|
43863
|
+
const task = await store.unarchiveTask(req.params.id);
|
|
43864
|
+
broadcast({ type: "tasks:unarchived", task });
|
|
43865
|
+
res.json({ success: true, task });
|
|
43866
|
+
} catch (error46) {
|
|
43867
|
+
console.error("Error unarchiving task:", error46);
|
|
43868
|
+
res.status(500).json({ error: String(error46) });
|
|
43869
|
+
}
|
|
43870
|
+
});
|
|
43871
|
+
return router;
|
|
43872
|
+
}
|
|
43873
|
+
|
|
43874
|
+
// src/server/routes/time.ts
|
|
43875
|
+
var import_express7 = __toESM(require_express2(), 1);
|
|
43876
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
43877
|
+
import { readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
|
|
43878
|
+
import { join as join14 } from "node:path";
|
|
43879
|
+
function createTimeRoutes(ctx) {
|
|
43880
|
+
const router = (0, import_express7.Router)();
|
|
43881
|
+
const { store, broadcast } = ctx;
|
|
43882
|
+
const timePath = join14(store.projectRoot, ".knowns", "time.json");
|
|
43883
|
+
async function loadTimeData2() {
|
|
43884
|
+
if (!existsSync10(timePath)) {
|
|
43885
|
+
return { active: null };
|
|
43886
|
+
}
|
|
43887
|
+
const content = await readFile7(timePath, "utf-8");
|
|
43888
|
+
return JSON.parse(content);
|
|
43889
|
+
}
|
|
43890
|
+
async function saveTimeData2(data) {
|
|
43891
|
+
await writeFile5(timePath, JSON.stringify(data, null, 2), "utf-8");
|
|
43892
|
+
}
|
|
43893
|
+
router.get("/status", async (_req, res) => {
|
|
43894
|
+
try {
|
|
43895
|
+
const data = await loadTimeData2();
|
|
43896
|
+
if (!data.active) {
|
|
43897
|
+
res.json({ active: null });
|
|
43898
|
+
return;
|
|
43899
|
+
}
|
|
43900
|
+
const task = await store.getTask(data.active.taskId);
|
|
43901
|
+
res.json({
|
|
43902
|
+
active: {
|
|
43903
|
+
...data.active,
|
|
43904
|
+
taskTitle: task?.title || "Unknown Task"
|
|
43905
|
+
}
|
|
43906
|
+
});
|
|
43907
|
+
} catch (error46) {
|
|
43908
|
+
console.error("Error getting time status:", error46);
|
|
43909
|
+
res.status(500).json({ error: String(error46) });
|
|
43910
|
+
}
|
|
43911
|
+
});
|
|
43912
|
+
router.post("/start", async (req, res) => {
|
|
43913
|
+
try {
|
|
43914
|
+
const { taskId } = req.body;
|
|
43915
|
+
if (!taskId) {
|
|
43916
|
+
res.status(400).json({ error: "taskId is required" });
|
|
43917
|
+
return;
|
|
43918
|
+
}
|
|
43919
|
+
const task = await store.getTask(taskId);
|
|
43920
|
+
if (!task) {
|
|
43921
|
+
res.status(404).json({ error: `Task ${taskId} not found` });
|
|
43922
|
+
return;
|
|
43923
|
+
}
|
|
43924
|
+
const data = await loadTimeData2();
|
|
43925
|
+
if (data.active) {
|
|
43926
|
+
res.status(409).json({
|
|
43927
|
+
error: `Timer already running for task #${data.active.taskId}`,
|
|
43928
|
+
activeTaskId: data.active.taskId
|
|
43929
|
+
});
|
|
43930
|
+
return;
|
|
43931
|
+
}
|
|
43932
|
+
data.active = {
|
|
43933
|
+
taskId,
|
|
43934
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
43935
|
+
pausedAt: null,
|
|
43936
|
+
totalPausedMs: 0
|
|
43937
|
+
};
|
|
43938
|
+
await saveTimeData2(data);
|
|
43939
|
+
broadcast({ type: "time:updated", active: { ...data.active, taskTitle: task.title } });
|
|
43940
|
+
res.json({
|
|
43941
|
+
success: true,
|
|
43942
|
+
active: { ...data.active, taskTitle: task.title }
|
|
43943
|
+
});
|
|
43944
|
+
} catch (error46) {
|
|
43945
|
+
console.error("Error starting timer:", error46);
|
|
43946
|
+
res.status(500).json({ error: String(error46) });
|
|
43947
|
+
}
|
|
43948
|
+
});
|
|
43949
|
+
router.post("/stop", async (req, res) => {
|
|
43950
|
+
try {
|
|
43951
|
+
const data = await loadTimeData2();
|
|
43952
|
+
if (!data.active) {
|
|
43953
|
+
res.status(400).json({ error: "No active timer" });
|
|
43954
|
+
return;
|
|
43955
|
+
}
|
|
43956
|
+
const { taskId, startedAt, pausedAt, totalPausedMs } = data.active;
|
|
43957
|
+
const endTime = pausedAt ? new Date(pausedAt) : /* @__PURE__ */ new Date();
|
|
43958
|
+
const elapsed = endTime.getTime() - new Date(startedAt).getTime() - totalPausedMs;
|
|
43959
|
+
const seconds = Math.floor(elapsed / 1e3);
|
|
43960
|
+
const task = await store.getTask(taskId);
|
|
43961
|
+
if (task) {
|
|
43962
|
+
const entry = {
|
|
43963
|
+
id: `te-${Date.now()}`,
|
|
43964
|
+
startedAt: new Date(startedAt),
|
|
43965
|
+
endedAt: endTime,
|
|
43966
|
+
duration: seconds
|
|
43967
|
+
};
|
|
43968
|
+
task.timeEntries.push(entry);
|
|
43969
|
+
task.timeSpent += seconds;
|
|
43970
|
+
await store.updateTask(taskId, {
|
|
43971
|
+
timeEntries: task.timeEntries,
|
|
43972
|
+
timeSpent: task.timeSpent
|
|
43973
|
+
});
|
|
43974
|
+
broadcast({ type: "tasks:updated", task });
|
|
43975
|
+
}
|
|
43976
|
+
data.active = null;
|
|
43977
|
+
await saveTimeData2(data);
|
|
43978
|
+
broadcast({ type: "time:updated", active: null });
|
|
43979
|
+
res.json({
|
|
43980
|
+
success: true,
|
|
43981
|
+
duration: seconds,
|
|
43982
|
+
taskId
|
|
43983
|
+
});
|
|
43984
|
+
} catch (error46) {
|
|
43985
|
+
console.error("Error stopping timer:", error46);
|
|
43986
|
+
res.status(500).json({ error: String(error46) });
|
|
43987
|
+
}
|
|
43988
|
+
});
|
|
43989
|
+
router.post("/pause", async (_req, res) => {
|
|
43990
|
+
try {
|
|
43991
|
+
const data = await loadTimeData2();
|
|
43992
|
+
if (!data.active) {
|
|
43993
|
+
res.status(400).json({ error: "No active timer" });
|
|
43994
|
+
return;
|
|
43995
|
+
}
|
|
43996
|
+
if (data.active.pausedAt) {
|
|
43997
|
+
res.status(400).json({ error: "Timer is already paused" });
|
|
43998
|
+
return;
|
|
43999
|
+
}
|
|
44000
|
+
data.active.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
44001
|
+
await saveTimeData2(data);
|
|
44002
|
+
const task = await store.getTask(data.active.taskId);
|
|
44003
|
+
broadcast({ type: "time:updated", active: { ...data.active, taskTitle: task?.title } });
|
|
44004
|
+
res.json({
|
|
44005
|
+
success: true,
|
|
44006
|
+
active: { ...data.active, taskTitle: task?.title }
|
|
44007
|
+
});
|
|
44008
|
+
} catch (error46) {
|
|
44009
|
+
console.error("Error pausing timer:", error46);
|
|
44010
|
+
res.status(500).json({ error: String(error46) });
|
|
44011
|
+
}
|
|
44012
|
+
});
|
|
44013
|
+
router.post("/resume", async (_req, res) => {
|
|
44014
|
+
try {
|
|
44015
|
+
const data = await loadTimeData2();
|
|
44016
|
+
if (!data.active) {
|
|
44017
|
+
res.status(400).json({ error: "No active timer" });
|
|
44018
|
+
return;
|
|
44019
|
+
}
|
|
44020
|
+
if (!data.active.pausedAt) {
|
|
44021
|
+
res.status(400).json({ error: "Timer is not paused" });
|
|
44022
|
+
return;
|
|
44023
|
+
}
|
|
44024
|
+
const pausedDuration = Date.now() - new Date(data.active.pausedAt).getTime();
|
|
44025
|
+
data.active.totalPausedMs += pausedDuration;
|
|
44026
|
+
data.active.pausedAt = null;
|
|
44027
|
+
await saveTimeData2(data);
|
|
44028
|
+
const task = await store.getTask(data.active.taskId);
|
|
44029
|
+
broadcast({ type: "time:updated", active: { ...data.active, taskTitle: task?.title } });
|
|
44030
|
+
res.json({
|
|
44031
|
+
success: true,
|
|
44032
|
+
active: { ...data.active, taskTitle: task?.title }
|
|
44033
|
+
});
|
|
44034
|
+
} catch (error46) {
|
|
44035
|
+
console.error("Error resuming timer:", error46);
|
|
44036
|
+
res.status(500).json({ error: String(error46) });
|
|
44037
|
+
}
|
|
44038
|
+
});
|
|
44039
|
+
return router;
|
|
44040
|
+
}
|
|
44041
|
+
|
|
44042
|
+
// src/server/routes/index.ts
|
|
44043
|
+
function createRoutes(ctx) {
|
|
44044
|
+
const router = (0, import_express8.Router)();
|
|
44045
|
+
router.use("/tasks", createTaskRoutes(ctx));
|
|
44046
|
+
router.use("/docs", createDocRoutes(ctx));
|
|
44047
|
+
router.use("/config", createConfigRoutes(ctx));
|
|
44048
|
+
router.use("/search", createSearchRoutes(ctx));
|
|
44049
|
+
router.use("/activities", createActivityRoutes(ctx));
|
|
44050
|
+
router.use("/notify", createNotifyRoutes(ctx));
|
|
44051
|
+
router.use("/time", createTimeRoutes(ctx));
|
|
44052
|
+
return router;
|
|
44053
|
+
}
|
|
44054
|
+
|
|
44055
|
+
// src/server/index.ts
|
|
44056
|
+
var isBun2 = typeof globalThis.Bun !== "undefined";
|
|
44057
|
+
async function startServer(options2) {
|
|
44058
|
+
const { port, projectRoot, open } = options2;
|
|
44059
|
+
const store = new FileStore(projectRoot);
|
|
44060
|
+
const clients = /* @__PURE__ */ new Set();
|
|
44061
|
+
const broadcast = (data) => {
|
|
44062
|
+
const msg = JSON.stringify(data);
|
|
44063
|
+
for (const client of clients) {
|
|
44064
|
+
if (client.readyState === 1) {
|
|
44065
|
+
client.send(msg);
|
|
44066
|
+
}
|
|
44067
|
+
}
|
|
44068
|
+
};
|
|
44069
|
+
const currentFile = realpathSync(fileURLToPath(import.meta.url));
|
|
44070
|
+
const currentDir = dirname3(currentFile);
|
|
44071
|
+
let packageRoot = currentDir;
|
|
44072
|
+
const normalizedDir = currentDir.replace(/[/\\]+$/, "");
|
|
44073
|
+
const dirName = basename2(normalizedDir);
|
|
44074
|
+
if (dirName === "dist") {
|
|
44075
|
+
packageRoot = join15(currentDir, "..");
|
|
44076
|
+
} else if (normalizedDir.includes("/src/server") || normalizedDir.includes("\\src\\server")) {
|
|
44077
|
+
packageRoot = join15(currentDir, "..", "..");
|
|
44078
|
+
}
|
|
44079
|
+
const uiDistPath = join15(packageRoot, "dist", "ui");
|
|
44080
|
+
if (!existsSync11(join15(uiDistPath, "index.html"))) {
|
|
44081
|
+
throw new Error(`UI build not found at ${uiDistPath}. Run 'bun run build:ui' first.`);
|
|
44082
|
+
}
|
|
44083
|
+
const app = (0, import_express9.default)();
|
|
44084
|
+
app.use((0, import_cors.default)());
|
|
44085
|
+
app.use(import_express9.default.json());
|
|
44086
|
+
const httpServer = createServer(app);
|
|
44087
|
+
const wss = new import_websocket_server.default({ server: httpServer, path: "/ws" });
|
|
44088
|
+
wss.on("connection", (ws) => {
|
|
44089
|
+
clients.add(ws);
|
|
44090
|
+
ws.on("close", () => {
|
|
44091
|
+
clients.delete(ws);
|
|
44092
|
+
});
|
|
44093
|
+
ws.on("error", (error46) => {
|
|
44094
|
+
console.error("WebSocket error:", error46);
|
|
44095
|
+
clients.delete(ws);
|
|
44096
|
+
});
|
|
44097
|
+
});
|
|
44098
|
+
app.use(
|
|
44099
|
+
"/assets",
|
|
44100
|
+
import_express9.default.static(join15(uiDistPath, "assets"), {
|
|
44101
|
+
maxAge: "1y",
|
|
44102
|
+
immutable: true
|
|
44103
|
+
})
|
|
44104
|
+
);
|
|
44105
|
+
app.use("/api", createRoutes({ store, broadcast }));
|
|
44106
|
+
app.use(errorHandler);
|
|
43633
44107
|
app.get("/{*path}", (_req, res) => {
|
|
43634
|
-
const indexPath =
|
|
43635
|
-
if (
|
|
43636
|
-
res.sendFile(
|
|
44108
|
+
const indexPath = join15(uiDistPath, "index.html");
|
|
44109
|
+
if (existsSync11(indexPath)) {
|
|
44110
|
+
res.sendFile("index.html", { root: uiDistPath });
|
|
43637
44111
|
} else {
|
|
43638
44112
|
res.status(404).send("Not Found");
|
|
43639
44113
|
}
|
|
@@ -43667,41 +44141,26 @@ async function startServer(options2) {
|
|
|
43667
44141
|
});
|
|
43668
44142
|
});
|
|
43669
44143
|
}
|
|
43670
|
-
async function findMarkdownFiles(dir, baseDir) {
|
|
43671
|
-
const files = [];
|
|
43672
|
-
const entries = await readdir4(dir, { withFileTypes: true });
|
|
43673
|
-
for (const entry of entries) {
|
|
43674
|
-
const fullPath = join10(dir, entry.name);
|
|
43675
|
-
if (entry.isDirectory()) {
|
|
43676
|
-
const subFiles = await findMarkdownFiles(fullPath, baseDir);
|
|
43677
|
-
files.push(...subFiles);
|
|
43678
|
-
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
43679
|
-
const relativePath = relative(baseDir, fullPath);
|
|
43680
|
-
files.push(relativePath);
|
|
43681
|
-
}
|
|
43682
|
-
}
|
|
43683
|
-
return files;
|
|
43684
|
-
}
|
|
43685
44144
|
|
|
43686
44145
|
// src/commands/browser.ts
|
|
43687
44146
|
var DEFAULT_PORT2 = 6420;
|
|
43688
44147
|
var CONFIG_FILE = ".knowns/config.json";
|
|
43689
44148
|
async function saveServerPort(projectRoot, port) {
|
|
43690
|
-
const configPath =
|
|
43691
|
-
const knownsDir =
|
|
43692
|
-
if (!
|
|
44149
|
+
const configPath = join16(projectRoot, CONFIG_FILE);
|
|
44150
|
+
const knownsDir = join16(projectRoot, ".knowns");
|
|
44151
|
+
if (!existsSync12(knownsDir)) {
|
|
43693
44152
|
await mkdir6(knownsDir, { recursive: true });
|
|
43694
44153
|
}
|
|
43695
44154
|
try {
|
|
43696
44155
|
let config2 = {};
|
|
43697
|
-
if (
|
|
43698
|
-
const content = await
|
|
44156
|
+
if (existsSync12(configPath)) {
|
|
44157
|
+
const content = await readFile8(configPath, "utf-8");
|
|
43699
44158
|
config2 = JSON.parse(content);
|
|
43700
44159
|
}
|
|
43701
44160
|
const settings = config2.settings || {};
|
|
43702
44161
|
settings.serverPort = port;
|
|
43703
44162
|
config2.settings = settings;
|
|
43704
|
-
await
|
|
44163
|
+
await writeFile6(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
43705
44164
|
} catch {
|
|
43706
44165
|
}
|
|
43707
44166
|
}
|
|
@@ -43733,17 +44192,17 @@ var browserCommand = new Command("browser").description("Open web UI for task ma
|
|
|
43733
44192
|
});
|
|
43734
44193
|
|
|
43735
44194
|
// src/commands/doc.ts
|
|
43736
|
-
import { existsSync as
|
|
43737
|
-
import { mkdir as mkdir7, readFile as
|
|
43738
|
-
import { join as
|
|
43739
|
-
var
|
|
43740
|
-
var DOCS_DIR =
|
|
44195
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
44196
|
+
import { mkdir as mkdir7, readFile as readFile9, readdir as readdir5, writeFile as writeFile7 } from "node:fs/promises";
|
|
44197
|
+
import { join as join17 } from "node:path";
|
|
44198
|
+
var import_gray_matter5 = __toESM(require_gray_matter(), 1);
|
|
44199
|
+
var DOCS_DIR = join17(process.cwd(), ".knowns", "docs");
|
|
43741
44200
|
async function getAllMdFiles(dir, basePath = "") {
|
|
43742
44201
|
const files = [];
|
|
43743
44202
|
const entries = await readdir5(dir, { withFileTypes: true });
|
|
43744
44203
|
for (const entry of entries) {
|
|
43745
|
-
const fullPath =
|
|
43746
|
-
const relativePath = basePath ?
|
|
44204
|
+
const fullPath = join17(dir, entry.name);
|
|
44205
|
+
const relativePath = basePath ? join17(basePath, entry.name) : entry.name;
|
|
43747
44206
|
if (entry.isDirectory()) {
|
|
43748
44207
|
const subFiles = await getAllMdFiles(fullPath, relativePath);
|
|
43749
44208
|
files.push(...subFiles);
|
|
@@ -43754,13 +44213,39 @@ async function getAllMdFiles(dir, basePath = "") {
|
|
|
43754
44213
|
return files;
|
|
43755
44214
|
}
|
|
43756
44215
|
async function ensureDocsDir() {
|
|
43757
|
-
if (!
|
|
44216
|
+
if (!existsSync13(DOCS_DIR)) {
|
|
43758
44217
|
await mkdir7(DOCS_DIR, { recursive: true });
|
|
43759
44218
|
}
|
|
43760
44219
|
}
|
|
43761
44220
|
function titleToFilename(title) {
|
|
43762
44221
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
43763
44222
|
}
|
|
44223
|
+
async function resolveDocPath(name) {
|
|
44224
|
+
await ensureDocsDir();
|
|
44225
|
+
let filename = name.endsWith(".md") ? name : `${name}.md`;
|
|
44226
|
+
let filepath = join17(DOCS_DIR, filename);
|
|
44227
|
+
if (!existsSync13(filepath)) {
|
|
44228
|
+
filename = `${titleToFilename(name)}.md`;
|
|
44229
|
+
filepath = join17(DOCS_DIR, filename);
|
|
44230
|
+
}
|
|
44231
|
+
if (!existsSync13(filepath)) {
|
|
44232
|
+
const allFiles = await getAllMdFiles(DOCS_DIR);
|
|
44233
|
+
const searchName = name.toLowerCase().replace(/\.md$/, "");
|
|
44234
|
+
const matchingFile = allFiles.find((file3) => {
|
|
44235
|
+
const fileNameOnly = file3.toLowerCase().replace(/\.md$/, "");
|
|
44236
|
+
const fileBaseName = file3.split("/").pop()?.toLowerCase().replace(/\.md$/, "");
|
|
44237
|
+
return fileNameOnly === searchName || fileBaseName === searchName || file3 === name;
|
|
44238
|
+
});
|
|
44239
|
+
if (matchingFile) {
|
|
44240
|
+
filename = matchingFile;
|
|
44241
|
+
filepath = join17(DOCS_DIR, matchingFile);
|
|
44242
|
+
}
|
|
44243
|
+
}
|
|
44244
|
+
if (!existsSync13(filepath)) {
|
|
44245
|
+
return null;
|
|
44246
|
+
}
|
|
44247
|
+
return { filepath, filename };
|
|
44248
|
+
}
|
|
43764
44249
|
var createCommand3 = new Command("create").description("Create a new documentation file").argument("<title>", "Document title").option("-d, --description <text>", "Document description").option("-t, --tags <tags>", "Comma-separated tags").option("-f, --folder <path>", "Folder path (e.g., guides, patterns/auth)").option("--plain", "Plain text output for AI").action(async (title, options2) => {
|
|
43765
44250
|
try {
|
|
43766
44251
|
await ensureDocsDir();
|
|
@@ -43769,14 +44254,14 @@ var createCommand3 = new Command("create").description("Create a new documentati
|
|
|
43769
44254
|
let relativePath = filename;
|
|
43770
44255
|
if (options2.folder) {
|
|
43771
44256
|
const folderPath = options2.folder.replace(/^\/|\/$/g, "");
|
|
43772
|
-
targetDir =
|
|
43773
|
-
relativePath =
|
|
43774
|
-
if (!
|
|
44257
|
+
targetDir = join17(DOCS_DIR, folderPath);
|
|
44258
|
+
relativePath = join17(folderPath, filename);
|
|
44259
|
+
if (!existsSync13(targetDir)) {
|
|
43775
44260
|
await mkdir7(targetDir, { recursive: true });
|
|
43776
44261
|
}
|
|
43777
44262
|
}
|
|
43778
|
-
const filepath =
|
|
43779
|
-
if (
|
|
44263
|
+
const filepath = join17(targetDir, filename);
|
|
44264
|
+
if (existsSync13(filepath)) {
|
|
43780
44265
|
console.error(source_default.red(`\u2717 Document already exists: ${relativePath}`));
|
|
43781
44266
|
process.exit(1);
|
|
43782
44267
|
}
|
|
@@ -43787,13 +44272,13 @@ var createCommand3 = new Command("create").description("Create a new documentati
|
|
|
43787
44272
|
updatedAt: now
|
|
43788
44273
|
};
|
|
43789
44274
|
if (options2.description) {
|
|
43790
|
-
metadata.description = options2.description;
|
|
44275
|
+
metadata.description = normalizeRefs(options2.description);
|
|
43791
44276
|
}
|
|
43792
44277
|
if (options2.tags) {
|
|
43793
44278
|
metadata.tags = options2.tags.split(",").map((t) => t.trim());
|
|
43794
44279
|
}
|
|
43795
|
-
const content =
|
|
43796
|
-
await
|
|
44280
|
+
const content = import_gray_matter5.default.stringify("# Content\n\nWrite your documentation here.\n", metadata);
|
|
44281
|
+
await writeFile7(filepath, content, "utf-8");
|
|
43797
44282
|
await notifyDocUpdate(relativePath);
|
|
43798
44283
|
if (options2.plain) {
|
|
43799
44284
|
console.log(`Created: ${relativePath}`);
|
|
@@ -43821,8 +44306,8 @@ var listCommand2 = new Command("list").description("List all documentation files
|
|
|
43821
44306
|
}
|
|
43822
44307
|
const docs = [];
|
|
43823
44308
|
for (const file3 of mdFiles) {
|
|
43824
|
-
const content = await
|
|
43825
|
-
const { data } = (0,
|
|
44309
|
+
const content = await readFile9(join17(DOCS_DIR, file3), "utf-8");
|
|
44310
|
+
const { data } = (0, import_gray_matter5.default)(content);
|
|
43826
44311
|
docs.push({
|
|
43827
44312
|
filename: file3,
|
|
43828
44313
|
metadata: data
|
|
@@ -43880,12 +44365,12 @@ var viewCommand2 = new Command("view").description("View a documentation file").
|
|
|
43880
44365
|
try {
|
|
43881
44366
|
await ensureDocsDir();
|
|
43882
44367
|
let filename = name.endsWith(".md") ? name : `${name}.md`;
|
|
43883
|
-
let filepath =
|
|
43884
|
-
if (!
|
|
44368
|
+
let filepath = join17(DOCS_DIR, filename);
|
|
44369
|
+
if (!existsSync13(filepath)) {
|
|
43885
44370
|
filename = `${titleToFilename(name)}.md`;
|
|
43886
|
-
filepath =
|
|
44371
|
+
filepath = join17(DOCS_DIR, filename);
|
|
43887
44372
|
}
|
|
43888
|
-
if (!
|
|
44373
|
+
if (!existsSync13(filepath)) {
|
|
43889
44374
|
const allFiles = await getAllMdFiles(DOCS_DIR);
|
|
43890
44375
|
const searchName = name.toLowerCase().replace(/\.md$/, "");
|
|
43891
44376
|
const matchingFile = allFiles.find((file3) => {
|
|
@@ -43895,15 +44380,15 @@ var viewCommand2 = new Command("view").description("View a documentation file").
|
|
|
43895
44380
|
});
|
|
43896
44381
|
if (matchingFile) {
|
|
43897
44382
|
filename = matchingFile;
|
|
43898
|
-
filepath =
|
|
44383
|
+
filepath = join17(DOCS_DIR, matchingFile);
|
|
43899
44384
|
}
|
|
43900
44385
|
}
|
|
43901
|
-
if (!
|
|
44386
|
+
if (!existsSync13(filepath)) {
|
|
43902
44387
|
console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
|
|
43903
44388
|
process.exit(1);
|
|
43904
44389
|
}
|
|
43905
|
-
const fileContent = await
|
|
43906
|
-
const { data, content } = (0,
|
|
44390
|
+
const fileContent = await readFile9(filepath, "utf-8");
|
|
44391
|
+
const { data, content } = (0, import_gray_matter5.default)(fileContent);
|
|
43907
44392
|
const metadata = data;
|
|
43908
44393
|
if (options2.plain) {
|
|
43909
44394
|
const border = "-".repeat(50);
|
|
@@ -43989,13 +44474,13 @@ var editCommand2 = new Command("edit").description("Edit a documentation file (m
|
|
|
43989
44474
|
try {
|
|
43990
44475
|
await ensureDocsDir();
|
|
43991
44476
|
let filename = name.endsWith(".md") ? name : `${name}.md`;
|
|
43992
|
-
let filepath =
|
|
43993
|
-
if (!
|
|
44477
|
+
let filepath = join17(DOCS_DIR, filename);
|
|
44478
|
+
if (!existsSync13(filepath)) {
|
|
43994
44479
|
const baseName = name.includes("/") ? name : titleToFilename(name);
|
|
43995
44480
|
filename = baseName.endsWith(".md") ? baseName : `${baseName}.md`;
|
|
43996
|
-
filepath =
|
|
44481
|
+
filepath = join17(DOCS_DIR, filename);
|
|
43997
44482
|
}
|
|
43998
|
-
if (!
|
|
44483
|
+
if (!existsSync13(filepath)) {
|
|
43999
44484
|
const allFiles = await getAllMdFiles(DOCS_DIR);
|
|
44000
44485
|
const searchName = name.toLowerCase().replace(/\.md$/, "");
|
|
44001
44486
|
const matchingFile = allFiles.find((file3) => {
|
|
@@ -44005,31 +44490,31 @@ var editCommand2 = new Command("edit").description("Edit a documentation file (m
|
|
|
44005
44490
|
});
|
|
44006
44491
|
if (matchingFile) {
|
|
44007
44492
|
filename = matchingFile;
|
|
44008
|
-
filepath =
|
|
44493
|
+
filepath = join17(DOCS_DIR, matchingFile);
|
|
44009
44494
|
}
|
|
44010
44495
|
}
|
|
44011
|
-
if (!
|
|
44496
|
+
if (!existsSync13(filepath)) {
|
|
44012
44497
|
console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
|
|
44013
44498
|
process.exit(1);
|
|
44014
44499
|
}
|
|
44015
|
-
const fileContent = await
|
|
44016
|
-
const { data, content } = (0,
|
|
44500
|
+
const fileContent = await readFile9(filepath, "utf-8");
|
|
44501
|
+
const { data, content } = (0, import_gray_matter5.default)(fileContent);
|
|
44017
44502
|
const metadata = data;
|
|
44018
44503
|
if (options2.title) metadata.title = options2.title;
|
|
44019
|
-
if (options2.description) metadata.description = options2.description;
|
|
44504
|
+
if (options2.description) metadata.description = normalizeRefs(options2.description);
|
|
44020
44505
|
if (options2.tags) metadata.tags = options2.tags.split(",").map((t) => t.trim());
|
|
44021
44506
|
metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
44022
44507
|
let updatedContent = content;
|
|
44023
44508
|
if (options2.content) {
|
|
44024
|
-
updatedContent = options2.content;
|
|
44509
|
+
updatedContent = normalizeRefs(options2.content);
|
|
44025
44510
|
}
|
|
44026
44511
|
if (options2.append) {
|
|
44027
44512
|
updatedContent = `${content.trimEnd()}
|
|
44028
44513
|
|
|
44029
|
-
${options2.append}`;
|
|
44514
|
+
${normalizeRefs(options2.append)}`;
|
|
44030
44515
|
}
|
|
44031
|
-
const newFileContent =
|
|
44032
|
-
await
|
|
44516
|
+
const newFileContent = import_gray_matter5.default.stringify(updatedContent, metadata);
|
|
44517
|
+
await writeFile7(filepath, newFileContent, "utf-8");
|
|
44033
44518
|
await notifyDocUpdate(filename);
|
|
44034
44519
|
if (options2.plain) {
|
|
44035
44520
|
console.log(`Updated: ${filename}`);
|
|
@@ -44045,12 +44530,109 @@ ${options2.append}`;
|
|
|
44045
44530
|
}
|
|
44046
44531
|
}
|
|
44047
44532
|
);
|
|
44048
|
-
var docCommand = new Command("doc").description("Manage documentation").
|
|
44533
|
+
var docCommand = new Command("doc").description("Manage documentation").argument("[name]", "Document name (shorthand for 'doc view <name>')").option("--plain", "Plain text output for AI").action(async (name, options2) => {
|
|
44534
|
+
if (!name) {
|
|
44535
|
+
docCommand.help();
|
|
44536
|
+
return;
|
|
44537
|
+
}
|
|
44538
|
+
try {
|
|
44539
|
+
const resolved = await resolveDocPath(name);
|
|
44540
|
+
if (!resolved) {
|
|
44541
|
+
console.error(source_default.red(`\u2717 Documentation not found: ${name}`));
|
|
44542
|
+
process.exit(1);
|
|
44543
|
+
}
|
|
44544
|
+
const { filepath, filename } = resolved;
|
|
44545
|
+
const fileContent = await readFile9(filepath, "utf-8");
|
|
44546
|
+
const { data, content } = (0, import_gray_matter5.default)(fileContent);
|
|
44547
|
+
const metadata = data;
|
|
44548
|
+
if (options2.plain) {
|
|
44549
|
+
const border = "-".repeat(50);
|
|
44550
|
+
const titleBorder = "=".repeat(50);
|
|
44551
|
+
const projectRoot = process.cwd();
|
|
44552
|
+
console.log(`File: ${projectRoot}/.knowns/docs/${filename}`);
|
|
44553
|
+
console.log();
|
|
44554
|
+
console.log(metadata.title);
|
|
44555
|
+
console.log(titleBorder);
|
|
44556
|
+
console.log();
|
|
44557
|
+
console.log(`Created: ${metadata.createdAt}`);
|
|
44558
|
+
console.log(`Updated: ${metadata.updatedAt}`);
|
|
44559
|
+
if (metadata.tags && metadata.tags.length > 0) {
|
|
44560
|
+
console.log(`Tags: ${metadata.tags.join(", ")}`);
|
|
44561
|
+
}
|
|
44562
|
+
if (metadata.description) {
|
|
44563
|
+
console.log();
|
|
44564
|
+
console.log("Description:");
|
|
44565
|
+
console.log(border);
|
|
44566
|
+
console.log(metadata.description);
|
|
44567
|
+
}
|
|
44568
|
+
console.log();
|
|
44569
|
+
console.log("Content:");
|
|
44570
|
+
console.log(border);
|
|
44571
|
+
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
44572
|
+
let enhancedContent = content.trimEnd();
|
|
44573
|
+
const linksToAdd = [];
|
|
44574
|
+
let match;
|
|
44575
|
+
while ((match = linkPattern.exec(content)) !== null) {
|
|
44576
|
+
const _linkText = match[1];
|
|
44577
|
+
const linkTarget = match[2];
|
|
44578
|
+
const fullMatch = match[0];
|
|
44579
|
+
if (linkTarget.startsWith("http://") || linkTarget.startsWith("https://")) {
|
|
44580
|
+
continue;
|
|
44581
|
+
}
|
|
44582
|
+
if (linkTarget.startsWith("../")) {
|
|
44583
|
+
continue;
|
|
44584
|
+
}
|
|
44585
|
+
let resolvedFilename = linkTarget.replace(/^\.\//, "");
|
|
44586
|
+
if (!resolvedFilename.endsWith(".md")) {
|
|
44587
|
+
resolvedFilename = `${resolvedFilename}.md`;
|
|
44588
|
+
}
|
|
44589
|
+
const resolvedPath = `@.knowns/docs/${resolvedFilename}`;
|
|
44590
|
+
linksToAdd.push({
|
|
44591
|
+
original: fullMatch,
|
|
44592
|
+
resolved: resolvedPath
|
|
44593
|
+
});
|
|
44594
|
+
}
|
|
44595
|
+
for (const { original, resolved: resolved2 } of linksToAdd) {
|
|
44596
|
+
enhancedContent = enhancedContent.replace(original, resolved2);
|
|
44597
|
+
}
|
|
44598
|
+
const knownProjectRoot = findProjectRoot();
|
|
44599
|
+
if (knownProjectRoot) {
|
|
44600
|
+
const fileStore = new FileStore(knownProjectRoot);
|
|
44601
|
+
const allTasks = await fileStore.getAllTasks();
|
|
44602
|
+
const taskMap = buildTaskMap(allTasks);
|
|
44603
|
+
enhancedContent = transformMentionsToRefs(enhancedContent, taskMap);
|
|
44604
|
+
}
|
|
44605
|
+
console.log(enhancedContent);
|
|
44606
|
+
} else {
|
|
44607
|
+
console.log(source_default.bold(`
|
|
44608
|
+
\u{1F4C4} ${metadata.title}
|
|
44609
|
+
`));
|
|
44610
|
+
if (metadata.description) {
|
|
44611
|
+
console.log(source_default.gray(metadata.description));
|
|
44612
|
+
console.log();
|
|
44613
|
+
}
|
|
44614
|
+
if (metadata.tags && metadata.tags.length > 0) {
|
|
44615
|
+
console.log(source_default.gray(`Tags: ${metadata.tags.join(", ")}`));
|
|
44616
|
+
}
|
|
44617
|
+
console.log(source_default.gray(`Updated: ${new Date(metadata.updatedAt).toLocaleString()}`));
|
|
44618
|
+
console.log(source_default.gray("-".repeat(60)));
|
|
44619
|
+
console.log(content);
|
|
44620
|
+
console.log();
|
|
44621
|
+
}
|
|
44622
|
+
} catch (error46) {
|
|
44623
|
+
console.error(source_default.red("Error viewing documentation:"), error46 instanceof Error ? error46.message : String(error46));
|
|
44624
|
+
process.exit(1);
|
|
44625
|
+
}
|
|
44626
|
+
});
|
|
44627
|
+
docCommand.addCommand(createCommand3);
|
|
44628
|
+
docCommand.addCommand(listCommand2);
|
|
44629
|
+
docCommand.addCommand(viewCommand2);
|
|
44630
|
+
docCommand.addCommand(editCommand2);
|
|
44049
44631
|
|
|
44050
44632
|
// src/commands/config.ts
|
|
44051
|
-
import { existsSync as
|
|
44052
|
-
import { mkdir as mkdir8, readFile as
|
|
44053
|
-
import { join as
|
|
44633
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
44634
|
+
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
|
|
44635
|
+
import { join as join18 } from "node:path";
|
|
44054
44636
|
|
|
44055
44637
|
// node_modules/zod/v4/classic/external.js
|
|
44056
44638
|
var external_exports = {};
|
|
@@ -57607,12 +58189,12 @@ function getProjectRoot2() {
|
|
|
57607
58189
|
return projectRoot;
|
|
57608
58190
|
}
|
|
57609
58191
|
async function loadConfig(projectRoot) {
|
|
57610
|
-
const configPath =
|
|
57611
|
-
if (!
|
|
58192
|
+
const configPath = join18(projectRoot, CONFIG_FILE2);
|
|
58193
|
+
if (!existsSync14(configPath)) {
|
|
57612
58194
|
return { ...DEFAULT_CONFIG };
|
|
57613
58195
|
}
|
|
57614
58196
|
try {
|
|
57615
|
-
const content = await
|
|
58197
|
+
const content = await readFile10(configPath, "utf-8");
|
|
57616
58198
|
const data = JSON.parse(content);
|
|
57617
58199
|
const settings = data.settings || {};
|
|
57618
58200
|
const validated = ConfigSchema.parse(settings);
|
|
@@ -57626,22 +58208,22 @@ async function loadConfig(projectRoot) {
|
|
|
57626
58208
|
}
|
|
57627
58209
|
}
|
|
57628
58210
|
async function saveConfig(projectRoot, config2) {
|
|
57629
|
-
const configPath =
|
|
57630
|
-
const knownsDir =
|
|
57631
|
-
if (!
|
|
58211
|
+
const configPath = join18(projectRoot, CONFIG_FILE2);
|
|
58212
|
+
const knownsDir = join18(projectRoot, ".knowns");
|
|
58213
|
+
if (!existsSync14(knownsDir)) {
|
|
57632
58214
|
await mkdir8(knownsDir, { recursive: true });
|
|
57633
58215
|
}
|
|
57634
58216
|
try {
|
|
57635
58217
|
let existingData = {};
|
|
57636
|
-
if (
|
|
57637
|
-
const content = await
|
|
58218
|
+
if (existsSync14(configPath)) {
|
|
58219
|
+
const content = await readFile10(configPath, "utf-8");
|
|
57638
58220
|
existingData = JSON.parse(content);
|
|
57639
58221
|
}
|
|
57640
58222
|
const merged = {
|
|
57641
58223
|
...existingData,
|
|
57642
58224
|
settings: config2
|
|
57643
58225
|
};
|
|
57644
|
-
await
|
|
58226
|
+
await writeFile8(configPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
57645
58227
|
} catch (error46) {
|
|
57646
58228
|
console.error(source_default.red("\u2717 Failed to save config"));
|
|
57647
58229
|
if (error46 instanceof Error) {
|
|
@@ -57785,7 +58367,7 @@ var configCommand = new Command("config").description("Manage configuration sett
|
|
|
57785
58367
|
// package.json
|
|
57786
58368
|
var package_default = {
|
|
57787
58369
|
name: "knowns",
|
|
57788
|
-
version: "0.
|
|
58370
|
+
version: "0.3.0",
|
|
57789
58371
|
description: "CLI tool for dev teams to manage tasks and documentation",
|
|
57790
58372
|
module: "index.ts",
|
|
57791
58373
|
type: "module",
|
|
@@ -57810,13 +58392,13 @@ var package_default = {
|
|
|
57810
58392
|
url: "https://github.com/knowns-dev/knowns/issues"
|
|
57811
58393
|
},
|
|
57812
58394
|
scripts: {
|
|
57813
|
-
dev: "
|
|
58395
|
+
dev: "npx tsx src/index.ts browser --no-open",
|
|
57814
58396
|
watch: "nodemon",
|
|
57815
58397
|
build: "npm run build:cli && npm run build:ui",
|
|
57816
58398
|
"build:cli": "node scripts/build-cli.js",
|
|
57817
58399
|
"build:ui": "vite build",
|
|
57818
|
-
start: "
|
|
57819
|
-
mcp: "
|
|
58400
|
+
start: "npx tsx src/index.ts",
|
|
58401
|
+
mcp: "npx tsx src/mcp/server.ts",
|
|
57820
58402
|
test: "vitest run",
|
|
57821
58403
|
lint: "biome check .",
|
|
57822
58404
|
"lint:fix": "biome check --write .",
|
|
@@ -57824,15 +58406,25 @@ var package_default = {
|
|
|
57824
58406
|
prepare: "husky"
|
|
57825
58407
|
},
|
|
57826
58408
|
dependencies: {
|
|
58409
|
+
"@dnd-kit/core": "^6.3.1",
|
|
58410
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
58411
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
57827
58412
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
58413
|
+
"@radix-ui/react-checkbox": "^1.3.3",
|
|
57828
58414
|
"@radix-ui/react-collapsible": "^1.1.12",
|
|
57829
58415
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
58416
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
58417
|
+
"@radix-ui/react-label": "^2.1.8",
|
|
57830
58418
|
"@radix-ui/react-navigation-menu": "^1.2.14",
|
|
57831
58419
|
"@radix-ui/react-popover": "^1.1.15",
|
|
58420
|
+
"@radix-ui/react-progress": "^1.1.8",
|
|
57832
58421
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
58422
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
57833
58423
|
"@radix-ui/react-separator": "^1.1.8",
|
|
57834
58424
|
"@radix-ui/react-slot": "^1.2.4",
|
|
57835
58425
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
58426
|
+
"@tanstack/react-table": "^8.21.3",
|
|
58427
|
+
"@types/cors": "^2.8.19",
|
|
57836
58428
|
"@uiw/react-md-editor": "^4.0.11",
|
|
57837
58429
|
chalk: "^5.3.0",
|
|
57838
58430
|
"class-variance-authority": "^0.7.1",
|
|
@@ -57841,6 +58433,7 @@ var package_default = {
|
|
|
57841
58433
|
commander: "^12.1.0",
|
|
57842
58434
|
cors: "^2.8.5",
|
|
57843
58435
|
express: "^5.2.1",
|
|
58436
|
+
"framer-motion": "^12.23.26",
|
|
57844
58437
|
"gray-matter": "^4.0.3",
|
|
57845
58438
|
"lucide-react": "^0.562.0",
|
|
57846
58439
|
marked: "^17.0.1",
|
|
@@ -57848,7 +58441,9 @@ var package_default = {
|
|
|
57848
58441
|
react: "^19.2.3",
|
|
57849
58442
|
"react-diff-viewer": "^3.1.1",
|
|
57850
58443
|
"react-dom": "^19.2.3",
|
|
58444
|
+
sonner: "^2.0.7",
|
|
57851
58445
|
"tailwind-merge": "^3.4.0",
|
|
58446
|
+
"tunnel-rat": "^0.1.2",
|
|
57852
58447
|
turndown: "^7.2.2",
|
|
57853
58448
|
"unist-util-visit": "^5.0.0",
|
|
57854
58449
|
ws: "^8.18.3",
|
|
@@ -57864,6 +58459,7 @@ var package_default = {
|
|
|
57864
58459
|
"@types/react-dom": "^19.2.3",
|
|
57865
58460
|
"@types/ws": "^8.18.1",
|
|
57866
58461
|
"@vitejs/plugin-react": "^5.1.2",
|
|
58462
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
57867
58463
|
autoprefixer: "^10.4.23",
|
|
57868
58464
|
concurrently: "^9.2.1",
|
|
57869
58465
|
esbuild: "^0.27.2",
|