@trikhub/cli 0.2.0 → 0.3.1

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 CHANGED
@@ -48,14 +48,15 @@ trik install @scope/trik-name --version 1.2.3
48
48
  ```
49
49
 
50
50
  The install process:
51
- 1. Resolves configuration (see [Local vs Global Configuration](#local-vs-global-configuration))
52
- 2. Fetches trik metadata from the registry
53
- 3. Downloads the tarball from GitHub Releases
54
- 4. Extracts to the configured triks directory
51
+
52
+ 1. **Tries npm registry first** - If the package is published to npm, installs via your package manager (npm/pnpm/yarn)
53
+ 2. **Falls back to TrikHub registry** - For GitHub-only packages, downloads the tarball from GitHub Releases
54
+ 3. Adds the dependency to `package.json` (npm packages use version, TrikHub packages use tarball URL)
55
+ 4. Extracts to `node_modules/`
55
56
  5. **Validates** the trik (manifest structure, security rules)
56
- 6. Updates the lockfile
57
+ 6. Registers the trik in `.trikhub/config.json`
57
58
 
58
- If validation fails, the trik is removed and installation aborts.
59
+ This hybrid approach means triks work like regular npm packages while supporting GitHub-only distributions.
59
60
 
60
61
  ### `trik search <query>`
61
62
 
@@ -107,6 +108,23 @@ trik upgrade @acme/article-search
107
108
  trik upgrade --force
108
109
  ```
109
110
 
111
+ ### `trik sync`
112
+
113
+ Discover trik packages in `node_modules` and register them in `.trikhub/config.json`.
114
+
115
+ ```bash
116
+ # Scan node_modules and add triks to config
117
+ trik sync
118
+
119
+ # Preview what would be synced
120
+ trik sync --dry-run
121
+
122
+ # Output as JSON
123
+ trik sync --json
124
+ ```
125
+
126
+ This is useful when you manually add a trik to `package.json` and run `npm install`. The sync command will detect the trik and register it.
127
+
110
128
  ## Authentication
111
129
 
112
130
  ### `trik login`
@@ -168,16 +186,19 @@ trik publish --skip-release
168
186
 
169
187
  The CLI will:
170
188
 
171
- 1. Validate your trik structure (manifest.json, trikhub.json, dist/)
172
- 2. Create a tarball with required files
189
+ 1. Validate your trik structure (manifest.json, trikhub.json, package.json, dist/)
190
+ 2. Create an **npm-compatible tarball** with files inside a `package/` directory
173
191
  3. Compute SHA-256 hash for integrity verification
174
192
  4. Create a GitHub Release with the tarball attached
175
193
  5. Register the trik with the TrikHub registry
176
194
 
195
+ The tarball format is compatible with npm, so users can install directly via the tarball URL.
196
+
177
197
  ### Required Files
178
198
 
179
199
  ```
180
200
  your-trik/
201
+ ├── package.json # npm package definition (required)
181
202
  ├── manifest.json # Trik manifest (required)
182
203
  ├── trikhub.json # Registry metadata (required)
183
204
  ├── dist/
@@ -210,102 +231,59 @@ Triks use scoped names similar to npm:
210
231
 
211
232
  **Note:** All trik names are normalized to lowercase. `@Acme/Article-Search` becomes `@acme/article-search`.
212
233
 
213
- ## Local vs Global Configuration
214
-
215
- The CLI supports both **local** (project-level) and **global** (user-level) configurations. This allows you to have project-specific trik installations or share triks across all projects.
216
-
217
- ### Configuration Resolution
218
-
219
- When you run a command like `trik install`, the CLI resolves configuration in this order:
220
-
221
- 1. **Local config**: Checks for `.trikhub/config.json` in the current directory
222
- 2. **Global config**: Falls back to `~/.trikhub/config.json` in your home directory
223
- 3. **Setup prompt**: If neither exists, prompts you to choose where to set up
224
-
225
- ```
226
- $ trik install @acme/article-search
227
-
228
- No TrikHub configuration found.
229
- Triks need a place to be installed.
230
-
231
- ? Where would you like to set up TrikHub?
232
- ❯ Global (~/.trikhub) - Available to all projects
233
- Local (./.trikhub) - Project-specific configuration
234
- ```
235
-
236
- ### Global Configuration (Default)
237
-
238
- Triks are installed in your home directory and available to all projects:
239
-
240
- ```
241
- ~/.trikhub/
242
- ├── config.json # CLI configuration
243
- ├── triks.lock # Lockfile tracking installed versions
244
- └── triks/ # Installed triks
245
- └── @scope/trik-name/
246
- ```
247
-
248
- ### Local Configuration
234
+ ## How Triks Work with npm
249
235
 
250
- Triks are installed in the current project directory. Useful for:
236
+ Triks are installed as regular npm packages in your project's `node_modules/`. The CLI tracks which packages are triks in `.trikhub/config.json`.
251
237
 
252
- - Project-specific trik versions
253
- - Sharing trik configurations with your team (commit `.trikhub/` to git)
254
- - Isolated environments
238
+ ### Project Structure
255
239
 
256
240
  ```
