@useshifu/coding-harness 0.3.35 → 0.3.36

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/README.md CHANGED
@@ -280,30 +280,30 @@ The companion endpoint is used only when the authenticated connection advertises
280
280
 
281
281
  Use only compatible preparation writers on a shared configuration directory. An older runner can read and transport the unchanged base queue, but cannot enforce new preparation holds. Stop its preparation schedule or use a compatible rollback build. Never delete checkpoints or replay accepted history to repair missing continuity.
282
282
 
283
- ## Visual onboarding
283
+ ## Set up Shifu
284
284
 
285
- Version `0.3.35` adds a bundled, local HTML wizard and the thin `shifu-onboarding` skill:
285
+ Use the `shifu-onboarding` skill in your coding assistant, or open Shifu setup yourself:
286
286
 
287
287
  ```sh
288
- npx --yes @useshifu/coding-harness@0.3.35 onboard --harness codex
288
+ npx --yes @useshifu/coding-harness@0.3.36 onboard --harness codex
289
289
  ```
290
290
 
291
- Choose `codex`, `claude_code`, `opencode`, or `cursor`. Run this on the local macOS/Linux device whose coding history you want to connect. A remote/headless assistant cannot open this device's loopback wizard: run the command in your own local terminal instead. The launcher prints a local URL if opening the browser fails. That URL carries a per-launch capability in its fragment; do not share it. It is not your Shifu connection key.
291
+ Choose `codex`, `claude_code`, `opencode`, or `cursor`. Run this on the local macOS/Linux device whose coding history you want to connect. A remote/headless assistant cannot open this device's setup page: run the command in your own local terminal instead. The launcher prints a local URL if opening the browser fails. That URL carries a per-launch capability in its fragment; do not share it. It is not your Shifu connection key.
292
292
 
293
- The prebuilt wizard collects initial history (15 days, 30 days, custom 1–36500 days, or start from now), independent growth-feedback and decision-alignment choices, and shows examples. Saved settings and the original history cutoff are reused. Keys go directly from the wizard to the local runner, never through chat. Existing connections are verified and reused. Review explicitly authorizes preparation and upload of the selected coding history; source text uses your configured coding-model provider. The one-time backfill does not change an existing manual approval policy for future work. Folder-file uploads are separate from this coding-history backfill.
293
+ The setup page collects initial history (15 days, 30 days, custom 1–36500 days, or start from now), independent growth-feedback and decision-alignment choices, and shows examples. Saved settings and the original history cutoff are reused. Keys go directly from the setup page to the local runner, never through chat. Existing connections are verified and reused. Review explicitly authorizes preparation and upload of the selected coding history; source text uses your configured coding-model provider. The one-time backfill does not change an existing manual approval policy for future work. Folder-file uploads are separate from this coding-history backfill.
294
294
 
295
295
  Accepted ranges use existing checkpoints and exact frozen payloads. Initial discovery freezes session boundaries for this run; later activity follows ordinary sync. Closing the browser does not stop the detached worker. The scheduled runner resumes unfinished ranges on its next tick; **Resume unfinished backfill** retries immediately. Older unsynced turns outside the saved window remain blocked rather than silently expanding scope. A zero-day selection skips historical work. Empty history is reported honestly, not counted as captured context.
296
296
 
297
- The wizard checks hosted saved-work availability before offering **Copy first question**. Reload the assistant as instructed and paste that short prompt. The non-secret run reference only identifies local state; it never grants account access. The skill checks status without reinstalling, reasking choices or replaying accepted history:
297
+ Setup checks saved-work availability before offering **Copy a question**. Reload the assistant as instructed and paste that short prompt. The non-secret run reference only identifies local state; it never grants account access. The skill checks status without reinstalling, reasking choices or replaying accepted history:
298
298
 
299
299
  ```sh
300
- npx --yes @useshifu/coding-harness@0.3.35 onboard --harness codex --status --run <reference>
300
+ npx --yes @useshifu/coding-harness@0.3.36 onboard --harness codex --status --run <reference>
301
301
  ```
302
302
 
303
303
  Only after explicit user confirmation that the sourced answer helped may the assistant add `--helpful`. The backend requires saved evidence and a real context retrieval before recording activation. Assistant usefulness telemetry is not user confirmation.
304
304
 
305
- To change choices after a completed, skipped or empty run, use **Change choices**, or explicitly launch with `--new`. This preserves accepted sync checkpoints. An active selection cannot be discarded with `--new`. If a key was revoked, replace it through the existing interactive `connect` flow first (which resets local checkpoints), then use `onboard --new` to bind the changed connection. A key replacement or package upgrade alone does not authorize another backfill.
305
+ To change choices after a completed, skipped or empty run, use **Change preferences**, or explicitly launch with `--new`. This preserves accepted sync checkpoints. An active selection cannot be discarded with `--new`. If a key was revoked, replace it through the existing interactive `connect` flow first (which resets local checkpoints), then use `onboard --new` to bind the changed connection. A key replacement or package upgrade alone does not authorize another backfill.
306
306
 
307
- Release order: deploy the onboarding progress API and migration, publish package `0.3.35`, then deploy dashboard prompts pinned to that release. The wizard and skill are package assets; do not deploy a prompt naming an unpublished release. No package is published merely by changing this checkout.
307
+ The setup page and skill ship in package `0.3.36`, with the same light theme and bundled fonts as the dashboard. The dashboard intentionally has no onboarding entry points. The existing progress API remains compatible. No package is published merely by changing this checkout.
308
308
 
309
- For an unfinished selection that you explicitly want to leave, **Change choices** asks for confirmation and preserves accepted records/checkpoints. The equivalent terminal operation is `onboard --harness codex --new --abandon`. Ordinary `--new` refuses to discard an active run. If you explicitly resolve an older blocked prefix through the existing backfill workflow, Resume rechecks only the frozen blocked targets and can finish without importing later appended turns.
309
+ For an unfinished selection that you explicitly want to leave, **Change preferences** asks for confirmation and preserves accepted records/checkpoints. The equivalent terminal operation is `onboard --harness codex --new --abandon`. Ordinary `--new` refuses to discard an active run. If you explicitly resolve an older blocked prefix through the existing backfill workflow, Resume rechecks only the frozen blocked targets and can finish without importing later appended turns.
package/bin/onboarding.js CHANGED
@@ -28,7 +28,7 @@ function fingerprint(config) { return config?.token ? createHash('sha256').updat
28
28
  function load(runtime, runId) {
29
29
  const state = read(file(runtime));
30
30
  if (runId && (!ID.test(runId) || state?.runId !== runId)) throw new Error('This setup run is not available on this device.');
31
- if (state && state.connectionFingerprint && state.connectionFingerprint !== fingerprint(runtime.readConfig())) throw new Error('The connection changed. Start a new onboarding run.');
31
+ if (state && state.connectionFingerprint && state.connectionFingerprint !== fingerprint(runtime.readConfig())) throw new Error('The connection changed. Ask your assistant to start Shifu setup again.');
32
32
  return state;
33
33
  }
34
34
  function save(runtime, state) {
@@ -58,7 +58,7 @@ function projection(state) {
58
58
  }
59
59
  function safeStatus(runtime, state) {
60
60
  return state ? { ...projection(state), contextAvailable: Boolean(state.contextAvailable), retrievalAvailable: Boolean(state.retrievalAvailable), configured: Boolean(runtime.readConfig()), authorized: state.authorized === true,
61
- reloadRequired: state.connectionStatus === 'reload_required', resumePrompt: `Continue Shifu onboarding for run ${state.runId}. Use the shifu-onboarding skill to check saved status without repeating settings or restarting backfill. Once Shifu tools are loaded, retrieve my work context and answer: What decisions and constraints should I remember from my recent work?` } : { configured: Boolean(runtime.readConfig()), authorized: false };
61
+ reloadRequired: state.connectionStatus === 'reload_required', resumePrompt: `Use my saved Shifu setup (reference ${state.runId}). Check progress with the shifu-onboarding skill, then answer using my work context: What decisions and constraints should I remember from my recent work?` } : { configured: Boolean(runtime.readConfig()), authorized: false };
62
62
  }
63
63
  async function publish(runtime, state) {
64
64
  const config = runtime.readConfig();
@@ -153,17 +153,18 @@ async function startServer(runtime, state, { port=0 }={}) {
153
153
  let origin, busy=false;
154
154
  const server = http.createServer(async (request,response) => {
155
155
  response.setHeader('Cache-Control','no-store'); response.setHeader('Referrer-Policy','no-referrer'); response.setHeader('X-Content-Type-Options','nosniff');
156
- response.setHeader('Content-Security-Policy',"default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'");
156
+ response.setHeader('Content-Security-Policy',"default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self'; font-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'");
157
157
  const send = (status,value) => { response.writeHead(status,{'Content-Type':'application/json'}); response.end(JSON.stringify(value)); };
158
- if (request.headers.host !== new URL(origin).host || (request.headers.origin && request.headers.origin !== origin)) return send(403,{error:'Untrusted wizard origin.'});
158
+ if (request.headers.host !== new URL(origin).host || (request.headers.origin && request.headers.origin !== origin)) return send(403,{error:'This page could not be verified. Reopen Shifu setup from your assistant.'});
159
159
  const url = new URL(request.url,origin);
160
- if (request.method === 'GET' && ['/', '/wizard.js','/wizard.css'].includes(url.pathname)) {
161
- const asset = url.pathname === '/' ? 'index.html' : url.pathname.slice(1);
162
- response.writeHead(200,{'Content-Type':asset.endsWith('.js')?'text/javascript':asset.endsWith('.css')?'text/css':'text/html'});
160
+ const assets = { '/': ['index.html', 'text/html'], '/wizard.js': ['wizard.js', 'text/javascript'], '/wizard.css': ['wizard.css', 'text/css'], '/brand-mark.png': ['brand-mark.png', 'image/png'], '/fonts/plus-jakarta-sans.ttf': ['fonts/plus-jakarta-sans.ttf', 'font/ttf'], '/fonts/ibm-plex-mono.ttf': ['fonts/ibm-plex-mono.ttf', 'font/ttf'] };
161
+ if (request.method === 'GET' && Object.hasOwn(assets, url.pathname)) {
162
+ const [asset, contentType] = assets[url.pathname];
163
+ response.writeHead(200,{'Content-Type':contentType});
163
164
  return response.end(fs.readFileSync(path.join(runtime.assets,asset)));
164
165
  }
165
166
  const supplied = Buffer.from(String(request.headers['x-shifu-wizard'] || ''));
166
- if (supplied.length !== token.length || !timingSafeEqual(supplied,Buffer.from(token))) return send(401,{error:'Reopen the wizard from your assistant.'});
167
+ if (supplied.length !== token.length || !timingSafeEqual(supplied,Buffer.from(token))) return send(401,{error:'Reopen Shifu setup from your assistant.'});
167
168
  try {
168
169
  if (request.method === 'GET' && url.pathname === '/api/status') {
169
170
  const current = load(runtime,state.runId); await publish(runtime,current); return send(200,safeStatus(runtime,current));
@@ -171,7 +172,7 @@ async function startServer(runtime, state, { port=0 }={}) {
171
172
  if (request.method === 'POST' && url.pathname === '/api/apply') {
172
173
  if (busy) return send(409,{error:'Setup is already running.'});
173
174
  const body = await bodyJSON(request); busy=true;
174
- try { const result=await runtime.setupLock(async()=>{state=load(runtime,state.runId);await apply(runtime,state,body);});if(result?.skipped)return send(409,{error:"Setup is already running in another wizard."}); } finally { busy=false; }
175
+ try { const result=await runtime.setupLock(async()=>{state=load(runtime,state.runId);await apply(runtime,state,body);});if(result?.skipped)return send(409,{error:"Shifu setup is already open in another window."}); } finally { busy=false; }
175
176
  return send(200,safeStatus(runtime,state));
176
177
  }
177
178
  if (request.method === 'POST' && url.pathname === '/api/new') {
@@ -189,7 +190,7 @@ async function startServer(runtime, state, { port=0 }={}) {
189
190
  if (!state.authorized) return send(409,{error:'Approve setup first.'});
190
191
  spawnWorker(runtime,state); return send(202,safeStatus(runtime,state));
191
192
  }
192
- return send(404,{error:'Unknown wizard action.'});
193
+ return send(404,{error:'This setup action is unavailable.'});
193
194
  } catch (error) {
194
195
  // Never relay provider or connector errors: those can contain source text or credentials.
195
196
  return send(400,{error: ['Choose a valid history window and both feedback options.','The connection key format is invalid.','Unsupported setup option.','This run has a fixed history selection. Finish it before changing the history window.'].includes(error.message) ? error.message : 'Setup could not finish. Your accepted work is preserved; check the connection and retry.'});
@@ -210,13 +211,13 @@ async function main(runtime, options={}) {
210
211
  if (!state?.authorized) throw new Error('Verify setup before confirming usefulness.');
211
212
  const config=runtime.readConfig();
212
213
  const response=await fetch(`${config.apiUrl}/v1/connectors/coding-sessions/onboarding/feedback`,{method:'POST',headers:{Authorization:`Bearer ${config.token}`,'Content-Type':'application/json'},body:JSON.stringify({harness:runtime.harness,runId:state.runId,action:'helpful'}),signal:AbortSignal.timeout(10000)});
213
- if (!response.ok) throw new Error('Shifu could not confirm onboarding feedback.');
214
+ if (!response.ok) throw new Error('Shifu could not save your feedback.');
214
215
  return console.log(JSON.stringify({helpful:true,runId:state.runId}));
215
216
  }
216
- if (options.worker) { if (!state?.authorized) throw new Error('Approve onboarding before backfill.'); return resumeBackfill(runtime); }
217
+ if (options.worker) { if (!state?.authorized) throw new Error('Confirm your preferences before syncing history.'); return resumeBackfill(runtime); }
217
218
  if (!state) {
218
219
  const result=await runtime.setupLock(async()=>{state=load(runtime)||initial(runtime);});
219
- if(result?.skipped)throw new Error("Another wizard is starting. Retry this command.");
220
+ if(result?.skipped)throw new Error("Shifu setup is already opening. Try again in a moment.");
220
221
  }
221
222
  if (options.serve) {
222
223
  const instance=await startServer(runtime,state);
@@ -237,8 +238,8 @@ async function main(runtime, options={}) {
237
238
  child.on('error',()=>{});child.unref();
238
239
  for(let attempt=0;attempt<30;attempt+=1){ await new Promise(resolve=>setTimeout(resolve,100)); const next=read(file(runtime,'wizard'));if(next?.runId===state.runId && next.pid===child.pid){url=next.url;break;} }
239
240
  }
240
- if(!url) throw new Error('Could not start the local wizard. Run this command on your local macOS or Linux machine.');
241
+ if(!url) throw new Error('Could not open Shifu setup. Run this command on your Mac or Linux computer.');
241
242
  if(process.env.SHIFU_ONBOARD_NO_BROWSER!=='1') execFile(process.platform==='darwin'?'open':'xdg-open',[url],()=>{});
242
- console.log(JSON.stringify({runId:state.runId,wizardUrl:url,message:'Open this local wizard. Enter keys only in the wizard, never in chat. Copy the resume prompt after setup; reload your assistant if required.'}));
243
+ console.log(JSON.stringify({runId:state.runId,wizardUrl:url,message:'Open Shifu setup and choose your preferences. Enter your connection key there, never in chat. When ready, return to your assistant with the question provided.'}));
243
244
  }
244
245
  module.exports={main,startServer,initial,load,projection,safeStatus,validateChoices,apply,resumeBackfill};
@@ -47,7 +47,7 @@ const MAX_TURNS_PER_SEGMENT = 12;
47
47
  const MAX_TITLE_LENGTH = 160;
48
48
  const MAX_WORK_DETAIL_LENGTH = 600;
49
49
  const CONFIG_VERSION = 2;
50
- const RUNNER_VERSION = "0.3.35";
50
+ const RUNNER_VERSION = "0.3.36";
51
51
  const REDACTION_VERSION = 5;
52
52
  const LEGACY_REDACTION_VERSION = 4;
53
53
  const MAX_PREPARATIONS_PER_RUN = 4;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useshifu/coding-harness",
3
- "version": "0.3.35",
3
+ "version": "0.3.36",
4
4
  "description": "Scheduled, redacted coding-harness activity sync for Shifu",
5
5
  "bin": {
6
6
  "coding-harness": "bin/shifu-harness.js",
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  name: shifu-onboarding
3
- description: Set up or resume Shifu through its bundled local wizard; check progress and continue to a first useful answer.
3
+ description: Set up or resume Shifu through its setup page; check progress and continue to a first useful answer.
4
4
  ---
5
5
 
6
- Identify the current supported coding tool; ask if unclear. Launch `npx --yes @useshifu/coding-harness onboard --harness codex`. Open its local wizard URL; never ask for a connection key in chat or read private connection files. The runner owns settings, connection checks and approved backfill. Do not generate HTML, inspect unrelated projects, force sync, or repeat selected choices.
6
+ Identify the current supported coding tool; ask if unclear. Launch `npx --yes @useshifu/coding-harness onboard --harness codex`. Open the returned setup URL; never ask for a connection key in chat or read private connection files. The runner owns settings, connection checks and approved backfill. Do not generate HTML, inspect unrelated projects, force sync, or repeat selected choices.
7
+
8
+ Call the experience “Shifu setup”. Give only the next action in plain language. Keep wizard, server, capability and checkpoint details out of user-facing replies. Before starting history processing, explain the selected history and model-account usage. Keep connection keys out of chat.
7
9
 
8
10
  On a resume prompt, run `npx --yes @useshifu/coding-harness onboard --harness codex --status --run <reference>`. A reference is not a credential. Reuse saved state. If a reload is required and Shifu tools are unavailable in this chat, explain the tool-specific reload instructions and have the user paste the same prompt in a new chat. OpenCode 2 may require `opencode service stop` before reopening; never stop an active service automatically.
9
11
 
10
- Once tools are available and context is ready, retrieve context for the user's question and answer with sources. Ask whether it helped. Only after an explicit positive answer, run `npx --yes @useshifu/coding-harness onboard --harness codex --run <reference> --helpful`. Never treat assistant usefulness telemetry as user confirmation. Zero or empty history is a valid setup result, not proof of captured work. Resume unfinished work through the wizard; preserve the selected cutoff and accepted checkpoints.
12
+ Once tools are available and context is ready, retrieve context for the user's question and answer with sources. Ask whether it helped. Only after an explicit positive answer, run `npx --yes @useshifu/coding-harness onboard --harness codex --run <reference> --helpful`. Never treat assistant usefulness telemetry as user confirmation. Zero or empty history is a valid setup result, not proof of captured work. Resume unfinished work through the setup page; preserve the selected cutoff and accepted checkpoints.
Binary file
@@ -0,0 +1,93 @@
1
+ Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+
5
+ This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file
@@ -0,0 +1,93 @@
1
+ Copyright 2020 The Plus Jakarta Sans Project Authors (https://github.com/tokotype/PlusJakartaSans)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
package/wizard/index.html CHANGED
@@ -1,18 +1,63 @@
1
1
  <!doctype html>
2
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Shifu setup</title><link rel="stylesheet" href="/wizard.css"><script src="/wizard.js" defer></script></head>
3
- <body><main><header><span class="mark">s</span><span>Shifu setup</span><span class="local">ON YOUR DEVICE</span></header><section>
4
- <p class="eyebrow">Your work has a history. Put it to work.</p><h1>Welcome to Shifu.</h1><p class="intro">Your personal AI context engine. Three quick choices, then connect your assistant and bring in selected history.</p>
5
- <form id="settings"><fieldset><legend>How should Shifu help?</legend>
6
- <label class="label" for="history">Initial history</label><select id="history"><option value="15">Last 15 days · recommended</option><option value="30">Last 30 days</option><option value="custom">Custom days</option><option value="0">Start from now</option></select>
7
- <label id="custom-label" hidden>How many days?<input id="custom" type="number" min="1" max="36500" step="1" value="7"></label><p class="example">Recover decisions and lessons from recent work. More history can mean more processing through your model account.</p>
8
- <fieldset class="option"><legend>Helpful growth feedback</legend><label><input type="radio" name="growth" value="on" checked> On · recommended</label><label><input type="radio" name="growth" value="off"> Off</label><p class="example">“Your trade-off is clear. Adding a success measure could strengthen the recommendation.”</p></fieldset>
9
- <fieldset class="option"><legend>Decision alignment</legend><label><input type="radio" name="alignment" value="on" checked> On · recommended</label><label><input type="radio" name="alignment" value="off"> Off</label><p class="example">“Earlier notes say 30 minutes; this proposal uses an hour. Has the requirement changed?”</p><details><summary>Another example</summary><p>“This follows the notification window agreed in the earlier meeting.”</p></details></fieldset>
10
- <p class="note">Historical retrieval stays on. Feedback choices are independent, and your current instructions take priority.</p>
11
- <div id="key-entry"><label class="label" for="key">Connection key for <span id="harness"></span></label><input id="key" type="password" autocomplete="off" spellcheck="false" placeholder="Paste your tool-specific key"><p class="note">Create a key on your Shifu dashboard. It goes directly to the local runner for verification; never paste it into chat.</p></div>
12
- <div id="saved-key" class="note" hidden>Your saved connection will be reused. No new key is needed.<details><summary>Saved key revoked or no longer valid?</summary><p>Replace it in your own interactive terminal using the command below. The terminal asks for the new key. Replacement resets local checkpoints; saved server records remain. Then reopen onboarding with --new to bind the new connection.</p><code id="replace-command"></code><p><code id="reopen-command"></code></p></details></div>
13
- </fieldset><button type="submit">Review setup →</button></form>
14
- <div id="review" hidden><h2>Your setup</h2><dl id="summary"></dl><p class="note" id="scope"></p><p class="note">For a new connection, ongoing sync defaults to hourly automatic work sync. Existing sync settings are preserved.</p><p class="note">Selected coding text is prepared by your coding tool’s configured model provider. Shifu receives selected redacted records. Redaction can miss confidential details. History can include work across projects.</p><div class="actions"><button id="apply" type="button">Connect and start selected backfill</button><button id="edit" class="secondary" type="button">Edit choices</button></div></div>
15
- <div id="progress" hidden><h2 id="progress-title">Your context is taking shape.</h2><dl id="progress-facts"></dl><p id="progress-note" class="note"></p><div class="actions"><button id="resume" type="button" hidden>Resume unfinished backfill</button><button id="copy" type="button" hidden>Copy first question</button><button id="change" class="secondary" type="button" hidden>Change choices</button></div><p id="handoff" class="note" hidden>Reload your coding assistant or start a new chat to load Shifu tools, then paste this short prompt. Your settings and accepted backfill are saved.</p><textarea id="prompt" readonly hidden aria-label="Assistant resume prompt"></textarea></div>
16
- <p id="message" role="status" aria-live="polite"></p>
17
- <details class="privacy"><summary>What happens to my data?</summary><p>Setup choices and progress do not require model calls. Preparing your selected history uses your configured model account; exact cost depends on that history.</p><p>Files placed in the Shifu input folder upload as provided for server processing, separately from coding-session review. Keep unapproved drafts outside that folder.</p><p>Disconnecting stops future access. Retained data deletion is a separate beta request. <a href="https://www.useshifu.com/data" target="_blank" rel="noreferrer">Data handling</a> · <a href="https://www.useshifu.com/privacy" target="_blank" rel="noreferrer">Privacy policy</a></p></details>
18
- </section></main></body></html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Connect Shifu</title>
7
+ <link rel="stylesheet" href="/wizard.css">
8
+ <script src="/wizard.js" defer></script>
9
+ </head>
10
+ <body>
11
+ <main>
12
+ <header><img src="/brand-mark.png" alt=""><span>Shifu</span><small>Setup</small></header>
13
+ <section>
14
+ <div id="welcome"><h1>Set up Shifu</h1><p class="intro">Choose what to remember and how you’d like Shifu to help.</p></div>
15
+ <form id="settings">
16
+ <fieldset>
17
+ <legend class="sr-only">Your preferences</legend>
18
+ <label class="label" for="history">How much recent work should Shifu remember?</label>
19
+ <select id="history"><option value="15">Last 15 days · recommended</option><option value="30">Last 30 days</option><option value="custom">Choose a number of days</option><option value="0">Start from now</option></select>
20
+ <label id="custom-label" hidden>Number of days<input id="custom" type="number" min="1" max="36500" step="1" value="7"></label>
21
+ <p class="example">Include recent decisions and lessons. More history uses more of your model account.</p>
22
+ <fieldset class="option"><legend>Feedback on your work</legend>
23
+ <label><input type="radio" name="growth" value="on" checked>On</label><label><input type="radio" name="growth" value="off">Off</label>
24
+ <p class="example">For example: “Adding a success measure could make this plan easier to evaluate.”</p>
25
+ </fieldset>
26
+ <fieldset class="option"><legend>Reminders about past decisions</legend>
27
+ <label><input type="radio" name="alignment" value="on" checked>On</label><label><input type="radio" name="alignment" value="off">Off</label>
28
+ <p class="example">For example: “Earlier notes set a 30-minute window. Has that changed?”</p>
29
+ </fieldset>
30
+ <p class="note">Your past work stays searchable with either option off.</p>
31
+ <div id="key-entry">
32
+ <label class="label" for="key">Your <span id="harness"></span> connection key</label>
33
+ <input id="key" type="password" autocomplete="off" spellcheck="false" placeholder="Paste your Shifu connection key">
34
+ <p class="note"><a href="https://app.useshifu.com/#/sources" target="_blank" rel="noreferrer">Get a key from Sources</a>, then paste it here. Keep it out of chat.</p>
35
+ </div>
36
+ <div id="saved-key" class="note" hidden>Your saved connection is ready to use.
37
+ <details><summary>Need to replace your key?</summary><p>Ask your assistant to reconnect Shifu. Enter the new key in the secure prompt, never in chat. Replacing a connection can reset local sync progress; work already saved in Shifu remains.</p></details>
38
+ </div>
39
+ </fieldset>
40
+ <button type="submit">Continue</button>
41
+ </form>
42
+ <div id="review" hidden>
43
+ <h1>Review your choices</h1><dl id="summary"></dl><p id="scope" class="note"></p>
44
+ <p class="note">Your current sync settings stay the same. New connections sync automatically every hour.</p>
45
+ <p class="note">Your coding tool’s model provider prepares the selected text. Shifu receives redacted records, but redaction can miss sensitive details. History may include multiple projects.</p>
46
+ <div class="actions"><button id="apply" type="button">Connect and sync</button><button id="edit" class="secondary" type="button">Back</button></div>
47
+ </div>
48
+ <div id="progress" hidden>
49
+ <h1 id="progress-title">Connecting Shifu…</h1><dl id="progress-facts"></dl><p id="progress-note" class="note"></p>
50
+ <div class="actions"><button id="resume" type="button" hidden>Continue syncing</button><button id="copy" type="button" hidden>Copy a question</button><button id="change" class="secondary" type="button" hidden>Change preferences</button></div>
51
+ <p id="handoff" class="note" hidden>Return to your assistant and paste the question. If Shifu isn’t available there yet, start a new chat first.</p>
52
+ <textarea id="prompt" readonly hidden aria-label="Question to copy"></textarea>
53
+ </div>
54
+ <p id="message" role="status" aria-live="polite"></p>
55
+ <details class="privacy"><summary>Your data and model usage</summary>
56
+ <p>Choosing preferences uses no model tokens. Preparing your selected history uses your coding tool’s model account; cost depends on how much work you include.</p>
57
+ <p>Files you add to the Shifu folder upload as provided for server processing. Keep unapproved drafts outside that folder.</p>
58
+ <p>Disconnecting stops future access. Deleting saved work is a separate beta request. <a href="https://www.useshifu.com/data" target="_blank" rel="noreferrer">Data handling</a> · <a href="https://www.useshifu.com/privacy" target="_blank" rel="noreferrer">Privacy</a></p>
59
+ </details>
60
+ </section>
61
+ </main>
62
+ </body>
63
+ </html>
package/wizard/wizard.css CHANGED
@@ -1 +1,78 @@
1
- :root{color-scheme:light dark;--bg:light-dark(#fcfaf8,#1c171a);--panel:light-dark(#fff,#251e22);--ink:light-dark(#38252b,#f4e9ed);--muted:light-dark(#735e66,#c8b5be);--line:light-dark(#e5dadd,#4a3942);--accent:light-dark(#7a3049,#e9a7bc);--on:light-dark(#fff,#38202b);font:14px/1.6 system-ui,sans-serif;color:var(--ink);background:var(--bg)}*{box-sizing:border-box}body{margin:0;padding:24px 16px}main{max-width:650px;margin:auto;background:var(--panel);border:1px solid var(--line);border-radius:8px;overflow:hidden}header{display:flex;align-items:center;gap:10px;border-bottom:1px solid var(--line);padding:18px 24px}.mark{font-size:22px;color:var(--accent)}.local{margin-left:auto;font:10px monospace;color:var(--muted)}section{padding:28px}h1{font-size:28px;font-weight:500;letter-spacing:-.04em;margin:8px 0}h2{font-size:20px;font-weight:500}.eyebrow{font:11px monospace;color:var(--accent);text-transform:uppercase}.intro,.note,.example{color:var(--muted)}.note{font-size:12px}.intro{font-size:14px}fieldset{border:0;margin:0;padding:0}legend,.label{font-weight:500;font-size:14px}.label{display:block;margin:18px 0 8px}select,input[type=password],input[type=number],textarea{font:inherit;color:var(--ink);background:var(--bg);border:1px solid var(--line);border-radius:5px;padding:10px;width:100%}.option{border-top:1px solid var(--line);margin-top:18px;padding-top:12px}.option label{display:inline-flex;gap:7px;margin-right:14px;align-items:center;min-height:36px;font-size:13px}input[type=radio]{accent-color:var(--accent)}.example{font-size:12px;border-left:2px solid var(--line);padding-left:12px}.option details{font-size:12px;color:var(--muted)}button{font:inherit;font-size:13px;border:0;border-radius:5px;background:var(--accent);color:var(--on);padding:12px 15px;min-height:44px;cursor:pointer;margin-top:14px}button:disabled{opacity:.6;cursor:wait}.secondary{background:var(--bg);color:var(--ink);border:1px solid var(--line)}.actions{display:flex;gap:10px;flex-wrap:wrap}summary{cursor:pointer;color:var(--accent)}.privacy{border-top:1px solid var(--line);padding-top:18px;margin-top:24px;font-size:12px}a{color:var(--accent)}dl{display:grid;gap:12px}dl div{display:grid;grid-template-columns:130px minmax(0,1fr);gap:10px}dt{color:var(--muted)}dd{margin:0}#message{font-size:12px;color:var(--accent)}textarea{min-height:150px;margin-top:14px;font-size:12px}button:focus-visible,input:focus-visible,select:focus-visible,summary:focus-visible,a:focus-visible{outline:2px solid var(--accent);outline-offset:3px}[hidden]{display:none!important}@media(max-width:480px){body{padding:12px 8px}section{padding:20px 16px}header{padding:14px 16px}dl div{grid-template-columns:110px minmax(0,1fr)}h1{font-size:25px}}
1
+ @font-face {
2
+ font-family: 'Plus Jakarta Sans';
3
+ src: url('/fonts/plus-jakarta-sans.ttf') format('truetype');
4
+ font-weight: 200 800;
5
+ font-display: swap;
6
+ }
7
+ @font-face {
8
+ font-family: 'IBM Plex Mono';
9
+ src: url('/fonts/ibm-plex-mono.ttf') format('truetype');
10
+ font-weight: 400;
11
+ font-display: swap;
12
+ }
13
+ :root {
14
+ color-scheme: light;
15
+ --app-canvas: #fcfaf8;
16
+ --app-panel: #ffffff;
17
+ --app-subtle: #fff1e8;
18
+ --app-border: #e7dcd3;
19
+ --app-text: #362b25;
20
+ --app-muted: #716256;
21
+ --app-accent: #bc4617;
22
+ --app-accent-text: #ffffff;
23
+ font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
24
+ font-size: 14px;
25
+ line-height: 1.5;
26
+ letter-spacing: -.015em;
27
+ color: var(--app-text);
28
+ background: var(--app-canvas);
29
+ -webkit-font-smoothing: antialiased;
30
+ }
31
+ * { box-sizing: border-box; }
32
+ body { min-width: 320px; margin: 0; padding: 32px 20px; }
33
+ main { max-width: 640px; margin: auto; border: 1px solid var(--app-border); border-radius: 6px; background: var(--app-panel); }
34
+ header { display: flex; align-items: center; gap: 10px; padding: 18px 28px; border-bottom: 1px solid var(--app-border); font-weight: 650; }
35
+ header img { width: 28px; height: 28px; }
36
+ header small { margin-left: auto; color: var(--app-muted); font: 11px 'IBM Plex Mono', monospace; text-transform: uppercase; letter-spacing: .04em; }
37
+ section { padding: 28px; }
38
+ h1 { margin: 0 0 10px; font-size: 26px; line-height: 1.2; font-weight: 550; letter-spacing: -.035em; }
39
+ h2 { font-size: 18px; font-weight: 550; }
40
+ p { margin: 8px 0 14px; }
41
+ .intro, .note, .example { color: var(--app-muted); }
42
+ .intro { font-size: 14px; margin-bottom: 24px; }
43
+ .note, .example, details { font-size: 12px; line-height: 1.6; }
44
+ fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
45
+ legend, .label { font-size: 14px; font-weight: 550; }
46
+ .label { display: block; margin: 18px 0 8px; }
47
+ select, input[type=password], input[type=number], textarea { width: 100%; min-height: 44px; padding: 10px 12px; border: 1px solid var(--app-border); border-radius: 6px; background: var(--app-panel); color: var(--app-text); font: inherit; }
48
+ .option { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--app-border); }
49
+ .option legend { float: left; width: 100%; margin-bottom: 4px; }
50
+ .option label { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; margin-right: 20px; }
51
+ input[type=radio] { width: 16px; height: 16px; accent-color: var(--app-accent); }
52
+ .example { margin: 4px 0 0; }
53
+ button { min-height: 44px; margin-top: 18px; padding: 10px 16px; border: 1px solid transparent; border-radius: 6px; background: var(--app-accent); color: var(--app-accent-text); font: inherit; font-size: 13px; font-weight: 550; cursor: pointer; }
54
+ button:hover { filter: brightness(.95); }
55
+ button:disabled { opacity: .6; cursor: wait; }
56
+ .secondary { border-color: var(--app-border); background: transparent; color: var(--app-text); }
57
+ .actions { display: flex; flex-wrap: wrap; gap: 10px; }
58
+ a, summary { color: var(--app-accent); }
59
+ summary { min-height: 32px; padding: 6px 0; cursor: pointer; }
60
+ .privacy { margin-top: 24px; padding-top: 14px; border-top: 1px solid var(--app-border); }
61
+ dl { display: grid; gap: 12px; }
62
+ dl div { display: grid; grid-template-columns: 130px minmax(0, 1fr); gap: 12px; }
63
+ dt { color: var(--app-muted); }
64
+ dd { margin: 0; overflow-wrap: anywhere; }
65
+ #message { margin: 14px 0 0; color: var(--app-accent); font-size: 12px; }
66
+ #message:empty { display: none; }
67
+ textarea { min-height: 120px; margin-top: 14px; font: 12px/1.6 'IBM Plex Mono', monospace; }
68
+ :focus-visible { outline: 2px solid var(--app-accent); outline-offset: 3px; }
69
+ [hidden] { display: none !important; }
70
+ .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
71
+ @media (max-width: 480px) {
72
+ body { padding: 16px 12px; }
73
+ section { padding: 22px 18px; }
74
+ header { padding: 16px 18px; }
75
+ h1 { font-size: 24px; }
76
+ dl div { grid-template-columns: 100px minmax(0, 1fr); }
77
+ input[type=password], input[type=number], select { font-size: 16px; }
78
+ }
package/wizard/wizard.js CHANGED
@@ -11,14 +11,15 @@
11
11
  function settings(value){
12
12
  el('history').value=['0','15','30'].includes(String(value.historyDays))?String(value.historyDays):'custom';el('custom').value=value.historyDays||7;el('custom-label').hidden=el('history').value!=='custom';
13
13
  document.querySelector(`input[name=growth][value=${value.growthFeedback?'on':'off'}]`).checked=true;document.querySelector(`input[name=alignment][value=${value.decisionAlignment?'on':'off'}]`).checked=true;
14
- el('key-entry').hidden=value.configured;el('saved-key').hidden=!value.configured;el('harness').textContent=names[value.harness]||value.harness;el('replace-command').textContent='npx --yes @useshifu/coding-harness@0.3.35 connect --harness '+value.harness;el('reopen-command').textContent='npx --yes @useshifu/coding-harness@0.3.35 onboard --harness '+value.harness+' --new';
14
+ el('key-entry').hidden=value.configured;el('saved-key').hidden=!value.configured;el('harness').textContent=names[value.harness]||value.harness;
15
15
  }
16
16
  function renderProgress(){
17
- el('settings').hidden=true;el('review').hidden=true;el('progress').hidden=false;
18
- const labels={pending:'Waiting to prepare history',running:'Preparing selected history',partial:'Part of your selected history is saved',blocked:'Older unsynced turns need your direction',failed:'Backfill needs attention',complete:'Selected backfill complete',skipped:'Historical backfill skipped',empty:'No eligible history found'};
19
- el('progress-title').textContent=state.contextAvailable?'Your first context is ready.':labels[state.backfillStatus]||'Setup is continuing.';
20
- facts('progress-facts',[['Connection',state.connectionStatus==='reload_required'?'Verified · reload assistant required':state.connectionStatus],['History',state.historyDays?`Last ${state.historyDays} days`:'Start from now'],['Backfill',labels[state.backfillStatus]||state.backfillStatus],['Accepted',String(state.acceptedSegments||0)+' segments'],['Remaining',String(state.pendingSegments||0)+' segments'],['Blocked',String(state.blockedSegments||0)+' sessions']]);
21
- el('progress-note').textContent=state.lastErrorCode?'Accepted work is preserved. Retry the connection or ask your assistant for help; the selected window will not be expanded automatically.':state.backfillStatus==='skipped'?'New captured work can become context for future questions. No historical content was imported.':'You can continue working. Backfill is checkpointed independently from this browser; full completion is separate from first context availability.';
17
+ el('welcome').hidden=true;el('settings').hidden=true;el('review').hidden=true;el('progress').hidden=false;
18
+ const labels={pending:'Ready to sync your recent work',running:'Syncing your recent work',partial:'Some work is saved; sync will continue',blocked:'Some older work needs your attention',failed:'Sync needs a retry',complete:'Your selected history is saved',skipped:'Ready for new work',empty:'No recent work found in this time range'};
19
+ const connections={pending:'Connecting',verified:'Connected',reload_required:'Connected · start a new chat',failed:'Reconnect required'};
20
+ el('progress-title').textContent=state.contextAvailable?'Shifu is ready to help.':labels[state.backfillStatus]||'Setup is continuing.';
21
+ facts('progress-facts',[['Assistant',names[state.harness]||state.harness],['Connection',connections[state.connectionStatus]||'Checking connection'],['History',labels[state.backfillStatus]||'Checking your recent work']]);
22
+ el('progress-note').textContent=state.lastErrorCode?'Work already saved is kept. Try again, or ask your assistant to help reconnect Shifu.':state.backfillStatus==='skipped'?'Your next captured work can become context for future questions. No past work was imported.':'You can close this page and keep working. Sync continues in the background and resumes without repeating saved work.';
22
23
  el('resume').hidden=!['failed','partial','blocked','pending'].includes(state.backfillStatus);el('copy').hidden=!state.contextAvailable;el('handoff').hidden=!state.contextAvailable;
23
24
  el('prompt').value=state.resumePrompt||'';el('change').hidden=state.backfillStatus==='running';
24
25
  }
@@ -27,21 +28,21 @@
27
28
  event.preventDefault();const days=el('history').value==='custom'?Number(el('custom').value):Number(el('history').value);
28
29
  if(!Number.isSafeInteger(days)||days<0||days>36500||(el('history').value==='custom'&&days===0)){el('message').textContent='Choose a positive whole number of custom days, or Start from now.';return;}
29
30
  choices={historyDays:days,growthFeedback:document.querySelector('input[name=growth]:checked').value==='on',decisionAlignment:document.querySelector('input[name=alignment]:checked').value==='on'};
30
- facts('summary',[['History',days?`Last ${days} days`:'Start from now'],['Growth feedback',choices.growthFeedback?'On':'Off'],['Decision alignment',choices.decisionAlignment?'On':'Off']]);
31
- el('scope').textContent=days?`Prepare and sync the selected ${days}-day history window using your configured coding-model account. Actual usage depends on selected history.`:'Connect your assistant and skip historical backfill.';
32
- el('apply').textContent=days?'Connect and start selected backfill':'Connect and skip historical backfill';el('settings').hidden=true;el('review').hidden=false;el('message').textContent='';
31
+ facts('summary',[['History',days?`Last ${days} days`:'Start from now'],['Feedback',choices.growthFeedback?'On':'Off'],['Decision reminders',choices.decisionAlignment?'On':'Off']]);
32
+ el('scope').textContent=days?`Include the last ${days} days of work. Preparing this history uses your coding tool’s model account; cost depends on the amount of work.`:'Connect your assistant and start with new work. Past work will not be imported.';
33
+ el('apply').textContent=days?'Connect and sync':'Connect Shifu';el('welcome').hidden=true;el('settings').hidden=true;el('review').hidden=false;el('message').textContent='';
33
34
  });
34
- el('edit').addEventListener('click',()=>{el('review').hidden=true;el('settings').hidden=false;});
35
+ el('edit').addEventListener('click',()=>{el('welcome').hidden=false;el('review').hidden=true;el('settings').hidden=false;});
35
36
  el('apply').addEventListener('click',async()=>{
36
37
  if(busy)return;busy=true;el('apply').disabled=true;el('message').textContent='Verifying your connection and configuring Shifu…';
37
38
  const key=el('key').value.trim();el('key').value='';
38
39
  try{state=await api('apply',{...choices,...(!state.configured?{key}:{})});renderProgress();el('message').textContent='';}
39
- catch(error){el('message').textContent=error.message;el('review').hidden=true;el('settings').hidden=false;}
40
+ catch(error){el('message').textContent=error.message;el('welcome').hidden=false;el('review').hidden=true;el('settings').hidden=false;}
40
41
  finally{busy=false;el('apply').disabled=false;}
41
42
  });
42
- el('change').addEventListener('click',async()=>{try{const abandon=!['complete','skipped','empty'].includes(state.backfillStatus);if(abandon&&!window.confirm('Leave the unfinished history selection and choose new settings? Accepted records and sync checkpoints will remain. No older work will be added automatically.'))return;state=await api('new',{abandon});settings(state);el('progress').hidden=true;el('settings').hidden=false;}catch(error){el('message').textContent=error.message;}});
43
- el('resume').addEventListener('click',async()=>{try{state=await api('resume',{});renderProgress();el('message').textContent='Resume requested. Accepted ranges will not be replayed.';}catch(error){el('message').textContent=error.message;}});
44
- el('copy').addEventListener('click',async()=>{try{await navigator.clipboard.writeText(state.resumePrompt);el('message').textContent='Copied. Reload your assistant if needed, then paste the prompt.';}catch{el('prompt').hidden=false;el('prompt').focus();el('prompt').select();el('message').textContent='Copy this prompt and paste it into your assistant.';}});
43
+ el('change').addEventListener('click',async()=>{try{const abandon=!['complete','skipped','empty'].includes(state.backfillStatus);if(abandon&&!window.confirm('Change your history selection? Work already saved will stay in Shifu. This stops the current selection; no older work will be added automatically.'))return;state=await api('new',{abandon});settings(state);el('welcome').hidden=false;el('progress').hidden=true;el('settings').hidden=false;}catch(error){el('message').textContent=error.message;}});
44
+ el('resume').addEventListener('click',async()=>{try{state=await api('resume',{});renderProgress();el('message').textContent='Sync is continuing. Work already saved will not be sent again.';}catch(error){el('message').textContent=error.message;}});
45
+ el('copy').addEventListener('click',async()=>{try{await navigator.clipboard.writeText(state.resumePrompt);el('message').textContent='Question copied. Return to your assistant and paste it there. Start a new chat if Shifu is not available yet.';}catch{el('prompt').hidden=false;el('prompt').focus();el('prompt').select();el('message').textContent='Copy the question below and paste it into your assistant.';}});
45
46
  async function refresh(){if(busy||document.hidden)return;try{state=await api('status');if(state.authorized)renderProgress();}catch(error){el('message').textContent=error.message;}}
46
47
  api('status').then(value=>{state=value;settings(value);if(value.authorized)renderProgress();}).catch(error=>{el('settings').hidden=true;el('message').textContent=error.message;});
47
48
  setInterval(refresh,5000);document.addEventListener('visibilitychange',()=>{if(!document.hidden)refresh();});