ash-sh 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.
package/.env.example ADDED
@@ -0,0 +1,28 @@
1
+ # ash — Self Hosting Harness
2
+ # Copy to .env and fill in your values: cp .env.example .env
3
+
4
+ # --- Domain ---
5
+ ASH_DOMAIN=yourdomain.com
6
+ ASH_EMAIL=you@example.com
7
+
8
+ # --- DNS Provider (for wildcard TLS) ---
9
+ # Supported: route53, cloudflare
10
+ ASH_DNS_PROVIDER=route53
11
+ AWS_REGION=us-east-1
12
+ AWS_ACCESS_KEY_ID=
13
+ AWS_SECRET_ACCESS_KEY=
14
+ # CLOUDFLARE_API_TOKEN=
15
+
16
+ # --- Tailscale (optional, for private network) ---
17
+ # TAILSCALE_AUTH_KEY=
18
+
19
+ # --- LLM Providers ---
20
+ OLLAMA_API_KEY=ollama
21
+ # OPENROUTER_API_KEY=
22
+ # OPENAI_API_KEY=
23
+ # ANTHROPIC_API_KEY=
24
+
25
+ # --- Services auth ---
26
+ WEBDAV_USER=
27
+ WEBDAV_PASS_HASH=
28
+ CODE_SERVER_PASSWORD=
package/bin/ash.js ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const command = process.argv[2] || 'setup';
8
+
9
+ if (command === 'setup') {
10
+ await import(join(__dirname, '..', 'setup', 'index.js'));
11
+ } else {
12
+ console.log(`ash: unknown command '${command}'`);
13
+ console.log('Usage: ash setup');
14
+ process.exit(1);
15
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "ash-sh",
3
+ "version": "0.1.0",
4
+ "description": "ash — Self Hosting Harness",
5
+ "type": "module",
6
+ "bin": {
7
+ "ash": "./bin/ash.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/ash.js",
11
+ "setup": "node setup/index.js"
12
+ },
13
+ "keywords": [
14
+ "self-hosted",
15
+ "ai",
16
+ "caddy",
17
+ "ollama",
18
+ "dev-environment",
19
+ "cli"
20
+ ],
21
+ "author": "Aman Agrawal <workwithamaagra@gmail.com>",
22
+ "license": "MIT",
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
26
+ "dependencies": {
27
+ "@clack/prompts": "^0.11.0",
28
+ "open": "^10.0.0"
29
+ }
30
+ }
package/setup/index.js ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as p from '@clack/prompts';
4
+ import open from 'open';
5
+ import { setTimeout } from 'timers/promises';
6
+
7
+ p.intro('ash — Self Hosting Harness');
8
+
9
+ const name = await p.text({
10
+ message: 'What is your name?',
11
+ placeholder: 'your name',
12
+ validate: (val) => {
13
+ if (!val) return 'Name is required';
14
+ },
15
+ });
16
+
17
+ if (p.isCancel(name)) {
18
+ p.cancel('Setup cancelled.');
19
+ process.exit(0);
20
+ }
21
+
22
+ const domain = await p.text({
23
+ message: 'Your domain:',
24
+ placeholder: 'yourdomain.com',
25
+ validate: (val) => {
26
+ if (!val) return 'Domain is required';
27
+ if (!val.includes('.')) return 'Enter a valid domain';
28
+ },
29
+ });
30
+
31
+ if (p.isCancel(domain)) {
32
+ p.cancel('Setup cancelled.');
33
+ process.exit(0);
34
+ }
35
+
36
+ const dnsProvider = await p.select({
37
+ message: 'DNS provider (for wildcard TLS):',
38
+ options: [
39
+ { value: 'route53', label: 'AWS Route 53' },
40
+ { value: 'cloudflare', label: 'Cloudflare' },
41
+ { value: 'manual', label: 'Manual (I\'ll configure DNS myself)' },
42
+ ],
43
+ });
44
+
45
+ if (p.isCancel(dnsProvider)) {
46
+ p.cancel('Setup cancelled.');
47
+ process.exit(0);
48
+ }
49
+
50
+ const services = await p.multiselect({
51
+ message: 'Which services to enable?',
52
+ options: [
53
+ { value: 'code-server', label: 'VS Code Server', hint: 'code.domain.com' },
54
+ { value: 'ollama', label: 'Ollama (local LLM)', hint: 'local inference' },
55
+ { value: 'dsh', label: 'DeepSeek Harness', hint: 'dsh.domain.com' },
56
+ { value: 'webdav', label: 'Obsidian WebDAV', hint: 'obsidian.domain.com' },
57
+ ],
58
+ required: false,
59
+ });
60
+
61
+ if (p.isCancel(services)) {
62
+ p.cancel('Setup cancelled.');
63
+ process.exit(0);
64
+ }
65
+
66
+ const tailscale = await p.confirm({
67
+ message: 'Enable Tailscale (private network)?',
68
+ initialValue: true,
69
+ });
70
+
71
+ if (p.isCancel(tailscale)) {
72
+ p.cancel('Setup cancelled.');
73
+ process.exit(0);
74
+ }
75
+
76
+ // Simulate browser OAuth flow
77
+ if (dnsProvider === 'route53') {
78
+ const authAws = await p.confirm({
79
+ message: 'Authenticate with AWS? (opens browser)',
80
+ });
81
+
82
+ if (authAws && !p.isCancel(authAws)) {
83
+ const s = p.spinner();
84
+ s.start('Opening browser for AWS authentication...');
85
+ // In real impl: open OAuth URL, start local server for callback
86
+ // await open('https://signin.aws.amazon.com/...');
87
+ await setTimeout(1500);
88
+ s.stop('AWS authentication placeholder (will connect in impl)');
89
+ }
90
+ }
91
+
92
+ // Summary
93
+ p.note(
94
+ [
95
+ `Name: ${name}`,
96
+ `Domain: ${domain}`,
97
+ `DNS: ${dnsProvider}`,
98
+ `Services: ${services.length ? services.join(', ') : 'none'}`,
99
+ `Tailscale: ${tailscale ? 'yes' : 'no'}`,
100
+ ].join('\n'),
101
+ 'Configuration'
102
+ );
103
+
104
+ // Hand off to impl
105
+ const s = p.spinner();
106
+ s.start('Initializing...');
107
+ await setTimeout(1000);
108
+ s.stop('Ready');
109
+
110
+ p.outro(`Hello ${name}! Your harness will be at https://*.${domain}`);