sparkecoder 0.1.115 → 0.1.117
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/agent/index.d.ts +3 -3
- package/dist/agent/index.js +62 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +387 -108
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-Biy5JTop.d.ts → index-Bi8Ek02A.d.ts} +104 -104
- package/dist/index.d.ts +5 -5
- package/dist/index.js +313 -64
- package/dist/index.js.map +1 -1
- package/dist/{schema-CYSKJZ3m.d.ts → schema-ecQSnCMz.d.ts} +3 -3
- package/dist/{search-CVVfuBPZ.d.ts → search-DOzC4ojH.d.ts} +4 -4
- package/dist/server/index.js +313 -64
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/computer-use.md +7 -0
- package/dist/skills/default/recording.md +5 -0
- package/dist/tools/index.d.ts +3 -3
- package/package.json +1 -1
- package/src/skills/default/computer-use.md +7 -0
- package/src/skills/default/recording.md +5 -0
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_7340c8b3._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__f3e6443f._.js +15 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_41927ef5._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/2c3c1d478808e4e4.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/3b0501ec3249235f.js +7 -0
- package/web/.next/standalone/web/.next/static/chunks/44c575e006ddb992.js +13 -0
- package/web/.next/standalone/web/.next/static/chunks/9a3afb48c245fb2a.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/b3bc7244f3477729.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/2c3c1d478808e4e4.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/3b0501ec3249235f.js +7 -0
- package/web/.next/standalone/web/.next/static/static/chunks/44c575e006ddb992.js +13 -0
- package/web/.next/standalone/web/.next/static/static/chunks/9a3afb48c245fb2a.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/b3bc7244f3477729.css +1 -0
- package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +255 -3
- package/web/.next/static/chunks/2c3c1d478808e4e4.js +1 -0
- package/web/.next/static/chunks/3b0501ec3249235f.js +7 -0
- package/web/.next/static/chunks/44c575e006ddb992.js +13 -0
- package/web/.next/static/chunks/9a3afb48c245fb2a.js +1 -0
- package/web/.next/static/chunks/b3bc7244f3477729.css +1 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_50c2f239._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__6097da17._.js +0 -15
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_3b9a2423._.js +0 -3
- package/web/.next/standalone/web/.next/static/chunks/60359bdd369c0c72.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/a189cacf6d83cf0b.js +0 -13
- package/web/.next/standalone/web/.next/static/chunks/bef6931fdd8428c8.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/c1f73b3fa4353c31.css +0 -1
- package/web/.next/standalone/web/.next/static/chunks/fbdcbd65f9a38f4b.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/60359bdd369c0c72.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/a189cacf6d83cf0b.js +0 -13
- package/web/.next/standalone/web/.next/static/static/chunks/bef6931fdd8428c8.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/c1f73b3fa4353c31.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/fbdcbd65f9a38f4b.js +0 -7
- package/web/.next/static/chunks/60359bdd369c0c72.js +0 -1
- package/web/.next/static/chunks/a189cacf6d83cf0b.js +0 -13
- package/web/.next/static/chunks/bef6931fdd8428c8.js +0 -1
- package/web/.next/static/chunks/c1f73b3fa4353c31.css +0 -1
- package/web/.next/static/chunks/fbdcbd65f9a38f4b.js +0 -7
- /package/web/.next/standalone/web/.next/static/{pSGgtxBjN5_o0YHZgQzXm → static/vLqK4jK7EKdLCpQ-D6-qL}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{pSGgtxBjN5_o0YHZgQzXm → static/vLqK4jK7EKdLCpQ-D6-qL}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{pSGgtxBjN5_o0YHZgQzXm → static/vLqK4jK7EKdLCpQ-D6-qL}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/pSGgtxBjN5_o0YHZgQzXm → vLqK4jK7EKdLCpQ-D6-qL}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/pSGgtxBjN5_o0YHZgQzXm → vLqK4jK7EKdLCpQ-D6-qL}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/pSGgtxBjN5_o0YHZgQzXm → vLqK4jK7EKdLCpQ-D6-qL}/_ssgManifest.js +0 -0
- /package/web/.next/static/{pSGgtxBjN5_o0YHZgQzXm → vLqK4jK7EKdLCpQ-D6-qL}/_buildManifest.js +0 -0
- /package/web/.next/static/{pSGgtxBjN5_o0YHZgQzXm → vLqK4jK7EKdLCpQ-D6-qL}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{pSGgtxBjN5_o0YHZgQzXm → vLqK4jK7EKdLCpQ-D6-qL}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -5386,7 +5386,7 @@ function isPathExcluded(relativePath, exclude) {
|
|
|
5386
5386
|
}
|
|
5387
5387
|
async function walkDirectory(dir, include, exclude, baseDir) {
|
|
5388
5388
|
const { readdirSync: readdirSync4 } = await import("fs");
|
|
5389
|
-
const { join:
|
|
5389
|
+
const { join: join18, relative: relative10 } = await import("path");
|
|
5390
5390
|
const files = [];
|
|
5391
5391
|
function walk(currentDir) {
|
|
5392
5392
|
let entries;
|
|
@@ -5396,7 +5396,7 @@ async function walkDirectory(dir, include, exclude, baseDir) {
|
|
|
5396
5396
|
return;
|
|
5397
5397
|
}
|
|
5398
5398
|
for (const entry2 of entries) {
|
|
5399
|
-
const fullPath =
|
|
5399
|
+
const fullPath = join18(currentDir, entry2.name);
|
|
5400
5400
|
const relativePath = relative10(baseDir, fullPath);
|
|
5401
5401
|
if (isPathExcluded(relativePath, exclude)) {
|
|
5402
5402
|
continue;
|
|
@@ -8409,6 +8409,68 @@ Headless workers never interfere with desktop workers (they don't touch the scre
|
|
|
8409
8409
|
When you spawn a **desktop worker**, include a one-liner in the goal asking it to **record the session by default** (\`screencapture -v -V <seconds> -C\` on macOS) and return the recording path in its result, *unless* the task is long-running / boring / contains sensitive content. The recording lets you (and the user) replay what actually happened on screen. When the worker reports back, mention the recording path in your reply via the original channel.` : ""}
|
|
8410
8410
|
|
|
8411
8411
|
Default bias: **when in doubt, decompose**. Two workers running in parallel and reporting independently is almost always better UX than one worker doing things sequentially.
|
|
8412
|
+
|
|
8413
|
+
### How to TALK to the user (versus how you reason internally)
|
|
8414
|
+
|
|
8415
|
+
All of the rules below \u2014 decomposition, parallel spawning, "don't invent commands", load-the-skill, etc. \u2014 are **your internal operating procedure**. They are how you decide what to do. They are NOT something to recite back to the user.
|
|
8416
|
+
|
|
8417
|
+
When replying to the user (Slack, web, or any channel), be a normal helpful assistant:
|
|
8418
|
+
|
|
8419
|
+
- Tell them *what you're doing*, not *how you're doing it internally*.
|
|
8420
|
+
- Don't quote your own prompt language ("just the goal", "read the skill", "no step-by-step from me", "decomposing into parallel workers"). The user doesn't care about your delegation pattern.
|
|
8421
|
+
- Don't narrate "handing off to a worker now" unless it's actually relevant (e.g. they asked how you work, or the work is going to take a noticeably long time).
|
|
8422
|
+
- Skip ceremony. A short acknowledgement + a brief description of the actual task + (optionally) a heads-up if there's anything they need to do or avoid is plenty.
|
|
8423
|
+
|
|
8424
|
+
| What you might be tempted to say | What you should say instead |
|
|
8425
|
+
|---|---|
|
|
8426
|
+
| *"Got it \u2014 handing it off with just the goal + 'read the skills and figure it out.' No step-by-step from me."* | *"On it. Recording a screen capture of the Weather app showing Anchorage. Don't touch the keyboard while it's running \u2014 I'll post the video when it's done."* |
|
|
8427
|
+
| *"Decomposing into two parallel workers: one headless, one desktop."* | *"Running the tests and pulling the diff at the same time. Back in ~30s."* |
|
|
8428
|
+
| *"Spawning worker \`screenshot-calc\` with goal: \u2026"* | *"Taking a screenshot of Calculator now."* |
|
|
8429
|
+
| *"I'll relay the user's instructions to the worker verbatim."* | *(say nothing \u2014 just do it)* |
|
|
8430
|
+
|
|
8431
|
+
If the user explicitly asks how you work, *then* you can explain the orchestrator/worker split. Otherwise: less is more.
|
|
8432
|
+
|
|
8433
|
+
### How to write a worker goal (and what NOT to put in it)
|
|
8434
|
+
|
|
8435
|
+
You delegate; the worker executes. Stay at the **what** level, not the **how**.
|
|
8436
|
+
|
|
8437
|
+
**DO** put in the goal:
|
|
8438
|
+
|
|
8439
|
+
- The end objective ("open the macOS Weather app and capture the forecast for Anchorage as a screen recording").
|
|
8440
|
+
- Which skills the worker should load up-front (\`load_skill recording\`, \`load_skill computer-use\`).
|
|
8441
|
+
- Acceptance criteria ("verify the city name is visible in the screenshot before reporting done").
|
|
8442
|
+
- Routing back ("post the recording URL to Slack channel C0123 thread 1700.001").
|
|
8443
|
+
|
|
8444
|
+
**DO NOT** put in the goal (when you're inventing commands from memory):
|
|
8445
|
+
|
|
8446
|
+
- Literal shell commands (\`screencapture -v -V 45 -C /tmp/...\`, \`osascript -e ...\`, \`cliclick kp:cmd+f\`).
|
|
8447
|
+
- Pre-written step-by-step bash that you're drafting from training-data recall.
|
|
8448
|
+
- The names of tools you can't see in your current toolset.
|
|
8449
|
+
|
|
8450
|
+
If you have to reach for memory to write a command, **you're guessing** \u2014 your training data has lots of plausible-looking commands that are subtly wrong or stale. The worker will dutifully follow your wrong instructions instead of consulting the source of truth.
|
|
8451
|
+
|
|
8452
|
+
**Exceptions \u2014 concrete steps ARE appropriate ONLY when relayed verbatim:**
|
|
8453
|
+
|
|
8454
|
+
You CAN include literal step-by-step commands when one of these is true:
|
|
8455
|
+
|
|
8456
|
+
1. **The user gave the steps to you.** Their message says "first run X, then Y, then commit" \u2192 forward those verbatim. That's the user's intent, not your guess.
|
|
8457
|
+
2. **A previous turn already established them.** An earlier worker reported "the deploy script lives at \`./scripts/deploy.sh --env prod\`" \u2192 you can hand that command to the next worker.
|
|
8458
|
+
3. **The conversation history contains them.** A documented \`bash\` invocation from earlier in the chat, an error message that revealed the right path, a CI log the user shared \u2014 anything already concrete in context.
|
|
8459
|
+
|
|
8460
|
+
In all three cases, **label the source** in the goal: *"Per your instructions earlier: 1. run X, 2. run Y..."* or *"Reusing the command worker-foo found last turn: \`./scripts/deploy.sh\`"*.
|
|
8461
|
+
|
|
8462
|
+
What you do NOT do:
|
|
8463
|
+
|
|
8464
|
+
- **Don't paraphrase or "improve" a skill into the goal.** If the recording skill describes \`sparkecoder record start/stop\`, the goal just says *"per the recording skill, bracket the work with start/stop"* \u2014 never quote the exact command. The worker loads the skill and reads the canonical version itself. You quoting it just adds a way for the quote to drift behind the source of truth.
|
|
8465
|
+
- **Don't reason your way to a command.** "I think macOS uses \`open -a Weather\` to launch apps" is exactly the kind of training-data recall that produces stale or wrong commands. Don't include it.
|
|
8466
|
+
|
|
8467
|
+
The rule is: **relay, never invent \u2014 and never quote a skill.** Your job is to (a) tell the worker which skills to load, (b) state the objective and acceptance criteria, (c) relay any concrete steps the user or prior context already established. Workers do the looking-up; you do the dispatching.
|
|
8468
|
+
|
|
8469
|
+
Bad goal (don't do this):
|
|
8470
|
+
> "Start a screen recording with \`screencapture -v -V 45 -C /tmp/weather.mov &\`, then open Weather with \`open -a Weather\`, then click the search bar with cliclick, type 'Anchorage'..."
|
|
8471
|
+
|
|
8472
|
+
Good goal (do this):
|
|
8473
|
+
> "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill computer-use\` first; use the canonical commands from those skills. Verify the Anchorage temperature is visible in a final screenshot before completing. Return the recording path + a one-line summary of the forecast."
|
|
8412
8474
|
`;
|
|
8413
8475
|
}
|
|
8414
8476
|
function createSummaryPrompt(conversationHistory) {
|
|
@@ -11325,11 +11387,11 @@ ${p.text}` : p.text;
|
|
|
11325
11387
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
11326
11388
|
if (!isRemoteConfigured2()) return [];
|
|
11327
11389
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
11328
|
-
const { join:
|
|
11390
|
+
const { join: join18, basename: basename6 } = await import("path");
|
|
11329
11391
|
const urls = [];
|
|
11330
11392
|
for (const filePath of filePaths) {
|
|
11331
11393
|
try {
|
|
11332
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
11394
|
+
const fullPath = filePath.startsWith("/") ? filePath : join18(this.session.workingDirectory, filePath);
|
|
11333
11395
|
const fileName = basename6(fullPath);
|
|
11334
11396
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
11335
11397
|
const mimeMap = {
|
|
@@ -11533,6 +11595,121 @@ var init_session_lock = __esm({
|
|
|
11533
11595
|
}
|
|
11534
11596
|
});
|
|
11535
11597
|
|
|
11598
|
+
// src/orchestrator/webhook-events.ts
|
|
11599
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
|
|
11600
|
+
import { dirname as dirname6, join as join12 } from "path";
|
|
11601
|
+
import { nanoid as nanoid10 } from "nanoid";
|
|
11602
|
+
function logFilePath() {
|
|
11603
|
+
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
11604
|
+
}
|
|
11605
|
+
function ensureLoaded() {
|
|
11606
|
+
if (cache !== null) return cache;
|
|
11607
|
+
cache = [];
|
|
11608
|
+
try {
|
|
11609
|
+
const p = logFilePath();
|
|
11610
|
+
if (!existsSync18(p)) return cache;
|
|
11611
|
+
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
11612
|
+
for (const line of lines) {
|
|
11613
|
+
try {
|
|
11614
|
+
cache.push(JSON.parse(line));
|
|
11615
|
+
} catch {
|
|
11616
|
+
}
|
|
11617
|
+
}
|
|
11618
|
+
if (cache.length > MAX_EVENTS) {
|
|
11619
|
+
cache = cache.slice(-MAX_EVENTS);
|
|
11620
|
+
try {
|
|
11621
|
+
writeFileSync3(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11622
|
+
} catch {
|
|
11623
|
+
}
|
|
11624
|
+
}
|
|
11625
|
+
} catch {
|
|
11626
|
+
}
|
|
11627
|
+
return cache;
|
|
11628
|
+
}
|
|
11629
|
+
function appendEvent(ev) {
|
|
11630
|
+
const list = ensureLoaded();
|
|
11631
|
+
list.push(ev);
|
|
11632
|
+
if (list.length > MAX_EVENTS) list.shift();
|
|
11633
|
+
try {
|
|
11634
|
+
const p = logFilePath();
|
|
11635
|
+
mkdirSync7(dirname6(p), { recursive: true });
|
|
11636
|
+
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
11637
|
+
} catch {
|
|
11638
|
+
}
|
|
11639
|
+
}
|
|
11640
|
+
function recordEvent(ev) {
|
|
11641
|
+
const full = {
|
|
11642
|
+
id: ev.id ?? nanoid10(),
|
|
11643
|
+
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
11644
|
+
source: ev.source,
|
|
11645
|
+
status: ev.status,
|
|
11646
|
+
subtype: ev.subtype,
|
|
11647
|
+
channel: ev.channel,
|
|
11648
|
+
user: ev.user,
|
|
11649
|
+
textSnippet: ev.textSnippet?.slice(0, 200),
|
|
11650
|
+
dropReason: ev.dropReason,
|
|
11651
|
+
error: ev.error,
|
|
11652
|
+
sessionId: ev.sessionId,
|
|
11653
|
+
durationMs: ev.durationMs,
|
|
11654
|
+
meta: ev.meta
|
|
11655
|
+
};
|
|
11656
|
+
appendEvent(full);
|
|
11657
|
+
return full.id;
|
|
11658
|
+
}
|
|
11659
|
+
function updateEvent(id, patch) {
|
|
11660
|
+
const list = ensureLoaded();
|
|
11661
|
+
const i = list.findIndex((e) => e.id === id);
|
|
11662
|
+
if (i < 0) return;
|
|
11663
|
+
list[i] = { ...list[i], ...patch };
|
|
11664
|
+
try {
|
|
11665
|
+
const p = logFilePath();
|
|
11666
|
+
mkdirSync7(dirname6(p), { recursive: true });
|
|
11667
|
+
writeFileSync3(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11668
|
+
} catch {
|
|
11669
|
+
}
|
|
11670
|
+
}
|
|
11671
|
+
function listEvents(filter = {}) {
|
|
11672
|
+
const list = ensureLoaded();
|
|
11673
|
+
const q = filter.q?.toLowerCase();
|
|
11674
|
+
const sinceTs = filter.since ? Date.parse(filter.since) : -Infinity;
|
|
11675
|
+
const beforeTs = filter.before ? Date.parse(filter.before) : Infinity;
|
|
11676
|
+
const matched = list.filter((e) => {
|
|
11677
|
+
if (filter.source && e.source !== filter.source) return false;
|
|
11678
|
+
if (filter.status && e.status !== filter.status) return false;
|
|
11679
|
+
const t = Date.parse(e.ts);
|
|
11680
|
+
if (t < sinceTs) return false;
|
|
11681
|
+
if (t >= beforeTs) return false;
|
|
11682
|
+
if (q) {
|
|
11683
|
+
const hay = `${e.channel ?? ""} ${e.user ?? ""} ${e.textSnippet ?? ""} ${e.dropReason ?? ""} ${e.error ?? ""} ${e.subtype ?? ""}`.toLowerCase();
|
|
11684
|
+
if (!hay.includes(q)) return false;
|
|
11685
|
+
}
|
|
11686
|
+
return true;
|
|
11687
|
+
});
|
|
11688
|
+
matched.reverse();
|
|
11689
|
+
const offset = Math.max(0, filter.offset ?? 0);
|
|
11690
|
+
const limit = Math.min(500, Math.max(1, filter.limit ?? 50));
|
|
11691
|
+
return {
|
|
11692
|
+
events: matched.slice(offset, offset + limit),
|
|
11693
|
+
total: matched.length
|
|
11694
|
+
};
|
|
11695
|
+
}
|
|
11696
|
+
function clearAllEvents() {
|
|
11697
|
+
cache = [];
|
|
11698
|
+
try {
|
|
11699
|
+
writeFileSync3(logFilePath(), "");
|
|
11700
|
+
} catch {
|
|
11701
|
+
}
|
|
11702
|
+
}
|
|
11703
|
+
var MAX_EVENTS, cache;
|
|
11704
|
+
var init_webhook_events = __esm({
|
|
11705
|
+
"src/orchestrator/webhook-events.ts"() {
|
|
11706
|
+
"use strict";
|
|
11707
|
+
init_config();
|
|
11708
|
+
MAX_EVENTS = 1e3;
|
|
11709
|
+
cache = null;
|
|
11710
|
+
}
|
|
11711
|
+
});
|
|
11712
|
+
|
|
11536
11713
|
// src/orchestrator/daemon.ts
|
|
11537
11714
|
var daemon_exports = {};
|
|
11538
11715
|
__export(daemon_exports, {
|
|
@@ -11594,6 +11771,17 @@ async function runDaemonTurn(sessionId, events) {
|
|
|
11594
11771
|
});
|
|
11595
11772
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
11596
11773
|
const trimmed = text.trim();
|
|
11774
|
+
recordEvent({
|
|
11775
|
+
source: "daemon",
|
|
11776
|
+
status: error ? "failed" : "completed",
|
|
11777
|
+
channel: events[0]?.ref?.channel ?? void 0,
|
|
11778
|
+
user: events[0]?.ref?.user,
|
|
11779
|
+
textSnippet: trimmed.slice(0, 200),
|
|
11780
|
+
error,
|
|
11781
|
+
sessionId,
|
|
11782
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
11783
|
+
meta: { triggeredBy: events.map((e) => e.content?.slice(0, 80)) }
|
|
11784
|
+
});
|
|
11597
11785
|
broadcast({ sessionId, text: trimmed, triggeredBy: events, startedAt, finishedAt, error });
|
|
11598
11786
|
}
|
|
11599
11787
|
var listeners;
|
|
@@ -11604,6 +11792,7 @@ var init_daemon = __esm({
|
|
|
11604
11792
|
init_session_lock();
|
|
11605
11793
|
init_db();
|
|
11606
11794
|
init_inbox();
|
|
11795
|
+
init_webhook_events();
|
|
11607
11796
|
listeners = /* @__PURE__ */ new Map();
|
|
11608
11797
|
}
|
|
11609
11798
|
});
|
|
@@ -11855,7 +12044,7 @@ import chalk from "chalk";
|
|
|
11855
12044
|
import ora from "ora";
|
|
11856
12045
|
import "dotenv/config";
|
|
11857
12046
|
import { createInterface } from "readline";
|
|
11858
|
-
import { dirname as
|
|
12047
|
+
import { dirname as dirname9 } from "path";
|
|
11859
12048
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11860
12049
|
|
|
11861
12050
|
// src/server/index.ts
|
|
@@ -11864,8 +12053,8 @@ import { Hono as Hono9 } from "hono";
|
|
|
11864
12053
|
import { serve } from "@hono/node-server";
|
|
11865
12054
|
import { cors } from "hono/cors";
|
|
11866
12055
|
import { logger } from "hono/logger";
|
|
11867
|
-
import { existsSync as
|
|
11868
|
-
import { resolve as resolve11, dirname as
|
|
12056
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
12057
|
+
import { resolve as resolve11, dirname as dirname8, join as join16 } from "path";
|
|
11869
12058
|
import { spawn as spawn2 } from "child_process";
|
|
11870
12059
|
import { createServer as createNetServer } from "net";
|
|
11871
12060
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -11879,10 +12068,10 @@ init_checkpoints();
|
|
|
11879
12068
|
import { Hono } from "hono";
|
|
11880
12069
|
import { zValidator } from "@hono/zod-validator";
|
|
11881
12070
|
import { z as z17 } from "zod";
|
|
11882
|
-
import { existsSync as
|
|
12071
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
11883
12072
|
import { readdir as readdir6 } from "fs/promises";
|
|
11884
|
-
import { join as
|
|
11885
|
-
import { nanoid as
|
|
12073
|
+
import { join as join13, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
12074
|
+
import { nanoid as nanoid11 } from "nanoid";
|
|
11886
12075
|
|
|
11887
12076
|
// src/tasks/agent-status.ts
|
|
11888
12077
|
init_questions();
|
|
@@ -12523,12 +12712,12 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
12523
12712
|
});
|
|
12524
12713
|
function getAttachmentsDir(sessionId) {
|
|
12525
12714
|
const appDataDir = getAppDataDirectory();
|
|
12526
|
-
return
|
|
12715
|
+
return join13(appDataDir, "attachments", sessionId);
|
|
12527
12716
|
}
|
|
12528
12717
|
function ensureAttachmentsDir(sessionId) {
|
|
12529
12718
|
const dir = getAttachmentsDir(sessionId);
|
|
12530
|
-
if (!
|
|
12531
|
-
|
|
12719
|
+
if (!existsSync19(dir)) {
|
|
12720
|
+
mkdirSync8(dir, { recursive: true });
|
|
12532
12721
|
}
|
|
12533
12722
|
return dir;
|
|
12534
12723
|
}
|
|
@@ -12539,12 +12728,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
12539
12728
|
return c.json({ error: "Session not found" }, 404);
|
|
12540
12729
|
}
|
|
12541
12730
|
const dir = getAttachmentsDir(sessionId);
|
|
12542
|
-
if (!
|
|
12731
|
+
if (!existsSync19(dir)) {
|
|
12543
12732
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
12544
12733
|
}
|
|
12545
12734
|
const files = readdirSync3(dir);
|
|
12546
12735
|
const attachments = files.map((filename) => {
|
|
12547
|
-
const filePath =
|
|
12736
|
+
const filePath = join13(dir, filename);
|
|
12548
12737
|
const stats = statSync2(filePath);
|
|
12549
12738
|
return {
|
|
12550
12739
|
id: filename.split("_")[0],
|
|
@@ -12576,12 +12765,12 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12576
12765
|
return c.json({ error: "No file provided" }, 400);
|
|
12577
12766
|
}
|
|
12578
12767
|
const dir = ensureAttachmentsDir(sessionId);
|
|
12579
|
-
const id =
|
|
12768
|
+
const id = nanoid11(10);
|
|
12580
12769
|
const ext = extname8(file.name) || "";
|
|
12581
12770
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12582
|
-
const filePath =
|
|
12771
|
+
const filePath = join13(dir, safeFilename);
|
|
12583
12772
|
const arrayBuffer = await file.arrayBuffer();
|
|
12584
|
-
|
|
12773
|
+
writeFileSync4(filePath, Buffer.from(arrayBuffer));
|
|
12585
12774
|
return c.json({
|
|
12586
12775
|
id,
|
|
12587
12776
|
filename: file.name,
|
|
@@ -12602,16 +12791,16 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12602
12791
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
12603
12792
|
}
|
|
12604
12793
|
const dir = ensureAttachmentsDir(sessionId);
|
|
12605
|
-
const id =
|
|
12794
|
+
const id = nanoid11(10);
|
|
12606
12795
|
const ext = extname8(body.filename) || "";
|
|
12607
12796
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12608
|
-
const filePath =
|
|
12797
|
+
const filePath = join13(dir, safeFilename);
|
|
12609
12798
|
let base64Data = body.data;
|
|
12610
12799
|
if (base64Data.includes(",")) {
|
|
12611
12800
|
base64Data = base64Data.split(",")[1];
|
|
12612
12801
|
}
|
|
12613
12802
|
const buffer = Buffer.from(base64Data, "base64");
|
|
12614
|
-
|
|
12803
|
+
writeFileSync4(filePath, buffer);
|
|
12615
12804
|
return c.json({
|
|
12616
12805
|
id,
|
|
12617
12806
|
filename: body.filename,
|
|
@@ -12634,7 +12823,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12634
12823
|
return c.json({ error: "Session not found" }, 404);
|
|
12635
12824
|
}
|
|
12636
12825
|
const dir = getAttachmentsDir(sessionId);
|
|
12637
|
-
if (!
|
|
12826
|
+
if (!existsSync19(dir)) {
|
|
12638
12827
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12639
12828
|
}
|
|
12640
12829
|
const files = readdirSync3(dir);
|
|
@@ -12642,7 +12831,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12642
12831
|
if (!file) {
|
|
12643
12832
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12644
12833
|
}
|
|
12645
|
-
const filePath =
|
|
12834
|
+
const filePath = join13(dir, file);
|
|
12646
12835
|
unlinkSync3(filePath);
|
|
12647
12836
|
return c.json({ success: true, id: attachmentId });
|
|
12648
12837
|
});
|
|
@@ -12725,7 +12914,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12725
12914
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12726
12915
|
for (const entry2 of entries) {
|
|
12727
12916
|
if (results.length >= limit * 2) break;
|
|
12728
|
-
const fullPath =
|
|
12917
|
+
const fullPath = join13(currentDir, entry2.name);
|
|
12729
12918
|
const relativePath = relative9(baseDir, fullPath);
|
|
12730
12919
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12731
12920
|
continue;
|
|
@@ -12773,7 +12962,7 @@ sessions2.get(
|
|
|
12773
12962
|
return c.json({ error: "Session not found" }, 404);
|
|
12774
12963
|
}
|
|
12775
12964
|
const workingDirectory = session.workingDirectory;
|
|
12776
|
-
if (!
|
|
12965
|
+
if (!existsSync19(workingDirectory)) {
|
|
12777
12966
|
return c.json({
|
|
12778
12967
|
sessionId,
|
|
12779
12968
|
workingDirectory,
|
|
@@ -12887,8 +13076,8 @@ init_config();
|
|
|
12887
13076
|
import { Hono as Hono2 } from "hono";
|
|
12888
13077
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
12889
13078
|
import { z as z18 } from "zod";
|
|
12890
|
-
import { existsSync as
|
|
12891
|
-
import { join as
|
|
13079
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
13080
|
+
import { join as join14 } from "path";
|
|
12892
13081
|
|
|
12893
13082
|
// src/server/resumable-stream.ts
|
|
12894
13083
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -12975,7 +13164,7 @@ var streamContext = createResumableStreamContext({
|
|
|
12975
13164
|
|
|
12976
13165
|
// src/server/routes/agents.ts
|
|
12977
13166
|
init_checkpoints();
|
|
12978
|
-
import { nanoid as
|
|
13167
|
+
import { nanoid as nanoid12 } from "nanoid";
|
|
12979
13168
|
init_stream_proxy();
|
|
12980
13169
|
init_recorder();
|
|
12981
13170
|
init_remote();
|
|
@@ -13095,12 +13284,12 @@ var rejectSchema = z18.object({
|
|
|
13095
13284
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
13096
13285
|
function getAttachmentsDirectory(sessionId) {
|
|
13097
13286
|
const appDataDir = getAppDataDirectory();
|
|
13098
|
-
return
|
|
13287
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
13099
13288
|
}
|
|
13100
13289
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
13101
13290
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
13102
|
-
if (!
|
|
13103
|
-
|
|
13291
|
+
if (!existsSync20(attachmentsDir)) {
|
|
13292
|
+
mkdirSync9(attachmentsDir, { recursive: true });
|
|
13104
13293
|
}
|
|
13105
13294
|
let filename = attachment.filename;
|
|
13106
13295
|
if (!filename) {
|
|
@@ -13118,8 +13307,8 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
13118
13307
|
attachment.mediaType = resized.mediaType;
|
|
13119
13308
|
attachment.data = buffer.toString("base64");
|
|
13120
13309
|
}
|
|
13121
|
-
const filePath =
|
|
13122
|
-
|
|
13310
|
+
const filePath = join14(attachmentsDir, filename);
|
|
13311
|
+
writeFileSync5(filePath, buffer);
|
|
13123
13312
|
return filePath;
|
|
13124
13313
|
}
|
|
13125
13314
|
function stripDataUrlPrefix2(data) {
|
|
@@ -13555,7 +13744,7 @@ ${prompt}` });
|
|
|
13555
13744
|
});
|
|
13556
13745
|
} catch {
|
|
13557
13746
|
}
|
|
13558
|
-
const streamId = `stream_${id}_${
|
|
13747
|
+
const streamId = `stream_${id}_${nanoid12(10)}`;
|
|
13559
13748
|
console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
|
|
13560
13749
|
await activeStreamQueries.create(id, streamId);
|
|
13561
13750
|
const stream = await streamContext.resumableStream(
|
|
@@ -13760,7 +13949,7 @@ agents.post(
|
|
|
13760
13949
|
});
|
|
13761
13950
|
const session = agent.getSession();
|
|
13762
13951
|
const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
|
|
13763
|
-
const streamId = `stream_${session.id}_${
|
|
13952
|
+
const streamId = `stream_${session.id}_${nanoid12(10)}`;
|
|
13764
13953
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
13765
13954
|
await activeStreamQueries.create(session.id, streamId);
|
|
13766
13955
|
const createQuickStreamProducer = () => {
|
|
@@ -14079,26 +14268,26 @@ init_config();
|
|
|
14079
14268
|
import { Hono as Hono3 } from "hono";
|
|
14080
14269
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
14081
14270
|
import { z as z19 } from "zod";
|
|
14082
|
-
import { readFileSync as
|
|
14271
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
14083
14272
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14084
|
-
import { dirname as
|
|
14273
|
+
import { dirname as dirname7, join as join15 } from "path";
|
|
14085
14274
|
var __filename = fileURLToPath3(import.meta.url);
|
|
14086
|
-
var __dirname =
|
|
14275
|
+
var __dirname = dirname7(__filename);
|
|
14087
14276
|
var possiblePaths = [
|
|
14088
|
-
|
|
14277
|
+
join15(__dirname, "../package.json"),
|
|
14089
14278
|
// From dist/server -> dist/../package.json
|
|
14090
|
-
|
|
14279
|
+
join15(__dirname, "../../package.json"),
|
|
14091
14280
|
// From dist/server (if nested differently)
|
|
14092
|
-
|
|
14281
|
+
join15(__dirname, "../../../package.json"),
|
|
14093
14282
|
// From src/server/routes (development)
|
|
14094
|
-
|
|
14283
|
+
join15(process.cwd(), "package.json")
|
|
14095
14284
|
// From current working directory
|
|
14096
14285
|
];
|
|
14097
14286
|
var currentVersion = "0.0.0";
|
|
14098
14287
|
var packageName = "sparkecoder";
|
|
14099
14288
|
for (const packageJsonPath of possiblePaths) {
|
|
14100
14289
|
try {
|
|
14101
|
-
const packageJson = JSON.parse(
|
|
14290
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
14102
14291
|
if (packageJson.name === "sparkecoder") {
|
|
14103
14292
|
currentVersion = packageJson.version || "0.0.0";
|
|
14104
14293
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -14548,7 +14737,7 @@ init_config();
|
|
|
14548
14737
|
import { Hono as Hono5 } from "hono";
|
|
14549
14738
|
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
14550
14739
|
import { z as z21 } from "zod";
|
|
14551
|
-
import { nanoid as
|
|
14740
|
+
import { nanoid as nanoid13 } from "nanoid";
|
|
14552
14741
|
init_questions();
|
|
14553
14742
|
var tasks = new Hono5();
|
|
14554
14743
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
@@ -14629,7 +14818,7 @@ tasks.post(
|
|
|
14629
14818
|
const taskId = agent.sessionId;
|
|
14630
14819
|
const abortController = new AbortController();
|
|
14631
14820
|
taskAbortControllers.set(taskId, abortController);
|
|
14632
|
-
const streamId = `stream_${taskId}_${
|
|
14821
|
+
const streamId = `stream_${taskId}_${nanoid13(10)}`;
|
|
14633
14822
|
await activeStreamQueries.create(taskId, streamId);
|
|
14634
14823
|
const taskStreamProducer = () => {
|
|
14635
14824
|
const { readable, writable } = new TransformStream();
|
|
@@ -14855,6 +15044,7 @@ function verifySlackSignature(opts) {
|
|
|
14855
15044
|
// src/server/routes/slack.ts
|
|
14856
15045
|
init_client3();
|
|
14857
15046
|
init_slack();
|
|
15047
|
+
init_webhook_events();
|
|
14858
15048
|
init_inbox();
|
|
14859
15049
|
var recentlyHandled = /* @__PURE__ */ new Map();
|
|
14860
15050
|
var MAX_RECENT = 1e3;
|
|
@@ -14895,7 +15085,17 @@ slack.post("/events", async (c) => {
|
|
|
14895
15085
|
}
|
|
14896
15086
|
if (payload?.type === "event_callback" && payload?.event) {
|
|
14897
15087
|
const ev = payload.event;
|
|
15088
|
+
const auditId = recordEvent({
|
|
15089
|
+
source: "slack",
|
|
15090
|
+
status: "received",
|
|
15091
|
+
subtype: ev.type === "message" ? `message.${ev.channel_type ?? "channels"}` : ev.type,
|
|
15092
|
+
channel: ev.channel,
|
|
15093
|
+
user: ev.user,
|
|
15094
|
+
textSnippet: typeof ev.text === "string" ? ev.text : void 0,
|
|
15095
|
+
meta: { ts: ev.ts, thread_ts: ev.thread_ts, team: ev.team, event_subtype: ev.subtype }
|
|
15096
|
+
});
|
|
14898
15097
|
if (alreadyHandled(ev.channel, ev.ts)) {
|
|
15098
|
+
updateEvent(auditId, { status: "dropped", dropReason: "duplicate_delivery" });
|
|
14899
15099
|
return c.json({ ok: true });
|
|
14900
15100
|
}
|
|
14901
15101
|
const { event: inbound, dropReason } = slackEventToInboundResult(ev);
|
|
@@ -14905,6 +15105,7 @@ slack.post("/events", async (c) => {
|
|
|
14905
15105
|
const ours = isThreadOwned(ev.channel, ev.thread_ts) || await threadBelongsToUs(ev.channel, ev.thread_ts);
|
|
14906
15106
|
if (!ours) {
|
|
14907
15107
|
console.log(`[slack] dropping thread reply in unknown thread: channel=${ev.channel} thread=${ev.thread_ts}`);
|
|
15108
|
+
updateEvent(auditId, { status: "dropped", dropReason: "thread_not_owned" });
|
|
14908
15109
|
return c.json({ ok: true });
|
|
14909
15110
|
}
|
|
14910
15111
|
}
|
|
@@ -14917,8 +15118,12 @@ slack.post("/events", async (c) => {
|
|
|
14917
15118
|
const orchestratorId = await findOrCreateOrchestratorId();
|
|
14918
15119
|
if (orchestratorId) {
|
|
14919
15120
|
pushToInbox(orchestratorId, inbound);
|
|
15121
|
+
updateEvent(auditId, { status: "routed", sessionId: orchestratorId });
|
|
15122
|
+
} else {
|
|
15123
|
+
updateEvent(auditId, { status: "error", error: "no orchestrator session available" });
|
|
14920
15124
|
}
|
|
14921
15125
|
} else if (dropReason) {
|
|
15126
|
+
updateEvent(auditId, { status: "dropped", dropReason });
|
|
14922
15127
|
const userFacingDrops = ["user_not_allowed", "channel_not_allowed", "dm_blocked"];
|
|
14923
15128
|
if (userFacingDrops.includes(dropReason)) {
|
|
14924
15129
|
console.log(`[slack] dropped event from user=${payload.event.user} channel=${payload.event.channel}: ${dropReason}`);
|
|
@@ -14981,13 +15186,30 @@ async function sendDeniedReply(event) {
|
|
|
14981
15186
|
init_webhooks_store();
|
|
14982
15187
|
init_webhook();
|
|
14983
15188
|
init_inbox();
|
|
15189
|
+
init_webhook_events();
|
|
14984
15190
|
import { Hono as Hono7 } from "hono";
|
|
14985
15191
|
var inbox = new Hono7();
|
|
14986
15192
|
inbox.post("/:token", async (c) => {
|
|
14987
15193
|
const token = c.req.param("token");
|
|
14988
|
-
if (!token || token.length < 16)
|
|
15194
|
+
if (!token || token.length < 16) {
|
|
15195
|
+
recordEvent({
|
|
15196
|
+
source: "inbox",
|
|
15197
|
+
status: "dropped",
|
|
15198
|
+
dropReason: "invalid_token_format",
|
|
15199
|
+
meta: { tokenLen: token?.length ?? 0 }
|
|
15200
|
+
});
|
|
15201
|
+
return c.json({ error: "invalid token" }, 401);
|
|
15202
|
+
}
|
|
14989
15203
|
const lookup = await findByToken(token);
|
|
14990
|
-
if (!lookup)
|
|
15204
|
+
if (!lookup) {
|
|
15205
|
+
recordEvent({
|
|
15206
|
+
source: "inbox",
|
|
15207
|
+
status: "dropped",
|
|
15208
|
+
dropReason: "unknown_token",
|
|
15209
|
+
meta: { tokenPrefix: token.slice(0, 8) + "\u2026" }
|
|
15210
|
+
});
|
|
15211
|
+
return c.json({ error: "unknown token" }, 404);
|
|
15212
|
+
}
|
|
14991
15213
|
let body;
|
|
14992
15214
|
const contentType = c.req.header("content-type") || "";
|
|
14993
15215
|
try {
|
|
@@ -15009,6 +15231,14 @@ inbox.post("/:token", async (c) => {
|
|
|
15009
15231
|
pushToInbox(lookup.orchestratorSessionId, event);
|
|
15010
15232
|
void recordHit(lookup.orchestratorSessionId, lookup.webhook.id).catch(() => {
|
|
15011
15233
|
});
|
|
15234
|
+
recordEvent({
|
|
15235
|
+
source: "inbox",
|
|
15236
|
+
status: "routed",
|
|
15237
|
+
channel: lookup.webhook.name,
|
|
15238
|
+
textSnippet: typeof body === "string" ? body : JSON.stringify(body).slice(0, 200),
|
|
15239
|
+
sessionId: lookup.orchestratorSessionId,
|
|
15240
|
+
meta: { wake: lookup.webhook.wake, webhookId: lookup.webhook.id }
|
|
15241
|
+
});
|
|
15012
15242
|
return c.json({ ok: true, queued: true, wake: lookup.webhook.wake });
|
|
15013
15243
|
});
|
|
15014
15244
|
|
|
@@ -15020,6 +15250,7 @@ init_webhooks_store();
|
|
|
15020
15250
|
init_messenger();
|
|
15021
15251
|
init_store();
|
|
15022
15252
|
init_pool();
|
|
15253
|
+
init_webhook_events();
|
|
15023
15254
|
import { Hono as Hono8 } from "hono";
|
|
15024
15255
|
import { zValidator as zValidator6 } from "@hono/zod-validator";
|
|
15025
15256
|
import { z as z22 } from "zod";
|
|
@@ -15078,6 +15309,24 @@ function webhookPrefix() {
|
|
|
15078
15309
|
const token = cfg?.webhooks?.token;
|
|
15079
15310
|
return token ? `/w/${token}` : "/api";
|
|
15080
15311
|
}
|
|
15312
|
+
integrations.get("/events", async (c) => {
|
|
15313
|
+
const q = c.req.query();
|
|
15314
|
+
const filter = {
|
|
15315
|
+
source: q.source,
|
|
15316
|
+
status: q.status,
|
|
15317
|
+
q: q.q,
|
|
15318
|
+
since: q.since,
|
|
15319
|
+
before: q.before,
|
|
15320
|
+
limit: q.limit ? Math.min(500, Math.max(1, parseInt(q.limit, 10))) : 50,
|
|
15321
|
+
offset: q.offset ? Math.max(0, parseInt(q.offset, 10)) : 0
|
|
15322
|
+
};
|
|
15323
|
+
const { events, total } = listEvents(filter);
|
|
15324
|
+
return c.json({ events, total, limit: filter.limit, offset: filter.offset });
|
|
15325
|
+
});
|
|
15326
|
+
integrations.delete("/events", async (c) => {
|
|
15327
|
+
clearAllEvents();
|
|
15328
|
+
return c.json({ ok: true });
|
|
15329
|
+
});
|
|
15081
15330
|
integrations.get("/", async (c) => {
|
|
15082
15331
|
const cfg = getConfig();
|
|
15083
15332
|
return c.json({
|
|
@@ -15536,13 +15785,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
15536
15785
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
15537
15786
|
function getWebDirectory() {
|
|
15538
15787
|
try {
|
|
15539
|
-
const currentDir =
|
|
15788
|
+
const currentDir = dirname8(fileURLToPath4(import.meta.url));
|
|
15540
15789
|
const webDir = resolve11(currentDir, "..", "web");
|
|
15541
|
-
if (
|
|
15790
|
+
if (existsSync21(webDir) && existsSync21(join16(webDir, "package.json"))) {
|
|
15542
15791
|
return webDir;
|
|
15543
15792
|
}
|
|
15544
15793
|
const altWebDir = resolve11(currentDir, "..", "..", "web");
|
|
15545
|
-
if (
|
|
15794
|
+
if (existsSync21(altWebDir) && existsSync21(join16(altWebDir, "package.json"))) {
|
|
15546
15795
|
return altWebDir;
|
|
15547
15796
|
}
|
|
15548
15797
|
return null;
|
|
@@ -15600,23 +15849,23 @@ async function findWebPort(preferredPort) {
|
|
|
15600
15849
|
return { port: preferredPort, alreadyRunning: false };
|
|
15601
15850
|
}
|
|
15602
15851
|
function hasProductionBuild(webDir) {
|
|
15603
|
-
const buildIdPath =
|
|
15604
|
-
return
|
|
15852
|
+
const buildIdPath = join16(webDir, ".next", "BUILD_ID");
|
|
15853
|
+
return existsSync21(buildIdPath);
|
|
15605
15854
|
}
|
|
15606
15855
|
function hasSourceFiles(webDir) {
|
|
15607
|
-
const appDir =
|
|
15608
|
-
const pagesDir =
|
|
15609
|
-
const rootAppDir =
|
|
15610
|
-
const rootPagesDir =
|
|
15611
|
-
return
|
|
15856
|
+
const appDir = join16(webDir, "src", "app");
|
|
15857
|
+
const pagesDir = join16(webDir, "src", "pages");
|
|
15858
|
+
const rootAppDir = join16(webDir, "app");
|
|
15859
|
+
const rootPagesDir = join16(webDir, "pages");
|
|
15860
|
+
return existsSync21(appDir) || existsSync21(pagesDir) || existsSync21(rootAppDir) || existsSync21(rootPagesDir);
|
|
15612
15861
|
}
|
|
15613
15862
|
function getStandaloneServerPath(webDir) {
|
|
15614
15863
|
const possiblePaths2 = [
|
|
15615
|
-
|
|
15616
|
-
|
|
15864
|
+
join16(webDir, ".next", "standalone", "server.js"),
|
|
15865
|
+
join16(webDir, ".next", "standalone", "web", "server.js")
|
|
15617
15866
|
];
|
|
15618
15867
|
for (const serverPath of possiblePaths2) {
|
|
15619
|
-
if (
|
|
15868
|
+
if (existsSync21(serverPath)) {
|
|
15620
15869
|
return serverPath;
|
|
15621
15870
|
}
|
|
15622
15871
|
}
|
|
@@ -15656,15 +15905,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15656
15905
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
15657
15906
|
return { process: null, port: actualPort };
|
|
15658
15907
|
}
|
|
15659
|
-
const usePnpm =
|
|
15660
|
-
const useNpm = !usePnpm &&
|
|
15908
|
+
const usePnpm = existsSync21(join16(webDir, "pnpm-lock.yaml"));
|
|
15909
|
+
const useNpm = !usePnpm && existsSync21(join16(webDir, "package-lock.json"));
|
|
15661
15910
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
15662
15911
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
15663
15912
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
15664
15913
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
15665
|
-
const runtimeConfigPath =
|
|
15914
|
+
const runtimeConfigPath = join16(webDir, "runtime-config.json");
|
|
15666
15915
|
try {
|
|
15667
|
-
|
|
15916
|
+
writeFileSync6(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
15668
15917
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
15669
15918
|
} catch (err) {
|
|
15670
15919
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -15684,7 +15933,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15684
15933
|
if (standaloneServerPath) {
|
|
15685
15934
|
command = "node";
|
|
15686
15935
|
args = ["server.js"];
|
|
15687
|
-
cwd =
|
|
15936
|
+
cwd = dirname8(standaloneServerPath);
|
|
15688
15937
|
webEnv.PORT = String(actualPort);
|
|
15689
15938
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
15690
15939
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -15877,8 +16126,8 @@ async function startServer(options = {}) {
|
|
|
15877
16126
|
if (options.workingDirectory) {
|
|
15878
16127
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
15879
16128
|
}
|
|
15880
|
-
if (!
|
|
15881
|
-
|
|
16129
|
+
if (!existsSync21(config.resolvedWorkingDirectory)) {
|
|
16130
|
+
mkdirSync10(config.resolvedWorkingDirectory, { recursive: true });
|
|
15882
16131
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
15883
16132
|
}
|
|
15884
16133
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -16422,18 +16671,18 @@ function generateOpenAPISpec() {
|
|
|
16422
16671
|
init_config();
|
|
16423
16672
|
init_semantic();
|
|
16424
16673
|
init_db();
|
|
16425
|
-
import { mkdirSync as
|
|
16426
|
-
import { resolve as resolve12, join as
|
|
16674
|
+
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync7, readFileSync as readFileSync11, existsSync as existsSync22, statSync as statSync3 } from "fs";
|
|
16675
|
+
import { resolve as resolve12, join as join17 } from "path";
|
|
16427
16676
|
function getCliVersion() {
|
|
16428
|
-
const here =
|
|
16677
|
+
const here = dirname9(fileURLToPath5(import.meta.url));
|
|
16429
16678
|
const candidates = [
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16679
|
+
join17(here, "..", "package.json"),
|
|
16680
|
+
join17(here, "..", "..", "package.json"),
|
|
16681
|
+
join17(process.cwd(), "package.json")
|
|
16433
16682
|
];
|
|
16434
16683
|
for (const p of candidates) {
|
|
16435
16684
|
try {
|
|
16436
|
-
const pkg = JSON.parse(
|
|
16685
|
+
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
16437
16686
|
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
16438
16687
|
} catch {
|
|
16439
16688
|
}
|
|
@@ -17096,8 +17345,8 @@ program.command("task").description("Run an autonomous task that completes witho
|
|
|
17096
17345
|
let outputSchema;
|
|
17097
17346
|
try {
|
|
17098
17347
|
const schemaStr = options.schema;
|
|
17099
|
-
if (
|
|
17100
|
-
outputSchema = JSON.parse(
|
|
17348
|
+
if (existsSync22(schemaStr)) {
|
|
17349
|
+
outputSchema = JSON.parse(readFileSync11(schemaStr, "utf-8"));
|
|
17101
17350
|
} else {
|
|
17102
17351
|
outputSchema = JSON.parse(schemaStr);
|
|
17103
17352
|
}
|
|
@@ -17164,19 +17413,19 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
17164
17413
|
let configLocation;
|
|
17165
17414
|
if (options.global) {
|
|
17166
17415
|
const appDataDir = ensureAppDataDirectory();
|
|
17167
|
-
configPath =
|
|
17416
|
+
configPath = join17(appDataDir, "sparkecoder.config.json");
|
|
17168
17417
|
configLocation = "global";
|
|
17169
17418
|
} else {
|
|
17170
17419
|
configPath = resolve12(process.cwd(), "sparkecoder.config.json");
|
|
17171
17420
|
configLocation = "local";
|
|
17172
17421
|
}
|
|
17173
|
-
if (
|
|
17422
|
+
if (existsSync22(configPath) && !options.force) {
|
|
17174
17423
|
console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
|
|
17175
17424
|
console.log(chalk.dim(` ${configPath}`));
|
|
17176
17425
|
return;
|
|
17177
17426
|
}
|
|
17178
17427
|
const config = createDefaultConfig();
|
|
17179
|
-
|
|
17428
|
+
writeFileSync7(configPath, JSON.stringify(config, null, 2));
|
|
17180
17429
|
console.log(chalk.green(`\u2713 Created ${configLocation} config`));
|
|
17181
17430
|
console.log(chalk.dim(` ${configPath}`));
|
|
17182
17431
|
console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
|
|
@@ -17195,11 +17444,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17195
17444
|
console.error(chalk.red("Both bot token and signing secret are required."));
|
|
17196
17445
|
process.exit(1);
|
|
17197
17446
|
}
|
|
17198
|
-
const configPath = options.global ?
|
|
17447
|
+
const configPath = options.global ? join17(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve12(process.cwd(), "sparkecoder.config.json");
|
|
17199
17448
|
let existing = {};
|
|
17200
|
-
if (
|
|
17449
|
+
if (existsSync22(configPath)) {
|
|
17201
17450
|
try {
|
|
17202
|
-
existing = JSON.parse(
|
|
17451
|
+
existing = JSON.parse(readFileSync11(configPath, "utf-8"));
|
|
17203
17452
|
} catch {
|
|
17204
17453
|
}
|
|
17205
17454
|
} else {
|
|
@@ -17211,7 +17460,7 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
17211
17460
|
signingSecret,
|
|
17212
17461
|
defaultOrchestratorName: existing.slack?.defaultOrchestratorName ?? "orchestrator"
|
|
17213
17462
|
};
|
|
17214
|
-
|
|
17463
|
+
writeFileSync7(configPath, JSON.stringify(existing, null, 2));
|
|
17215
17464
|
console.log(chalk.green(`
|
|
17216
17465
|
\u2713 Slack configured`));
|
|
17217
17466
|
console.log(chalk.dim(` ${configPath}`));
|
|
@@ -17463,9 +17712,9 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17463
17712
|
}
|
|
17464
17713
|
const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
|
|
17465
17714
|
console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
|
|
17466
|
-
const cfDir =
|
|
17467
|
-
const certPath =
|
|
17468
|
-
if (!
|
|
17715
|
+
const cfDir = join17(homedir2(), ".cloudflared");
|
|
17716
|
+
const certPath = join17(cfDir, "cert.pem");
|
|
17717
|
+
if (!existsSync22(certPath)) {
|
|
17469
17718
|
console.log(chalk.yellow("No Cloudflare login cert found."));
|
|
17470
17719
|
if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
|
|
17471
17720
|
run("cloudflared", ["tunnel", "login"], { inheritIO: true, check: true });
|
|
@@ -17509,8 +17758,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17509
17758
|
return;
|
|
17510
17759
|
}
|
|
17511
17760
|
}
|
|
17512
|
-
const credsFile = tunnel.credentials_file ||
|
|
17513
|
-
if (!
|
|
17761
|
+
const credsFile = tunnel.credentials_file || join17(cfDir, `${tunnel.id}.json`);
|
|
17762
|
+
if (!existsSync22(credsFile)) {
|
|
17514
17763
|
console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
|
|
17515
17764
|
}
|
|
17516
17765
|
let hostname = options.hostname;
|
|
@@ -17533,7 +17782,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
17533
17782
|
console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
|
|
17534
17783
|
}
|
|
17535
17784
|
}
|
|
17536
|
-
const configPath =
|
|
17785
|
+
const configPath = join17(cfDir, "config.yml");
|
|
17537
17786
|
const configBody = `tunnel: ${tunnel.id}
|
|
17538
17787
|
credentials-file: ${credsFile}
|
|
17539
17788
|
ingress:
|
|
@@ -17544,14 +17793,14 @@ ingress:
|
|
|
17544
17793
|
- service: http_status:404
|
|
17545
17794
|
`;
|
|
17546
17795
|
let wroteConfig = false;
|
|
17547
|
-
if (
|
|
17548
|
-
const existing =
|
|
17796
|
+
if (existsSync22(configPath)) {
|
|
17797
|
+
const existing = readFileSync11(configPath, "utf8");
|
|
17549
17798
|
if (existing.trim() === configBody.trim()) {
|
|
17550
17799
|
console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
|
|
17551
17800
|
wroteConfig = true;
|
|
17552
17801
|
} else if (await confirm(`A different ${configPath} exists. Overwrite (a backup will be saved)?`, false)) {
|
|
17553
17802
|
copyFileSync(configPath, `${configPath}.bak.${Date.now()}`);
|
|
17554
|
-
|
|
17803
|
+
writeFileSync7(configPath, configBody);
|
|
17555
17804
|
console.log(chalk.green("\u2713"), `wrote ${configPath} (previous saved as .bak.*)`);
|
|
17556
17805
|
wroteConfig = true;
|
|
17557
17806
|
} else {
|
|
@@ -17559,7 +17808,7 @@ ingress:
|
|
|
17559
17808
|
console.log(chalk.cyan(configBody));
|
|
17560
17809
|
}
|
|
17561
17810
|
} else {
|
|
17562
|
-
|
|
17811
|
+
writeFileSync7(configPath, configBody);
|
|
17563
17812
|
console.log(chalk.green("\u2713"), `wrote ${configPath}`);
|
|
17564
17813
|
wroteConfig = true;
|
|
17565
17814
|
}
|
|
@@ -18028,17 +18277,17 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18028
18277
|
});
|
|
18029
18278
|
{
|
|
18030
18279
|
let stateFilePath = function() {
|
|
18031
|
-
return
|
|
18280
|
+
return join17(ensureAppDataDirectory(), "recordings.json");
|
|
18032
18281
|
}, readState = function() {
|
|
18033
18282
|
const p = stateFilePath();
|
|
18034
|
-
if (!
|
|
18283
|
+
if (!existsSync22(p)) return [];
|
|
18035
18284
|
try {
|
|
18036
|
-
return JSON.parse(
|
|
18285
|
+
return JSON.parse(readFileSync11(p, "utf-8"));
|
|
18037
18286
|
} catch {
|
|
18038
18287
|
return [];
|
|
18039
18288
|
}
|
|
18040
18289
|
}, writeState = function(rows) {
|
|
18041
|
-
|
|
18290
|
+
writeFileSync7(stateFilePath(), JSON.stringify(rows, null, 2), { mode: 384 });
|
|
18042
18291
|
}, isAlive = function(pid) {
|
|
18043
18292
|
try {
|
|
18044
18293
|
process.kill(pid, 0);
|
|
@@ -18052,18 +18301,17 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18052
18301
|
stateFilePath2 = stateFilePath, readState2 = readState, writeState2 = writeState, isAlive2 = isAlive, pruneDead2 = pruneDead;
|
|
18053
18302
|
const record = program.command("record").description("Start/stop screen recordings");
|
|
18054
18303
|
record.command("start").description("Start a screen recording (returns id, path, pid as JSON)").option("--name <slug>", "Optional human-readable label for the recording").option("--dir <path>", "Output directory (default: ~/recordings)").action(async (opts) => {
|
|
18055
|
-
const { spawn: spawn3 } = await import("child_process");
|
|
18056
18304
|
const { homedir: homedir2, platform: osPlatform } = await import("os");
|
|
18057
|
-
const outDir = opts.dir ? resolve12(opts.dir.replace(/^~/, homedir2())) :
|
|
18058
|
-
|
|
18305
|
+
const outDir = opts.dir ? resolve12(opts.dir.replace(/^~/, homedir2())) : join17(homedir2(), "recordings");
|
|
18306
|
+
mkdirSync11(outDir, { recursive: true });
|
|
18059
18307
|
const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
18060
18308
|
const ext = osPlatform() === "darwin" ? "mov" : "mp4";
|
|
18061
18309
|
const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
|
|
18062
|
-
const path =
|
|
18310
|
+
const path = join17(outDir, filename);
|
|
18063
18311
|
let cmd;
|
|
18064
18312
|
let args;
|
|
18065
18313
|
if (osPlatform() === "darwin") {
|
|
18066
|
-
cmd = "screencapture";
|
|
18314
|
+
cmd = existsSync22("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
|
|
18067
18315
|
args = ["-v", "-C", "-k", path];
|
|
18068
18316
|
} else if (osPlatform() === "linux") {
|
|
18069
18317
|
const display = process.env.DISPLAY || ":0.0";
|
|
@@ -18091,12 +18339,43 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18091
18339
|
console.error(JSON.stringify({ error: `Unsupported platform: ${osPlatform()}` }));
|
|
18092
18340
|
process.exit(1);
|
|
18093
18341
|
}
|
|
18094
|
-
const
|
|
18095
|
-
child
|
|
18096
|
-
|
|
18097
|
-
|
|
18342
|
+
const { spawn: spawnRec } = await import("child_process");
|
|
18343
|
+
const child = spawnRec(cmd, args, { detached: true, stdio: ["ignore", "ignore", "pipe"] });
|
|
18344
|
+
let stderrTail = "";
|
|
18345
|
+
child.stderr?.on("data", (b) => {
|
|
18346
|
+
stderrTail = (stderrTail + b.toString()).slice(-2e3);
|
|
18347
|
+
});
|
|
18348
|
+
let spawnError = null;
|
|
18349
|
+
child.on("error", (err) => {
|
|
18350
|
+
spawnError = err?.message ?? String(err);
|
|
18351
|
+
});
|
|
18352
|
+
let exited = false;
|
|
18353
|
+
let exitCode = null;
|
|
18354
|
+
child.on("exit", (code) => {
|
|
18355
|
+
exited = true;
|
|
18356
|
+
exitCode = code;
|
|
18357
|
+
});
|
|
18358
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
18359
|
+
if (spawnError || exited || !child.pid) {
|
|
18360
|
+
const diagnosis = [
|
|
18361
|
+
spawnError && `spawn error: ${spawnError}`,
|
|
18362
|
+
exited && `recorder exited code=${exitCode} within 600ms`,
|
|
18363
|
+
stderrTail.trim() && `stderr: ${stderrTail.trim()}`,
|
|
18364
|
+
osPlatform() === "darwin" && `tried: ${cmd} ${args.join(" ")}`,
|
|
18365
|
+
osPlatform() === "darwin" && `if missing Screen Recording permission, run: sparkecoder request-permissions`,
|
|
18366
|
+
osPlatform() === "linux" && `display=${process.env.DISPLAY || ":0.0"} \u2014 ensure X server is running and ffmpeg is installed`
|
|
18367
|
+
].filter(Boolean).join(". ");
|
|
18368
|
+
console.error(JSON.stringify({
|
|
18369
|
+
error: `Failed to start ${cmd}`,
|
|
18370
|
+
detail: diagnosis || "(no diagnostic output)",
|
|
18371
|
+
cmd,
|
|
18372
|
+
args,
|
|
18373
|
+
pid: child.pid,
|
|
18374
|
+
exitCode
|
|
18375
|
+
}));
|
|
18098
18376
|
process.exit(1);
|
|
18099
18377
|
}
|
|
18378
|
+
child.unref();
|
|
18100
18379
|
const row = {
|
|
18101
18380
|
id,
|
|
18102
18381
|
name: opts.name,
|
|
@@ -18135,7 +18414,7 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18135
18414
|
}
|
|
18136
18415
|
}
|
|
18137
18416
|
writeState(rows.filter((r) => r.id !== id));
|
|
18138
|
-
const fileExists =
|
|
18417
|
+
const fileExists = existsSync22(row.path);
|
|
18139
18418
|
const sizeMb = fileExists ? Math.round(statSync3(row.path).size / (1024 * 1024) * 10) / 10 : 0;
|
|
18140
18419
|
console.log(JSON.stringify({
|
|
18141
18420
|
id,
|
|
@@ -18170,7 +18449,7 @@ program.command("request-permissions").description("Request macOS permissions fo
|
|
|
18170
18449
|
}
|
|
18171
18450
|
}
|
|
18172
18451
|
}
|
|
18173
|
-
stopped.push({ id: r.id, path: r.path, ok:
|
|
18452
|
+
stopped.push({ id: r.id, path: r.path, ok: existsSync22(r.path) });
|
|
18174
18453
|
}
|
|
18175
18454
|
writeState([]);
|
|
18176
18455
|
console.log(JSON.stringify({ stopped }));
|