@alashi/cli 0.13.0 → 0.15.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 +249 -55
- package/assets/v1/system.css +40 -0
- package/dist/cli.mjs +1 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +272 -78
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{store-DoAGOynU.mjs → store-dipRcfKX.mjs} +815 -47
- package/dist/store-dipRcfKX.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();
|
|
@@ -253,11 +263,232 @@
|
|
|
253
263
|
this._showError(bubble, false, `Failed to load history: ${String(err)}`);
|
|
254
264
|
this._status.textContent = String(err);
|
|
255
265
|
}
|
|
266
|
+
await this._loadPendingApprovals().catch(() => {});
|
|
267
|
+
// Fresh connect: the host replays the in-flight turn (everything before
|
|
268
|
+
// it is already in the history we just rendered), then follows live.
|
|
269
|
+
this._connect(id);
|
|
256
270
|
this._loadConversations().catch(() => {});
|
|
257
271
|
this._emit('conversation', { conversationId: id });
|
|
258
272
|
this._input.focus();
|
|
259
273
|
}
|
|
260
274
|
|
|
275
|
+
/* Standing per-conversation EventSource. `fromStart` replays the whole
|
|
276
|
+
* ring — used right after creating a conversation, whose only events are
|
|
277
|
+
* this first turn. Reconnects resume via Last-Event-ID automatically; a
|
|
278
|
+
* gap beyond the ring arrives as a `resync` event instead. */
|
|
279
|
+
_connect(id, { fromStart = false } = {}) {
|
|
280
|
+
this._disconnect();
|
|
281
|
+
const url = this._hostApi(
|
|
282
|
+
`/chat/${encodeURIComponent(id)}/events${fromStart ? '?lastEventId=0' : ''}`,
|
|
283
|
+
);
|
|
284
|
+
this._events = new EventSource(url);
|
|
285
|
+
this._events.onmessage = (e) => {
|
|
286
|
+
let event;
|
|
287
|
+
try {
|
|
288
|
+
event = JSON.parse(e.data);
|
|
289
|
+
} catch {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (this.conversationId === id) this._handleEvent(event);
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
_disconnect() {
|
|
297
|
+
if (this._events) {
|
|
298
|
+
this._events.close();
|
|
299
|
+
this._events = null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
_resetTurn() {
|
|
304
|
+
this._reply = null;
|
|
305
|
+
this._replyNote = null;
|
|
306
|
+
this._replyText = '';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* The current turn's assistant bubble, created on demand so a turn echoed
|
|
310
|
+
* from another tab renders the same as one submitted here. */
|
|
311
|
+
_ensureReply() {
|
|
312
|
+
if (!this._reply?.isConnected) {
|
|
313
|
+
const { bubble, note } = this._appendPending();
|
|
314
|
+
this._reply = bubble;
|
|
315
|
+
this._replyNote = note;
|
|
316
|
+
this._replyText = '';
|
|
317
|
+
}
|
|
318
|
+
return this._reply;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
_handleEvent(event) {
|
|
322
|
+
if (event.type === 'user_message') {
|
|
323
|
+
this._appendMsg(event.text, 'user');
|
|
324
|
+
this._ensureReply();
|
|
325
|
+
this._status.textContent = 'thinking…';
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (event.type === 'text') {
|
|
329
|
+
const reply = this._ensureReply();
|
|
330
|
+
this._replyText += event.text;
|
|
331
|
+
reply.classList.remove('pending');
|
|
332
|
+
this._setMsgText(reply, this._replyText, 'assistant');
|
|
333
|
+
this._log.scrollTop = this._log.scrollHeight;
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (event.type === 'tool_use') {
|
|
337
|
+
this._status.textContent = `using tool: ${event.name}`;
|
|
338
|
+
if (this._reply?.classList.contains('pending'))
|
|
339
|
+
this._replyNote.textContent = `using tool: ${event.name}…`;
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (event.type === 'approval_required') {
|
|
343
|
+
this._renderApproval(event);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (event.type === 'approval_resolved') {
|
|
347
|
+
this._resolveApproval(event);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (event.type === 'compaction') {
|
|
351
|
+
this._log.append(
|
|
352
|
+
el('div', {
|
|
353
|
+
className: 'chat-divider',
|
|
354
|
+
textContent: `context compacted · ${event.trigger}`,
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
this._log.scrollTop = this._log.scrollHeight;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (event.type === 'resync') {
|
|
361
|
+
// Missed too much to replay: rebuild from history + pending snapshot.
|
|
362
|
+
this.openConversation(this.conversationId).catch(() => {});
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (event.type === 'result') {
|
|
366
|
+
this._status.textContent = event.ok
|
|
367
|
+
? `done${event.costUsd ? ` ($${event.costUsd.toFixed(4)})` : ''}`
|
|
368
|
+
: `error: ${event.error}`;
|
|
369
|
+
const reply = this._ensureReply();
|
|
370
|
+
if (!event.ok) {
|
|
371
|
+
this._showError(reply, this._replyText !== '', `Something went wrong: ${event.error}`);
|
|
372
|
+
} else if (this._replyText === '') {
|
|
373
|
+
reply.className = 'msg assistant';
|
|
374
|
+
reply.replaceChildren(
|
|
375
|
+
el('span', { className: 'typing-note', textContent: '(no reply)' }),
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
this._emit('turn', {
|
|
379
|
+
ok: event.ok,
|
|
380
|
+
costUsd: event.costUsd,
|
|
381
|
+
error: event.error,
|
|
382
|
+
conversationId: this.conversationId,
|
|
383
|
+
});
|
|
384
|
+
this._resetTurn();
|
|
385
|
+
this._send.disabled = false;
|
|
386
|
+
this._input.focus();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* Inline approval card: tool, input preview, and the four decisions. The
|
|
391
|
+
* decision travels over the respond endpoint; the card collapses when the
|
|
392
|
+
* matching approval_resolved event comes back on the bus (so every tab
|
|
393
|
+
* collapses, not just the one that clicked). */
|
|
394
|
+
_renderApproval(event) {
|
|
395
|
+
if (this._approvalCard(event.approvalId)) return; // snapshot + replay overlap
|
|
396
|
+
const card = el('div', { className: 'approval-card' });
|
|
397
|
+
card.dataset.approvalId = event.approvalId;
|
|
398
|
+
card.dataset.tool = event.tool;
|
|
399
|
+
let preview = '';
|
|
400
|
+
try {
|
|
401
|
+
preview = JSON.stringify(event.input, null, 2);
|
|
402
|
+
} catch {
|
|
403
|
+
preview = String(event.input);
|
|
404
|
+
}
|
|
405
|
+
if (preview && preview.length > 2000) preview = `${preview.slice(0, 2000)}…`;
|
|
406
|
+
const actions = el('div', { className: 'approval-actions' });
|
|
407
|
+
const act = (label, body, primary) => {
|
|
408
|
+
const button = el('button', {
|
|
409
|
+
type: 'button',
|
|
410
|
+
textContent: label,
|
|
411
|
+
className: primary ? 'primary' : '',
|
|
412
|
+
});
|
|
413
|
+
button.addEventListener('click', () => {
|
|
414
|
+
for (const b of actions.querySelectorAll('button')) b.disabled = true;
|
|
415
|
+
this._respondToApproval(event.approvalId, body).catch(() => {
|
|
416
|
+
for (const b of actions.querySelectorAll('button')) b.disabled = false;
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
actions.append(button);
|
|
420
|
+
};
|
|
421
|
+
act('Approve', { behavior: 'allow' }, true);
|
|
422
|
+
act('Approve for session', { behavior: 'allow', scope: 'session' });
|
|
423
|
+
act('Always allow', { behavior: 'allow', scope: 'always' });
|
|
424
|
+
act('Deny', { behavior: 'deny' });
|
|
425
|
+
card.append(
|
|
426
|
+
el('div', {
|
|
427
|
+
className: 'approval-title',
|
|
428
|
+
textContent: `Approval required: ${event.tool}`,
|
|
429
|
+
}),
|
|
430
|
+
...(preview ? [el('pre', {}, [el('code', { textContent: preview })])] : []),
|
|
431
|
+
actions,
|
|
432
|
+
);
|
|
433
|
+
// Keep the typing indicator visually last while the engine is suspended.
|
|
434
|
+
if (this._reply?.isConnected) this._log.insertBefore(card, this._reply);
|
|
435
|
+
else this._log.append(card);
|
|
436
|
+
this._log.scrollTop = this._log.scrollHeight;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
_approvalCard(approvalId) {
|
|
440
|
+
for (const card of this._log.querySelectorAll('.approval-card')) {
|
|
441
|
+
if (card.dataset.approvalId === approvalId) return card;
|
|
442
|
+
}
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async _respondToApproval(approvalId, body) {
|
|
447
|
+
const res = await fetch(
|
|
448
|
+
this._hostApi(
|
|
449
|
+
`/chat/${encodeURIComponent(this.conversationId)}/approvals/${encodeURIComponent(approvalId)}`,
|
|
450
|
+
),
|
|
451
|
+
{
|
|
452
|
+
method: 'POST',
|
|
453
|
+
headers: { 'content-type': 'application/json' },
|
|
454
|
+
body: JSON.stringify(body),
|
|
455
|
+
},
|
|
456
|
+
);
|
|
457
|
+
if (!res.ok && res.status !== 409) throw new Error(`HTTP ${res.status}`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
_resolveApproval(event) {
|
|
461
|
+
const card = this._approvalCard(event.approvalId);
|
|
462
|
+
if (!card) return;
|
|
463
|
+
const verdict = event.behavior === 'allow' ? 'Approved' : 'Denied';
|
|
464
|
+
const why =
|
|
465
|
+
event.reason && event.reason !== 'user' ? ` · ${event.reason.replace('_', ' ')}` : '';
|
|
466
|
+
card.className = 'approval-card resolved';
|
|
467
|
+
card.replaceChildren(
|
|
468
|
+
el('div', {
|
|
469
|
+
className: 'approval-title',
|
|
470
|
+
textContent: `${verdict}: ${card.dataset.tool}${why}`,
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async _loadPendingApprovals() {
|
|
476
|
+
if (!this.conversationId) return;
|
|
477
|
+
const res = await fetch(
|
|
478
|
+
this._hostApi(`/chat/${encodeURIComponent(this.conversationId)}/pending-approvals`),
|
|
479
|
+
);
|
|
480
|
+
if (!res.ok) return;
|
|
481
|
+
const { approvals } = await res.json();
|
|
482
|
+
for (const approval of approvals) {
|
|
483
|
+
this._renderApproval({
|
|
484
|
+
type: 'approval_required',
|
|
485
|
+
approvalId: approval.approvalId,
|
|
486
|
+
tool: approval.tool,
|
|
487
|
+
input: approval.input,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
261
492
|
async adoptSession(sessionId, title) {
|
|
262
493
|
const res = await fetch(this._hostApi('/chat/conversations'), {
|
|
263
494
|
method: 'POST',
|
|
@@ -272,6 +503,8 @@
|
|
|
272
503
|
|
|
273
504
|
newChat() {
|
|
274
505
|
this.conversationId = null;
|
|
506
|
+
this._disconnect();
|
|
507
|
+
this._resetTurn();
|
|
275
508
|
this._log.replaceChildren();
|
|
276
509
|
this._status.textContent = '';
|
|
277
510
|
this._loadConversations().catch(() => {});
|
|
@@ -283,15 +516,14 @@
|
|
|
283
516
|
this.dispatchEvent(new CustomEvent(`alashi-chat:${name}`, { detail, bubbles: true }));
|
|
284
517
|
}
|
|
285
518
|
|
|
519
|
+
/* Enqueue-only: the 202 carries the conversation id, and the turn itself
|
|
520
|
+
* (including the echo of this message) arrives over the EventSource. */
|
|
286
521
|
async _submit() {
|
|
287
522
|
const message = this._input.value.trim();
|
|
288
523
|
if (!message) return;
|
|
289
524
|
this._input.value = '';
|
|
290
525
|
this._send.disabled = true;
|
|
291
|
-
this._appendMsg(message, 'user');
|
|
292
|
-
const { bubble: reply, note } = this._appendPending();
|
|
293
526
|
this._status.textContent = 'thinking…';
|
|
294
|
-
let replyText = '';
|
|
295
527
|
|
|
296
528
|
try {
|
|
297
529
|
const res = await fetch(this._hostApi('/chat'), {
|
|
@@ -304,58 +536,20 @@
|
|
|
304
536
|
});
|
|
305
537
|
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
|
306
538
|
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
|
-
}
|
|
539
|
+
const { conversationId } = await res.json();
|
|
540
|
+
this.conversationId = conversationId || this.conversationId;
|
|
541
|
+
if (isNew) {
|
|
542
|
+
// New conversation: nothing on the bus predates this turn, so replay
|
|
543
|
+
// it all — the submit happened before the stream existed.
|
|
544
|
+
this._connect(this.conversationId, { fromStart: true });
|
|
545
|
+
this._emit('conversation', { conversationId: this.conversationId });
|
|
546
|
+
this._loadConversations().catch(() => {});
|
|
353
547
|
}
|
|
354
|
-
if (isNew) this._loadConversations().catch(() => {});
|
|
355
548
|
} catch (err) {
|
|
356
549
|
this._status.textContent = String(err);
|
|
357
|
-
this.
|
|
358
|
-
|
|
550
|
+
this._appendMsg(message, 'user');
|
|
551
|
+
const { bubble } = this._appendPending();
|
|
552
|
+
this._showError(bubble, false, `Something went wrong: ${String(err)}`);
|
|
359
553
|
this._send.disabled = false;
|
|
360
554
|
this._input.focus();
|
|
361
555
|
}
|
package/assets/v1/system.css
CHANGED
|
@@ -675,3 +675,43 @@ 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 pre {
|
|
690
|
+
max-height: 10rem;
|
|
691
|
+
overflow: auto;
|
|
692
|
+
margin: 0 0 0.5rem;
|
|
693
|
+
font-size: 0.8rem;
|
|
694
|
+
}
|
|
695
|
+
.approval-card.resolved {
|
|
696
|
+
border-color: var(--border);
|
|
697
|
+
color: var(--muted);
|
|
698
|
+
}
|
|
699
|
+
.approval-card.resolved .approval-title {
|
|
700
|
+
font-weight: 400;
|
|
701
|
+
margin-bottom: 0;
|
|
702
|
+
font-size: 0.85rem;
|
|
703
|
+
}
|
|
704
|
+
.approval-actions {
|
|
705
|
+
display: flex;
|
|
706
|
+
flex-wrap: wrap;
|
|
707
|
+
gap: 0.4rem;
|
|
708
|
+
}
|
|
709
|
+
.chat-divider {
|
|
710
|
+
color: var(--muted);
|
|
711
|
+
font-size: 0.75rem;
|
|
712
|
+
border-top: 1px dashed var(--border);
|
|
713
|
+
padding-top: 0.25rem;
|
|
714
|
+
margin: 0.4rem auto;
|
|
715
|
+
width: 60%;
|
|
716
|
+
text-align: center;
|
|
717
|
+
}
|
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-dipRcfKX.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";
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/banner.ts","../src/create.ts","../src/cli.ts"],"sourcesContent":["import { homedir } from 'node:os';\n\n/** Everything the serve banner reports about the freshly booted host. */\nexport interface BannerInfo {\n version: string;\n /** Provider name — the \"kernel\" in OS terms (e.g. \"claude-code\"). */\n kernel: string;\n appCount: number;\n jobCount: number;\n stateDir: string;\n url: string;\n /** Auth warnings / sign-in URLs, printed after the stats block. */\n notes?: string[];\n}\n\n// Palette from alashi-dev.github.io: cyan #8fd8d8, cyan-hi #4fe0d2, dim #7e93ab.\nconst THEME = {\n logo: { rgb: [143, 216, 216], ansi: '36' },\n accent: { rgb: [79, 224, 210], ansi: '96' },\n dim: { rgb: [126, 147, 171], ansi: '90' },\n} as const;\n\ntype Tone = keyof typeof THEME;\n\nfunction makeTint(color: boolean): (tone: Tone, text: string) => string {\n if (!color) return (_tone, text) => text;\n const truecolor = /truecolor|24bit/i.test(process.env.COLORTERM ?? '');\n return (tone, text) => {\n const { rgb, ansi } = THEME[tone];\n const open = truecolor ? `\\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m` : `\\x1b[${ansi}m`;\n return `${open}${text}\\x1b[0m`;\n };\n}\n\nconst LOGO = [\n ' _ _ _',\n ' __ _| |__ _ __| |_ (_)',\n ' / _` | / _` (_-< \\' \\\\| |',\n ' \\\\__,_|_\\\\__,_/__/_||_|_|',\n];\n\nfunction tilde(path: string): string {\n const home = homedir();\n return path === home || path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;\n}\n\n/** Pure layout; printBanner adds the timing. Exported for tests. */\nexport function renderBanner(info: BannerInfo, color: boolean): string[] {\n const tint = makeTint(color);\n const jobs = `${info.jobCount} ${info.jobCount === 1 ? 'job' : 'jobs'} on cron`;\n const stat = (label: string, value: string): string =>\n ` ${tint('dim', label.padEnd(12))}${value}`;\n return [\n ...LOGO.slice(0, -1).map((row) => tint('accent', row)),\n `${tint('accent', LOGO[LOGO.length - 1] ?? '')} ${tint('dim', `アラシ v${info.version}`)}`,\n tint('dim', ' the harness is the kernel. this is the OS.'),\n '',\n stat('kernel', tint('logo', info.kernel)),\n stat('apps', `${info.appCount} installed · ${jobs}`),\n stat('state', tilde(info.stateDir)),\n stat('dashboard', tint('accent', info.url)),\n ...(info.notes ?? []).map((note) => ` ${note}`),\n '',\n ];\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((done) => setTimeout(done, ms));\n\n/**\n * Boot banner for `alashi serve`. On an interactive terminal the lines reveal\n * one by one (logo fast, stats slower); piped or NO_COLOR output gets the same\n * text instantly and uncolored, so logs and greps stay clean.\n */\nexport async function printBanner(\n info: BannerInfo,\n out: NodeJS.WriteStream = process.stdout,\n): Promise<void> {\n const color = out.isTTY === true && process.env.NO_COLOR === undefined;\n const animate = color && process.env.CI === undefined;\n const lines = renderBanner(info, color);\n for (const [index, line] of lines.entries()) {\n out.write(`${line}\\n`);\n if (animate) await sleep(index < LOGO.length ? 30 : 65);\n }\n}\n","import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { APP_NAME_PATTERN, AppSdkError } from '@alashi/apps-sdk';\n\n// Works from both src/ (tests) and dist/ (published build) — the templates\n// dir sits one level up in the package root either way.\nconst TEMPLATE_DIR = fileURLToPath(new URL('../templates/basic-app', import.meta.url));\n\n// npm strips .gitignore from published packages, so it ships unprefixed.\nconst RENAMES: Record<string, string> = { gitignore: '.gitignore' };\n\n/**\n * Scaffolds a new app from the basic-app template into targetDir,\n * substituting the app name. Returns the created files, relative to\n * targetDir. Refuses to write into a non-empty directory.\n */\nexport async function scaffoldApp(name: string, targetDir: string): Promise<string[]> {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(\n `Invalid app name \"${name}\": use lowercase letters, digits, and hyphens (max 64 chars)`,\n );\n }\n const existing = await readdir(targetDir).catch(() => undefined);\n if (existing && existing.length > 0) {\n throw new AppSdkError(`Directory is not empty: ${targetDir}`);\n }\n\n const written: string[] = [];\n async function copyDir(rel: string): Promise<void> {\n const entries = await readdir(join(TEMPLATE_DIR, rel), { withFileTypes: true });\n await mkdir(join(targetDir, rel), { recursive: true });\n for (const entry of entries) {\n const relPath = join(rel, entry.name);\n if (entry.isDirectory()) {\n await copyDir(relPath);\n continue;\n }\n const content = await readFile(join(TEMPLATE_DIR, relPath), 'utf8');\n const outPath = join(rel, RENAMES[entry.name] ?? entry.name);\n await writeFile(join(targetDir, outPath), content.replaceAll('__APP_NAME__', name), 'utf8');\n written.push(outPath);\n }\n }\n await copyDir('');\n return written.sort();\n}\n","#!/usr/bin/env node\nimport { createRequire } from 'node:module';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { ClaudeCodeProvider } from '@alashi/provider-claude';\nimport { serve } from '@hono/node-server';\nimport { Command } from 'commander';\nimport { AuthService } from './auth.js';\nimport { printBanner } from './banner.js';\nimport { budgetStatus, setAppBudget } from './budget.js';\nimport { scaffoldApp } from './create.js';\nimport { installApp, removeApp, updateApp } from './installer.js';\nimport { loadApp } from './loader.js';\nimport { exportApp, importApp } from './portability.js';\nimport { addRegistry, removeRegistry } from './registry.js';\nimport { resolveAppConfig } from './runtime.js';\nimport { createServer } from './server.js';\nimport { Store } from './store.js';\nimport { UsageRecorder } from './usage.js';\n\nconst program = new Command();\nconst store = new Store();\n\nprogram.name('alashi').description('Install, manage, and run Claude Code-powered apps');\n\nasync function serveDashboard(portArg?: string, hostArg?: string): Promise<void> {\n const config = await store.readGlobalConfig();\n const port = portArg ? Number(portArg) : (config.port ?? 4141);\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new Error(`Invalid port: ${portArg}`);\n }\n // Loopback by default: only an explicit --host (or config) exposes the\n // dashboard beyond this machine, and then auth is in front of it.\n const hostname = hostArg ?? config.host ?? '127.0.0.1';\n const loopback = hostname === '127.0.0.1' || hostname === 'localhost' || hostname === '::1';\n const mode = config.auth?.mode ?? 'auto';\n const provider = new ClaudeCodeProvider();\n const server = createServer(store, provider);\n await server.scheduler.start();\n const appCount = (await store.listInstalled()).length;\n const { version } = createRequire(import.meta.url)('../package.json') as { version: string };\n const httpServer = serve({ fetch: server.app.fetch, port, hostname }, (info) => {\n const notes: string[] = [];\n if (mode === 'off' && !loopback) {\n notes.push(\n `WARNING: listening on ${hostname} with auth.mode \"off\" — anyone who can reach this address owns the host`,\n );\n }\n // Local browsing on `auto` needs no credential (the console heuristic\n // covers it); print a login URL whenever something else will need one.\n if (mode === 'required' || (mode === 'auto' && !loopback)) {\n const secret = server.auth.issueBootToken();\n notes.push(`sign in: http://localhost:${info.port}/login?token=${secret}`);\n notes.push('(one-time URL — mint reusable tokens with `alashi token create`)');\n }\n void printBanner({\n version,\n kernel: provider.name,\n appCount,\n jobCount: server.scheduler.jobCount,\n stateDir: store.root,\n url: `http://localhost:${info.port}`,\n notes,\n });\n });\n const shutdown = (): void => {\n httpServer.close();\n void server.close().finally(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nprogram\n .command('serve', { isDefault: true })\n .option('-p, --port <port>', 'port to listen on (default: config port or 4141)')\n .option(\n '--host <host>',\n 'address to bind (default: 127.0.0.1; non-loopback binds require sign-in)',\n )\n .description('Serve the web dashboard (the default when no command is given)')\n .action(async (options: { port?: string; host?: string }) =>\n serveDashboard(options.port, options.host),\n );\n\nprogram\n .command('create')\n .argument('<name>', 'app name (lowercase letters, digits, hyphens)')\n .argument('[directory]', 'target directory (default: ./<name>)')\n .description('Scaffold a new app from the basic template')\n .action(async (name: string, directory?: string) => {\n const target = directory ?? name;\n const files = await scaffoldApp(name, resolve(target));\n console.log(`Created ${name}/`);\n for (const file of files) console.log(` ${file}`);\n console.log('\\nNext steps:');\n console.log(` cd ${target}`);\n console.log(' npm install');\n console.log(' alashi install .');\n console.log(' alashi # dashboard at http://localhost:4141');\n });\n\nprogram\n .command('install')\n .argument('<source>', 'app name (via registry), git URL, or local path')\n .description('Install an app')\n .action(async (source: string) => {\n const { record, warnings } = await installApp(store, source);\n console.log(`Installed ${record.name}@${record.version} (${record.source.type})`);\n console.log(` data: ${store.appDataDir(record.name)}`);\n console.log(` config: ${store.appConfigPath(record.name)}`);\n printManifestWarnings(warnings);\n await printSetupHints(record.name);\n });\n\n/**\n * Unknown manifest keys never fail a command — the host strips them and runs.\n * Surfacing them here is how an author notices a typo or a too-new field at\n * install time instead of only in the server log. Making them an *error* for\n * authors is `alashi validate` (card #22).\n */\nfunction printManifestWarnings(warnings: string[]): void {\n for (const warning of warnings) console.log(` warning: ${warning}`);\n}\n\n/** After install: what still needs configuring, and which integrations exist. */\nasync function printSetupHints(name: string): Promise<void> {\n let manifest: Awaited<ReturnType<typeof loadApp>>['manifest'];\n try {\n // Warnings were already printed from the install result; stay quiet here.\n ({ manifest } = await loadApp(store.appRepoDir(name), { warn: () => {} }));\n } catch {\n return; // install already succeeded; hints are best-effort\n }\n const { status } = await resolveAppConfig(store, manifest);\n if (status.missing.length > 0) {\n console.log(` requires: ${status.missing.join(', ')}`);\n }\n const optional = status.integrations.filter((i) => i.optional && !i.configured);\n if (optional.length > 0) {\n const list = optional.map((i) => `${i.name} (needs ${i.missing.join(', ')})`).join(', ');\n console.log(` optional integrations: ${list}`);\n }\n if (status.missing.length > 0 || optional.length > 0) {\n console.log(` configure via the dashboard settings page or the config file above`);\n }\n}\n\nprogram\n .command('update')\n .argument('<name>', 'installed app name')\n .description('Update an installed app')\n .action(async (name: string) => {\n const { record, warnings } = await updateApp(store, name);\n console.log(`Updated ${record.name} to ${record.version}`);\n printManifestWarnings(warnings);\n });\n\nprogram\n .command('remove')\n .argument('<name>', 'installed app name')\n .option('--purge', 'also delete the app data and config')\n .description('Remove an installed app (keeps data unless --purge)')\n .action(async (name: string, options: { purge?: boolean }) => {\n await removeApp(store, name, { purge: options.purge ?? false });\n console.log(options.purge ? `Removed ${name} (purged)` : `Removed ${name} (data kept)`);\n });\n\n/** Prints archive paths the way the user would type them. */\nfunction displayPath(path: string): string {\n const rel = relative(process.cwd(), path);\n return rel === '' || rel.startsWith('..') || isAbsolute(rel) ? path : `./${rel}`;\n}\n\nprogram\n .command('export')\n .argument('<name>', 'installed app name')\n .option('-o, --out <file>', 'archive path (default: ./<name>-<version>.tar.gz)')\n .description('Pack an app container and its chat transcripts into a tar.gz')\n .action(async (name: string, options: { out?: string }) => {\n const result = await exportApp(store, new ClaudeCodeProvider(), name, {\n ...(options.out && { out: options.out }),\n });\n for (const warning of result.warnings) console.log(` warning: ${warning}`);\n console.log(\n `Exported ${result.record.name}@${result.record.version} -> ${displayPath(result.archivePath)} (${result.transcriptCount} transcripts)`,\n );\n if (result.record.source.type === 'local') {\n console.log(' note: repo/ was captured as real files, not as the local symlink');\n }\n });\n\n// `import` is a reserved word, so the handler cannot be named after it; the\n// commander command string itself is fine.\nasync function importArchive(file: string): Promise<void> {\n const result = await importApp(store, new ClaudeCodeProvider(), file);\n for (const warning of result.warnings) console.log(` warning: ${warning}`);\n console.log(\n `Imported ${result.record.name}@${result.record.version} (${result.transcriptCount} transcripts restored)`,\n );\n console.log(` data: ${store.appDataDir(result.record.name)}`);\n console.log(` config: ${store.appConfigPath(result.record.name)}`);\n}\n\nprogram\n .command('import')\n .argument('<file>', 'archive produced by alashi export')\n .description('Restore an app container and its chat transcripts from a tar.gz')\n .action(importArchive);\n\nprogram\n .command('list')\n .option('--json', 'machine-readable output')\n .description('List installed apps')\n .action(async (options: { json?: boolean }) => {\n const usage = new UsageRecorder(store);\n const records = await store.listInstalled();\n const spend = new Map(\n await Promise.all(\n records.map(\n async (record) => [record.name, await budgetStatus(store, usage, record.name)] as const,\n ),\n ),\n );\n if (options.json) {\n console.log(\n JSON.stringify(\n records.map((record) => {\n const budget = spend.get(record.name);\n return {\n ...record,\n todayCostUsd: budget?.spentUsd ?? 0,\n recorded: budget?.recorded ?? false,\n overBudget: budget?.overBudget ?? false,\n ...(budget?.dailyUsd !== undefined && { dailyBudgetUsd: budget.dailyUsd }),\n };\n }),\n null,\n 2,\n ),\n );\n return;\n }\n if (records.length === 0) {\n console.log('No apps installed. Try: alashi install <name|git-url|path>');\n return;\n }\n for (const record of records) {\n const origin = record.source.type === 'git' ? record.source.url : record.source.path;\n const budget = spend.get(record.name);\n const cap =\n budget?.dailyUsd === undefined\n ? ''\n : ` of $${budget.dailyUsd.toFixed(2)} cap${budget.overBudget ? ' (OVER BUDGET)' : ''}`;\n // An em dash where the host has no ledger for the app at all; a real\n // $0.0000 only once something has actually been metered.\n const today = budget?.recorded ? `$${budget.spentUsd.toFixed(4)} today` : '— today';\n console.log(\n `${record.name}@${record.version} ${record.source.type} ${origin} ${today}${cap}`,\n );\n }\n });\n\nprogram\n .command('budget')\n .argument('<name>', 'installed app name')\n .argument('[dailyUsd]', 'daily cap in USD, or \"none\" to remove it (omit to just show)')\n .description(\"Show or set an app's host-enforced daily AI spend cap\")\n .action(async (name: string, dailyUsd?: string) => {\n if (!(await store.readAppRecord(name))) throw new Error(`App \"${name}\" is not installed`);\n if (dailyUsd !== undefined) {\n const cleared = dailyUsd === 'none' || dailyUsd === 'off';\n const amount = cleared ? undefined : Number(dailyUsd);\n if (amount !== undefined && (!Number.isFinite(amount) || amount < 0)) {\n throw new Error(`Invalid daily spend cap: ${dailyUsd}`);\n }\n await setAppBudget(store, name, amount);\n }\n const status = await budgetStatus(store, new UsageRecorder(store), name);\n // \"Never metered\" is not \"spent nothing\": the host counts only sessions the\n // app opens through ctx.ai, so say so rather than printing a made-up zero.\n const spent = status.recorded\n ? `$${status.spentUsd.toFixed(4)} spent on ${status.date} (UTC)`\n : 'no spend recorded yet (the host meters only sessions opened through ctx.ai)';\n if (status.dailyUsd === undefined) {\n console.log(`${name}: no daily cap — ${spent}`);\n return;\n }\n console.log(\n `${name}: $${status.dailyUsd.toFixed(2)} daily cap — ${spent}, $${(status.remainingUsd ?? 0).toFixed(4)} left`,\n );\n if (status.overBudget)\n console.log(' OVER BUDGET: new AI sessions are refused until 00:00 UTC');\n });\n\nconst token = program\n .command('token')\n .description('Manage owner bearer tokens for the dashboard/API (see config.auth)');\n\ntoken\n .command('create')\n .argument('[label]', 'a name for the token (e.g. \"laptop\", \"ci\")')\n .description('Mint an owner token; the secret is printed once and never stored')\n .action(async (label?: string) => {\n const { id, secret } = await new AuthService(store).mintToken(label);\n console.log(secret);\n console.log(\n ` id: ${id}${label ? ` (${label})` : ''} — sign in at /login or send it as \"Authorization: Bearer ...\"`,\n );\n });\n\ntoken\n .command('list')\n .description('List tokens (ids and labels only; secrets are never stored)')\n .action(async () => {\n const tokens = await new AuthService(store).listTokens();\n if (tokens.length === 0) {\n console.log('No tokens. Mint one with `alashi token create [label]`.');\n return;\n }\n for (const entry of tokens) {\n const label = entry.label ? ` ${entry.label}` : '';\n const used = entry.lastUsedAt ? `, last used ${entry.lastUsedAt}` : ', never used';\n console.log(`${entry.id}${label} — created ${entry.createdAt}${used}`);\n }\n });\n\ntoken\n .command('revoke')\n .argument('<id>', 'token id (see `alashi token list`)')\n .description('Revoke a token')\n .action(async (id: string) => {\n if (!(await new AuthService(store).revokeToken(id))) {\n throw new Error(`No token with id \"${id}\"`);\n }\n console.log(`Revoked ${id}`);\n });\n\nconst registry = program.command('registry').description('Manage app registries');\n\nregistry\n .command('add')\n .argument('<name>', 'registry name')\n .argument('<url>', 'index.json URL or git repo URL')\n .action(async (name: string, url: string) => {\n const index = await addRegistry(store, name, url);\n console.log(`Added registry \"${name}\" (${index.apps.length} apps)`);\n });\n\nregistry\n .command('remove')\n .argument('<name>', 'registry name')\n .action(async (name: string) => {\n await removeRegistry(store, name);\n console.log(`Removed registry \"${name}\"`);\n });\n\nregistry.command('list').action(async () => {\n const config = await store.readGlobalConfig();\n if (config.registries.length === 0) {\n console.log('No registries configured.');\n return;\n }\n for (const r of config.registries) console.log(`${r.name} ${r.url}`);\n});\n\nprogram.parseAsync().catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : err);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/banner.ts","../src/create.ts","../src/cli.ts"],"sourcesContent":["import { homedir } from 'node:os';\n\n/** Everything the serve banner reports about the freshly booted host. */\nexport interface BannerInfo {\n version: string;\n /** Provider name — the \"kernel\" in OS terms (e.g. \"claude-code\"). */\n kernel: string;\n appCount: number;\n jobCount: number;\n stateDir: string;\n url: string;\n /** Auth warnings / sign-in URLs, printed after the stats block. */\n notes?: string[];\n}\n\n// Palette from alashi-dev.github.io: cyan #8fd8d8, cyan-hi #4fe0d2, dim #7e93ab.\nconst THEME = {\n logo: { rgb: [143, 216, 216], ansi: '36' },\n accent: { rgb: [79, 224, 210], ansi: '96' },\n dim: { rgb: [126, 147, 171], ansi: '90' },\n} as const;\n\ntype Tone = keyof typeof THEME;\n\nfunction makeTint(color: boolean): (tone: Tone, text: string) => string {\n if (!color) return (_tone, text) => text;\n const truecolor = /truecolor|24bit/i.test(process.env.COLORTERM ?? '');\n return (tone, text) => {\n const { rgb, ansi } = THEME[tone];\n const open = truecolor ? `\\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m` : `\\x1b[${ansi}m`;\n return `${open}${text}\\x1b[0m`;\n };\n}\n\nconst LOGO = [\n ' _ _ _',\n ' __ _| |__ _ __| |_ (_)',\n \" / _` | / _` (_-< ' \\\\| |\",\n ' \\\\__,_|_\\\\__,_/__/_||_|_|',\n];\n\nfunction tilde(path: string): string {\n const home = homedir();\n return path === home || path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;\n}\n\n/** Pure layout; printBanner adds the timing. Exported for tests. */\nexport function renderBanner(info: BannerInfo, color: boolean): string[] {\n const tint = makeTint(color);\n const jobs = `${info.jobCount} ${info.jobCount === 1 ? 'job' : 'jobs'} on cron`;\n const stat = (label: string, value: string): string =>\n ` ${tint('dim', label.padEnd(12))}${value}`;\n return [\n ...LOGO.slice(0, -1).map((row) => tint('accent', row)),\n `${tint('accent', LOGO[LOGO.length - 1] ?? '')} ${tint('dim', `アラシ v${info.version}`)}`,\n tint('dim', ' the harness is the kernel. this is the OS.'),\n '',\n stat('kernel', tint('logo', info.kernel)),\n stat('apps', `${info.appCount} installed · ${jobs}`),\n stat('state', tilde(info.stateDir)),\n stat('dashboard', tint('accent', info.url)),\n ...(info.notes ?? []).map((note) => ` ${note}`),\n '',\n ];\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((done) => setTimeout(done, ms));\n\n/**\n * Boot banner for `alashi serve`. On an interactive terminal the lines reveal\n * one by one (logo fast, stats slower); piped or NO_COLOR output gets the same\n * text instantly and uncolored, so logs and greps stay clean.\n */\nexport async function printBanner(\n info: BannerInfo,\n out: NodeJS.WriteStream = process.stdout,\n): Promise<void> {\n const color = out.isTTY === true && process.env.NO_COLOR === undefined;\n const animate = color && process.env.CI === undefined;\n const lines = renderBanner(info, color);\n for (const [index, line] of lines.entries()) {\n out.write(`${line}\\n`);\n if (animate) await sleep(index < LOGO.length ? 30 : 65);\n }\n}\n","import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { APP_NAME_PATTERN, AppSdkError } from '@alashi/apps-sdk';\n\n// Works from both src/ (tests) and dist/ (published build) — the templates\n// dir sits one level up in the package root either way.\nconst TEMPLATE_DIR = fileURLToPath(new URL('../templates/basic-app', import.meta.url));\n\n// npm strips .gitignore from published packages, so it ships unprefixed.\nconst RENAMES: Record<string, string> = { gitignore: '.gitignore' };\n\n/**\n * Scaffolds a new app from the basic-app template into targetDir,\n * substituting the app name. Returns the created files, relative to\n * targetDir. Refuses to write into a non-empty directory.\n */\nexport async function scaffoldApp(name: string, targetDir: string): Promise<string[]> {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(\n `Invalid app name \"${name}\": use lowercase letters, digits, and hyphens (max 64 chars)`,\n );\n }\n const existing = await readdir(targetDir).catch(() => undefined);\n if (existing && existing.length > 0) {\n throw new AppSdkError(`Directory is not empty: ${targetDir}`);\n }\n\n const written: string[] = [];\n async function copyDir(rel: string): Promise<void> {\n const entries = await readdir(join(TEMPLATE_DIR, rel), { withFileTypes: true });\n await mkdir(join(targetDir, rel), { recursive: true });\n for (const entry of entries) {\n const relPath = join(rel, entry.name);\n if (entry.isDirectory()) {\n await copyDir(relPath);\n continue;\n }\n const content = await readFile(join(TEMPLATE_DIR, relPath), 'utf8');\n const outPath = join(rel, RENAMES[entry.name] ?? entry.name);\n await writeFile(join(targetDir, outPath), content.replaceAll('__APP_NAME__', name), 'utf8');\n written.push(outPath);\n }\n }\n await copyDir('');\n return written.sort();\n}\n","#!/usr/bin/env node\nimport { createRequire } from 'node:module';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { ClaudeCodeProvider } from '@alashi/provider-claude';\nimport { serve } from '@hono/node-server';\nimport { Command } from 'commander';\nimport { AuthService } from './auth.js';\nimport { printBanner } from './banner.js';\nimport { budgetStatus, setAppBudget } from './budget.js';\nimport { scaffoldApp } from './create.js';\nimport { installApp, removeApp, updateApp } from './installer.js';\nimport { loadApp } from './loader.js';\nimport { exportApp, importApp } from './portability.js';\nimport { addRegistry, removeRegistry } from './registry.js';\nimport { resolveAppConfig } from './runtime.js';\nimport { createServer } from './server.js';\nimport { Store } from './store.js';\nimport { UsageRecorder } from './usage.js';\n\nconst program = new Command();\nconst store = new Store();\n\nprogram.name('alashi').description('Install, manage, and run Claude Code-powered apps');\n\nasync function serveDashboard(portArg?: string, hostArg?: string): Promise<void> {\n const config = await store.readGlobalConfig();\n const port = portArg ? Number(portArg) : (config.port ?? 4141);\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new Error(`Invalid port: ${portArg}`);\n }\n // Loopback by default: only an explicit --host (or config) exposes the\n // dashboard beyond this machine, and then auth is in front of it.\n const hostname = hostArg ?? config.host ?? '127.0.0.1';\n const loopback = hostname === '127.0.0.1' || hostname === 'localhost' || hostname === '::1';\n const mode = config.auth?.mode ?? 'auto';\n const provider = new ClaudeCodeProvider();\n const server = createServer(store, provider);\n await server.scheduler.start();\n const appCount = (await store.listInstalled()).length;\n const { version } = createRequire(import.meta.url)('../package.json') as { version: string };\n const httpServer = serve({ fetch: server.app.fetch, port, hostname }, (info) => {\n const notes: string[] = [];\n if (mode === 'off' && !loopback) {\n notes.push(\n `WARNING: listening on ${hostname} with auth.mode \"off\" — anyone who can reach this address owns the host`,\n );\n }\n // Local browsing on `auto` needs no credential (the console heuristic\n // covers it); print a login URL whenever something else will need one.\n if (mode === 'required' || (mode === 'auto' && !loopback)) {\n const secret = server.auth.issueBootToken();\n notes.push(`sign in: http://localhost:${info.port}/login?token=${secret}`);\n notes.push('(one-time URL — mint reusable tokens with `alashi token create`)');\n }\n void printBanner({\n version,\n kernel: provider.name,\n appCount,\n jobCount: server.scheduler.jobCount,\n stateDir: store.root,\n url: `http://localhost:${info.port}`,\n notes,\n });\n });\n const shutdown = (): void => {\n httpServer.close();\n void server.close().finally(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nprogram\n .command('serve', { isDefault: true })\n .option('-p, --port <port>', 'port to listen on (default: config port or 4141)')\n .option(\n '--host <host>',\n 'address to bind (default: 127.0.0.1; non-loopback binds require sign-in)',\n )\n .description('Serve the web dashboard (the default when no command is given)')\n .action(async (options: { port?: string; host?: string }) =>\n serveDashboard(options.port, options.host),\n );\n\nprogram\n .command('create')\n .argument('<name>', 'app name (lowercase letters, digits, hyphens)')\n .argument('[directory]', 'target directory (default: ./<name>)')\n .description('Scaffold a new app from the basic template')\n .action(async (name: string, directory?: string) => {\n const target = directory ?? name;\n const files = await scaffoldApp(name, resolve(target));\n console.log(`Created ${name}/`);\n for (const file of files) console.log(` ${file}`);\n console.log('\\nNext steps:');\n console.log(` cd ${target}`);\n console.log(' npm install');\n console.log(' alashi install .');\n console.log(' alashi # dashboard at http://localhost:4141');\n });\n\nprogram\n .command('install')\n .argument('<source>', 'app name (via registry), git URL, or local path')\n .description('Install an app')\n .action(async (source: string) => {\n const { record, warnings } = await installApp(store, source);\n console.log(`Installed ${record.name}@${record.version} (${record.source.type})`);\n console.log(` data: ${store.appDataDir(record.name)}`);\n console.log(` config: ${store.appConfigPath(record.name)}`);\n printManifestWarnings(warnings);\n await printSetupHints(record.name);\n });\n\n/**\n * Unknown manifest keys never fail a command — the host strips them and runs.\n * Surfacing them here is how an author notices a typo or a too-new field at\n * install time instead of only in the server log. Making them an *error* for\n * authors is `alashi validate` (card #22).\n */\nfunction printManifestWarnings(warnings: string[]): void {\n for (const warning of warnings) console.log(` warning: ${warning}`);\n}\n\n/** After install: what still needs configuring, and which integrations exist. */\nasync function printSetupHints(name: string): Promise<void> {\n let manifest: Awaited<ReturnType<typeof loadApp>>['manifest'];\n try {\n // Warnings were already printed from the install result; stay quiet here.\n ({ manifest } = await loadApp(store.appRepoDir(name), { warn: () => {} }));\n } catch {\n return; // install already succeeded; hints are best-effort\n }\n const { status } = await resolveAppConfig(store, manifest);\n if (status.missing.length > 0) {\n console.log(` requires: ${status.missing.join(', ')}`);\n }\n const optional = status.integrations.filter((i) => i.optional && !i.configured);\n if (optional.length > 0) {\n const list = optional.map((i) => `${i.name} (needs ${i.missing.join(', ')})`).join(', ');\n console.log(` optional integrations: ${list}`);\n }\n if (status.missing.length > 0 || optional.length > 0) {\n console.log(` configure via the dashboard settings page or the config file above`);\n }\n}\n\nprogram\n .command('update')\n .argument('<name>', 'installed app name')\n .description('Update an installed app')\n .action(async (name: string) => {\n const { record, warnings } = await updateApp(store, name);\n console.log(`Updated ${record.name} to ${record.version}`);\n printManifestWarnings(warnings);\n });\n\nprogram\n .command('remove')\n .argument('<name>', 'installed app name')\n .option('--purge', 'also delete the app data and config')\n .description('Remove an installed app (keeps data unless --purge)')\n .action(async (name: string, options: { purge?: boolean }) => {\n await removeApp(store, name, { purge: options.purge ?? false });\n console.log(options.purge ? `Removed ${name} (purged)` : `Removed ${name} (data kept)`);\n });\n\n/** Prints archive paths the way the user would type them. */\nfunction displayPath(path: string): string {\n const rel = relative(process.cwd(), path);\n return rel === '' || rel.startsWith('..') || isAbsolute(rel) ? path : `./${rel}`;\n}\n\nprogram\n .command('export')\n .argument('<name>', 'installed app name')\n .option('-o, --out <file>', 'archive path (default: ./<name>-<version>.tar.gz)')\n .description('Pack an app container and its chat transcripts into a tar.gz')\n .action(async (name: string, options: { out?: string }) => {\n const result = await exportApp(store, new ClaudeCodeProvider(), name, {\n ...(options.out && { out: options.out }),\n });\n for (const warning of result.warnings) console.log(` warning: ${warning}`);\n console.log(\n `Exported ${result.record.name}@${result.record.version} -> ${displayPath(result.archivePath)} (${result.transcriptCount} transcripts)`,\n );\n if (result.record.source.type === 'local') {\n console.log(' note: repo/ was captured as real files, not as the local symlink');\n }\n });\n\n// `import` is a reserved word, so the handler cannot be named after it; the\n// commander command string itself is fine.\nasync function importArchive(file: string): Promise<void> {\n const result = await importApp(store, new ClaudeCodeProvider(), file);\n for (const warning of result.warnings) console.log(` warning: ${warning}`);\n console.log(\n `Imported ${result.record.name}@${result.record.version} (${result.transcriptCount} transcripts restored)`,\n );\n console.log(` data: ${store.appDataDir(result.record.name)}`);\n console.log(` config: ${store.appConfigPath(result.record.name)}`);\n}\n\nprogram\n .command('import')\n .argument('<file>', 'archive produced by alashi export')\n .description('Restore an app container and its chat transcripts from a tar.gz')\n .action(importArchive);\n\nprogram\n .command('list')\n .option('--json', 'machine-readable output')\n .description('List installed apps')\n .action(async (options: { json?: boolean }) => {\n const usage = new UsageRecorder(store);\n const records = await store.listInstalled();\n const spend = new Map(\n await Promise.all(\n records.map(\n async (record) => [record.name, await budgetStatus(store, usage, record.name)] as const,\n ),\n ),\n );\n if (options.json) {\n console.log(\n JSON.stringify(\n records.map((record) => {\n const budget = spend.get(record.name);\n return {\n ...record,\n todayCostUsd: budget?.spentUsd ?? 0,\n recorded: budget?.recorded ?? false,\n overBudget: budget?.overBudget ?? false,\n ...(budget?.dailyUsd !== undefined && { dailyBudgetUsd: budget.dailyUsd }),\n };\n }),\n null,\n 2,\n ),\n );\n return;\n }\n if (records.length === 0) {\n console.log('No apps installed. Try: alashi install <name|git-url|path>');\n return;\n }\n for (const record of records) {\n const origin = record.source.type === 'git' ? record.source.url : record.source.path;\n const budget = spend.get(record.name);\n const cap =\n budget?.dailyUsd === undefined\n ? ''\n : ` of $${budget.dailyUsd.toFixed(2)} cap${budget.overBudget ? ' (OVER BUDGET)' : ''}`;\n // An em dash where the host has no ledger for the app at all; a real\n // $0.0000 only once something has actually been metered.\n const today = budget?.recorded ? `$${budget.spentUsd.toFixed(4)} today` : '— today';\n console.log(\n `${record.name}@${record.version} ${record.source.type} ${origin} ${today}${cap}`,\n );\n }\n });\n\nprogram\n .command('budget')\n .argument('<name>', 'installed app name')\n .argument('[dailyUsd]', 'daily cap in USD, or \"none\" to remove it (omit to just show)')\n .description(\"Show or set an app's host-enforced daily AI spend cap\")\n .action(async (name: string, dailyUsd?: string) => {\n if (!(await store.readAppRecord(name))) throw new Error(`App \"${name}\" is not installed`);\n if (dailyUsd !== undefined) {\n const cleared = dailyUsd === 'none' || dailyUsd === 'off';\n const amount = cleared ? undefined : Number(dailyUsd);\n if (amount !== undefined && (!Number.isFinite(amount) || amount < 0)) {\n throw new Error(`Invalid daily spend cap: ${dailyUsd}`);\n }\n await setAppBudget(store, name, amount);\n }\n const status = await budgetStatus(store, new UsageRecorder(store), name);\n // \"Never metered\" is not \"spent nothing\": the host counts only sessions the\n // app opens through ctx.ai, so say so rather than printing a made-up zero.\n const spent = status.recorded\n ? `$${status.spentUsd.toFixed(4)} spent on ${status.date} (UTC)`\n : 'no spend recorded yet (the host meters only sessions opened through ctx.ai)';\n if (status.dailyUsd === undefined) {\n console.log(`${name}: no daily cap — ${spent}`);\n return;\n }\n console.log(\n `${name}: $${status.dailyUsd.toFixed(2)} daily cap — ${spent}, $${(status.remainingUsd ?? 0).toFixed(4)} left`,\n );\n if (status.overBudget)\n console.log(' OVER BUDGET: new AI sessions are refused until 00:00 UTC');\n });\n\nconst token = program\n .command('token')\n .description('Manage owner bearer tokens for the dashboard/API (see config.auth)');\n\ntoken\n .command('create')\n .argument('[label]', 'a name for the token (e.g. \"laptop\", \"ci\")')\n .description('Mint an owner token; the secret is printed once and never stored')\n .action(async (label?: string) => {\n const { id, secret } = await new AuthService(store).mintToken(label);\n console.log(secret);\n console.log(\n ` id: ${id}${label ? ` (${label})` : ''} — sign in at /login or send it as \"Authorization: Bearer ...\"`,\n );\n });\n\ntoken\n .command('list')\n .description('List tokens (ids and labels only; secrets are never stored)')\n .action(async () => {\n const tokens = await new AuthService(store).listTokens();\n if (tokens.length === 0) {\n console.log('No tokens. Mint one with `alashi token create [label]`.');\n return;\n }\n for (const entry of tokens) {\n const label = entry.label ? ` ${entry.label}` : '';\n const used = entry.lastUsedAt ? `, last used ${entry.lastUsedAt}` : ', never used';\n console.log(`${entry.id}${label} — created ${entry.createdAt}${used}`);\n }\n });\n\ntoken\n .command('revoke')\n .argument('<id>', 'token id (see `alashi token list`)')\n .description('Revoke a token')\n .action(async (id: string) => {\n if (!(await new AuthService(store).revokeToken(id))) {\n throw new Error(`No token with id \"${id}\"`);\n }\n console.log(`Revoked ${id}`);\n });\n\nconst registry = program.command('registry').description('Manage app registries');\n\nregistry\n .command('add')\n .argument('<name>', 'registry name')\n .argument('<url>', 'index.json URL or git repo URL')\n .action(async (name: string, url: string) => {\n const index = await addRegistry(store, name, url);\n console.log(`Added registry \"${name}\" (${index.apps.length} apps)`);\n });\n\nregistry\n .command('remove')\n .argument('<name>', 'registry name')\n .action(async (name: string) => {\n await removeRegistry(store, name);\n console.log(`Removed registry \"${name}\"`);\n });\n\nregistry.command('list').action(async () => {\n const config = await store.readGlobalConfig();\n if (config.registries.length === 0) {\n console.log('No registries configured.');\n return;\n }\n for (const r of config.registries) console.log(`${r.name} ${r.url}`);\n});\n\nprogram.parseAsync().catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : err);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|