omakit 0.2.1 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omakit",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "The safe place to find out: everything knowable about an Omarchy Quattro plugin submission before you post it, on your own machine. Agent-first, read-only against the marketplace, posts nothing, zero dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Maarten Tolhuijs",
@@ -56,7 +56,15 @@ export function subcommandsOf(commands = COMMANDS) {
56
56
  * expression is exported so a test can run it through jq.
57
57
  */
58
58
  export const PLUGIN_IDS_JQ = "[.[] | select(((.kinds // []) | index(\"bar\")) | not)] | sort_by(.enabled | not) | .[].id"
59
- export const PLUGIN_IDS_COMMAND = `timeout 1 omarchy-shell shell listPlugins 2>/dev/null | jq -r '${PLUGIN_IDS_JQ}' 2>/dev/null`
59
+ /**
60
+ * The pipeline, in the shell's own syntax. `timeout 1` bounds the wait
61
+ * where coreutils has it; a system without `timeout` (measured: the macOS
62
+ * runner in CI, where the TAB fell back to directories) runs the command
63
+ * unbounded rather than never, since `omarchy-shell` itself gives up on
64
+ * its IPC timeout.
65
+ */
66
+ export const PLUGIN_IDS_COMMAND = `omarchy-shell shell listPlugins 2>/dev/null | jq -r '${PLUGIN_IDS_JQ}' 2>/dev/null`
67
+ export const PLUGIN_IDS_TIMED = `timeout 1 ${PLUGIN_IDS_COMMAND}`
60
68
 
61
69
  /** What a valued flag takes, by its placeholder: a controlled value, a file, or free text. */
62
70
  function placeholderKind(placeholder) {
@@ -175,7 +183,7 @@ function bash({ subcommands, categories, tags, pin, version }) {
175
183
  lines.push("# left out, read at TAB time; nothing when the shell does not answer in a")
176
184
  lines.push("# second, and the caller falls back to a directory.")
177
185
  lines.push("_omakit_plugin_ids() {")
178
- lines.push(` ${PLUGIN_IDS_COMMAND}`)
186
+ lines.push(` if command -v timeout >/dev/null 2>&1; then ${PLUGIN_IDS_TIMED}; else ${PLUGIN_IDS_COMMAND}; fi`)
179
187
  lines.push("}")
180
188
  lines.push("")
181
189
  lines.push("# A controlled value may contain a space, so each match is one line and is")
@@ -243,7 +251,7 @@ function zsh({ subcommands, categories, tags, pin, version }) {
243
251
  lines.push("# left out, read at TAB time; a directory when the shell does not answer.")
244
252
  lines.push("_omakit_plugins() {")
245
253
  lines.push(" local -a ids")
246
- lines.push(` ids=(\${(f)"$(${PLUGIN_IDS_COMMAND})"})`)
254
+ lines.push(` if (( $+commands[timeout] )); then ids=(\${(f)"$(${PLUGIN_IDS_TIMED})"}); else ids=(\${(f)"$(${PLUGIN_IDS_COMMAND})"}); fi`)
247
255
  lines.push(" if (( ${#ids} )); then compadd -a ids; else _directories; fi")
248
256
  lines.push("}")
249
257
  lines.push("")
@@ -263,7 +271,7 @@ function fish({ subcommands, categories, tags, pin, version }) {
263
271
  lines.push("# left out, read at TAB time; nothing when the shell does not answer, and")
264
272
  lines.push("# the directories offered beside them stand.")
265
273
  lines.push("function __omakit_plugin_ids")
266
- lines.push(` ${PLUGIN_IDS_COMMAND}`)
274
+ lines.push(` if command -q timeout; ${PLUGIN_IDS_TIMED}; else; ${PLUGIN_IDS_COMMAND}; end`)
267
275
  lines.push("end")
268
276
  lines.push("")
269
277
  for (const sub of subcommands) {
@@ -416,10 +416,10 @@ export function continuation(value, c) {
416
416
  * under itself. There is exactly one of these under any failure, and it is the
417
417
  * only line in the tool that starts with an arrow, so it can be found by shape.
418
418
  */
419
- export function action(text, c, { indent = GUTTER } = {}) {
419
+ export function action(text, c, { indent = GUTTER, width: total = COLUMNS } = {}) {
420
420
  // Painted after wrapping, and all of it cyan: the whole line is the thing
421
421
  // to do, so a backticked word inside it has nothing to stand out from.
422
- const lines = wrap(text, { indent: indent + 2 })
422
+ const lines = wrap(text, { indent: indent + 2, width: total })
423
423
  return lines.map((line, index) => (index === 0
424
424
  ? `${" ".repeat(indent)}${c("typeable.bold", ARROW)} ${c("typeable", line.trimStart())}`
425
425
  : `${" ".repeat(indent + 2)}${c("typeable", line.trimStart())}`))
@@ -155,7 +155,10 @@ export const COMPLETION_REFRESH_ARGS = Object.freeze(["setup", "--completion"])
155
155
 
156
156
  function refreshCompletionWith(root, stream) {
157
157
  const entryPoint = join(root, "bin/omakit")
158
- if (!existsSync(entryPoint)) return { ran: false, reason: `${entryPoint} is not there` }
158
+ // The reason names the relative path: the root is printed above it, and a
159
+ // temporary directory on macOS is long enough to push an absolute one
160
+ // past eighty columns in a sentence (measured in CI: 109).
161
+ if (!existsSync(entryPoint)) return { ran: false, reason: "this install has no bin/omakit under its root" }
159
162
  try {
160
163
  const out = execFileSync(process.execPath, [entryPoint, ...COMPLETION_REFRESH_ARGS], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
161
164
  stream.write(out)
@@ -5,13 +5,30 @@
5
5
  // typed here is written anywhere.
6
6
 
7
7
  import { createInterface } from "node:readline"
8
- import { action, colourEnabled, styler } from "../marketplace/style.mjs"
8
+ import { action, colourEnabled, COLUMNS, styler } from "../marketplace/style.mjs"
9
9
 
10
10
  /**
11
- * @param {{ input?: NodeJS.ReadStream, output?: NodeJS.WriteStream, colour?: boolean, question: string }} options
12
- * @returns {Promise<boolean>} true only for `y` or `yes`, in any case; the end of stdin is no
11
+ * The question as it is written to the terminal: the arrow line, wrapped
12
+ * at the contract width like every other action, with `[y/N]:` at the end
13
+ * of the last line, and no newline after it, so the cursor waits there.
14
+ * Measured on 0.2.1: the question grew a clause about --runs, wrapped to
15
+ * two lines, and only the first was written, so a person saw "for a" and
16
+ * no prompt, pressed Enter to see the rest, and the empty line was No.
17
+ *
18
+ * @param {string} question
19
+ * @param {(name: string, text: string) => string} c
20
+ * @param {{ width?: number }} [options]
21
+ * @returns {string} every line, joined, ending in `[y/N]: `
13
22
  */
14
- export function askYes({ input = process.stdin, output = process.stderr, colour = colourEnabled(output), question }) {
23
+ export function renderQuestion(question, c, { width = COLUMNS } = {}) {
24
+ return `${action(`${question} [y/N]:`, c, { indent: 0, width }).join("\n")} `
25
+ }
26
+
27
+ /**
28
+ * @param {{ input?: NodeJS.ReadStream, output?: NodeJS.WriteStream, colour?: boolean, question: string, width?: number }} options
29
+ * @returns {Promise<boolean>} true only for `y` or `yes`, in any case; an empty line and the end of stdin are no
30
+ */
31
+ export function askYes({ input = process.stdin, output = process.stderr, colour = colourEnabled(output), question, width = COLUMNS }) {
15
32
  const c = styler(colour)
16
33
  return new Promise((resolve) => {
17
34
  const rl = createInterface({ input, terminal: false })
@@ -27,6 +44,6 @@ export function askYes({ input = process.stdin, output = process.stderr, colour
27
44
  if (!answered) output.write("\n")
28
45
  settle(false)
29
46
  })
30
- output.write(`${action(`${question} [y/N]:`, c, { indent: 0 })[0]} `)
47
+ output.write(renderQuestion(question, c, { width }))
31
48
  })
32
49
  }