@tryarcanist/cli 0.1.2 → 0.1.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.
Files changed (2) hide show
  1. package/dist/index.js +65 -47
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -73,13 +73,13 @@ function validateApiUrl(url) {
73
73
  }
74
74
 
75
75
  // src/commands/create.ts
76
+ var REPO_URL_PATTERNS = [
77
+ /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/,
78
+ /^git@[^:]+:[^/]+\/[^/]+?(?:\.git)?$/,
79
+ /^https?:\/\/github\.com\/[^/]+\/[^/]+?(?:\.git)?\/?$/
80
+ ];
76
81
  function validateRepoUrl(url) {
77
- const shorthand = /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(url);
78
- if (shorthand) return null;
79
- const ssh = /^git@[^:]+:[^/]+\/[^/]+?(?:\.git)?$/.test(url);
80
- if (ssh) return null;
81
- const https = /^https?:\/\/github\.com\/[^/]+\/[^/]+?(?:\.git)?\/?$/.test(url);
82
- if (https) return null;
82
+ if (REPO_URL_PATTERNS.some((pattern) => pattern.test(url))) return null;
83
83
  return `Invalid repo URL: "${url}". Expected a GitHub URL (https://github.com/owner/repo) or owner/repo shorthand.`;
84
84
  }
85
85
  async function createCommand(repoUrl, prompt, options) {
@@ -139,7 +139,6 @@ async function loginCommand(options) {
139
139
  console.error("Error: Invalid token format. Token must start with 'arc_'.");
140
140
  process.exit(1);
141
141
  }
142
- const apiUrl = options.apiUrl ?? loadConfig()?.apiUrl ?? "https://app.tryarcanist.com";
143
142
  if (options.apiUrl) {
144
143
  const urlError = validateApiUrl(options.apiUrl);
145
144
  if (urlError) {
@@ -147,6 +146,7 @@ async function loginCommand(options) {
147
146
  process.exit(1);
148
147
  }
149
148
  }
149
+ const apiUrl = options.apiUrl ?? loadConfig()?.apiUrl ?? "https://app.tryarcanist.com";
150
150
  saveConfig({ apiUrl, token });
151
151
  console.log(`Logged in. API: ${apiUrl}`);
152
152
  try {
@@ -222,24 +222,32 @@ async function stopCommand(sessionId) {
222
222
  // src/utils/session-output.ts
223
223
  function flattenSessionEvents(raw) {
224
224
  const merged = [];
225
- const textIndexById = /* @__PURE__ */ new Map();
225
+ const streamableIndexById = /* @__PURE__ */ new Map();
226
226
  const toolCallIndexById = /* @__PURE__ */ new Map();
227
227
  for (const event of raw) {
228
228
  const data = event.data ?? {};
229
229
  if (event.type === "sandbox_compaction_start") {
230
- merged.push({ type: "compaction_start", id: `cs-${data.timestamp ?? merged.length}` });
230
+ const entry2 = { type: "compaction_start", id: `cs-${data.timestamp ?? merged.length}` };
231
+ if (data.contextTokens != null) entry2.contextTokens = data.contextTokens;
232
+ merged.push(entry2);
231
233
  continue;
232
234
  }
233
235
  if (event.type === "sandbox_compaction_complete") {
234
- merged.push({ type: "compaction_complete", id: `cc-${data.timestamp ?? merged.length}` });
236
+ const entry2 = { type: "compaction_complete", id: `cc-${data.timestamp ?? merged.length}` };
237
+ if (data.contextTokensBefore != null) entry2.contextTokensBefore = data.contextTokensBefore;
238
+ if (data.contextTokensAfter != null) entry2.contextTokensAfter = data.contextTokensAfter;
239
+ merged.push(entry2);
235
240
  continue;
236
241
  }
237
242
  if (event.type === "sandbox_context_fill_warning") {
238
- merged.push({
243
+ const entry2 = {
239
244
  type: "context_fill_warning",
240
245
  id: `cfw-${data.timestamp ?? merged.length}`,
241
246
  fillPercent: Number(data.fillPercent ?? 0)
242
- });
247
+ };
248
+ if (data.contextTokens != null) entry2.contextTokens = data.contextTokens;
249
+ if (data.contextWindow != null) entry2.contextWindow = data.contextWindow;
250
+ merged.push(entry2);
243
251
  continue;
244
252
  }
245
253
  if (event.type === "sandbox_tool_truncated") {
@@ -255,12 +263,13 @@ function flattenSessionEvents(raw) {
255
263
  continue;
256
264
  }
257
265
  if (event.type === "session_error") {
258
- merged.push({
266
+ const entry2 = {
259
267
  type: "session_error",
260
268
  id: String(data.id ?? `err-${merged.length}`),
261
- error: String(data.error ?? "Unknown error"),
262
- ...data.code ? { code: String(data.code) } : {}
263
- });
269
+ error: String(data.error ?? "Unknown error")
270
+ };
271
+ if (data.code) entry2.code = String(data.code);
272
+ merged.push(entry2);
264
273
  continue;
265
274
  }
266
275
  if (event.type === "raw_opencode") {
@@ -270,12 +279,12 @@ function flattenSessionEvents(raw) {
270
279
  if (event.type === "reasoning") {
271
280
  const id = String(data.id ?? `reasoning-${merged.length}`);
272
281
  const text = String(data.text ?? "");
273
- const existingIdx = textIndexById.get(id);
282
+ const existingIdx = streamableIndexById.get(id);
274
283
  if (existingIdx !== void 0) {
275
284
  const existing = merged[existingIdx];
276
285
  if (existing.type === "reasoning") existing.text += text;
277
286
  } else {
278
- textIndexById.set(id, merged.length);
287
+ streamableIndexById.set(id, merged.length);
279
288
  merged.push({ type: "reasoning", id, text });
280
289
  }
281
290
  continue;
@@ -304,32 +313,35 @@ function flattenSessionEvents(raw) {
304
313
  if (event.type === "text") {
305
314
  const id = String(data.id ?? `text-${merged.length}`);
306
315
  const text = String(data.text ?? "");
307
- const existingIdx = textIndexById.get(id);
316
+ const existingIdx = streamableIndexById.get(id);
308
317
  if (existingIdx !== void 0) {
309
318
  const existing = merged[existingIdx];
310
319
  if (existing.type === "text") existing.text += text;
311
320
  } else {
312
- textIndexById.set(id, merged.length);
321
+ streamableIndexById.set(id, merged.length);
313
322
  merged.push({ type: "text", id, text });
314
323
  }
315
324
  continue;
316
325
  }
317
326
  if (event.type === "tool_call") {
318
327
  const id = String(data.id ?? `tool-${merged.length}`);
319
- const nextEvent = {
328
+ const entry2 = {
320
329
  type: "tool_call",
321
330
  id,
322
331
  tool: String(data.tool ?? "unknown"),
323
- summary: String(data.summary ?? ""),
324
- ...data.input && typeof data.input === "object" ? { input: data.input } : {}
332
+ summary: String(data.summary ?? "")
325
333
  };
334
+ if (data.input && typeof data.input === "object") entry2.input = data.input;
326
335
  const existingIdx = toolCallIndexById.get(id);
327
336
  if (existingIdx !== void 0) {
328
337
  const prev = merged[existingIdx];
329
- if (prev.type === "tool_call") merged[existingIdx] = { ...nextEvent, ...prev.toolStatus ? { toolStatus: prev.toolStatus } : {} };
338
+ if (prev.type === "tool_call") {
339
+ if (prev.toolStatus) entry2.toolStatus = prev.toolStatus;
340
+ merged[existingIdx] = entry2;
341
+ }
330
342
  } else {
331
343
  toolCallIndexById.set(id, merged.length);
332
- merged.push(nextEvent);
344
+ merged.push(entry2);
333
345
  }
334
346
  continue;
335
347
  }
@@ -338,19 +350,20 @@ function flattenSessionEvents(raw) {
338
350
  const existingIdx = toolCallIndexById.get(id);
339
351
  if (existingIdx !== void 0) {
340
352
  const prev = merged[existingIdx];
341
- if (prev.type === "tool_call") {
342
- merged[existingIdx] = { ...prev, ...data.status ? { toolStatus: String(data.status) } : {} };
353
+ if (prev.type === "tool_call" && data.status) {
354
+ prev.toolStatus = String(data.status);
343
355
  }
344
356
  }
345
357
  continue;
346
358
  }
347
- merged.push({
359
+ const entry = {
348
360
  type: "question",
349
361
  id: String(data.id ?? `question-${merged.length}`),
350
362
  question: String(data.question ?? ""),
351
- answer: data.answer == null ? null : String(data.answer),
352
- ...Array.isArray(data.options) ? { options: data.options } : {}
353
- });
363
+ answer: data.answer == null ? null : String(data.answer)
364
+ };
365
+ if (Array.isArray(data.options)) entry.options = data.options;
366
+ merged.push(entry);
354
367
  }
355
368
  return merged;
356
369
  }
@@ -396,11 +409,17 @@ ${event.text}
396
409
  ${event.answer ? `**Answer:** ${event.answer}
397
410
  ` : ""}`;
398
411
  case "compaction_start":
399
- return "*[context compacted]*\n";
412
+ return event.contextTokens ? `*[compacting context at ${Math.round(event.contextTokens / 1e3)}k tokens]*
413
+ ` : "*[compacting context]*\n";
400
414
  case "compaction_complete":
401
- return "";
415
+ if (event.contextTokensBefore && event.contextTokensAfter && event.contextTokensBefore > 0) {
416
+ const savedPct = Math.round((1 - event.contextTokensAfter / event.contextTokensBefore) * 100);
417
+ return `*[context compacted: ${Math.round(event.contextTokensBefore / 1e3)}k \u2192 ${Math.round(event.contextTokensAfter / 1e3)}k (${savedPct}% saved)]*
418
+ `;
419
+ }
420
+ return "*[context compacted]*\n";
402
421
  case "context_fill_warning":
403
- return `*[context ${event.fillPercent}% full]*
422
+ return `*[context ${Math.round(event.fillPercent * 100)}% full]*
404
423
  `;
405
424
  case "tool_truncated":
406
425
  return `*[${event.tool} output truncated]*
@@ -481,7 +500,9 @@ function parseSsePayload(payload) {
481
500
  let currentData = [];
482
501
  function flush() {
483
502
  if (currentData.length === 0 && currentId === void 0 && currentEvent === "message") return;
484
- messages.push({ event: currentEvent, ...currentId !== void 0 ? { id: currentId } : {}, data: currentData.join("\n") });
503
+ const msg = { event: currentEvent, data: currentData.join("\n") };
504
+ if (currentId !== void 0) msg.id = currentId;
505
+ messages.push(msg);
485
506
  currentEvent = "message";
486
507
  currentId = void 0;
487
508
  currentData = [];
@@ -511,11 +532,14 @@ function parseSsePayload(payload) {
511
532
  for (const message of messages) {
512
533
  const data = message.data ? parseJsonObject(message.data) : {};
513
534
  if (message.event === "status") {
514
- status = {
515
- status: typeof data.status === "string" ? data.status : "unknown",
516
- ...typeof data.title === "string" ? { title: data.title } : {},
517
- ...typeof data.spawnDurationMs === "number" || data.spawnDurationMs === null ? { spawnDurationMs: data.spawnDurationMs } : {}
535
+ const entry = {
536
+ status: typeof data.status === "string" ? data.status : "unknown"
518
537
  };
538
+ if (typeof data.title === "string") entry.title = data.title;
539
+ if (typeof data.spawnDurationMs === "number" || data.spawnDurationMs === null) {
540
+ entry.spawnDurationMs = data.spawnDurationMs;
541
+ }
542
+ status = entry;
519
543
  continue;
520
544
  }
521
545
  events.push({
@@ -656,13 +680,7 @@ async function fetchPromptLabels(config, sessionId) {
656
680
  }
657
681
  async function watchCommand(sessionId, options) {
658
682
  const config = requireConfig();
659
- let pollIntervalMs;
660
- try {
661
- pollIntervalMs = parsePollInterval(options.pollInterval);
662
- } catch (err) {
663
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
664
- process.exit(1);
665
- }
683
+ const pollIntervalMs = parsePollInterval(options.pollInterval);
666
684
  let promptLabels = /* @__PURE__ */ new Map();
667
685
  try {
668
686
  promptLabels = await fetchPromptLabels(config, sessionId);
@@ -683,7 +701,7 @@ async function watchCommand(sessionId, options) {
683
701
  afterSequence: String(afterSequence),
684
702
  limit: String(WATCH_REPLAY_PAGE_SIZE)
685
703
  });
686
- const payload = await apiFetchText(config, `/api/sessions/${sessionId}/events?${query.toString()}`);
704
+ const payload = await apiFetchText(config, `/api/sessions/${sessionId}/events?${query}`);
687
705
  const parsed = parseSsePayload(payload);
688
706
  const receivedFullPage = parsed.events.length >= WATCH_REPLAY_PAGE_SIZE;
689
707
  if (parsed.status) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {