@gengjiawen/os-init 1.3.2 → 1.5.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.
@@ -6,6 +6,7 @@ on:
6
6
  workflow_dispatch:
7
7
 
8
8
  permissions:
9
+ id-token: write # Required for OIDC
9
10
  contents: write
10
11
  issues: write
11
12
  pull-requests: write
@@ -44,7 +45,5 @@ jobs:
44
45
  if: ${{ steps.release.outputs.release_created }}
45
46
 
46
47
  - name: Publish
47
- env:
48
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
49
48
  run: npm publish
50
49
  if: ${{ steps.release.outputs.release_created }}
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.0](https://github.com/gengjiawen/os-init/compare/v1.4.0...v1.5.0) (2025-10-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * add dev environment setup command and update dependencies ([3bd05ae](https://github.com/gengjiawen/os-init/commit/3bd05aec3ba20c55e0b2323fb0da390eb4de539d))
9
+
10
+ ## [1.4.0](https://github.com/gengjiawen/os-init/compare/v1.3.2...v1.4.0) (2025-10-25)
11
+
12
+
13
+ ### Features
14
+
15
+ * add Raycast AI configuration setup and command ([b5d64d4](https://github.com/gengjiawen/os-init/commit/b5d64d4fa65dddf739b7b3b6e50ce3f94346538a))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * format ([ad7cc79](https://github.com/gengjiawen/os-init/commit/ad7cc791efb1437c2eaae916fe3ba43bc545b355))
21
+
3
22
  ## [1.3.2](https://github.com/gengjiawen/os-init/compare/v1.3.1...v1.3.2) (2025-10-19)
4
23
 
5
24
 
package/README.md CHANGED
@@ -1,14 +1,57 @@
1
1
  ## os-init CLI
2
2
 
3
- What it does
3
+ A CLI tool to quickly configure AI development tools and environments.
4
4
 
5
- - Configure Claude Code Router with your API key (writes `~/.claude-code-router/config.json`).
6
- - Configure Codex CLI with your API key (writes `~/.codex/config.toml` and `~/.codex/auth.json`).
7
- - Install global tools: `@anthropic-ai/claude-code`, `@musistudio/claude-code-router`, `@openai/codex`.
5
+ ## Usage
8
6
 
9
- Usage
7
+ ### Configure Claude Code
10
8
 
11
- - `pnpx @gengjiawen/os-init set-cc <API_KEY>`
12
- - `pnpx @gengjiawen/os-init set-codex <API_KEY>`
9
+ ```bash
10
+ pnpx @gengjiawen/os-init set-cc <API_KEY>
11
+ ```
12
+
13
+ Configures Claude Code Router with your API key. This command will:
14
+ - Write `~/.claude-code-router/config.json`
15
+ - Write `~/.claude/settings.json`
16
+ - Install global tools: `@anthropic-ai/claude-code`, `@musistudio/claude-code-router`
17
+
18
+ ### Configure Codex CLI
19
+
20
+ ```bash
21
+ pnpx @gengjiawen/os-init set-codex <API_KEY>
22
+ ```
23
+
24
+ Configures Codex CLI with your API key. This command will:
25
+ - Write `~/.codex/config.toml`
26
+ - Write `~/.codex/auth.json`
27
+ - Install global tool: `@openai/codex`
28
+
29
+ ### Configure Raycast AI
30
+
31
+ ```bash
32
+ pnpx @gengjiawen/os-init set-raycast-ai <API_KEY>
33
+ ```
34
+
35
+ Configures Raycast AI providers with your API key. This command will:
36
+ - Write `~/.config/raycast/ai/providers.yaml`
37
+
38
+ ### Setup Dev Environment
39
+
40
+ ```bash
41
+ pnpx @gengjiawen/os-init set-dev <SSH_PUBLIC_KEY>
42
+ ```
43
+
44
+ Sets up a Docker-based development environment with SSH access. This command will:
45
+ - Copy `dev-setup` directory to your current directory (or specify with `-t, --target <dir>`)
46
+ - Configure SSH public key in Dockerfile
47
+ - Automatically run `docker-compose build && docker-compose up -d` (if docker-compose is available)
48
+ - Display SSH connection command with your local IP address
49
+
50
+ Example:
51
+ ```bash
52
+ pnpx @gengjiawen/os-init set-dev "ssh-rsa AAAAB3NzaC1yc2..."
53
+ ```
54
+
55
+ ---
13
56
 
14
57
  Project generated by [gengjiawen/ts-scaffold](https://github.com/gengjiawen/ts-scaffold)
package/bin/bin.js CHANGED
@@ -6,6 +6,8 @@ const {
6
6
  installDeps,
7
7
  writeCodexConfig,
8
8
  installCodexDeps,
9
+ writeRaycastConfig,
10
+ setupDevEnvironment,
9
11
  } = require('../build')
10
12
 
11
13
  const program = new Command()
@@ -30,7 +32,9 @@ program
30
32
  console.error('Failed to complete setup:', err.message)
31
33
  process.exit(1)
32
34
  }
33
- console.log('use `ccr code` in terminal to start building')
35
+ console.log(
36
+ 'Claude code is ready, use `claude` in terminal to start building'
37
+ )
34
38
  })
35
39
 
36
40
  program
@@ -55,4 +59,47 @@ program
55
59
  console.log('Codex is ready. use `codex` in terminal to start building')
56
60
  })
57
61
 
62
+ program
63
+ .command('set-raycast-ai')
64
+ .description('setup Raycast AI providers config')
65
+ .argument('<apiKey>', 'API key to set for Raycast AI')
66
+ .action(async (apiKey) => {
67
+ if (!apiKey || String(apiKey).trim().length === 0) {
68
+ console.error('Missing required argument: <apiKey>')
69
+ program.help({ error: true })
70
+ return
71
+ }
72
+ try {
73
+ const { configPath } = writeRaycastConfig(apiKey)
74
+ console.log(`Raycast AI config written to: ${configPath}`)
75
+ } catch (err) {
76
+ console.error('Failed to setup Raycast AI:', err.message)
77
+ process.exit(1)
78
+ }
79
+ console.log('Raycast AI is ready to use')
80
+ })
81
+
82
+ program
83
+ .command('set-dev')
84
+ .description('setup dev environment with SSH access')
85
+ .argument('<sshPublicKey>', 'SSH public key to set')
86
+ .option('-t, --target <dir>', 'Target directory for dev-setup')
87
+ .action(async (sshPublicKey, options) => {
88
+ if (!sshPublicKey || String(sshPublicKey).trim().length === 0) {
89
+ console.error('Missing required argument: <sshPublicKey>')
90
+ program.help({ error: true })
91
+ return
92
+ }
93
+ try {
94
+ const { targetPath } = await setupDevEnvironment(
95
+ sshPublicKey,
96
+ options.target
97
+ )
98
+ console.log(`\n✅ Dev environment setup completed at: ${targetPath}`)
99
+ } catch (err) {
100
+ console.error('Failed to setup dev environment:', err.message)
101
+ process.exit(1)
102
+ }
103
+ })
104
+
58
105
  program.parse(process.argv)
@@ -0,0 +1,3 @@
1
+ export declare function setupDevEnvironment(sshPublicKey: string, targetDir?: string): Promise<{
2
+ targetPath: string;
3
+ }>;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupDevEnvironment = setupDevEnvironment;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const execa_1 = require("execa");
7
+ const ip = require("ip");
8
+ function ensureDir(dirPath) {
9
+ fs.mkdirSync(dirPath, { recursive: true });
10
+ }
11
+ function copyDirSync(src, dest) {
12
+ ensureDir(dest);
13
+ const entries = fs.readdirSync(src, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ const srcPath = path.join(src, entry.name);
16
+ const destPath = path.join(dest, entry.name);
17
+ if (entry.isDirectory()) {
18
+ copyDirSync(srcPath, destPath);
19
+ }
20
+ else {
21
+ fs.copyFileSync(srcPath, destPath);
22
+ }
23
+ }
24
+ }
25
+ async function commandExists(command) {
26
+ try {
27
+ const { failed } = await (0, execa_1.execa)(command, ['--version'], {
28
+ stdio: 'ignore',
29
+ reject: false,
30
+ });
31
+ return !failed;
32
+ }
33
+ catch (error) {
34
+ return false;
35
+ }
36
+ }
37
+ async function setupDevEnvironment(sshPublicKey, targetDir) {
38
+ const sourceDir = path.join(__dirname, '..', 'dev-setup');
39
+ const defaultTargetDir = path.join(process.cwd(), 'dev-setup');
40
+ const targetPath = targetDir || defaultTargetDir;
41
+ if (!fs.existsSync(sourceDir)) {
42
+ throw new Error(`Source directory not found: ${sourceDir}`);
43
+ }
44
+ console.log(`Copying dev-setup to: ${targetPath}`);
45
+ copyDirSync(sourceDir, targetPath);
46
+ const dockerfilePath = path.join(targetPath, 'Dockerfile.txt');
47
+ if (fs.existsSync(dockerfilePath)) {
48
+ let content = fs.readFileSync(dockerfilePath, 'utf-8');
49
+ content = content.replace('ssh-public-key-placeholder', sshPublicKey);
50
+ fs.writeFileSync(dockerfilePath, content);
51
+ console.log('SSH public key has been configured in Dockerfile.txt');
52
+ }
53
+ else {
54
+ throw new Error(`Dockerfile.txt not found in ${targetPath}`);
55
+ }
56
+ const hasDockerCompose = await commandExists('docker-compose');
57
+ if (hasDockerCompose) {
58
+ console.log('\n🐳 Docker Compose detected. Building and starting containers...');
59
+ try {
60
+ console.log('Running: docker-compose build');
61
+ await (0, execa_1.execa)('docker-compose', ['build'], {
62
+ cwd: targetPath,
63
+ stdio: 'inherit',
64
+ });
65
+ console.log('Running: docker-compose up -d');
66
+ await (0, execa_1.execa)('docker-compose', ['up', '-d'], {
67
+ cwd: targetPath,
68
+ stdio: 'inherit',
69
+ });
70
+ console.log('\n✅ Dev environment is up and running!');
71
+ }
72
+ catch (error) {
73
+ const errorMessage = error instanceof Error ? error.message : String(error);
74
+ console.error('\n⚠️ Failed to start containers:', errorMessage);
75
+ console.log('\nYou can manually start the containers with:');
76
+ console.log(` cd ${targetPath}`);
77
+ console.log(' docker-compose build && docker-compose up -d');
78
+ }
79
+ }
80
+ else {
81
+ console.log('\n⚠️ Docker Compose not found.');
82
+ console.log('\nTo start the dev environment, run:');
83
+ console.log(` cd ${targetPath}`);
84
+ console.log(' docker-compose build && docker-compose up -d');
85
+ }
86
+ const localIp = ip.address();
87
+ console.log('\n📡 SSH Connection:');
88
+ console.log(` ssh gitpod@${localIp} -p 2222`);
89
+ console.log('\nOr use localhost if connecting from the same machine:');
90
+ console.log(' ssh gitpod@localhost -p 2222');
91
+ return { targetPath };
92
+ }
package/build/index.d.ts CHANGED
@@ -8,3 +8,7 @@ export declare function writeCodexConfig(apiKey: string): {
8
8
  authPath: string;
9
9
  };
10
10
  export declare function installCodexDeps(): Promise<void>;
11
+ export declare function writeRaycastConfig(apiKey: string): {
12
+ configPath: string;
13
+ };
14
+ export { setupDevEnvironment } from './dev-setup';
package/build/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupDevEnvironment = void 0;
3
4
  exports.writeClaudeConfig = writeClaudeConfig;
4
5
  exports.installDeps = installDeps;
5
6
  exports.writeCodexConfig = writeCodexConfig;
6
7
  exports.installCodexDeps = installCodexDeps;
8
+ exports.writeRaycastConfig = writeRaycastConfig;
7
9
  const fs = require("fs");
