acrylblends 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +5 -2
  2. package/dist/index.d.ts +7 -4
  3. package/dist/index.js +83 -33
  4. package/dist/index.js.map +1 -1
  5. package/legacy/README.md +1 -1
  6. package/legacy/agent/46c6eadf9230/src/app.ts +293 -0
  7. package/legacy/agent/46c6eadf9230/src/routes.ts +163 -0
  8. package/legacy/layouts.json +15 -0
  9. package/lib/{client-modules-Bkdr83jO.js → client-modules-C5qiVroz.js} +1 -1
  10. package/lib/cordis/door.js +1 -1
  11. package/lib/node-server/register.js +6 -1
  12. package/lib/node-server.js +1 -1
  13. package/lib/{transpile-QUK9b3eG.js → transpile-CRRaixyS.js} +19 -4
  14. package/package.json +1 -1
  15. package/plugins/acryl-door/src/cli/acryl.ts +6 -2
  16. package/plugins/acryl-node-server/src/register.ts +10 -1
  17. package/plugins/acryl-node-server/src/transpile.ts +25 -6
  18. package/plugins/acryl-page/src/favicon.ts +19 -0
  19. package/plugins/acryl-page/src/index.ts +18 -13
  20. package/plugins/agent-panel/client/main.ts +9 -0
  21. package/plugins/agent-panel/headless.ts +2 -2
  22. package/plugins/desktop/app-page.ts +151 -0
  23. package/plugins/desktop/assets/acrylblends-logo.png +0 -0
  24. package/plugins/desktop/brand.ts +164 -0
  25. package/plugins/desktop/frame.ts +88 -7
  26. package/plugins/desktop/git.ts +44 -0
  27. package/plugins/desktop/host-supervisor.ts +63 -0
  28. package/plugins/desktop/launch.ts +17 -1
  29. package/plugins/desktop/main.ts +268 -17
  30. package/plugins/desktop/preload.cjs +8 -0
  31. package/plugins/desktop/product.ts +117 -0
  32. package/plugins/desktop/window.ts +4 -0
  33. package/plugins/desktop-web/main.ts +3 -2
  34. package/plugins/desktop-web/plugin.json +1 -1
  35. package/plugins/extensions/index.ts +52 -7
  36. package/plugins/instance/archive.ts +395 -0
  37. package/plugins/instance/bundle-overlay.ts +10 -0
  38. package/plugins/instance/bundle.ts +201 -0
  39. package/plugins/instance/home.ts +386 -0
  40. package/plugins/instance/index.ts +132 -24
  41. package/plugins/instance/storage.ts +413 -0
  42. package/shared/skills/acryl-remove-plugin/SKILL.md +1 -1
  43. package/templates/agent/README.md +1 -1
  44. package/templates/agent/gitignore +2 -0
  45. package/templates/blank/README.md +1 -1
  46. package/templates/blank/gitignore +2 -0
  47. package/templates/blank/src/routes.ts +9 -5
  48. package/templates/chat/gitignore +2 -0
  49. package/templates/chat/src/chat-app.ts +7 -3
  50. package/templates/chat/src/llm.ts +7 -1
  51. package/templates/chat/test/chat.test.ts +16 -1
  52. package/templates/nodejs/gitignore +2 -0
  53. package/templates/notes/gitignore +2 -0
  54. package/templates/todo/README.md +1 -1
  55. package/templates/todo/gitignore +2 -0
package/README.md CHANGED
@@ -397,10 +397,13 @@ plugins installed while the app runs, an agent that builds plugins, durable task
397
397
  secrets):
398
398
 
399
399
  ```sh
400
- npx acrylblends init my-app --agent -y
401
- cd my-app && npm install && npm run dev
400
+ npx acrylblends@latest init my-app -y
401
+ cd my-app && npm install && npm run dev # the app in the ACRYL frame: chat, agents, branches, versions, Settings
402
+ npm run desktop # the same in its own window
402
403
  ```
403
404
 
