Compare commits
4
Commits
e33d98df12
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b43e4c825e | ||
|
|
bb689a1bc2 | ||
|
|
addbf37fc5 | ||
|
|
92218d528c |
@@ -75,7 +75,11 @@ main() {
|
|||||||
local local_mcp_overrides_file="$canonical_home/.local/mcp.local.jsonc"
|
local local_mcp_overrides_file="$canonical_home/.local/mcp.local.jsonc"
|
||||||
local session_start_hook_file="$canonical_home/resources/hooks/session-audit.json"
|
local session_start_hook_file="$canonical_home/resources/hooks/session-audit.json"
|
||||||
local session_start_hook_script="$canonical_home/resources/scripts/report-hook-event.sh"
|
local session_start_hook_script="$canonical_home/resources/scripts/report-hook-event.sh"
|
||||||
|
local session_end_hook_script="$canonical_home/resources/scripts/report-hook-event-end.sh"
|
||||||
local port_registry_script="$canonical_home/resources/scripts/update-port-registry.mjs"
|
local port_registry_script="$canonical_home/resources/scripts/update-port-registry.mjs"
|
||||||
|
local cleanup_history_script="$canonical_home/resources/scripts/cleanup-copilot-history.sh"
|
||||||
|
local restore_history_script="$canonical_home/resources/scripts/restore-copilot-history.sh"
|
||||||
|
local purge_history_script="$canonical_home/resources/scripts/purge-copilot-history-quarantine.sh"
|
||||||
|
|
||||||
check_path "repo root" "$repo_root"
|
check_path "repo root" "$repo_root"
|
||||||
check_path "canonical home" "$canonical_home"
|
check_path "canonical home" "$canonical_home"
|
||||||
@@ -86,8 +90,16 @@ main() {
|
|||||||
check_path "session start hook" "$session_start_hook_file"
|
check_path "session start hook" "$session_start_hook_file"
|
||||||
check_path "session start hook script" "$session_start_hook_script"
|
check_path "session start hook script" "$session_start_hook_script"
|
||||||
check_readable_file "session start hook script" "$session_start_hook_script"
|
check_readable_file "session start hook script" "$session_start_hook_script"
|
||||||
|
check_path "session end hook script" "$session_end_hook_script"
|
||||||
|
check_readable_file "session end hook script" "$session_end_hook_script"
|
||||||
check_path "port registry updater script" "$port_registry_script"
|
check_path "port registry updater script" "$port_registry_script"
|
||||||
check_readable_file "port registry updater script" "$port_registry_script"
|
check_readable_file "port registry updater script" "$port_registry_script"
|
||||||
|
check_path "history cleanup script" "$cleanup_history_script"
|
||||||
|
check_readable_file "history cleanup script" "$cleanup_history_script"
|
||||||
|
check_path "history restore script" "$restore_history_script"
|
||||||
|
check_readable_file "history restore script" "$restore_history_script"
|
||||||
|
check_path "history purge script" "$purge_history_script"
|
||||||
|
check_readable_file "history purge script" "$purge_history_script"
|
||||||
check_command "session start hook shell" "bash"
|
check_command "session start hook shell" "bash"
|
||||||
check_command "port registry updater runtime" "node"
|
check_command "port registry updater runtime" "node"
|
||||||
check_path "prompts link" "$vscode_user_dir/prompts"
|
check_path "prompts link" "$vscode_user_dir/prompts"
|
||||||
|
|||||||
@@ -7,6 +7,14 @@
|
|||||||
"linux": "bash ~/.copilot-resources/resources/scripts/report-hook-event.sh",
|
"linux": "bash ~/.copilot-resources/resources/scripts/report-hook-event.sh",
|
||||||
"windows": "powershell -NoProfile -File \"$HOME/.copilot-resources/resources/scripts/report-hook-event.ps1\""
|
"windows": "powershell -NoProfile -File \"$HOME/.copilot-resources/resources/scripts/report-hook-event.ps1\""
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"SessionStop": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"osx": "bash ~/.copilot-resources/resources/scripts/report-hook-event-end.sh",
|
||||||
|
"linux": "bash ~/.copilot-resources/resources/scripts/report-hook-event-end.sh",
|
||||||
|
"windows": "powershell -NoProfile -File \"$HOME/.copilot-resources/resources/scripts/report-hook-event-end.ps1\""
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: "maintain-copilot-history"
|
||||||
|
description: "Quarantine, restore, or purge Copilot chat and session history artifacts using the shared maintenance scripts."
|
||||||
|
agent: "agent"
|
||||||
|
tools: [read, search, execute]
|
||||||
|
argument-hint: "action=<cleanup|restore|purge> audit-dir=<path> [execute=true] [before=<iso>] [keep-latest=<n>] [filter=<text>] [path=<absolute-path>]"
|
||||||
|
---
|
||||||
|
|
||||||
|
Manage Copilot history artifacts using the shared quarantine-based maintenance
|
||||||
|
scripts.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Default to dry-run for every action unless the user explicitly asks to apply.
|
||||||
|
- For `cleanup`: use `resources/scripts/cleanup-copilot-history.sh`; require
|
||||||
|
`--audit-dir` to set the completion boundary.
|
||||||
|
- For `restore`: use `resources/scripts/restore-copilot-history.sh`; prefer
|
||||||
|
`--filter` or `--path` to scope the restore rather than restoring everything.
|
||||||
|
- For `purge`: use `resources/scripts/purge-copilot-history-quarantine.sh`;
|
||||||
|
always include `--keep-latest 1` unless the user explicitly overrides it.
|
||||||
|
- Never pass `--execute` without confirming the planned changes with the user
|
||||||
|
first; show the dry-run output and ask for confirmation.
|
||||||
|
- Summarize what was quarantined, restored, or purged after each action.
|
||||||
|
|
||||||
|
Protected paths that must never be targeted:
|
||||||
|
|
||||||
|
- `~/.copilot-resources`
|
||||||
|
- `~/.copilot/*` symlink targets
|
||||||
|
- `~/.copilot-resources-state/publish-log.tsv`
|
||||||
|
- `~/.copilot-resources-state/project-ports-registry.json`
|
||||||
|
- Repository source under `resources/`, `docs/`, and `templates/`
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
name: "scaffold-vue3-vite"
|
||||||
|
description: "Scaffold a new Vue 3 + Vite project with vue-router, atomic component directories, and SCSS styling foundations."
|
||||||
|
agent: "agent"
|
||||||
|
tools: [read, search, execute]
|
||||||
|
argument-hint: "project-root=<path> app-name=<name> mode=<dry-run|apply> port=<dev-server-port>"
|
||||||
|
---
|
||||||
|
|
||||||
|
Scaffold a new Vue 3 + Vite project using the shared scaffold script.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Prefer `resources/scripts/scaffold-vue3-vite.sh` over manually creating files.
|
||||||
|
- Default to `--mode dry-run` unless the user explicitly asks for apply mode.
|
||||||
|
- Resolve `--project-root` and `--app-name` before running; ask if missing.
|
||||||
|
- After `--mode apply` completes, instruct the user to run `npm install` then
|
||||||
|
`npm run dev` to verify the dev server starts cleanly.
|
||||||
|
- Summarize the files created and the next steps.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$StateDir = if ($env:COPILOT_RESOURCES_STATE_DIR) { $env:COPILOT_RESOURCES_STATE_DIR } else { Join-Path $HOME '.copilot-resources-state' }
|
||||||
|
New-Item -ItemType Directory -Path $StateDir -Force | Out-Null
|
||||||
|
|
||||||
|
$Payload = [Console]::In.ReadToEnd()
|
||||||
|
$Payload | Add-Content -LiteralPath (Join-Path $StateDir 'hook-events.log')
|
||||||
|
|
||||||
|
# Append a TSV row to session-events.tsv for duration tracking.
|
||||||
|
"$(Get-Date -AsUTC -Format o)`tSessionEnd" | Add-Content -LiteralPath (Join-Path $StateDir 'session-events.tsv')
|
||||||
|
|
||||||
|
'{"continue": true}'
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
state_dir="${COPILOT_RESOURCES_STATE_DIR:-$HOME/.copilot-resources-state}"
|
||||||
|
mkdir -p -- "$state_dir"
|
||||||
|
|
||||||
|
event_payload="$(cat)"
|
||||||
|
printf '%s\n' "$event_payload" >> "$state_dir/hook-events.log"
|
||||||
|
|
||||||
|
# Append a TSV row to session-events.tsv for duration tracking.
|
||||||
|
printf '%s\tSessionEnd\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$state_dir/session-events.tsv"
|
||||||
|
|
||||||
|
printf '{"continue": true}\n'
|
||||||
@@ -6,6 +6,9 @@ New-Item -ItemType Directory -Path $StateDir -Force | Out-Null
|
|||||||
$Payload = [Console]::In.ReadToEnd()
|
$Payload = [Console]::In.ReadToEnd()
|
||||||
$Payload | Add-Content -LiteralPath (Join-Path $StateDir 'hook-events.log')
|
$Payload | Add-Content -LiteralPath (Join-Path $StateDir 'hook-events.log')
|
||||||
|
|
||||||
|
# Append a TSV row to session-events.tsv for duration tracking.
|
||||||
|
"$(Get-Date -AsUTC -Format o)`tSessionStart" | Add-Content -LiteralPath (Join-Path $StateDir 'session-events.tsv')
|
||||||
|
|
||||||
$PortRegistryScript = Join-Path $PSScriptRoot 'update-port-registry.mjs'
|
$PortRegistryScript = Join-Path $PSScriptRoot 'update-port-registry.mjs'
|
||||||
$NodeCommand = Get-Command node -ErrorAction SilentlyContinue
|
$NodeCommand = Get-Command node -ErrorAction SilentlyContinue
|
||||||
if ($NodeCommand -and (Test-Path -LiteralPath $PortRegistryScript)) {
|
if ($NodeCommand -and (Test-Path -LiteralPath $PortRegistryScript)) {
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ mkdir -p -- "$state_dir"
|
|||||||
event_payload="$(cat)"
|
event_payload="$(cat)"
|
||||||
printf '%s\n' "$event_payload" >> "$state_dir/hook-events.log"
|
printf '%s\n' "$event_payload" >> "$state_dir/hook-events.log"
|
||||||
|
|
||||||
|
# Append a TSV row to session-events.tsv for duration tracking.
|
||||||
|
printf '%s\tSessionStart\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$state_dir/session-events.tsv"
|
||||||
|
|
||||||
if command -v node >/dev/null 2>&1 && [[ -f "$port_registry_script" ]]; then
|
if command -v node >/dev/null 2>&1 && [[ -f "$port_registry_script" ]]; then
|
||||||
if ! printf '%s' "$event_payload" | node "$port_registry_script"; then
|
if ! printf '%s' "$event_payload" | node "$port_registry_script"; then
|
||||||
printf '%s update-port-registry failed\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$state_dir/project-ports-errors.log"
|
printf '%s update-port-registry failed\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$state_dir/project-ports-errors.log"
|
||||||
|
|||||||
@@ -0,0 +1,325 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
const DEFAULTS = {
|
||||||
|
mode: 'dry-run',
|
||||||
|
port: '5173',
|
||||||
|
};
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
console.error(`Usage: resources/scripts/scaffold-vue3-vite.sh [options]
|
||||||
|
|
||||||
|
Required:
|
||||||
|
--project-root <path> Target project path.
|
||||||
|
--app-name <name> Application name used in package.json and index.html.
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
--mode <dry-run|apply> Default: dry-run
|
||||||
|
--port <number> Vite dev server port. Default: 5173
|
||||||
|
--force Overwrite existing files.
|
||||||
|
--help Show this help text.
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const options = {
|
||||||
|
mode: DEFAULTS.mode,
|
||||||
|
port: DEFAULTS.port,
|
||||||
|
force: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let index = 0; index < argv.length; index += 1) {
|
||||||
|
const arg = argv[index];
|
||||||
|
|
||||||
|
if (arg === '--help') {
|
||||||
|
usage();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--project-root') {
|
||||||
|
options.projectRoot = argv[index + 1];
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--app-name') {
|
||||||
|
options.appName = argv[index + 1];
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--mode') {
|
||||||
|
options.mode = argv[index + 1];
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--port') {
|
||||||
|
options.port = argv[index + 1];
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--force') {
|
||||||
|
options.force = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown argument: ${arg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.projectRoot) throw new Error('--project-root is required.');
|
||||||
|
if (!options.appName) throw new Error('--app-name is required.');
|
||||||
|
if (!['dry-run', 'apply'].includes(options.mode)) {
|
||||||
|
throw new Error('--mode must be dry-run or apply.');
|
||||||
|
}
|
||||||
|
|
||||||
|
options.projectRoot = path.resolve(options.projectRoot);
|
||||||
|
options.slug = slugify(options.appName);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function slugify(value) {
|
||||||
|
return String(value)
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFileWithGuard(filePath, content, { mode, force }) {
|
||||||
|
const exists = fs.existsSync(filePath);
|
||||||
|
if (exists && !force) return { action: 'skipped', filePath };
|
||||||
|
const action = exists ? 'updated' : 'created';
|
||||||
|
if (mode === 'apply') {
|
||||||
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
fs.writeFileSync(filePath, content, 'utf8');
|
||||||
|
}
|
||||||
|
return { action, filePath };
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchDir(dirPath, { mode }) {
|
||||||
|
const keepPath = path.join(dirPath, '.gitkeep');
|
||||||
|
const exists = fs.existsSync(keepPath);
|
||||||
|
if (mode === 'apply' && !exists) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
fs.writeFileSync(keepPath, '', 'utf8');
|
||||||
|
}
|
||||||
|
return { action: exists ? 'skipped' : 'created', filePath: keepPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Renderers ---
|
||||||
|
|
||||||
|
function renderPackageJson({ slug, appName, port }) {
|
||||||
|
return JSON.stringify(
|
||||||
|
{
|
||||||
|
name: slug,
|
||||||
|
version: '0.1.0',
|
||||||
|
private: true,
|
||||||
|
type: 'module',
|
||||||
|
scripts: {
|
||||||
|
dev: `vite --port ${port}`,
|
||||||
|
build: 'vite build',
|
||||||
|
preview: 'vite preview',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
vue: '^3.4.0',
|
||||||
|
'vue-router': '^4.3.0',
|
||||||
|
},
|
||||||
|
devDependencies: {
|
||||||
|
'@vitejs/plugin-vue': '^5.0.0',
|
||||||
|
sass: '^1.77.0',
|
||||||
|
vite: '^5.3.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
) + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderViteConfig({ port }) {
|
||||||
|
return `import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: ${port},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderIndexHtml({ appName }) {
|
||||||
|
return `<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>${appName}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMainJs() {
|
||||||
|
return `import { createApp } from 'vue';
|
||||||
|
import App from './App.vue';
|
||||||
|
import router from './router/index.js';
|
||||||
|
import '@/assets/styles/global-reset.scss';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
app.use(router);
|
||||||
|
app.mount('#app');
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAppVue() {
|
||||||
|
return `<template>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
</script>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRouter() {
|
||||||
|
return `import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
import HomePage from '@/pages/HomePage.vue';
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: HomePage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHomePage({ appName }) {
|
||||||
|
return `<template>
|
||||||
|
<main>
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const title = '${appName}';
|
||||||
|
</script>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGlobalTokens() {
|
||||||
|
return `// Global design tokens.
|
||||||
|
// Add project-specific custom properties and SCSS variables here.
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-bg: #ffffff;
|
||||||
|
--color-text: #1a1a1a;
|
||||||
|
--color-primary: #5865f2;
|
||||||
|
--font-base: system-ui, sans-serif;
|
||||||
|
--space-1: 0.25rem;
|
||||||
|
--space-2: 0.5rem;
|
||||||
|
--space-4: 1rem;
|
||||||
|
--space-8: 2rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGlobalReset() {
|
||||||
|
return `@use 'global-tokens' as *;
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-family: var(--font-base);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGitIgnore() {
|
||||||
|
return `node_modules
|
||||||
|
dist
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.local
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main ---
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const options = parseArgs(process.argv.slice(2));
|
||||||
|
|
||||||
|
if (!fs.existsSync(options.projectRoot)) {
|
||||||
|
throw new Error(`Project root does not exist: ${options.projectRoot}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const src = (rel) => path.join(options.projectRoot, rel);
|
||||||
|
|
||||||
|
const writes = [
|
||||||
|
writeFileWithGuard(src('package.json'), renderPackageJson(options), options),
|
||||||
|
writeFileWithGuard(src('vite.config.js'), renderViteConfig(options), options),
|
||||||
|
writeFileWithGuard(src('index.html'), renderIndexHtml(options), options),
|
||||||
|
writeFileWithGuard(src('.gitignore'), renderGitIgnore(), options),
|
||||||
|
writeFileWithGuard(src('src/main.js'), renderMainJs(), options),
|
||||||
|
writeFileWithGuard(src('src/App.vue'), renderAppVue(), options),
|
||||||
|
writeFileWithGuard(src('src/router/index.js'), renderRouter(), options),
|
||||||
|
writeFileWithGuard(src('src/pages/HomePage.vue'), renderHomePage(options), options),
|
||||||
|
writeFileWithGuard(src('src/assets/styles/global-tokens.scss'), renderGlobalTokens(), options),
|
||||||
|
writeFileWithGuard(src('src/assets/styles/global-reset.scss'), renderGlobalReset(), options),
|
||||||
|
touchDir(src('src/components/atoms'), options),
|
||||||
|
touchDir(src('src/components/molecules'), options),
|
||||||
|
touchDir(src('src/components/organisms'), options),
|
||||||
|
touchDir(src('src/components/layouts'), options),
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(`Mode: ${options.mode}`);
|
||||||
|
console.log(`Project root: ${options.projectRoot}`);
|
||||||
|
console.log(`App name: ${options.appName}`);
|
||||||
|
console.log(`Dev port: ${options.port}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
for (const entry of writes) {
|
||||||
|
console.log(`${entry.action.toUpperCase()}: ${entry.filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.mode === 'dry-run') {
|
||||||
|
console.log('');
|
||||||
|
console.log('No files were written. Re-run with --mode apply to persist changes.');
|
||||||
|
} else {
|
||||||
|
console.log('');
|
||||||
|
console.log('Next: cd into the project root and run npm install, then npm run dev.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||||
|
|
||||||
|
if command -v node >/dev/null 2>&1; then
|
||||||
|
node_bin="$(command -v node)"
|
||||||
|
elif command -v nodejs >/dev/null 2>&1; then
|
||||||
|
node_bin="$(command -v nodejs)"
|
||||||
|
else
|
||||||
|
printf 'Node.js is required to scaffold a Vue 3 + Vite project.\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$node_bin" "$script_dir/scaffold-vue3-vite.mjs" "$@"
|
||||||
Executable
+125
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sync persistent Copilot state files between machines using rsync over SSH.
|
||||||
|
# Only publish-log.tsv and project-ports-registry.json are synced; machine-
|
||||||
|
# local files (hook-events.log, session-events.tsv) and the quarantine
|
||||||
|
# directory are always excluded.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# sync-copilot-state.sh --mode push|pull --remote user@host [options]
|
||||||
|
#
|
||||||
|
# Configuration:
|
||||||
|
# Set COPILOT_STATE_SYNC_REMOTE in environment or in
|
||||||
|
# ~/.copilot-resources-state/sync.conf to avoid passing --remote every time.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
state_dir="${COPILOT_RESOURCES_STATE_DIR:-$HOME/.copilot-resources-state}"
|
||||||
|
sync_conf="$state_dir/sync.conf"
|
||||||
|
|
||||||
|
dry_run="true"
|
||||||
|
mode=""
|
||||||
|
remote=""
|
||||||
|
ssh_port="22"
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
cat >&2 <<EOF
|
||||||
|
Usage: sync-copilot-state.sh --mode <push|pull> [options]
|
||||||
|
|
||||||
|
Required:
|
||||||
|
--mode <push|pull> push: send local state to remote.
|
||||||
|
pull: fetch remote state to local.
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
--remote <user@host> Remote SSH target. Overrides COPILOT_STATE_SYNC_REMOTE
|
||||||
|
and sync.conf.
|
||||||
|
--remote-path <path> Remote state directory. Default: ~/.copilot-resources-state
|
||||||
|
--ssh-port <port> SSH port. Default: 22
|
||||||
|
--execute Apply the sync. Default is dry-run.
|
||||||
|
--help Show this help text.
|
||||||
|
|
||||||
|
Configuration file:
|
||||||
|
$sync_conf
|
||||||
|
Supported keys (shell variable syntax):
|
||||||
|
COPILOT_STATE_SYNC_REMOTE=user@host
|
||||||
|
COPILOT_STATE_SYNC_REMOTE_PATH=/custom/path
|
||||||
|
COPILOT_STATE_SYNC_SSH_PORT=22
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load optional config file before parsing args so args take precedence.
|
||||||
|
if [[ -f "$sync_conf" ]]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$sync_conf"
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote="${COPILOT_STATE_SYNC_REMOTE:-}"
|
||||||
|
remote_path="${COPILOT_STATE_SYNC_REMOTE_PATH:-.copilot-resources-state}"
|
||||||
|
ssh_port="${COPILOT_STATE_SYNC_SSH_PORT:-22}"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--mode)
|
||||||
|
mode="$2"; shift 2 ;;
|
||||||
|
--remote)
|
||||||
|
remote="$2"; shift 2 ;;
|
||||||
|
--remote-path)
|
||||||
|
remote_path="$2"; shift 2 ;;
|
||||||
|
--ssh-port)
|
||||||
|
ssh_port="$2"; shift 2 ;;
|
||||||
|
--execute)
|
||||||
|
dry_run="false"; shift ;;
|
||||||
|
--help)
|
||||||
|
usage; exit 0 ;;
|
||||||
|
*)
|
||||||
|
printf 'Unknown argument: %s\n' "$1" >&2; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$mode" ]]; then
|
||||||
|
printf '--mode push|pull is required.\n' >&2; usage; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$mode" != "push" && "$mode" != "pull" ]]; then
|
||||||
|
printf '--mode must be push or pull.\n' >&2; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$remote" ]]; then
|
||||||
|
printf 'No remote configured. Pass --remote user@host or set COPILOT_STATE_SYNC_REMOTE.\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p -- "$state_dir"
|
||||||
|
|
||||||
|
# Files to sync: only durable shared state; exclude machine-local and ephemeral.
|
||||||
|
sync_files=(
|
||||||
|
"publish-log.tsv"
|
||||||
|
"project-ports-registry.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
rsync_args=(-avz --checksum -e "ssh -p ${ssh_port}")
|
||||||
|
|
||||||
|
if [[ "$dry_run" == "true" ]]; then
|
||||||
|
rsync_args+=(--dry-run)
|
||||||
|
printf 'Dry-run mode — no changes will be made. Pass --execute to apply.\n\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$mode" == "push" ]]; then
|
||||||
|
printf 'Pushing state to %s:%s\n\n' "$remote" "$remote_path"
|
||||||
|
for file in "${sync_files[@]}"; do
|
||||||
|
local_file="$state_dir/$file"
|
||||||
|
if [[ ! -f "$local_file" ]]; then
|
||||||
|
printf 'SKIP (not found): %s\n' "$local_file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf 'SYNC: %s -> %s:%s/%s\n' "$local_file" "$remote" "$remote_path" "$file"
|
||||||
|
rsync "${rsync_args[@]}" "$local_file" "${remote}:${remote_path}/${file}"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
printf 'Pulling state from %s:%s\n\n' "$remote" "$remote_path"
|
||||||
|
for file in "${sync_files[@]}"; do
|
||||||
|
printf 'SYNC: %s:%s/%s -> %s/%s\n' "$remote" "$remote_path" "$file" "$state_dir" "$file"
|
||||||
|
rsync "${rsync_args[@]}" "${remote}:${remote_path}/${file}" "$state_dir/${file}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\nDone.\n'
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
name: vue3-vite
|
||||||
|
description: "Use when scaffolding a new Vue 3 + Vite project with vue-router, a pages layer, an atomic component structure, and SCSS styling foundations."
|
||||||
|
argument-hint: "project-root=<path> app-name=<name> mode=<dry-run|apply> port=<dev-server-port>"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue 3 + Vite Scaffold
|
||||||
|
|
||||||
|
Use this skill when starting a new Vue 3 + Vite frontend project that needs
|
||||||
|
routing, a component hierarchy, and a SCSS style foundation from the first
|
||||||
|
commit.
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
1. Confirm required inputs: `project-root` and `app-name`.
|
||||||
|
2. Run `resources/scripts/scaffold-vue3-vite.sh` in `--mode dry-run` first to
|
||||||
|
review planned files before writing.
|
||||||
|
3. After reviewing dry-run output, re-run with `--mode apply` to write files.
|
||||||
|
4. Run `npm install` in the project root to install dependencies.
|
||||||
|
5. Run `npm run dev` to verify the dev server starts cleanly on the configured
|
||||||
|
port.
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
- `package.json` (Vue 3, Vite, vue-router, sass)
|
||||||
|
- `vite.config.js` (dev server port, `@` alias pointing to `src/`)
|
||||||
|
- `index.html`
|
||||||
|
- `src/main.js`
|
||||||
|
- `src/App.vue`
|
||||||
|
- `src/router/index.js`
|
||||||
|
- `src/pages/HomePage.vue`
|
||||||
|
- `src/components/atoms/.gitkeep`
|
||||||
|
- `src/components/molecules/.gitkeep`
|
||||||
|
- `src/components/organisms/.gitkeep`
|
||||||
|
- `src/components/layouts/.gitkeep`
|
||||||
|
- `src/assets/styles/global-tokens.scss`
|
||||||
|
- `src/assets/styles/global-reset.scss`
|
||||||
|
- `.gitignore`
|
||||||
|
|
||||||
|
## Do Not Use
|
||||||
|
|
||||||
|
- Do not use this workflow when a project already has a `package.json` or
|
||||||
|
`vite.config.js` unless `--force` is passed.
|
||||||
|
- Do not use this workflow when the project requires SSR or a meta-framework
|
||||||
|
such as Nuxt.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Component directories follow strict atomic design:
|
||||||
|
`atoms`, `molecules`, `organisms`, `layouts`.
|
||||||
|
- Every new component should have a colocated SCSS file and a `*.stories.js`
|
||||||
|
stub; this scaffold creates the directory structure but not the components.
|
||||||
|
- The `@` alias is wired in both `vite.config.js` and expected by the router.
|
||||||
|
- SCSS files use `@use` for token and reset imports; avoid `@import`.
|
||||||
Reference in New Issue
Block a user