create-obsidian-arrow 0.1.0 → 0.1.8

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
@@ -24,10 +24,44 @@ pnpm dev
24
24
  > `public/app.css` is git-ignored and never bundled — it's Obsidian's proprietary
25
25
  > CSS, so each developer extracts it from their own install via `pnpm pull-css`.
26
26
 
27
- Local dev of the initializer itself (from the sandbox repo, before publishing):
27
+ ### Install the bundled skills
28
+
29
+ The scaffold ships agent skills and installs them via the [`skills`](https://github.com/vercel-labs/skills)
30
+ CLI:
31
+
32
+ ```sh
33
+ pnpm skills:install --yes # non-interactive — installs all
34
+ pnpm skills:install # interactive picker
35
+ pnpm skills:install --yes --project-dir=<root> # install at another repo root (nesting)
36
+ pnpm skills:update # update an existing setup
37
+ ```
38
+
39
+ If you scaffold **inside an existing repo**, skills install scoped to the
40
+ scaffold folder (the CLI is cwd-relative); use `--project-dir=<outer-repo>` (or
41
+ `--global`) to install where an agent at the outer repo looks. The scaffolder
42
+ prints this hint when it detects nesting.
43
+
44
+ ## Update an existing project
45
+
46
+ The scaffolder is create-only, but `update` refreshes an existing project's
47
+ **managed** tooling (`scripts/`, `skills/`, `docs/`, `.github/`, `.husky/`,
48
+ `biome.json`, agent guides) and merges new `package.json` scripts/deps — it never
49
+ touches your `src/`, `public/`, `index.html`, or build configs:
50
+
51
+ ```sh
52
+ npx create-obsidian-arrow update # in the project (or: update <dir>)
53
+ npx create-obsidian-arrow update --dry-run # preview
54
+ ```
55
+
56
+ Then `pnpm install && pnpm check`.
57
+
58
+ ## Local dev of the initializer
59
+
60
+ From the sandbox repo, before publishing:
28
61
 
29
62
  ```sh
30
- node create-obsidian-arrow/index.mjs ../my-app
63
+ node create-obsidian-arrow/index.mjs ../my-app # scaffold
64
+ node create-obsidian-arrow/index.mjs update ../my-app # update
31
65
  ```
32
66
 
33
67
  ## What you get
package/index.mjs CHANGED
@@ -1,13 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * create-obsidian-arrow — scaffold a new Obsidian-styled Arrow.js UI sandbox.
3
+ * create-obsidian-arrow — scaffold or update an Obsidian-styled Arrow.js sandbox.
4
4
  *
5
- * pnpm create obsidian-arrow my-app # once published
6
- * node create-obsidian-arrow/index.mjs my-app # locally, before publishing
5
+ * create-obsidian-arrow <dir> scaffold a new project into <dir>
6
+ * create-obsidian-arrow update [dir] refresh an existing project's tooling
7
+ * (default dir: cwd) — preserves your code
7
8
  *
8
- * Copies the vendored template/ into <dir>, restores .gitignore (npm strips it
9
- * from packages, so it's vendored as _gitignore), rewrites the project name,
10
- * and runs `git init`. The template is a full, verified sandbox — see template/.
9
+ * pnpm create obsidian-arrow my-app
10
+ * node create-obsidian-arrow/index.mjs my-app # locally
11
+ * node create-obsidian-arrow/index.mjs update ./my-app
12
+ *
13
+ * Scaffold copies the vendored template/, restores .gitignore (vendored as
14
+ * _gitignore), names the project, and runs `git init`.
15
+ *
16
+ * Update refreshes only the *managed* tooling files from the template and merges
17
+ * package.json scripts + missing deps — it never touches your src/, public/,
18
+ * index.html, or the core build configs. Use --dry-run to preview.
11
19
  */
12
20
  import { spawnSync } from "node:child_process";
13
21
  import fs from "node:fs";
@@ -17,60 +25,172 @@ import { fileURLToPath } from "node:url";
17
25
  const here = path.dirname(fileURLToPath(import.meta.url));
18
26
  const templateDir = path.join(here, "template");
19
27
 
28
+ // Files/dirs the template owns and `update` may overwrite/merge. Everything else
29
+ // (src/, public/, index.html, vite.config.ts, tsconfig.json, lockfile, .gitignore,
30
+ // port-parity.json, …) is treated as user-owned and left alone.
31
+ const MANAGED = [
32
+ "scripts",
33
+ "skills",
34
+ "docs",
35
+ ".github",
36
+ ".husky",
37
+ "biome.json",
38
+ "AGENTS.md",
39
+ "CLAUDE.md",
40
+ ];
41
+
42
+ const argv = process.argv.slice(2);
43
+ const dryRun = argv.includes("--dry-run");
44
+
20
45
  function fail(message) {
21
46
  console.error(`create-obsidian-arrow: ${message}`);
22
47
  process.exit(1);
23
48
  }
24
49
 
25
- const targetArg = process.argv[2];
26
- if (!targetArg) {
27
- fail("usage: create-obsidian-arrow <directory>");
50
+ if (!fs.existsSync(templateDir)) {
51
+ fail("template/ is missing — run scripts/sync-template.mjs to build it.");
28
52
  }
29
53
 
30
- const destRoot = path.resolve(process.cwd(), targetArg);
31
- const appName = path.basename(destRoot);
54
+ /** Nearest ancestor *above* `dir` that is a git repo, or null. */
55
+ function outerRepoAbove(dir) {
56
+ let current = path.dirname(path.resolve(dir));
57
+ const fsRoot = path.parse(current).root;
58
+ while (current && current !== fsRoot) {
59
+ if (fs.existsSync(path.join(current, ".git"))) {
60
+ return current;
61
+ }
62
+ current = path.dirname(current);
63
+ }
64
+ return null;
65
+ }
32
66
 
33
- if (fs.existsSync(destRoot) && fs.readdirSync(destRoot).length > 0) {
34
- fail(`target "${targetArg}" already exists and is not empty.`);
67
+ /** Recursively copy src→dest; returns the list of written file paths (relative
68
+ * to `base`). Honors --dry-run (records but doesn't write). */
69
+ function copyTree(src, dest, base, written) {
70
+ const stat = fs.statSync(src);
71
+ if (stat.isDirectory()) {
72
+ if (!dryRun) {
73
+ fs.mkdirSync(dest, { recursive: true });
74
+ }
75
+ for (const entry of fs.readdirSync(src)) {
76
+ copyTree(path.join(src, entry), path.join(dest, entry), base, written);
77
+ }
78
+ return;
79
+ }
80
+ written.push(path.relative(base, dest));
81
+ if (!dryRun) {
82
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
83
+ fs.copyFileSync(src, dest);
84
+ }
35
85
  }
36
- if (!fs.existsSync(templateDir)) {
37
- fail("template/ is missing — run scripts/sync-template.mjs to build it.");
86
+
87
+ function scaffold(targetArg) {
88
+ const destRoot = path.resolve(process.cwd(), targetArg);
89
+ const appName = path.basename(destRoot);
90
+
91
+ if (fs.existsSync(destRoot) && fs.readdirSync(destRoot).length > 0) {
92
+ fail(`target "${targetArg}" already exists and is not empty (use \`update\` to refresh it).`);
93
+ }
94
+
95
+ const written = [];
96
+ copyTree(templateDir, destRoot, destRoot, written);
97
+ // Restore .gitignore (npm omits .gitignore from packages; vendored as _gitignore).
98
+ const vendoredIgnore = path.join(destRoot, "_gitignore");
99
+ if (fs.existsSync(vendoredIgnore)) {
100
+ fs.renameSync(vendoredIgnore, path.join(destRoot, ".gitignore"));
101
+ }
102
+
103
+ // Personalize the project name via targeted replace (keeps Biome formatting).
104
+ const pkgPath = path.join(destRoot, "package.json");
105
+ const renamed = fs
106
+ .readFileSync(pkgPath, "utf8")
107
+ .replace(/("name":\s*)"[^"]*"/, (_m, prefix) => `${prefix}${JSON.stringify(appName)}`);
108
+ fs.writeFileSync(pkgPath, renamed);
109
+
110
+ spawnSync("git", ["init", "-q"], { cwd: destRoot, stdio: "ignore" });
111
+
112
+ console.log(`\nScaffolded ${appName} in ${path.relative(process.cwd(), destRoot) || "."}\n`);
113
+ console.log("Next steps:");
114
+ console.log(` cd ${targetArg}`);
115
+ console.log(" pnpm install");
116
+ console.log(" pnpm pull-css # extract Obsidian's app.css (macOS auto-detect)");
117
+ console.log(" pnpm dev\n");
118
+
119
+ const outer = outerRepoAbove(destRoot);
120
+ if (outer) {
121
+ console.log(`Note: this project is nested inside the repo at ${outer}.`);
122
+ console.log(" Bundled skills install scoped to THIS project. To install them at the");
123
+ console.log(` outer repo instead: pnpm skills:install --yes --project-dir=${outer}\n`);
124
+ }
125
+ }
126
+
127
+ /** Merge template package.json scripts + missing deps into the target's,
128
+ * preserving name/version/identity and existing dep versions. */
129
+ function mergePackageJson(targetPkgPath) {
130
+ const tpl = JSON.parse(fs.readFileSync(path.join(templateDir, "package.json"), "utf8"));
131
+ const pkg = JSON.parse(fs.readFileSync(targetPkgPath, "utf8"));
132
+ const changes = [];
133
+
134
+ pkg.scripts ??= {};
135
+ for (const [name, cmd] of Object.entries(tpl.scripts ?? {})) {
136
+ if (pkg.scripts[name] !== cmd) {
137
+ changes.push(`script ${name}`);
138
+ pkg.scripts[name] = cmd;
139
+ }
140
+ }
141
+ for (const field of ["dependencies", "devDependencies"]) {
142
+ pkg[field] ??= {};
143
+ for (const [name, version] of Object.entries(tpl[field] ?? {})) {
144
+ if (!(name in pkg[field])) {
145
+ changes.push(`${field}: ${name}`);
146
+ pkg[field][name] = version;
147
+ }
148
+ }
149
+ }
150
+
151
+ if (changes.length > 0 && !dryRun) {
152
+ fs.writeFileSync(targetPkgPath, `${JSON.stringify(pkg, null, "\t")}\n`);
153
+ }
154
+ return changes;
38
155
  }
39
156
 
40
- function copyDir(src, dest) {
41
- fs.mkdirSync(dest, { recursive: true });
42
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
43
- const srcPath = path.join(src, entry.name);
44
- // Vendored as _gitignore because npm omits .gitignore from published tarballs.
45
- const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
46
- const destPath = path.join(dest, destName);
47
- if (entry.isDirectory()) {
48
- copyDir(srcPath, destPath);
49
- } else {
50
- fs.copyFileSync(srcPath, destPath);
157
+ function update(targetArg) {
158
+ const root = path.resolve(process.cwd(), targetArg ?? ".");
159
+ if (!fs.existsSync(path.join(root, "package.json"))) {
160
+ fail(`no package.json in ${root} — is this a scaffolded project?`);
161
+ }
162
+
163
+ const written = [];
164
+ for (const name of MANAGED) {
165
+ const src = path.join(templateDir, name);
166
+ if (fs.existsSync(src)) {
167
+ copyTree(src, path.join(root, name), root, written);
51
168
  }
52
169
  }
170
+ const pkgChanges = mergePackageJson(path.join(root, "package.json"));
171
+
172
+ const verb = dryRun ? "Would refresh" : "Refreshed";
173
+ console.log(
174
+ `${verb} ${written.length} managed file(s) in ${path.relative(process.cwd(), root) || "."}:`
175
+ );
176
+ for (const file of written) {
177
+ console.log(` ${file}`);
178
+ }
179
+ if (pkgChanges.length > 0) {
180
+ console.log(`package.json: ${dryRun ? "would update" : "updated"} ${pkgChanges.join(", ")}`);
181
+ }
182
+ console.log(
183
+ dryRun
184
+ ? "\n(dry run — nothing written.)"
185
+ : "\nLeft alone: src/, public/, index.html, vite.config.ts, tsconfig.json, .gitignore.\nRun `pnpm install` then `pnpm check`.\n"
186
+ );
53
187
  }
54
188
 
55
- copyDir(templateDir, destRoot);
56
-
57
- // Personalize the project name. Use a targeted replace (not JSON.parse +
58
- // stringify) so the template's existing Biome formatting stays byte-identical
59
- // and the fresh project passes `pnpm lint` out of the box.
60
- const pkgPath = path.join(destRoot, "package.json");
61
- const pkgText = fs.readFileSync(pkgPath, "utf8");
62
- const renamed = pkgText.replace(
63
- /("name":\s*)"[^"]*"/,
64
- (_match, prefix) => `${prefix}${JSON.stringify(appName)}`
65
- );
66
- fs.writeFileSync(pkgPath, renamed);
67
-
68
- // Initialize a git repo (best-effort; ignore if git is unavailable).
69
- spawnSync("git", ["init", "-q"], { cwd: destRoot, stdio: "ignore" });
70
-
71
- console.log(`\nScaffolded ${appName} in ${path.relative(process.cwd(), destRoot) || "."}\n`);
72
- console.log("Next steps:");
73
- console.log(` cd ${targetArg}`);
74
- console.log(" pnpm install");
75
- console.log(" pnpm pull-css # extract Obsidian's app.css (macOS auto-detect)");
76
- console.log(" pnpm dev\n");
189
+ const command = argv[0];
190
+ if (command === "update") {
191
+ update(argv.find((a) => a !== "update" && !a.startsWith("--")));
192
+ } else if (!command || command.startsWith("--")) {
193
+ fail("usage: create-obsidian-arrow <directory> | update [directory]");
194
+ } else {
195
+ scaffold(command);
196
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-obsidian-arrow",
3
- "version": "0.1.0",
3
+ "version": "0.1.8",
4
4
  "description": "Scaffold an Obsidian-styled Arrow.js UI sandbox (pnpm create obsidian-arrow <dir>).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,8 +4,20 @@ Operating guide for AI agents working in **obsidian-arrow-sandbox** — a
4
4
  client-only environment for prototyping [Arrow.js](https://arrow-js.com/) UI that
5
5
  ports into an Obsidian plugin with near-zero refactoring.
6
6
 
7
- Full design + rationale: [`docs/superpowers/specs`](docs/superpowers/specs/2026-06-29-obsidian-arrow-sandbox-design.md).
8
- Deeper how-to skills: [`skills/`](skills/) (install via `npx skills`).
7
+ ## Docs map (start here)
8
+
9
+ This file is the hub — everything else is linked from here:
10
+
11
+ - [`docs/workflow.md`](docs/workflow.md) — fresh-machine → running workflow.
12
+ - [`skills/`](skills/) — installable domain skills (`pnpm skills:install`):
13
+ obsidian-arrow-sandbox, arrow-js-obsidian-templates, arrow-js-obsidian-patterns,
14
+ arrow-js-obsidian-porting (sandbox→plugin parity check), obsidian-arrow-maintenance
15
+ (updating an existing project).
16
+ - [`docs/prompts/agent-setup.md`](docs/prompts/agent-setup.md) — prompt for
17
+ briefing a fresh agent (scaffold + orient).
18
+
19
+ Design rationale (why `core`+`framework`, no SSR, how `app.css` is sourced) is
20
+ summarized in "What this is (and isn't)" below and in the README.
9
21
 
10
22
  ## What this is (and isn't)
11
23
 
@@ -29,6 +41,14 @@ pnpm dev # Vite + HMR
29
41
  `public/app.css` is **git-ignored** (Obsidian's proprietary CSS — not
30
42
  redistributed); run `pnpm pull-css` once before `pnpm dev`.
31
43
 
44
+ Install the bundled skills: `pnpm skills:install --yes` (non-interactive, all
45
+ skills) or `pnpm skills:install` (TUI); update with `pnpm skills:update`. Scope
46
+ flags: `--agent <name>`, `--project-dir=<path>`, `--global`. **Nested inside
47
+ another repo?** Skills install cwd-relative — use `--project-dir=<outer-repo>` so
48
+ they land where an agent at the outer repo looks. To refresh an existing
49
+ project's tooling, run `npx create-obsidian-arrow update` (see the
50
+ obsidian-arrow-maintenance skill).
51
+
32
52
  ## Arrow v1.0.6 footguns — READ BEFORE WRITING TEMPLATES
33
53
 
34
54
  These are hard runtime errors, not style nits. They are encoded in CI
@@ -0,0 +1,4 @@
1
+ # CLAUDE.md
2
+
3
+ Read @AGENTS.md first — it's the operating guide and docs map (workflow, skills,
4
+ footguns, conventions, and the design record are all linked from there).
@@ -6,8 +6,8 @@ written with `@arrow-js/core` (+ `@arrow-js/framework` for async boundaries) and
6
6
  styled entirely by Obsidian's real `app.css`, so what you see here is what you
7
7
  get inside a plugin view.
8
8
 
9
- See the design + decision record in
10
- [`docs/superpowers/specs`](docs/superpowers/specs/2026-06-29-obsidian-arrow-sandbox-design.md).
9
+ New machine? See [`docs/workflow.md`](docs/workflow.md) for the full
10
+ fresh-checkout-to-running workflow.
11
11
 
12
12
  ## Scaffold a new project
13
13
 
@@ -24,6 +24,21 @@ Then `cd my-app && pnpm install && pnpm pull-css && pnpm dev`. A freshly
24
24
  scaffolded project passes `pnpm run ci` out of the box. The initializer's
25
25
  template is generated from this repo (`pnpm create:sync`), so it never drifts.
26
26
 
27
+ **Update an existing project's tooling** (refreshes the managed files — scripts,
28
+ skills, docs, agent guides, CI, `biome.json` — and merges new `package.json`
29
+ scripts/deps; never touches `src/`, `public/`, `index.html`, or build configs):
30
+
31
+ ```sh
32
+ npx create-obsidian-arrow update # in the project (or: update <dir>)
33
+ npx create-obsidian-arrow update --dry-run # preview first
34
+ ```
35
+
36
+ > **Nested in another repo?** If you scaffold inside an existing repo, bundled
37
+ > skills install scoped to the scaffold folder (the `skills` CLI is cwd-relative).
38
+ > To install them at the outer repo instead:
39
+ > `pnpm skills:install --yes --project-dir=<outer-repo>` (or `--global` for
40
+ > user-level). The scaffolder prints this hint when it detects nesting.
41
+
27
42
  > This repo (the full sandbox) is **not** published to npm — only the
28
43
  > `create-obsidian-arrow/` initializer is. An agent-onboarding prompt lives in
29
44
  > [`docs/prompts/agent-setup.md`](docs/prompts/agent-setup.md).
@@ -75,17 +90,31 @@ under [`skills/`](skills/) — it doubles as a local skill marketplace:
75
90
  - `arrow-js-obsidian-templates` — Arrow v1.0.6 template rules + footguns.
76
91
  - `arrow-js-obsidian-patterns` — integration patterns: icons (Lucide / data-icon
77
92
  sweep), CSS scoping vs Obsidian globals, mount/unmount lifecycle, reactive state.
93
+ - `arrow-js-obsidian-porting` — content-addressed porting parity: the
94
+ `component-hash` tool + a husky/CI check that the plugin copy hasn't drifted
95
+ from the sandbox source.
96
+ - `obsidian-arrow-maintenance` — updating an existing project: `create-obsidian-arrow
97
+ update`, `skills:update`, nesting/`--project-dir`, re-pull styling.
78
98
 
79
- Install them into your agent via the interactive TUI:
99
+ Install them into your agent:
80
100
 
81
101
  ```sh
82
- pnpm skills:install # = npx skills add . (pick skills in the TUI)
102
+ pnpm skills:install # interactive picker (TUI) on a terminal
103
+ pnpm skills:install --yes # non-interactive — installs ALL bundled skills
104
+ pnpm skills:install --yes --agent claude-code # install for one agent only
105
+ pnpm skills:install --yes --project-dir=<repo-root> # install into another project root
106
+ pnpm skills:update # update an existing setup to the latest
83
107
  ```
84
108
 
85
- `postinstall` offers this automatically after `pnpm install`, but only in an
86
- interactive terminal — it skips in CI / non-TTY (set `SKIP_SKILLS_INSTALL=1` to
87
- opt out entirely). You can also add this repo from anywhere with
88
- `npx skills add <git-url-or-path>`.
109
+ `postinstall` offers the picker automatically after `pnpm install`, but only in
110
+ an interactive terminal — in CI / non-TTY it just prints how to install (never
111
+ hangs). For agents/CI, use `--yes` (it runs `npx skills add . --all --yes`).
112
+ Scope flags: `--agent <name>` (one agent), `--project-dir=<path>` (install into a
113
+ different project root, e.g. an outer repo a scaffold is nested in), `--global`
114
+ (user-level, available everywhere). `pnpm skills:update` runs
115
+ `npx skills update -y`. The auto `postinstall` step takes no CLI args, so the env
116
+ forms `SKILLS_AGENT` / `SKILLS_PROJECT_DIR` / `SKILLS_GLOBAL` (and
117
+ `SKIP_SKILLS_INSTALL=1`) influence *that* path.
89
118
 
90
119
  ## Porting a component into the plugin
91
120
 
@@ -3,6 +3,10 @@ dist
3
3
  .DS_Store
4
4
  *.tgz
5
5
 
6
+ # `skills` install artifacts (regenerated by `pnpm skills:install`); the source
7
+ # of truth is skills/. (skills-lock.json is intentionally kept tracked.)
8
+ .agents/
9
+
6
10
  # Obsidian's app.css is proprietary — extract it locally with `pnpm pull-css`,
7
11
  # don't commit/redistribute it.
8
12
  public/app.css
@@ -32,15 +32,21 @@ Then:
32
32
  # or set OBSIDIAN_ASAR=<path>.
33
33
  pnpm dev # open the printed URL: / is the examples index, /example the demo.
34
34
  # The toolbar slider/presets + edge drag handle resize the panel.
35
- pnpm skills:install # install the bundled agent skills (npx skills add .) — pick
36
- # them in the TUI; this is how you load the domain knowledge.
35
+ pnpm skills:install --yes # install ALL bundled agent skills non-interactively
36
+ # (runs `npx skills add . --all --yes`) this loads
37
+ # the domain knowledge. Drop --yes for an interactive picker.
38
+ # NESTED inside another repo? Skills install cwd-relative,
39
+ # so add --project-dir=<outer-repo> (or --global) to put them
40
+ # where an agent at the outer repo will find them.
37
41
 
38
42
  READ FIRST
39
- - AGENTS.md (root) — operating guide: run, footguns, CSS scoping, verify, port.
43
+ - AGENTS.md (root) — operating guide + docs map (links everything below).
44
+ - docs/workflow.md — fresh-machine → running workflow.
40
45
  - skills/*/SKILL.md — obsidian-arrow-sandbox (workflow), arrow-js-obsidian-
41
46
  templates (template syntax + footguns), arrow-js-obsidian-patterns (icons via
42
- Lucide/data-icon sweep, CSS scoping, mount/unmount lifecycle, reactive state).
43
- - docs/superpowers/specs/ — design + decision record (why core+framework, no SSR).
47
+ Lucide/data-icon sweep, CSS scoping, mount/unmount lifecycle, reactive state),
48
+ arrow-js-obsidian-porting (sandbox→plugin parity check),
49
+ obsidian-arrow-maintenance (updating an existing project).
44
50
 
45
51
  ARROW v1.0.6 FOOTGUNS — do not relearn these the hard way:
46
52
  1. NO literal HTML comments inside html`` templates — Arrow treats HTML comments
@@ -75,7 +81,12 @@ PORTING TO A PLUGIN
75
81
  Copy the component file into the plugin's view dir and mount from
76
82
  ItemView.onOpen() via `template(this.contentEl)`. If it uses boundary()/async
77
83
  components, add @arrow-js/framework to the plugin and the side-effect
78
- `import '@arrow-js/framework'`. Leave src/sandbox/* behind.
84
+ `import '@arrow-js/framework'`. Leave src/sandbox/* behind. Guard against drift
85
+ with the porting-parity check (see the arrow-js-obsidian-porting skill).
86
+
87
+ MAINTENANCE (existing project)
88
+ Refresh tooling later with `npx create-obsidian-arrow update` (preserves src/),
89
+ update skills with `pnpm skills:update`. See the obsidian-arrow-maintenance skill.
79
90
 
80
91
  Start by scaffolding, running setup steps, then read AGENTS.md and confirm
81
92
  `pnpm dev` renders /example correctly. Report what you see.
@@ -0,0 +1,85 @@
1
+ # Workflow
2
+
3
+ How to go from a fresh machine to building Obsidian plugin UI in the sandbox.
4
+
5
+ ## Prerequisites (one-time, per machine)
6
+
7
+ - **Node ≥ 18** and **pnpm** — `corepack enable` (ships with Node) or `npm i -g pnpm`.
8
+ - **Obsidian desktop app installed** — required by `pull-css` to extract `app.css`.
9
+ macOS is auto-detected; Windows/WSL needs an explicit path (see below).
10
+
11
+ ## Spin up a sandbox
12
+
13
+ ```sh
14
+ # 1. Scaffold from the published initializer (any one of these)
15
+ pnpm create obsidian-arrow my-ui # or: npm create obsidian-arrow@latest my-ui
16
+ # or: npx create-obsidian-arrow my-ui
17
+ cd my-ui
18
+
19
+ # 2. Install dependencies
20
+ pnpm install
21
+
22
+ # 3. Pull Obsidian's styling locally — REQUIRED before dev (needs Obsidian installed)
23
+ pnpm pull-css # macOS: auto-detects /Applications/Obsidian.app
24
+ # else: pnpm pull-css --path <obsidian.asar|app.css>
25
+ # or OBSIDIAN_ASAR=<path> pnpm pull-css
26
+
27
+ # 4. Run it
28
+ pnpm dev # open the printed URL — / is the index, /example the demo
29
+ ```
30
+
31
+ `public/app.css` is **git-ignored and never shipped** (it's Obsidian's proprietary
32
+ CSS), so step 3 must run on every fresh checkout, or the sandbox renders unstyled.
33
+
34
+ ## Make your agent aware (optional)
35
+
36
+ From inside the scaffolded project:
37
+
38
+ ```sh
39
+ pnpm skills:install # interactive picker (TUI) on a terminal
40
+ pnpm skills:install --yes # non-interactive — installs ALL bundled skills (for agents/CI)
41
+ pnpm skills:update # update an already-installed setup to the latest
42
+ ```
43
+
44
+ Then point the agent at [`AGENTS.md`](../AGENTS.md), or brief a fresh agent with
45
+ [`docs/prompts/agent-setup.md`](prompts/agent-setup.md).
46
+
47
+ **Nested inside another repo?** Skills install scoped to the current folder (the
48
+ `skills` CLI is cwd-relative). To install them at the outer repo instead:
49
+ `pnpm skills:install --yes --project-dir=<outer-repo>` (or `--global`).
50
+
51
+ **Refresh an existing project's tooling** (scripts, skills, docs, CI — never your
52
+ `src/`): `npx create-obsidian-arrow update` (add `--dry-run` to preview).
53
+
54
+ ## Build → verify → port loop
55
+
56
+ ```sh
57
+ # add a component in src/components/, register it in src/examples/registry.ts
58
+ pnpm dev # iterate with HMR
59
+ pnpm run ci # biome + typecheck + tests + build before trusting it
60
+ ```
61
+
62
+ Always confirm the actual render in the browser — Arrow's footguns surface only
63
+ at render, so a passing `tsc` is not proof a component works. See the footguns in
64
+ [`AGENTS.md`](../AGENTS.md) and the `arrow-js-obsidian-templates` skill.
65
+
66
+ **Port a component into a plugin:** copy the file into the plugin's view directory
67
+ and mount it from `ItemView.onOpen()` via `template(this.contentEl)`. If it uses
68
+ `boundary()`/async components, add `@arrow-js/framework` to the plugin and the
69
+ side-effect `import '@arrow-js/framework'`. Leave `src/sandbox/*` behind.
70
+
71
+ ## Scaffold vs. clone
72
+
73
+ - **Scaffold** (`pnpm create obsidian-arrow`) when you want to **build plugin UI** —
74
+ the normal use.
75
+ - **Clone** this repo (`kylebrodeur/obsidian-arrow-sandbox`) only to **change the
76
+ sandbox or the initializer itself**, then `pnpm create:sync` and publish the
77
+ `create-obsidian-arrow/` package (`cd create-obsidian-arrow && pnpm publish`).
78
+
79
+ ## Troubleshooting
80
+
81
+ | Symptom | Fix |
82
+ |---|---|
83
+ | `pnpm dev` renders unstyled / `var(--…)` not resolving | Run `pnpm pull-css` (step 3). |
84
+ | `pull-css` can't find Obsidian | Pass `--path <obsidian.asar\|app.css>` or set `OBSIDIAN_ASAR` (Windows/WSL not auto-detected). |
85
+ | `Invalid HTML position` at render | An Arrow footgun — no HTML comments in templates; attribute expressions must be the whole value. See `AGENTS.md`. |
@@ -18,6 +18,7 @@
18
18
  "check": "biome check --write . && pnpm typecheck && pnpm test",
19
19
  "ci": "biome ci . && pnpm typecheck && pnpm test && pnpm build",
20
20
  "skills:install": "node scripts/install-skills.mjs --force",
21
+ "skills:update": "node scripts/install-skills.mjs --update",
21
22
  "postinstall": "node scripts/install-skills.mjs",
22
23
  "prepare": "husky"
23
24
  },