@youtyan/code-viewer 0.6.3 → 0.6.4
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/README.md +14 -12
- package/dist/code-viewer.js +2355 -201
- package/package.json +1 -1
- package/skills/code-viewer-journal/SKILL.md +131 -0
- package/web/app.js +2543 -262
- package/web/index.html +1 -0
- package/web/style.css +1653 -169
package/dist/code-viewer.js
CHANGED
|
@@ -823,7 +823,7 @@ function isCommandNotFoundMessage(command, message) {
|
|
|
823
823
|
}
|
|
824
824
|
var EXTERNAL_COMMAND_NAMES, commandNameSet, activeOverrides;
|
|
825
825
|
var init_command_resolver = __esm(() => {
|
|
826
|
-
EXTERNAL_COMMAND_NAMES = ["git", "rg", "docker"];
|
|
826
|
+
EXTERNAL_COMMAND_NAMES = ["git", "rg", "docker", "gh"];
|
|
827
827
|
commandNameSet = new Set(EXTERNAL_COMMAND_NAMES);
|
|
828
828
|
activeOverrides = new Map;
|
|
829
829
|
});
|
|
@@ -2231,6 +2231,15 @@ function takeValue(argv, index, flag) {
|
|
|
2231
2231
|
function shellSingleQuote(value) {
|
|
2232
2232
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
2233
2233
|
}
|
|
2234
|
+
async function readStdin() {
|
|
2235
|
+
if (process.stdin.isTTY)
|
|
2236
|
+
return "";
|
|
2237
|
+
const chunks = [];
|
|
2238
|
+
for await (const chunk of process.stdin) {
|
|
2239
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2240
|
+
}
|
|
2241
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
2242
|
+
}
|
|
2234
2243
|
function isUnsafeText(value) {
|
|
2235
2244
|
if (value.includes("\x00"))
|
|
2236
2245
|
return true;
|
|
@@ -2704,15 +2713,6 @@ function parseAnnotateArgs(argv) {
|
|
|
2704
2713
|
}
|
|
2705
2714
|
return { ok: false, error: `unknown annotate command: ${subcommand}` };
|
|
2706
2715
|
}
|
|
2707
|
-
async function readStdin() {
|
|
2708
|
-
if (process.stdin.isTTY)
|
|
2709
|
-
return "";
|
|
2710
|
-
const chunks = [];
|
|
2711
|
-
for await (const chunk of process.stdin) {
|
|
2712
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2713
|
-
}
|
|
2714
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
2715
|
-
}
|
|
2716
2716
|
async function annotateRequest(serverUrl, method, action, body) {
|
|
2717
2717
|
return requestJson(serverUrl, "/_annotations", method, body, action);
|
|
2718
2718
|
}
|
|
@@ -3016,8 +3016,8 @@ location and renders your explanation directly under the annotated lines.
|
|
|
3016
3016
|
- The body is Markdown. Code spans, fenced blocks, and links work. Long
|
|
3017
3017
|
bodies: use --body-file <path> or pipe via stdin instead of --body.
|
|
3018
3018
|
- Give every annotation a short --title; it becomes the inline heading.
|
|
3019
|
-
- Annotating unchanged code is fine: the
|
|
3020
|
-
|
|
3019
|
+
- Annotating unchanged code is fine: when the selected lines are not an
|
|
3020
|
+
after-side diff change, the viewer falls back to the full source view.
|
|
3021
3021
|
|
|
3022
3022
|
## Sessions
|
|
3023
3023
|
|
|
@@ -3080,7 +3080,7 @@ bunch of probing commands or open the \uD83E\uDE7A browser panel.
|
|
|
3080
3080
|
## When to use
|
|
3081
3081
|
|
|
3082
3082
|
- A previous command failed; you want to confirm Node / Bun / SQLite /
|
|
3083
|
-
Docker / git state before suggesting a fix.
|
|
3083
|
+
Docker / git / gh state before suggesting a fix.
|
|
3084
3084
|
- The human asks "is my environment OK", "why does install fail", or
|
|
3085
3085
|
similar; \`code-viewer doctor\` is the single source of truth.
|
|
3086
3086
|
- Pre-flight before a destructive step (DB snapshot reset, npx upgrade)
|
|
@@ -3111,7 +3111,7 @@ Stable JSON contract (see core/doctor-types.ts):
|
|
|
3111
3111
|
"generation": number, // monotonic, restart resets to 1
|
|
3112
3112
|
"worstStatus": "ok"|"warn"|"error",
|
|
3113
3113
|
"groups": [
|
|
3114
|
-
{ "id": "runtime"|"package"|"sqlite"|"snapshot"|"git"
|
|
3114
|
+
{ "id": "runtime"|"package"|"sqlite"|"snapshot"|"git"|"github"
|
|
3115
3115
|
|"discovery"|"datastore"|"docker"|"server",
|
|
3116
3116
|
"title": string,
|
|
3117
3117
|
"rows": [
|
|
@@ -3142,6 +3142,9 @@ The exit code is 1 iff \`worstStatus === "error"\` — never on \`warn\`.
|
|
|
3142
3142
|
- \`hint\` is the human-readable fix; quote it verbatim when reporting back.
|
|
3143
3143
|
- A \`runtime.node\` row failing means Node < 20 — almost everything else
|
|
3144
3144
|
is downstream of that. Fix it first.
|
|
3145
|
+
- A \`github.gh\` row warning means GitHub Issue listing/linking cannot use
|
|
3146
|
+
the GitHub CLI yet. Install \`gh\` or pass \`--bin gh=/absolute/path\`.
|
|
3147
|
+
Doctor intentionally does not inspect GitHub auth metadata.
|
|
3145
3148
|
- A \`sqlite.*\` row failing usually points at npx cache; the hint shows
|
|
3146
3149
|
the rm -rf ~/.npm/_npx workaround.
|
|
3147
3150
|
- Docker rows are advisory — \`code-viewer\` works without Docker; warnings
|
|
@@ -4045,123 +4048,1291 @@ Diff Viewer views.
|
|
|
4045
4048
|
- Git must be available on PATH, or supplied with --bin git=/absolute/path
|
|
4046
4049
|
/ CODE_VIEWER_BIN_GIT.
|
|
4047
4050
|
|
|
4048
|
-
## How to call
|
|
4051
|
+
## How to call
|
|
4052
|
+
|
|
4053
|
+
code-viewer file blame --path src/sample.ts --json
|
|
4054
|
+
code-viewer file blame --path src/sample.ts --base HEAD --json
|
|
4055
|
+
code-viewer file history --path src/sample.ts --limit 10 --json
|
|
4056
|
+
code-viewer file history --path src/sample.ts --query "author:tester" --json
|
|
4057
|
+
code-viewer file show --path src/sample.ts --json
|
|
4058
|
+
code-viewer file show --path src/sample.ts --start 100 --end 150 --json
|
|
4059
|
+
code-viewer file show --path src/sample.ts --ref main --json
|
|
4060
|
+
code-viewer file diff --path src/sample.ts --json
|
|
4061
|
+
code-viewer file diff --path src/sample.ts --from main --to HEAD --json
|
|
4062
|
+
code-viewer file diff --path src/sample.ts --full --json
|
|
4063
|
+
code-viewer file diff --path new_sample.ts --untracked --json
|
|
4064
|
+
code-viewer file diff --path renamed.ts --old-path original.ts --from HEAD~1 --to HEAD --json
|
|
4065
|
+
|
|
4066
|
+
## Output contract
|
|
4067
|
+
|
|
4068
|
+
--json shapes (mirroring git.ts DTOs verbatim where possible):
|
|
4069
|
+
|
|
4070
|
+
blame:
|
|
4071
|
+
{
|
|
4072
|
+
"path": string,
|
|
4073
|
+
"ref": string, // the literal ref the CLI resolved to
|
|
4074
|
+
"base": "worktree"|"HEAD",
|
|
4075
|
+
"result": GitBlameResult // { lines, commits, isUntracked?, isSynthetic?, error? }
|
|
4076
|
+
}
|
|
4077
|
+
history:
|
|
4078
|
+
{
|
|
4079
|
+
"path": string,
|
|
4080
|
+
"ref": string,
|
|
4081
|
+
"limit": number,
|
|
4082
|
+
"skip": number,
|
|
4083
|
+
"query"?: string,
|
|
4084
|
+
"result": { commits: GitHistoryCommit[], hasMore: boolean, error?: string }
|
|
4085
|
+
}
|
|
4086
|
+
show:
|
|
4087
|
+
{
|
|
4088
|
+
"path": string,
|
|
4089
|
+
"ref": string,
|
|
4090
|
+
"start"?: number,
|
|
4091
|
+
"end"?: number,
|
|
4092
|
+
"totalLines": number, // total lines in the file at <ref>
|
|
4093
|
+
"complete": boolean, // true when the returned text covers the whole file
|
|
4094
|
+
"text": string
|
|
4095
|
+
}
|
|
4096
|
+
diff:
|
|
4097
|
+
{
|
|
4098
|
+
"path": string,
|
|
4099
|
+
"old_path"?: string,
|
|
4100
|
+
"from": string, // "/dev/null" when untracked is true
|
|
4101
|
+
"to": string,
|
|
4102
|
+
"untracked": boolean,
|
|
4103
|
+
"ignore_ws": boolean,
|
|
4104
|
+
"ignore_blank": boolean,
|
|
4105
|
+
"mode": "preview"|"full",
|
|
4106
|
+
"max_hunks": number|null, // null in full mode
|
|
4107
|
+
"max_lines": number|null, // null in full mode
|
|
4108
|
+
"diff": string, // unified diff text
|
|
4109
|
+
"hunk_count": number, // total hunks before truncation
|
|
4110
|
+
"rendered_hunk_count": number, // hunks actually included in diff
|
|
4111
|
+
"line_count": number, // total newlines in the returned diff
|
|
4112
|
+
"truncated": boolean, // true when preview dropped hunks/lines
|
|
4113
|
+
"binary": boolean, // git emitted "Binary files ..."
|
|
4114
|
+
"error"?: string // git stderr (also exits 1)
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
Default (non --json) output:
|
|
4118
|
+
|
|
4119
|
+
blame: one line per source line → <line><TAB><shortSha or "worktree"><TAB><summary>
|
|
4120
|
+
uncommitted edits show as "worktree" and "<uncommitted>".
|
|
4121
|
+
history: one line per commit → <shortSha><TAB><whenISO><TAB><author><TAB><subject>
|
|
4122
|
+
0 commits prints "no history" to stderr, exit 0.
|
|
4123
|
+
show: the file (or sliced lines) as text. 0-byte files succeed (exit 0).
|
|
4124
|
+
diff: unified diff text (the same bytes the JSON "diff" field carries).
|
|
4125
|
+
Empty (or worktree==worktree) returns nothing on stdout, exit 0.
|
|
4126
|
+
|
|
4127
|
+
## Tips
|
|
4128
|
+
|
|
4129
|
+
- Prefer --json. blame DTO carries author / authorMail / authorTime per
|
|
4130
|
+
commit; the plain-text format only shows summary.
|
|
4131
|
+
- For huge files, slice with --start/--end before piping to a model
|
|
4132
|
+
context — "totalLines" / "complete" tell you what was dropped.
|
|
4133
|
+
- file show reads the worktree by default. Pass --ref HEAD / --ref <branch>
|
|
4134
|
+
when you need a committed snapshot.
|
|
4135
|
+
- A non-fatal git failure (e.g. unknown ref, unsafe path) returns the
|
|
4136
|
+
error string inside the JSON "result.error" / "error" field AND exits 1.
|
|
4137
|
+
- file diff defaults to preview mode (${FILE_DIFF_DEFAULT_MAX_HUNKS} hunks /
|
|
4138
|
+
${FILE_DIFF_DEFAULT_MAX_LINES} lines). Use --full only when you need every
|
|
4139
|
+
hunk — preview matches the browser's initial paint and is almost always
|
|
4140
|
+
enough context for an LLM. In full mode, max_hunks / max_lines are null in
|
|
4141
|
+
JSON because no preview cap is applied. Worktree == Worktree range returns
|
|
4142
|
+
"" instantly.
|
|
4143
|
+
- Use "code-viewer search code" to discover the path first, then drill
|
|
4144
|
+
in with blame / history / show / diff.
|
|
4145
|
+
`;
|
|
4146
|
+
VALUE_FLAGS = new Set([
|
|
4147
|
+
"--path",
|
|
4148
|
+
"--ref",
|
|
4149
|
+
"--base",
|
|
4150
|
+
"--limit",
|
|
4151
|
+
"--skip",
|
|
4152
|
+
"--query",
|
|
4153
|
+
"--start",
|
|
4154
|
+
"--end",
|
|
4155
|
+
"--from",
|
|
4156
|
+
"--to",
|
|
4157
|
+
"--old-path",
|
|
4158
|
+
"--max-hunks",
|
|
4159
|
+
"--max-lines"
|
|
4160
|
+
]);
|
|
4161
|
+
BOOL_FLAGS = new Set([
|
|
4162
|
+
"--json",
|
|
4163
|
+
"--untracked",
|
|
4164
|
+
"--ignore-ws",
|
|
4165
|
+
"--ignore-blank",
|
|
4166
|
+
"--full"
|
|
4167
|
+
]);
|
|
4168
|
+
});
|
|
4169
|
+
|
|
4170
|
+
// web-src/core/journal.ts
|
|
4171
|
+
function isJournalTaskStatus(value) {
|
|
4172
|
+
return JOURNAL_TASK_STATUSES.includes(value);
|
|
4173
|
+
}
|
|
4174
|
+
function isJournalTaskPriority(value) {
|
|
4175
|
+
return JOURNAL_TASK_PRIORITIES.includes(value);
|
|
4176
|
+
}
|
|
4177
|
+
function isIsoDate(value) {
|
|
4178
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
4179
|
+
return false;
|
|
4180
|
+
const date = new Date(`${value}T00:00:00.000Z`);
|
|
4181
|
+
return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
|
|
4182
|
+
}
|
|
4183
|
+
function todayIsoDate(now = new Date) {
|
|
4184
|
+
const year = now.getFullYear();
|
|
4185
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
4186
|
+
const date = String(now.getDate()).padStart(2, "0");
|
|
4187
|
+
return `${year}-${month}-${date}`;
|
|
4188
|
+
}
|
|
4189
|
+
function taskClaimActive(task, now = Date.now()) {
|
|
4190
|
+
if (!task.claim)
|
|
4191
|
+
return false;
|
|
4192
|
+
const expires = Date.parse(task.claim.lease_expires_at);
|
|
4193
|
+
return Number.isFinite(expires) && expires > now;
|
|
4194
|
+
}
|
|
4195
|
+
function normalizeJournalLabel(value) {
|
|
4196
|
+
if (typeof value !== "string")
|
|
4197
|
+
return null;
|
|
4198
|
+
const label = value.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^\p{L}\p{N}._:-]+/gu, "").slice(0, JOURNAL_LABEL_MAX_CHARS).replace(/^[-_.:]+|[-_.:]+$/g, "");
|
|
4199
|
+
return label || null;
|
|
4200
|
+
}
|
|
4201
|
+
function normalizeJournalLabels(raw) {
|
|
4202
|
+
if (!Array.isArray(raw))
|
|
4203
|
+
return [];
|
|
4204
|
+
const labels = [];
|
|
4205
|
+
const seen = new Set;
|
|
4206
|
+
for (const item of raw) {
|
|
4207
|
+
const label = normalizeJournalLabel(item);
|
|
4208
|
+
if (!label || seen.has(label))
|
|
4209
|
+
continue;
|
|
4210
|
+
seen.add(label);
|
|
4211
|
+
labels.push(label);
|
|
4212
|
+
if (labels.length >= JOURNAL_MAX_LABELS)
|
|
4213
|
+
break;
|
|
4214
|
+
}
|
|
4215
|
+
return labels;
|
|
4216
|
+
}
|
|
4217
|
+
function journalIssueLabel(issueNumber) {
|
|
4218
|
+
return `issue-${issueNumber}`;
|
|
4219
|
+
}
|
|
4220
|
+
function journalIssueRepoLabel(repo) {
|
|
4221
|
+
if (typeof repo !== "string")
|
|
4222
|
+
return null;
|
|
4223
|
+
return normalizeJournalLabel(`repo-${repo.replace(/[\\/]+/g, "-")}`);
|
|
4224
|
+
}
|
|
4225
|
+
function collectJournalLabels(journal, tasks) {
|
|
4226
|
+
const labels = new Set;
|
|
4227
|
+
for (const entry of journal.entries) {
|
|
4228
|
+
for (const label of entry.labels)
|
|
4229
|
+
labels.add(label);
|
|
4230
|
+
}
|
|
4231
|
+
for (const task of tasks.tasks) {
|
|
4232
|
+
for (const label of task.labels)
|
|
4233
|
+
labels.add(label);
|
|
4234
|
+
}
|
|
4235
|
+
return [...labels].sort((a, b) => a.localeCompare(b));
|
|
4236
|
+
}
|
|
4237
|
+
function filterJournalTasks(state, filter = {}) {
|
|
4238
|
+
const statuses = Array.isArray(filter.status) ? filter.status : filter.status ? [filter.status] : undefined;
|
|
4239
|
+
const labels = filter.labels?.filter(Boolean) || [];
|
|
4240
|
+
return state.tasks.filter((task) => {
|
|
4241
|
+
if (statuses && !statuses.includes(task.status))
|
|
4242
|
+
return false;
|
|
4243
|
+
if (labels.length && !labels.every((label) => task.labels.includes(label))) {
|
|
4244
|
+
return false;
|
|
4245
|
+
}
|
|
4246
|
+
if (!filter.includeClaimed && taskClaimActive(task, filter.now))
|
|
4247
|
+
return false;
|
|
4248
|
+
return true;
|
|
4249
|
+
});
|
|
4250
|
+
}
|
|
4251
|
+
function selectNextJournalTasks(state, filter = {}, limit = 1) {
|
|
4252
|
+
const status = filter.status || "todo";
|
|
4253
|
+
const indexed = filterJournalTasks(state, { ...filter, status }).map((task, index) => ({ task, index }));
|
|
4254
|
+
indexed.sort((a, b) => {
|
|
4255
|
+
const priority = PRIORITY_SCORE[a.task.priority] - PRIORITY_SCORE[b.task.priority];
|
|
4256
|
+
if (priority !== 0)
|
|
4257
|
+
return priority;
|
|
4258
|
+
if (a.index !== b.index)
|
|
4259
|
+
return a.index - b.index;
|
|
4260
|
+
return a.task.created_at.localeCompare(b.task.created_at);
|
|
4261
|
+
});
|
|
4262
|
+
return indexed.slice(0, Math.max(0, limit)).map((item) => item.task);
|
|
4263
|
+
}
|
|
4264
|
+
var JOURNAL_TASK_STATUSES, JOURNAL_TASK_PRIORITIES, JOURNAL_LABEL_MAX_CHARS = 48, JOURNAL_MAX_LABELS = 24, PRIORITY_SCORE;
|
|
4265
|
+
var init_journal = __esm(() => {
|
|
4266
|
+
JOURNAL_TASK_STATUSES = [
|
|
4267
|
+
"draft",
|
|
4268
|
+
"todo",
|
|
4269
|
+
"doing",
|
|
4270
|
+
"blocked",
|
|
4271
|
+
"done"
|
|
4272
|
+
];
|
|
4273
|
+
JOURNAL_TASK_PRIORITIES = ["p0", "p1", "p2", "p3"];
|
|
4274
|
+
PRIORITY_SCORE = {
|
|
4275
|
+
p0: 0,
|
|
4276
|
+
p1: 1,
|
|
4277
|
+
p2: 2,
|
|
4278
|
+
p3: 3
|
|
4279
|
+
};
|
|
4280
|
+
});
|
|
4281
|
+
|
|
4282
|
+
// web-src/server/github-issues.ts
|
|
4283
|
+
function normalizeGithubIssueListState(value) {
|
|
4284
|
+
return value === "closed" || value === "all" ? value : "open";
|
|
4285
|
+
}
|
|
4286
|
+
function normalizeGithubIssueListLimit(value) {
|
|
4287
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
4288
|
+
return 30;
|
|
4289
|
+
}
|
|
4290
|
+
return Math.min(Math.floor(value), 100);
|
|
4291
|
+
}
|
|
4292
|
+
function singleLineGithubOption(value) {
|
|
4293
|
+
if (!value)
|
|
4294
|
+
return;
|
|
4295
|
+
const trimmed = value.trim();
|
|
4296
|
+
if (!trimmed || trimmed.includes("\x00") || /[\r\n]/.test(trimmed)) {
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
4299
|
+
return trimmed.slice(0, 200);
|
|
4300
|
+
}
|
|
4301
|
+
function githubSearchHasStateQualifier(value) {
|
|
4302
|
+
const search = singleLineGithubOption(value);
|
|
4303
|
+
return !!search && /(^|\s)(is|state):(open|closed|all)(?=\s|$)/i.test(search);
|
|
4304
|
+
}
|
|
4305
|
+
function extractGithubIssueLabels(raw) {
|
|
4306
|
+
if (!Array.isArray(raw))
|
|
4307
|
+
return [];
|
|
4308
|
+
const labels = [];
|
|
4309
|
+
for (const item of raw) {
|
|
4310
|
+
if (typeof item === "string") {
|
|
4311
|
+
const label = singleLineGithubOption(item);
|
|
4312
|
+
if (label)
|
|
4313
|
+
labels.push(label);
|
|
4314
|
+
} else if (item && typeof item === "object" && "name" in item) {
|
|
4315
|
+
const label = singleLineGithubOption(item.name);
|
|
4316
|
+
if (label)
|
|
4317
|
+
labels.push(label);
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
return labels.slice(0, 12);
|
|
4321
|
+
}
|
|
4322
|
+
function normalizeGithubIssueListItem(raw) {
|
|
4323
|
+
if (!raw || typeof raw !== "object")
|
|
4324
|
+
return null;
|
|
4325
|
+
const issue = raw;
|
|
4326
|
+
const number = issue.number;
|
|
4327
|
+
const title = issue.title;
|
|
4328
|
+
if (typeof number !== "number" || !Number.isInteger(number) || number <= 0 || typeof title !== "string" || !title.trim()) {
|
|
4329
|
+
return null;
|
|
4330
|
+
}
|
|
4331
|
+
const url = singleLineGithubOption(issue.url);
|
|
4332
|
+
const state = typeof issue.state === "string" && issue.state.trim() ? issue.state.trim().toLowerCase() : "open";
|
|
4333
|
+
return {
|
|
4334
|
+
number,
|
|
4335
|
+
title: title.trim().slice(0, 200),
|
|
4336
|
+
state,
|
|
4337
|
+
...url ? { url } : {},
|
|
4338
|
+
labels: extractGithubIssueLabels(issue.labels)
|
|
4339
|
+
};
|
|
4340
|
+
}
|
|
4341
|
+
function parseGithubIssueListOutput(stdout) {
|
|
4342
|
+
const parsed = JSON.parse(stdout);
|
|
4343
|
+
if (!Array.isArray(parsed))
|
|
4344
|
+
return [];
|
|
4345
|
+
return parsed.map(normalizeGithubIssueListItem).filter((issue) => issue !== null);
|
|
4346
|
+
}
|
|
4347
|
+
function parseGithubIssueViewOutput(stdout) {
|
|
4348
|
+
const issue = normalizeGithubIssueListItem(JSON.parse(stdout));
|
|
4349
|
+
if (!issue)
|
|
4350
|
+
throw new GithubIssueListError("failed to parse gh issue output");
|
|
4351
|
+
return issue;
|
|
4352
|
+
}
|
|
4353
|
+
function buildGithubIssueListArgs(options) {
|
|
4354
|
+
const search = singleLineGithubOption(options.search);
|
|
4355
|
+
const args = [
|
|
4356
|
+
commandForExternal("gh"),
|
|
4357
|
+
"issue",
|
|
4358
|
+
"list",
|
|
4359
|
+
"--json",
|
|
4360
|
+
"number,title,state,labels,url",
|
|
4361
|
+
"--limit",
|
|
4362
|
+
String(normalizeGithubIssueListLimit(options.limit)),
|
|
4363
|
+
"--state",
|
|
4364
|
+
search && githubSearchHasStateQualifier(search) ? "all" : normalizeGithubIssueListState(options.state)
|
|
4365
|
+
];
|
|
4366
|
+
const repo = singleLineGithubOption(options.repo);
|
|
4367
|
+
if (repo)
|
|
4368
|
+
args.push("--repo", repo);
|
|
4369
|
+
if (search)
|
|
4370
|
+
args.push("--search", search);
|
|
4371
|
+
const seenLabels = new Set;
|
|
4372
|
+
for (const rawLabel of options.labels || []) {
|
|
4373
|
+
const label = singleLineGithubOption(rawLabel);
|
|
4374
|
+
if (!label || seenLabels.has(label))
|
|
4375
|
+
continue;
|
|
4376
|
+
seenLabels.add(label);
|
|
4377
|
+
args.push("--label", label);
|
|
4378
|
+
if (seenLabels.size >= GITHUB_ISSUE_LABEL_FILTER_LIMIT)
|
|
4379
|
+
break;
|
|
4380
|
+
}
|
|
4381
|
+
return args;
|
|
4382
|
+
}
|
|
4383
|
+
function buildGithubIssueViewArgs(options) {
|
|
4384
|
+
const args = [
|
|
4385
|
+
commandForExternal("gh"),
|
|
4386
|
+
"issue",
|
|
4387
|
+
"view",
|
|
4388
|
+
String(options.number),
|
|
4389
|
+
"--json",
|
|
4390
|
+
"number,title,state,labels,url"
|
|
4391
|
+
];
|
|
4392
|
+
const repo = singleLineGithubOption(options.repo);
|
|
4393
|
+
if (repo)
|
|
4394
|
+
args.push("--repo", repo);
|
|
4395
|
+
return args;
|
|
4396
|
+
}
|
|
4397
|
+
function readGithubIssueList(options) {
|
|
4398
|
+
const proc = runSync(buildGithubIssueListArgs(options), options.cwd, {
|
|
4399
|
+
timeout: 30000
|
|
4400
|
+
});
|
|
4401
|
+
if (proc.code !== 0) {
|
|
4402
|
+
const detail = isCommandNotFoundResult("gh", proc) ? commandNotFoundDetail("gh") : proc.stderr.trim() || `gh issue list exited with code ${proc.code}`;
|
|
4403
|
+
throw new GithubIssueListError(detail);
|
|
4404
|
+
}
|
|
4405
|
+
try {
|
|
4406
|
+
return parseGithubIssueListOutput(proc.stdout);
|
|
4407
|
+
} catch {
|
|
4408
|
+
throw new GithubIssueListError("failed to parse gh issue list output");
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
function readGithubIssue(options) {
|
|
4412
|
+
const proc = runSync(buildGithubIssueViewArgs(options), options.cwd, {
|
|
4413
|
+
timeout: 30000
|
|
4414
|
+
});
|
|
4415
|
+
if (proc.code !== 0) {
|
|
4416
|
+
const detail = isCommandNotFoundResult("gh", proc) ? commandNotFoundDetail("gh") : proc.stderr.trim() || `gh issue view exited with code ${proc.code}`;
|
|
4417
|
+
throw new GithubIssueListError(detail);
|
|
4418
|
+
}
|
|
4419
|
+
try {
|
|
4420
|
+
return parseGithubIssueViewOutput(proc.stdout);
|
|
4421
|
+
} catch (error) {
|
|
4422
|
+
if (error instanceof GithubIssueListError)
|
|
4423
|
+
throw error;
|
|
4424
|
+
throw new GithubIssueListError("failed to parse gh issue output");
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
var GITHUB_ISSUE_LABEL_FILTER_LIMIT = 24, GithubIssueListError;
|
|
4428
|
+
var init_github_issues = __esm(() => {
|
|
4429
|
+
init_command_resolver();
|
|
4430
|
+
init_runtime();
|
|
4431
|
+
GithubIssueListError = class GithubIssueListError extends Error {
|
|
4432
|
+
status;
|
|
4433
|
+
constructor(message, status = 502) {
|
|
4434
|
+
super(message);
|
|
4435
|
+
this.status = status;
|
|
4436
|
+
}
|
|
4437
|
+
};
|
|
4438
|
+
});
|
|
4439
|
+
|
|
4440
|
+
// web-src/server/journal-cli.ts
|
|
4441
|
+
var exports_journal_cli = {};
|
|
4442
|
+
__export(exports_journal_cli, {
|
|
4443
|
+
runJournalCli: () => runJournalCli,
|
|
4444
|
+
parseJournalArgs: () => parseJournalArgs,
|
|
4445
|
+
JOURNAL_HELP: () => JOURNAL_HELP,
|
|
4446
|
+
JOURNAL_AGENT_HELP: () => JOURNAL_AGENT_HELP
|
|
4447
|
+
});
|
|
4448
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
4449
|
+
function parsePositiveInteger(value, flag) {
|
|
4450
|
+
if (value === undefined)
|
|
4451
|
+
return;
|
|
4452
|
+
const n = Number(value);
|
|
4453
|
+
if (!Number.isInteger(n) || n < 1)
|
|
4454
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
4455
|
+
return n;
|
|
4456
|
+
}
|
|
4457
|
+
function parseDateValue(value, flag) {
|
|
4458
|
+
if (value === undefined)
|
|
4459
|
+
return;
|
|
4460
|
+
if (value === "today")
|
|
4461
|
+
return todayIsoDate();
|
|
4462
|
+
if (value === "none")
|
|
4463
|
+
return "";
|
|
4464
|
+
if (!isIsoDate(value))
|
|
4465
|
+
throw new Error(`${flag} must be YYYY-MM-DD or today`);
|
|
4466
|
+
return value;
|
|
4467
|
+
}
|
|
4468
|
+
function parseStatus(value) {
|
|
4469
|
+
if (value === undefined)
|
|
4470
|
+
return;
|
|
4471
|
+
if (isJournalTaskStatus(value))
|
|
4472
|
+
return value;
|
|
4473
|
+
throw new Error("--status must be draft, todo, doing, blocked, or done");
|
|
4474
|
+
}
|
|
4475
|
+
function parsePriority(value) {
|
|
4476
|
+
if (value === undefined)
|
|
4477
|
+
return;
|
|
4478
|
+
if (isJournalTaskPriority(value))
|
|
4479
|
+
return value;
|
|
4480
|
+
throw new Error("--priority must be p0, p1, p2, or p3");
|
|
4481
|
+
}
|
|
4482
|
+
function parseGithubIssueState(value) {
|
|
4483
|
+
if (value === undefined)
|
|
4484
|
+
return "open";
|
|
4485
|
+
if (value === "open" || value === "closed" || value === "all")
|
|
4486
|
+
return value;
|
|
4487
|
+
throw new Error("--state must be open, closed, or all");
|
|
4488
|
+
}
|
|
4489
|
+
function parseGithubOption(value, flag) {
|
|
4490
|
+
if (value === undefined)
|
|
4491
|
+
return;
|
|
4492
|
+
const normalized = singleLineGithubOption(value);
|
|
4493
|
+
if (!normalized)
|
|
4494
|
+
throw new Error(`${flag} must be a non-empty single line`);
|
|
4495
|
+
return normalized;
|
|
4496
|
+
}
|
|
4497
|
+
function parseJournalArgs(argv) {
|
|
4498
|
+
const rest = [];
|
|
4499
|
+
let cwd;
|
|
4500
|
+
let server;
|
|
4501
|
+
const options = new Map;
|
|
4502
|
+
const multiOptions = new Map;
|
|
4503
|
+
const flags = new Set;
|
|
4504
|
+
const commandOverrides = [];
|
|
4505
|
+
const valueFlags = new Set([
|
|
4506
|
+
"--date",
|
|
4507
|
+
"--title",
|
|
4508
|
+
"--body",
|
|
4509
|
+
"--body-file",
|
|
4510
|
+
"--note",
|
|
4511
|
+
"--note-file",
|
|
4512
|
+
"--label",
|
|
4513
|
+
"--status",
|
|
4514
|
+
"--priority",
|
|
4515
|
+
"--due",
|
|
4516
|
+
"--source-date",
|
|
4517
|
+
"--before",
|
|
4518
|
+
"--after",
|
|
4519
|
+
"--position",
|
|
4520
|
+
"--by",
|
|
4521
|
+
"--lease-minutes",
|
|
4522
|
+
"--wip-limit",
|
|
4523
|
+
"--limit",
|
|
4524
|
+
"--repo",
|
|
4525
|
+
"--gh-label",
|
|
4526
|
+
"--search",
|
|
4527
|
+
"--state"
|
|
4528
|
+
]);
|
|
4529
|
+
try {
|
|
4530
|
+
for (let i = 0;i < argv.length; i++) {
|
|
4531
|
+
const arg = argv[i];
|
|
4532
|
+
if (arg === "--help" || arg === "-h")
|
|
4533
|
+
return { ok: true, args: { command: { kind: "help" }, dryRun: false } };
|
|
4534
|
+
if (arg === "--cwd" || arg === "--server") {
|
|
4535
|
+
const taken = takeValue(argv, i, arg);
|
|
4536
|
+
if ("error" in taken)
|
|
4537
|
+
return { ok: false, error: taken.error };
|
|
4538
|
+
if (arg === "--cwd")
|
|
4539
|
+
cwd = taken.value;
|
|
4540
|
+
else
|
|
4541
|
+
server = taken.value;
|
|
4542
|
+
i = taken.next;
|
|
4543
|
+
} else if (arg === "--bin") {
|
|
4544
|
+
const taken = takeValue(argv, i, arg);
|
|
4545
|
+
if ("error" in taken)
|
|
4546
|
+
return { ok: false, error: taken.error };
|
|
4547
|
+
const parsed = parseExternalCommandOverride(taken.value, "--bin", [
|
|
4548
|
+
"gh"
|
|
4549
|
+
]);
|
|
4550
|
+
if (parsed.ok === false)
|
|
4551
|
+
return { ok: false, error: parsed.error };
|
|
4552
|
+
commandOverrides.push(parsed.override);
|
|
4553
|
+
i = taken.next;
|
|
4554
|
+
} else if (valueFlags.has(arg)) {
|
|
4555
|
+
const taken = takeValue(argv, i, arg);
|
|
4556
|
+
if ("error" in taken)
|
|
4557
|
+
return { ok: false, error: taken.error };
|
|
4558
|
+
options.set(arg, taken.value);
|
|
4559
|
+
const values = multiOptions.get(arg) || [];
|
|
4560
|
+
values.push(taken.value);
|
|
4561
|
+
multiOptions.set(arg, values);
|
|
4562
|
+
i = taken.next;
|
|
4563
|
+
} else if (arg === "--json" || arg === "--dry-run" || arg === "--clear-labels") {
|
|
4564
|
+
flags.add(arg);
|
|
4565
|
+
} else if (arg.startsWith("-")) {
|
|
4566
|
+
return { ok: false, error: `unknown option: ${arg}` };
|
|
4567
|
+
} else {
|
|
4568
|
+
rest.push(arg);
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
const labels = multiOptions.get("--label") || [];
|
|
4572
|
+
const subcommand = rest[0];
|
|
4573
|
+
const dryRun = flags.has("--dry-run");
|
|
4574
|
+
if (!subcommand)
|
|
4575
|
+
return { ok: true, args: { command: { kind: "help" }, dryRun } };
|
|
4576
|
+
if (subcommand === "agent-help")
|
|
4577
|
+
return { ok: true, args: { command: { kind: "agent-help" }, dryRun } };
|
|
4578
|
+
if (commandOverrides.length > 0 && subcommand !== "github-issues" && subcommand !== "task-link-issue") {
|
|
4579
|
+
return {
|
|
4580
|
+
ok: false,
|
|
4581
|
+
error: "--bin is only supported with github-issues or task-link-issue"
|
|
4582
|
+
};
|
|
4583
|
+
}
|
|
4584
|
+
if (subcommand === "list") {
|
|
4585
|
+
return {
|
|
4586
|
+
ok: true,
|
|
4587
|
+
args: {
|
|
4588
|
+
command: {
|
|
4589
|
+
kind: "list",
|
|
4590
|
+
date: parseDateValue(options.get("--date"), "--date"),
|
|
4591
|
+
limit: parsePositiveInteger(options.get("--limit"), "--limit"),
|
|
4592
|
+
json: flags.has("--json")
|
|
4593
|
+
},
|
|
4594
|
+
cwd,
|
|
4595
|
+
server,
|
|
4596
|
+
dryRun
|
|
4597
|
+
}
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
if (subcommand === "add") {
|
|
4601
|
+
const date = parseDateValue(options.get("--date"), "--date");
|
|
4602
|
+
if (!date)
|
|
4603
|
+
return { ok: false, error: "add requires --date <YYYY-MM-DD|today>" };
|
|
4604
|
+
return {
|
|
4605
|
+
ok: true,
|
|
4606
|
+
args: {
|
|
4607
|
+
command: {
|
|
4608
|
+
kind: "add",
|
|
4609
|
+
date,
|
|
4610
|
+
title: options.get("--title"),
|
|
4611
|
+
labels,
|
|
4612
|
+
body: options.get("--body"),
|
|
4613
|
+
bodyFile: options.get("--body-file")
|
|
4614
|
+
},
|
|
4615
|
+
cwd,
|
|
4616
|
+
server,
|
|
4617
|
+
dryRun
|
|
4618
|
+
}
|
|
4619
|
+
};
|
|
4620
|
+
}
|
|
4621
|
+
if (subcommand === "edit") {
|
|
4622
|
+
const id = rest[1];
|
|
4623
|
+
if (!id)
|
|
4624
|
+
return { ok: false, error: "edit requires a journal entry id" };
|
|
4625
|
+
return {
|
|
4626
|
+
ok: true,
|
|
4627
|
+
args: {
|
|
4628
|
+
command: {
|
|
4629
|
+
kind: "edit",
|
|
4630
|
+
id,
|
|
4631
|
+
date: parseDateValue(options.get("--date"), "--date"),
|
|
4632
|
+
title: options.get("--title"),
|
|
4633
|
+
labels: multiOptions.has("--label") ? labels : undefined,
|
|
4634
|
+
body: options.get("--body"),
|
|
4635
|
+
bodyFile: options.get("--body-file")
|
|
4636
|
+
},
|
|
4637
|
+
cwd,
|
|
4638
|
+
server,
|
|
4639
|
+
dryRun
|
|
4640
|
+
}
|
|
4641
|
+
};
|
|
4642
|
+
}
|
|
4643
|
+
if (subcommand === "tasks") {
|
|
4644
|
+
return {
|
|
4645
|
+
ok: true,
|
|
4646
|
+
args: {
|
|
4647
|
+
command: {
|
|
4648
|
+
kind: "tasks",
|
|
4649
|
+
status: parseStatus(options.get("--status")),
|
|
4650
|
+
labels,
|
|
4651
|
+
json: flags.has("--json")
|
|
4652
|
+
},
|
|
4653
|
+
cwd,
|
|
4654
|
+
server,
|
|
4655
|
+
dryRun
|
|
4656
|
+
}
|
|
4657
|
+
};
|
|
4658
|
+
}
|
|
4659
|
+
if (subcommand === "task-add") {
|
|
4660
|
+
const title = options.get("--title");
|
|
4661
|
+
if (!title)
|
|
4662
|
+
return { ok: false, error: "task-add requires --title <text>" };
|
|
4663
|
+
return {
|
|
4664
|
+
ok: true,
|
|
4665
|
+
args: {
|
|
4666
|
+
command: {
|
|
4667
|
+
kind: "task-add",
|
|
4668
|
+
title,
|
|
4669
|
+
status: parseStatus(options.get("--status")),
|
|
4670
|
+
priority: parsePriority(options.get("--priority")),
|
|
4671
|
+
labels,
|
|
4672
|
+
dueDate: parseDateValue(options.get("--due"), "--due"),
|
|
4673
|
+
sourceDate: parseDateValue(options.get("--source-date"), "--source-date"),
|
|
4674
|
+
before: options.get("--before"),
|
|
4675
|
+
after: options.get("--after"),
|
|
4676
|
+
position: parsePositiveInteger(options.get("--position"), "--position"),
|
|
4677
|
+
body: options.get("--body"),
|
|
4678
|
+
bodyFile: options.get("--body-file")
|
|
4679
|
+
},
|
|
4680
|
+
cwd,
|
|
4681
|
+
server,
|
|
4682
|
+
dryRun
|
|
4683
|
+
}
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4686
|
+
if (subcommand === "task-update") {
|
|
4687
|
+
const id = rest[1];
|
|
4688
|
+
if (!id)
|
|
4689
|
+
return { ok: false, error: "task-update requires a task id" };
|
|
4690
|
+
return {
|
|
4691
|
+
ok: true,
|
|
4692
|
+
args: {
|
|
4693
|
+
command: {
|
|
4694
|
+
kind: "task-update",
|
|
4695
|
+
id,
|
|
4696
|
+
title: options.get("--title"),
|
|
4697
|
+
status: parseStatus(options.get("--status")),
|
|
4698
|
+
priority: parsePriority(options.get("--priority")),
|
|
4699
|
+
labels: multiOptions.has("--label") ? labels : undefined,
|
|
4700
|
+
clearLabels: flags.has("--clear-labels"),
|
|
4701
|
+
dueDate: parseDateValue(options.get("--due"), "--due") ?? undefined,
|
|
4702
|
+
sourceDate: parseDateValue(options.get("--source-date"), "--source-date") ?? undefined,
|
|
4703
|
+
body: options.get("--body"),
|
|
4704
|
+
bodyFile: options.get("--body-file")
|
|
4705
|
+
},
|
|
4706
|
+
cwd,
|
|
4707
|
+
server,
|
|
4708
|
+
dryRun
|
|
4709
|
+
}
|
|
4710
|
+
};
|
|
4711
|
+
}
|
|
4712
|
+
if (subcommand === "task-next") {
|
|
4713
|
+
return {
|
|
4714
|
+
ok: true,
|
|
4715
|
+
args: {
|
|
4716
|
+
command: {
|
|
4717
|
+
kind: "task-next",
|
|
4718
|
+
status: parseStatus(options.get("--status")),
|
|
4719
|
+
labels,
|
|
4720
|
+
limit: parsePositiveInteger(options.get("--limit"), "--limit") || 1,
|
|
4721
|
+
json: flags.has("--json")
|
|
4722
|
+
},
|
|
4723
|
+
cwd,
|
|
4724
|
+
server,
|
|
4725
|
+
dryRun
|
|
4726
|
+
}
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4729
|
+
if (subcommand === "github-issues") {
|
|
4730
|
+
if (multiOptions.has("--label")) {
|
|
4731
|
+
return {
|
|
4732
|
+
ok: false,
|
|
4733
|
+
error: "github-issues uses --gh-label, not --label"
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4736
|
+
return {
|
|
4737
|
+
ok: true,
|
|
4738
|
+
args: {
|
|
4739
|
+
command: {
|
|
4740
|
+
kind: "github-issues",
|
|
4741
|
+
repo: parseGithubOption(options.get("--repo"), "--repo"),
|
|
4742
|
+
ghState: parseGithubIssueState(options.get("--state")),
|
|
4743
|
+
ghLabels: (multiOptions.get("--gh-label") || []).map((label) => parseGithubOption(label, "--gh-label")),
|
|
4744
|
+
search: parseGithubOption(options.get("--search"), "--search"),
|
|
4745
|
+
limit: parsePositiveInteger(options.get("--limit"), "--limit") || 30,
|
|
4746
|
+
json: flags.has("--json")
|
|
4747
|
+
},
|
|
4748
|
+
cwd,
|
|
4749
|
+
server,
|
|
4750
|
+
commandOverrides,
|
|
4751
|
+
dryRun
|
|
4752
|
+
}
|
|
4753
|
+
};
|
|
4754
|
+
}
|
|
4755
|
+
if (subcommand === "task-link-issue") {
|
|
4756
|
+
if (multiOptions.has("--gh-label")) {
|
|
4757
|
+
return {
|
|
4758
|
+
ok: false,
|
|
4759
|
+
error: "task-link-issue uses --label for local labels"
|
|
4760
|
+
};
|
|
4761
|
+
}
|
|
4762
|
+
const issueNumber = parsePositiveInteger(rest[1], "issue number");
|
|
4763
|
+
if (!issueNumber) {
|
|
4764
|
+
return {
|
|
4765
|
+
ok: false,
|
|
4766
|
+
error: "task-link-issue requires <number>"
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4769
|
+
return {
|
|
4770
|
+
ok: true,
|
|
4771
|
+
args: {
|
|
4772
|
+
command: {
|
|
4773
|
+
kind: "task-link-issue",
|
|
4774
|
+
issueNumber,
|
|
4775
|
+
repo: parseGithubOption(options.get("--repo"), "--repo"),
|
|
4776
|
+
status: parseStatus(options.get("--status")),
|
|
4777
|
+
priority: parsePriority(options.get("--priority")),
|
|
4778
|
+
labels,
|
|
4779
|
+
before: options.get("--before"),
|
|
4780
|
+
after: options.get("--after"),
|
|
4781
|
+
position: parsePositiveInteger(options.get("--position"), "--position"),
|
|
4782
|
+
json: flags.has("--json")
|
|
4783
|
+
},
|
|
4784
|
+
cwd,
|
|
4785
|
+
server,
|
|
4786
|
+
commandOverrides,
|
|
4787
|
+
dryRun
|
|
4788
|
+
}
|
|
4789
|
+
};
|
|
4790
|
+
}
|
|
4791
|
+
if (subcommand === "task-claim") {
|
|
4792
|
+
const id = rest[1];
|
|
4793
|
+
if (!id)
|
|
4794
|
+
return { ok: false, error: "task-claim requires a task id" };
|
|
4795
|
+
return {
|
|
4796
|
+
ok: true,
|
|
4797
|
+
args: {
|
|
4798
|
+
command: {
|
|
4799
|
+
kind: "task-claim",
|
|
4800
|
+
id,
|
|
4801
|
+
by: options.get("--by"),
|
|
4802
|
+
leaseMinutes: parsePositiveInteger(options.get("--lease-minutes"), "--lease-minutes"),
|
|
4803
|
+
wipLimit: parsePositiveInteger(options.get("--wip-limit"), "--wip-limit")
|
|
4804
|
+
},
|
|
4805
|
+
cwd,
|
|
4806
|
+
server,
|
|
4807
|
+
dryRun
|
|
4808
|
+
}
|
|
4809
|
+
};
|
|
4810
|
+
}
|
|
4811
|
+
if (subcommand === "task-done") {
|
|
4812
|
+
const id = rest[1];
|
|
4813
|
+
if (!id)
|
|
4814
|
+
return { ok: false, error: "task-done requires a task id" };
|
|
4815
|
+
if (!options.get("--by"))
|
|
4816
|
+
return { ok: false, error: "task-done requires --by <agent>" };
|
|
4817
|
+
return {
|
|
4818
|
+
ok: true,
|
|
4819
|
+
args: {
|
|
4820
|
+
command: {
|
|
4821
|
+
kind: "task-done",
|
|
4822
|
+
id,
|
|
4823
|
+
by: options.get("--by"),
|
|
4824
|
+
note: options.get("--note"),
|
|
4825
|
+
noteFile: options.get("--note-file")
|
|
4826
|
+
},
|
|
4827
|
+
cwd,
|
|
4828
|
+
server,
|
|
4829
|
+
dryRun
|
|
4830
|
+
}
|
|
4831
|
+
};
|
|
4832
|
+
}
|
|
4833
|
+
if (subcommand === "task-delete") {
|
|
4834
|
+
const id = rest[1];
|
|
4835
|
+
if (!id)
|
|
4836
|
+
return { ok: false, error: "task-delete requires a task id" };
|
|
4837
|
+
return {
|
|
4838
|
+
ok: true,
|
|
4839
|
+
args: { command: { kind: "task-delete", id }, cwd, server, dryRun }
|
|
4840
|
+
};
|
|
4841
|
+
}
|
|
4842
|
+
return { ok: false, error: `unknown journal command: ${subcommand}` };
|
|
4843
|
+
} catch (error) {
|
|
4844
|
+
return {
|
|
4845
|
+
ok: false,
|
|
4846
|
+
error: error instanceof Error ? error.message : "invalid journal arguments"
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
async function textFromBody(command, required) {
|
|
4851
|
+
if (command.body !== undefined && command.bodyFile !== undefined) {
|
|
4852
|
+
console.error("use either --body or --body-file");
|
|
4853
|
+
process.exit(1);
|
|
4854
|
+
}
|
|
4855
|
+
if (command.body !== undefined)
|
|
4856
|
+
return command.body;
|
|
4857
|
+
if (command.bodyFile !== undefined) {
|
|
4858
|
+
try {
|
|
4859
|
+
return readFileSync5(command.bodyFile, "utf8");
|
|
4860
|
+
} catch {
|
|
4861
|
+
console.error(`could not read --body-file: ${command.bodyFile}`);
|
|
4862
|
+
process.exit(1);
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
const stdin = await readStdin();
|
|
4866
|
+
if (stdin.trim())
|
|
4867
|
+
return stdin;
|
|
4868
|
+
if (required) {
|
|
4869
|
+
console.error("body is empty. Pass --body, --body-file, or pipe stdin.");
|
|
4870
|
+
process.exit(1);
|
|
4871
|
+
}
|
|
4872
|
+
return;
|
|
4873
|
+
}
|
|
4874
|
+
function textFromNote(command) {
|
|
4875
|
+
if (command.note !== undefined && command.noteFile !== undefined) {
|
|
4876
|
+
console.error("use either --note or --note-file");
|
|
4877
|
+
process.exit(1);
|
|
4878
|
+
}
|
|
4879
|
+
if (command.note !== undefined)
|
|
4880
|
+
return command.note;
|
|
4881
|
+
if (command.noteFile !== undefined) {
|
|
4882
|
+
try {
|
|
4883
|
+
return readFileSync5(command.noteFile, "utf8");
|
|
4884
|
+
} catch {
|
|
4885
|
+
console.error(`could not read --note-file: ${command.noteFile}`);
|
|
4886
|
+
process.exit(1);
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
return;
|
|
4890
|
+
}
|
|
4891
|
+
async function journalRequest(serverUrl, method, action, body) {
|
|
4892
|
+
return requestJson(serverUrl, "/_journal", method, body, action);
|
|
4893
|
+
}
|
|
4894
|
+
function selectEntries(data, date, limit) {
|
|
4895
|
+
const entries = date ? data.journal.entries.filter((entry) => entry.date === date) : data.journal.entries;
|
|
4896
|
+
if (limit === undefined)
|
|
4897
|
+
return entries;
|
|
4898
|
+
return [...entries].sort((a, b) => b.date.localeCompare(a.date) || b.updated_at.localeCompare(a.updated_at) || b.created_at.localeCompare(a.created_at)).slice(0, limit);
|
|
4899
|
+
}
|
|
4900
|
+
function printEntries(data, date, limit) {
|
|
4901
|
+
const entries = selectEntries(data, date, limit);
|
|
4902
|
+
if (!entries.length) {
|
|
4903
|
+
console.log("no journal entries");
|
|
4904
|
+
return;
|
|
4905
|
+
}
|
|
4906
|
+
for (const entry of entries) {
|
|
4907
|
+
const labels = entry.labels.length ? ` #${entry.labels.join(" #")}` : "";
|
|
4908
|
+
const title = entry.title ? ` ${entry.title}` : "";
|
|
4909
|
+
console.log(`${entry.date} [${entry.id}]${title}${labels}`);
|
|
4910
|
+
console.log(` ${entry.body.split(`
|
|
4911
|
+
`)[0].slice(0, 120)}`);
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
function printTasks(tasks) {
|
|
4915
|
+
if (!tasks.length) {
|
|
4916
|
+
console.log("no tasks");
|
|
4917
|
+
return;
|
|
4918
|
+
}
|
|
4919
|
+
for (const task of tasks) {
|
|
4920
|
+
const labels = task.labels.length ? ` #${task.labels.join(" #")}` : "";
|
|
4921
|
+
console.log(`[${task.id}] ${task.status} ${task.priority} ${task.title}${labels}`);
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
function printGithubIssues(issues) {
|
|
4925
|
+
if (!issues.length) {
|
|
4926
|
+
console.log("no GitHub issues");
|
|
4927
|
+
return;
|
|
4928
|
+
}
|
|
4929
|
+
for (const issue of issues) {
|
|
4930
|
+
const labels = issue.labels.length ? ` #${issue.labels.join(" #")}` : "";
|
|
4931
|
+
const url = issue.url ? ` ${issue.url}` : "";
|
|
4932
|
+
console.log(`#${issue.number} ${issue.state} ${issue.title}${labels}${url}`);
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
function taskLinkIssuePayload(command, issue) {
|
|
4936
|
+
return {
|
|
4937
|
+
action: "link-github-issue",
|
|
4938
|
+
issue_number: issue.number,
|
|
4939
|
+
repo: command.repo,
|
|
4940
|
+
title: issue.title,
|
|
4941
|
+
url: issue.url,
|
|
4942
|
+
memo_label: "Memo:",
|
|
4943
|
+
status: command.status,
|
|
4944
|
+
priority: command.priority,
|
|
4945
|
+
labels: command.labels,
|
|
4946
|
+
before_id: command.before,
|
|
4947
|
+
after_id: command.after,
|
|
4948
|
+
position: command.position
|
|
4949
|
+
};
|
|
4950
|
+
}
|
|
4951
|
+
function writePayload(payload) {
|
|
4952
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4953
|
+
}
|
|
4954
|
+
async function getJournalData(serverUrl) {
|
|
4955
|
+
return await journalRequest(serverUrl, "GET", "journal list");
|
|
4956
|
+
}
|
|
4957
|
+
async function runJournalCli(argv) {
|
|
4958
|
+
const parsed = parseJournalArgs(argv);
|
|
4959
|
+
if (parsed.ok === false) {
|
|
4960
|
+
console.error(parsed.error);
|
|
4961
|
+
console.error('Run "code-viewer journal --help" for usage.');
|
|
4962
|
+
process.exit(1);
|
|
4963
|
+
}
|
|
4964
|
+
const { command, cwd, server, commandOverrides = [], dryRun } = parsed.args;
|
|
4965
|
+
if (command.kind === "help") {
|
|
4966
|
+
console.log(JOURNAL_HELP);
|
|
4967
|
+
return;
|
|
4968
|
+
}
|
|
4969
|
+
if (command.kind === "agent-help") {
|
|
4970
|
+
console.log(JOURNAL_AGENT_HELP);
|
|
4971
|
+
return;
|
|
4972
|
+
}
|
|
4973
|
+
const dryRunPayload = async (action, body) => {
|
|
4974
|
+
if (!dryRun)
|
|
4975
|
+
return false;
|
|
4976
|
+
writePayload({ action, ...body });
|
|
4977
|
+
return true;
|
|
4978
|
+
};
|
|
4979
|
+
if (dryRun) {
|
|
4980
|
+
if (command.kind === "add") {
|
|
4981
|
+
writePayload({
|
|
4982
|
+
action: "add-entry",
|
|
4983
|
+
date: command.date,
|
|
4984
|
+
title: command.title,
|
|
4985
|
+
labels: command.labels,
|
|
4986
|
+
body: await textFromBody(command, true)
|
|
4987
|
+
});
|
|
4988
|
+
return;
|
|
4989
|
+
}
|
|
4990
|
+
if (command.kind === "edit") {
|
|
4991
|
+
writePayload({
|
|
4992
|
+
action: "update-entry",
|
|
4993
|
+
id: command.id,
|
|
4994
|
+
date: command.date,
|
|
4995
|
+
title: command.title,
|
|
4996
|
+
labels: command.labels,
|
|
4997
|
+
body: await textFromBody(command, false)
|
|
4998
|
+
});
|
|
4999
|
+
return;
|
|
5000
|
+
}
|
|
5001
|
+
if (command.kind === "task-add") {
|
|
5002
|
+
writePayload({
|
|
5003
|
+
action: "add-task",
|
|
5004
|
+
title: command.title,
|
|
5005
|
+
status: command.status,
|
|
5006
|
+
priority: command.priority,
|
|
5007
|
+
labels: command.labels,
|
|
5008
|
+
due_date: command.dueDate === "" ? null : command.dueDate,
|
|
5009
|
+
source_date: command.sourceDate === "" ? null : command.sourceDate,
|
|
5010
|
+
before_id: command.before,
|
|
5011
|
+
after_id: command.after,
|
|
5012
|
+
position: command.position,
|
|
5013
|
+
body: await textFromBody(command, false)
|
|
5014
|
+
});
|
|
5015
|
+
return;
|
|
5016
|
+
}
|
|
5017
|
+
if (command.kind === "task-update") {
|
|
5018
|
+
writePayload({
|
|
5019
|
+
action: "update-task",
|
|
5020
|
+
id: command.id,
|
|
5021
|
+
title: command.title,
|
|
5022
|
+
status: command.status,
|
|
5023
|
+
priority: command.priority,
|
|
5024
|
+
labels: command.clearLabels ? [] : command.labels,
|
|
5025
|
+
due_date: command.dueDate === "" ? null : command.dueDate,
|
|
5026
|
+
source_date: command.sourceDate === "" ? null : command.sourceDate,
|
|
5027
|
+
body: await textFromBody(command, false)
|
|
5028
|
+
});
|
|
5029
|
+
return;
|
|
5030
|
+
}
|
|
5031
|
+
if (command.kind === "task-claim") {
|
|
5032
|
+
writePayload({
|
|
5033
|
+
action: "claim-task",
|
|
5034
|
+
id: command.id,
|
|
5035
|
+
by: command.by,
|
|
5036
|
+
lease_minutes: command.leaseMinutes,
|
|
5037
|
+
wip_limit: command.wipLimit
|
|
5038
|
+
});
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
5041
|
+
if (command.kind === "task-done") {
|
|
5042
|
+
writePayload({
|
|
5043
|
+
action: "complete-task",
|
|
5044
|
+
id: command.id,
|
|
5045
|
+
by: command.by,
|
|
5046
|
+
note: textFromNote(command)
|
|
5047
|
+
});
|
|
5048
|
+
return;
|
|
5049
|
+
}
|
|
5050
|
+
if (command.kind === "task-delete") {
|
|
5051
|
+
writePayload({ action: "delete-task", id: command.id });
|
|
5052
|
+
return;
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
const root = resolveRepoRoot(cwd);
|
|
5056
|
+
if (command.kind === "github-issues") {
|
|
5057
|
+
const commandConfig = configureExternalCommands({
|
|
5058
|
+
cwd: root,
|
|
5059
|
+
cliOverrides: commandOverrides,
|
|
5060
|
+
allowedNames: ["gh"]
|
|
5061
|
+
});
|
|
5062
|
+
if (commandConfig.ok === false) {
|
|
5063
|
+
console.error(commandConfig.error);
|
|
5064
|
+
process.exit(1);
|
|
5065
|
+
}
|
|
5066
|
+
const issues = readGithubIssueList({
|
|
5067
|
+
cwd: root,
|
|
5068
|
+
repo: command.repo,
|
|
5069
|
+
labels: command.ghLabels,
|
|
5070
|
+
search: command.search,
|
|
5071
|
+
state: command.ghState,
|
|
5072
|
+
limit: command.limit
|
|
5073
|
+
});
|
|
5074
|
+
if (command.json)
|
|
5075
|
+
console.log(JSON.stringify({ issues }, null, 2));
|
|
5076
|
+
else
|
|
5077
|
+
printGithubIssues(issues);
|
|
5078
|
+
return;
|
|
5079
|
+
}
|
|
5080
|
+
if (command.kind === "task-link-issue") {
|
|
5081
|
+
const commandConfig = configureExternalCommands({
|
|
5082
|
+
cwd: root,
|
|
5083
|
+
cliOverrides: commandOverrides,
|
|
5084
|
+
allowedNames: ["gh"]
|
|
5085
|
+
});
|
|
5086
|
+
if (commandConfig.ok === false) {
|
|
5087
|
+
console.error(commandConfig.error);
|
|
5088
|
+
process.exit(1);
|
|
5089
|
+
}
|
|
5090
|
+
const issue = readGithubIssue({
|
|
5091
|
+
cwd: root,
|
|
5092
|
+
number: command.issueNumber,
|
|
5093
|
+
repo: command.repo
|
|
5094
|
+
});
|
|
5095
|
+
if (dryRun) {
|
|
5096
|
+
writePayload(taskLinkIssuePayload(command, issue));
|
|
5097
|
+
return;
|
|
5098
|
+
}
|
|
5099
|
+
const serverUrl2 = await ensureServerUrl(root, server, "/_journal");
|
|
5100
|
+
const result2 = await journalRequest(serverUrl2, "POST", "journal task-link-issue", taskLinkIssuePayload(command, issue));
|
|
5101
|
+
if (command.json)
|
|
5102
|
+
console.log(JSON.stringify({
|
|
5103
|
+
issue,
|
|
5104
|
+
task: result2.task,
|
|
5105
|
+
action: result2.created ? "created" : result2.moved ? "moved" : "existing"
|
|
5106
|
+
}, null, 2));
|
|
5107
|
+
else if (result2.created)
|
|
5108
|
+
console.log(`linked issue #${issue.number} to task ${result2.task.id}`);
|
|
5109
|
+
else if (result2.moved)
|
|
5110
|
+
console.log(`moved linked issue #${issue.number} task ${result2.task.id}`);
|
|
5111
|
+
else
|
|
5112
|
+
console.log(`issue #${issue.number} is linked to task ${result2.task.id}`);
|
|
5113
|
+
return;
|
|
5114
|
+
}
|
|
5115
|
+
const serverUrl = await ensureServerUrl(root, server, "/_journal");
|
|
5116
|
+
if (command.kind === "list") {
|
|
5117
|
+
const data = await getJournalData(serverUrl);
|
|
5118
|
+
const entries = selectEntries(data, command.date, command.limit);
|
|
5119
|
+
if (command.json)
|
|
5120
|
+
console.log(JSON.stringify({ version: data.journal.version, entries }, null, 2));
|
|
5121
|
+
else
|
|
5122
|
+
printEntries(data, command.date, command.limit);
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
5125
|
+
if (command.kind === "add") {
|
|
5126
|
+
const body = await textFromBody(command, true);
|
|
5127
|
+
const result2 = await journalRequest(serverUrl, "POST", "journal add", {
|
|
5128
|
+
action: "add-entry",
|
|
5129
|
+
date: command.date,
|
|
5130
|
+
title: command.title,
|
|
5131
|
+
labels: command.labels,
|
|
5132
|
+
body
|
|
5133
|
+
});
|
|
5134
|
+
console.log(`added journal entry ${result2.entry.id} for ${result2.entry.date}`);
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
if (command.kind === "edit") {
|
|
5138
|
+
const body = await textFromBody(command, false);
|
|
5139
|
+
const payload2 = {
|
|
5140
|
+
action: "update-entry",
|
|
5141
|
+
id: command.id,
|
|
5142
|
+
date: command.date,
|
|
5143
|
+
title: command.title,
|
|
5144
|
+
labels: command.labels,
|
|
5145
|
+
body
|
|
5146
|
+
};
|
|
5147
|
+
if (await dryRunPayload("update-entry", payload2))
|
|
5148
|
+
return;
|
|
5149
|
+
await journalRequest(serverUrl, "POST", "journal edit", payload2);
|
|
5150
|
+
console.log(`updated journal entry ${command.id}`);
|
|
5151
|
+
return;
|
|
5152
|
+
}
|
|
5153
|
+
if (command.kind === "tasks") {
|
|
5154
|
+
const data = await getJournalData(serverUrl);
|
|
5155
|
+
const tasks = filterJournalTasks(data.tasks, {
|
|
5156
|
+
status: command.status,
|
|
5157
|
+
labels: command.labels,
|
|
5158
|
+
includeClaimed: true
|
|
5159
|
+
});
|
|
5160
|
+
if (command.json)
|
|
5161
|
+
console.log(JSON.stringify({ version: 1, tasks }, null, 2));
|
|
5162
|
+
else
|
|
5163
|
+
printTasks(tasks);
|
|
5164
|
+
return;
|
|
5165
|
+
}
|
|
5166
|
+
if (command.kind === "task-add") {
|
|
5167
|
+
const body = await textFromBody(command, false);
|
|
5168
|
+
const payload2 = {
|
|
5169
|
+
action: "add-task",
|
|
5170
|
+
title: command.title,
|
|
5171
|
+
status: command.status,
|
|
5172
|
+
priority: command.priority,
|
|
5173
|
+
labels: command.labels,
|
|
5174
|
+
due_date: command.dueDate === "" ? null : command.dueDate,
|
|
5175
|
+
source_date: command.sourceDate === "" ? null : command.sourceDate,
|
|
5176
|
+
before_id: command.before,
|
|
5177
|
+
after_id: command.after,
|
|
5178
|
+
position: command.position,
|
|
5179
|
+
body
|
|
5180
|
+
};
|
|
5181
|
+
if (await dryRunPayload("add-task", payload2))
|
|
5182
|
+
return;
|
|
5183
|
+
const result2 = await journalRequest(serverUrl, "POST", "journal task-add", payload2);
|
|
5184
|
+
console.log(`added task ${result2.task.id} ${result2.task.title}`);
|
|
5185
|
+
return;
|
|
5186
|
+
}
|
|
5187
|
+
if (command.kind === "task-update") {
|
|
5188
|
+
const body = await textFromBody(command, false);
|
|
5189
|
+
const payload2 = {
|
|
5190
|
+
action: "update-task",
|
|
5191
|
+
id: command.id,
|
|
5192
|
+
title: command.title,
|
|
5193
|
+
status: command.status,
|
|
5194
|
+
priority: command.priority,
|
|
5195
|
+
labels: command.clearLabels ? [] : command.labels,
|
|
5196
|
+
due_date: command.dueDate === "" ? null : command.dueDate,
|
|
5197
|
+
source_date: command.sourceDate === "" ? null : command.sourceDate,
|
|
5198
|
+
body
|
|
5199
|
+
};
|
|
5200
|
+
if (await dryRunPayload("update-task", payload2))
|
|
5201
|
+
return;
|
|
5202
|
+
await journalRequest(serverUrl, "POST", "journal task-update", payload2);
|
|
5203
|
+
console.log(`updated task ${command.id}`);
|
|
5204
|
+
return;
|
|
5205
|
+
}
|
|
5206
|
+
if (command.kind === "task-next") {
|
|
5207
|
+
const data = await getJournalData(serverUrl);
|
|
5208
|
+
const tasks = selectNextJournalTasks(data.tasks, {
|
|
5209
|
+
status: command.status || "todo",
|
|
5210
|
+
labels: command.labels
|
|
5211
|
+
}, command.limit);
|
|
5212
|
+
if (command.json)
|
|
5213
|
+
console.log(JSON.stringify({ tasks }, null, 2));
|
|
5214
|
+
else
|
|
5215
|
+
printTasks(tasks);
|
|
5216
|
+
return;
|
|
5217
|
+
}
|
|
5218
|
+
if (command.kind === "task-claim") {
|
|
5219
|
+
const payload2 = {
|
|
5220
|
+
action: "claim-task",
|
|
5221
|
+
id: command.id,
|
|
5222
|
+
by: command.by,
|
|
5223
|
+
lease_minutes: command.leaseMinutes,
|
|
5224
|
+
wip_limit: command.wipLimit
|
|
5225
|
+
};
|
|
5226
|
+
if (await dryRunPayload("claim-task", payload2))
|
|
5227
|
+
return;
|
|
5228
|
+
const result2 = await journalRequest(serverUrl, "POST", "journal task-claim", payload2);
|
|
5229
|
+
console.log(`claimed task ${result2.task.id} until ${result2.task.claim?.lease_expires_at}`);
|
|
5230
|
+
return;
|
|
5231
|
+
}
|
|
5232
|
+
if (command.kind === "task-done") {
|
|
5233
|
+
const payload2 = {
|
|
5234
|
+
action: "complete-task",
|
|
5235
|
+
id: command.id,
|
|
5236
|
+
by: command.by,
|
|
5237
|
+
note: textFromNote(command)
|
|
5238
|
+
};
|
|
5239
|
+
if (await dryRunPayload("complete-task", payload2))
|
|
5240
|
+
return;
|
|
5241
|
+
await journalRequest(serverUrl, "POST", "journal task-done", payload2);
|
|
5242
|
+
console.log(`completed task ${command.id}`);
|
|
5243
|
+
return;
|
|
5244
|
+
}
|
|
5245
|
+
const payload = { action: "delete-task", id: command.id };
|
|
5246
|
+
if (await dryRunPayload("delete-task", payload))
|
|
5247
|
+
return;
|
|
5248
|
+
const result = await journalRequest(serverUrl, "POST", "journal task-delete", payload);
|
|
5249
|
+
if (!result.removed) {
|
|
5250
|
+
console.error(`task not found: ${command.id}`);
|
|
5251
|
+
process.exit(1);
|
|
5252
|
+
}
|
|
5253
|
+
console.log(`deleted task ${command.id}`);
|
|
5254
|
+
}
|
|
5255
|
+
var JOURNAL_HELP = `code-viewer journal — daily work journal and task queue
|
|
5256
|
+
|
|
5257
|
+
The journal is stored in <repo>/.code-viewer/daily-journal.json and tasks are
|
|
5258
|
+
stored in <repo>/.code-viewer/tasks.json. A running code-viewer server for the
|
|
5259
|
+
repository is required unless you pass --dry-run for a write command.
|
|
5260
|
+
The github-issues command is read-only and runs gh directly without a server.
|
|
4049
5261
|
|
|
4050
|
-
|
|
4051
|
-
code-viewer file blame --path src/sample.ts --base HEAD --json
|
|
4052
|
-
code-viewer file history --path src/sample.ts --limit 10 --json
|
|
4053
|
-
code-viewer file history --path src/sample.ts --query "author:tester" --json
|
|
4054
|
-
code-viewer file show --path src/sample.ts --json
|
|
4055
|
-
code-viewer file show --path src/sample.ts --start 100 --end 150 --json
|
|
4056
|
-
code-viewer file show --path src/sample.ts --ref main --json
|
|
4057
|
-
code-viewer file diff --path src/sample.ts --json
|
|
4058
|
-
code-viewer file diff --path src/sample.ts --from main --to HEAD --json
|
|
4059
|
-
code-viewer file diff --path src/sample.ts --full --json
|
|
4060
|
-
code-viewer file diff --path new_sample.ts --untracked --json
|
|
4061
|
-
code-viewer file diff --path renamed.ts --old-path original.ts --from HEAD~1 --to HEAD --json
|
|
5262
|
+
Run "code-viewer journal agent-help" for an AI-agent oriented guide.
|
|
4062
5263
|
|
|
4063
|
-
|
|
5264
|
+
Usage:
|
|
5265
|
+
code-viewer journal list [--date <YYYY-MM-DD|today>] [--limit <n>] [--json]
|
|
5266
|
+
code-viewer journal add --date <YYYY-MM-DD|today> [--title <text>]
|
|
5267
|
+
[--label <label>...] [--body <markdown> | --body-file <path>]
|
|
5268
|
+
code-viewer journal edit <id> [--date <YYYY-MM-DD|today>] [--title <text>]
|
|
5269
|
+
[--label <label>...] [--body <markdown> | --body-file <path>]
|
|
5270
|
+
code-viewer journal tasks [--status <draft|todo|doing|blocked|done>]
|
|
5271
|
+
[--label <label>...] [--json]
|
|
5272
|
+
code-viewer journal task-add --title <text>
|
|
5273
|
+
[--status <status>] [--priority <p0|p1|p2|p3>] [--label <label>...]
|
|
5274
|
+
[--due <YYYY-MM-DD|none>] [--source-date <YYYY-MM-DD|today|none>]
|
|
5275
|
+
[--before <id> | --after <id> | --position <n>]
|
|
5276
|
+
[--body <markdown> | --body-file <path>]
|
|
5277
|
+
code-viewer journal task-update <id> [--title <text>] [--status <status>]
|
|
5278
|
+
[--priority <p0|p1|p2|p3>] [--label <label>...] [--clear-labels]
|
|
5279
|
+
[--due <YYYY-MM-DD|none>] [--source-date <YYYY-MM-DD|today|none>]
|
|
5280
|
+
[--body <markdown> | --body-file <path>]
|
|
5281
|
+
code-viewer journal task-next [--label <label>...] [--status <status>]
|
|
5282
|
+
[--limit <n>] [--json]
|
|
5283
|
+
code-viewer journal github-issues [--repo <owner/repo>]
|
|
5284
|
+
[--state <open|closed|all>] [--gh-label <label>...]
|
|
5285
|
+
[--search <query>] [--limit <n>] [--json] [--bin gh=<path>]
|
|
5286
|
+
code-viewer journal task-link-issue <number> [--repo <owner/repo>]
|
|
5287
|
+
[--status <status>] [--priority <p0|p1|p2|p3>] [--label <label>...]
|
|
5288
|
+
[--before <id> | --after <id> | --position <n>] [--json]
|
|
5289
|
+
[--dry-run] [--bin gh=<path>]
|
|
5290
|
+
code-viewer journal task-claim <id> [--by <agent>] [--lease-minutes <n>]
|
|
5291
|
+
[--wip-limit <n>]
|
|
5292
|
+
code-viewer journal task-done <id> --by <agent>
|
|
5293
|
+
[--note <markdown> | --note-file <path>]
|
|
5294
|
+
code-viewer journal task-delete <id>
|
|
4064
5295
|
|
|
4065
|
-
|
|
5296
|
+
Global options:
|
|
5297
|
+
--cwd <dir> repository root (default: current directory)
|
|
5298
|
+
--server <url> code-viewer server URL (default: auto-discovered)
|
|
5299
|
+
--bin gh=<p> override gh executable path for github-issues
|
|
5300
|
+
--dry-run print the write payload without sending it
|
|
5301
|
+
`, JOURNAL_AGENT_HELP = `code-viewer journal — agent guide
|
|
4066
5302
|
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
"path": string,
|
|
4070
|
-
"ref": string, // the literal ref the CLI resolved to
|
|
4071
|
-
"base": "worktree"|"HEAD",
|
|
4072
|
-
"result": GitBlameResult // { lines, commits, isUntracked?, isSynthetic?, error? }
|
|
4073
|
-
}
|
|
4074
|
-
history:
|
|
4075
|
-
{
|
|
4076
|
-
"path": string,
|
|
4077
|
-
"ref": string,
|
|
4078
|
-
"limit": number,
|
|
4079
|
-
"skip": number,
|
|
4080
|
-
"query"?: string,
|
|
4081
|
-
"result": { commits: GitHistoryCommit[], hasMore: boolean, error?: string }
|
|
4082
|
-
}
|
|
4083
|
-
show:
|
|
4084
|
-
{
|
|
4085
|
-
"path": string,
|
|
4086
|
-
"ref": string,
|
|
4087
|
-
"start"?: number,
|
|
4088
|
-
"end"?: number,
|
|
4089
|
-
"totalLines": number, // total lines in the file at <ref>
|
|
4090
|
-
"complete": boolean, // true when the returned text covers the whole file
|
|
4091
|
-
"text": string
|
|
4092
|
-
}
|
|
4093
|
-
diff:
|
|
4094
|
-
{
|
|
4095
|
-
"path": string,
|
|
4096
|
-
"old_path"?: string,
|
|
4097
|
-
"from": string, // "/dev/null" when untracked is true
|
|
4098
|
-
"to": string,
|
|
4099
|
-
"untracked": boolean,
|
|
4100
|
-
"ignore_ws": boolean,
|
|
4101
|
-
"ignore_blank": boolean,
|
|
4102
|
-
"mode": "preview"|"full",
|
|
4103
|
-
"max_hunks": number|null, // null in full mode
|
|
4104
|
-
"max_lines": number|null, // null in full mode
|
|
4105
|
-
"diff": string, // unified diff text
|
|
4106
|
-
"hunk_count": number, // total hunks before truncation
|
|
4107
|
-
"rendered_hunk_count": number, // hunks actually included in diff
|
|
4108
|
-
"line_count": number, // total newlines in the returned diff
|
|
4109
|
-
"truncated": boolean, // true when preview dropped hunks/lines
|
|
4110
|
-
"binary": boolean, // git emitted "Binary files ..."
|
|
4111
|
-
"error"?: string // git stderr (also exits 1)
|
|
4112
|
-
}
|
|
5303
|
+
You are an AI coding agent. Use this tool to write daily work notes and process
|
|
5304
|
+
explicit task queues without guessing from memory.
|
|
4113
5305
|
|
|
4114
|
-
|
|
5306
|
+
## Workflow
|
|
4115
5307
|
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
5308
|
+
1. Check the queue before doing label-scoped work:
|
|
5309
|
+
code-viewer journal task-next --label ai-ready --limit 5 --json
|
|
5310
|
+
2. Claim exactly one task before editing code:
|
|
5311
|
+
code-viewer journal task-claim <task-id> --by agent --wip-limit 1
|
|
5312
|
+
3. When finished, mark it done with a short note:
|
|
5313
|
+
code-viewer journal task-done <task-id> --by agent --note "Implemented and verified."
|
|
5314
|
+
4. Add a daily journal entry for work that is not already represented by a task:
|
|
5315
|
+
code-viewer journal add --date today --label ai --body "..."
|
|
5316
|
+
5. Inspect GitHub issues read-only before deciding what local task to create:
|
|
5317
|
+
code-viewer journal github-issues --repo owner/repo --json
|
|
5318
|
+
6. Link a GitHub issue to the local board without updating GitHub:
|
|
5319
|
+
code-viewer journal task-link-issue 123 --repo owner/repo --status draft --label ai-ready
|
|
4123
5320
|
|
|
4124
|
-
##
|
|
5321
|
+
## Rules
|
|
4125
5322
|
|
|
4126
|
-
-
|
|
4127
|
-
|
|
4128
|
-
-
|
|
4129
|
-
|
|
4130
|
-
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
error string inside the JSON "result.error" / "error" field AND exits 1.
|
|
4134
|
-
- file diff defaults to preview mode (${FILE_DIFF_DEFAULT_MAX_HUNKS} hunks /
|
|
4135
|
-
${FILE_DIFF_DEFAULT_MAX_LINES} lines). Use --full only when you need every
|
|
4136
|
-
hunk — preview matches the browser's initial paint and is almost always
|
|
4137
|
-
enough context for an LLM. In full mode, max_hunks / max_lines are null in
|
|
4138
|
-
JSON because no preview cap is applied. Worktree == Worktree range returns
|
|
4139
|
-
"" instantly.
|
|
4140
|
-
- Use "code-viewer search code" to discover the path first, then drill
|
|
4141
|
-
in with blame / history / show / diff.
|
|
5323
|
+
- Treat labels as filters, priority as importance, and card order as human order.
|
|
5324
|
+
- Do not process draft tasks unless the human asked for drafts.
|
|
5325
|
+
- Use task-next for ordering; do not sort the JSON yourself unless asked.
|
|
5326
|
+
- Use --dry-run before large generated entries.
|
|
5327
|
+
- GitHub issue listing is read-only. Create or update local tasks explicitly.
|
|
5328
|
+
- task-link-issue reads issue metadata only and stores a local task link plus
|
|
5329
|
+
your local labels. It does not copy the issue body or update GitHub.
|
|
4142
5330
|
`;
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
"--skip",
|
|
4149
|
-
"--query",
|
|
4150
|
-
"--start",
|
|
4151
|
-
"--end",
|
|
4152
|
-
"--from",
|
|
4153
|
-
"--to",
|
|
4154
|
-
"--old-path",
|
|
4155
|
-
"--max-hunks",
|
|
4156
|
-
"--max-lines"
|
|
4157
|
-
]);
|
|
4158
|
-
BOOL_FLAGS = new Set([
|
|
4159
|
-
"--json",
|
|
4160
|
-
"--untracked",
|
|
4161
|
-
"--ignore-ws",
|
|
4162
|
-
"--ignore-blank",
|
|
4163
|
-
"--full"
|
|
4164
|
-
]);
|
|
5331
|
+
var init_journal_cli = __esm(() => {
|
|
5332
|
+
init_journal();
|
|
5333
|
+
init_cli_helpers();
|
|
5334
|
+
init_command_resolver();
|
|
5335
|
+
init_github_issues();
|
|
4165
5336
|
});
|
|
4166
5337
|
|
|
4167
5338
|
// web-src/core/routes.ts
|
|
@@ -4214,6 +5385,19 @@ function buildRoute(route) {
|
|
|
4214
5385
|
const qs = params.toString();
|
|
4215
5386
|
return `/history${qs ? `?${qs}` : ""}`;
|
|
4216
5387
|
}
|
|
5388
|
+
case "journal": {
|
|
5389
|
+
const params = new URLSearchParams;
|
|
5390
|
+
if (route.tab && route.tab !== "journal")
|
|
5391
|
+
params.set("tab", route.tab);
|
|
5392
|
+
if (route.date)
|
|
5393
|
+
params.set("date", route.date);
|
|
5394
|
+
if (route.label)
|
|
5395
|
+
params.set("label", route.label);
|
|
5396
|
+
if (route.task)
|
|
5397
|
+
params.set("task", route.task);
|
|
5398
|
+
const qs = params.toString();
|
|
5399
|
+
return `/journal${qs ? `?${qs}` : ""}`;
|
|
5400
|
+
}
|
|
4217
5401
|
case "database": {
|
|
4218
5402
|
const params = new URLSearchParams;
|
|
4219
5403
|
if (route.db)
|
|
@@ -4245,6 +5429,7 @@ var init_routes = __esm(() => {
|
|
|
4245
5429
|
"/file",
|
|
4246
5430
|
"/help",
|
|
4247
5431
|
"/history",
|
|
5432
|
+
"/journal",
|
|
4248
5433
|
"/database",
|
|
4249
5434
|
"/doctor"
|
|
4250
5435
|
];
|
|
@@ -7494,9 +8679,10 @@ missing bundled skills).
|
|
|
7494
8679
|
## What gets installed
|
|
7495
8680
|
|
|
7496
8681
|
The bundled set covers code-viewer's own AI workflows (annotation
|
|
7497
|
-
walkthroughs, doctor introspection,
|
|
7498
|
-
install, ask the AI to use
|
|
7499
|
-
|
|
8682
|
+
walkthroughs, Work Log task queues, doctor introspection, and
|
|
8683
|
+
query/snapshot inspection). After install, ask the AI to use
|
|
8684
|
+
\`code-viewer annotate\`, \`code-viewer journal\`, or the related
|
|
8685
|
+
commands and the skills will guide it.
|
|
7500
8686
|
`;
|
|
7501
8687
|
var init_skill_cli = __esm(() => {
|
|
7502
8688
|
init_root();
|
|
@@ -7954,6 +9140,7 @@ var AGENT_GUIDES;
|
|
|
7954
9140
|
var init_agent_help = __esm(() => {
|
|
7955
9141
|
init_annotate_cli();
|
|
7956
9142
|
init_file_cli();
|
|
9143
|
+
init_journal_cli();
|
|
7957
9144
|
init_query_cli();
|
|
7958
9145
|
init_search_cli();
|
|
7959
9146
|
init_skill_cli();
|
|
@@ -7974,6 +9161,11 @@ var init_agent_help = __esm(() => {
|
|
|
7974
9161
|
signature: firstLine(ANNOTATE_AGENT_HELP),
|
|
7975
9162
|
rerun: "code-viewer annotate agent-help"
|
|
7976
9163
|
},
|
|
9164
|
+
{
|
|
9165
|
+
name: "journal",
|
|
9166
|
+
signature: firstLine(JOURNAL_AGENT_HELP),
|
|
9167
|
+
rerun: "code-viewer journal agent-help"
|
|
9168
|
+
},
|
|
7977
9169
|
{
|
|
7978
9170
|
name: "search",
|
|
7979
9171
|
signature: firstLine(SEARCH_AGENT_HELP),
|
|
@@ -17135,7 +18327,7 @@ var init_handle = __esm(() => {
|
|
|
17135
18327
|
});
|
|
17136
18328
|
|
|
17137
18329
|
// web-src/server/doctor.ts
|
|
17138
|
-
import { accessSync as accessSync2, constants as constants2, readFileSync as
|
|
18330
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync5 } from "node:fs";
|
|
17139
18331
|
import { dirname as dirname4, join as join14, relative as relative5 } from "node:path";
|
|
17140
18332
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
17141
18333
|
function statusWorse(a, b) {
|
|
@@ -17232,7 +18424,7 @@ function findCodeViewerPackageJson() {
|
|
|
17232
18424
|
for (let depth = 0;depth < 8; depth += 1) {
|
|
17233
18425
|
const candidate = join14(cursor, "package.json");
|
|
17234
18426
|
try {
|
|
17235
|
-
const raw =
|
|
18427
|
+
const raw = readFileSync6(candidate, "utf8");
|
|
17236
18428
|
const pkg = JSON.parse(raw);
|
|
17237
18429
|
if (pkg.name === "@youtyan/code-viewer") {
|
|
17238
18430
|
return { version: pkg.version, path: candidate };
|
|
@@ -17439,6 +18631,36 @@ async function checkGit(cwd, signal) {
|
|
|
17439
18631
|
}
|
|
17440
18632
|
return { id: "git", title: "Git", rows };
|
|
17441
18633
|
}
|
|
18634
|
+
async function checkGithubCli(signal) {
|
|
18635
|
+
const versionRes = await runCached(versionCache, TTL.version, commandForExternal("gh"), ["--version"], TIMEOUT.version, signal);
|
|
18636
|
+
if (!versionRes || versionRes.code !== 0) {
|
|
18637
|
+
return {
|
|
18638
|
+
id: "github",
|
|
18639
|
+
title: "GitHub CLI",
|
|
18640
|
+
rows: [
|
|
18641
|
+
{
|
|
18642
|
+
id: "github.gh",
|
|
18643
|
+
title: "gh binary",
|
|
18644
|
+
status: "warn",
|
|
18645
|
+
detail: versionRes && isCommandNotFoundResult("gh", versionRes) ? commandNotFoundDetail("gh") : "gh --version failed",
|
|
18646
|
+
hint: "GitHub issue listing and issue-to-task linking require gh. Install GitHub CLI or pass --bin gh=/absolute/path."
|
|
18647
|
+
}
|
|
18648
|
+
]
|
|
18649
|
+
};
|
|
18650
|
+
}
|
|
18651
|
+
return {
|
|
18652
|
+
id: "github",
|
|
18653
|
+
title: "GitHub CLI",
|
|
18654
|
+
rows: [
|
|
18655
|
+
{
|
|
18656
|
+
id: "github.gh",
|
|
18657
|
+
title: "gh binary",
|
|
18658
|
+
status: "ok",
|
|
18659
|
+
detail: firstLine2(versionRes.stdout)
|
|
18660
|
+
}
|
|
18661
|
+
]
|
|
18662
|
+
};
|
|
18663
|
+
}
|
|
17442
18664
|
async function detectComposeBinary(signal) {
|
|
17443
18665
|
const v2 = await runCached(versionCache, TTL.version, commandForExternal("docker"), ["compose", "version", "--short"], TIMEOUT.version, signal);
|
|
17444
18666
|
if (v2 && v2.code === 0) {
|
|
@@ -18020,6 +19242,7 @@ async function buildDoctorReport(ctx) {
|
|
|
18020
19242
|
const sqlite = await checkSqlite(ctx.cwd);
|
|
18021
19243
|
const snapshot = checkSnapshotStore(ctx.cwd);
|
|
18022
19244
|
const git = await checkGit(ctx.cwd, ctx.signal);
|
|
19245
|
+
const github = await checkGithubCli(ctx.signal);
|
|
18023
19246
|
const discovery = await checkDiscovery(ctx.cwd, ctx.scopeOmitDirNames, ctx.signal);
|
|
18024
19247
|
const docker = await checkDocker(ctx.signal, discovery.dockerResult);
|
|
18025
19248
|
const datastore = await checkDatastoreConnectivity(ctx.cwd, ctx.scopeOmitDirNames, ctx.signal);
|
|
@@ -18030,6 +19253,7 @@ async function buildDoctorReport(ctx) {
|
|
|
18030
19253
|
sqlite,
|
|
18031
19254
|
snapshot,
|
|
18032
19255
|
git,
|
|
19256
|
+
github,
|
|
18033
19257
|
discovery.group,
|
|
18034
19258
|
datastore,
|
|
18035
19259
|
docker,
|
|
@@ -18139,7 +19363,8 @@ function parseDoctorCliArgs(argv) {
|
|
|
18139
19363
|
}
|
|
18140
19364
|
const parsed = parseExternalCommandOverride(next, "--bin", [
|
|
18141
19365
|
"git",
|
|
18142
|
-
"docker"
|
|
19366
|
+
"docker",
|
|
19367
|
+
"gh"
|
|
18143
19368
|
]);
|
|
18144
19369
|
if (parsed.ok === false)
|
|
18145
19370
|
return { kind: "error", message: parsed.error };
|
|
@@ -18225,7 +19450,7 @@ async function runDoctorCli(argv) {
|
|
|
18225
19450
|
const commandConfig = configureExternalCommands({
|
|
18226
19451
|
cwd,
|
|
18227
19452
|
cliOverrides: commandOverrides,
|
|
18228
|
-
allowedNames: ["git", "docker"]
|
|
19453
|
+
allowedNames: ["git", "docker", "gh"]
|
|
18229
19454
|
});
|
|
18230
19455
|
if (commandConfig.ok === false) {
|
|
18231
19456
|
process.stderr.write(`code-viewer doctor: ${commandConfig.error}
|
|
@@ -18251,13 +19476,13 @@ async function runDoctorCli(argv) {
|
|
|
18251
19476
|
var DOCTOR_HELP = `code-viewer doctor — diagnose the current environment
|
|
18252
19477
|
|
|
18253
19478
|
Usage:
|
|
18254
|
-
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker>=<path>]
|
|
19479
|
+
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker|gh>=<path>]
|
|
18255
19480
|
code-viewer doctor agent-help
|
|
18256
19481
|
|
|
18257
19482
|
Options:
|
|
18258
19483
|
--cwd <path> Working directory to inspect (default: process.cwd()).
|
|
18259
19484
|
--port <N> Listening port to mention in the report (default: 0 = no server).
|
|
18260
|
-
--bin <n>=<p> Override git/docker executable path. Repeatable.
|
|
19485
|
+
--bin <n>=<p> Override git/docker/gh executable path. Repeatable.
|
|
18261
19486
|
--json Print the full DoctorReport as JSON instead of a summary.
|
|
18262
19487
|
--help, -h Show this help.
|
|
18263
19488
|
|
|
@@ -18302,65 +19527,702 @@ import { join as join15 } from "node:path";
|
|
|
18302
19527
|
function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
|
|
18303
19528
|
return !!cached && now - cached.storedAt <= ttlMs;
|
|
18304
19529
|
}
|
|
18305
|
-
function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MAX_TIMED_CACHE_ENTRIES) {
|
|
18306
|
-
cache.set(key, { ...value, storedAt: now });
|
|
18307
|
-
while (cache.size > maxEntries) {
|
|
18308
|
-
const oldest = cache.keys().next().value;
|
|
18309
|
-
if (oldest === undefined)
|
|
19530
|
+
function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MAX_TIMED_CACHE_ENTRIES) {
|
|
19531
|
+
cache.set(key, { ...value, storedAt: now });
|
|
19532
|
+
while (cache.size > maxEntries) {
|
|
19533
|
+
const oldest = cache.keys().next().value;
|
|
19534
|
+
if (oldest === undefined)
|
|
19535
|
+
break;
|
|
19536
|
+
cache.delete(oldest);
|
|
19537
|
+
}
|
|
19538
|
+
}
|
|
19539
|
+
function worktreeFileSignature(path, cwd) {
|
|
19540
|
+
try {
|
|
19541
|
+
const stats = lstatSync3(join15(cwd, path));
|
|
19542
|
+
const inode = "ino" in stats ? stats.ino : 0;
|
|
19543
|
+
return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
|
|
19544
|
+
} catch {
|
|
19545
|
+
return "state:missing";
|
|
19546
|
+
}
|
|
19547
|
+
}
|
|
19548
|
+
function fileDiffCacheKey(options) {
|
|
19549
|
+
const worktreeTarget = options.range.from === "worktree" || !options.range.to || options.range.to === "worktree";
|
|
19550
|
+
if (options.isUntracked && !worktreeTarget) {
|
|
19551
|
+
throw new Error("untracked file diffs require a worktree range");
|
|
19552
|
+
}
|
|
19553
|
+
const signature = worktreeTarget ? `\x00${worktreeFileSignature(options.path, options.cwd)}` : "";
|
|
19554
|
+
if (options.isUntracked) {
|
|
19555
|
+
return `u\x00${options.path}${signature}\x00${options.extras.join("\x00")}`;
|
|
19556
|
+
}
|
|
19557
|
+
return `t\x00${options.path}\x00${options.oldPath || ""}${signature}\x00${[...options.extras, ...options.args].join("\x00")}`;
|
|
19558
|
+
}
|
|
19559
|
+
var CACHE_TTL_MS = 1500, MAX_TIMED_CACHE_ENTRIES = 200;
|
|
19560
|
+
var init_cache = () => {};
|
|
19561
|
+
|
|
19562
|
+
// web-src/server/dev-assets.ts
|
|
19563
|
+
import { basename as basename2 } from "node:path";
|
|
19564
|
+
function startDevAssetReload(options) {
|
|
19565
|
+
if (!options.enabled)
|
|
19566
|
+
return false;
|
|
19567
|
+
const watched = new Set(options.watchedFiles);
|
|
19568
|
+
const setTimer = options.setTimeoutFn || setTimeout;
|
|
19569
|
+
const clearTimer = options.clearTimeoutFn || clearTimeout;
|
|
19570
|
+
const debounceMs = options.debounceMs ?? 150;
|
|
19571
|
+
let timer = null;
|
|
19572
|
+
options.watch(options.webRoot, { persistent: false }, (_event, filename) => {
|
|
19573
|
+
if (!filename || !watched.has(basename2(filename.toString())))
|
|
19574
|
+
return;
|
|
19575
|
+
if (timer)
|
|
19576
|
+
clearTimer(timer);
|
|
19577
|
+
timer = setTimer(() => {
|
|
19578
|
+
timer = null;
|
|
19579
|
+
options.sendReload();
|
|
19580
|
+
}, debounceMs);
|
|
19581
|
+
});
|
|
19582
|
+
return true;
|
|
19583
|
+
}
|
|
19584
|
+
var init_dev_assets = () => {};
|
|
19585
|
+
|
|
19586
|
+
// web-src/server/journal.ts
|
|
19587
|
+
import { join as join16 } from "node:path";
|
|
19588
|
+
function dailyJournalFilePath(root) {
|
|
19589
|
+
return join16(root, CODE_VIEWER_DIR, DAILY_JOURNAL_FILE_NAME);
|
|
19590
|
+
}
|
|
19591
|
+
function journalTasksFilePath(root) {
|
|
19592
|
+
return join16(root, CODE_VIEWER_DIR, JOURNAL_TASKS_FILE_NAME);
|
|
19593
|
+
}
|
|
19594
|
+
function emptyDailyJournalState() {
|
|
19595
|
+
return { version: 1, entries: [] };
|
|
19596
|
+
}
|
|
19597
|
+
function emptyJournalTaskState() {
|
|
19598
|
+
return { version: 1, tasks: [] };
|
|
19599
|
+
}
|
|
19600
|
+
function makeJournalId(prefix) {
|
|
19601
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
19602
|
+
const time = Date.now().toString(36);
|
|
19603
|
+
return `${prefix}-${time}${random}`;
|
|
19604
|
+
}
|
|
19605
|
+
function optionalString3(value, maxLen) {
|
|
19606
|
+
if (typeof value !== "string")
|
|
19607
|
+
return;
|
|
19608
|
+
if (value.includes("\x00"))
|
|
19609
|
+
return;
|
|
19610
|
+
const trimmed = value.trim();
|
|
19611
|
+
if (!trimmed)
|
|
19612
|
+
return;
|
|
19613
|
+
return trimmed.slice(0, maxLen);
|
|
19614
|
+
}
|
|
19615
|
+
function optionalBody(value, maxBytes) {
|
|
19616
|
+
if (typeof value !== "string")
|
|
19617
|
+
return;
|
|
19618
|
+
if (value.includes("\x00"))
|
|
19619
|
+
return;
|
|
19620
|
+
if (Buffer.byteLength(value, "utf8") > maxBytes)
|
|
19621
|
+
return;
|
|
19622
|
+
return value;
|
|
19623
|
+
}
|
|
19624
|
+
function normalizeSource(value) {
|
|
19625
|
+
return value === "ai" || value === "imported" ? value : "user";
|
|
19626
|
+
}
|
|
19627
|
+
function normalizeJournalEntry(raw) {
|
|
19628
|
+
if (!raw || typeof raw !== "object")
|
|
19629
|
+
return null;
|
|
19630
|
+
const entry = raw;
|
|
19631
|
+
const id = optionalString3(entry.id, 128);
|
|
19632
|
+
const date = isIsoDate(entry.date) ? entry.date : undefined;
|
|
19633
|
+
const body = optionalBody(entry.body, JOURNAL_ENTRY_BODY_MAX_BYTES);
|
|
19634
|
+
if (!id || !date || body === undefined)
|
|
19635
|
+
return null;
|
|
19636
|
+
const title = optionalString3(entry.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19637
|
+
return {
|
|
19638
|
+
id,
|
|
19639
|
+
date,
|
|
19640
|
+
...title ? { title } : {},
|
|
19641
|
+
body,
|
|
19642
|
+
labels: normalizeJournalLabels(entry.labels),
|
|
19643
|
+
source: normalizeSource(entry.source),
|
|
19644
|
+
created_at: optionalString3(entry.created_at, 64) ?? new Date(0).toISOString(),
|
|
19645
|
+
updated_at: optionalString3(entry.updated_at, 64) ?? new Date(0).toISOString()
|
|
19646
|
+
};
|
|
19647
|
+
}
|
|
19648
|
+
function normalizeDailyJournalState(raw) {
|
|
19649
|
+
if (!raw || typeof raw !== "object")
|
|
19650
|
+
return emptyDailyJournalState();
|
|
19651
|
+
const entriesRaw = raw.entries;
|
|
19652
|
+
if (!Array.isArray(entriesRaw))
|
|
19653
|
+
return emptyDailyJournalState();
|
|
19654
|
+
const entries = [];
|
|
19655
|
+
for (const rawEntry of entriesRaw) {
|
|
19656
|
+
if (entries.length >= MAX_ENTRIES3)
|
|
19657
|
+
break;
|
|
19658
|
+
const entry = normalizeJournalEntry(rawEntry);
|
|
19659
|
+
if (entry)
|
|
19660
|
+
entries.push(entry);
|
|
19661
|
+
}
|
|
19662
|
+
entries.sort((a, b) => a.date.localeCompare(b.date));
|
|
19663
|
+
return { version: 1, entries };
|
|
19664
|
+
}
|
|
19665
|
+
function normalizeTaskNote(raw) {
|
|
19666
|
+
if (!raw || typeof raw !== "object")
|
|
19667
|
+
return null;
|
|
19668
|
+
const note = raw;
|
|
19669
|
+
const id = optionalString3(note.id, 128);
|
|
19670
|
+
const body = optionalBody(note.body, JOURNAL_TASK_NOTE_MAX_BYTES);
|
|
19671
|
+
if (!id || body === undefined)
|
|
19672
|
+
return null;
|
|
19673
|
+
return {
|
|
19674
|
+
id,
|
|
19675
|
+
at: optionalString3(note.at, 64) ?? new Date(0).toISOString(),
|
|
19676
|
+
body,
|
|
19677
|
+
source: normalizeSource(note.source)
|
|
19678
|
+
};
|
|
19679
|
+
}
|
|
19680
|
+
function normalizeTaskClaim(raw) {
|
|
19681
|
+
if (!raw || typeof raw !== "object")
|
|
19682
|
+
return;
|
|
19683
|
+
const claim = raw;
|
|
19684
|
+
const by = optionalString3(claim.by, 128);
|
|
19685
|
+
const claimedAt = optionalString3(claim.claimed_at, 64);
|
|
19686
|
+
const leaseExpiresAt = optionalString3(claim.lease_expires_at, 64);
|
|
19687
|
+
if (!by || !claimedAt || !leaseExpiresAt)
|
|
19688
|
+
return;
|
|
19689
|
+
return {
|
|
19690
|
+
by,
|
|
19691
|
+
claimed_at: claimedAt,
|
|
19692
|
+
lease_expires_at: leaseExpiresAt
|
|
19693
|
+
};
|
|
19694
|
+
}
|
|
19695
|
+
function normalizeJournalTask(raw) {
|
|
19696
|
+
if (!raw || typeof raw !== "object")
|
|
19697
|
+
return null;
|
|
19698
|
+
const task = raw;
|
|
19699
|
+
const id = optionalString3(task.id, 128);
|
|
19700
|
+
const title = optionalString3(task.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19701
|
+
if (!id || !title)
|
|
19702
|
+
return null;
|
|
19703
|
+
const status = isJournalTaskStatus(task.status) ? task.status : "todo";
|
|
19704
|
+
const priority = isJournalTaskPriority(task.priority) ? task.priority : "p2";
|
|
19705
|
+
const body = optionalBody(task.body, JOURNAL_TASK_BODY_MAX_BYTES) ?? "";
|
|
19706
|
+
const dueDate = isIsoDate(task.due_date) ? task.due_date : undefined;
|
|
19707
|
+
const sourceDate = isIsoDate(task.source_date) ? task.source_date : undefined;
|
|
19708
|
+
const journalEntryId = optionalString3(task.journal_entry_id, 128);
|
|
19709
|
+
const completedAt = optionalString3(task.completed_at, 64);
|
|
19710
|
+
const notes = Array.isArray(task.notes) ? task.notes.slice(0, MAX_NOTES_PER_TASK).map(normalizeTaskNote).filter((note) => note !== null) : [];
|
|
19711
|
+
const claim = normalizeTaskClaim(task.claim);
|
|
19712
|
+
return {
|
|
19713
|
+
id,
|
|
19714
|
+
title,
|
|
19715
|
+
body,
|
|
19716
|
+
status,
|
|
19717
|
+
priority,
|
|
19718
|
+
labels: normalizeJournalLabels(task.labels),
|
|
19719
|
+
created_at: optionalString3(task.created_at, 64) ?? new Date(0).toISOString(),
|
|
19720
|
+
updated_at: optionalString3(task.updated_at, 64) ?? new Date(0).toISOString(),
|
|
19721
|
+
...dueDate ? { due_date: dueDate } : {},
|
|
19722
|
+
...sourceDate ? { source_date: sourceDate } : {},
|
|
19723
|
+
...journalEntryId ? { journal_entry_id: journalEntryId } : {},
|
|
19724
|
+
...completedAt ? { completed_at: completedAt } : {},
|
|
19725
|
+
...claim ? { claim } : {},
|
|
19726
|
+
...notes.length ? { notes } : {}
|
|
19727
|
+
};
|
|
19728
|
+
}
|
|
19729
|
+
function normalizeJournalTaskState(raw) {
|
|
19730
|
+
if (!raw || typeof raw !== "object")
|
|
19731
|
+
return emptyJournalTaskState();
|
|
19732
|
+
const tasksRaw = raw.tasks;
|
|
19733
|
+
if (!Array.isArray(tasksRaw))
|
|
19734
|
+
return emptyJournalTaskState();
|
|
19735
|
+
const tasks = [];
|
|
19736
|
+
for (const rawTask of tasksRaw) {
|
|
19737
|
+
if (tasks.length >= MAX_TASKS)
|
|
18310
19738
|
break;
|
|
18311
|
-
|
|
19739
|
+
const task = normalizeJournalTask(rawTask);
|
|
19740
|
+
if (task)
|
|
19741
|
+
tasks.push(task);
|
|
18312
19742
|
}
|
|
19743
|
+
return { version: 1, tasks };
|
|
18313
19744
|
}
|
|
18314
|
-
function
|
|
18315
|
-
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
19745
|
+
async function loadDailyJournalState(root) {
|
|
19746
|
+
return dailyJournalStore.load(root);
|
|
19747
|
+
}
|
|
19748
|
+
async function loadJournalTaskState(root) {
|
|
19749
|
+
return journalTaskStore.load(root);
|
|
19750
|
+
}
|
|
19751
|
+
async function updateDailyJournalState(root, updater) {
|
|
19752
|
+
return dailyJournalStore.update(root, updater);
|
|
19753
|
+
}
|
|
19754
|
+
async function updateJournalTaskState(root, updater) {
|
|
19755
|
+
return journalTaskStore.update(root, updater);
|
|
19756
|
+
}
|
|
19757
|
+
function insertOptionCount2(input) {
|
|
19758
|
+
return (input.before_id ? 1 : 0) + (input.after_id ? 1 : 0) + (input.position !== undefined ? 1 : 0);
|
|
19759
|
+
}
|
|
19760
|
+
function taskInsertIndex(tasks, status, input) {
|
|
19761
|
+
if (insertOptionCount2(input) > 1)
|
|
19762
|
+
return { ok: false, error: "use only one of before, after, or position" };
|
|
19763
|
+
if (input.before_id || input.after_id) {
|
|
19764
|
+
const anchorId = input.before_id || input.after_id || "";
|
|
19765
|
+
const anchorIndex = tasks.findIndex((task) => task.id === anchorId);
|
|
19766
|
+
const anchor = tasks[anchorIndex];
|
|
19767
|
+
if (!anchor)
|
|
19768
|
+
return { ok: false, error: "anchor task not found" };
|
|
19769
|
+
if (anchor.status !== status)
|
|
19770
|
+
return { ok: false, error: "anchor task belongs to another column" };
|
|
19771
|
+
return {
|
|
19772
|
+
ok: true,
|
|
19773
|
+
index: input.before_id ? anchorIndex : anchorIndex + 1
|
|
19774
|
+
};
|
|
18321
19775
|
}
|
|
19776
|
+
const statusIndexes = tasks.map((task, index) => ({ task, index })).filter((item) => item.task.status === status).map((item) => item.index);
|
|
19777
|
+
if (input.position !== undefined) {
|
|
19778
|
+
if (!Number.isInteger(input.position) || input.position < 1)
|
|
19779
|
+
return { ok: false, error: "position must be a positive integer" };
|
|
19780
|
+
if (input.position > statusIndexes.length + 1)
|
|
19781
|
+
return { ok: false, error: "position is out of range" };
|
|
19782
|
+
if (input.position <= statusIndexes.length)
|
|
19783
|
+
return { ok: true, index: statusIndexes[input.position - 1] };
|
|
19784
|
+
}
|
|
19785
|
+
const last = statusIndexes[statusIndexes.length - 1];
|
|
19786
|
+
return { ok: true, index: last === undefined ? tasks.length : last + 1 };
|
|
19787
|
+
}
|
|
19788
|
+
function validateEntryInput(input) {
|
|
19789
|
+
if (!isIsoDate(input.date))
|
|
19790
|
+
return { ok: false, error: "date must be YYYY-MM-DD" };
|
|
19791
|
+
if (optionalBody(input.body, JOURNAL_ENTRY_BODY_MAX_BYTES) === undefined)
|
|
19792
|
+
return { ok: false, error: "body is required or too large" };
|
|
19793
|
+
if (!input.body.trim())
|
|
19794
|
+
return { ok: false, error: "body is required" };
|
|
19795
|
+
return { ok: true };
|
|
18322
19796
|
}
|
|
18323
|
-
function
|
|
18324
|
-
const
|
|
18325
|
-
if (
|
|
18326
|
-
|
|
19797
|
+
function addDailyJournalEntry(state, input, now, makeId3 = makeJournalId) {
|
|
19798
|
+
const valid = validateEntryInput(input);
|
|
19799
|
+
if (valid.ok === false)
|
|
19800
|
+
return valid;
|
|
19801
|
+
const title = optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19802
|
+
const entry = {
|
|
19803
|
+
id: makeId3("j"),
|
|
19804
|
+
date: input.date,
|
|
19805
|
+
...title ? { title } : {},
|
|
19806
|
+
body: input.body,
|
|
19807
|
+
labels: normalizeJournalLabels(input.labels),
|
|
19808
|
+
source: input.source || "user",
|
|
19809
|
+
created_at: now,
|
|
19810
|
+
updated_at: now
|
|
19811
|
+
};
|
|
19812
|
+
return {
|
|
19813
|
+
ok: true,
|
|
19814
|
+
state: { version: 1, entries: [...state.entries, entry] },
|
|
19815
|
+
entry
|
|
19816
|
+
};
|
|
19817
|
+
}
|
|
19818
|
+
function updateDailyJournalEntry(state, id, patch, now) {
|
|
19819
|
+
const entry = state.entries.find((item) => item.id === id);
|
|
19820
|
+
if (!entry)
|
|
19821
|
+
return { ok: false, error: "journal entry not found" };
|
|
19822
|
+
const next = {
|
|
19823
|
+
...entry,
|
|
19824
|
+
updated_at: now
|
|
19825
|
+
};
|
|
19826
|
+
if (patch.date !== undefined) {
|
|
19827
|
+
if (!isIsoDate(patch.date))
|
|
19828
|
+
return { ok: false, error: "date must be YYYY-MM-DD" };
|
|
19829
|
+
next.date = patch.date;
|
|
19830
|
+
}
|
|
19831
|
+
if (patch.title !== undefined) {
|
|
19832
|
+
const title = optionalString3(patch.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19833
|
+
if (title)
|
|
19834
|
+
next.title = title;
|
|
19835
|
+
else
|
|
19836
|
+
delete next.title;
|
|
18327
19837
|
}
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
19838
|
+
if (patch.body !== undefined) {
|
|
19839
|
+
const body = optionalBody(patch.body, JOURNAL_ENTRY_BODY_MAX_BYTES);
|
|
19840
|
+
if (body === undefined || !body.trim())
|
|
19841
|
+
return { ok: false, error: "body is required or too large" };
|
|
19842
|
+
next.body = body;
|
|
19843
|
+
}
|
|
19844
|
+
if (patch.labels !== undefined)
|
|
19845
|
+
next.labels = normalizeJournalLabels(patch.labels);
|
|
19846
|
+
if (patch.source !== undefined)
|
|
19847
|
+
next.source = patch.source;
|
|
19848
|
+
return {
|
|
19849
|
+
ok: true,
|
|
19850
|
+
state: {
|
|
19851
|
+
version: 1,
|
|
19852
|
+
entries: state.entries.map((item) => item.id === id ? next : item)
|
|
19853
|
+
},
|
|
19854
|
+
entry: next
|
|
19855
|
+
};
|
|
19856
|
+
}
|
|
19857
|
+
function deleteDailyJournalEntry(state, id) {
|
|
19858
|
+
const entries = state.entries.filter((entry) => entry.id !== id);
|
|
19859
|
+
return {
|
|
19860
|
+
state: { version: 1, entries },
|
|
19861
|
+
removed: entries.length !== state.entries.length
|
|
19862
|
+
};
|
|
19863
|
+
}
|
|
19864
|
+
function validateTaskInput(input) {
|
|
19865
|
+
if (!optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS))
|
|
19866
|
+
return { ok: false, error: "title is required" };
|
|
19867
|
+
if (input.body !== undefined && optionalBody(input.body, JOURNAL_TASK_BODY_MAX_BYTES) === undefined)
|
|
19868
|
+
return { ok: false, error: "body is too large" };
|
|
19869
|
+
if (input.due_date !== undefined && !isIsoDate(input.due_date))
|
|
19870
|
+
return { ok: false, error: "due date must be YYYY-MM-DD" };
|
|
19871
|
+
if (input.source_date !== undefined && !isIsoDate(input.source_date))
|
|
19872
|
+
return { ok: false, error: "source date must be YYYY-MM-DD" };
|
|
19873
|
+
return { ok: true };
|
|
19874
|
+
}
|
|
19875
|
+
function completedAtForStatus(status, previous, now) {
|
|
19876
|
+
return status === "done" ? previous || now : undefined;
|
|
19877
|
+
}
|
|
19878
|
+
function githubIssueTaskBody(issueNumber, issueUrl, memoLabel) {
|
|
19879
|
+
return [
|
|
19880
|
+
`GitHub issue #${issueNumber}`,
|
|
19881
|
+
...issueUrl ? [issueUrl] : [],
|
|
19882
|
+
"",
|
|
19883
|
+
memoLabel || "Memo:"
|
|
19884
|
+
].join(`
|
|
19885
|
+
`);
|
|
19886
|
+
}
|
|
19887
|
+
function githubIssueRequiredLabels(issueNumber, repo, labels) {
|
|
19888
|
+
const repoLabel = journalIssueRepoLabel(repo);
|
|
19889
|
+
return normalizeJournalLabels([
|
|
19890
|
+
"github",
|
|
19891
|
+
journalIssueLabel(issueNumber),
|
|
19892
|
+
...repoLabel ? [repoLabel] : [],
|
|
19893
|
+
...Array.isArray(labels) ? labels : []
|
|
19894
|
+
]);
|
|
19895
|
+
}
|
|
19896
|
+
function taskHasPlacement(input, currentStatus) {
|
|
19897
|
+
return !!input.status && input.status !== currentStatus || !!input.before_id || !!input.after_id || input.position !== undefined;
|
|
19898
|
+
}
|
|
19899
|
+
function addJournalTask(state, input, now, makeId3 = makeJournalId) {
|
|
19900
|
+
const valid = validateTaskInput(input);
|
|
19901
|
+
if (valid.ok === false)
|
|
19902
|
+
return valid;
|
|
19903
|
+
const anchorId = input.before_id || input.after_id;
|
|
19904
|
+
const anchor = anchorId ? state.tasks.find((task2) => task2.id === anchorId) : undefined;
|
|
19905
|
+
if (anchorId && !anchor)
|
|
19906
|
+
return { ok: false, error: "anchor task not found" };
|
|
19907
|
+
const status = input.status || anchor?.status || "todo";
|
|
19908
|
+
const task = {
|
|
19909
|
+
id: makeId3("t"),
|
|
19910
|
+
title: optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS) || "Untitled task",
|
|
19911
|
+
body: input.body || "",
|
|
19912
|
+
status,
|
|
19913
|
+
priority: input.priority || "p2",
|
|
19914
|
+
labels: normalizeJournalLabels(input.labels),
|
|
19915
|
+
created_at: now,
|
|
19916
|
+
updated_at: now,
|
|
19917
|
+
...input.due_date ? { due_date: input.due_date } : {},
|
|
19918
|
+
...input.source_date ? { source_date: input.source_date } : {},
|
|
19919
|
+
...input.journal_entry_id ? { journal_entry_id: input.journal_entry_id } : {},
|
|
19920
|
+
...completedAtForStatus(status, undefined, now) ? { completed_at: now } : {}
|
|
19921
|
+
};
|
|
19922
|
+
const insertAt = taskInsertIndex(state.tasks, status, input);
|
|
19923
|
+
if (insertAt.ok === false)
|
|
19924
|
+
return insertAt;
|
|
19925
|
+
const tasks = [...state.tasks];
|
|
19926
|
+
tasks.splice(insertAt.index, 0, task);
|
|
19927
|
+
return { ok: true, state: { version: 1, tasks }, task };
|
|
19928
|
+
}
|
|
19929
|
+
function updateJournalTask(state, id, patch, now) {
|
|
19930
|
+
const task = state.tasks.find((item) => item.id === id);
|
|
19931
|
+
if (!task)
|
|
19932
|
+
return { ok: false, error: "task not found" };
|
|
19933
|
+
const next = { ...task, updated_at: now };
|
|
19934
|
+
if (patch.title !== undefined) {
|
|
19935
|
+
const title = optionalString3(patch.title, JOURNAL_TITLE_MAX_CHARS);
|
|
19936
|
+
if (!title)
|
|
19937
|
+
return { ok: false, error: "title is required" };
|
|
19938
|
+
next.title = title;
|
|
18331
19939
|
}
|
|
18332
|
-
|
|
19940
|
+
if (patch.body !== undefined) {
|
|
19941
|
+
const body = optionalBody(patch.body, JOURNAL_TASK_BODY_MAX_BYTES);
|
|
19942
|
+
if (body === undefined)
|
|
19943
|
+
return { ok: false, error: "body is too large" };
|
|
19944
|
+
next.body = body;
|
|
19945
|
+
}
|
|
19946
|
+
if (patch.status !== undefined) {
|
|
19947
|
+
next.status = patch.status;
|
|
19948
|
+
const completedAt = completedAtForStatus(patch.status, next.completed_at, now);
|
|
19949
|
+
if (completedAt)
|
|
19950
|
+
next.completed_at = completedAt;
|
|
19951
|
+
else
|
|
19952
|
+
delete next.completed_at;
|
|
19953
|
+
if (patch.status !== "doing")
|
|
19954
|
+
delete next.claim;
|
|
19955
|
+
}
|
|
19956
|
+
if (patch.priority !== undefined)
|
|
19957
|
+
next.priority = patch.priority;
|
|
19958
|
+
if (patch.labels !== undefined)
|
|
19959
|
+
next.labels = normalizeJournalLabels(patch.labels);
|
|
19960
|
+
if (patch.due_date !== undefined) {
|
|
19961
|
+
if (patch.due_date === null || patch.due_date === "")
|
|
19962
|
+
delete next.due_date;
|
|
19963
|
+
else if (isIsoDate(patch.due_date))
|
|
19964
|
+
next.due_date = patch.due_date;
|
|
19965
|
+
else
|
|
19966
|
+
return { ok: false, error: "due date must be YYYY-MM-DD" };
|
|
19967
|
+
}
|
|
19968
|
+
if (patch.source_date !== undefined) {
|
|
19969
|
+
if (patch.source_date === null || patch.source_date === "")
|
|
19970
|
+
delete next.source_date;
|
|
19971
|
+
else if (isIsoDate(patch.source_date))
|
|
19972
|
+
next.source_date = patch.source_date;
|
|
19973
|
+
else
|
|
19974
|
+
return { ok: false, error: "source date must be YYYY-MM-DD" };
|
|
19975
|
+
}
|
|
19976
|
+
if (patch.journal_entry_id !== undefined) {
|
|
19977
|
+
const journalEntryId = optionalString3(patch.journal_entry_id, 128);
|
|
19978
|
+
if (journalEntryId)
|
|
19979
|
+
next.journal_entry_id = journalEntryId;
|
|
19980
|
+
else
|
|
19981
|
+
delete next.journal_entry_id;
|
|
19982
|
+
}
|
|
19983
|
+
return {
|
|
19984
|
+
ok: true,
|
|
19985
|
+
state: {
|
|
19986
|
+
version: 1,
|
|
19987
|
+
tasks: state.tasks.map((item) => item.id === id ? next : item)
|
|
19988
|
+
},
|
|
19989
|
+
task: next
|
|
19990
|
+
};
|
|
18333
19991
|
}
|
|
18334
|
-
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
const
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
const
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18349
|
-
|
|
18350
|
-
|
|
18351
|
-
|
|
18352
|
-
|
|
18353
|
-
|
|
18354
|
-
|
|
18355
|
-
|
|
19992
|
+
function moveJournalTask(state, id, input, now) {
|
|
19993
|
+
const source = state.tasks.find((task) => task.id === id);
|
|
19994
|
+
if (!source)
|
|
19995
|
+
return { ok: false, error: "task not found" };
|
|
19996
|
+
if (input.before_id === id || input.after_id === id)
|
|
19997
|
+
return { ok: false, error: "cannot move task relative to itself" };
|
|
19998
|
+
const tasksWithoutSource = state.tasks.filter((task) => task.id !== id);
|
|
19999
|
+
const anchorId = input.before_id || input.after_id;
|
|
20000
|
+
const anchor = anchorId ? tasksWithoutSource.find((task) => task.id === anchorId) : undefined;
|
|
20001
|
+
if (anchorId && !anchor)
|
|
20002
|
+
return { ok: false, error: "anchor task not found" };
|
|
20003
|
+
const status = input.status || anchor?.status || source.status;
|
|
20004
|
+
const moved = {
|
|
20005
|
+
...source,
|
|
20006
|
+
status,
|
|
20007
|
+
updated_at: now
|
|
20008
|
+
};
|
|
20009
|
+
const completedAt = completedAtForStatus(status, moved.completed_at, now);
|
|
20010
|
+
if (completedAt)
|
|
20011
|
+
moved.completed_at = completedAt;
|
|
20012
|
+
else
|
|
20013
|
+
delete moved.completed_at;
|
|
20014
|
+
if (status !== "doing")
|
|
20015
|
+
delete moved.claim;
|
|
20016
|
+
const insertAt = taskInsertIndex(tasksWithoutSource, status, input);
|
|
20017
|
+
if (insertAt.ok === false)
|
|
20018
|
+
return insertAt;
|
|
20019
|
+
const tasks = [...tasksWithoutSource];
|
|
20020
|
+
tasks.splice(insertAt.index, 0, moved);
|
|
20021
|
+
return { ok: true, state: { version: 1, tasks }, task: moved };
|
|
20022
|
+
}
|
|
20023
|
+
function linkGithubIssueTask(state, input, now, makeId3 = makeJournalId) {
|
|
20024
|
+
if (!Number.isInteger(input.issue_number) || input.issue_number < 1) {
|
|
20025
|
+
return { ok: false, error: "issue number must be a positive integer" };
|
|
20026
|
+
}
|
|
20027
|
+
const title = optionalString3(input.title, JOURNAL_TITLE_MAX_CHARS) || `GitHub issue #${input.issue_number}`;
|
|
20028
|
+
const repo = optionalString3(input.repo, 120);
|
|
20029
|
+
const issueUrl = optionalString3(input.url, 240);
|
|
20030
|
+
const memoLabel = optionalString3(input.memo_label, 80);
|
|
20031
|
+
const requiredLabels = githubIssueRequiredLabels(input.issue_number, repo, input.labels);
|
|
20032
|
+
const linkLabel = journalIssueLabel(input.issue_number);
|
|
20033
|
+
const repoLabel = journalIssueRepoLabel(repo);
|
|
20034
|
+
const linked = state.tasks.find((task2) => {
|
|
20035
|
+
if (!task2.labels.includes("github") || !task2.labels.includes(linkLabel)) {
|
|
20036
|
+
return false;
|
|
20037
|
+
}
|
|
20038
|
+
if (repoLabel)
|
|
20039
|
+
return task2.labels.includes(repoLabel);
|
|
20040
|
+
return !task2.labels.some((label) => label.startsWith("repo-"));
|
|
18356
20041
|
});
|
|
18357
|
-
|
|
20042
|
+
if (!linked) {
|
|
20043
|
+
const hasAnchor = !!input.before_id || !!input.after_id;
|
|
20044
|
+
const result = addJournalTask(state, {
|
|
20045
|
+
title,
|
|
20046
|
+
body: githubIssueTaskBody(input.issue_number, issueUrl, memoLabel),
|
|
20047
|
+
status: input.status || (hasAnchor ? undefined : "draft"),
|
|
20048
|
+
priority: input.priority || "p2",
|
|
20049
|
+
labels: requiredLabels,
|
|
20050
|
+
before_id: input.before_id,
|
|
20051
|
+
after_id: input.after_id,
|
|
20052
|
+
position: input.position
|
|
20053
|
+
}, now, makeId3);
|
|
20054
|
+
if (result.ok === false)
|
|
20055
|
+
return result;
|
|
20056
|
+
return {
|
|
20057
|
+
ok: true,
|
|
20058
|
+
state: result.state,
|
|
20059
|
+
task: result.task,
|
|
20060
|
+
created: true,
|
|
20061
|
+
moved: false
|
|
20062
|
+
};
|
|
20063
|
+
}
|
|
20064
|
+
let nextState = state;
|
|
20065
|
+
let task = linked;
|
|
20066
|
+
let moved = false;
|
|
20067
|
+
const placement = {
|
|
20068
|
+
status: input.status,
|
|
20069
|
+
before_id: input.before_id,
|
|
20070
|
+
after_id: input.after_id,
|
|
20071
|
+
position: input.position
|
|
20072
|
+
};
|
|
20073
|
+
if (taskHasPlacement(placement, task.status)) {
|
|
20074
|
+
const result = moveJournalTask(nextState, task.id, placement, now);
|
|
20075
|
+
if (result.ok === false)
|
|
20076
|
+
return result;
|
|
20077
|
+
nextState = result.state;
|
|
20078
|
+
task = result.task;
|
|
20079
|
+
moved = true;
|
|
20080
|
+
}
|
|
20081
|
+
const labels = normalizeJournalLabels([...task.labels, ...requiredLabels]);
|
|
20082
|
+
const shouldUpdateLabels = labels.length !== task.labels.length || labels.some((label, index) => label !== task.labels[index]);
|
|
20083
|
+
if (input.priority || shouldUpdateLabels) {
|
|
20084
|
+
const result = updateJournalTask(nextState, task.id, {
|
|
20085
|
+
...input.priority ? { priority: input.priority } : {},
|
|
20086
|
+
...shouldUpdateLabels ? { labels } : {}
|
|
20087
|
+
}, now);
|
|
20088
|
+
if (result.ok === false)
|
|
20089
|
+
return result;
|
|
20090
|
+
nextState = result.state;
|
|
20091
|
+
task = result.task;
|
|
20092
|
+
}
|
|
20093
|
+
return { ok: true, state: nextState, task, created: false, moved };
|
|
20094
|
+
}
|
|
20095
|
+
function claimJournalTask(state, id, input, now) {
|
|
20096
|
+
const task = state.tasks.find((item) => item.id === id);
|
|
20097
|
+
if (!task)
|
|
20098
|
+
return { ok: false, error: "task not found" };
|
|
20099
|
+
const nowMs = Date.parse(now);
|
|
20100
|
+
const activeClaim = task.claim && Number.isFinite(Date.parse(task.claim.lease_expires_at)) && Date.parse(task.claim.lease_expires_at) > nowMs;
|
|
20101
|
+
if (activeClaim)
|
|
20102
|
+
return { ok: false, error: "task is already claimed" };
|
|
20103
|
+
if (task.status !== "todo" && task.status !== "doing")
|
|
20104
|
+
return {
|
|
20105
|
+
ok: false,
|
|
20106
|
+
error: "only todo or expired doing tasks can be claimed"
|
|
20107
|
+
};
|
|
20108
|
+
const by = optionalString3(input.by, 128) || "ai";
|
|
20109
|
+
const wipLimit = input.wip_limit;
|
|
20110
|
+
if (wipLimit !== undefined && wipLimit > 0) {
|
|
20111
|
+
const activeDoing = state.tasks.filter((item) => {
|
|
20112
|
+
if (item.status !== "doing" || !item.claim)
|
|
20113
|
+
return false;
|
|
20114
|
+
if (item.claim.by !== by)
|
|
20115
|
+
return false;
|
|
20116
|
+
const expires = Date.parse(item.claim.lease_expires_at);
|
|
20117
|
+
return Number.isFinite(expires) && expires > nowMs;
|
|
20118
|
+
}).length;
|
|
20119
|
+
if (activeDoing >= wipLimit)
|
|
20120
|
+
return { ok: false, error: "WIP limit reached" };
|
|
20121
|
+
}
|
|
20122
|
+
const leaseMinutes = Number.isFinite(input.lease_minutes) && input.lease_minutes ? Math.min(1440, Math.max(1, Math.round(input.lease_minutes))) : 120;
|
|
20123
|
+
const leaseExpiresAt = new Date(nowMs + leaseMinutes * 60000).toISOString();
|
|
20124
|
+
const next = {
|
|
20125
|
+
...task,
|
|
20126
|
+
status: "doing",
|
|
20127
|
+
updated_at: now,
|
|
20128
|
+
claim: {
|
|
20129
|
+
by,
|
|
20130
|
+
claimed_at: now,
|
|
20131
|
+
lease_expires_at: leaseExpiresAt
|
|
20132
|
+
}
|
|
20133
|
+
};
|
|
20134
|
+
return {
|
|
20135
|
+
ok: true,
|
|
20136
|
+
state: {
|
|
20137
|
+
version: 1,
|
|
20138
|
+
tasks: state.tasks.map((item) => item.id === id ? next : item)
|
|
20139
|
+
},
|
|
20140
|
+
task: next
|
|
20141
|
+
};
|
|
18358
20142
|
}
|
|
18359
|
-
|
|
20143
|
+
function completeJournalTask(state, id, input, now, makeId3 = makeJournalId) {
|
|
20144
|
+
const task = state.tasks.find((item) => item.id === id);
|
|
20145
|
+
if (!task)
|
|
20146
|
+
return { ok: false, error: "task not found" };
|
|
20147
|
+
if (task.status !== "doing")
|
|
20148
|
+
return { ok: false, error: "only doing tasks can be completed" };
|
|
20149
|
+
const nowMs = Date.parse(now);
|
|
20150
|
+
const activeClaim = task.claim && Number.isFinite(Date.parse(task.claim.lease_expires_at)) && Date.parse(task.claim.lease_expires_at) > nowMs;
|
|
20151
|
+
if (!activeClaim)
|
|
20152
|
+
return { ok: false, error: "task must be claimed before completion" };
|
|
20153
|
+
const by = optionalString3(input.by, 128);
|
|
20154
|
+
if (!by)
|
|
20155
|
+
return { ok: false, error: "task completion requires claim owner" };
|
|
20156
|
+
if (task.claim?.by !== by)
|
|
20157
|
+
return { ok: false, error: "task claim belongs to another agent" };
|
|
20158
|
+
const notes = [...task.notes || []];
|
|
20159
|
+
if (input.note?.trim()) {
|
|
20160
|
+
const body = optionalBody(input.note, JOURNAL_TASK_NOTE_MAX_BYTES);
|
|
20161
|
+
if (body === undefined)
|
|
20162
|
+
return { ok: false, error: "note is too large" };
|
|
20163
|
+
notes.push({
|
|
20164
|
+
id: makeId3("n"),
|
|
20165
|
+
at: now,
|
|
20166
|
+
body,
|
|
20167
|
+
source: input.source || "ai"
|
|
20168
|
+
});
|
|
20169
|
+
if (notes.length > MAX_NOTES_PER_TASK) {
|
|
20170
|
+
notes.splice(0, notes.length - MAX_NOTES_PER_TASK);
|
|
20171
|
+
}
|
|
20172
|
+
}
|
|
20173
|
+
const next = {
|
|
20174
|
+
...task,
|
|
20175
|
+
status: "done",
|
|
20176
|
+
updated_at: now,
|
|
20177
|
+
completed_at: now,
|
|
20178
|
+
...notes.length ? { notes } : {}
|
|
20179
|
+
};
|
|
20180
|
+
delete next.claim;
|
|
20181
|
+
return {
|
|
20182
|
+
ok: true,
|
|
20183
|
+
state: {
|
|
20184
|
+
version: 1,
|
|
20185
|
+
tasks: state.tasks.map((item) => item.id === id ? next : item)
|
|
20186
|
+
},
|
|
20187
|
+
task: next
|
|
20188
|
+
};
|
|
20189
|
+
}
|
|
20190
|
+
function deleteJournalTask(state, id) {
|
|
20191
|
+
const tasks = state.tasks.filter((task) => task.id !== id);
|
|
20192
|
+
return {
|
|
20193
|
+
state: { version: 1, tasks },
|
|
20194
|
+
removed: tasks.length !== state.tasks.length
|
|
20195
|
+
};
|
|
20196
|
+
}
|
|
20197
|
+
var DAILY_JOURNAL_FILE_NAME = "daily-journal.json", JOURNAL_TASKS_FILE_NAME = "tasks.json", JOURNAL_ENTRY_BODY_MAX_BYTES, JOURNAL_TASK_BODY_MAX_BYTES, JOURNAL_TASK_NOTE_MAX_BYTES, JOURNAL_TITLE_MAX_CHARS = 200, MAX_DAILY_JOURNAL_JSON_BYTES = 8000000, MAX_JOURNAL_TASKS_JSON_BYTES = 8000000, MAX_ENTRIES3 = 2000, MAX_TASKS = 2000, MAX_NOTES_PER_TASK = 100, dailyJournalStore, journalTaskStore;
|
|
20198
|
+
var init_journal2 = __esm(() => {
|
|
20199
|
+
init_journal();
|
|
20200
|
+
init_annotations();
|
|
20201
|
+
init_json_store();
|
|
20202
|
+
JOURNAL_ENTRY_BODY_MAX_BYTES = 128 * 1024;
|
|
20203
|
+
JOURNAL_TASK_BODY_MAX_BYTES = 128 * 1024;
|
|
20204
|
+
JOURNAL_TASK_NOTE_MAX_BYTES = 64 * 1024;
|
|
20205
|
+
dailyJournalStore = createJsonFileStore({
|
|
20206
|
+
filePath: dailyJournalFilePath,
|
|
20207
|
+
empty: emptyDailyJournalState,
|
|
20208
|
+
sanitize: normalizeDailyJournalState,
|
|
20209
|
+
maxBytes: MAX_DAILY_JOURNAL_JSON_BYTES,
|
|
20210
|
+
backupSuffix: "corrupt",
|
|
20211
|
+
sizeErrorMessage: "daily journal state too large"
|
|
20212
|
+
});
|
|
20213
|
+
journalTaskStore = createJsonFileStore({
|
|
20214
|
+
filePath: journalTasksFilePath,
|
|
20215
|
+
empty: emptyJournalTaskState,
|
|
20216
|
+
sanitize: normalizeJournalTaskState,
|
|
20217
|
+
maxBytes: MAX_JOURNAL_TASKS_JSON_BYTES,
|
|
20218
|
+
backupSuffix: "corrupt",
|
|
20219
|
+
sizeErrorMessage: "journal task state too large"
|
|
20220
|
+
});
|
|
20221
|
+
});
|
|
18360
20222
|
|
|
18361
20223
|
// web-src/server/search-service.ts
|
|
18362
|
-
import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as
|
|
18363
|
-
import { join as
|
|
20224
|
+
import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync7, realpathSync as realpathSync5 } from "node:fs";
|
|
20225
|
+
import { join as join17, relative as relative6 } from "node:path";
|
|
18364
20226
|
function rgAvailable(cwd) {
|
|
18365
20227
|
if (rgAvailableCache !== null)
|
|
18366
20228
|
return rgAvailableCache;
|
|
@@ -18381,7 +20243,7 @@ function safeWorktreePath(env, path) {
|
|
|
18381
20243
|
return null;
|
|
18382
20244
|
if (isGitInternalPath(path))
|
|
18383
20245
|
return null;
|
|
18384
|
-
const full =
|
|
20246
|
+
const full = join17(env.cwd, path);
|
|
18385
20247
|
if (!existsSync7(full))
|
|
18386
20248
|
return null;
|
|
18387
20249
|
let realCwd;
|
|
@@ -18427,7 +20289,7 @@ function grepWorktreeFallback(env, query, max, paths) {
|
|
|
18427
20289
|
continue;
|
|
18428
20290
|
let data;
|
|
18429
20291
|
try {
|
|
18430
|
-
data =
|
|
20292
|
+
data = readFileSync7(full);
|
|
18431
20293
|
} catch {
|
|
18432
20294
|
continue;
|
|
18433
20295
|
}
|
|
@@ -18560,8 +20422,8 @@ var init_search_service = __esm(() => {
|
|
|
18560
20422
|
});
|
|
18561
20423
|
|
|
18562
20424
|
// web-src/server/mcp.ts
|
|
18563
|
-
import { readFileSync as
|
|
18564
|
-
import { join as
|
|
20425
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
20426
|
+
import { join as join18 } from "node:path";
|
|
18565
20427
|
function defaultMcpTools(options = {}) {
|
|
18566
20428
|
return [
|
|
18567
20429
|
{
|
|
@@ -20035,7 +21897,7 @@ var init_mcp = __esm(() => {
|
|
|
20035
21897
|
init_search_cli();
|
|
20036
21898
|
init_search_service();
|
|
20037
21899
|
init_status_cli();
|
|
20038
|
-
PACKAGE_VERSION = JSON.parse(
|
|
21900
|
+
PACKAGE_VERSION = JSON.parse(readFileSync8(join18(ROOT, "package.json"), "utf8")).version;
|
|
20039
21901
|
MCP_SERVER_INFO = {
|
|
20040
21902
|
name: "code-viewer",
|
|
20041
21903
|
title: "code-viewer",
|
|
@@ -20122,7 +21984,7 @@ import {
|
|
|
20122
21984
|
lstatSync as lstatSync5,
|
|
20123
21985
|
mkdirSync as mkdirSync4,
|
|
20124
21986
|
openSync as openSync2,
|
|
20125
|
-
readFileSync as
|
|
21987
|
+
readFileSync as readFileSync9,
|
|
20126
21988
|
realpathSync as realpathSync6,
|
|
20127
21989
|
renameSync,
|
|
20128
21990
|
statSync as statSync6,
|
|
@@ -20131,7 +21993,7 @@ import {
|
|
|
20131
21993
|
writeFileSync as writeFileSync2
|
|
20132
21994
|
} from "node:fs";
|
|
20133
21995
|
import { homedir as homedir3 } from "node:os";
|
|
20134
|
-
import { basename as basename3, dirname as dirname5, extname as extname2, join as
|
|
21996
|
+
import { basename as basename3, dirname as dirname5, extname as extname2, join as join19, relative as relative7 } from "node:path";
|
|
20135
21997
|
function parseCli() {
|
|
20136
21998
|
const rest = [];
|
|
20137
21999
|
for (let i = 2;i < process.argv.length; i++) {
|
|
@@ -20143,17 +22005,18 @@ Usage:
|
|
|
20143
22005
|
code-viewer [--cwd <repo>] [--port <port>] [--open] [--bin <name>=<path>] [git-diff-args...]
|
|
20144
22006
|
code-viewer status [--cwd <repo>] [--bin git=<path>] [--ref <ref>] [--limit <N>] [--json]
|
|
20145
22007
|
code-viewer annotate <start|add|add-db|rename|edit|move|list|delete|clear> [options]
|
|
22008
|
+
code-viewer journal <list|add|edit|tasks|task-add|task-update|task-next|github-issues|task-link-issue|task-claim|task-done|task-delete> [options]
|
|
20146
22009
|
code-viewer query <sources|schemas|schema|columns|ddl|exec|list|clear|snapshot|diff|search|redis|elasticsearch|s3> [options] [--bin git=<path>]
|
|
20147
22010
|
code-viewer search code --term <text> [--ref <ref>] [--path <p>...] [--regex] [--max <n>] [--json] [--bin git=<path>]
|
|
20148
22011
|
code-viewer search files --term <pattern> [--ref <ref>] [--max <n>] [--json] [--bin git=<path>]
|
|
20149
22012
|
code-viewer file <blame|history|show|diff> --path <p> [--ref <ref>] [...subcommand options] [--json] [--bin git=<path>]
|
|
20150
22013
|
code-viewer skill install [--agent <list>] [--global]
|
|
20151
|
-
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker>=<path>]
|
|
22014
|
+
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker|gh>=<path>]
|
|
20152
22015
|
code-viewer agent-help
|
|
20153
22016
|
code-viewer help
|
|
20154
22017
|
|
|
20155
22018
|
AI-agent index (start here): code-viewer agent-help
|
|
20156
|
-
Subcommand guides (AI agents): code-viewer <status|annotate|query|search|file|skill|doctor> agent-help
|
|
22019
|
+
Subcommand guides (AI agents): code-viewer <status|annotate|journal|query|search|file|skill|doctor> agent-help
|
|
20157
22020
|
|
|
20158
22021
|
Examples:
|
|
20159
22022
|
code-viewer --open
|
|
@@ -20251,7 +22114,7 @@ Examples:
|
|
|
20251
22114
|
}
|
|
20252
22115
|
function warnIfLegacyConfigPresent() {
|
|
20253
22116
|
try {
|
|
20254
|
-
if (existsSync8(
|
|
22117
|
+
if (existsSync8(join19(cwd, ".code-viewer.json"))) {
|
|
20255
22118
|
console.warn("[code-viewer] .code-viewer.json is no longer used; configure scope and upload from Viewer Settings instead. The file can be safely removed.");
|
|
20256
22119
|
}
|
|
20257
22120
|
} catch {}
|
|
@@ -20358,10 +22221,10 @@ function staticFile(pathname) {
|
|
|
20358
22221
|
const spec = map[pathname];
|
|
20359
22222
|
if (!spec)
|
|
20360
22223
|
return null;
|
|
20361
|
-
const full =
|
|
22224
|
+
const full = join19(WEB_ROOT, spec[0]);
|
|
20362
22225
|
if (!existsSync8(full))
|
|
20363
22226
|
return text("not found", 404);
|
|
20364
|
-
return new Response(
|
|
22227
|
+
return new Response(readFileSync9(full), {
|
|
20365
22228
|
headers: { "Content-Type": spec[1], "Cache-Control": "no-store" }
|
|
20366
22229
|
});
|
|
20367
22230
|
}
|
|
@@ -20613,7 +22476,7 @@ function safeWorktreePath2(path) {
|
|
|
20613
22476
|
return safeWorktreePath(currentSearchEnv(), path);
|
|
20614
22477
|
}
|
|
20615
22478
|
function worktreePath(path) {
|
|
20616
|
-
return
|
|
22479
|
+
return join19(cwd, path);
|
|
20617
22480
|
}
|
|
20618
22481
|
function safeOpenWorktreePath(path) {
|
|
20619
22482
|
if (path === "") {
|
|
@@ -20698,7 +22561,7 @@ function readReadme(target, dirPath) {
|
|
|
20698
22561
|
if (!full)
|
|
20699
22562
|
continue;
|
|
20700
22563
|
try {
|
|
20701
|
-
return { path, text:
|
|
22564
|
+
return { path, text: readFileSync9(full, "utf8") };
|
|
20702
22565
|
} catch {
|
|
20703
22566
|
continue;
|
|
20704
22567
|
}
|
|
@@ -20884,7 +22747,7 @@ function handleLog(url) {
|
|
|
20884
22747
|
}
|
|
20885
22748
|
function blamePathKey(p) {
|
|
20886
22749
|
try {
|
|
20887
|
-
const st = statSync6(
|
|
22750
|
+
const st = statSync6(join19(cwd, p));
|
|
20888
22751
|
return `${st.mtimeMs}:${st.size}`;
|
|
20889
22752
|
} catch {
|
|
20890
22753
|
return "missing";
|
|
@@ -21378,7 +23241,7 @@ async function handleUploadFiles(req) {
|
|
|
21378
23241
|
total += file.size;
|
|
21379
23242
|
if (total > MAX_UPLOAD_TOTAL_BYTES)
|
|
21380
23243
|
return text("upload too large", 413);
|
|
21381
|
-
const target =
|
|
23244
|
+
const target = join19(realDir, safeName);
|
|
21382
23245
|
if (relative7(realDir, dirname5(target)) !== "")
|
|
21383
23246
|
return text("invalid filename", 400);
|
|
21384
23247
|
if (existsSync8(target))
|
|
@@ -21500,9 +23363,9 @@ function triggerUpdate(changedPaths) {
|
|
|
21500
23363
|
sendSse("update", data);
|
|
21501
23364
|
}
|
|
21502
23365
|
function moveMacPathIntoTrash(path) {
|
|
21503
|
-
const trashDir =
|
|
23366
|
+
const trashDir = join19(homedir3(), ".Trash");
|
|
21504
23367
|
const base = basename3(path) || "code-viewer-trash-item";
|
|
21505
|
-
const target =
|
|
23368
|
+
const target = join19(trashDir, `${base}-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
|
|
21506
23369
|
try {
|
|
21507
23370
|
mkdirSync4(trashDir, { recursive: true });
|
|
21508
23371
|
renameSync(path, target);
|
|
@@ -21544,7 +23407,7 @@ function restoreTrashPath(originalPath, trashPath) {
|
|
|
21544
23407
|
if (!existsSync8(trashPath))
|
|
21545
23408
|
return { ok: false, error: "trash item not found" };
|
|
21546
23409
|
try {
|
|
21547
|
-
const trashRoot =
|
|
23410
|
+
const trashRoot = join19(homedir3(), ".Trash");
|
|
21548
23411
|
const trashRelative = relative7(trashRoot, trashPath);
|
|
21549
23412
|
if (trashRelative === "" || trashRelative.startsWith("..") || trashRelative.startsWith("/") || trashRelative.startsWith("\\"))
|
|
21550
23413
|
return { ok: false, error: "invalid trash handle" };
|
|
@@ -21694,7 +23557,7 @@ async function handleCreateDirectory(req) {
|
|
|
21694
23557
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
21695
23558
|
if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
|
|
21696
23559
|
return text("invalid target", 400);
|
|
21697
|
-
const target =
|
|
23560
|
+
const target = join19(parent, name);
|
|
21698
23561
|
if (existsSync8(target))
|
|
21699
23562
|
return text("already exists", 409);
|
|
21700
23563
|
try {
|
|
@@ -21779,6 +23642,282 @@ async function handleMcp(req) {
|
|
|
21779
23642
|
}
|
|
21780
23643
|
return json2(dispatched.body);
|
|
21781
23644
|
}
|
|
23645
|
+
function journalSse(kind, id) {
|
|
23646
|
+
sendSse("journal", JSON.stringify({ kind, id }));
|
|
23647
|
+
}
|
|
23648
|
+
function bodyString(body, key) {
|
|
23649
|
+
const value = body[key];
|
|
23650
|
+
return typeof value === "string" ? value : undefined;
|
|
23651
|
+
}
|
|
23652
|
+
function bodyStringList(body, key) {
|
|
23653
|
+
const value = body[key];
|
|
23654
|
+
if (!Array.isArray(value))
|
|
23655
|
+
return [];
|
|
23656
|
+
return value.filter((item) => typeof item === "string");
|
|
23657
|
+
}
|
|
23658
|
+
function bodyTaskStatus(body, key) {
|
|
23659
|
+
const value = body[key];
|
|
23660
|
+
if (value === undefined)
|
|
23661
|
+
return;
|
|
23662
|
+
if (isJournalTaskStatus(value))
|
|
23663
|
+
return value;
|
|
23664
|
+
throw new JournalRequestError(`${key} must be draft, todo, doing, blocked, or done`);
|
|
23665
|
+
}
|
|
23666
|
+
function bodyTaskPriority(body, key) {
|
|
23667
|
+
const value = body[key];
|
|
23668
|
+
if (value === undefined)
|
|
23669
|
+
return;
|
|
23670
|
+
if (isJournalTaskPriority(value))
|
|
23671
|
+
return value;
|
|
23672
|
+
throw new JournalRequestError(`${key} must be p0, p1, p2, or p3`);
|
|
23673
|
+
}
|
|
23674
|
+
function bodyNumber(body, key) {
|
|
23675
|
+
const value = body[key];
|
|
23676
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
23677
|
+
}
|
|
23678
|
+
async function handleJournal(req) {
|
|
23679
|
+
if (req.method === "GET") {
|
|
23680
|
+
const journal = await loadDailyJournalState(cwd);
|
|
23681
|
+
const tasks = await loadJournalTaskState(cwd);
|
|
23682
|
+
return json2({
|
|
23683
|
+
generation,
|
|
23684
|
+
journal,
|
|
23685
|
+
tasks,
|
|
23686
|
+
labels: collectJournalLabels(journal, tasks)
|
|
23687
|
+
});
|
|
23688
|
+
}
|
|
23689
|
+
if (req.method !== "POST")
|
|
23690
|
+
return text("method not allowed", 405);
|
|
23691
|
+
if (!sideEffectRequestAllowed(req))
|
|
23692
|
+
return text("forbidden", 403);
|
|
23693
|
+
const contentType = req.headers.get("content-type") || "";
|
|
23694
|
+
if (!/^application\/json(?:;|$)/i.test(contentType))
|
|
23695
|
+
return text("unsupported media type", 415);
|
|
23696
|
+
const maxBytes = Math.max(JOURNAL_ENTRY_BODY_MAX_BYTES, JOURNAL_TASK_BODY_MAX_BYTES) + 8192;
|
|
23697
|
+
const length = Number(req.headers.get("content-length") || "0");
|
|
23698
|
+
if (length > maxBytes)
|
|
23699
|
+
return text("payload too large", 413);
|
|
23700
|
+
let body = {};
|
|
23701
|
+
try {
|
|
23702
|
+
const raw = await req.text();
|
|
23703
|
+
if (raw.length > maxBytes)
|
|
23704
|
+
return text("payload too large", 413);
|
|
23705
|
+
body = JSON.parse(raw);
|
|
23706
|
+
} catch {
|
|
23707
|
+
return text("invalid json", 400);
|
|
23708
|
+
}
|
|
23709
|
+
try {
|
|
23710
|
+
const action = body.action;
|
|
23711
|
+
const now = new Date().toISOString();
|
|
23712
|
+
if (action === "add-entry") {
|
|
23713
|
+
const entry = await updateDailyJournalState(cwd, (state) => {
|
|
23714
|
+
const result = addDailyJournalEntry(state, {
|
|
23715
|
+
date: bodyString(body, "date") || "",
|
|
23716
|
+
title: bodyString(body, "title"),
|
|
23717
|
+
body: bodyString(body, "body") || "",
|
|
23718
|
+
labels: body.labels,
|
|
23719
|
+
source: body.source === "ai" ? "ai" : "user"
|
|
23720
|
+
}, now);
|
|
23721
|
+
if (result.ok === false)
|
|
23722
|
+
throw new JournalRequestError(result.error);
|
|
23723
|
+
return { state: result.state, result: result.entry };
|
|
23724
|
+
});
|
|
23725
|
+
journalSse("add-entry", entry.id);
|
|
23726
|
+
return json2({ ok: true, entry, generation });
|
|
23727
|
+
}
|
|
23728
|
+
if (action === "list-github-issues") {
|
|
23729
|
+
const label = bodyString(body, "label");
|
|
23730
|
+
const labels = bodyStringList(body, "labels");
|
|
23731
|
+
if (label)
|
|
23732
|
+
labels.push(label);
|
|
23733
|
+
const issues = readGithubIssueList({
|
|
23734
|
+
cwd,
|
|
23735
|
+
repo: bodyString(body, "repo"),
|
|
23736
|
+
labels,
|
|
23737
|
+
search: bodyString(body, "search"),
|
|
23738
|
+
state: normalizeGithubIssueListState(bodyString(body, "state")),
|
|
23739
|
+
limit: normalizeGithubIssueListLimit(bodyNumber(body, "limit"))
|
|
23740
|
+
});
|
|
23741
|
+
return json2({ ok: true, issues, generation });
|
|
23742
|
+
}
|
|
23743
|
+
if (action === "update-entry") {
|
|
23744
|
+
const id = bodyString(body, "id") || "";
|
|
23745
|
+
if (!id)
|
|
23746
|
+
return text("invalid id", 400);
|
|
23747
|
+
const entry = await updateDailyJournalState(cwd, (state) => {
|
|
23748
|
+
const result = updateDailyJournalEntry(state, id, {
|
|
23749
|
+
date: bodyString(body, "date"),
|
|
23750
|
+
title: bodyString(body, "title"),
|
|
23751
|
+
body: bodyString(body, "body"),
|
|
23752
|
+
labels: body.labels,
|
|
23753
|
+
source: body.source === "ai" ? "ai" : undefined
|
|
23754
|
+
}, now);
|
|
23755
|
+
if (result.ok === false)
|
|
23756
|
+
throw new JournalRequestError(result.error);
|
|
23757
|
+
return { state: result.state, result: result.entry };
|
|
23758
|
+
});
|
|
23759
|
+
journalSse("update-entry", entry.id);
|
|
23760
|
+
return json2({ ok: true, entry, generation });
|
|
23761
|
+
}
|
|
23762
|
+
if (action === "delete-entry") {
|
|
23763
|
+
const id = bodyString(body, "id") || "";
|
|
23764
|
+
if (!id)
|
|
23765
|
+
return text("invalid id", 400);
|
|
23766
|
+
const removed = await updateDailyJournalState(cwd, (state) => {
|
|
23767
|
+
const result = deleteDailyJournalEntry(state, id);
|
|
23768
|
+
return { state: result.state, result: result.removed };
|
|
23769
|
+
});
|
|
23770
|
+
if (removed)
|
|
23771
|
+
journalSse("delete-entry", id);
|
|
23772
|
+
return json2({ ok: true, removed, generation });
|
|
23773
|
+
}
|
|
23774
|
+
if (action === "link-github-issue") {
|
|
23775
|
+
const result = await updateJournalTaskState(cwd, (state) => {
|
|
23776
|
+
const linked = linkGithubIssueTask(state, {
|
|
23777
|
+
issue_number: bodyNumber(body, "issue_number") || 0,
|
|
23778
|
+
repo: bodyString(body, "repo"),
|
|
23779
|
+
title: bodyString(body, "title"),
|
|
23780
|
+
url: bodyString(body, "url"),
|
|
23781
|
+
memo_label: bodyString(body, "memo_label"),
|
|
23782
|
+
status: bodyTaskStatus(body, "status"),
|
|
23783
|
+
priority: bodyTaskPriority(body, "priority"),
|
|
23784
|
+
labels: body.labels,
|
|
23785
|
+
before_id: bodyString(body, "before_id"),
|
|
23786
|
+
after_id: bodyString(body, "after_id"),
|
|
23787
|
+
position: bodyNumber(body, "position")
|
|
23788
|
+
}, now);
|
|
23789
|
+
if (linked.ok === false)
|
|
23790
|
+
throw new JournalRequestError(linked.error);
|
|
23791
|
+
return { state: linked.state, result: linked };
|
|
23792
|
+
});
|
|
23793
|
+
journalSse("link-github-issue", result.task.id);
|
|
23794
|
+
return json2({
|
|
23795
|
+
ok: true,
|
|
23796
|
+
task: result.task,
|
|
23797
|
+
created: result.created,
|
|
23798
|
+
moved: result.moved,
|
|
23799
|
+
generation
|
|
23800
|
+
});
|
|
23801
|
+
}
|
|
23802
|
+
if (action === "add-task") {
|
|
23803
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23804
|
+
const result = addJournalTask(state, {
|
|
23805
|
+
title: bodyString(body, "title") || "",
|
|
23806
|
+
body: bodyString(body, "body"),
|
|
23807
|
+
status: bodyTaskStatus(body, "status"),
|
|
23808
|
+
priority: bodyTaskPriority(body, "priority"),
|
|
23809
|
+
labels: body.labels,
|
|
23810
|
+
due_date: bodyString(body, "due_date"),
|
|
23811
|
+
source_date: bodyString(body, "source_date"),
|
|
23812
|
+
journal_entry_id: bodyString(body, "journal_entry_id"),
|
|
23813
|
+
before_id: bodyString(body, "before_id"),
|
|
23814
|
+
after_id: bodyString(body, "after_id"),
|
|
23815
|
+
position: bodyNumber(body, "position")
|
|
23816
|
+
}, now);
|
|
23817
|
+
if (result.ok === false)
|
|
23818
|
+
throw new JournalRequestError(result.error);
|
|
23819
|
+
return { state: result.state, result: result.task };
|
|
23820
|
+
});
|
|
23821
|
+
journalSse("add-task", task.id);
|
|
23822
|
+
return json2({ ok: true, task, generation });
|
|
23823
|
+
}
|
|
23824
|
+
if (action === "update-task") {
|
|
23825
|
+
const id = bodyString(body, "id") || "";
|
|
23826
|
+
if (!id)
|
|
23827
|
+
return text("invalid id", 400);
|
|
23828
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23829
|
+
const result = updateJournalTask(state, id, {
|
|
23830
|
+
title: bodyString(body, "title"),
|
|
23831
|
+
body: bodyString(body, "body"),
|
|
23832
|
+
status: bodyTaskStatus(body, "status"),
|
|
23833
|
+
priority: bodyTaskPriority(body, "priority"),
|
|
23834
|
+
labels: body.labels,
|
|
23835
|
+
due_date: body.due_date === null ? null : bodyString(body, "due_date"),
|
|
23836
|
+
source_date: body.source_date === null ? null : bodyString(body, "source_date"),
|
|
23837
|
+
journal_entry_id: body.journal_entry_id === null ? null : bodyString(body, "journal_entry_id")
|
|
23838
|
+
}, now);
|
|
23839
|
+
if (result.ok === false)
|
|
23840
|
+
throw new JournalRequestError(result.error);
|
|
23841
|
+
return { state: result.state, result: result.task };
|
|
23842
|
+
});
|
|
23843
|
+
journalSse("update-task", task.id);
|
|
23844
|
+
return json2({ ok: true, task, generation });
|
|
23845
|
+
}
|
|
23846
|
+
if (action === "move-task") {
|
|
23847
|
+
const id = bodyString(body, "id") || "";
|
|
23848
|
+
if (!id)
|
|
23849
|
+
return text("invalid id", 400);
|
|
23850
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23851
|
+
const result = moveJournalTask(state, id, {
|
|
23852
|
+
status: bodyTaskStatus(body, "status"),
|
|
23853
|
+
before_id: bodyString(body, "before_id"),
|
|
23854
|
+
after_id: bodyString(body, "after_id"),
|
|
23855
|
+
position: bodyNumber(body, "position")
|
|
23856
|
+
}, now);
|
|
23857
|
+
if (result.ok === false)
|
|
23858
|
+
throw new JournalRequestError(result.error);
|
|
23859
|
+
return { state: result.state, result: result.task };
|
|
23860
|
+
});
|
|
23861
|
+
journalSse("move-task", task.id);
|
|
23862
|
+
return json2({ ok: true, task, generation });
|
|
23863
|
+
}
|
|
23864
|
+
if (action === "claim-task") {
|
|
23865
|
+
const id = bodyString(body, "id") || "";
|
|
23866
|
+
if (!id)
|
|
23867
|
+
return text("invalid id", 400);
|
|
23868
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23869
|
+
const result = claimJournalTask(state, id, {
|
|
23870
|
+
by: bodyString(body, "by"),
|
|
23871
|
+
lease_minutes: bodyNumber(body, "lease_minutes"),
|
|
23872
|
+
wip_limit: bodyNumber(body, "wip_limit")
|
|
23873
|
+
}, now);
|
|
23874
|
+
if (result.ok === false)
|
|
23875
|
+
throw new JournalRequestError(result.error);
|
|
23876
|
+
return { state: result.state, result: result.task };
|
|
23877
|
+
});
|
|
23878
|
+
journalSse("claim-task", task.id);
|
|
23879
|
+
return json2({ ok: true, task, generation });
|
|
23880
|
+
}
|
|
23881
|
+
if (action === "complete-task") {
|
|
23882
|
+
const id = bodyString(body, "id") || "";
|
|
23883
|
+
if (!id)
|
|
23884
|
+
return text("invalid id", 400);
|
|
23885
|
+
const task = await updateJournalTaskState(cwd, (state) => {
|
|
23886
|
+
const result = completeJournalTask(state, id, {
|
|
23887
|
+
by: bodyString(body, "by"),
|
|
23888
|
+
note: bodyString(body, "note"),
|
|
23889
|
+
source: body.source === "user" ? "user" : "ai"
|
|
23890
|
+
}, now);
|
|
23891
|
+
if (result.ok === false)
|
|
23892
|
+
throw new JournalRequestError(result.error);
|
|
23893
|
+
return { state: result.state, result: result.task };
|
|
23894
|
+
});
|
|
23895
|
+
journalSse("complete-task", task.id);
|
|
23896
|
+
return json2({ ok: true, task, generation });
|
|
23897
|
+
}
|
|
23898
|
+
if (action === "delete-task") {
|
|
23899
|
+
const id = bodyString(body, "id") || "";
|
|
23900
|
+
if (!id)
|
|
23901
|
+
return text("invalid id", 400);
|
|
23902
|
+
const removed = await updateJournalTaskState(cwd, (state) => {
|
|
23903
|
+
const result = deleteJournalTask(state, id);
|
|
23904
|
+
return { state: result.state, result: result.removed };
|
|
23905
|
+
});
|
|
23906
|
+
if (removed)
|
|
23907
|
+
journalSse("delete-task", id);
|
|
23908
|
+
return json2({ ok: true, removed, generation });
|
|
23909
|
+
}
|
|
23910
|
+
} catch (error) {
|
|
23911
|
+
if (error instanceof GithubIssueListError) {
|
|
23912
|
+
return text(error.message, error.status);
|
|
23913
|
+
}
|
|
23914
|
+
if (error instanceof JournalRequestError) {
|
|
23915
|
+
return text(error.message, error.status);
|
|
23916
|
+
}
|
|
23917
|
+
throw error;
|
|
23918
|
+
}
|
|
23919
|
+
return text("invalid action", 400);
|
|
23920
|
+
}
|
|
21782
23921
|
async function handleAnnotations(req) {
|
|
21783
23922
|
if (req.method === "GET")
|
|
21784
23923
|
return json2(await loadAnnotationsState(cwd));
|
|
@@ -21995,8 +24134,9 @@ function restartWorktreeWatch() {
|
|
|
21995
24134
|
}
|
|
21996
24135
|
worktreeWatch = startScopedWorktreeWatch();
|
|
21997
24136
|
}
|
|
21998
|
-
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, commandOverrides, cwdWasExplicit = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, enc, sseClients, sseKeepalives, fileCache, blameCache, BLAME_CACHE_MAX = 64, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, safePath, MCP_MAX_BODY_BYTES = 1048576, MCP_INSTRUCTIONS, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
24137
|
+
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, commandOverrides, cwdWasExplicit = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, enc, sseClients, sseKeepalives, fileCache, blameCache, BLAME_CACHE_MAX = 64, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, safePath, MCP_MAX_BODY_BYTES = 1048576, MCP_INSTRUCTIONS, JournalRequestError, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
21999
24138
|
var init_preview = __esm(async () => {
|
|
24139
|
+
init_journal();
|
|
22000
24140
|
init_routes();
|
|
22001
24141
|
init_annotations();
|
|
22002
24142
|
init_cache();
|
|
@@ -22004,6 +24144,8 @@ var init_preview = __esm(async () => {
|
|
|
22004
24144
|
init_dev_assets();
|
|
22005
24145
|
init_doctor();
|
|
22006
24146
|
init_git();
|
|
24147
|
+
init_github_issues();
|
|
24148
|
+
init_journal2();
|
|
22007
24149
|
init_mcp();
|
|
22008
24150
|
init_raw_file_headers();
|
|
22009
24151
|
init_root();
|
|
@@ -22013,8 +24155,8 @@ var init_preview = __esm(async () => {
|
|
|
22013
24155
|
init_server_registry();
|
|
22014
24156
|
init_state_store();
|
|
22015
24157
|
init_worktree_watcher();
|
|
22016
|
-
WEB_ROOT =
|
|
22017
|
-
VERSION = JSON.parse(
|
|
24158
|
+
WEB_ROOT = join19(ROOT, "web");
|
|
24159
|
+
VERSION = JSON.parse(readFileSync9(join19(ROOT, "package.json"), "utf8")).version;
|
|
22018
24160
|
DEFAULT_ARGS = ["HEAD"];
|
|
22019
24161
|
WATCHED_ASSET_FILES = ["index.html", "style.css", "app.js"];
|
|
22020
24162
|
LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
@@ -22076,6 +24218,13 @@ var init_preview = __esm(async () => {
|
|
|
22076
24218
|
blobBytesCache = new Map;
|
|
22077
24219
|
safePath = isSafePath;
|
|
22078
24220
|
MCP_INSTRUCTIONS = buildMcpInstructions();
|
|
24221
|
+
JournalRequestError = class JournalRequestError extends Error {
|
|
24222
|
+
status;
|
|
24223
|
+
constructor(message, status = 400) {
|
|
24224
|
+
super(message);
|
|
24225
|
+
this.status = status;
|
|
24226
|
+
}
|
|
24227
|
+
};
|
|
22079
24228
|
isCodeViewerInternalPath = isToolInternalPath;
|
|
22080
24229
|
parseCli();
|
|
22081
24230
|
applyPersistedSettings(await loadAppSettingsState(cwd));
|
|
@@ -22141,6 +24290,8 @@ var init_preview = __esm(async () => {
|
|
|
22141
24290
|
}
|
|
22142
24291
|
if (url.pathname === "/_mcp")
|
|
22143
24292
|
return handleMcp(req);
|
|
24293
|
+
if (url.pathname === "/_journal")
|
|
24294
|
+
return handleJournal(req);
|
|
22144
24295
|
if (url.pathname === "/_annotations")
|
|
22145
24296
|
return handleAnnotations(req);
|
|
22146
24297
|
if (url.pathname === "/_refs") {
|
|
@@ -22271,6 +24422,9 @@ if (process.argv[2] === "agent-help") {
|
|
|
22271
24422
|
} else if (process.argv[2] === "annotate") {
|
|
22272
24423
|
const { runAnnotateCli: runAnnotateCli2 } = await Promise.resolve().then(() => (init_annotate_cli(), exports_annotate_cli));
|
|
22273
24424
|
await runAnnotateCli2(process.argv.slice(3));
|
|
24425
|
+
} else if (process.argv[2] === "journal") {
|
|
24426
|
+
const { runJournalCli: runJournalCli2 } = await Promise.resolve().then(() => (init_journal_cli(), exports_journal_cli));
|
|
24427
|
+
await runJournalCli2(process.argv.slice(3));
|
|
22274
24428
|
} else if (process.argv[2] === "query") {
|
|
22275
24429
|
const sub = process.argv[3];
|
|
22276
24430
|
const helpOnly = !sub || sub === "help" || sub === "agent-help" || sub === "--help" || sub === "-h";
|