@shardflux/desktop-linux-arm64 0.1.0-next.5 → 0.1.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/CHANGELOG.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0 (not yet published)
3
+ ## 0.1.1 (not yet published)
4
4
 
5
- - Shardflux for Ubuntu 24.04: desktop onboarding, conversations and launcher.
5
+ - The app reads `shard`'s answers whole: Settings → Agents shows Connected (or Connect) instead of staying on
6
+ Checking…, the top-bar panel refreshes your conversations, and a conversation you start opens instead of showing
7
+ its details as an error. A note shard prints next to its answer (such as `Initialized a git repository …`) no
8
+ longer gets in the way, and a failed command shows its message. Installed with CLI 0.18.1+.
6
9
 
7
- - Port all seven Settings panes, device and vault approvals, actionable desktop notifications, file placeholders, screenshot capture and self-update. Use the custom StatusNotifierItem panel, publisher app marks, licensed Lucide icons and system-accent selections.
10
+ ## 0.1.0 (2026-10-10)
11
+
12
+ - The Shardflux app for Ubuntu 24.04 (arm64): guided sign-in and agent setup, the top-bar panel for your
13
+ conversations, the launcher (Alt+Shift+Space), Settings, device and sign-in approvals, notifications with actions,
14
+ file downloads and self-update. Installed with `npm i -g shardflux` (CLI 0.18.0+) and opened by `shard`.
package/app/manifest.json CHANGED
@@ -1 +1 @@
1
- {"id":"dev.shardflux.Desktop","version":"0.1.0-next.5"}
1
+ {"id": "dev.shardflux.Desktop", "version": "0.1.1"}
Binary file
package/app/ui/app.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {icon,appMark,spinner} from './icons.mjs';
2
2
  import {installTray} from './tray.mjs';
3
- import {steps,samplePrompt,readProgress,canContinue,anyAgentConnected,accountConnected,demoPhase,cardState,commands,recordStart,DemoTurn,browserEvent,streamURL,safeHTTPS,observeActivity,recap,openPlace,shortcutLabel,escape as e} from './core.mjs';
3
+ import {steps,samplePrompt,readProgress,cliResult,streams,canContinue,anyAgentConnected,accountConnected,demoPhase,cardState,commands,recordStart,DemoTurn,browserEvent,streamURL,safeHTTPS,observeActivity,recap,openPlace,shortcutLabel,escape as e} from './core.mjs';
4
4
  import {agentRows} from './agents.mjs';
5
5
  import {installSurfaces} from './surfaces.mjs';
6
6
  import {installSettings} from './settings.mjs';
@@ -14,7 +14,9 @@ model.rows=[];
14
14
  model.api=api;
15
15
  window.addEventListener('sf-profile',async ev=>{api=ev.detail;model.api=api;model.p=readProgress(await host('read',{name:'onboarding.json'}),api);model.error='';await refresh();render();});
16
16
  export const button=(title,action,cls='',disabled=false)=>`<button class="${cls}" data-action="${action}" ${disabled?'disabled':''}>${title}</button>`;
17
- export async function cli(argv,options={}) {let raw;try{raw=await host('cli',{argv,...options});}catch(err){raw=err.message;let detail;for(const line of raw.trim().split('\n')){try{detail=JSON.parse(line).error;}catch{}}if(detail)throw Object.assign(new Error(detail.message),{detail});throw err;}let result;for(const line of raw.trim().split('\n')){try{result=JSON.parse(line);}catch{}}if(result===undefined)throw new Error(raw.trim()||'shard returned no response.');if(result.error)throw Object.assign(new Error(result.error.message),{detail:result.error});return result;}
17
+ // stdout and stderr arrive apart (see cliResult): the result is stdout's JSON document, or its NDJSON events for the
18
+ // browser sign-in. A run that could not start at all rejects in the native host.
19
+ export async function cli(argv,options={}) {return cliResult(await host('cli',{argv,...options}),{stream:streams(argv)});}
18
20
  export async function openConversation(key,target='auto') {const d=await cli(commands.open(key,target));if(d.url)await host('open',{url:d.url});else if(d.target==='terminal')await host('terminal',{argv:d.command||['shard','agents','open',key]});else if(!d.opened&&!d.ok)throw new Error('shard did not return a conversation destination. Update Shardflux and try Open again.');return d;}