257
241
  ./your-project/
242
+ ├── package.json # Trik dependencies listed here
243
+ ├── node_modules/
244
+ │ └── @scope/trik-name/ # Trik installed like any npm package
258
245
  └── .trikhub/
259
- ├── config.json # Project-specific configuration
260
- ├── triks.lock # Project lockfile
261
- └── triks/ # Project-specific triks
262
- └── @scope/trik-name/
246
+ └── config.json # Lists which packages are triks
263
247
  ```
264
248
 
265
- ### Switching Between Scopes
249
+ ### The Config File
266
250
 
267
- The CLI automatically detects which scope to use based on the presence of `.trikhub/config.json` in the current directory:
251
+ `.trikhub/config.json` tracks which npm packages are triks:
268
252
 
269
- ```bash
270
- # In a project with local config
271
- $ trik list
272
- Installed triks (2) (local: /path/to/project/.trikhub):
273
- ● @acme/article-search v1.0.0
274
-
275
- # In a directory without local config (uses global)
276
- $ cd ~
277
- $ trik list
278
- Installed triks (5) (global):
279
- ● @acme/other-trik v2.0.0
253
+ ```json
254
+ {
255
+ "triks": ["@acme/article-search", "@acme/web-scraper"]
256
+ }
280
257
  ```
281
258
 
282
- ### Initializing a Local Config
259
+ This file is used by the TrikHub Gateway to know which packages to load as triks.
283
260
 
284
- If you have a global config but want to set up a local one for a project:
261
+ ### TrikHub Registry Packages
285
262
 
286
- ```bash
287
- $ trik install @scope/some-trik
288
- # When prompted "Use global configuration?", select "No"
289
- # This will initialize a local .trikhub/ directory
290
- ```
263
+ For packages not published to npm (GitHub-only), the CLI:
291
264
 
292
- ## File Locations
265
+ 1. Downloads the tarball from GitHub Releases
266
+ 2. Extracts to `node_modules/`
267
+ 3. Adds the tarball URL to `package.json`
293
268
 
294
- ### Global (Default)
269
+ ```json
270
+ {
271
+ "dependencies": {
272
+ "@acme/article-search": "https://github.com/acme/article-search/releases/download/v1.0.0/article-search-1.0.0.tar.gz"
273
+ }
274
+ }
275
+ ```
295
276
 
296
- | Path | Description |
297
- |------|-------------|
298
- | `~/.trikhub/config.json` | CLI configuration |
299
- | `~/.trikhub/triks.lock` | Lockfile tracking installed versions |
300
- | `~/.trikhub/triks/` | Installed triks directory |
277
+ This means `npm install` works natively - npm fetches from the tarball URL.
301
278
 
302
- ### Local (Project-Level)
279
+ ## File Locations
303
280
 
304
281
  | Path | Description |
305
282
  |------|-------------|
306
- | `./.trikhub/config.json` | Project-specific configuration |
307
- | `./.trikhub/triks.lock` | Project lockfile |
308
- | `./.trikhub/triks/` | Project-specific triks |
283
+ | `~/.trikhub/config.json` | Global CLI configuration (auth tokens, registry URL) |
284
+ | `./.trikhub/config.json` | Project trik registry (list of trik package names) |
285
+ | `./package.json` | Trik dependencies (managed by npm) |
286
+ | `./node_modules/` | Installed triks (managed by npm) |
309
287
 
310
288
  ## Validation
311
289
 
@@ -322,26 +300,39 @@ Triks that fail validation are rejected to prevent prompt injection vulnerabilit
322
300
 
323
301
  ### Registry URL
324
302
 
325
- By default, the CLI connects to `https://api.trikhub.com`. For development, you can override this:
303
+ The registry URL is determined by environment:
304
+
305
+ | Environment | Registry URL |
306
+ | ----------- | ------------ |
307
+ | Production (default) | `https://api.trikhub.com` |
308
+ | Development (`--dev` flag) | `http://localhost:3001` |
309
+
310
+ Use the `--dev` flag for local development:
326
311
 
