@alashi/cli 0.14.0 → 0.16.0
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/assets/v1/chat.js +298 -57
- package/assets/v1/system.css +45 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +358 -78
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{store-DoAGOynU.mjs → store-DvLX0Kft.mjs} +1215 -55
- package/dist/store-DvLX0Kft.mjs.map +1 -0
- package/package.json +3 -3
- package/templates/basic-app/index.mjs +1 -1
- package/templates/basic-app/package.json +1 -1
- package/dist/store-DoAGOynU.mjs.map +0 -1
package/assets/v1/chat.js
CHANGED
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
* <script src="/assets/v1/chat.js"></script>
|
|
6
6
|
* <alashi-chat></alashi-chat>
|
|
7
7
|
*
|
|
8
|
-
* and get conversations,
|
|
9
|
-
* and cost display. Renders into
|
|
10
|
-
* inherits the app's theme tokens.
|
|
8
|
+
* and get conversations, live event streaming, markdown rendering, tool-use
|
|
9
|
+
* status, interactive tool-approval cards, and cost display. Renders into
|
|
10
|
+
* light DOM using system.css classes, so it inherits the app's theme tokens.
|
|
11
|
+
*
|
|
12
|
+
* Turns are enqueue-only: POST /chat returns 202 and every event (including
|
|
13
|
+
* the echo of the user's own message) arrives over a standing per-conversation
|
|
14
|
+
* EventSource, so any number of tabs render the same transcript and the
|
|
15
|
+
* stream survives refreshes via Last-Event-ID replay + the pending-approvals
|
|
16
|
+
* snapshot.
|
|
11
17
|
*
|
|
12
18
|
* The same script also registers <alashi-markdown>, the markdown renderer on
|
|
13
19
|
* its own for apps that display prose outside a conversation (see the bottom
|
|
@@ -121,6 +127,8 @@
|
|
|
121
127
|
if (this._initialized) return;
|
|
122
128
|
this._initialized = true;
|
|
123
129
|
this.conversationId = null;
|
|
130
|
+
this._events = null;
|
|
131
|
+
this._resetTurn();
|
|
124
132
|
this._render();
|
|
125
133
|
// Single-conversation mode: the embedding app opens the transcript it
|
|
126
134
|
// wants, so skip both the listing fetch and the auto-open.
|
|
@@ -238,6 +246,8 @@
|
|
|
238
246
|
|
|
239
247
|
async openConversation(id) {
|
|
240
248
|
this.conversationId = id;
|
|
249
|
+
this._disconnect();
|
|
250
|
+
this._resetTurn();
|
|
241
251
|
this._log.replaceChildren();
|
|
242
252
|
this._status.textContent = 'loading history…';
|
|
243
253
|
const { bubble, note } = this._appendPending();
|
|
@@ -245,19 +255,287 @@
|
|
|
245
255
|
try {
|
|
246
256
|
const res = await fetch(this._hostApi(`/chat/${encodeURIComponent(id)}/history`));
|
|
247
257
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
248
|
-
const { messages } = await res.json();
|
|
258
|
+
const { messages, approvals = [] } = await res.json();
|
|
249
259
|
bubble.remove();
|
|
250
|
-
|
|
260
|
+
// Settled approvals interleave by time; transcript messages carry
|
|
261
|
+
// timestamps, approvals carry requestedAt.
|
|
262
|
+
const items = [
|
|
263
|
+
...messages.map((m) => ({
|
|
264
|
+
at: m.timestamp ?? '',
|
|
265
|
+
render: () => this._appendMsg(m.text, m.role),
|
|
266
|
+
})),
|
|
267
|
+
...approvals.map((a) => ({
|
|
268
|
+
at: a.requestedAt ?? '',
|
|
269
|
+
render: () => this._renderSettledApproval(a),
|
|
270
|
+
})),
|
|
271
|
+
].sort((x, y) => (x.at < y.at ? -1 : x.at > y.at ? 1 : 0));
|
|
272
|
+
for (const item of items) item.render();
|
|
251
273
|
this._status.textContent = '';
|
|
252
274
|
} catch (err) {
|
|
253
275
|
this._showError(bubble, false, `Failed to load history: ${String(err)}`);
|
|
254
276
|
this._status.textContent = String(err);
|
|
255
277
|
}
|
|
278
|
+
await this._loadPendingApprovals().catch(() => {});
|
|
279
|
+
// Fresh connect: the host replays the in-flight turn (everything before
|
|
280
|
+
// it is already in the history we just rendered), then follows live.
|
|
281
|
+
this._connect(id);
|
|
256
282
|
this._loadConversations().catch(() => {});
|
|
257
283
|
this._emit('conversation', { conversationId: id });
|
|
258
284
|
this._input.focus();
|
|
259
285
|
}
|
|
260
286
|
|
|
287
|
+
/* Standing per-conversation EventSource. `fromStart` replays the whole
|
|
288
|
+
* ring — used right after creating a conversation, whose only events are
|
|
289
|
+
* this first turn. Reconnects resume via Last-Event-ID automatically; a
|
|
290
|
+
* gap beyond the ring arrives as a `resync` event instead. */
|
|
291
|
+
_connect(id, { fromStart = false } = {}) {
|
|
292
|
+
this._disconnect();
|
|
293
|
+
const url = this._hostApi(
|
|
294
|
+
`/chat/${encodeURIComponent(id)}/events${fromStart ? '?lastEventId=0' : ''}`,
|
|
295
|
+
);
|
|
296
|
+
this._events = new EventSource(url);
|
|
297
|
+
this._events.onmessage = (e) => {
|
|
298
|
+
let event;
|
|
299
|
+
try {
|
|
300
|
+
event = JSON.parse(e.data);
|
|
301
|
+
} catch {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (this.conversationId === id) this._handleEvent(event);
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
_disconnect() {
|
|
309
|
+
if (this._events) {
|
|
310
|
+
this._events.close();
|
|
311
|
+
this._events = null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
_resetTurn() {
|
|
316
|
+
this._reply = null;
|
|
317
|
+
this._replyNote = null;
|
|
318
|
+
this._replyText = '';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* The current turn's assistant bubble, created on demand so a turn echoed
|
|
322
|
+
* from another tab renders the same as one submitted here. */
|
|
323
|
+
_ensureReply() {
|
|
324
|
+
if (!this._reply?.isConnected) {
|
|
325
|
+
const { bubble, note } = this._appendPending();
|
|
326
|
+
this._reply = bubble;
|
|
327
|
+
this._replyNote = note;
|
|
328
|
+
this._replyText = '';
|
|
329
|
+
}
|
|
330
|
+
return this._reply;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
_handleEvent(event) {
|
|
334
|
+
if (event.type === 'user_message') {
|
|
335
|
+
this._appendMsg(event.text, 'user');
|
|
336
|
+
this._ensureReply();
|
|
337
|
+
this._status.textContent = 'thinking…';
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (event.type === 'text') {
|
|
341
|
+
const reply = this._ensureReply();
|
|
342
|
+
this._replyText += event.text;
|
|
343
|
+
reply.classList.remove('pending');
|
|
344
|
+
this._setMsgText(reply, this._replyText, 'assistant');
|
|
345
|
+
this._log.scrollTop = this._log.scrollHeight;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (event.type === 'tool_use') {
|
|
349
|
+
this._status.textContent = `using tool: ${event.name}`;
|
|
350
|
+
if (this._reply?.classList.contains('pending'))
|
|
351
|
+
this._replyNote.textContent = `using tool: ${event.name}…`;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (event.type === 'approval_required') {
|
|
355
|
+
this._renderApproval(event);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (event.type === 'approval_resolved') {
|
|
359
|
+
this._resolveApproval(event);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (event.type === 'compaction') {
|
|
363
|
+
this._log.append(
|
|
364
|
+
el('div', {
|
|
365
|
+
className: 'chat-divider',
|
|
366
|
+
textContent: `context compacted · ${event.trigger}`,
|
|
367
|
+
}),
|
|
368
|
+
);
|
|
369
|
+
this._log.scrollTop = this._log.scrollHeight;
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (event.type === 'resync') {
|
|
373
|
+
// Missed too much to replay: rebuild from history + pending snapshot.
|
|
374
|
+
this.openConversation(this.conversationId).catch(() => {});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (event.type === 'result') {
|
|
378
|
+
this._status.textContent = event.ok
|
|
379
|
+
? `done${event.costUsd ? ` ($${event.costUsd.toFixed(4)})` : ''}`
|
|
380
|
+
: `error: ${event.error}`;
|
|
381
|
+
const reply = this._ensureReply();
|
|
382
|
+
if (!event.ok) {
|
|
383
|
+
this._showError(reply, this._replyText !== '', `Something went wrong: ${event.error}`);
|
|
384
|
+
} else if (this._replyText === '') {
|
|
385
|
+
reply.className = 'msg assistant';
|
|
386
|
+
reply.replaceChildren(
|
|
387
|
+
el('span', { className: 'typing-note', textContent: '(no reply)' }),
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
this._emit('turn', {
|
|
391
|
+
ok: event.ok,
|
|
392
|
+
costUsd: event.costUsd,
|
|
393
|
+
error: event.error,
|
|
394
|
+
conversationId: this.conversationId,
|
|
395
|
+
});
|
|
396
|
+
this._resetTurn();
|
|
397
|
+
this._send.disabled = false;
|
|
398
|
+
this._input.focus();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* Inline approval card: tool, input preview, and the four decisions. The
|
|
403
|
+
* decision travels over the respond endpoint; the card collapses when the
|
|
404
|
+
* matching approval_resolved event comes back on the bus (so every tab
|
|
405
|
+
* collapses, not just the one that clicked). */
|
|
406
|
+
_renderApproval(event) {
|
|
407
|
+
if (this._approvalCard(event.approvalId)) return; // snapshot + replay overlap
|
|
408
|
+
const card = el('div', { className: 'approval-card' });
|
|
409
|
+
card.dataset.approvalId = event.approvalId;
|
|
410
|
+
card.dataset.tool = event.tool;
|
|
411
|
+
let preview = '';
|
|
412
|
+
try {
|
|
413
|
+
preview = JSON.stringify(event.input, null, 2);
|
|
414
|
+
} catch {
|
|
415
|
+
preview = String(event.input);
|
|
416
|
+
}
|
|
417
|
+
if (preview && preview.length > 2000) preview = `${preview.slice(0, 2000)}…`;
|
|
418
|
+
const actions = el('div', { className: 'approval-actions' });
|
|
419
|
+
const act = (label, body, primary) => {
|
|
420
|
+
const button = el('button', {
|
|
421
|
+
type: 'button',
|
|
422
|
+
textContent: label,
|
|
423
|
+
className: primary ? 'primary' : '',
|
|
424
|
+
});
|
|
425
|
+
button.addEventListener('click', () => {
|
|
426
|
+
for (const b of actions.querySelectorAll('button')) b.disabled = true;
|
|
427
|
+
this._respondToApproval(event.approvalId, body).catch(() => {
|
|
428
|
+
for (const b of actions.querySelectorAll('button')) b.disabled = false;
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
actions.append(button);
|
|
432
|
+
};
|
|
433
|
+
act('Approve', { behavior: 'allow' }, true);
|
|
434
|
+
act('Approve for session', { behavior: 'allow', scope: 'session' });
|
|
435
|
+
act('Always allow', { behavior: 'allow', scope: 'always' });
|
|
436
|
+
// Grants cover the rule that asked, not the whole tool: say so.
|
|
437
|
+
const always = actions.querySelectorAll('button')[2];
|
|
438
|
+
if (always) {
|
|
439
|
+
always.title = event.reason
|
|
440
|
+
? `Always allow: ${event.reason}`
|
|
441
|
+
: `Always allow ${event.tool}`;
|
|
442
|
+
}
|
|
443
|
+
act('Deny', { behavior: 'deny' });
|
|
444
|
+
card.append(
|
|
445
|
+
el('div', {
|
|
446
|
+
className: 'approval-title',
|
|
447
|
+
textContent: `Approval required: ${event.tool}`,
|
|
448
|
+
}),
|
|
449
|
+
...(event.reason
|
|
450
|
+
? [
|
|
451
|
+
el('div', {
|
|
452
|
+
className: 'approval-reason',
|
|
453
|
+
textContent: `Asked by policy: ${event.reason}`,
|
|
454
|
+
}),
|
|
455
|
+
]
|
|
456
|
+
: []),
|
|
457
|
+
...(preview ? [el('pre', {}, [el('code', { textContent: preview })])] : []),
|
|
458
|
+
actions,
|
|
459
|
+
);
|
|
460
|
+
// Keep the typing indicator visually last while the engine is suspended.
|
|
461
|
+
if (this._reply?.isConnected) this._log.insertBefore(card, this._reply);
|
|
462
|
+
else this._log.append(card);
|
|
463
|
+
this._log.scrollTop = this._log.scrollHeight;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
_approvalCard(approvalId) {
|
|
467
|
+
for (const card of this._log.querySelectorAll('.approval-card')) {
|
|
468
|
+
if (card.dataset.approvalId === approvalId) return card;
|
|
469
|
+
}
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async _respondToApproval(approvalId, body) {
|
|
474
|
+
const res = await fetch(
|
|
475
|
+
this._hostApi(
|
|
476
|
+
`/chat/${encodeURIComponent(this.conversationId)}/approvals/${encodeURIComponent(approvalId)}`,
|
|
477
|
+
),
|
|
478
|
+
{
|
|
479
|
+
method: 'POST',
|
|
480
|
+
headers: { 'content-type': 'application/json' },
|
|
481
|
+
body: JSON.stringify(body),
|
|
482
|
+
},
|
|
483
|
+
);
|
|
484
|
+
if (!res.ok && res.status !== 409) throw new Error(`HTTP ${res.status}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
_resolveApproval(event) {
|
|
488
|
+
const card = this._approvalCard(event.approvalId);
|
|
489
|
+
if (!card) return;
|
|
490
|
+
const verdict = event.behavior === 'allow' ? 'Approved' : 'Denied';
|
|
491
|
+
const why =
|
|
492
|
+
event.reason && event.reason !== 'user' ? ` · ${event.reason.replace('_', ' ')}` : '';
|
|
493
|
+
card.className = 'approval-card resolved';
|
|
494
|
+
card.replaceChildren(
|
|
495
|
+
el('div', {
|
|
496
|
+
className: 'approval-title',
|
|
497
|
+
textContent: `${verdict}: ${card.dataset.tool}${why}`,
|
|
498
|
+
}),
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* A past decision rendered from the approval log (history reload):
|
|
503
|
+
* the same collapsed line a live approval_resolved produces. */
|
|
504
|
+
_renderSettledApproval(record) {
|
|
505
|
+
if (this._approvalCard(record.approvalId)) return;
|
|
506
|
+
const verdict = record.status === 'allowed' ? 'Approved' : 'Denied';
|
|
507
|
+
const why = record.status === 'timeout' ? ' · timeout' : '';
|
|
508
|
+
const card = el('div', { className: 'approval-card resolved' });
|
|
509
|
+
card.dataset.approvalId = record.approvalId;
|
|
510
|
+
card.dataset.tool = record.tool;
|
|
511
|
+
card.append(
|
|
512
|
+
el('div', {
|
|
513
|
+
className: 'approval-title',
|
|
514
|
+
textContent: `${verdict}: ${record.tool}${why}`,
|
|
515
|
+
}),
|
|
516
|
+
);
|
|
517
|
+
this._log.append(card);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async _loadPendingApprovals() {
|
|
521
|
+
if (!this.conversationId) return;
|
|
522
|
+
const res = await fetch(
|
|
523
|
+
this._hostApi(`/chat/${encodeURIComponent(this.conversationId)}/pending-approvals`),
|
|
524
|
+
);
|
|
525
|
+
if (!res.ok) return;
|
|
526
|
+
const { approvals } = await res.json();
|
|
527
|
+
for (const approval of approvals) {
|
|
528
|
+
this._renderApproval({
|
|
529
|
+
type: 'approval_required',
|
|
530
|
+
approvalId: approval.approvalId,
|
|
531
|
+
tool: approval.tool,
|
|
532
|
+
input: approval.input,
|
|
533
|
+
...(approval.ruleId ? { ruleId: approval.ruleId } : {}),
|
|
534
|
+
...(approval.reason ? { reason: approval.reason } : {}),
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
261
539
|
async adoptSession(sessionId, title) {
|
|
262
540
|
const res = await fetch(this._hostApi('/chat/conversations'), {
|
|
263
541
|
method: 'POST',
|
|
@@ -272,6 +550,8 @@
|
|
|
272
550
|
|
|
273
551
|
newChat() {
|
|
274
552
|
this.conversationId = null;
|
|
553
|
+
this._disconnect();
|
|
554
|
+
this._resetTurn();
|
|
275
555
|
this._log.replaceChildren();
|
|
276
556
|
this._status.textContent = '';
|
|
277
557
|
this._loadConversations().catch(() => {});
|
|
@@ -283,15 +563,14 @@
|
|
|
283
563
|
this.dispatchEvent(new CustomEvent(`alashi-chat:${name}`, { detail, bubbles: true }));
|
|
284
564
|
}
|
|
285
565
|
|
|
566
|
+
/* Enqueue-only: the 202 carries the conversation id, and the turn itself
|
|
567
|
+
* (including the echo of this message) arrives over the EventSource. */
|
|
286
568
|
async _submit() {
|
|
287
569
|
const message = this._input.value.trim();
|
|
288
570
|
if (!message) return;
|
|
289
571
|
this._input.value = '';
|
|
290
572
|
this._send.disabled = true;
|
|
291
|
-
this._appendMsg(message, 'user');
|
|
292
|
-
const { bubble: reply, note } = this._appendPending();
|
|
293
573
|
this._status.textContent = 'thinking…';
|
|
294
|
-
let replyText = '';
|
|
295
574
|
|
|
296
575
|
try {
|
|
297
576
|
const res = await fetch(this._hostApi('/chat'), {
|
|
@@ -304,58 +583,20 @@
|
|
|
304
583
|
});
|
|
305
584
|
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
|
306
585
|
const isNew = !this.conversationId;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
buffer += decoder.decode(value, { stream: true });
|
|
316
|
-
const frames = buffer.split('\n\n');
|
|
317
|
-
buffer = frames.pop();
|
|
318
|
-
for (const frame of frames) {
|
|
319
|
-
const dataLine = frame.split('\n').find((line) => line.startsWith('data: '));
|
|
320
|
-
if (!dataLine) continue;
|
|
321
|
-
const event = JSON.parse(dataLine.slice(6));
|
|
322
|
-
if (event.type === 'text') {
|
|
323
|
-
replyText += event.text;
|
|
324
|
-
reply.classList.remove('pending');
|
|
325
|
-
this._setMsgText(reply, replyText, 'assistant');
|
|
326
|
-
this._log.scrollTop = this._log.scrollHeight;
|
|
327
|
-
}
|
|
328
|
-
if (event.type === 'tool_use') {
|
|
329
|
-
this._status.textContent = `using tool: ${event.name}`;
|
|
330
|
-
if (reply.classList.contains('pending'))
|
|
331
|
-
note.textContent = `using tool: ${event.name}…`;
|
|
332
|
-
}
|
|
333
|
-
if (event.type === 'result') {
|
|
334
|
-
this._status.textContent = event.ok
|
|
335
|
-
? `done${event.costUsd ? ` ($${event.costUsd.toFixed(4)})` : ''}`
|
|
336
|
-
: `error: ${event.error}`;
|
|
337
|
-
if (!event.ok) {
|
|
338
|
-
this._showError(reply, replyText !== '', `Something went wrong: ${event.error}`);
|
|
339
|
-
} else if (replyText === '') {
|
|
340
|
-
reply.className = 'msg assistant';
|
|
341
|
-
reply.replaceChildren(
|
|
342
|
-
el('span', { className: 'typing-note', textContent: '(no reply)' }),
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
this._emit('turn', {
|
|
346
|
-
ok: event.ok,
|
|
347
|
-
costUsd: event.costUsd,
|
|
348
|
-
error: event.error,
|
|
349
|
-
conversationId: this.conversationId,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
586
|
+
const { conversationId } = await res.json();
|
|
587
|
+
this.conversationId = conversationId || this.conversationId;
|
|
588
|
+
if (isNew) {
|
|
589
|
+
// New conversation: nothing on the bus predates this turn, so replay
|
|
590
|
+
// it all — the submit happened before the stream existed.
|
|
591
|
+
this._connect(this.conversationId, { fromStart: true });
|
|
592
|
+
this._emit('conversation', { conversationId: this.conversationId });
|
|
593
|
+
this._loadConversations().catch(() => {});
|
|
353
594
|
}
|
|
354
|
-
if (isNew) this._loadConversations().catch(() => {});
|
|
355
595
|
} catch (err) {
|
|
356
596
|
this._status.textContent = String(err);
|
|
357
|
-
this.
|
|
358
|
-
|
|
597
|
+
this._appendMsg(message, 'user');
|
|
598
|
+
const { bubble } = this._appendPending();
|
|
599
|
+
this._showError(bubble, false, `Something went wrong: ${String(err)}`);
|
|
359
600
|
this._send.disabled = false;
|
|
360
601
|
this._input.focus();
|
|
361
602
|
}
|
package/assets/v1/system.css
CHANGED
|
@@ -675,3 +675,48 @@ alashi-markdown {
|
|
|
675
675
|
color: var(--muted);
|
|
676
676
|
margin-top: 0.4rem;
|
|
677
677
|
}
|
|
678
|
+
.approval-card {
|
|
679
|
+
border: 1px solid var(--warn, var(--border));
|
|
680
|
+
border-radius: 8px;
|
|
681
|
+
padding: 0.6rem 0.8rem;
|
|
682
|
+
margin: 0.25rem 0 0.6rem;
|
|
683
|
+
font-size: 0.9rem;
|
|
684
|
+
}
|
|
685
|
+
.approval-card .approval-title {
|
|
686
|
+
font-weight: 600;
|
|
687
|
+
margin-bottom: 0.4rem;
|
|
688
|
+
}
|
|
689
|
+
.approval-card .approval-reason {
|
|
690
|
+
color: var(--muted);
|
|
691
|
+
font-size: 0.8rem;
|
|
692
|
+
margin: -0.2rem 0 0.4rem;
|
|
693
|
+
}
|
|
694
|
+
.approval-card pre {
|
|
695
|
+
max-height: 10rem;
|
|
696
|
+
overflow: auto;
|
|
697
|
+
margin: 0 0 0.5rem;
|
|
698
|
+
font-size: 0.8rem;
|
|
699
|
+
}
|
|
700
|
+
.approval-card.resolved {
|
|
701
|
+
border-color: var(--border);
|
|
702
|
+
color: var(--muted);
|
|
703
|
+
}
|
|
704
|
+
.approval-card.resolved .approval-title {
|
|
705
|
+
font-weight: 400;
|
|
706
|
+
margin-bottom: 0;
|
|
707
|
+
font-size: 0.85rem;
|
|
708
|
+
}
|
|
709
|
+
.approval-actions {
|
|
710
|
+
display: flex;
|
|
711
|
+
flex-wrap: wrap;
|
|
712
|
+
gap: 0.4rem;
|
|
713
|
+
}
|
|
714
|
+
.chat-divider {
|
|
715
|
+
color: var(--muted);
|
|
716
|
+
font-size: 0.75rem;
|
|
717
|
+
border-top: 1px dashed var(--border);
|
|
718
|
+
padding-top: 0.25rem;
|
|
719
|
+
margin: 0.4rem auto;
|
|
720
|
+
width: 60%;
|
|
721
|
+
text-align: center;
|
|
722
|
+
}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as UsageRecorder, E as budgetStatus, N as AuthService, O as setAppBudget, b as loadApp, c as installApp, d as updateApp, f as addRegistry, h as removeRegistry, i as resolveAppConfig, n as createServer, o as exportApp, s as importApp, t as Store, u as removeApp } from "./store-
|
|
2
|
+
import { A as UsageRecorder, E as budgetStatus, N as AuthService, O as setAppBudget, b as loadApp, c as installApp, d as updateApp, f as addRegistry, h as removeRegistry, i as resolveAppConfig, n as createServer, o as exportApp, s as importApp, t as Store, u as removeApp } from "./store-DvLX0Kft.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { APP_NAME_PATTERN, AppSdkError } from "@alashi/apps-sdk";
|
|
5
5
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|