19
21
  export const save=()=>host('write',{name:'onboarding.json',value:model.p});
20
22
  export const savePrefs=()=>host('write',{name:'desktop-linux.json',value:model.prefs});
@@ -52,7 +54,7 @@ export function render(){
52
54
  }
53
55
  async function signedIn(email){model.email=email;model.signIn='done';model.p.completed=[...new Set([...model.p.completed,0])];render();await cli(commands.setup);await refresh();model.signIn='idle';await go(1);}
54
56
  let browserRun;
55
- async function signIn(browser){model.error='';model.signIn=browser?'browser':'working';render();if(browser){browserRun=host('cli',{argv:commands.browser('Shardflux for Linux'),stream:true,timeout:540});await browserRun;return;}try{const doc=await cli(commands.signIn);await signedIn(doc.user?.email);}catch(err){if(/mfa|two.factor|verification/i.test(err.message)){model.signIn='mfa';render();}else throw err;}}
57
+ async function signIn(browser){model.error='';model.signIn=browser?'browser':'working';render();if(browser){const argv=commands.browser('Shardflux for Linux');browserRun=host('cli',{argv,stream:true,timeout:540});const run=await browserRun;if(model.signIn==='browser'&&run.status!==0)cliResult(run,{stream:true});return;}try{const doc=await cli(commands.signIn);await signedIn(doc.user?.email);}catch(err){if(/mfa|two.factor|verification/i.test(err.message)){model.signIn='mfa';render();}else throw err;}}
56
58
  window.addEventListener('sf-cli-line',async ev=>{if(ev.detail.id!==browserRun?.id)return;const value=browserEvent(ev.detail.line);if(value?.event==='open'){model.code=value.user_code;model.url=value.verification_uri_complete;render();}else if(value?.event==='signed_in'){try{await signedIn(value.email);}catch(err){model.error=err.message;render();}}else if(value?.event==='error'){model.error=value.message;model.signIn='idle';render();}});
57
59
  async function go(step){model.p.step=step;await save();if(step===2&&!model.p.sample_path){const d=await cli(commands.sample);if(!d.sample_path?.startsWith('/'))throw new Error('The sample project could not be prepared.');model.p.sample_path=d.sample_path;await save();}render();}
58
60
  export async function connect(id){const c=model.inspection?.connections.find(c=>c.id===id);if(c?.action?.url){await host('open',{url:safeHTTPS(c.action.url)});return;}if(c?.action?.retry){await refresh(true,id==='codex');return;}if(c?.action?.command){const argv=c.action.command;if(argv[0]==='shard'){await cli([...argv.slice(1).filter(a=>a!=='--json'),'--json']);}else await host('terminal',{argv});}else await refresh(true,id==='codex');await refresh();}
package/app/ui/core.mjs CHANGED
@@ -55,6 +55,41 @@ export function reference(doc,joined=false) {
55
55
  if(doc.workspace?.workspace_key)return doc.workspace.workspace_key;
56
56
  throw new Error('shard did not return a conversation destination. Update Shardflux and try Open again.');
57
57
  }