327
312
  ```bash
328
- # Environment variable (highest priority)
329
- export TRIKHUB_REGISTRY=http://localhost:3000
313
+ trik --dev search article
314
+ trik --dev install @scope/name
315
+ ```
330
316
 
331
- # Or edit your config.json (local or global)
332
- {
333
- "registry": "http://localhost:3000"
334
- }
317
+ Alternatively, set `NODE_ENV=development`:
318
+
319
+ ```bash
320
+ export NODE_ENV=development
321
+ trik search article
335
322
  ```
336
323
 
337
- ### Config File
324
+ You can also override the registry URL with an environment variable:
338
325
 
339
- The `config.json` file (either local `.trikhub/config.json` or global `~/.trikhub/config.json`) stores:
326
+ ```bash
327
+ export TRIKHUB_REGISTRY=http://localhost:3000
328
+ ```
329
+
330
+ ### Global Config File (`~/.trikhub/config.json`)
331
+
332
+ Stores authentication and CLI settings:
340
333
 
341
334
  ```json
342
335
  {
343
- "registry": "https://api.trikhub.com",
344
- "triksDirectory": ".trikhub/triks",
345
336
  "analytics": true,
346
337
  "authToken": "...",
347
338
  "authExpiresAt": "2026-03-09T11:24:12.401Z",
@@ -351,13 +342,29 @@ The `config.json` file (either local `.trikhub/config.json` or global `~/.trikhu
351
342
 
352
343
  | Field | Description |
353
344
  | ----- | ----------- |
354
- | `registry` | TrikHub registry URL |
355
- | `triksDirectory` | Where triks are installed (relative to config location for local) |
356
345
  | `analytics` | Whether to send anonymous download analytics |
357
346
  | `authToken` | Authentication token (set by `trik login`) |
358
347
  | `authExpiresAt` | Token expiration timestamp |
359
348
  | `publisherUsername` | Authenticated GitHub username |
360
349
 
350
+ ### Project Config File (`.trikhub/config.json`)
351
+
352
+ Tracks which npm packages are triks:
353
+
354
+ ```json
355
+ {
356
+ "triks": ["@acme/article-search", "@acme/web-scraper"],
357
+ "trikhub": {
358
+ "@acme/article-search": "1.0.0"
359
+ }
360
+ }
361
+ ```
362
+
363
+ | Field | Description |
364
+ | ----- | ----------- |
365
+ | `triks` | List of npm package names that are triks |
366
+ | `trikhub` | Packages installed from TrikHub registry (version tracking) |
367
+
361
368
  ## Development
362
369
 
