@tenyokjs/devdevian-cli 0.5.1-alpha.1 → 0.6.1-alpha.1
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 +30 -2
- package/VERSIONS.md +17 -1
- package/bin/devdevian.mjs +13 -1
- package/lib/catalog.mjs +611 -0
- package/lib/client.mjs +14 -3
- package/lib/context-actions.mjs +2 -1
- package/lib/drafts.mjs +11 -4
- package/lib/help.mjs +40 -0
- package/lib/picker.mjs +34 -12
- package/lib/tui-data.mjs +10 -2
- package/lib/tui-model.mjs +3 -1
- package/lib/tui.mjs +357 -32
- package/package.json +1 -1
package/lib/help.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Static wordmark; no terminal probes, image protocols or account reads.
|
|
2
|
+
const font = {
|
|
3
|
+
D: ["████ ", "█ █", "█ █", "█ █", "████ "],
|
|
4
|
+
E: ["█████", "█ ", "████ ", "█ ", "█████"],
|
|
5
|
+
V: ["█ █", "█ █", "█ █", " █ █ ", " █ "],
|
|
6
|
+
I: ["█████", " █ ", " █ ", " █ ", "█████"],
|
|
7
|
+
A: [" ███ ", "█ █", "█████", "█ █", "█ █"],
|
|
8
|
+
N: ["█ █", "██ █", "█ █ █", "█ ██", "█ █"],
|
|
9
|
+
};
|
|
10
|
+
export function renderHelp(
|
|
11
|
+
text,
|
|
12
|
+
{ tty = false, columns = 80, noColor = false, json = false } = {},
|
|
13
|
+
) {
|
|
14
|
+
if (!tty || json) return text;
|
|
15
|
+
const paint = (value, color) =>
|
|
16
|
+
noColor ? value : `\x1b[38;5;${color}m${value}\x1b[0m`;
|
|
17
|
+
const banner =
|
|
18
|
+
columns >= 56
|
|
19
|
+
? Array.from({ length: 5 }, (_, row) =>
|
|
20
|
+
paint(
|
|
21
|
+
[..."DEVDEVIAN"].map((c) => font[c][row]).join(" "),
|
|
22
|
+
[111, 105, 141, 177, 183][row],
|
|
23
|
+
),
|
|
24
|
+
).join("\n")
|
|
25
|
+
: paint("DEVDEVIAN", 141);
|
|
26
|
+
const content = text
|
|
27
|
+
.split("\n")
|
|
28
|
+
.map((line) => {
|
|
29
|
+
if (line.startsWith("Devdevian CLI")) return paint(line, 183);
|
|
30
|
+
return line.replace(
|
|
31
|
+
/^( devdevian)(\s+[a-z]+(?:\s+(?:open|create|list|comment|thread|reply|resolve|reopen))?)?|(--[a-z][a-z-]*)/g,
|
|
32
|
+
(match, command, rest) =>
|
|
33
|
+
command
|
|
34
|
+
? paint(command, 111) + (rest ? paint(rest, 183) : "")
|
|
35
|
+
: paint(match, 147),
|
|
36
|
+
);
|
|
37
|
+
})
|
|
38
|
+
.join("\n");
|
|
39
|
+
return `\n${banner}\n\n${content}`;
|
|
40
|
+
}
|
package/lib/picker.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readImage } from "./catalog.mjs";
|
|
1
2
|
import React, { useState, useEffect, useRef } from "react";
|
|
2
3
|
import { Box, Text, useInput } from "ink";
|
|
3
4
|
import { basename, dirname } from "node:path";
|
|
@@ -16,6 +17,7 @@ export function WorkspacePicker({
|
|
|
16
17
|
width,
|
|
17
18
|
height,
|
|
18
19
|
initial = "files",
|
|
20
|
+
media = false,
|
|
19
21
|
onSelect,
|
|
20
22
|
onCancel,
|
|
21
23
|
}) {
|
|
@@ -113,13 +115,23 @@ export function WorkspacePicker({
|
|
|
113
115
|
try {
|
|
114
116
|
const data =
|
|
115
117
|
mode === "files"
|
|
116
|
-
?
|
|
118
|
+
? media
|
|
119
|
+
? await readImage(root, item.path)
|
|
120
|
+
: await readCodeFile(root, item.path)
|
|
117
121
|
: await readDiff(root, item.path, {
|
|
118
122
|
staged,
|
|
119
123
|
signal: controller.signal,
|
|
120
124
|
});
|
|
121
125
|
if (!alive.current) return;
|
|
122
|
-
setSelected(
|
|
126
|
+
setSelected(
|
|
127
|
+
media
|
|
128
|
+
? {
|
|
129
|
+
path: data.path,
|
|
130
|
+
kind: "image",
|
|
131
|
+
code: `${data.name}\n${data.mime} · ${data.size} bytes\nThis file is uploaded only after publication confirmation.`,
|
|
132
|
+
}
|
|
133
|
+
: { ...data, kind: mode === "files" ? "file" : "diff" },
|
|
134
|
+
);
|
|
123
135
|
setLine(0);
|
|
124
136
|
setAnchor(null);
|
|
125
137
|
setHunk(-1);
|
|
@@ -133,6 +145,10 @@ export function WorkspacePicker({
|
|
|
133
145
|
}
|
|
134
146
|
function accept() {
|
|
135
147
|
try {
|
|
148
|
+
if (selected.kind === "image") {
|
|
149
|
+
onSelect({ kind: "image", path: selected.path });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
136
152
|
if (selected.kind === "file") {
|
|
137
153
|
const start = anchor === null ? null : Math.min(anchor, line) + 1,
|
|
138
154
|
end = anchor === null ? null : Math.max(anchor, line) + 1;
|
|
@@ -229,7 +245,7 @@ export function WorkspacePicker({
|
|
|
229
245
|
setSearching(true);
|
|
230
246
|
return;
|
|
231
247
|
}
|
|
232
|
-
if (input === "g") {
|
|
248
|
+
if (input === "g" && !media) {
|
|
233
249
|
setMode((v) => (v === "files" ? "changes" : "files"));
|
|
234
250
|
clearSearch();
|
|
235
251
|
return;
|
|
@@ -338,15 +354,17 @@ export function WorkspacePicker({
|
|
|
338
354
|
},
|
|
339
355
|
row(selected.path, c.purple, "code-title"),
|
|
340
356
|
row(
|
|
341
|
-
selected.kind === "
|
|
342
|
-
?
|
|
357
|
+
selected.kind === "image"
|
|
358
|
+
? "Image selected · Enter use"
|
|
359
|
+
: selected.kind === "diff"
|
|
360
|
+
? (staged ? "Staged" : "Unstaged") +
|
|
343
361
|
" · " +
|
|
344
362
|
(hunk < 0
|
|
345
363
|
? "All hunks"
|
|
346
364
|
: `Hunk ${hunk + 1}/${selected.hunks.length}`)
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
365
|
+
: anchor === null
|
|
366
|
+
? "Whole file · Space selects a range"
|
|
367
|
+
: `Selected lines ${from + 1}–${to + 1}`,
|
|
350
368
|
c.blue,
|
|
351
369
|
"code-mode",
|
|
352
370
|
),
|
|
@@ -403,10 +421,14 @@ export function WorkspacePicker({
|
|
|
403
421
|
clip(
|
|
404
422
|
error ||
|
|
405
423
|
(selected
|
|
406
|
-
? selected.kind === "
|
|
407
|
-
? "
|
|
408
|
-
:
|
|
409
|
-
|
|
424
|
+
? selected.kind === "image"
|
|
425
|
+
? "Enter select image · Esc files"
|
|
426
|
+
: selected.kind === "file"
|
|
427
|
+
? "↑↓ move · Space start/reset range · Enter use · Esc files"
|
|
428
|
+
: "[ ] all / individual hunks · Enter use · Esc changes"
|
|
429
|
+
: media
|
|
430
|
+
? "Enter preview image · H hidden · / filter · Esc cancel"
|
|
431
|
+
: "G files / git diff · T staged / unstaged · H hidden · / filter · Esc cancel"),
|
|
410
432
|
width,
|
|
411
433
|
),
|
|
412
434
|
),
|
package/lib/tui-data.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadCatalog, isCatalog } from "./catalog.mjs";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
export function assertSnippet(code, hash) {
|
|
3
4
|
if (
|
|
@@ -17,6 +18,8 @@ export const navigation = [
|
|
|
17
18
|
["review", "Request code review", "Another pair of eyes"],
|
|
18
19
|
["drafts", "Local drafts", "Resume your writing"],
|
|
19
20
|
["communities", "Communities", "Your private spaces"],
|
|
21
|
+
["projects", "Projects", "Build in the open"],
|
|
22
|
+
["events", "Events", "Meet, learn and build"],
|
|
20
23
|
["search", "Search", "Find a conversation"],
|
|
21
24
|
["devices", "Connected terminals", "Manage CLI access"],
|
|
22
25
|
["appearance", "Appearance", "Themes & layout"],
|
|
@@ -56,6 +59,7 @@ export async function notificationRoute(api, item) {
|
|
|
56
59
|
};
|
|
57
60
|
}
|
|
58
61
|
export async function loadPage(api, route, signal) {
|
|
62
|
+
if (isCatalog(route)) return loadCatalog(api, route, signal);
|
|
59
63
|
const get = (path) => api(path, { signal }),
|
|
60
64
|
page = route.page || 1;
|
|
61
65
|
if (route.view === "feed")
|
|
@@ -226,7 +230,10 @@ export function contentLines(route, data) {
|
|
|
226
230
|
"R Reply in a conversation; refresh a list",
|
|
227
231
|
"V / W Open review workspace / switch code and discussion",
|
|
228
232
|
"Space / C Select a line range / comment on selected code",
|
|
229
|
-
"F Filter remarks
|
|
233
|
+
"F Filter remarks or the Projects / Events catalog",
|
|
234
|
+
"N / E Create / edit your project or event in its section",
|
|
235
|
+
"J Join / leave an event (confirmation required)",
|
|
236
|
+
"X in catalogs Links, project conversation, calendar and event management",
|
|
230
237
|
"X / ? Context actions (edit, delete, solution, live updates)",
|
|
231
238
|
"Live updates 45s active / 3min idle; pause in X actions or --no-live",
|
|
232
239
|
"C Add feedback on code lines",
|
|
@@ -238,12 +245,13 @@ export function contentLines(route, data) {
|
|
|
238
245
|
"Ctrl+L Save a draft and reconnect an expired session",
|
|
239
246
|
"Ctrl+S Preview the current form",
|
|
240
247
|
"Ctrl+O / Ctrl+G Select a code file / Git diff in a review form",
|
|
248
|
+
"Ctrl+O Pick a workspace image on logo/banner/screenshot fields",
|
|
241
249
|
"/drafts Restore locally saved drafts for this account/project",
|
|
242
250
|
"/appearance Preview and save local themes and layout",
|
|
243
251
|
"Ctrl+C Exit and restore the original terminal",
|
|
244
252
|
"",
|
|
245
253
|
"Commands: /feed /inbox /search words /open 142",
|
|
246
|
-
" /ask /review /communities /devices /logout /quit",
|
|
254
|
+
" /ask /review /communities /projects /events /devices /logout /quit",
|
|
247
255
|
"",
|
|
248
256
|
"Text fields: arrows move the caret; Home/End move within a line.",
|
|
249
257
|
"Multiline fields: Enter adds a line; Tab changes field.",
|
package/lib/tui-model.mjs
CHANGED
|
@@ -130,11 +130,13 @@ export function parseCommand(text) {
|
|
|
130
130
|
"devices",
|
|
131
131
|
"drafts",
|
|
132
132
|
"appearance",
|
|
133
|
+
"projects",
|
|
134
|
+
"events",
|
|
133
135
|
].includes(cmd)
|
|
134
136
|
)
|
|
135
137
|
return { command: cmd, arg };
|
|
136
138
|
if (
|
|
137
|
-
|
|
139
|
+
["open", "project", "event"].includes(cmd) &&
|
|
138
140
|
/^[1-9]\d*$/.test(arg) &&
|
|
139
141
|
Number.isSafeInteger(Number(arg))
|
|
140
142
|
)
|