58
+ // How the page reads a shard run, as the Mac's ConversationProcess does. The native host hands over {status,
59
+ // timed_out, stdout, stderr} with the two streams apart. `--json` prints one JSON document on stdout, pretty-printed
60
+ // over many lines; the only command that streams NDJSON events (one per line) is the browser sign-in, as in
61
+ // MenuBarCore (BrowserSignIn.run is ConversationProcess's one onLine caller). A failure's error envelope
62
+ // ({"error":{code,message,...}}) is read from stdout, else from the end of stderr, where CLI 0.18.0 writes it; any
63
+ // other stderr text is a diagnostic ("Initialized a git repository in …") and never decides the result.
64
+ export const streams = argv => argv[0]==='auth' && argv[1]==='login' && argv.includes('--browser');
65
+ const parseJSON = text => {try{return JSON.parse(text);}catch{return undefined;}};
66
+ /** The whole text as one JSON document, else the last one in it: pretty-printed JSON opens at column 0 and indents
67
+ * everything inside, so the last line starting with { or [ starts the last document. */
68
+ export function lastDocument(text='') {
69
+ const whole=parseJSON(text.trim());if(whole!==undefined)return whole;
70
+ const lines=text.split('\n');
71
+ for(let i=lines.length-1;i>=0;i--)if(/^[{[]/.test(lines[i])){const doc=parseJSON(lines.slice(i).join('\n').trim());if(doc!==undefined)return doc;}
72
+ return undefined;
73
+ }
74
+ const envelope = doc => doc && typeof doc==='object' && doc.error && typeof doc.error==='object' && typeof doc.error.message==='string' ? doc.error : null;
75
+ /** The command's answer, or an Error carrying `detail` (the error envelope) and `doc` (what stdout said). A run that
76
+ * exited 0 with a readable document is a success, whatever stderr holds. */
77
+ export function cliResult(run, {stream=false}={}) {
78
+ const {status=-1,stdout='',stderr='',timed_out:timedOut=false}=run&&typeof run==='object'?run:{};
79
+ let doc,detail=null;
80
+ if(stream){const events=stdout.split('\n').map(line=>parseJSON(line.trim())).filter(v=>v&&typeof v==='object');doc=events.at(-1);const failed=events.findLast(v=>v.event==='error');if(failed)detail={code:failed.code,message:failed.message};}
81
+ else doc=lastDocument(stdout);
82
+ detail??=envelope(doc);
83
+ if(status===0&&doc!==undefined&&!detail)return doc;
84
+ if(!detail&&status!==0)detail=envelope(lastDocument(stderr));
85
+ const extra=doc!==undefined&&!envelope(doc)?{doc}:{};
86
+ if(detail)throw Object.assign(new Error(detail.message),{detail},extra);
87
+ if(timedOut)throw Object.assign(new Error('shard took too long. Check your conversations before starting another one.'),extra);
88
+ if(status===0)throw new Error('shard returned a response this app can’t read. Update Shardflux and try again.');
89
+ // Without an envelope only shard's own `error:` line is worth showing; a diagnostic is not the reason it failed.
90
+ const said=stderr.split('\n').map(l=>l.trim()).findLast(l=>l.startsWith('error: '))?.slice(7).replace(/\s*\[code=[^\]]*\]$/,'');
91
+ throw Object.assign(new Error(said||'shard could not finish. Check your sign-in and try again.'),extra);
92
+ }
58
93
  export function browserEvent(line) {
59
94
  try {const e=JSON.parse(line);if(e.event==='open' && e.user_code)return {...e,verification_uri_complete:safeHTTPS(e.verification_uri_complete)};if(['signed_in','error'].includes(e.event))return e;}catch{}return null;
60
95
  }
@@ -175,6 +175,8 @@ export function installSurfaces({model,host,cli,save,savePrefs,render,button,reg
175
175
  const failure=t3Failure(err.detail);
176
176
  if(failure){L.remedy=t3Remedy(failure,L.targets?.t3?.mode)?failure:null;if(failure.threadID)L.created=failureReference(failure)??`t3:${failure.threadID}`;else if(failure.conversation)L.created=failure.conversation;}
177
177
  else if(err.detail?.details?.workspace_key)L.created=err.detail.details.workspace_key;
178
+ // shard printed the started conversation and then failed: offer that one, never a second Start.
179
+ else if(err.doc){try{L.created=reference(err.doc,!!L.on);}catch{}}
178
180
  L.error=err.message;
179
181
  }finally{L.stage=null;if(promptFile)await host('remove-prompt',{path:promptFile});render();}
180
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardflux/desktop-linux-arm64",
3
- "version": "0.1.0-next.5",
3
+ "version": "0.1.1",
4
4
  "description": "Shardflux desktop app for Ubuntu Linux",
5
5
  "license": "Apache-2.0",
6
6
  "os": [
@@ -20,5 +20,8 @@
20
20
  },
21
21
  "publishConfig": {
22
22
  "access": "public"
23
+ },
24
+ "scripts": {
25
+ "prepack": "node scripts/check-package.mjs"
23
26
  }
24
27
  }