363
370
  ```bash
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * Command-line interface for managing AI triks.
6
6
  */
7
7
  import { Command } from 'commander';
8
+ import { createRequire } from 'module';
8
9
  import { installCommand } from './commands/install.js';
9
10
  import { listCommand } from './commands/list.js';
10
11
  import { searchCommand } from './commands/search.js';
@@ -14,11 +15,21 @@ import { loginCommand, logoutCommand, whoamiCommand } from './commands/login.js'
14
15
  import { publishCommand } from './commands/publish.js';
15
16
  import { upgradeCommand, upgradeAllCommand } from './commands/upgrade.js';
16
17
  import { syncCommand } from './commands/sync.js';
18
+ // Read version from package.json
19
+ const require = createRequire(import.meta.url);
20
+ const pkg = require('../package.json');
17
21
  const program = new Command();
18
22
  program
19
23
  .name('trik')
20
24
  .description('TrikHub CLI - Teaching AI new triks')
21
- .version('0.1.0');
25
+ .version(pkg.version)
26
+ .option('--dev', 'Use development registry (localhost:3001)')
27
+ .hook('preAction', () => {
28
+ // Set NODE_ENV before any command runs if --dev flag is passed
29
+ if (program.opts().dev) {
30
+ process.env.NODE_ENV = 'development';
31
+ }
32
+ });
22
33
  // Install command
23
34
  program
24
35
  .command('install <trik>')
@@ -75,7 +86,6 @@ program
75
86
  .description('Publish a trik to the registry')
76
87
  .option('-d, --directory <path>', 'Trik directory to publish', '.')
77
88
  .option('-t, --tag <version>', 'Version tag (default: from manifest)')
78
- .option('--skip-release', 'Skip creating GitHub release (for manual upload)')
79
89
  .action(publishCommand);
80
90
  // Upgrade command
81
91
  program
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -5,9 +5,11 @@
5
5
  *
6
6
  * Workflow:
7
7
  * 1. Try npm registry first
8
- * 2. If not found, fall back to TrikHub registry (GitHub releases)
9
- * 3. Download and install
10
- * 4. Update .trikhub/config.json with the trik
8
+ * 2. If not found, fall back to TrikHub registry (uses git URLs)
9
+ * 3. Verify commit SHA for security
10
+ * 4. Add to package.json as github:owner/repo#tag
11
+ * 5. Run package manager install
12
+ * 6. Update .trikhub/config.json with the trik
11
13
  */
12
14
  interface InstallOptions {
13
15
  version?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,UAAU,cAAc;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAyUD,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CA0Ff"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,UAAU,cAAc;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiVD,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CA2Ff"}
@@ -5,20 +5,19 @@
5
5
  *
6
6
  * Workflow:
7
7
  * 1. Try npm registry first
8
- * 2. If not found, fall back to TrikHub registry (GitHub releases)
9
- * 3. Download and install
10
- * 4. Update .trikhub/config.json with the trik
8
+ * 2. If not found, fall back to TrikHub registry (uses git URLs)
9
+ * 3. Verify commit SHA for security
10
+ * 4. Add to package.json as github:owner/repo#tag
11
+ * 5. Run package manager install
12
+ * 6. Update .trikhub/config.json with the trik
11
13
  */
12
- import { existsSync, createWriteStream, rmSync, mkdirSync } from 'node:fs';
14
+ import { existsSync, mkdirSync } from 'node:fs';
13
15
  import { readFile, writeFile, mkdir } from 'node:fs/promises';
14
16
  import { join, dirname } from 'node:path';
15
17
  import { spawn } from 'node:child_process';
16
- import { pipeline } from 'node:stream/promises';
17
- import { Readable } from 'node:stream';
18
18
  import chalk from 'chalk';
19
19
  import ora from 'ora';
20
20
  import * as semver from 'semver';
21
- import * as tar from 'tar';
22
21
  import { validateManifest } from '@trikhub/manifest';
23
22
  import { registry } from '../lib/registry.js';
24
23
  const NPM_CONFIG_DIR = '.trikhub';
@@ -78,6 +77,7 @@ async function readNpmConfig(baseDir) {
78
77
  const config = JSON.parse(content);
79
78
  return {
80
79
  triks: Array.isArray(config.triks) ? config.triks : [],
80
+ trikhub: config.trikhub ?? {},
81
81
  };
82
82
  }
83
83
  catch {
@@ -115,44 +115,67 @@ async function isTrikPackage(packagePath) {
115
115
  }
116
116
  /**
117
117
  * Add a trik to the config
118
+ * @param trikhubVersion - If provided, marks this as a TrikHub-only package (not on npm)
118
119
  */
119
- async function addTrikToConfig(packageName, baseDir) {
120
+ async function addTrikToConfig(packageName, baseDir, trikhubVersion) {
120
121
  const config = await readNpmConfig(baseDir);
121
122
  if (!config.triks.includes(packageName)) {
122
123
  config.triks = [...config.triks, packageName].sort();
123
- await writeNpmConfig(config, baseDir);
124
124
  }
125
+ // Track TrikHub source for reinstallation
126
+ if (trikhubVersion) {
127
+ if (!config.trikhub) {
128
+ config.trikhub = {};
129
+ }
130
+ config.trikhub[packageName] = trikhubVersion;
131
+ }
132
+ await writeNpmConfig(config, baseDir);
125
133
  }
126
134
  /**
127
- * Download a file from a URL
135
+ * Verify that a GitHub tag points to the expected commit SHA
128
136
  */
129
- async function downloadFile(url, destPath) {
130
- const response = await fetch(url);
131
- if (!response.ok) {
132
- throw new Error(`Failed to download: ${response.status} ${response.statusText}`);
133
- }
134
- if (!response.body) {
135
- throw new Error('No response body');
137
+ async function verifyGitHubTagSha(githubRepo, gitTag, expectedSha) {
138
+ try {
139
+ // Use GitHub API to get the tag reference
140
+ const response = await fetch(`https://api.github.com/repos/${githubRepo}/git/refs/tags/${gitTag}`);
141
+ if (!response.ok) {
142
+ if (response.status === 404) {
143
+ return { valid: false };
144
+ }
145
+ throw new Error(`GitHub API error: ${response.status}`);
146
+ }
147
+ const data = await response.json();
148
+ let currentSha = data.object.sha;
149
+ // If it's an annotated tag, we need to dereference it
150
+ if (data.object.type === 'tag') {
151
+ const tagResponse = await fetch(`https://api.github.com/repos/${githubRepo}/git/tags/${currentSha}`);
152
+ if (tagResponse.ok) {
153
+ const tagData = await tagResponse.json();
154
+ currentSha = tagData.object.sha;
155
+ }
156
+ }
157
+ return {
158
+ valid: currentSha === expectedSha,
159
+ currentSha,
160
+ };
136
161
  }
