@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/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
- ? await readCodeFile(root, item.path)
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({ ...data, kind: mode === "files" ? "file" : "diff" });
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 === "diff"
342
- ? (staged ? "Staged" : "Unstaged") +
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
- : anchor === null
348
- ? "Whole file · Space selects a range"
349
- : `Selected lines ${from + 1}–${to + 1}`,
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 === "file"
407
- ? "↑↓ move · Space start/reset range · Enter use · Esc files"
408
- : "[ ] all / individual hunks · Enter use · Esc changes"
409
- : "G files / git diff · T staged / unstaged · H hidden · / filter · Esc cancel"),
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: all, open, resolved",
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
- cmd === "open" &&
139
+ ["open", "project", "event"].includes(cmd) &&
138
140
  /^[1-9]\d*$/.test(arg) &&
139
141
  Number.isSafeInteger(Number(arg))
140
142
  )