website-build-skill 0.1.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 (109) hide show
  1. package/.claude-plugin/marketplace.json +12 -0
  2. package/.claude-plugin/plugin.json +10 -0
  3. package/AGENTS.md +19 -0
  4. package/CHANGELOG.md +42 -0
  5. package/CLAUDE.md +16 -0
  6. package/CODE_OF_CONDUCT.md +127 -0
  7. package/CONTRIBUTING.md +49 -0
  8. package/LICENSE +21 -0
  9. package/MAINTAINERS.md +12 -0
  10. package/README.md +148 -0
  11. package/RELEASING.md +133 -0
  12. package/REPO_SETTINGS.md +91 -0
  13. package/SECURITY.md +28 -0
  14. package/adapters/agents-md.md +26 -0
  15. package/adapters/antigravity.md +68 -0
  16. package/adapters/chatgpt-project.md +57 -0
  17. package/adapters/claude-code.md +25 -0
  18. package/adapters/claude-desktop.md +33 -0
  19. package/adapters/codex.md +21 -0
  20. package/adapters/custom-gpt.md +57 -0
  21. package/adapters/grok.md +100 -0
  22. package/adapters/hermes.md +49 -0
  23. package/adapters/team.yaml +42 -0
  24. package/adapters/zero-install.md +17 -0
  25. package/bin/website-build-skill.mjs +6 -0
  26. package/docs/COMPATIBILITY.md +34 -0
  27. package/docs/EVALUATION.md +109 -0
  28. package/docs/INSTALLER.md +73 -0
  29. package/docs/LIBRARY-CHECK.md +95 -0
  30. package/docs/METHOD.md +32 -0
  31. package/docs/PROVENANCE.md +36 -0
  32. package/docs/TIPS.md +18 -0
  33. package/docs/bundles/checklists.md +414 -0
  34. package/docs/bundles/method.md +1398 -0
  35. package/docs/bundles/playbooks.md +1566 -0
  36. package/docs/bundles/prompts.md +987 -0
  37. package/docs/bundles/team.md +1781 -0
  38. package/docs/evaluation/host-trial.md +95 -0
  39. package/docs/evaluation/receipt-template.json +18 -0
  40. package/docs/evidence/audit-baseline.json +61 -0
  41. package/docs/evidence/content-review.json +141 -0
  42. package/docs/evidence/floor.json +345 -0
  43. package/docs/evidence/library-check.json +164 -0
  44. package/docs/evidence/local-checks.json +156 -0
  45. package/llms.txt +74 -0
  46. package/package.json +68 -0
  47. package/skills/website-build-skill/SKILL.md +124 -0
  48. package/skills/website-build-skill/archive/graphics-design-original.md +70 -0
  49. package/skills/website-build-skill/checklists/accessibility.md +38 -0
  50. package/skills/website-build-skill/checklists/brand-and-mockups.md +43 -0
  51. package/skills/website-build-skill/checklists/build.md +39 -0
  52. package/skills/website-build-skill/checklists/performance-seo.md +40 -0
  53. package/skills/website-build-skill/checklists/research.md +91 -0
  54. package/skills/website-build-skill/checklists/security.md +38 -0
  55. package/skills/website-build-skill/checklists/ship.md +59 -0
  56. package/skills/website-build-skill/examples/audit-cases.md +66 -0
  57. package/skills/website-build-skill/examples/fictional-studio-brief.md +73 -0
  58. package/skills/website-build-skill/examples/fictional-studio-handoff.md +98 -0
  59. package/skills/website-build-skill/manifest.json +1474 -0
  60. package/skills/website-build-skill/playbooks/accessibility.md +91 -0
  61. package/skills/website-build-skill/playbooks/choose-stack.md +69 -0
  62. package/skills/website-build-skill/playbooks/data-and-templates.md +89 -0
  63. package/skills/website-build-skill/playbooks/deploy-and-operate.md +87 -0
  64. package/skills/website-build-skill/playbooks/embed-facades.md +59 -0
  65. package/skills/website-build-skill/playbooks/image-pipeline.md +73 -0
  66. package/skills/website-build-skill/playbooks/performance-budgets.md +76 -0
  67. package/skills/website-build-skill/playbooks/research-and-memory.md +398 -0
  68. package/skills/website-build-skill/playbooks/security-headers.md +132 -0
  69. package/skills/website-build-skill/playbooks/self-hosted-fonts.md +62 -0
  70. package/skills/website-build-skill/playbooks/structured-data-and-llms.md +84 -0
  71. package/skills/website-build-skill/prompts/00-start.md +94 -0
  72. package/skills/website-build-skill/prompts/01-website-deep-research.md +233 -0
  73. package/skills/website-build-skill/prompts/03-extract-brand-kit.md +46 -0
  74. package/skills/website-build-skill/prompts/04-three-mockups.md +44 -0
  75. package/skills/website-build-skill/prompts/05-stack-and-build.md +46 -0
  76. package/skills/website-build-skill/prompts/06-site-audit.md +39 -0
  77. package/skills/website-build-skill/prompts/07-accessibility-contrast.md +38 -0
  78. package/skills/website-build-skill/prompts/08-seo-aeo.md +72 -0
  79. package/skills/website-build-skill/prompts/09-security-headers.md +40 -0
  80. package/skills/website-build-skill/prompts/10-adversarial-pre-ship.md +44 -0
  81. package/skills/website-build-skill/prompts/11-ship-and-verify.md +44 -0
  82. package/skills/website-build-skill/prompts/12-web-design-team.md +141 -0
  83. package/skills/website-build-skill/references/stacks/framework.md +76 -0
  84. package/skills/website-build-skill/references/stacks/scripted-static.md +76 -0
  85. package/skills/website-build-skill/references/stacks/static-html.md +72 -0
  86. package/skills/website-build-skill/roles/builder.md +139 -0
  87. package/skills/website-build-skill/roles/coordinator.md +132 -0
  88. package/skills/website-build-skill/roles/designer.md +125 -0
  89. package/skills/website-build-skill/roles/graphics.md +126 -0
  90. package/skills/website-build-skill/roles/optimizer.md +129 -0
  91. package/skills/website-build-skill/roles/researcher.md +202 -0
  92. package/skills/website-build-skill/roles/reviewer.md +139 -0
  93. package/skills/website-build-skill/templates/audit-brief.md +63 -0
  94. package/skills/website-build-skill/templates/brand.md +53 -0
  95. package/skills/website-build-skill/templates/brief.md +51 -0
  96. package/skills/website-build-skill/templates/evidence.md +100 -0
  97. package/skills/website-build-skill/templates/handoff.yaml +23 -0
  98. package/skills/website-build-skill/templates/install-choice.txt +28 -0
  99. package/skills/website-build-skill/templates/site-operations.md +71 -0
  100. package/skills/website-build-skill/templates/status.md +47 -0
  101. package/skills/website-build-skill/templates/tokens.json +72 -0
  102. package/src/agents.mjs +7 -0
  103. package/src/bundle.mjs +106 -0
  104. package/src/catalog.mjs +12 -0
  105. package/src/choice.mjs +6 -0
  106. package/src/cli.mjs +96 -0
  107. package/src/install.mjs +344 -0
  108. package/src/library-check.mjs +106 -0
  109. package/src/validate.mjs +351 -0
