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 +36 -2
- package/index.mjs +168 -48
- package/package.json +1 -1
- package/template/AGENTS.md +22 -2
- package/template/CLAUDE.md +4 -0
- package/template/README.md +37 -8
- package/template/_gitignore +4 -0
- package/template/docs/prompts/agent-setup.md +17 -6
- package/template/docs/workflow.md +85 -0
- package/template/package.json +1 -0
- package/template/scripts/component-hash.mjs +123 -0
- package/template/scripts/install-skills.mjs +136 -28
- package/template/scripts/lib/canonical-source.mjs +63 -0
- package/template/skills/arrow-js-obsidian-porting/SKILL.md +92 -0
- package/template/skills/arrow-js-obsidian-templates/SKILL.md +1 -1
- package/template/skills/obsidian-arrow-maintenance/SKILL.md +69 -0
- package/template/skills/obsidian-arrow-sandbox/SKILL.md +13 -0
- package/template/test/component-hash.test.mjs +48 -0
- package/template/test/skills-frontmatter.test.mjs +57 -0
- package/template/docs/superpowers/specs/2026-06-29-obsidian-arrow-sandbox-design.md +0 -206
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
|
-
|
|
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
|
|
3
|
+
* create-obsidian-arrow — scaffold or update an Obsidian-styled Arrow.js sandbox.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
package/template/AGENTS.md
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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
|
package/template/README.md
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
|
99
|
+
Install them into your agent:
|
|
80
100
|
|
|
81
101
|
```sh
|
|
82
|
-
pnpm skills:install
|
|
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
|
|
86
|
-
interactive terminal —
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
package/template/_gitignore
CHANGED
|
@@ -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
|
|
36
|
-
|
|
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
|
|
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
|
-
-
|
|
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`. |
|
package/template/package.json
CHANGED
|
@@ -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
|
},
|