137
- const dir = dirname(destPath);
138
- if (!existsSync(dir)) {
139
- await mkdir(dir, { recursive: true });
162
+ catch {
163
+ // If we can't verify, proceed with caution
164
+ return { valid: true };
140
165
  }
141
- const fileStream = createWriteStream(destPath);
142
- await pipeline(Readable.fromWeb(response.body), fileStream);
143
166
  }
144
167
  /**
145
- * Add a dependency to package.json
168
+ * Add a dependency to package.json using git URL format
146
169
  */
147
- async function addToPackageJson(packageName, version, baseDir) {
170
+ async function addToPackageJson(packageName, githubRepo, gitTag, baseDir) {
148
171
  const packageJsonPath = join(baseDir, 'package.json');
149
172
  const content = await readFile(packageJsonPath, 'utf-8');
150
173
  const pkg = JSON.parse(content);
151
174
  if (!pkg.dependencies) {
152
175
  pkg.dependencies = {};
153
176
  }
154
- // Use a special prefix to indicate it's a TrikHub package
155
- pkg.dependencies[packageName] = `trikhub:${version}`;
177
+ // Use GitHub shorthand format - clean and npm/pnpm compatible
178
+ pkg.dependencies[packageName] = `github:${githubRepo}#${gitTag}`;
156
179
  await writeFile(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
157
180
  }
158
181
  /**
@@ -172,10 +195,9 @@ async function tryNpmInstall(pm, packageSpec, baseDir) {
172
195
  return { success: false, notFound: isNotFound };
173
196
  }
174
197
  /**
175
- * Install from TrikHub registry (GitHub releases)
176
- * Extracts directly to node_modules since TrikHub tarballs may not be npm-compatible
198
+ * Install from TrikHub registry using git URLs
177
199
  */
178
- async function installFromTrikhub(packageName, requestedVersion, baseDir, spinner) {
200
+ async function installFromTrikhub(packageName, requestedVersion, baseDir, pm, spinner) {
179
201
  // Fetch trik info from TrikHub registry
180
202
  spinner.text = `Fetching ${chalk.cyan(packageName)} from TrikHub registry...`;
181
203
  const trikInfo = await registry.getTrik(packageName);
@@ -212,66 +234,33 @@ async function installFromTrikhub(packageName, requestedVersion, baseDir, spinne
212
234
  spinner.fail(`Version ${chalk.red(versionToInstall)} not found for ${packageName}`);
213
235
  return { success: false };
214
236
  }
215
- // Download the tarball
216
- spinner.text = `Downloading ${chalk.cyan(packageName)}@${versionToInstall}...`;
217
- const tempDir = join(baseDir, '.trikhub', '.tmp');
218
- const tarballPath = join(tempDir, `${packageName.replace('/', '-')}-${versionToInstall}.tgz`);
219
- try {
220
- await downloadFile(versionInfo.tarballUrl, tarballPath);
221
- // Extract directly to node_modules
222
- spinner.text = `Installing ${chalk.cyan(packageName)}@${versionToInstall}...`;
223
- // Create the target directory in node_modules
224
- const nodeModulesPath = join(baseDir, 'node_modules');
225
- const packagePath = join(nodeModulesPath, ...packageName.split('/'));
226
- // Ensure parent directories exist (for scoped packages)
227
- if (packageName.startsWith('@')) {
228
- const scopeDir = join(nodeModulesPath, packageName.split('/')[0]);
229
- if (!existsSync(scopeDir)) {
230
- mkdirSync(scopeDir, { recursive: true });
231
- }
232
- }
233
- // Remove existing installation if present
234
- if (existsSync(packagePath)) {
235
- rmSync(packagePath, { recursive: true, force: true });
237
+ // Verify the commit SHA hasn't changed (security check)
238
+ spinner.text = `Verifying ${chalk.cyan(packageName)}@${versionToInstall}...`;
239
+ const verification = await verifyGitHubTagSha(trikInfo.githubRepo, versionInfo.gitTag, versionInfo.commitSha);
240
+ if (!verification.valid) {
241
+ spinner.fail(`Security warning: Tag ${versionInfo.gitTag} has been modified!`);
242
+ console.log(chalk.red('\nThe git tag no longer points to the same commit as when it was published.'));
243
+ console.log(chalk.dim(` Expected SHA: ${versionInfo.commitSha}`));
244
+ if (verification.currentSha) {
245
+ console.log(chalk.dim(` Current SHA: ${verification.currentSha}`));
236
246
  }
237
- // Create target directory
238
- mkdirSync(packagePath, { recursive: true });
239
- // Extract tarball to the package directory
240
- await tar.extract({
241
- file: tarballPath,
242
- cwd: packagePath,
243
- });
244
- // Create a minimal package.json if one doesn't exist
245
- const pkgJsonPath = join(packagePath, 'package.json');
246
- if (!existsSync(pkgJsonPath)) {
247
- const minimalPkg = {
248
- name: packageName,
249
- version: versionToInstall,
250
- description: trikInfo.description || `TrikHub package: ${packageName}`,
251
- };
252
- await writeFile(pkgJsonPath, JSON.stringify(minimalPkg, null, 2) + '\n', 'utf-8');
253
- }
254
- // Add to package.json dependencies
255
- await addToPackageJson(packageName, versionToInstall, baseDir);
256
- // Report download for analytics
257
- registry.reportDownload(packageName, versionToInstall);
258
- return { success: true, version: versionToInstall };
247
+ console.log(chalk.red('\nThis could indicate tampering. Aborting installation.'));
248
+ return { success: false };
259
249
  }
260
- finally {
261
- // Clean up tarball
262
- if (existsSync(tarballPath)) {
263
- rmSync(tarballPath, { force: true });
264
- }
265
- // Clean up temp dir if empty
266
- if (existsSync(tempDir)) {
267
- try {
268
- rmSync(tempDir, { recursive: true, force: true });
269
- }
270
- catch {
271
- // Ignore cleanup errors
272
- }
273
- }
250
+ // Add to package.json using git URL format
251
+ spinner.text = `Adding ${chalk.cyan(packageName)}@${versionToInstall} to package.json...`;
252
+ await addToPackageJson(packageName, trikInfo.githubRepo, versionInfo.gitTag, baseDir);
253
+ // Run package manager install
254
+ spinner.text = `Installing ${chalk.cyan(packageName)}@${versionToInstall}...`;
255
+ const installResult = await runCommand(pm, ['install'], baseDir, { silent: true });
256
+ if (installResult.code !== 0) {
257
+ spinner.fail(`Failed to install ${packageName}`);
258
+ console.log(chalk.dim(installResult.stderr));
259
+ return { success: false };
274
260
  }
261
+ // Report download for analytics
262
+ registry.reportDownload(packageName, versionToInstall);
263
+ return { success: true, version: versionToInstall };
275
264
  }
276
265
  export async function installCommand(trikInput, options) {
277
266
  const spinner = ora();
@@ -314,7 +303,7 @@ export async function installCommand(trikInput, options) {
314
303
  else if (npmResult.notFound) {
315
304
  // Not on npm, try TrikHub registry
316
305
  spinner.text = `Not found on npm, checking TrikHub registry...`;
317
- const trikhubResult = await installFromTrikhub(packageName, versionSpec, baseDir, spinner);
306
+ const trikhubResult = await installFromTrikhub(packageName, versionSpec, baseDir, pm, spinner);
318
307
  if (trikhubResult.success) {
319
308
  spinner.succeed(`Installed ${chalk.green(packageName)}@${trikhubResult.version} from TrikHub`);
320
309
  installed = true;
@@ -334,7 +323,8 @@ export async function installCommand(trikInput, options) {
334
323
  spinner.start('Checking if package is a trik...');
335
324
  const packagePath = join(baseDir, 'node_modules', ...packageName.split('/'));
336
325
  if (await isTrikPackage(packagePath)) {
337
- await addTrikToConfig(packageName, baseDir);
326
+ // Pass version for TrikHub packages (for sync/upgrade tracking)
327
+ await addTrikToConfig(packageName, baseDir, installedVersion);
338
328
  spinner.succeed(`Registered ${chalk.green(packageName)} as a trik`);
339
329
  console.log();
340
330
  console.log(chalk.dim(` Added to: package.json`));