8
10
  const path = require("path");
9
11
  const os = require("os");
@@ -129,3 +131,37 @@ async function installCodexDeps() {
129
131
  }
130
132
  console.log('Codex dependency installed successfully.');
131
133
  }
134
+ function getRaycastAIConfigDir() {
135
+ return path.join(os.homedir(), '.config', 'raycast', 'ai');
136
+ }
137
+ const RAYCAST_PROVIDERS_YAML_TEMPLATE = `providers:
138
+ - id: my_provider
139
+ name: gengjiawen AI
140
+ base_url: https://ai.gengjiawen.com/api/openai/v1/
141
+ api_keys:
142
+ openai: API_KEY_PLACEHOLDER
143
+ models:
144
+ - id: sota
145
+ name: "sota"
146
+ context: 200000
147
+ provider: openai
148
+ abilities:
149
+ temperature:
150
+ supported: true
151
+ vision:
152
+ supported: true
153
+ system_message:
154
+ supported: true
155
+ tools:
156
+ supported: true
157
+ `;
158
+ function writeRaycastConfig(apiKey) {
159
+ const configDir = getRaycastAIConfigDir();
160
+ ensureDir(configDir);
161
+ const configPath = path.join(configDir, 'providers.yaml');
162
+ const content = RAYCAST_PROVIDERS_YAML_TEMPLATE.replace('API_KEY_PLACEHOLDER', apiKey);
163
+ fs.writeFileSync(configPath, content);
164
+ return { configPath };
165
+ }
166
+ var dev_setup_1 = require("./dev-setup");
167
+ Object.defineProperty(exports, "setupDevEnvironment", { enumerable: true, get: function () { return dev_setup_1.setupDevEnvironment; } });
@@ -0,0 +1,19 @@
1
+ FROM gengjiawen/node-build
2
+
3
+ # SSHD configuration
4
+ COPY sshd_config /etc/ssh/sshd_config
5
+
6
+ ENV SSH_PUBLIC_KEY="ssh-public-key-placeholder"
7
+ RUN set -eux; \
8
+ if id -u gitpod >/dev/null 2>&1; then \
9
+ userhome="$(getent passwd gitpod | cut -d: -f6)"; \
10
+ install -o gitpod -g gitpod -m 700 -d "$userhome/.ssh"; \
11
+ printf '%s\n' "$SSH_PUBLIC_KEY" > "$userhome/.ssh/authorized_keys"; \
12
+ chown gitpod:gitpod "$userhome/.ssh/authorized_keys"; \
13
+ chmod 600 "$userhome/.ssh/authorized_keys"; \
14
+ fi
15
+
16
+ # Run sshd in foreground
17
+ USER root
18
+ EXPOSE 22
19
+ CMD ["/usr/sbin/sshd", "-D", "-e"]
@@ -0,0 +1,15 @@
1
+ version: '3'
2
+
3
+ services:
4
+ node-core-dev:
5
+ build: ./
6
+ container_name: 'node-dev'
7
+ restart: always
8
+ tty: true
9
+ ports:
10
+ - '2222:22'
11
+ cap_add:
12
+ - SYS_PTRACE
13
+ volumes:
14
+ - .:/pwd
15
+ - $HOME/.local:/root/.local
@@ -0,0 +1,31 @@
1
+ Port 22
2
+ Protocol 2
3
+
4
+ # Host keys are generated at runtime with `ssh-keygen -A`
5
+ HostKey /etc/ssh/ssh_host_rsa_key
6
+ HostKey /etc/ssh/ssh_host_ed25519_key
7
+
8
+ SyslogFacility AUTHPRIV
9
+ LogLevel VERBOSE
10
+
11
+ # Disallow root login; use non-root user (e.g., gitpod)
12
+ PermitRootLogin no
13
+ PasswordAuthentication no
14
+ KbdInteractiveAuthentication no
15
+ ChallengeResponseAuthentication no
16
+ PubkeyAuthentication yes
17
+
18
+ # Use per-user authorized_keys in each home directory
19
+ AuthorizedKeysFile .ssh/authorized_keys
20
+
21
+ PermitEmptyPasswords no
22
+ X11Forwarding no
23
+ AllowAgentForwarding yes
24
+ AllowTcpForwarding yes
25
+ ClientAliveInterval 120
26
+ ClientAliveCountMax 3
27
+ PrintMotd no
28
+ AcceptEnv LANG LC_*
29
+
30
+ # SFTP Subsystem
31
+ Subsystem sftp /usr/lib/openssh/sftp-server
@@ -0,0 +1,118 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import { execa } from 'execa'
4
+ import * as ip from 'ip'
5
+
6
+ /** Ensure directory exists */
7
+ function ensureDir(dirPath: string): void {
8
+ fs.mkdirSync(dirPath, { recursive: true })
9
+ }
10
+
11
+ /** Copy directory recursively */
12
+ function copyDirSync(src: string, dest: string): void {
13
+ ensureDir(dest)
14
+ const entries = fs.readdirSync(src, { withFileTypes: true })
15
+
16
+ for (const entry of entries) {
17
+ const srcPath = path.join(src, entry.name)
18
+ const destPath = path.join(dest, entry.name)
19
+
20
+ if (entry.isDirectory()) {
21
+ copyDirSync(srcPath, destPath)
22
+ } else {
23
+ fs.copyFileSync(srcPath, destPath)
24
+ }
25
+ }
26
+ }
27
+
28
+ /** Check if a command exists */
29
+ async function commandExists(command: string): Promise<boolean> {
30
+ try {
31
+ const { failed } = await execa(command, ['--version'], {
32
+ stdio: 'ignore',
33
+ reject: false,
34
+ })
35
+ return !failed
36
+ } catch (error) {
37
+ return false
38
+ }
39
+ }
40
+
41
+ /** Setup dev environment by copying dev-setup directory and replacing SSH public key */
42
+ export async function setupDevEnvironment(
43
+ sshPublicKey: string,
44
+ targetDir?: string
45
+ ): Promise<{ targetPath: string }> {
46
+ // Determine source and target paths
47
+ const sourceDir = path.join(__dirname, '..', 'dev-setup')
48
+ const defaultTargetDir = path.join(process.cwd(), 'dev-setup')
49
+ const targetPath = targetDir || defaultTargetDir
50
+
51
+ // Check if source directory exists
52
+ if (!fs.existsSync(sourceDir)) {
53
+ throw new Error(`Source directory not found: ${sourceDir}`)
54
+ }
55
+
56
+ // Copy dev-setup directory
57
+ console.log(`Copying dev-setup to: ${targetPath}`)
58
+ copyDirSync(sourceDir, targetPath)
59
+
60
+ // Replace SSH_PUBLIC_KEY placeholder in Dockerfile.txt
61
+ const dockerfilePath = path.join(targetPath, 'Dockerfile.txt')
62
+ if (fs.existsSync(dockerfilePath)) {
63
+ let content = fs.readFileSync(dockerfilePath, 'utf-8')
64
+ content = content.replace('ssh-public-key-placeholder', sshPublicKey)
65
+ fs.writeFileSync(dockerfilePath, content)
66
+ console.log('SSH public key has been configured in Dockerfile.txt')
67
+ } else {
68
+ throw new Error(`Dockerfile.txt not found in ${targetPath}`)
69
+ }
70
+
71
+ // Check if docker-compose is available
72
+ const hasDockerCompose = await commandExists('docker-compose')
73
+
74
+ if (hasDockerCompose) {
75
+ console.log(
76
+ '\n🐳 Docker Compose detected. Building and starting containers...'
77
+ )
78
+
79
+ try {
80
+ // Run docker-compose build
81
+ console.log('Running: docker-compose build')
82
+ await execa('docker-compose', ['build'], {
83
+ cwd: targetPath,
84
+ stdio: 'inherit',
85
+ })
86
+
87
+ // Run docker-compose up -d
88
+ console.log('Running: docker-compose up -d')
89
+ await execa('docker-compose', ['up', '-d'], {
90
+ cwd: targetPath,
91
+ stdio: 'inherit',
92
+ })
93
+
94
+ console.log('\n✅ Dev environment is up and running!')
95
+ } catch (error) {
96
+ const errorMessage =
97
+ error instanceof Error ? error.message : String(error)
98
+ console.error('\n⚠️ Failed to start containers:', errorMessage)
99
+ console.log('\nYou can manually start the containers with:')
100
+ console.log(` cd ${targetPath}`)
101
+ console.log(' docker-compose build && docker-compose up -d')
102
+ }
103
+ } else {
104
+ console.log('\n⚠️ Docker Compose not found.')
105
+ console.log('\nTo start the dev environment, run:')
106
+ console.log(` cd ${targetPath}`)
107
+ console.log(' docker-compose build && docker-compose up -d')
108
+ }
109
+
110
+ // Display SSH connection instructions
111
+ const localIp = ip.address()
112
+ console.log('\n📡 SSH Connection:')
113
+ console.log(` ssh gitpod@${localIp} -p 2222`)
114
+ console.log('\nOr use localhost if connecting from the same machine:')
115
+ console.log(' ssh gitpod@localhost -p 2222')
116
+
117
+ return { targetPath }
118
+ }
package/libs/index.ts CHANGED
@@ -164,3 +164,49 @@ export async function installCodexDeps(): Promise<void> {
164
164
  }
