@tryarcanist/cli 0.1.25 → 0.1.27

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.
Files changed (2) hide show
  1. package/dist/index.js +107 -34
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -198,6 +198,7 @@ function validateApiUrl(url) {
198
198
  // src/runtime.ts
199
199
  import { randomUUID } from "crypto";
200
200
  import { createInterface } from "readline/promises";
201
+ var ANSI_CONTROL_SEQUENCE = /\u001b\[[0-9;?]*[ -/]*[@-~]/g;
201
202
  function getRuntimeOptions(command, options = {}) {
202
203
  const globals = command?.optsWithGlobals?.();
203
204
  const merged = { ...globals, ...options };
@@ -245,6 +246,70 @@ async function confirmOrThrow(message) {
245
246
  rl.close();
246
247
  }
247
248
  }
249
+ async function readHiddenPrompt(prompt) {
250
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
251
+ throw new CliError("user", "No interactive terminal available. Re-run with --token-stdin or set ARCANIST_TOKEN.");
252
+ }
253
+ return new Promise((resolve, reject) => {
254
+ const stdin = process.stdin;
255
+ const inputChars = [];
256
+ let ansiCarry = "";
257
+ let settled = false;
258
+ const cleanup = () => {
259
+ stdin.removeListener("data", onData);
260
+ stdin.removeListener("error", onError);
261
+ if (stdin.isTTY) stdin.setRawMode(false);
262
+ stdin.pause();
263
+ };
264
+ const finish = () => {
265
+ if (settled) return;
266
+ settled = true;
267
+ cleanup();
268
+ process.stdout.write("\n");
269
+ resolve(inputChars.join(""));
270
+ };
271
+ const fail = (error) => {
272
+ if (settled) return;
273
+ settled = true;
274
+ cleanup();
275
+ process.stdout.write("\n");
276
+ reject(error);
277
+ };
278
+ const onData = (chunk) => {
279
+ const normalized = normalizePromptChunk(chunk, ansiCarry);
280
+ ansiCarry = normalized.carry;
281
+ const text = normalized.text;
282
+ for (const char of text) {
283
+ if (char === "\n" || char === "\r") {
284
+ finish();
285
+ return;
286
+ }
287
+ if (char === "") {
288
+ fail(new CliError("user", "Interrupted.", { exitCode: 130 }));
289
+ return;
290
+ }
291
+ if (char === "\x7F" || char === "\b") {
292
+ if (inputChars.length > 0) {
293
+ inputChars.pop();
294
+ process.stdout.write("\b \b");
295
+ }
296
+ continue;
297
+ }
298
+ if (isControlCharacter(char)) continue;
299
+ inputChars.push(char);
300
+ process.stdout.write("*");
301
+ }
302
+ };
303
+ const onError = (error) => {
304
+ fail(new CliError("user", `Failed to read input: ${error.message}`));
305
+ };
306
+ process.stdout.write(prompt);
307
+ stdin.resume();
308
+ if (stdin.isTTY) stdin.setRawMode(true);
309
+ stdin.on("data", onData);
310
+ stdin.on("error", onError);
311
+ });
312
+ }
248
313
  function printDeprecatedAlias(alias, replacement, command, options = {}) {
249
314
  if (isQuiet(command, options)) return;
250
315
  process.stderr.write(`Warning: \`arcanist ${alias}\` is deprecated; use \`arcanist ${replacement}\`.
@@ -258,6 +323,47 @@ function applyColorEnvironment(options) {
258
323
  function randomIdempotencyKey() {
259
324
  return randomUUID();
260
325
  }
326
+ function normalizePromptChunk(chunk, carry = "") {
327
+ const raw = carry + (Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk);
328
+ let text = "";
329
+ let index = 0;
330
+ while (index < raw.length) {
331
+ const char = raw[index];
332
+ if (char !== "\x1B") {
333
+ text += char;
334
+ index += 1;
335
+ continue;
336
+ }
337
+ const sequence = matchAnsiControlSequence(raw, index);
338
+ if (sequence.kind === "incomplete") {
339
+ return { text, carry: raw.slice(index) };
340
+ }
341
+ if (sequence.kind === "complete") {
342
+ index = sequence.nextIndex;
343
+ continue;
344
+ }
345
+ index += 1;
346
+ }
347
+ return { text, carry: "" };
348
+ }
349
+ function matchAnsiControlSequence(text, startIndex) {
350
+ if (startIndex + 1 >= text.length) {
351
+ return { kind: "incomplete", nextIndex: startIndex };
352
+ }
353
+ if (text[startIndex + 1] !== "[") {
354
+ return { kind: "none", nextIndex: startIndex + 1 };
355
+ }
356
+ ANSI_CONTROL_SEQUENCE.lastIndex = startIndex;
357
+ const match = ANSI_CONTROL_SEQUENCE.exec(text);
358
+ if (match && match.index === startIndex) {
359
+ return { kind: "complete", nextIndex: startIndex + match[0].length };
360
+ }
361
+ return { kind: "incomplete", nextIndex: startIndex };
362
+ }
363
+ function isControlCharacter(char) {
364
+ const code = char.charCodeAt(0);
365
+ return code < 32 || code === 127;
366
+ }
261
367
 
262
368
  // src/commands/auth.ts
263
369
  async function whoamiCommand(options, command) {
@@ -1263,7 +1369,6 @@ async function createCommand(repoUrl, promptArg, options, command) {
1263
1369
  }
1264
1370
 
1265
1371
  // src/commands/login.ts
1266
- import { createInterface as createInterface2 } from "readline";
1267
1372
  async function loginCommand(options, command) {
1268
1373
  const runtime = getRuntimeOptions(command, options);
1269
1374
  let token;
@@ -1272,7 +1377,7 @@ async function loginCommand(options, command) {
1272
1377
  } else if (runtime.token) {
1273
1378
  token = runtime.token;
1274
1379
  } else {
1275
- token = await promptHidden("Enter your CLI token: ");
1380
+ token = await readHiddenPrompt("Enter your CLI token: ");
1276
1381
  }
1277
1382
  if (!token) {
1278
1383
  throw new CliError("user", "No token provided.");
@@ -1306,38 +1411,6 @@ async function loginCommand(options, command) {
1306
1411
  if (!runtime.json && !runtime.quiet) console.warn("Warning: Could not reach API to verify token.");
1307
1412
  }
1308
1413
  }
1309
- function promptHidden(prompt) {
1310
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
1311
- return Promise.reject(new CliError("user", "No interactive terminal available. Re-run with --token-stdin or set ARCANIST_TOKEN."));
1312
- }
1313
- return new Promise((resolve, reject) => {
1314
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
1315
- process.stdout.write(prompt);
1316
- const stdin = process.stdin;
1317
- if (stdin.isTTY) stdin.setRawMode(true);
1318
- let input = "";
1319
- const onData = (char) => {
1320
- const c = char.toString();
1321
- if (c === "\n" || c === "\r") {
1322
- if (stdin.isTTY) stdin.setRawMode(false);
1323
- stdin.removeListener("data", onData);
1324
- process.stdout.write("\n");
1325
- rl.close();
1326
- resolve(input);
1327
- } else if (c === "") {
1328
- if (stdin.isTTY) stdin.setRawMode(false);
1329
- stdin.removeListener("data", onData);
1330
- rl.close();
1331
- reject(new CliError("user", "Interrupted.", { exitCode: 130 }));
1332
- } else if (c === "\x7F" || c === "\b") {
1333
- input = input.slice(0, -1);
1334
- } else {
1335
- input += c;
1336
- }
1337
- };
1338
- stdin.on("data", onData);
1339
- });
1340
- }
1341
1414
 
1342
1415
  // src/commands/message.ts
1343
1416
  async function messageCommand(sessionId, promptArg, options = {}, command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {