thebird 1.2.109 → 1.2.111
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/docs/index.html +59 -2
- package/docs/shell-git-auth.js +69 -0
- package/docs/shell-git.js +150 -0
- package/docs/shell.js +4 -1
- package/package.json +3 -2
package/docs/index.html
CHANGED
|
@@ -564,7 +564,7 @@
|
|
|
564
564
|
<tr><td>license</td><td>MIT</td></tr>
|
|
565
565
|
<tr><td>runtime</td><td>browser (V8) + node ≥18</td></tr>
|
|
566
566
|
<tr><td>server required</td><td><strong style="color:var(--green)">none</strong> — 100% serverless</td></tr>
|
|
567
|
-
<tr><td>deps</td><td>acptoapi, @google/genai, xstate
|
|
567
|
+
<tr><td>deps</td><td>acptoapi, floosie, @google/genai, xstate</td></tr>
|
|
568
568
|
<tr><td>frontend</td><td>no framework · vanilla js + webjsx</td></tr>
|
|
569
569
|
<tr><td>storage</td><td>IndexedDB (no server, no cloud)</td></tr>
|
|
570
570
|
<tr><td>api key</td><td>localStorage only · never leaves browser</td></tr>
|
|
@@ -645,7 +645,11 @@
|
|
|
645
645
|
<button id="tab-chat" class="tui-tab active" onclick="switchTab('chat')">chat</button>
|
|
646
646
|
<button id="tab-term" class="tui-tab" onclick="switchTab('term')">terminal</button>
|
|
647
647
|
<button id="tab-preview" class="tui-tab" onclick="switchTab('preview')">preview</button>
|
|
648
|
-
<span style="margin-left:auto;
|
|
648
|
+
<span style="margin-left:auto;display:flex;align-items:center;gap:1ch;padding:0 2ch">
|
|
649
|
+
<span id="gh-user-badge" style="font-size:10px;color:var(--ink-dim)"></span>
|
|
650
|
+
<button id="gh-login-btn" onclick="githubLogin()" style="font-size:10px;font-family:var(--ff-mono);background:none;border:1px solid var(--ink-hair);color:var(--ink-dim);padding:2px 8px;cursor:pointer;transition:color 80ms,border-color 80ms" onmouseover="this.style.color='var(--ink)';this.style.borderColor='var(--ink)'" onmouseout="this.style.color='var(--ink-dim)';this.style.borderColor='var(--ink-hair)'">github login</button>
|
|
651
|
+
<span style="font-size:10px;color:var(--ink-dim)">· no server · <a href="https://github.com/AnEntrypoint/thebird" style="color:var(--ink-dim)">source ↗</a></span>
|
|
652
|
+
</span>
|
|
649
653
|
</div>
|
|
650
654
|
<div id="pane-chat" style="flex:1;overflow:hidden;display:flex;flex-direction:column">
|
|
651
655
|
<bird-chat></bird-chat>
|
|
@@ -751,6 +755,59 @@ function switchTab(t) {
|
|
|
751
755
|
if (t === 'preview') refreshPreview();
|
|
752
756
|
}
|
|
753
757
|
window.showPreview = () => switchTab('preview');
|
|
758
|
+
|
|
759
|
+
const GH_TOKEN_KEY = 'thebird_github_token';
|
|
760
|
+
const GH_USER_KEY = 'thebird_github_user';
|
|
761
|
+
|
|
762
|
+
function updateGhBadge() {
|
|
763
|
+
const user = localStorage.getItem(GH_USER_KEY);
|
|
764
|
+
const badge = document.getElementById('gh-user-badge');
|
|
765
|
+
const btn = document.getElementById('gh-login-btn');
|
|
766
|
+
if (!badge || !btn) return;
|
|
767
|
+
if (user) { badge.textContent = '● ' + user; badge.style.color = 'var(--green)'; btn.textContent = 'logout'; btn.onclick = githubLogout; }
|
|
768
|
+
else { badge.textContent = ''; badge.style.color = ''; btn.textContent = 'github login'; btn.onclick = githubLogin; }
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function githubLogout() {
|
|
772
|
+
localStorage.removeItem(GH_TOKEN_KEY); localStorage.removeItem(GH_USER_KEY);
|
|
773
|
+
updateGhBadge();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
async function githubLogin() {
|
|
777
|
+
const clientId = window.__debug?.githubClientId || prompt('GitHub OAuth App Client ID (create at github.com/settings/developers):');
|
|
778
|
+
if (!clientId) return;
|
|
779
|
+
window.__debug = window.__debug || {};
|
|
780
|
+
window.__debug.githubClientId = clientId;
|
|
781
|
+
const btn = document.getElementById('gh-login-btn');
|
|
782
|
+
try {
|
|
783
|
+
btn.disabled = true; btn.textContent = 'requesting...';
|
|
784
|
+
const codeRes = await fetch('https://github.com/login/device/code', { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: clientId, scope: 'repo' }) });
|
|
785
|
+
if (!codeRes.ok) throw new Error('device code failed: ' + codeRes.status);
|
|
786
|
+
const { device_code, user_code, verification_uri, interval } = await codeRes.json();
|
|
787
|
+
if (window.open(verification_uri, '_blank')) {}
|
|
788
|
+
btn.textContent = 'enter: ' + user_code;
|
|
789
|
+
let wait = (interval || 5) * 1000; const start = Date.now();
|
|
790
|
+
while (Date.now() - start < 300000) {
|
|
791
|
+
await new Promise(r => setTimeout(r, wait));
|
|
792
|
+
const res = await fetch('https://github.com/login/oauth/access_token', { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: clientId, device_code, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }) });
|
|
793
|
+
const data = await res.json();
|
|
794
|
+
if (data.access_token) {
|
|
795
|
+
const meRes = await fetch('https://api.github.com/user', { headers: { Authorization: 'Bearer ' + data.access_token, 'User-Agent': 'thebird' } });
|
|
796
|
+
const me = meRes.ok ? await meRes.json() : {};
|
|
797
|
+
localStorage.setItem(GH_TOKEN_KEY, data.access_token);
|
|
798
|
+
if (me.login) { localStorage.setItem(GH_USER_KEY, me.login); window.__debug.githubUser = me.login; }
|
|
799
|
+
updateGhBadge(); return;
|
|
800
|
+
}
|
|
801
|
+
if (data.error === 'slow_down') wait = ((data.interval || 5) + 5) * 1000;
|
|
802
|
+
else if (data.error !== 'authorization_pending') throw new Error(data.error_description || data.error);
|
|
803
|
+
}
|
|
804
|
+
throw new Error('timed out');
|
|
805
|
+
} catch(e) { alert('GitHub login failed: ' + e.message); }
|
|
806
|
+
finally { btn.disabled = false; updateGhBadge(); }
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
window.updateGhBadge = updateGhBadge;
|
|
810
|
+
document.addEventListener('DOMContentLoaded', updateGhBadge);
|
|
754
811
|
</script>
|
|
755
812
|
<script type="module" src="app.js"></script>
|
|
756
813
|
<script type="module" src="terminal.js"></script>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const GH_TOKEN_KEY = 'thebird_github_token';
|
|
2
|
+
const GH_USER_KEY = 'thebird_github_user';
|
|
3
|
+
|
|
4
|
+
export function getGithubToken() { return localStorage.getItem(GH_TOKEN_KEY); }
|
|
5
|
+
export function getGithubUser() { return localStorage.getItem(GH_USER_KEY); }
|
|
6
|
+
export function setGithubToken(token, user) { localStorage.setItem(GH_TOKEN_KEY, token); if (user) localStorage.setItem(GH_USER_KEY, user); }
|
|
7
|
+
export function clearGithubToken() { localStorage.removeItem(GH_TOKEN_KEY); localStorage.removeItem(GH_USER_KEY); }
|
|
8
|
+
|
|
9
|
+
export async function githubDeviceFlow(clientId, scope = 'repo') {
|
|
10
|
+
const res = await fetch('https://github.com/login/device/code', {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
13
|
+
body: JSON.stringify({ client_id: clientId, scope }),
|
|
14
|
+
});
|
|
15
|
+
if (!res.ok) throw new Error('device code failed: ' + res.status);
|
|
16
|
+
return res.json();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function pollGithubToken(clientId, device_code, interval = 5, maxWait = 300) {
|
|
20
|
+
const start = Date.now();
|
|
21
|
+
while ((Date.now() - start) / 1000 < maxWait) {
|
|
22
|
+
await new Promise(r => setTimeout(r, interval * 1000));
|
|
23
|
+
const res = await fetch('https://github.com/login/oauth/access_token', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify({ client_id: clientId, device_code, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }),
|
|
27
|
+
});
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
if (data.access_token) return data.access_token;
|
|
30
|
+
if (data.error === 'authorization_pending') continue;
|
|
31
|
+
if (data.error === 'slow_down') { interval = (data.interval || interval) + 5; continue; }
|
|
32
|
+
throw new Error(data.error_description || data.error || 'token poll failed');
|
|
33
|
+
}
|
|
34
|
+
throw new Error('device flow timed out');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function makeIdbFs() {
|
|
38
|
+
const snap = () => window.__debug?.idbSnapshot || {};
|
|
39
|
+
const persist = () => window.__debug?.idbPersist?.();
|
|
40
|
+
const norm = p => p.replace(/^\//, '');
|
|
41
|
+
return {
|
|
42
|
+
readFileSync(p, opts) {
|
|
43
|
+
const d = snap()[norm(p)];
|
|
44
|
+
if (d == null) { const e = new Error('ENOENT: ' + p); e.code = 'ENOENT'; throw e; }
|
|
45
|
+
if (typeof opts === 'string' ? opts : opts?.encoding) return typeof d === 'string' ? d : new TextDecoder().decode(d);
|
|
46
|
+
return typeof d === 'string' ? new TextEncoder().encode(d) : d;
|
|
47
|
+
},
|
|
48
|
+
writeFileSync(p, data) { snap()[norm(p)] = data; persist(); },
|
|
49
|
+
unlinkSync(p) { delete snap()[norm(p)]; persist(); },
|
|
50
|
+
readdirSync(p) {
|
|
51
|
+
const prefix = norm(p); const pSlash = prefix ? prefix + '/' : '';
|
|
52
|
+
const entries = new Set();
|
|
53
|
+
for (const k of Object.keys(snap())) { if (!k.startsWith(pSlash)) continue; const part = k.slice(pSlash.length).split('/')[0]; if (part) entries.add(part); }
|
|
54
|
+
return [...entries];
|
|
55
|
+
},
|
|
56
|
+
mkdirSync() {},
|
|
57
|
+
existsSync(p) { const k = norm(p); return k in snap() || Object.keys(snap()).some(x => x.startsWith(k + '/')); },
|
|
58
|
+
statSync(p) {
|
|
59
|
+
const k = norm(p); const d = snap()[k];
|
|
60
|
+
if (d != null) return { isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false, size: typeof d === 'string' ? d.length : (d.byteLength || d.length || 0), mtimeMs: Date.now(), mode: 0o100644 };
|
|
61
|
+
if (Object.keys(snap()).some(x => x.startsWith(k + '/'))) return { isFile: () => false, isDirectory: () => true, isSymbolicLink: () => false, size: 0, mtimeMs: Date.now(), mode: 0o040755 };
|
|
62
|
+
const e = new Error('ENOENT: ' + p); e.code = 'ENOENT'; throw e;
|
|
63
|
+
},
|
|
64
|
+
lstatSync(p) { return this.statSync(p); },
|
|
65
|
+
rmSync(p) { const k = norm(p); for (const key of Object.keys(snap())) { if (key === k || key.startsWith(k + '/')) delete snap()[key]; } persist(); },
|
|
66
|
+
symlinkSync() {},
|
|
67
|
+
readlinkSync(p) { return p; },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { preloadGit, makeGit } from './shell-node-git.js';
|
|
2
|
+
import { getGithubToken, getGithubUser, setGithubToken, clearGithubToken, githubDeviceFlow, pollGithubToken, makeIdbFs } from './shell-git-auth.js';
|
|
3
|
+
|
|
4
|
+
function makeOnAuth() {
|
|
5
|
+
const token = getGithubToken();
|
|
6
|
+
return token ? () => ({ username: token, password: '' }) : undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const USAGE = [
|
|
10
|
+
'git <command> [options]',
|
|
11
|
+
' init [dir] initialize repository',
|
|
12
|
+
' clone <url> [dir] clone remote repository',
|
|
13
|
+
' status show working tree status',
|
|
14
|
+
' add <path> stage file(s) (. for all)',
|
|
15
|
+
' commit -m "msg" commit staged changes',
|
|
16
|
+
' push [remote] [branch] push to remote',
|
|
17
|
+
' pull [remote] [branch] pull from remote',
|
|
18
|
+
' log [--oneline] [-N] show commit history',
|
|
19
|
+
' diff show unstaged changes',
|
|
20
|
+
' branch [-d] [name] list or create/delete branches',
|
|
21
|
+
' checkout <branch> switch branch',
|
|
22
|
+
' remote [-v] list remotes',
|
|
23
|
+
' auth login github device flow login',
|
|
24
|
+
' auth logout clear stored token',
|
|
25
|
+
' auth status show auth status',
|
|
26
|
+
].join('\r\n');
|
|
27
|
+
|
|
28
|
+
export function makeGitBuiltin(ctx) {
|
|
29
|
+
const term = ctx.term;
|
|
30
|
+
const wl = s => term.write(s + '\r\n');
|
|
31
|
+
const wr = s => term.write(s);
|
|
32
|
+
const git = makeGit(makeIdbFs());
|
|
33
|
+
const cwd = () => ctx.cwd;
|
|
34
|
+
|
|
35
|
+
async function handleAuth(args) {
|
|
36
|
+
const ac = args[1];
|
|
37
|
+
if (ac === 'logout') { clearGithubToken(); wl('github token cleared'); return; }
|
|
38
|
+
if (ac === 'status') {
|
|
39
|
+
const u = getGithubUser(); const t = getGithubToken();
|
|
40
|
+
wl(t ? 'logged in' + (u ? ' as ' + u : '') + ' · "git auth logout" to clear' : 'not logged in · "git auth login" to authenticate');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (ac !== 'login') { wl('git auth: use login | logout | status'); return; }
|
|
44
|
+
const clientId = ctx.env.GITHUB_CLIENT_ID || window.__debug?.githubClientId;
|
|
45
|
+
if (!clientId) { wl('error: set GITHUB_CLIENT_ID env var or window.__debug.githubClientId'); return; }
|
|
46
|
+
try {
|
|
47
|
+
const { user_code, verification_uri, device_code, interval } = await githubDeviceFlow(clientId);
|
|
48
|
+
wl('1. open: \x1b[36m' + verification_uri + '\x1b[0m');
|
|
49
|
+
wl('2. enter code: \x1b[1;33m' + user_code + '\x1b[0m');
|
|
50
|
+
wl('waiting for authorization...');
|
|
51
|
+
const token = await pollGithubToken(clientId, device_code, interval);
|
|
52
|
+
const meRes = await fetch('https://api.github.com/user', { headers: { Authorization: 'Bearer ' + token, 'User-Agent': 'thebird' } });
|
|
53
|
+
const me = meRes.ok ? await meRes.json() : {};
|
|
54
|
+
setGithubToken(token, me.login);
|
|
55
|
+
wl('\x1b[32m✓ logged in' + (me.login ? ' as ' + me.login : '') + '\x1b[0m');
|
|
56
|
+
if (window.__debug) { window.__debug.githubUser = me.login; if (window.updateGhBadge) window.updateGhBadge(); }
|
|
57
|
+
} catch(e) { wl('\x1b[31merror: ' + e.message + '\x1b[0m'); }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return async function gitCmd(args) {
|
|
61
|
+
const sub = args[0];
|
|
62
|
+
if (!sub || sub === '--help' || sub === '-h') { wl(USAGE); return; }
|
|
63
|
+
if (sub === 'auth') { await handleAuth(args); return; }
|
|
64
|
+
|
|
65
|
+
await preloadGit();
|
|
66
|
+
const onAuth = makeOnAuth();
|
|
67
|
+
const dir = cwd();
|
|
68
|
+
|
|
69
|
+
if (sub === 'init') {
|
|
70
|
+
const d = args[1] ? (args[1].startsWith('/') ? args[1] : dir + '/' + args[1]) : dir;
|
|
71
|
+
await git.init({ dir: d }); wl('initialized empty git repository in ' + d); return;
|
|
72
|
+
}
|
|
73
|
+
if (sub === 'clone') {
|
|
74
|
+
const url = args[1]; if (!url) { wl('git clone: url required'); return; }
|
|
75
|
+
const name = args[2] || url.split('/').pop().replace(/\.git$/, '');
|
|
76
|
+
const dest = name.startsWith('/') ? name : dir + '/' + name;
|
|
77
|
+
wl('cloning into ' + dest + '...');
|
|
78
|
+
await git.clone({ url, dir: dest, onProgress: p => { if (p.phase) wr('\r' + p.phase + ' ' + (p.loaded || '') + '/' + (p.total || '')); }, ...(onAuth ? { onAuth } : {}) });
|
|
79
|
+
wl('\rdone.'); return;
|
|
80
|
+
}
|
|
81
|
+
if (sub === 'status') {
|
|
82
|
+
const matrix = await git.statusMatrix({ dir });
|
|
83
|
+
const labels = [null, 'unmodified', 'modified', 'deleted'];
|
|
84
|
+
const rows = matrix.filter(([, h, w]) => h !== 1 || w !== 1);
|
|
85
|
+
if (!rows.length) { wl('nothing to commit, working tree clean'); return; }
|
|
86
|
+
for (const [fp,, work, stage] of rows) {
|
|
87
|
+
const staged = stage > 0 ? '\x1b[32mS\x1b[0m' : ' ';
|
|
88
|
+
wl(staged + ' ' + (work === 0 ? '\x1b[31m' : work === 2 ? '\x1b[33m' : '') + fp + '\x1b[0m' + ' [' + (labels[work] || '?') + ']');
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (sub === 'add') {
|
|
93
|
+
const p = args[1]; if (!p) { wl('git add: path required'); return; }
|
|
94
|
+
if (p === '.') { const matrix = await git.statusMatrix({ dir }); for (const [fp,, work] of matrix) { if (work !== 1) await git.add({ dir, filepath: fp }); } wl('staged all changes'); }
|
|
95
|
+
else { await git.add({ dir, filepath: p }); wl('staged ' + p); }
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (sub === 'commit') {
|
|
99
|
+
const mi = args.indexOf('-m'); const msg = mi >= 0 ? args[mi + 1] : null;
|
|
100
|
+
if (!msg) { wl('git commit: -m "message" required'); return; }
|
|
101
|
+
const name = ctx.env.GIT_AUTHOR_NAME || getGithubUser() || 'thebird';
|
|
102
|
+
const email = ctx.env.GIT_AUTHOR_EMAIL || (getGithubUser() ? getGithubUser() + '@users.noreply.github.com' : 'thebird@localhost');
|
|
103
|
+
const sha = await git.commit({ dir, message: msg, author: { name, email } });
|
|
104
|
+
wl('[' + sha.slice(0, 7) + '] ' + msg); return;
|
|
105
|
+
}
|
|
106
|
+
if (sub === 'push') {
|
|
107
|
+
if (!onAuth) { wl('git push: not authenticated. run "git auth login" first'); return; }
|
|
108
|
+
const remote = args[1] || 'origin'; const branch = args[2] || await git.currentBranch({ dir }) || 'main';
|
|
109
|
+
wl('pushing to ' + remote + '/' + branch + '...');
|
|
110
|
+
await git.push({ dir, remote, remoteRef: branch, onAuth, onAuthFailure: () => { throw new Error('auth failed'); } });
|
|
111
|
+
wl('done.'); return;
|
|
112
|
+
}
|
|
113
|
+
if (sub === 'pull') {
|
|
114
|
+
const remote = args[1] || 'origin'; const branch = args[2] || await git.currentBranch({ dir }) || 'main';
|
|
115
|
+
wl('pulling from ' + remote + '/' + branch + '...');
|
|
116
|
+
await git.pull({ dir, remote, remoteRef: branch, ...(onAuth ? { onAuth } : {}), author: { name: 'thebird', email: 'thebird@localhost' } });
|
|
117
|
+
wl('done.'); return;
|
|
118
|
+
}
|
|
119
|
+
if (sub === 'log') {
|
|
120
|
+
const oneline = args.includes('--oneline');
|
|
121
|
+
const nFlag = args.find(a => /^-\d+$/.test(a));
|
|
122
|
+
const commits = await git.log({ dir, depth: nFlag ? Math.abs(parseInt(nFlag, 10)) : 10 });
|
|
123
|
+
for (const { oid, commit } of commits) {
|
|
124
|
+
if (oneline) wl('\x1b[33m' + oid.slice(0, 7) + '\x1b[0m ' + commit.message.split('\n')[0]);
|
|
125
|
+
else { wl('\x1b[33mcommit ' + oid + '\x1b[0m'); wl('Author: ' + commit.author.name + ' <' + commit.author.email + '>'); wl('Date: ' + new Date(commit.author.timestamp * 1000).toUTCString()); wl(''); wl(' ' + commit.message.trim()); wl(''); }
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (sub === 'diff') { const changed = await git.diff({ dir }); wl(changed.length ? 'modified: ' + changed.join(', ') : 'no changes'); return; }
|
|
130
|
+
if (sub === 'branch') {
|
|
131
|
+
const del = args.includes('-d') || args.includes('-D'); const name = args.find(a => !a.startsWith('-'));
|
|
132
|
+
if (del && name) { await git.branch({ dir, ref: name, checkout: false }); wl('deleted branch ' + name); return; }
|
|
133
|
+
if (name) { await git.branch({ dir, ref: name }); wl('created branch ' + name); return; }
|
|
134
|
+
const branches = await git.listBranches({ dir }); const cur = await git.currentBranch({ dir });
|
|
135
|
+
for (const b of branches) wl((b === cur ? '* \x1b[32m' : ' ') + b + '\x1b[0m');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (sub === 'checkout') {
|
|
139
|
+
const ref = args[1]; if (!ref) { wl('git checkout: branch name required'); return; }
|
|
140
|
+
await git.checkout({ dir, ref }); wl('switched to branch ' + ref); return;
|
|
141
|
+
}
|
|
142
|
+
if (sub === 'remote') {
|
|
143
|
+
const remotes = await git.listRemotes({ dir }); if (!remotes.length) { wl('no remotes'); return; }
|
|
144
|
+
const verbose = args.includes('-v');
|
|
145
|
+
for (const { remote, url } of remotes) wl(remote + (verbose ? '\t' + url : ''));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
wl('git: unknown subcommand "' + sub + '". run "git --help"');
|
|
149
|
+
};
|
|
150
|
+
}
|
package/docs/shell.js
CHANGED
|
@@ -14,6 +14,7 @@ import { createFdTable, makeExecBuiltin } from './shell-fd.js';
|
|
|
14
14
|
import { readStream } from './shell-procsub.js';
|
|
15
15
|
import { makeExpander, makeCaptureRun, makeNodeRunner, makeNpmResultRunner } from './shell-exec.js';
|
|
16
16
|
import { createSwJobs, makeNohupBuiltin, makeNetcatStub, makeCurlBuiltin } from './shell-sw-jobs.js';
|
|
17
|
+
import { makeGitBuiltin } from './shell-git.js';
|
|
17
18
|
|
|
18
19
|
const machine = createMachine({ id: 'shell', initial: 'idle', states: {
|
|
19
20
|
idle: { on: { RUN: 'executing', ENTER_REPL: 'node-repl', NODE_START: 'node-running' } },
|
|
@@ -53,6 +54,7 @@ export function createShell({ term, onPreviewWrite }) {
|
|
|
53
54
|
const npmCmd = makeNpm(ctx); const npxCmd = makeNpx(npmCmd); ctx.exec = line => run(line);
|
|
54
55
|
const pmDispatch = makePmDispatcher(term, null, () => window.__debug.idbPersist?.(), ctx); const corepackCmd = makeCorepackStub(term); const dlxCmd = makeDlx(term, null, ctx, run);
|
|
55
56
|
ctx.nodeEval = createNodeEnv({ ctx, term });
|
|
57
|
+
const gitCmd = makeGitBuiltin(ctx);
|
|
56
58
|
const runNode = makeNodeRunner(ctx, actor);
|
|
57
59
|
const runNpmResult = makeNpmResultRunner(ctx, line => run(line));
|
|
58
60
|
|
|
@@ -105,6 +107,7 @@ export function createShell({ term, onPreviewWrite }) {
|
|
|
105
107
|
if (cmd === 'deno') { if (args[0] === 'run') { await runNode(args.slice(1)); return; } ctx.lastExitCode = await pmDispatch('deno', args[0] || 'task', args.slice(1)); return; }
|
|
106
108
|
if (cmd === 'corepack') { ctx.lastExitCode = await corepackCmd(args); return; }
|
|
107
109
|
if (cmd === 'node') { await runNode(args); return; }
|
|
110
|
+
if (cmd === 'git') { await gitCmd(args); return; }
|
|
108
111
|
if (cmd === 'exit') { BUILTINS.exit([], actor); return; }
|
|
109
112
|
if (writeOut) { writeOut(await invokeBuiltin(cmd, args, true)); return; }
|
|
110
113
|
await invokeBuiltin(cmd, args, false);
|
|
@@ -169,7 +172,7 @@ export function createShell({ term, onPreviewWrite }) {
|
|
|
169
172
|
if (onData) drainQueue(onData);
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
const getCompletions = (line, word) => (line.trim().split(/\s+/).length <= 1 && !line.includes(' ')) ? Object.keys(BUILTINS).concat(['npm', 'node', 'pnpm', 'yarn', 'bun', 'deno', 'npx', 'corepack']).filter(c => c.startsWith(word)) : Object.keys(snap()).filter(f => f.startsWith(word));
|
|
175
|
+
const getCompletions = (line, word) => (line.trim().split(/\s+/).length <= 1 && !line.includes(' ')) ? Object.keys(BUILTINS).concat(['npm', 'node', 'pnpm', 'yarn', 'bun', 'deno', 'npx', 'corepack', 'git']).filter(c => c.startsWith(word)) : Object.keys(snap()).filter(f => f.startsWith(word));
|
|
173
176
|
|
|
174
177
|
const handleLine = line => {
|
|
175
178
|
if (blockLines.length > 0 || isControlStart(line)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thebird",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.111",
|
|
4
4
|
"description": "Anthropic SDK to Gemini streaming bridge — drop-in proxy that translates Anthropic message format and tool calls to Google Gemini",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "node serve.js"
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"url": "https://github.com/AnEntrypoint/thebird.git"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"acptoapi": "file:../acptoapi"
|
|
41
|
+
"acptoapi": "file:../acptoapi",
|
|
42
|
+
"floosie": "^0.6.4"
|
|
42
43
|
},
|
|
43
44
|
"engines": {
|
|
44
45
|
"node": ">=18"
|