@torknetwork/cli
Advanced tools
+13
-10
@@ -126,3 +126,3 @@ import { Command } from '@oclif/core'; | ||
| checkSdk() { | ||
| const base = { id: 'sdk', name: 'tork-governance SDK installed in project' }; | ||
| const base = { id: 'sdk', name: 'Tork SDK installed in project (tork-governance or @torknetwork/sdk)' }; | ||
| const cwd = process.cwd(); | ||
@@ -133,10 +133,13 @@ const detected = detectStack(cwd); | ||
| } | ||
| const versions = []; | ||
| const found = []; | ||
| if (detected.node) { | ||
| const probe = spawnSync('npm', ['ls', 'tork-governance', '--depth=0', '--json'], { cwd, encoding: 'utf8' }); | ||
| const probe = spawnSync('npm', ['ls', 'tork-governance', '@torknetwork/sdk', '--depth=0', '--json'], { cwd, encoding: 'utf8' }); | ||
| try { | ||
| const parsed = JSON.parse(probe.stdout || '{}'); | ||
| const version = parsed.dependencies?.['tork-governance']?.version; | ||
| if (version !== undefined) | ||
| versions.push(`npm ${version}`); | ||
| const cloud = parsed.dependencies?.['@torknetwork/sdk']?.version; | ||
| const local = parsed.dependencies?.['tork-governance']?.version; | ||
| if (cloud !== undefined) | ||
| found.push(`@torknetwork/sdk ${cloud} — cloud mode, receipts go to tork.network`); | ||
| if (local !== undefined) | ||
| found.push(`tork-governance ${local} — local mode, on-device only (dashboard stays empty)`); | ||
| } | ||
@@ -153,8 +156,8 @@ catch { | ||
| if (match?.[1] !== undefined) | ||
| versions.push(`pip ${match[1]}`); | ||
| found.push(`tork-governance ${match[1]} (pip) — local mode, on-device only`); | ||
| } | ||
| } | ||
| if (versions.length > 0) | ||
| return { ...base, detail: versions.join(', '), status: 'pass' }; | ||
| return { ...base, detail: 'not installed — run tork init', status: 'warn' }; | ||
| if (found.length > 0) | ||
| return { ...base, detail: found.join('; '), status: 'pass' }; | ||
| return { ...base, detail: 'neither tork-governance nor @torknetwork/sdk installed — run tork init', status: 'warn' }; | ||
| } | ||
@@ -161,0 +164,0 @@ checkNodeVersion() { |
| import { Command } from '@oclif/core'; | ||
| type Lang = 'node' | 'python'; | ||
| type Mode = 'cloud' | 'local'; | ||
| interface InitResult { | ||
| lang: Lang; | ||
| mode: Mode; | ||
| sdkInstalled: boolean; | ||
@@ -15,6 +17,11 @@ configWritten: boolean; | ||
| lang: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>; | ||
| mode: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>; | ||
| yes: import("@oclif/core/interfaces").BooleanFlag<boolean>; | ||
| }; | ||
| run(): Promise<InitResult>; | ||
| /** Cloud governance is Node-only: tork-governance on PyPI is on-device and never calls tork.network. */ | ||
| private resolveMode; | ||
| private installSdk; | ||
| /** Copy-paste snippet matching the installed SDK — verified against the published packages. */ | ||
| private logSnippet; | ||
| /** Returns true if the file was written; never overwrites an existing config. */ | ||
@@ -21,0 +28,0 @@ private writeProjectConfig; |
+92
-15
@@ -19,6 +19,14 @@ import { select } from '@inquirer/prompts'; | ||
| static enableJsonFlag = true; | ||
| static examples = ['<%= config.bin %> init', '<%= config.bin %> init --yes --lang node']; | ||
| static examples = [ | ||
| '<%= config.bin %> init', | ||
| '<%= config.bin %> init --mode cloud', | ||
| '<%= config.bin %> init --yes --lang node --mode local', | ||
| ]; | ||
| static flags = { | ||
| lang: Flags.string({ description: 'skip stack detection', options: ['node', 'python'] }), | ||
| yes: Flags.boolean({ char: 'y', description: 'skip prompts (defaults to Node when both stacks are present)' }), | ||
| mode: Flags.string({ | ||
| description: 'cloud = governance via tork.network (receipts + dashboard, needs an API key); local = on-device PII detection only (no key, dashboard stays empty)', | ||
| options: ['cloud', 'local'], | ||
| }), | ||
| yes: Flags.boolean({ char: 'y', description: 'skip prompts (defaults to Node and cloud mode when unspecified)' }), | ||
| }; | ||
@@ -39,6 +47,6 @@ async run() { | ||
| choices: [ | ||
| { name: 'Node (npm install tork-governance)', value: 'node' }, | ||
| { name: 'Python (pip install tork-governance)', value: 'python' }, | ||
| { name: 'Node', value: 'node' }, | ||
| { name: 'Python (local mode only — the cloud SDK is Node-only today)', value: 'python' }, | ||
| ], | ||
| message: 'Both Node and Python detected — which SDK should I install?', | ||
| message: 'Both Node and Python detected — which language is this project?', | ||
| }); | ||
@@ -60,2 +68,3 @@ } | ||
| } | ||
| const mode = await this.resolveMode(lang, flags.mode, flags.yes); | ||
| const aiDeps = lang === 'node' ? detected.nodeAiDeps : detected.pythonAiDeps; | ||
@@ -65,8 +74,9 @@ if (aiDeps.length > 0) { | ||
| } | ||
| const sdkInstalled = this.installSdk(lang, cwd); | ||
| const sdkInstalled = this.installSdk(lang, mode, cwd); | ||
| const configPath = join(cwd, 'tork.config.json'); | ||
| const configWritten = this.writeProjectConfig(configPath); | ||
| if (configWritten) { | ||
| this.log(''); | ||
| this.log(`${ARROW} Next steps:`); | ||
| this.logSnippet(lang, mode); | ||
| this.log(''); | ||
| this.log(`${ARROW} Next steps:`); | ||
| if (mode === 'cloud') { | ||
| this.log(` 1. ${chalk.bold('tork login')} — authenticate with your Tork API key`); | ||
@@ -76,5 +86,43 @@ this.log(` 2. ${chalk.bold('tork test')} — send a governed test request end to end`); | ||
| } | ||
| return { configPath, configWritten, lang, sdkInstalled }; | ||
| else { | ||
| this.log(` 1. ${chalk.bold('tork doctor')} — verify your setup (local mode needs no API key)`); | ||
| this.log(` Local mode never sends anything to tork.network, so your dashboard stays empty.`); | ||
| this.log(` Re-run ${chalk.bold('tork init --mode cloud')} when you want receipts and an audit trail.`); | ||
| } | ||
| return { configPath, configWritten, lang, mode, sdkInstalled }; | ||
| } | ||
| installSdk(lang, cwd) { | ||
| /** Cloud governance is Node-only: tork-governance on PyPI is on-device and never calls tork.network. */ | ||
| async resolveMode(lang, flagMode, yes) { | ||
| if (lang === 'python') { | ||
| if (flagMode === 'cloud') { | ||
| this.log(`${CROSS} Cloud mode is not available for Python — tork-governance on PyPI runs on-device only.`); | ||
| this.log(' Use --mode local for Python, or --lang node to govern via tork.network with @torknetwork/sdk.'); | ||
| this.exit(1); | ||
| } | ||
| if (flagMode === undefined) { | ||
| this.log(`${ARROW} Python runs in local mode (on-device) — cloud governance is Node-only today.`); | ||
| } | ||
| return 'local'; | ||
| } | ||
| if (flagMode !== undefined) | ||
| return flagMode; | ||
| if (yes) { | ||
| this.log(`${ARROW} Defaulting to cloud mode (--yes). Re-run with --mode local for on-device-only governance.`); | ||
| return 'cloud'; | ||
| } | ||
| return select({ | ||
| choices: [ | ||
| { | ||
| name: 'Cloud (recommended) — governs via tork.network: real receipts + audit trail in your dashboard, needs an API key', | ||
| value: 'cloud', | ||
| }, | ||
| { | ||
| name: 'Local — on-device PII detection: no API key and works offline, but nothing reaches tork.network so your dashboard stays empty', | ||
| value: 'local', | ||
| }, | ||
| ], | ||
| message: 'How should this project run governance?', | ||
| }); | ||
| } | ||
| installSdk(lang, mode, cwd) { | ||
| // In --json mode child output must not pollute stdout | ||
@@ -85,5 +133,6 @@ const stdio = this.jsonEnabled() ? 'pipe' : 'inherit'; | ||
| if (lang === 'node') { | ||
| manualCommand = 'npm install tork-governance'; | ||
| this.log(`${ARROW} Installing tork-governance via npm…`); | ||
| status = spawnSync('npm', ['install', 'tork-governance'], { cwd, stdio }).status; | ||
| const pkg = mode === 'cloud' ? '@torknetwork/sdk' : 'tork-governance'; | ||
| manualCommand = `npm install ${pkg}`; | ||
| this.log(`${ARROW} Installing ${pkg} via npm…`); | ||
| status = spawnSync('npm', ['install', pkg], { cwd, stdio }).status; | ||
| } | ||
@@ -102,3 +151,3 @@ else { | ||
| if (status === 0) { | ||
| this.log(`${CHECK} Installed tork-governance (${lang})`); | ||
| this.log(`${CHECK} Installed ${lang === 'node' && mode === 'cloud' ? '@torknetwork/sdk' : 'tork-governance'} (${lang}, ${mode} mode)`); | ||
| return true; | ||
@@ -109,2 +158,30 @@ } | ||
| } | ||
| /** Copy-paste snippet matching the installed SDK — verified against the published packages. */ | ||
| logSnippet(lang, mode) { | ||
| this.log(''); | ||
| if (lang === 'python') { | ||
| this.log(`${ARROW} Try it (local, on-device — nothing is sent to tork.network):`); | ||
| this.log(chalk.dim(' from tork_governance import Tork')); | ||
| this.log(chalk.dim('')); | ||
| this.log(chalk.dim(' tork = Tork()')); | ||
| this.log(chalk.dim(' result = tork.govern("My email is jane@example.com")')); | ||
| this.log(chalk.dim(' print(result.action, result.output)')); | ||
| } | ||
| else if (mode === 'cloud') { | ||
| this.log(`${ARROW} Try it (save as govern.mjs, then run: TORK_API_KEY=tork_… node govern.mjs):`); | ||
| this.log(chalk.dim(" import {TorkClient} from '@torknetwork/sdk'")); | ||
| this.log(chalk.dim('')); | ||
| this.log(chalk.dim(' const tork = new TorkClient({apiKey: process.env.TORK_API_KEY})')); | ||
| this.log(chalk.dim(" const result = await tork.govern('My email is jane@example.com', {agentId: 'default-agent'})")); | ||
| this.log(chalk.dim(' console.log(result.action, result.receipt_id) // receipt appears in your tork.network dashboard')); | ||
| } | ||
| else { | ||
| this.log(`${ARROW} Try it (save as govern.mjs, then run: node govern.mjs — on-device, nothing is sent to tork.network):`); | ||
| this.log(chalk.dim(" import {Tork} from 'tork-governance'")); | ||
| this.log(chalk.dim('')); | ||
| this.log(chalk.dim(' const tork = new Tork()')); | ||
| this.log(chalk.dim(" const result = tork.govern('My email is jane@example.com')")); | ||
| this.log(chalk.dim(' console.log(result.action, result.output)')); | ||
| } | ||
| } | ||
| /** Returns true if the file was written; never overwrites an existing config. */ | ||
@@ -111,0 +188,0 @@ writeProjectConfig(configPath) { |
+1
-1
| { | ||
| "name": "@torknetwork/cli", | ||
| "version": "0.2.0", | ||
| "version": "0.3.0", | ||
| "description": "Tork Governance CLI — govern your AI agents from the terminal", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
+10
-4
@@ -49,4 +49,9 @@ # Tork CLI | ||
| Set up Tork in the current project. Detects your stack (Node via `package.json`, Python via `requirements.txt`/`pyproject.toml`), flags known AI dependencies (`openai`, `@anthropic-ai/sdk`, `anthropic`, `langchain`, `@langchain/*`), installs the `tork-governance` SDK with the matching package manager, and writes `tork.config.json`: | ||
| Set up Tork in the current project. Detects your stack (Node via `package.json`, Python via `requirements.txt`/`pyproject.toml`), flags known AI dependencies (`openai`, `@anthropic-ai/sdk`, `anthropic`, `langchain`, `@langchain/*`), asks how you want to govern, installs the matching SDK, prints a runnable snippet, and writes `tork.config.json`: | ||
| - **cloud** *(Node only)* — governance via tork.network: real receipts and an audit trail in your dashboard, needs an API key. Installs `@torknetwork/sdk`. | ||
| - **local** — on-device PII detection and redaction: no API key, works offline, but nothing reaches tork.network so your dashboard stays empty. Installs `tork-governance` (npm or pip). | ||
| Python is local-only today: `tork-governance` on PyPI runs entirely on-device and never calls the cloud, so `--mode cloud` with `--lang python` exits 1 with guidance. | ||
| ```json | ||
@@ -67,3 +72,4 @@ { | ||
| | `--lang node\|python` | Skip stack detection | | ||
| | `--yes`, `-y` | Skip prompts (defaults to Node when both stacks are present) | | ||
| | `--mode cloud\|local` | Skip the governance-mode prompt (CI, scripts) | | ||
| | `--yes`, `-y` | Skip prompts (defaults to Node and cloud mode when unspecified) | | ||
| | `--json` | Machine-readable result | | ||
@@ -124,3 +130,3 @@ | ||
| 6. `tork.config.json` present in the current directory *(warn only)* | ||
| 7. `tork-governance` SDK installed in the project, with version *(warn only)* | ||
| 7. Tork SDK installed in the project — passes for `tork-governance` (local mode) or `@torknetwork/sdk` (cloud mode) and reports which, with version *(warn only)* | ||
| 8. Node.js version ≥ 20 | ||
@@ -174,3 +180,3 @@ | ||
| | `No Node or Python project detected` | Ran `tork init` outside a project | `cd` into your project, or use `--lang` | | ||
| | SDK check warns `not installed` | Install failed or skipped | `npm install tork-governance` / `pip install tork-governance` | | ||
| | SDK check warns `not installed` | Install failed or skipped | `npm install @torknetwork/sdk` (cloud) or `npm install tork-governance` / `pip install tork-governance` (local) | | ||
| | Colored output breaks log parsing | ANSI codes in CI logs | Set `NO_COLOR=1` or use `--json` | | ||
@@ -177,0 +183,0 @@ | `1Password CLI (op) is not installed` | `--op` used without the op CLI | `brew install 1password-cli`, then `op signin` | |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
82075
7.77%1634
5.62%194
3.19%