405
+ `--bare` makes the plain app without the frame.
406
+
404
407
  See the [ACRYLBLENDS documentation](https://github.com/acrylblends/acrylblends/blob/main/docs/README.md) and
405
408
  [Acryl in a Cordis app](https://github.com/acrylblends/acrylblends/blob/main/docs/framework/cordis/acryl-in-a-cordis-app.md).
406
409
 
package/dist/index.d.ts CHANGED
@@ -53,7 +53,10 @@ interface FrameUrlRecord {
53
53
  readonly pid: number;
54
54
  readonly writtenAt: string;
55
55
  }
56
- /** The app home the frame uses: `ACRYLBLENDS_HOME` when set, else `<app>/.acrylblends` (the launcher's own rule). */
56
+ /**
57
+ * The app home the frame uses, as the app's own family chooses it (`plugins/instance`, which its launcher asks too): the CLI never keeps a second copy
58
+ * of that rule. A folder without the family is not a frame app.
59
+ */
57
60
  export declare function frameHome(appRoot: string, env?: Record<string, string | undefined>): string;
58
61
  export declare function frameUrlPath(home: string): string;
59
62
  /** The token a frame address carries, if it carries one. */
@@ -116,13 +119,13 @@ interface InitOptions {
116
119
  name: string | undefined;
117
120
  template: Template | undefined;
118
121
  install: boolean | undefined;
119
- /** Also add the desktop shell (`add desktop`): an Electron window on the app's own server. */
122
+ /** In the ACRYL frame with the desktop shell (`add desktop`): the default; `--bare` makes a plain app. */
120
123
  desktop: boolean;
121
124
  /** Print no "Next:" block: `create-acrylblends` runs `init` and prints its own, for the package manager it knows. */
122
125
  quietNext: boolean;
123
126
  yes: boolean;
124
127
  }
125
- /** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */
128
+ /** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [--bare] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */
126
129
  export declare function parseInitOptions(argv: readonly string[]): InitOptions;
127
130
  /** Scaffold a Cordis app. Returns the exit code: 0 done, 1 refused or failed. Never overwrites anything. */
128
131
  export declare function init(options: InitOptions, io: Io): Promise<number>;
@@ -221,7 +224,7 @@ export declare function parseUpgradeOptions(argv: readonly string[]): UpgradeOpt
221
224
  /**
222
225
  * `acrylblends upgrade`: bring the library plugins an app carries (the router, the server, the door, `extensions`, ...) up to the version of this package. Each file is compared with what
223
226
  * the app was given: an untouched file is replaced, a file the app edited stops the upgrade (it is shown as a diff and nothing changes) unless `--force`, which saves the edited version
224
- * under `.acrylblends/upgrade-backup/` first. Returns the exit code: 0 done or nothing to do, 1 refused or failed.
227
+ * under `.acryl/upgrade-backup/` first. Returns the exit code: 0 done or nothing to do, 1 refused or failed.
225
228
  */
226
229
  export declare function upgrade(options: UpgradeOptions, io: Io): Promise<number>;
227
230
  //#endregion
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ var packages = {
18
18
  },
19
19
  "dsh-client-ui-brand-acryl": {
20
20
  "name": "brand-client",
21
- "version": "0.1.0"
21
+ "version": "0.1.1"
22
22
  },
23
23
  "acryl-app-shell": {
24
24
  "name": "app-shell",
@@ -70,11 +70,11 @@ var packages = {
70
70
  },
71
71
  "acryl-versions": {
72
72
  "name": "versions",
73
- "version": "0.1.0"
73
+ "version": "0.1.1"
74
74
  },
75
75
  "acryl-blends-frame": {
76
76
  "name": "blends-frame",
77
- "version": "0.1.1"
77
+ "version": "0.1.2"
78
78
  }
79
79
  };
80
80
  //#endregion
@@ -88,19 +88,17 @@ var packages = {
88
88
  */
89
89
  /** Where an app keeps its data, for the plugins that inject `appInstance` (the framework's settings service does): the slice 0 row, kept in the app. */
90
90
  const APP_INSTANCE_ROW = `// Generated by acrylblends: the app's own folder family, for the plugins that inject appInstance.
91
- import { join } from 'node:path'
91
+ import { fileURLToPath } from 'node:url'
92
+ import { selectInstance } from '../plugins/instance/index.ts'
92
93
 
93
94
  export const name = 'app-instance'
94
95
 
95
96
  export function apply(ctx) {
96
97
  ctx.effect(() => {
97
- const home = process.env.ACRYLBLENDS_HOME ?? join(process.cwd(), '.acrylblends')
98
- const instance = ctx.reflect.provide('appInstance', {
99
- root: process.cwd(),
100
- home,
101
- // The engine home ACRYL's plugins read (support serves \`dshHome/logs\`); in this host the engine and the app share one home.
102
- dshHome: home,
103
- })
98
+ // Chosen by the app's own family, as the host chose it: this folder, or the source a desktop product names. The engine home ACRYL's plugins read
99
+ // (support serves \`dshHome/logs\`) is the app's home.
100
+ const chosen = selectInstance({ projectRoot: fileURLToPath(new URL('..', import.meta.url)), env: process.env })
101
+ const instance = ctx.reflect.provide('appInstance', chosen)
104
102
  return () => {
105
103
  void instance()
106
104
  }
@@ -525,9 +523,22 @@ function hostScriptOf(profile, frameModule) {
525
523
  "import { DEFAULT_SCHEMA, dump as dumpYaml, Type, load as parseYaml } from 'js-yaml'",
526
524
  "import { Acryl } from 'acrylblends'",
527
525
  "",
528
- "const appRoot = join(dirname(fileURLToPath(import.meta.url)), '..')",
526
+ "// The framework's files (this launcher, the composition it reads) sit next to it; the app is chosen by the app's own family: this folder, or the",
527
+ "// source a desktop product names when it runs the framework from its bundle. Its home and its port come with it, decided once.",
528
+ "const frameworkRoot = join(dirname(fileURLToPath(import.meta.url)), '..')",
529
+ "const { selectInstance } = await import(new URL('../plugins/instance/index.ts', import.meta.url).href)",
530
+ "const chosen = selectInstance({ projectRoot: frameworkRoot, env: process.env })",
531
+ "const appRoot = chosen.root",
532
+ "const home = chosen.home",
529
533
  `const profileName = '${profile}'`,
530
- "const home = process.env.ACRYLBLENDS_HOME ?? join(appRoot, '.acrylblends')",
534
+ "// The page and its tab are named for the app: the package's name, as the frame's header says it, else the folder's.",
535
+ "const appTitle = (() => {",
536
+ " try {",
537
+ " const name = JSON.parse(readFileSync(join(appRoot, 'package.json'), 'utf8')).name",
538
+ " if (typeof name === 'string' && name.trim() !== '') return name.trim()",
539
+ " } catch {}",
540
+ " return chosen.name",
541
+ "})()",
531
542
  "// DSH's rows keep their files in this app's own home: set before any driver row is imported.",
532
543
  "process.env.DSH_HOME = home",
533
544
  "// Development only: the app's code can be loaded again in this host (a version gone back to, a file written by the chat, an agent or an editor). Every file",
@@ -543,8 +554,7 @@ function hostScriptOf(profile, frameModule) {
543
554
  "const { boot } = await import('@deepseek-ai/dsh-app-boot')",
544
555
  "// The frame listens where the app's own server would: the folder's stable port (or the home's, when ACRYLBLENDS_HOME pins one). A restart then",
545
556
  "// keeps the page's origin, its auth cookie and its tabs; ACRYLBLENDS_PORT still decides, and the gates pass 0 for any free port.",
546
- "const { selectInstance } = await import(new URL('../plugins/instance/index.ts', import.meta.url).href)",
547
- "const port = String(selectInstance({ projectRoot: appRoot, env: process.env }).port.start)",
557
+ "const port = String(chosen.port.start)",
548
558
  "const check = process.argv.includes('--check')",
549
559
  "const require = createRequire(import.meta.url)",
550
560
  "/** The `!!js` tag the loader evaluates: js-yaml needs the same type the Cordis include plugin declares. */",
@@ -593,7 +603,8 @@ function hostScriptOf(profile, frameModule) {
593
603
  " return kept",
594
604
  "}",
595
605
  "",
596
- "const frameLayer = anchorRows(load(readFileSync(join(appRoot, 'cordis.patch.yml'), 'utf8')) ?? [], appRoot)",
606
+ "// The frame layer is the framework's file; its rows are anchored at the app, so the composition DSH keeps names the app's files, never the bundle's.",
607
+ "const frameLayer = anchorRows(load(readFileSync(join(frameworkRoot, 'cordis.patch.yml'), 'utf8')) ?? [], appRoot)",
597
608
  "/** Our host rows first: the framework's node server, then the webServer port the driver rows register on (they are the app's, not DSH's). */",
598
609
  `const clientRows = ${JSON.stringify(CLIENT_ROWS, null, 2).replaceAll("\\n", "\\n")}`,
599
610
  `const agentRows = ${JSON.stringify(AGENT_PRESET_ROWS, null, 2)}`,
@@ -613,7 +624,7 @@ function hostScriptOf(profile, frameModule) {
613
624
  " insert: [",
614
625
  " { id: 'node-server', name: './plugins/acryl-node-server/src/index.ts', config: { hostname: '127.0.0.1', port: Number(port), scan: Number(port) === 0 ? 0 : 20 } },",
615
626
  " { id: 'web-server', name: './plugins/acryl-web-server/src/index.ts' },",
616
- " { id: 'page', name: './plugins/acryl-page/src/index.ts' },",
627
+ " { id: 'page', name: './plugins/acryl-page/src/index.ts', config: { title: appTitle } },",
617
628
  " ...clientRows,",
618
629
  " ...agentRows,",
619
630
  " ],",
@@ -664,7 +675,7 @@ function hostScriptOf(profile, frameModule) {
664
675
  "const layers = [compositionLayer, settingsLayer]",
665
676
  "let ctx",
666
677
  "try {",
667
- " ctx = await boot('" + profile + "', join(appRoot, 'cordis.yml'), layers.flat(), (hostContext) => {",
678
+ " ctx = await boot('" + profile + "', join(frameworkRoot, 'cordis.yml'), layers.flat(), (hostContext) => {",
668
679
  " hostContext.provide('profileContext', profileContext)",
669
680
  " }, pathToFileURL(join(appRoot, 'package.json')).href)",
670
681
  "} catch (error) {",
@@ -837,9 +848,10 @@ function overlayOf(packages, door = false, appRows = []) {
837
848
  for (const entry of rows) {
838
849
  lines.push(` - id: ${entry.row ?? entry.name}`, ` name: ${yamlName(entry.name)}`);
839
850
  if ((entry.row ?? entry.name) === "acryl-blends-frame") lines.push(" config:", ` appPage: ${FRAME_PAGE_PREFIX}`);
851
+ if ((entry.row ?? entry.name) === "acryl-router") lines.push(" config:", " trailingSlash: ignore");
840
852
  }
841
853
  lines.push("- id: connection", " inject: []", " config:", " trustedHosts: ['127.0.0.1']");
842
- lines.push("- id: workspace-controller", " config:", " documentsDirectory: !!js process.env.ACRYLBLENDS_HOME ?? process.cwd() + '/.acrylblends'");
854
+ lines.push("- id: workspace-controller", " config:", " documentsDirectory: !!js process.env.DSH_HOME");
843
855
  for (const id of [...disabled].sort()) lines.push(`- id: ${id}`, " disabled: true");
844
856
  return `${lines.join("\n")}\n`;
845
857
  }
@@ -1890,7 +1902,7 @@ function startRepository(directory) {
1890
1902
  "Initial commit from acrylblends"
1891
1903
  ]);
1892
1904
  }
1893
- /** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */
1905
+ /** Parse `init [dir] [--template t] [--agent|--no-agent] [--name n] [--install] [--bare] [-y]`. `--agent` means `--template agent`, `--no-agent` means `--template blank`. */
1894
1906
  function parseInitOptions(argv) {
1895
1907
  let parsed;
1896
1908
  try {
@@ -1900,6 +1912,7 @@ function parseInitOptions(argv) {
1900
1912
  }
1901
1913
  const { values, positionals } = parsed;
1902
1914
  if (positionals.length > 1) throw new UsageError("init takes at most one directory");
1915
+ if (values.bare === true && values.desktop === true) throw new UsageError("use --bare or --desktop, not both");
1903
1916
  if (values.agent && values["no-agent"]) throw new UsageError("use --agent or --no-agent, not both");
1904
1917
  const named = values.template === void 0 ? void 0 : resolveTemplate(values.template);
1905
1918
  if (values.template !== void 0 && named === void 0) throw new UsageError(`unknown template "${values.template}" (${TEMPLATES.join(", ")})`);
@@ -1910,7 +1923,7 @@ function parseInitOptions(argv) {
1910
1923
  name: values.name,
1911
1924
  template: named ?? alias,
1912
1925
  install: values.install,
1913
- desktop: values.desktop === true,
1926
+ desktop: values.bare !== true,
1914
1927
  quietNext: values["no-next"] === true,
1915
1928
  yes: values.yes === true
1916
1929
  };
@@ -1926,6 +1939,7 @@ const parse$3 = (argv) => parseArgs({
1926
1939
  "no-agent": { type: "boolean" },
1927
1940
  install: { type: "boolean" },
1928
1941
  desktop: { type: "boolean" },
1942
+ bare: { type: "boolean" },
1929
1943
  "no-next": { type: "boolean" },
1930
1944
  yes: {
1931
1945
  type: "boolean",
@@ -2141,7 +2155,8 @@ async function add(options, io) {
2141
2155
  const code = addExternal(resolve(io.cwd, folder), appDir, io);
2142
2156
  if (code !== 0) return code;
2143
2157
  }
2144
- const plain = !existsSync(join(pluginsFolder(appDir).dir, "instance"));
2158
+ const folder = pluginsFolder(appDir).dir;
2159
+ const plain = !existsSync(join(appDir, "blend.yaml")) && (!existsSync(join(folder, "instance")) || existsSync(join(folder, "desktop-web")));
2145
2160
  if (plain && options.names.includes("db")) {
2146
2161
  io.stderr.write("error: \"db\" is the Cordis plugin for a Blend app. A plain Acryl app opens its database with openDb from \"acrylblends/db/sqlite\" (see docs/framework/guides/data.md)\n");
2147
2162
  return 1;
@@ -2497,7 +2512,7 @@ function checkDeps(options, io) {
2497
2512
  * The running frame's address, kept where the app's own data lives.
2498
2513
  *
2499
2514
  * The generated launcher prints the frame's URL with the launch token in it, and that token is the only way into a frame whose browser session ended.
2500
- * So the address is written where the app keeps its data - `.acrylblends/`, which every scaffold's `.gitignore` already covers - at mint time,
2515
+ * So the address is written where the app keeps its data - its folder under `~/.acryl`, outside the repository - at mint time,
2501
2516
  * owner-only (0600 here; a restricted ACL on Windows), and atomically (a temp file in the same folder renamed over it, so a reader never sees half a
2502
2517
  * record). On every later boot the launcher reuses the stored token instead of minting a new one, so the address - and an open page - survives a
2503
2518
  * restart; `acrylblends open` prints it, and `acrylblends open --new` rotates it (the running host replaces the token in place through
@@ -2512,10 +2527,20 @@ function checkDeps(options, io) {
2512
2527
  const FRAME_URL_FILE = "frame-url.json";
2513
2528
  /** Where a running frame rotates its token: a route the launcher mounts on the frame's own router. */
2514
2529
  const FRAME_TOKEN_PATH = "/api/acryl-frame/token/new";
2515
- /** The app home the frame uses: `ACRYLBLENDS_HOME` when set, else `<app>/.acrylblends` (the launcher's own rule). */
2530
+ /** Where an app keeps its own family: the instance plugin its launcher imports. */
2531
+ const familyOf = (appRoot) => join(resolve(appRoot), "plugins", "instance", "index.ts");
2532
+ /**
2533
+ * The app home the frame uses, as the app's own family chooses it (`plugins/instance`, which its launcher asks too): the CLI never keeps a second copy
2534
+ * of that rule. A folder without the family is not a frame app.
2535
+ */
2516
2536
  function frameHome(appRoot, env = process.env) {
2517
- const pinned = env.ACRYLBLENDS_HOME?.trim();
2518
- return pinned === void 0 || pinned === "" ? join(resolve(appRoot), ".acrylblends") : resolve(pinned);
2537
+ const family = familyOf(appRoot);
2538
+ if (!existsSync(family)) throw new Error(`${appRoot} is not a frame app: it has no plugins/instance/index.ts`);
2539
+ const { selectInstance } = createRequire(import.meta.url)(family);
2540
+ return selectInstance({
2541
+ projectRoot: resolve(appRoot),
2542
+ env
2543
+ }).home;
2519
2544
  }
2520
2545
  function frameUrlPath(home) {
2521
2546
  return join(home, FRAME_URL_FILE);
@@ -2674,6 +2699,10 @@ async function openFrame(args, io) {
2674
2699
  return 2;
2675
2700
  }
2676
2701
  const directory = resolve(io.cwd, rest[0] ?? ".");
2702
+ if (!existsSync(familyOf(directory))) {
2703
+ io.stderr.write(`no frame has run in ${directory}: it is not a frame app (it has no plugins/instance)\n`);
2704
+ return 1;
2705
+ }
2677
2706
  const found = await lookUpFrameUrl(directory);
2678
2707
  if (found.kind === "none") {
2679
2708
  io.stderr.write(`no frame has run in ${directory} yet; start it with npm run dev\n`);
@@ -3526,7 +3555,15 @@ function planFrameStep(appDir, library, templateDirFor = templateDir) {
3526
3555
  file: broken
3527
3556
  };
3528
3557
  const template = identifyTemplate(appDir, name);
3529
- if (template === void 0) return { kind: "unidentified" };
3558
+ if (template === void 0) {
3559
+ if (!ownsFrameRow(appDir)) return { kind: "unidentified" };
3560
+ const specs = frameProfileForApp(appDir);
3561
+ if (specs === void 0) return {
3562
+ kind: "unavailable",
3563
+ template: void 0
3564
+ };
3565
+ return planFrameWrites(appDir, library, name, specs, void 0, [], [], void 0);
3566
+ }
3530
3567
  if (!carriesFrame(template, templateDirFor(template))) return {
3531
3568
  kind: "no-frame-template",
3532
3569
  template
@@ -3567,6 +3604,18 @@ function planFrameStep(appDir, library, templateDirFor = templateDir) {
3567
3604
  else conflictLines = { "src/app.ts": ownerChangeLines(current, target) };
3568
3605
  }
3569
3606
  }
3607
+ return planFrameWrites(appDir, library, name, specs, template, files, kept, conflictLines);
3608
+ }
3609
+ /**
3610
+ * Whether an app built on its own carries what the frame mounts: its routes row (`src/frame.ts`), a `src/routes.ts` that says where the frame shows it, and
3611
+ * the composition the generated row calls (`composeInto` in `src/app.ts`). A registry blueprint made in the frame has all three.
3612
+ */
3613
+ function ownsFrameRow(appDir) {
3614
+ const at = (file) => join(appDir, "src", file);
3615
+ return existsSync(at("frame.ts")) && existsSync(at("routes.ts")) && FRAME_PREFIX_EXPORT.test(readText(at("routes.ts"))) && existsSync(at("app.ts")) && carriesComposeInto(readText(at("app.ts")));
3616
+ }
3617
+ /** The part of the frame plan every framed app shares: the generated composition row, the host plugins, the launcher and overlay, and what conflicts. */
3618
+ function planFrameWrites(appDir, library, name, specs, template, files, kept, conflictLines) {
3570
3619
  const appTs = files.find((file) => file.file === "src/app.ts")?.text ?? (existsSync(join(appDir, "src", "app.ts")) ? readText(join(appDir, "src", "app.ts")) : void 0);
3571
3620
  const rows = appFrameRows(appDir, {
3572
3621
  ...appTs === void 0 ? {} : { appTs },
@@ -3621,7 +3670,7 @@ function frameReport(frame) {
3621
3670
  /**
3622
3671
  * `acrylblends upgrade`: bring the library plugins an app carries (the router, the server, the door, `extensions`, ...) up to the version of this package. Each file is compared with what
3623
3672
  * the app was given: an untouched file is replaced, a file the app edited stops the upgrade (it is shown as a diff and nothing changes) unless `--force`, which saves the edited version
3624
- * under `.acrylblends/upgrade-backup/` first. Returns the exit code: 0 done or nothing to do, 1 refused or failed.
3673
+ * under `.acryl/upgrade-backup/` first. Returns the exit code: 0 done or nothing to do, 1 refused or failed.
3625
3674
  */
3626
3675
  async function upgrade(options, io) {
3627
3676
  const appDir = resolve(io.cwd, options.directory ?? ".");
@@ -3646,7 +3695,7 @@ async function upgrade(options, io) {
3646
3695
  if (frame.kind === "planned" && frame.conflicts.length > 0 && !options.force) {
3647
3696
  const own = frame.conflictLines?.["src/app.ts"] ?? [];
3648
3697
  const detail = own.length === 0 ? "" : `\n src/app.ts keeps the framework's plugin registrations; the app's own lines are ${own.map((line) => `line ${String(line.line)} (${JSON.stringify(line.text)})`).join(", ")}`;
3649
- io.stderr.write(`error: ${frame.conflicts.join(", ")} in ${appDir} ${frame.conflicts.length === 1 ? "is" : "are"} not the previous release's scaffold and acrylblends did not write ${frame.conflicts.length === 1 ? "it" : "them"}; nothing was changed.${detail} Move the app's version aside (or merge it into the template's) and run again, or run again with --force (the app's versions are saved under .acrylblends/upgrade-backup/ first)\n`);
3698
+ io.stderr.write(`error: ${frame.conflicts.join(", ")} in ${appDir} ${frame.conflicts.length === 1 ? "is" : "are"} not the previous release's scaffold and acrylblends did not write ${frame.conflicts.length === 1 ? "it" : "them"}; nothing was changed.${detail} Move the app's version aside (or merge it into the template's) and run again, or run again with --force (the app's versions are saved under .acryl/upgrade-backup/ first)\n`);
3650
3699
  return 1;
3651
3700
  }
3652
3701
  const frameChanged = frame.kind === "planned" && frame.changed;
@@ -3654,7 +3703,7 @@ async function upgrade(options, io) {
3654
3703
  io.stdout.write(frame.kind === "planned" ? "nothing to upgrade: the library plugins and the frame are up to date\n" : `nothing to upgrade: the app carries no library plugins\n ${"frame".padEnd(22)} ${frameReport(frame)}\n`);
3655
3704
  return 0;
3656
3705
  }
3657
- const scratch = join(appDir, ".acrylblends", `upgrade-${process.pid}-${Date.now()}`);
3706
+ const scratch = join(appDir, ".acryl", `upgrade-${process.pid}-${Date.now()}`);
3658
3707
  const lock = readLock(folder.dir);
3659
3708
  const history = readHistory();
3660
3709
  const plans = [];
@@ -3732,7 +3781,7 @@ async function upgrade(options, io) {
3732
3781
  io.stdout.write(`\n${state === "conflict" ? "EDITED HERE, the upgrade would replace it:\n" : ""}${unifiedDiff(name, before, after)}`);
3733
3782
  }
3734
3783
  if (conflicts > 0 && !options.force) {
3735
- io.stderr.write(`\nerror: ${conflicts} file(s) were edited in this app and the upgrade would replace them; nothing was changed. Review the diffs above, move your changes out of the plugin (a plugin of your own in plugins/<name>/), or run again with --force (the edited versions are saved under .acrylblends/upgrade-backup/)\n`);
3784
+ io.stderr.write(`\nerror: ${conflicts} file(s) were edited in this app and the upgrade would replace them; nothing was changed. Review the diffs above, move your changes out of the plugin (a plugin of your own in plugins/<name>/), or run again with --force (the edited versions are saved under .acryl/upgrade-backup/)\n`);
3736
3785
  return 1;
3737
3786
  }
3738
3787
  if (options.dryRun) {
@@ -3743,7 +3792,7 @@ async function upgrade(options, io) {
3743
3792
  io.stdout.write("\neverything is up to date\n");
3744
3793
  return 0;
3745
3794
  }
3746
- const backup = join(appDir, ".acrylblends", "upgrade-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/gu, "-"));
3795
+ const backup = join(appDir, ".acryl", "upgrade-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/gu, "-"));
3747
3796
  const addedDependencies = [];
3748
3797
  for (const plan of plans) {
3749
3798
  const here = join(folder.dir, plan.plugin.name);
@@ -3843,7 +3892,8 @@ Options for init:
3843
3892
  --template <name> one of the templates below (asked when not given); --agent means --template agent,
3844
3893
  --no-agent means --template blank
3845
3894
  --name <name> the project name (default: the directory name)
3846
- --desktop also add the desktop shell: npm run desktop opens a window on the app
3895
+ --bare a plain app, without the ACRYL frame (the frame, its chat and agents and npm run desktop, are the default)
3896
+ --desktop the frame and the desktop shell (the default; kept for older scripts)
3847
3897
  --install run npm install after copying
3848
3898
  -y, --yes ask nothing; use defaults (the blank template, no install)
3849
3899