picosh 0.1.7 → 0.2.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/index.js +89 -17
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -6,6 +6,8 @@ const os = require('os');
|
|
|
6
6
|
const https = require('https');
|
|
7
7
|
const http = require('http');
|
|
8
8
|
|
|
9
|
+
// ─── image paste ────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
9
11
|
const SAVE_DIR = path.join(os.tmpdir(), 'picosh');
|
|
10
12
|
const SAVE_PATH = path.join(SAVE_DIR, 'clip_latest.png');
|
|
11
13
|
|
|
@@ -21,34 +23,110 @@ function hasImage(clipboard) {
|
|
|
21
23
|
|
|
22
24
|
function saveBitmap(clipboard) {
|
|
23
25
|
fs.mkdirSync(SAVE_DIR, {recursive: true});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return Promise.resolve(filepath);
|
|
26
|
+
fs.writeFileSync(SAVE_PATH, clipboard.readImage().toPNG());
|
|
27
|
+
return Promise.resolve(SAVE_PATH);
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function downloadImage(url) {
|
|
30
31
|
return new Promise((resolve) => {
|
|
31
32
|
fs.mkdirSync(SAVE_DIR, {recursive: true});
|
|
32
|
-
const
|
|
33
|
-
const file = fs.createWriteStream(filepath);
|
|
33
|
+
const file = fs.createWriteStream(SAVE_PATH);
|
|
34
34
|
const client = url.startsWith('https') ? https : http;
|
|
35
|
-
|
|
36
35
|
client.get(url, (res) => {
|
|
37
36
|
res.pipe(file);
|
|
38
|
-
file.on('finish', () => file.close(() => resolve(
|
|
37
|
+
file.on('finish', () => file.close(() => resolve(SAVE_PATH)));
|
|
39
38
|
}).on('error', () => {
|
|
40
|
-
fs.unlink(
|
|
39
|
+
fs.unlink(SAVE_PATH, () => {});
|
|
41
40
|
resolve(null);
|
|
42
41
|
});
|
|
43
42
|
});
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
// ─── AI waiting indicator ───────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
// ANSIエスケープを除去してプロンプト判定
|
|
48
|
+
const ANSI_RE = /\x1b\[[0-9;]*[mGKHF]/g;
|
|
49
|
+
// Claude Code / 一般的なAI CLIのプロンプトパターン
|
|
50
|
+
const PROMPT_RE = />\s*$|❯\s*$/;
|
|
51
|
+
|
|
52
|
+
const timers = {};
|
|
53
|
+
|
|
54
|
+
exports.middleware = (store) => (next) => (action) => {
|
|
55
|
+
if (action.type === 'SESSION_ADD_DATA') {
|
|
56
|
+
const {uid, data} = action;
|
|
57
|
+
const clean = data.replace(ANSI_RE, '');
|
|
58
|
+
|
|
59
|
+
clearTimeout(timers[uid]);
|
|
60
|
+
// 待機中フラグを一旦解除
|
|
61
|
+
store.dispatch({type: 'PICOSH_AI_WAITING', uid, waiting: false});
|
|
62
|
+
|
|
63
|
+
timers[uid] = setTimeout(() => {
|
|
64
|
+
if (PROMPT_RE.test(clean)) {
|
|
65
|
+
store.dispatch({type: 'PICOSH_AI_WAITING', uid, waiting: true});
|
|
66
|
+
}
|
|
67
|
+
}, 500);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (action.type === 'SESSION_PTY_DATA') {
|
|
71
|
+
const {uid} = action;
|
|
72
|
+
clearTimeout(timers[uid]);
|
|
73
|
+
store.dispatch({type: 'PICOSH_AI_WAITING', uid, waiting: false});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return next(action);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
exports.reduceUI = (state, action) => {
|
|
80
|
+
if (action.type === 'PICOSH_AI_WAITING') {
|
|
81
|
+
const waiting = state.get('picoshWaiting') || {};
|
|
82
|
+
return state.set('picoshWaiting', {...waiting, [action.uid]: action.waiting});
|
|
83
|
+
}
|
|
84
|
+
return state;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
exports.mapTermsState = (state, map) => {
|
|
88
|
+
return Object.assign(map, {picoshWaiting: state.ui.get('picoshWaiting') || {}});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
exports.getTabsProps = (parentProps, props) => {
|
|
92
|
+
return Object.assign(props, {picoshWaiting: parentProps.picoshWaiting});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
exports.getTabProps = (tab, parentProps, props) => {
|
|
96
|
+
return Object.assign(props, {
|
|
97
|
+
isAiWaiting: !!(parentProps.picoshWaiting && parentProps.picoshWaiting[props.uid]),
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
exports.decorateTab = (Tab, {React}) => {
|
|
102
|
+
return function PicoshTab(props) {
|
|
103
|
+
const style = props.isAiWaiting ? {
|
|
104
|
+
boxShadow: '0 0 8px 2px #4a9eff',
|
|
105
|
+
borderRadius: '4px',
|
|
106
|
+
animation: 'picosh-glow 1.5s ease-in-out infinite',
|
|
107
|
+
} : {};
|
|
108
|
+
|
|
109
|
+
return React.createElement(
|
|
110
|
+
'div',
|
|
111
|
+
{style: {position: 'relative', display: 'contents'}},
|
|
112
|
+
React.createElement(Tab, props),
|
|
113
|
+
props.isAiWaiting && React.createElement('style', {key: 'glow-style'}, `
|
|
114
|
+
@keyframes picosh-glow {
|
|
115
|
+
0%, 100% { opacity: 1; }
|
|
116
|
+
50% { opacity: 0.6; }
|
|
117
|
+
}
|
|
118
|
+
`),
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// ─── term: image paste + glow wrapper ───────────────────────────────────────
|
|
124
|
+
|
|
46
125
|
exports.decorateTerm = (Term, {React}) => {
|
|
47
126
|
return class extends React.Component {
|
|
48
127
|
componentDidMount() {
|
|
49
128
|
this._onKeyDown = (e) => {
|
|
50
129
|
if (!((e.ctrlKey || e.metaKey) && e.key === 'v')) return;
|
|
51
|
-
|
|
52
130
|
try {
|
|
53
131
|
const {clipboard} = require('electron');
|
|
54
132
|
const found = hasImage(clipboard);
|
|
@@ -57,18 +135,12 @@ exports.decorateTerm = (Term, {React}) => {
|
|
|
57
135
|
e.preventDefault();
|
|
58
136
|
e.stopPropagation();
|
|
59
137
|
|
|
60
|
-
const save = found.type === 'bitmap'
|
|
61
|
-
? saveBitmap(clipboard)
|
|
62
|
-
: downloadImage(found.src);
|
|
63
|
-
|
|
138
|
+
const save = found.type === 'bitmap' ? saveBitmap(clipboard) : downloadImage(found.src);
|
|
64
139
|
save.then((filepath) => {
|
|
65
|
-
if (filepath && this.props.onData)
|
|
66
|
-
this.props.onData(filepath);
|
|
67
|
-
}
|
|
140
|
+
if (filepath && this.props.onData) this.props.onData(filepath);
|
|
68
141
|
});
|
|
69
142
|
} catch (_) {}
|
|
70
143
|
};
|
|
71
|
-
|
|
72
144
|
document.addEventListener('keydown', this._onKeyDown, true);
|
|
73
145
|
}
|
|
74
146
|
|