@promptctl/cc-candybar 1.153.0 → 1.154.1
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/dist/attach-preload.cjs +1 -1
- package/dist/cc-extra/PROVENANCE +1 -1
- package/dist/cc-extra/src/bun-attach.js +53 -20
- package/dist/index.mjs +124 -125
- package/package.json +5 -5
package/dist/attach-preload.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:fs`);c=s(c,1);let l=require(`node:os`);l=s(l,1);let u=require(`node:path`);u=s(u,1);function d(e){return u.default.join(u.default.dirname(e),`cc-extra`,`src`,`bun-attach.js`)}function f(e){let t=[],n=null,r=0;for(let i=0;i<e.length;i++){let a=e[i];if(/\s/.test(a)){n!==null&&t.push({text:n,raw:e.slice(r,i)}),n=null;continue}n===null&&(r=i),n=(n??``)+(a===`\\`&&i+1<e.length?e[++i]:a)}return n!==null&&t.push({text:n,raw:e.slice(r)}),t}function
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:fs`);c=s(c,1);let l=require(`node:os`);l=s(l,1);let u=require(`node:path`);u=s(u,1);function d(e){return u.default.join(u.default.dirname(e),`cc-extra`,`src`,`bun-attach.js`)}function f(e,t,n){for(let r of(t??``).split(u.default.delimiter)){if(!u.default.isAbsolute(r))continue;let t=u.default.join(r,e);if(n(t))return t}return null}function p(e,t){try{return c.default.accessSync(e,t),c.default.statSync(e).isFile()}catch{return!1}}function m(e){try{return c.default.realpathSync(e)}catch{return u.default.resolve(e)}}function h(e){let t=[],n=null,r=0;for(let i=0;i<e.length;i++){let a=e[i];if(/\s/.test(a)){n!==null&&t.push({text:n,raw:e.slice(r,i)}),n=null;continue}n===null&&(r=i),n=(n??``)+(a===`\\`&&i+1<e.length?e[++i]:a)}return n!==null&&t.push({text:n,raw:e.slice(r)}),t}function g(e,t){let n=h(e),r=[];for(let e=0;e<n.length;e++){let i=n[e],a=n[e+1];if((i.text===`--preload`||i.text===`-r`)&&a!==void 0){if(t(a.text)){e++;continue}}else if(i.raw.startsWith(`--preload=`)&&t(i.raw.slice(10)))continue;r.push(i.raw)}return r.length===0?null:r.join(` `)}function _(e,t,n){let r=f(`node`,e,t);return r===null?{kind:`refused`,why:"no `node` on PATH to run the attach launcher"}:u.default.basename(n(r))===`bun`?{kind:`refused`,why:`the \`node\` on PATH (${r}) is Bun; the attach launcher runs under Node`}:{kind:`node`,file:r}}function v(e){if(!(e.argv[1]??``).startsWith(`/$bunfs/`))return{kind:`refused`,why:`${e.executable} is not a Bun-compiled Claude Code — put BUN_OPTIONS with this preload before \`claude\` alone, never export it`};let t=d(e.self);if(!e.exists(t))return{kind:`refused`,why:`the attach launcher is not at ${t} beside this preload — rebuild (\`pnpm build\`) or reinstall cc-candybar`};let n=_(e.env.PATH,e.isExecutable,e.canonical);if(n.kind===`refused`)return n;let r=e.canonical(e.self),i=t=>e.canonical(t)===r,a=[];for(let[t,n]of Object.entries(e.env))if(n!==void 0){if(t===`BUN_OPTIONS`){let e=g(n,i);e!==null&&a.push(`${t}=${e}`);continue}a.push(`${t}=${n}`)}return{kind:`exec`,file:n.file,argv:[n.file,t,e.executable,...e.argv.slice(2)],env:a}}const y=new Map(Object.entries(l.default.constants.errno).map(([e,t])=>[t,e]));function b(e){process.stderr.write(`cc-candybar: not attached — ${e}; nothing was started\n`),process.exit(1)}function x(e,t,n){let r,i;try{r=require(`bun:ffi`),i=r.dlopen(process.platform===`darwin`?`/usr/lib/libSystem.B.dylib`:`libc.so.6`,{execve:{args:[`ptr`,`ptr`,`ptr`],returns:`i32`},[process.platform===`darwin`?`__error`:`__errno_location`]:{args:[],returns:`ptr`}})}catch(e){return`execve is not reachable through Bun's FFI here: ${e instanceof Error?e.message:String(e)}`}let{ptr:a,read:o}=r,s=[],c=e=>{let t=Buffer.from(`${e}\0`);return s.push(t),t},l=e=>{let t=new BigUint64Array(e.length+1);return e.forEach((e,n)=>{t[n]=BigInt(a(c(e)))}),s.push(t),t};i.symbols.execve(a(c(e)),a(l(t)),a(l(n)));let u=i.symbols.__error??i.symbols.__errno_location,d=o.i32(u());return`execve ${e} failed: ${y.get(d)??`errno ${d}`}`}const S=v({self:__filename,executable:c.default.realpathSync(process.execPath),argv:process.argv,env:process.env,exists:e=>p(e,c.default.constants.R_OK),isExecutable:e=>p(e,c.default.constants.X_OK),canonical:m});S.kind===`refused`&&b(S.why),b(x(S.file,S.argv,S.env));
|
package/dist/cc-extra/PROVENANCE
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
cc-extra
|
|
1
|
+
cc-extra b6134fb6e35aae96fe984b37f63d9c25f67912f8
|
|
2
2
|
Copied by scripts/vendor-cc-extra.mjs; do not edit these files here.
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
// one as its chunk parses (MEASURED: `breakpointResolved` at the predicted line and column).
|
|
20
20
|
// [LAW:no-ambient-temporal-coupling] the order is this sequence's, and two waits in it are on a clock,
|
|
21
21
|
// each with one owner: the socket's appearance, which has a deadline, and the question asked of the
|
|
22
|
-
// released process (WHY IT STAYS CONNECTED), which is asked
|
|
22
|
+
// released process (WHY IT STAYS CONNECTED), which is asked again and again until it is a TUI or
|
|
23
|
+
// ASK_WITHIN_MS has passed.
|
|
23
24
|
//
|
|
24
25
|
// WHY A UNIX SOCKET. The inspector runs arbitrary code in the session. On TCP it is reachable by every
|
|
25
26
|
// process on the machine; a socket in a directory only this user can enter is reachable by this user.
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
// (MEASURED: `/exit` ended it, exit 0). The run it lets go is a one-shot: the inspector holds a process
|
|
34
35
|
// that ends by draining its loop (`--version`) for as long as it is connected, and that process gives
|
|
35
36
|
// no sign it is done (MEASURED: `beforeExit` never fires while it is held). Which of the two a run is,
|
|
36
|
-
// the process is ASKED,
|
|
37
|
+
// the process is ASKED, every ASK_EVERY_MS after release: a TUI puts stdin in raw mode as it mounts,
|
|
38
|
+
// and the first answer that it has is the verdict.
|
|
37
39
|
//
|
|
38
40
|
// FAILURE. Every step before release happens while the binary is held at entry, where nothing the user
|
|
39
41
|
// asked for has run, so a failure there is answered by killing the held process and exiting non-zero,
|
|
@@ -66,10 +68,15 @@ const SOCKET_DEADLINE_MS = 5000;
|
|
|
66
68
|
// `--version` not raw, exiting as soon as it was let go — the asking does not hold it).
|
|
67
69
|
const TUI_IS_UP = 'process.stdin.isTTY === true && process.stdin.isRaw === true';
|
|
68
70
|
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
71
|
+
// How often after release the process is asked TUI_IS_UP, and for how long. A TUI is attached the
|
|
72
|
+
// moment it answers raw, so the deadline is paid only by a run that never does — an interactive
|
|
73
|
+
// one-shot like `--version`, held that long before it is let go. Asking once, at 2s, took a TUI that
|
|
74
|
+
// mounted later for a one-shot: `--resume` restarts on 2.1.289 reached raw mode 2.0-3.3s after release,
|
|
75
|
+
// and 2 of 11 ran without the push. A real session (more plugins, a longer transcript) mounts slower
|
|
76
|
+
// still, so the deadline is three times the slowest measured mount. A TUI slower than that is taken for
|
|
77
|
+
// a one-shot and runs without the push; the log says which, and when the answer came.
|
|
78
|
+
const ASK_EVERY_MS = 100;
|
|
79
|
+
const ASK_WITHIN_MS = 10000;
|
|
73
80
|
const SOCKET_NAME = 'inspector.sock';
|
|
74
81
|
|
|
75
82
|
// Per user, never the shared temp dir: on Linux that is /tmp, where another user could plant the log
|
|
@@ -132,20 +139,45 @@ function socketAppears(dir, name, deadlineMs, exited) {
|
|
|
132
139
|
|
|
133
140
|
const describeEnding = ({ code, signal, error }) => (error ? `could not start: ${error}` : `exited ${signal || code}`);
|
|
134
141
|
|
|
135
|
-
// What the released run is, asked of it `
|
|
136
|
-
//
|
|
137
|
-
//
|
|
142
|
+
// What the released run is, asked of it every `everyMs` after release, one question in flight at a
|
|
143
|
+
// time: `{ runs: 'tui' }` at the first answer that stdin is raw, `{ runs: 'one-shot' }` at the first
|
|
144
|
+
// answer that it is not once `withinMs` has passed, `{ runs: 'closed' }` when the inspector went away
|
|
145
|
+
// first (the run ended, or the connection did), or `{ runs: 'unanswered', why }` when the first answer
|
|
146
|
+
// after the deadline is a question that failed. A failure before then is asked again: one of a hundred
|
|
147
|
+
// questions failing while the run mounts says nothing about whether it will mount. A close resolves
|
|
138
148
|
// before the rejection it causes reaches here, so a rejection that does is the question failing.
|
|
149
|
+
// [LAW:no-ambient-temporal-coupling] the verdict waits on the fact it is about: the deadline ends the
|
|
150
|
+
// ASKING, and the question in flight is always waited for, never decided over while it could still say
|
|
151
|
+
// otherwise — so a run that never answers at all is waited on for as long as it is connected.
|
|
139
152
|
// `session` is anything with the session's `send` and `on`; `delay(ms, fn)` is a cancellable timer. A
|
|
140
153
|
// pure seam, so the suite drives it with neither an inspector nor a clock. [LAW:effects-at-boundaries]
|
|
141
|
-
function askWhatRuns(session,
|
|
154
|
+
function askWhatRuns(session, { everyMs, withinMs }, delay) {
|
|
142
155
|
return new Promise((resolve) => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
156
|
+
let settled = false;
|
|
157
|
+
let expired = false;
|
|
158
|
+
let cancelNext = () => {};
|
|
159
|
+
const cancelDeadline = delay(withinMs, () => { expired = true; });
|
|
160
|
+
const settle = (verdict) => {
|
|
161
|
+
if (settled) return;
|
|
162
|
+
settled = true;
|
|
163
|
+
cancelNext();
|
|
164
|
+
cancelDeadline();
|
|
165
|
+
resolve(verdict);
|
|
166
|
+
};
|
|
167
|
+
// `why` is the failure of the question just answered, null when it answered not raw.
|
|
168
|
+
const notYet = (why) => {
|
|
169
|
+
if (expired) return settle(why === null ? { runs: 'one-shot' } : { runs: 'unanswered', why });
|
|
170
|
+
if (!settled) cancelNext = delay(everyMs, ask);
|
|
171
|
+
};
|
|
172
|
+
const answered = (r) => {
|
|
173
|
+
if (r.wasThrown) return notYet(r.result.description ?? JSON.stringify(r.result.value));
|
|
174
|
+
if (r.result.value === true) return settle({ runs: 'tui' });
|
|
175
|
+
return notYet(null);
|
|
176
|
+
};
|
|
177
|
+
const ask = () => session.send('Runtime.evaluate', { expression: TUI_IS_UP, returnByValue: true })
|
|
178
|
+
.then(answered, (e) => notYet(e.message));
|
|
179
|
+
cancelNext = delay(everyMs, ask);
|
|
180
|
+
session.on('close', () => settle({ runs: 'closed' }));
|
|
149
181
|
});
|
|
150
182
|
}
|
|
151
183
|
|
|
@@ -197,7 +229,7 @@ async function attachAndRun(binary, args, io = {}) {
|
|
|
197
229
|
// Only a TUI has a status line to drive. A headless, piped or print-mode run (`-p`, at a terminal or
|
|
198
230
|
// not) is a one-shot that would pay the wait below for nothing and wants to stay exactly as fast as
|
|
199
231
|
// it shipped. [LAW:one-source-of-truth] whether a run renders a TUI is launch.js's one parse of it; an
|
|
200
|
-
// interactive `--version` still reads as a TUI there, and is let go at
|
|
232
|
+
// interactive `--version` still reads as a TUI there, and is let go at ASK_WITHIN_MS.
|
|
201
233
|
if (!rendersTui(args, io.terminal ?? attachedToTerminal())) return runStock(binary, args, 'not a TUI: no status line to drive', { stderr });
|
|
202
234
|
|
|
203
235
|
const graph = readGraphFromFile(binary);
|
|
@@ -261,8 +293,9 @@ async function attachAndRun(binary, args, io = {}) {
|
|
|
261
293
|
// Only a run that answered 'tui' keeps the launcher connected for the session (see WHY IT STAYS
|
|
262
294
|
// CONNECTED). Anything else is let go, because staying is what holds a one-shot, and a question that
|
|
263
295
|
// could not be answered is said why.
|
|
264
|
-
const
|
|
265
|
-
|
|
296
|
+
const releasedAt = Date.now();
|
|
297
|
+
const { runs, why } = await askWhatRuns(session, { everyMs: ASK_EVERY_MS, withinMs: ASK_WITHIN_MS }, (ms, fn) => { const t = setTimeout(fn, ms); return () => clearTimeout(t); });
|
|
298
|
+
log(`after release: ${runs} at ${Date.now() - releasedAt}ms${why ? ` (${why})` : ''}`);
|
|
266
299
|
if (runs !== 'tui') session.close();
|
|
267
300
|
if (runs === 'unanswered') troubles.add(`could not ask the session whether it is a TUI, so it ran without the push: ${why}`);
|
|
268
301
|
|
|
@@ -278,7 +311,7 @@ async function attachAndRun(binary, args, io = {}) {
|
|
|
278
311
|
return exitStatus(code, signal);
|
|
279
312
|
}
|
|
280
313
|
|
|
281
|
-
module.exports = { SOCKET_DEADLINE_MS,
|
|
314
|
+
module.exports = { SOCKET_DEADLINE_MS, ASK_EVERY_MS, ASK_WITHIN_MS, TUI_IS_UP, LOG_PATH, inspectedEnv, attachCalls, readSample, socketAppears, askWhatRuns, attachAndRun };
|
|
282
315
|
|
|
283
316
|
if (require.main === module) (async function main() {
|
|
284
317
|
const [binary, ...args] = process.argv.slice(2);
|