@@ -0,0 +1,28 @@
1
+ How do you want to work?
2
+
3
+ 1 Teach my AI One AI learns the whole thing and does every job itself.
4
+ Works with any AI: Claude, ChatGPT, Grok, Cursor, Copilot.
5
+ Nothing else to install.
6
+
7
+ 2 Give me the team Seven specialist agents, one per job, starting with a researcher that gets them all current.
8
+ Needs Claude Code, Codex, Hermes, Antigravity, or Grok Bot.
9
+ One command installs their files; you open each session yourself.
10
+
11
+ Not sure? Pick 1. Moving to 2 later costs you nothing you have already done.
12
+
13
+ Where should the work be saved?
14
+
15
+ 1 Obsidian vault Give an existing folder inside your vault. The library is
16
+ plain Markdown, so it opens and links natively.
17
+
18
+ 2 A folder on this computer
19
+ Give an existing directory. The default is the
20
+ website project folder.
21
+
22
+ 3 Notion Working copy still lives in a folder on this computer,
23
+ because every gate reads its own files back by path.
24
+ Notion receives an export of the finished library.
25
+
26
+ 4 Somewhere else Type an existing local destination yourself.
27
+
28
+ Whatever you pick becomes the workspace root. Everything below is written inside it.
@@ -0,0 +1,71 @@
1
+ # Site operations
2
+
3
+ Date rule: [ASK DATE and evidence provenance](../playbooks/research-and-memory.md#ask-date-rule).
4
+ This is a blank artifact contract. Fill it from actual inputs and evidence at runtime.
5
+ Unresolved required fields keep the relevant gate BLOCKED; never prefill a PASS.
6
+
7
+ ## What and why
8
+
9
+ Record audience, primary action, public destination, implementation source and current artifact.
10
+ Name the operational owner by approved role or contact mechanism, never by invented identity.
11
+
12
+ ## Trigger
13
+
14
+ State what starts content updates, scheduled publication, builds and release promotion.
15
+ Name the actual scheduler or manual owner. If nothing is scheduled, say manual.
16
+ A date in a template does not create a rebuild trigger.
17
+
18
+ ## Invocation chain
19
+
20
+ Write the exact sequence from content edit through validation, image generation, build,
21
+ preview, checks, promotion and live verification. Include working directories and commands
22
+ as project-relative paths. Record the operator and failure stop at each step.
23
+ Do not require a second document to reconstruct the procedure.
24
+
25
+ ## Dependencies
26
+
27
+ List verified runtime/tool versions, dependency lock, host plan, integrations and access roles.
28
+ Name credential storage/injection mechanisms and environment variable names only.
29
+ Do not include secret values, authenticated links or personal machine paths.
30
+
31
+ ## Reads
32
+
33
+ List content records, assets, brand tokens, approved discovery inputs, configuration,
34
+ external resources, and authority needed to read each.
35
+
36
+ ## Writes
37
+
38
+ List generated output, deployment target, logs, analytics/event side effects,
39
+ and any runtime persistence with owners and retention constraints.
40
+ Keep private research, raw brand sources, scratch artifacts and credentials out of uploads.
41
+
42
+ ## The closed loop
43
+
44
+ Name what watches the site, including nothing when no watcher exists.
45
+ For each watcher record check, cadence, expected signal, recipient and response procedure.
46
+ State how missed instrumentation or a broken watcher becomes visible.
47
+ If manual only, name the responsible role and the concrete manual verification trigger.
48
+
49
+ ## Failure modes
50
+
51
+ | Symptom | Diagnostic command/action | Expected evidence | Recovery | Responsible role |
52
+ | --- | --- | --- | --- | --- |
53
+
54
+ Cover failed builds, bad routes/assets, CSP breakage, form/integration failure,
55
+ stale scheduled content, invalid discovery data, unavailable credentials and failed deploys.
56
+ Record rollback identity, retention limits and safe recovery for an initial release.
57
+
58
+ ## Run-and-verify by hand
59
+
60
+ Provide exact content editing, clean build, tests, preview, promotion and rollback commands.
61
+ For each command include environment requirements, observed success evidence and failure response.
62
+ Include live HTTPS, route, error, asset, primary journey, header and metadata checks.
63
+ Include verification after rollback, not only the restoration command.
64
+ Mark every untested procedure UNVERIFIED with the exact settling test.
65
+
66
+ ## Source of truth
67
+
68
+ Identify maintained source, content owner, approved research/brand/design revisions,
69
+ current deployed artifact, build recipe, configuration authority and release receipt.
70
+ Explain which generated files must not be edited and how future changes reopen affected gates.
71
+ Keep this document updated with the deployed artifact and actual watcher state.
@@ -0,0 +1,47 @@
1
+ # Website stage status
2
+
3
+ Date rule: [ASK DATE and evidence provenance](../playbooks/research-and-memory.md#ask-date-rule).
4
+ This is a blank artifact contract. Fill it from actual inputs and evidence at runtime.
5
+ Unresolved required fields keep the relevant gate BLOCKED; never prefill a PASS.
6
+
7
+ ## Current state
8
+
9
+ - Engagement: reference the stable request and scope.md ASK DATE provenance.
10
+ - Mode: recorded SOLO or TEAM, actual workers and family evidence.
11
+ - Stage: name the active stage and accountable role.
12
+ - Revision: record current brief, library, brand, mockup, source and candidate identities.
13
+ - Next action: name one concrete action, owner, prerequisite and expected evidence.
14
+
15
+ ## Gate acceptance
16
+
17
+ | Stage | Producer receipt | Artifact revision | Required checks | Status | Accepted evidence | Next owner |
18
+ | --- | --- | --- | --- | --- | --- | --- |
19
+
20
+ Coordinator alone writes acceptance after opening the producer's artifacts.
21
+ Use PASS, FAIL, BLOCKED or reasoned NOT APPLICABLE, never an unsupported complete label.
22
+ Research precedes every other role; three visuals and a named selection precede site code.
23
+ Internal review leaves independent gate NOT MET and ship BLOCKED.
24
+ Keep pre-promotion and live-release verification separate.
25
+
26
+ ## User decisions
27
+
28
+ | Decision | Exact scope | Source evidence | Approved revision | Affected stages |
29
+ | --- | --- | --- | --- | --- |
30
+
31
+ Record existing instructions and decisions without requesting them again.
32
+ Silence is not a mockup selection or release authorization.
33
+
34
+ ## Blockers and invalidation
35
+
36
+ | Check or claim | Failure/limit | Dependent work paused | Owner | Settling action | Recheck evidence |
37
+ | --- | --- | --- | --- | --- | --- |
38
+
39
+ Invalidate affected receipts after changed scope, sources, artifact or expiry.
40
+ Preserve the previous result as history rather than silently replacing it.
41
+
42
+ ## Release and follow-through
43
+
44
+ Record exact target/authority, reviewed and final artifact identities, independent family
45
+ proof, ROUND 1 disposition, preview/live receipt, rollback, operations document and watcher.
46
+ Keep missing field data, cache observations and receiving-property access UNVERIFIED with owners.
47
+ Record pending memory separately from saved/read-back project artifacts.
@@ -0,0 +1,72 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "description": "Synthetic starting values for mapping primitive and semantic roles, not an approved brand.",
4
+ "dateRule": "playbooks/research-and-memory.md#ask-date-rule",
5
+ "approvalStatus": "UNAPPROVED",
6
+ "provenance": {
7
+ "synthetic-example": {
8
+ "kind": "invented",
9
+ "source": "This contract only",
10
+ "rightsStatus": "No external asset supplied",
11
+ "approvalEvidence": null
12
+ }
13
+ },
14
+ "primitive": {
15
+ "paper": {
16
+ "value": "#FAF7EF",
17
+ "sourceId": "synthetic-example"
18
+ },
19
+ "ink": {
20
+ "value": "#202823",
21
+ "sourceId": "synthetic-example"
22
+ },
23
+ "leaf": {
24
+ "value": "#276443",
25
+ "sourceId": "synthetic-example"
26
+ },
27
+ "stone": {
28
+ "value": "#616A64",
29
+ "sourceId": "synthetic-example"
30
+ }
31
+ },
32
+ "semantic": {
33
+ "canvas": "paper",
34
+ "surface": "paper",
35
+ "text": "ink",
36
+ "mutedText": "stone",
37
+ "accent": "leaf",
38
+ "actionBackground": "leaf",
39
+ "actionText": "paper",
40
+ "border": "stone",
41
+ "focus": "ink",
42
+ "error": null,
43
+ "success": "leaf"
44
+ },
45
+ "typography": {
46
+ "body": {
47
+ "family": "system-ui, sans-serif",
48
+ "weight": 400
49
+ },
50
+ "heading": {
51
+ "family": "system-ui, sans-serif",
52
+ "weight": 700
53
+ },
54
+ "externalFontRights": "No font files supplied"
55
+ },
56
+ "spacing": {
57
+ "unit": "rem",
58
+ "small": 0.5,
59
+ "medium": 1,
60
+ "large": 2
61
+ },
62
+ "motion": {
63
+ "reducedMotion": "Remove nonessential movement; retain state information"
64
+ },
65
+ "contrast": {
66
+ "status": "UNVERIFIED",
67
+ "method": null,
68
+ "ratios": [],
69
+ "requiredCheck": "checklists/accessibility.md AX01"
70
+ },
71
+ "usage": "Assign missing roles and compute actual rendered contrast before approval. Do not claim the synthetic values are accessible or selected."
72
+ }
package/src/agents.mjs ADDED
@@ -0,0 +1,7 @@
1
+ // Planned native generation. Unknown host schemas must fail closed.
2
+ // Required future implementation: full canonical bodies, reference rebasing,
3
+ // observed tool mapping, read-only Reviewer, research-first setup acknowledgements,
4
+ // Hermes declarative/session branch evidence and separate activation receipts.
5
+ export function generateAgents(_request) {
6
+ throw Error('UNAVAILABLE: native agent generation is planned; use the full team bundle.');
7
+ }
package/src/bundle.mjs ADDED
@@ -0,0 +1,106 @@
1
+ import { readFileSync, readdirSync, lstatSync, existsSync } from 'node:fs';
2
+ import { resolve, relative, dirname, sep, posix } from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+
5
+ export const CORE = 'skills/website-build-skill';
6
+ export const ARCHIVE = 'archive/graphics-design-original.md';
7
+ export const ARCHIVE_SHA = 'd985b6fcaf5c0de9c6d60f2511bb9fa5728ad0d2d543a64a673962647ea5388a';
8
+ export const digest = bytes => createHash('sha256').update(bytes).digest('hex');
9
+
10
+ // Resolve only existing, regular, repository-owned inputs. Never follow a symlink.
11
+ export function owned(root, name, mustExist = true) {
12
+ if (typeof name !== 'string' || !name || name.includes('\\') || name.startsWith('/') || /^[A-Za-z]:/.test(name)) throw Error('invalid owned path');
13
+ const base = resolve(root), full = resolve(base, name);
14
+ if (full !== base && !full.startsWith(base + sep)) throw Error('path escapes repository');
15
+ let part = base;
16
+ if (lstatSync(base).isSymbolicLink()) throw Error('symlink root');
17
+ for (const segment of relative(base, full).split(sep).filter(Boolean)) {
18
+ part = resolve(part, segment);
19
+ // existsSync follows the link, so it is FALSE for a dangling symlink and the symlink
20
+ // check below was skipped. An ordinary write then follows that link outside the
21
+ // repository. lstat never follows, so it sees the link whether or not the target exists.
22
+ let entry = null;
23
+ try { entry = lstatSync(part); } catch { entry = null; }
24
+ if (entry) {
25
+ if (entry.isSymbolicLink()) throw Error('symlink path');
26
+ } else if (mustExist) throw Error(`missing path: ${name}`);
27
+ }
28
+ return full;
29
+ }
30
+
31
+ export function files(root, directory = '') {
32
+ const out = [];
33
+ for (const item of readdirSync(owned(root, directory || '.'), { withFileTypes: true }).sort((a,b) => a.name.localeCompare(b.name, 'en'))) {
34
+ if (['.git', 'node_modules', '.test-work', '.build-local'].includes(item.name)) continue;
35
+ // Local, gitignored working files are not part of the published tree. Reviewing them
36
+ // would fail the gate on notes that never ship.
37
+ if (/^AUDIT_BRIEF.*\.md$/.test(item.name) || /\.(plan|brief)\.md$/.test(item.name) || item.name === 'notes-local') continue;
38
+ const name = directory ? `${directory}/${item.name}` : item.name;
39
+ if (item.isSymbolicLink()) throw Error(`symlink in public tree: ${name}`);
40
+ if (item.isDirectory()) out.push(...files(root, name));
41
+ else if (item.isFile()) out.push(name);
42
+ }
43
+ return out;
44
+ }
45
+
46
+ export const read = (root, name) => readFileSync(owned(root, name), 'utf8');
47
+ export const manifest = root => JSON.parse(read(root, `${CORE}/manifest.json`));
48
+
49
+ // Rebase rendered links; bare canonical filenames retain an explicit source-base label.
50
+ export function rebase(text, source, target) {
51
+ const rewrite = url => {
52
+ if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(url)) return url;
53
+ const [file, fragment] = url.split('#');
54
+ const destination = file ? posix.normalize(posix.join(posix.dirname(source), file)) : source;
55
+ return posix.relative(posix.dirname(target), destination) + (fragment === undefined ? '' : '#' + fragment);
56
+ };
57
+ let fenced = false;
58
+ return text.split('\n').map(line => {
59
+ if (/^\s*(`{3,}|~{3,})/.test(line)) { fenced = !fenced; return line; }
60
+ if (fenced) return line;
61
+ return line.replace(/(!?\[[^\]\n]*\]\()([^\s)]+)([^)]*\))/g, (_, a, b, c) => a + rewrite(b) + c)
62
+ .replace(/^(\s*\[[^\]]+\]:\s*)(\S+)/, (_, a, b) => a + rewrite(b))
63
+ .replace(/((?:href|src)=["'])([^"']+)(["'])/g, (_, a, b, c) => a + rewrite(b) + c);
64
+ }).join('\n');
65
+ }
66
+
67
+ export function generated(root) {
68
+ const m = manifest(root), output = {};
69
+ const byPath = new Map(m.assets.map(a => [a.path, a]));
70
+ for (const [name, members] of Object.entries(m.bundles)) {
71
+ if (!/^(method|prompts|playbooks|checklists|team)$/.test(name)) throw Error('unknown bundle');
72
+ const target = `docs/bundles/${name}.md`;
73
+ let text = `# Website build: ${name}\n\nGenerated from the canonical manifest. Do not edit this file.\n\n`;
74
+ text += 'Bare skill paths in the included instructions resolve from the canonical skill directory.\n';
75
+ text += 'For uploads, locate the named source section in these bundles; request an attachment when absent.\n';
76
+ text += 'Project paths resolve only in the authorized website workspace. Native loading is UNVERIFIED.\n\n## Contents\n\n';
77
+ for (const member of members) {
78
+ const asset = byPath.get(member);
79
+ if (!asset?.active || member === ARCHIVE) throw Error('inactive asset requested in active bundle');
80
+ text += `- [${member}](../../${CORE}/${member})\n`;
81
+ }
82
+ for (const member of members) {
83
+ const source = `${CORE}/${member}`;
84
+ text += `\n<!-- BEGIN SOURCE: ${member} -->\n\n## Source: ${member}\n\n`;
85
+ const body = read(root, source);
86
+ text += member.endsWith('.md') ? rebase(body, source, target) : `\n\`\`\`text\n${body.trimEnd()}\n\`\`\`\n`;
87
+ text += `\n<!-- END SOURCE: ${member} -->\n`;
88
+ }
89
+ if (name === 'team') {
90
+ const source = 'adapters/grok.md';
91
+ const manual = read(root, source).split('## Manual fallback\n')[1];
92
+ if (!manual) throw Error('missing manual fallback');
93
+ text += '\n' + rebase('## Manual fallback\n' + manual, source, target);
94
+ text += '\n## Artifact ownership roster\n\n```yaml\n' + read(root, 'adapters/team.yaml').trimEnd() + '\n```\n';
95
+ }
96
+ output[target] = text.trimEnd() + '\n';
97
+ }
98
+ let index = '# website-build-skill\n\n> Research-first website instructions, role boundaries and evidence gates.\n\n';
99
+ index += 'Generated from skills/website-build-skill/manifest.json. Published at https://github.com/aunysillyme/website-build-skill. Host loading is UNVERIFIED.\n\n## Active sources\n\n';
100
+ for (const asset of m.assets.filter(a => a.active)) index += `- [${asset.path}](${CORE}/${asset.path}): ${asset.kind}\n`;
101
+ index += '\n## Bundles\n\n';
102
+ for (const name of Object.keys(m.bundles)) index += `- [${name}](docs/bundles/${name}.md): generated upload packet\n`;
103
+ index += `\n## Inactive provenance\n\n- [Graphics original](${CORE}/${ARCHIVE}): archive only; never execute\n`;
104
+ output['llms.txt'] = index;
105
+ return output;
106
+ }
@@ -0,0 +1,12 @@
1
+ // Candidate destinations, not a verified host support registry.
2
+ export const targets = Object.freeze({
3
+ 'claude-code': '.claude/skills/website-build-skill',
4
+ codex: '.agents/skills/website-build-skill',
5
+ hermes: '.agents/skills/website-build-skill',
6
+ antigravity: '.website-build-skill',
7
+ grok: '.grok/skills/website-build-skill',
8
+ agents: '.website-build-skill',
9
+ portable: '.website-build-skill',
10
+ chatgpt: 'website-build-skill-upload',
11
+ });
12
+ export const status = 'UNVERIFIED: host activation; file installer implemented';
package/src/choice.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import { CORE, read } from './bundle.mjs';
2
+
3
+ // Content accessor only. Interactive installation is planned and unavailable.
4
+ export function installQuestion(root) {
5
+ return read(root, `${CORE}/templates/install-choice.txt`);
6
+ }
package/src/cli.mjs ADDED
@@ -0,0 +1,96 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { install, packageInfo, choiceText } from './install.mjs';
3
+ import { targets } from './catalog.mjs';
4
+ import { checkLibrary } from './library-check.mjs';
5
+
6
+ const help = `website-build-skill ${packageInfo.version}
7
+ check-library <site-work-dir> (read-only receipt validation)
8
+ --solo | --team --target ${Object.keys(targets).join('|')}
9
+ --dir <path> --scope project|user --output-dir <path>
10
+ --yes --dry-run --receipt <path> --uninstall --help --version
11
+ --bundle-dir <path> (Hermes TEAM only)
12
+ Headless installs require explicit mode, target and --dir.
13
+ Activation remains UNVERIFIED.`;
14
+
15
+ export function parseArgs(args) {
16
+ const request = {};
17
+ const values = { '--target': 'target', '--dir': 'dir', '--scope': 'scope', '--output-dir': 'outputDir', '--receipt': 'receipt', '--bundle-dir': 'bundleDir' };
18
+ const switches = { '--yes': 'yes', '--dry-run': 'dryRun', '--uninstall': 'uninstall', '--help': 'help', '--version': 'version' };
19
+ const seen = new Set();
20
+ for (let i = 0; i < args.length; i++) {
21
+ const flag = args[i];
22
+ if (seen.has(flag)) throw Error(`Duplicate flag: ${flag}`);
23
+ seen.add(flag);
24
+ if (flag === '--solo' || flag === '--team') {
25
+ if (request.mode) throw Error('--solo and --team are mutually exclusive');
26
+ request.mode = flag.slice(2);
27
+ } else if (Object.hasOwn(values, flag)) {
28
+ const value = args[++i];
29
+ if (!value || value.startsWith('--')) throw Error(`Missing value for ${flag}`);
30
+ request[values[flag]] = value;
31
+ } else if (Object.hasOwn(switches, flag)) request[switches[flag]] = true;
32
+ else throw Error(`Unknown flag: ${flag}`);
33
+ }
34
+ if (request.target !== undefined && !Object.hasOwn(targets, request.target)) throw Error('Unknown target');
35
+ if (request.scope !== undefined && !['project', 'user'].includes(request.scope)) throw Error('Invalid scope');
36
+ if (request.bundleDir !== undefined && (request.mode !== 'team' || request.target !== 'hermes')) throw Error('--bundle-dir requires --team --target hermes');
37
+ return request;
38
+ }
39
+
40
+ export async function main(args = process.argv.slice(2), { input = process.stdin, output = process.stdout } = {}) {
41
+ let request;
42
+ try {
43
+ if (args[0] === 'check-library') {
44
+ if (args.length !== 2 || !args[1] || args[1].startsWith('--')) throw Error('Usage: website-build-skill check-library <site-work-dir>');
45
+ return checkLibrary(args[1]);
46
+ }
47
+ request = parseArgs(args);
48
+ if (request.help) return { code: 0, message: help };
49
+ if (request.version) return { code: 0, message: packageInfo.version };
50
+ if (request.uninstall) {
51
+ if (!request.receipt && (!request.target || !request.dir)) throw Error('Uninstall requires --receipt or --target and --dir');
52
+ return install(request);
53
+ }
54
+ if (request.yes && (!request.mode || !request.target || !request.dir)) throw Error('--yes requires mode, target and --dir; it never prompts');
55
+ if (!request.mode || !request.target || (!request.yes && !request.outputDir)) {
56
+ const reader = createInterface({ input, crlfDelay: Infinity });
57
+ const answers = reader[Symbol.asyncIterator]();
58
+ const ask = async text => {
59
+ output.write(text);
60
+ const answer = await answers.next();
61
+ if (answer.done) throw Error('Invalid input: stdin ended before an answer (EOF)');
62
+ // Paths and export destinations can be case-sensitive. Normalize choices only.
63
+ return answer.value.trim();
64
+ };
65
+ try {
66
+ if (!request.mode) {
67
+ const answer = (await ask(choiceText() + '\nWork mode (1 or 2):\n')).toLowerCase();
68
+ request.mode = ({ '1': 'solo', solo: 'solo', '2': 'team', team: 'team' })[answer];
69
+ if (!request.mode) throw Error('Invalid mode answer');
70
+ }
71
+ if (!request.target) {
72
+ request.target = (await ask(`Target (${Object.keys(targets).join(', ')}):\n`)).toLowerCase();
73
+ if (!Object.hasOwn(targets, request.target)) throw Error('Invalid target answer');
74
+ }
75
+ if (!request.yes && !request.outputDir) {
76
+ const answer = await ask('Save location: 1 Obsidian, 2 local folder, 3 Notion export, 4 another local destination:\n');
77
+ const kind = ({ '1': 'obsidian', '2': 'folder', '3': 'notion', '4': 'other' })[answer];
78
+ if (!kind) throw Error('Invalid save-location answer');
79
+ const label = kind === 'obsidian' ? 'Existing folder inside your Obsidian vault'
80
+ : kind === 'notion' ? 'Existing local working directory (authoritative copy)'
81
+ : 'Existing output directory';
82
+ const path = await ask(`${label}${kind === 'folder' ? ' (Enter uses the website project folder)' : ''}:\n`);
83
+ if (!path && kind !== 'folder') throw Error('An existing output directory is required');
84
+ request.outputDir = path || request.dir || process.cwd();
85
+ request.outputStorage = { kind };
86
+ if (kind === 'notion') {
87
+ const exportTarget = await ask('Notion export destination (page URL or name; no upload is performed):\n');
88
+ if (!exportTarget) throw Error('Notion export destination is required');
89
+ request.outputStorage.exportTarget = exportTarget;
90
+ }
91
+ }
92
+ } finally { reader.close(); }
93
+ }
94
+ return install(request);
95
+ } catch (error) { return { code: 2, message: error.message }; }
96
+ }