165
165
  console.log('Codex dependency installed successfully.')
166
166
  }
167
+
168
+ /** Return Raycast AI configuration directory path */
169
+ function getRaycastAIConfigDir(): string {
170
+ return path.join(os.homedir(), '.config', 'raycast', 'ai')
171
+ }
172
+
173
+ /** Template for Raycast AI providers.yaml */
174
+ const RAYCAST_PROVIDERS_YAML_TEMPLATE = `providers:
175
+ - id: my_provider
176
+ name: gengjiawen AI
177
+ base_url: https://ai.gengjiawen.com/api/openai/v1/
178
+ api_keys:
179
+ openai: API_KEY_PLACEHOLDER
180
+ models:
181
+ - id: sota
182
+ name: "sota"
183
+ context: 200000
184
+ provider: openai
185
+ abilities:
186
+ temperature:
187
+ supported: true
188
+ vision:
189
+ supported: true
190
+ system_message:
191
+ supported: true
192
+ tools:
193
+ supported: true
194
+ `
195
+
196
+ /** Write Raycast AI providers.yaml */
197
+ export function writeRaycastConfig(apiKey: string): { configPath: string } {
198
+ const configDir = getRaycastAIConfigDir()
199
+ ensureDir(configDir)
200
+
201
+ const configPath = path.join(configDir, 'providers.yaml')
202
+ const content = RAYCAST_PROVIDERS_YAML_TEMPLATE.replace(
203
+ 'API_KEY_PLACEHOLDER',
204
+ apiKey
205
+ )
206
+ fs.writeFileSync(configPath, content)
207
+
208
+ return { configPath }
209
+ }
210
+
211
+ // Re-export dev-setup functionality
212
+ export { setupDevEnvironment } from './dev-setup'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gengjiawen/os-init",
3
3
  "private": false,
4
- "version": "1.3.2",
4
+ "version": "1.5.0",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "bin": {
@@ -25,14 +25,20 @@
25
25
  "publishConfig": {
26
26
  "access": "public"
27
27
  },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/gengjiawen/os-init"
31
+ },
28
32
  "engines": {
29
33
  "node": ">22.12.0"
30
34
  },
31
35
  "devDependencies": {
36
+ "@types/ip": "^1.1.3",
32
37
  "@types/jest": "29.5.12",
33
38
  "@types/node": "24.1.0",
34
39
  "cpy-cli": "^4.2.0",
35
40
  "husky": "9.1.6",
41
+ "ip": "^2.0.1",
36
42
  "jest": "29.7.0",
37
43
  "lint-staged": "^15.2.2",
38
44
  "nodemon": "3.1.4",