gosuper 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GoSuper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,29 @@
1
+ gosuper
2
+ =======
3
+
4
+ This package contains code adapted from the following open-source projects:
5
+
6
+ vercel-labs/skills (https://github.com/vercel-labs/skills)
7
+ Licensed under MIT.
8
+ Adapted files in this package: src/agents.ts, src/source-parser.ts,
9
+ src/sanitize.ts, src/frontmatter.ts.
10
+
11
+ Copyright (c) Vercel, Inc.
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # gosuper
2
+
3
+ The GoSuper bundle installer.
4
+
5
+ ```bash
6
+ npx gosuper install <license-key>
7
+ ```
8
+
9
+ Installs your purchased GoSuper bundle into every coding agent it detects on
10
+ your machine (Claude Code, Cursor, Codex, OpenCode, Gemini CLI, …).
11
+
12
+ ## Commands
13
+
14
+ | Command | Description |
15
+ | ----------------------------- | --------------------------------------------------- |
16
+ | `gosuper install <key>` | Install (or reinstall) the bundle bound to `<key>`. |
17
+ | `gosuper update` | Pull the latest published version. |
18
+ | `gosuper list` | List skills in your bundle. |
19
+ | `gosuper info` | Show license + bundle metadata. |
20
+ | `gosuper status` | Verify local copies match the server manifest. |
21
+ | `gosuper uninstall` | Remove all bundle skills from every agent. |
22
+ | `gosuper help` | Print this help. |
23
+
24
+ ## Where things land
25
+
26
+ | Agent | Path |
27
+ | ------------ | -------------------------------------- |
28
+ | Claude Code | `~/.claude/skills/<bundle>-*` |
29
+ | Cursor | `~/.cursor/skills/<bundle>-*` |
30
+ | Codex | `~/.codex/skills/<bundle>-*` |
31
+ | OpenCode | `~/.config/opencode/skills/<bundle>-*` |
32
+ | Gemini CLI | `~/.gemini/skills/<bundle>-*` |
33
+ | Universal | `.agents/skills/<bundle>-*` (project) |
34
+
35
+ Lock file (license + installed agents): `~/.config/gosuper/lock.json`.
36
+
37
+ ## Env overrides
38
+
39
+ | Variable | Effect |
40
+ | -------------------- | ------------------------------------------- |
41
+ | `GOSUPER_API_BASE` | Override API base (default `gosuper.ai/api/v1`). |
42
+ | `GOSUPER_CONFIG_DIR` | Override `~/.config/gosuper/`. |
43
+ | `GOSUPER_AGENTS` | Comma-separated agent allowlist (default: auto-detect). |
44
+
45
+ ## Acknowledgements
46
+
47
+ The agent registry, source parser, and SKILL.md frontmatter parser are adapted
48
+ from [vercel-labs/skills](https://github.com/vercel-labs/skills) (MIT). See
49
+ `NOTICE` for full attribution.
package/bin/cli.mjs ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { run } from '../dist/cli.js';
3
+
4
+ run(process.argv.slice(2)).catch((err) => {
5
+ console.error(err instanceof Error ? err.message : String(err));
6
+ process.exit(1);
7
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Agent registry.
3
+ *
4
+ * Adapted from vercel-labs/skills (MIT) — src/agents.ts.
5
+ * Trimmed to the agents GoSuper actively supports. Add more by extending
6
+ * the AgentType union in ./types.ts and adding a config entry below.
7
+ */
8
+ import type { AgentConfig, AgentType } from './types.js';
9
+ export declare const agents: Record<AgentType, AgentConfig>;
10
+ /**
11
+ * Detect which agents appear to be installed on this machine.
12
+ * Always returns at least `universal` so `gosuper install` works on a fresh box.
13
+ */
14
+ export declare function detectInstalledAgents(): AgentType[];
15
+ export declare function parseAgentList(value: string | undefined): AgentType[] | null;
package/dist/agents.js ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Agent registry.
3
+ *
4
+ * Adapted from vercel-labs/skills (MIT) — src/agents.ts.
5
+ * Trimmed to the agents GoSuper actively supports. Add more by extending
6
+ * the AgentType union in ./types.ts and adding a config entry below.
7
+ */
8
+ var _a, _b, _c;
9
+ import { existsSync } from 'node:fs';
10
+ import { homedir } from 'node:os';
11
+ import { join } from 'node:path';
12
+ const home = homedir();
13
+ const xdgConfigHome = ((_a = process.env.XDG_CONFIG_HOME) === null || _a === void 0 ? void 0 : _a.trim()) || join(home, '.config');
14
+ const claudeHome = ((_b = process.env.CLAUDE_CONFIG_DIR) === null || _b === void 0 ? void 0 : _b.trim()) || join(home, '.claude');
15
+ const codexHome = ((_c = process.env.CODEX_HOME) === null || _c === void 0 ? void 0 : _c.trim()) || join(home, '.codex');
16
+ export const agents = {
17
+ 'claude-code': {
18
+ name: 'claude-code',
19
+ displayName: 'Claude Code',
20
+ projectSkillsDir: '.claude/skills',
21
+ globalSkillsDir: join(claudeHome, 'skills'),
22
+ detect: () => existsSync(claudeHome),
23
+ },
24
+ cursor: {
25
+ name: 'cursor',
26
+ displayName: 'Cursor',
27
+ projectSkillsDir: '.agents/skills',
28
+ globalSkillsDir: join(home, '.cursor/skills'),
29
+ detect: () => existsSync(join(home, '.cursor')),
30
+ },
31
+ codex: {
32
+ name: 'codex',
33
+ displayName: 'Codex',
34
+ projectSkillsDir: '.agents/skills',
35
+ globalSkillsDir: join(codexHome, 'skills'),
36
+ detect: () => existsSync(codexHome),
37
+ },
38
+ opencode: {
39
+ name: 'opencode',
40
+ displayName: 'OpenCode',
41
+ projectSkillsDir: '.agents/skills',
42
+ globalSkillsDir: join(xdgConfigHome, 'opencode', 'skills'),
43
+ detect: () => existsSync(join(xdgConfigHome, 'opencode')),
44
+ },
45
+ 'gemini-cli': {
46
+ name: 'gemini-cli',
47
+ displayName: 'Gemini CLI',
48
+ projectSkillsDir: '.agents/skills',
49
+ globalSkillsDir: join(home, '.gemini/skills'),
50
+ detect: () => existsSync(join(home, '.gemini')),
51
+ },
52
+ universal: {
53
+ name: 'universal',
54
+ displayName: 'Universal (.agents/skills)',
55
+ projectSkillsDir: '.agents/skills',
56
+ globalSkillsDir: join(xdgConfigHome, 'agents', 'skills'),
57
+ detect: () => true,
58
+ },
59
+ };
60
+ /**
61
+ * Detect which agents appear to be installed on this machine.
62
+ * Always returns at least `universal` so `gosuper install` works on a fresh box.
63
+ */
64
+ export function detectInstalledAgents() {
65
+ const detected = Object.keys(agents).filter((name) => name !== 'universal' && agents[name].detect());
66
+ if (detected.length === 0)
67
+ return ['universal'];
68
+ return detected;
69
+ }
70
+ export function parseAgentList(value) {
71
+ if (!value)
72
+ return null;
73
+ const requested = value
74
+ .split(',')
75
+ .map((s) => s.trim().toLowerCase())
76
+ .filter(Boolean);
77
+ const valid = [];
78
+ for (const name of requested) {
79
+ if (name in agents)
80
+ valid.push(name);
81
+ else
82
+ throw new Error(`Unknown agent: ${name}`);
83
+ }
84
+ return valid;
85
+ }
package/dist/api.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Thin HTTP client for the GoSuper API.
3
+ */
4
+ import type { InfoResponse, ManifestResponse, ResolveResponse } from './types.js';
5
+ export declare function getApiBase(): string;
6
+ export declare function assertKey(key: string): void;
7
+ export declare function resolveBundle(key: string): Promise<ResolveResponse>;
8
+ export declare function getInfo(key: string): Promise<InfoResponse>;
9
+ export declare function getManifest(key: string): Promise<ManifestResponse>;
10
+ export declare function postEvent(key: string, type: 'install' | 'update' | 'status' | 'list' | 'info' | 'uninstall' | 'error', meta?: Record<string, unknown>): Promise<void>;
package/dist/api.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Thin HTTP client for the GoSuper API.
3
+ */
4
+ const KEY_REGEX = /^[a-zA-Z0-9_-]{16,64}$/;
5
+ export function getApiBase() {
6
+ var _a, _b;
7
+ return ((_b = (_a = process.env.GOSUPER_API_BASE) === null || _a === void 0 ? void 0 : _a.trim().replace(/\/$/, '')) !== null && _b !== void 0 ? _b : 'https://gosuper.ai/api/v1');
8
+ }
9
+ export function assertKey(key) {
10
+ if (!KEY_REGEX.test(key)) {
11
+ throw new Error(`Invalid license key. Expected 16–64 chars of [a-zA-Z0-9_-], got ${key.length}.`);
12
+ }
13
+ }
14
+ async function getJson(path) {
15
+ const url = `${getApiBase()}${path}`;
16
+ const res = await fetch(url, {
17
+ headers: { Accept: 'application/json', 'User-Agent': 'gosuper-cli' },
18
+ });
19
+ if (!res.ok) {
20
+ let message = `${res.status} ${res.statusText}`;
21
+ try {
22
+ const body = (await res.json());
23
+ if (body === null || body === void 0 ? void 0 : body.error)
24
+ message = body.error;
25
+ }
26
+ catch (_a) {
27
+ /* ignore */
28
+ }
29
+ throw new Error(`API error: ${message}`);
30
+ }
31
+ return (await res.json());
32
+ }
33
+ export function resolveBundle(key) {
34
+ assertKey(key);
35
+ return getJson(`/resolve/${encodeURIComponent(key)}`);
36
+ }
37
+ export function getInfo(key) {
38
+ assertKey(key);
39
+ return getJson(`/info/${encodeURIComponent(key)}`);
40
+ }
41
+ export function getManifest(key) {
42
+ assertKey(key);
43
+ return getJson(`/manifest/${encodeURIComponent(key)}`);
44
+ }
45
+ export async function postEvent(key, type, meta = {}) {
46
+ // Best-effort — never throw. Telemetry must not block lifecycle commands.
47
+ if (!KEY_REGEX.test(key))
48
+ return;
49
+ try {
50
+ const url = `${getApiBase()}/install/${encodeURIComponent(key)}/event`;
51
+ await fetch(url, {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json',
55
+ 'User-Agent': 'gosuper-cli',
56
+ },
57
+ body: JSON.stringify({ type, meta }),
58
+ });
59
+ }
60
+ catch (_a) {
61
+ /* ignore */
62
+ }
63
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Argv dispatcher for `npx gosuper`.
3
+ *
4
+ * Subcommands: install, info, list, status, update, uninstall, help, version.
5
+ * No external CLI framework — keeps zero runtime dependencies.
6
+ */
7
+ export declare function run(argv: string[]): Promise<void>;
package/dist/cli.js ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Argv dispatcher for `npx gosuper`.
3
+ *
4
+ * Subcommands: install, info, list, status, update, uninstall, help, version.
5
+ * No external CLI framework — keeps zero runtime dependencies.
6
+ */
7
+ import { runInfo, runInstall, runList, runStatus, runUninstall, runUpdate, } from './commands.js';
8
+ import { fail } from './output.js';
9
+ const VERSION = '0.1.0';
10
+ const HELP = `gosuper · install and manage GoSuper skill bundles
11
+
12
+ Usage:
13
+ gosuper install <license-key> [--agents <list>] [--project]
14
+ gosuper update [--key <license>]
15
+ gosuper status [--key <license>]
16
+ gosuper list [--key <license>]
17
+ gosuper info [--key <license>]
18
+ gosuper uninstall [--key <license>]
19
+ gosuper version
20
+ gosuper help
21
+
22
+ Options:
23
+ --agents <list> Comma-separated agent allowlist (default: auto-detect).
24
+ Valid: claude-code, cursor, codex, opencode, gemini-cli, universal.
25
+ --project Install to project (.agents/skills/) instead of user-home.
26
+ --key <license> Target a specific license (only needed if you have several).
27
+
28
+ Environment:
29
+ GOSUPER_API_BASE Override API base URL (default: https://gosuper.ai/api/v1).
30
+ GOSUPER_AGENTS Comma-separated agent allowlist (overridden by --agents).
31
+ GOSUPER_CONFIG_DIR Override ~/.config/gosuper/.
32
+
33
+ Documentation: https://gosuper.ai
34
+ Support: hello@gosuper.ai
35
+ `;
36
+ function parseArgs(argv) {
37
+ const positional = [];
38
+ const flags = {};
39
+ for (let i = 0; i < argv.length; i += 1) {
40
+ const arg = argv[i];
41
+ if (arg.startsWith('--')) {
42
+ const name = arg.slice(2);
43
+ const next = argv[i + 1];
44
+ if (next !== undefined && !next.startsWith('--')) {
45
+ flags[name] = next;
46
+ i += 1;
47
+ }
48
+ else {
49
+ flags[name] = true;
50
+ }
51
+ }
52
+ else {
53
+ positional.push(arg);
54
+ }
55
+ }
56
+ return { positional, flags };
57
+ }
58
+ export async function run(argv) {
59
+ var _a;
60
+ const { positional, flags } = parseArgs(argv);
61
+ const command = (_a = positional[0]) !== null && _a !== void 0 ? _a : 'help';
62
+ if (flags.help === true || flags.h === true) {
63
+ console.log(HELP);
64
+ return;
65
+ }
66
+ switch (command) {
67
+ case 'install': {
68
+ const key = positional[1];
69
+ if (!key)
70
+ fail('Missing license key. Usage: gosuper install <license-key>');
71
+ await runInstall({
72
+ key: key,
73
+ agentsArg: typeof flags.agents === 'string' ? flags.agents : undefined,
74
+ global: flags.project !== true,
75
+ });
76
+ return;
77
+ }
78
+ case 'info':
79
+ await runInfo({ key: stringFlag(flags, 'key') });
80
+ return;
81
+ case 'list':
82
+ await runList({ key: stringFlag(flags, 'key') });
83
+ return;
84
+ case 'status':
85
+ await runStatus({ key: stringFlag(flags, 'key') });
86
+ return;
87
+ case 'update':
88
+ await runUpdate({ key: stringFlag(flags, 'key') });
89
+ return;
90
+ case 'uninstall':
91
+ await runUninstall({
92
+ key: stringFlag(flags, 'key'),
93
+ force: flags.force === true,
94
+ });
95
+ return;
96
+ case 'version':
97
+ case '--version':
98
+ case '-v':
99
+ console.log(`gosuper ${VERSION}`);
100
+ return;
101
+ case 'help':
102
+ case '--help':
103
+ case '-h':
104
+ console.log(HELP);
105
+ return;
106
+ default:
107
+ fail(`Unknown command: ${command}\n\n${HELP}`);
108
+ }
109
+ }
110
+ function stringFlag(flags, name) {
111
+ const v = flags[name];
112
+ return typeof v === 'string' ? v : undefined;
113
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Lifecycle commands wired by cli.ts.
3
+ *
4
+ * Each command is independent; they share the lock file and the API client.
5
+ */
6
+ export interface BaseOpts {
7
+ /** Optional license key. Defaults to the only license in lock.json, if any. */
8
+ key?: string;
9
+ }
10
+ export declare function runInstall(opts: {
11
+ key: string;
12
+ agentsArg?: string;
13
+ global?: boolean;
14
+ }): Promise<void>;
15
+ export declare function runInfo(opts: BaseOpts): Promise<void>;
16
+ export declare function runList(opts: BaseOpts): Promise<void>;
17
+ export declare function runStatus(opts: BaseOpts): Promise<void>;
18
+ export declare function runUpdate(opts: BaseOpts): Promise<void>;
19
+ export declare function runUninstall(opts: BaseOpts & {
20
+ force?: boolean;
21
+ }): Promise<void>;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Lifecycle commands wired by cli.ts.
3
+ *
4
+ * Each command is independent; they share the lock file and the API client.
5
+ */
6
+ import { agents } from './agents.js';
7
+ import { getInfo, getManifest, postEvent } from './api.js';
8
+ import { install } from './install.js';
9
+ import { getLicense, listLicenses, maskKey, removeLicense, upsertLicense, } from './lock.js';
10
+ import { bold, dim, ok, say, warn } from './output.js';
11
+ import { existsSync, readdirSync, rmSync, statSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ function pickKey(provided) {
14
+ if (provided)
15
+ return provided;
16
+ const licenses = listLicenses();
17
+ if (licenses.length === 0) {
18
+ throw new Error('No license found. Run `gosuper install <license-key>` first.');
19
+ }
20
+ if (licenses.length > 1) {
21
+ throw new Error(`Multiple licenses installed. Pass one explicitly:\n ${licenses
22
+ .map((l) => `gosuper <command> --key ${maskKey(l.key)}`)
23
+ .join('\n ')}`);
24
+ }
25
+ return licenses[0].key;
26
+ }
27
+ export async function runInstall(opts) {
28
+ await install({
29
+ key: opts.key,
30
+ agentsArg: opts.agentsArg,
31
+ global: opts.global,
32
+ });
33
+ }
34
+ export async function runInfo(opts) {
35
+ var _a, _b, _c, _d;
36
+ const key = pickKey(opts.key);
37
+ const info = await getInfo(key);
38
+ const local = getLicense(key);
39
+ console.log(bold(info.bundle_name));
40
+ if (info.bundle_description)
41
+ console.log(dim(info.bundle_description));
42
+ console.log('');
43
+ row('License', maskKey(key));
44
+ row('Customer', (_a = info.customer_name) !== null && _a !== void 0 ? _a : info.customer_email);
45
+ row('Status', info.status);
46
+ if (info.expires_at)
47
+ row('Expires', new Date(info.expires_at).toLocaleDateString());
48
+ row('Bundle', info.bundle_slug);
49
+ row('Server version', (_b = info.current_version) !== null && _b !== void 0 ? _b : '—');
50
+ row('Installed version', (_d = (_c = local === null || local === void 0 ? void 0 : local.installedVersion) !== null && _c !== void 0 ? _c : info.last_seen_version) !== null && _d !== void 0 ? _d : '—');
51
+ row('Install count', String(info.install_count));
52
+ if (local) {
53
+ row('Agents on this machine', local.agents.join(', '));
54
+ }
55
+ await postEvent(key, 'info');
56
+ }
57
+ export async function runList(opts) {
58
+ const key = pickKey(opts.key);
59
+ const manifest = await getManifest(key);
60
+ console.log(`${bold(manifest.bundle_name)} ${dim(`v${manifest.version} · ${manifest.skill_count} skills`)}`);
61
+ console.log('');
62
+ for (const skill of manifest.skills) {
63
+ const display = skill.slug.replace(`${manifest.bundle_slug}-`, '');
64
+ console.log(` ${display}`);
65
+ }
66
+ await postEvent(key, 'list', { version: manifest.version });
67
+ }
68
+ export async function runStatus(opts) {
69
+ var _a;
70
+ const key = pickKey(opts.key);
71
+ const local = getLicense(key);
72
+ if (!local) {
73
+ throw new Error('Nothing installed locally. Run `gosuper install <key>`.');
74
+ }
75
+ const manifest = await getManifest(key);
76
+ const installedSlugs = new Set(manifest.skills.map((s) => s.slug));
77
+ let drift = 0;
78
+ for (const agentName of local.agents) {
79
+ const paths = (_a = local.paths[agentName]) !== null && _a !== void 0 ? _a : [];
80
+ let present = 0;
81
+ for (const p of paths) {
82
+ if (existsSync(p))
83
+ present += 1;
84
+ else
85
+ drift += 1;
86
+ }
87
+ const expected = paths.length;
88
+ const cfg = agents[agentName];
89
+ const flag = present === expected ? ok : warn;
90
+ flag(`${cfg.displayName.padEnd(28)} ${dim('→')} ${present}/${expected} skills present`);
91
+ }
92
+ if (local.installedVersion !== manifest.version) {
93
+ warn(`Local v${local.installedVersion} → server v${manifest.version}. Run \`gosuper update\`.`);
94
+ }
95
+ else if (drift === 0) {
96
+ ok(`In sync · v${manifest.version}`);
97
+ }
98
+ // Detect "extra" folders in any agent dir starting with our bundle prefix
99
+ // that aren't in the lock — useful when a customer manually copied files.
100
+ for (const agentName of local.agents) {
101
+ const root = agents[agentName].globalSkillsDir;
102
+ if (!existsSync(root))
103
+ continue;
104
+ for (const name of readdirSync(root)) {
105
+ if (!name.startsWith(`${local.bundleSlug}-`))
106
+ continue;
107
+ if (!installedSlugs.has(name)) {
108
+ warn(`Stale folder ${dim(join(root, name))} (no longer in bundle manifest)`);
109
+ }
110
+ }
111
+ }
112
+ await postEvent(key, 'status', {
113
+ version: manifest.version,
114
+ drift,
115
+ });
116
+ }
117
+ export async function runUpdate(opts) {
118
+ var _a;
119
+ const key = pickKey(opts.key);
120
+ const local = getLicense(key);
121
+ if (!local) {
122
+ throw new Error('Nothing installed locally. Run `gosuper install <key>`.');
123
+ }
124
+ const manifest = await getManifest(key);
125
+ if (manifest.version === local.installedVersion) {
126
+ ok(`Already on v${manifest.version}. Nothing to do.`);
127
+ return;
128
+ }
129
+ say(`Updating ${local.bundleSlug}: v${local.installedVersion} → v${manifest.version}`);
130
+ // Reuse the install pipeline. Same agents, same scope.
131
+ await install({
132
+ key,
133
+ agentsArg: local.agents.join(','),
134
+ global: true,
135
+ });
136
+ // Refresh updatedAt; install() already wrote a fresh entry.
137
+ upsertLicense(key, Object.assign(Object.assign({}, ((_a = getLicense(key)) !== null && _a !== void 0 ? _a : {
138
+ bundleSlug: local.bundleSlug,
139
+ installedVersion: manifest.version,
140
+ installedAt: local.installedAt,
141
+ updatedAt: new Date().toISOString(),
142
+ agents: local.agents,
143
+ paths: local.paths,
144
+ })), { updatedAt: new Date().toISOString() }));
145
+ await postEvent(key, 'update', {
146
+ from: local.installedVersion,
147
+ to: manifest.version,
148
+ });
149
+ }
150
+ export async function runUninstall(opts) {
151
+ var _a;
152
+ const key = pickKey(opts.key);
153
+ const local = getLicense(key);
154
+ if (!local) {
155
+ throw new Error('Nothing installed locally for that license.');
156
+ }
157
+ let removed = 0;
158
+ for (const agentName of local.agents) {
159
+ for (const path of (_a = local.paths[agentName]) !== null && _a !== void 0 ? _a : []) {
160
+ if (existsSync(path) && statSync(path).isDirectory()) {
161
+ rmSync(path, { recursive: true, force: true });
162
+ removed += 1;
163
+ }
164
+ }
165
+ }
166
+ removeLicense(key);
167
+ ok(`Removed ${removed} skill folder${removed === 1 ? '' : 's'} for ${local.bundleSlug}`);
168
+ await postEvent(key, 'uninstall', { removed });
169
+ }
170
+ function row(label, value) {
171
+ console.log(` ${label.padEnd(22)} ${value}`);
172
+ }
@@ -0,0 +1,4 @@
1
+ export { run } from './cli.js';
2
+ export { install } from './install.js';
3
+ export { agents, detectInstalledAgents } from './agents.js';
4
+ export type { AgentConfig, AgentType, InfoResponse, LicenseEntry, LockFile, ManifestResponse, ResolveResponse, } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { run } from './cli.js';
2
+ export { install } from './install.js';
3
+ export { agents, detectInstalledAgents } from './agents.js';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Install pipeline:
3
+ * 1. Resolve license → tarball URL + sha256
4
+ * 2. Download tarball to a temp dir
5
+ * 3. Verify sha256
6
+ * 4. Extract via system `tar`
7
+ * 5. Materialize <bundle>-* skill folders into each detected agent's dir
8
+ * 6. Update lock.json + post 'install' event
9
+ */
10
+ export interface InstallOptions {
11
+ /** License key (validated upstream). */
12
+ key: string;
13
+ /** Comma-separated agent allowlist; null/undefined = auto-detect. */
14
+ agentsArg?: string;
15
+ /** Install to user-home dirs (true) vs project dirs (false). Default true. */
16
+ global?: boolean;
17
+ }
18
+ export declare function install(opts: InstallOptions): Promise<void>;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Install pipeline:
3
+ * 1. Resolve license → tarball URL + sha256
4
+ * 2. Download tarball to a temp dir
5
+ * 3. Verify sha256
6
+ * 4. Extract via system `tar`
7
+ * 5. Materialize <bundle>-* skill folders into each detected agent's dir
8
+ * 6. Update lock.json + post 'install' event
9
+ */
10
+ import { agents, detectInstalledAgents, parseAgentList } from './agents.js';
11
+ import { postEvent, resolveBundle } from './api.js';
12
+ import { maskKey, upsertLicense } from './lock.js';
13
+ import { dim, ok, say } from './output.js';
14
+ import { spawnSync } from 'node:child_process';
15
+ import { createHash } from 'node:crypto';
16
+ import { cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, } from 'node:fs';
17
+ import { tmpdir } from 'node:os';
18
+ import { join } from 'node:path';
19
+ import { Readable } from 'node:stream';
20
+ import { pipeline } from 'node:stream/promises';
21
+ export async function install(opts) {
22
+ var _a, _b;
23
+ const requested = parseAgentList((_a = opts.agentsArg) !== null && _a !== void 0 ? _a : process.env.GOSUPER_AGENTS);
24
+ const targetAgents = requested && requested.length > 0 ? requested : detectInstalledAgents();
25
+ const useGlobal = (_b = opts.global) !== null && _b !== void 0 ? _b : true;
26
+ say(`Resolving license ${dim(maskKey(opts.key))}…`);
27
+ const resolved = await resolveBundle(opts.key);
28
+ const { bundle, version } = resolved;
29
+ say(`${bundle.name} v${version.version} · ${version.skill_count} skills · ${formatBytes(version.byte_size)}`);
30
+ const tmpRoot = mkdtempSync(join(tmpdir(), 'gosuper-install-'));
31
+ const tarballPath = join(tmpRoot, `${bundle.slug}.tar.gz`);
32
+ const stagePath = join(tmpRoot, 'stage');
33
+ mkdirSync(stagePath, { recursive: true });
34
+ try {
35
+ say('Downloading bundle…');
36
+ await downloadTo(resolved.tarball_url, tarballPath);
37
+ say('Verifying checksum…');
38
+ const actual = sha256OfFile(tarballPath);
39
+ if (actual !== version.sha256) {
40
+ throw new Error(`Checksum mismatch.\n expected ${version.sha256}\n actual ${actual}`);
41
+ }
42
+ say('Extracting…');
43
+ extractTar(tarballPath, stagePath);
44
+ const skillsRoot = join(stagePath, 'skills');
45
+ if (!existsSync(skillsRoot)) {
46
+ throw new Error('Tarball missing skills/ directory.');
47
+ }
48
+ const skillDirs = readdirSync(skillsRoot)
49
+ .filter((name) => name.startsWith(`${bundle.slug}-`))
50
+ .map((name) => ({ name, full: join(skillsRoot, name) }))
51
+ .filter((s) => statSync(s.full).isDirectory());
52
+ if (skillDirs.length === 0) {
53
+ throw new Error(`Tarball contained no skills with slug prefix "${bundle.slug}-".`);
54
+ }
55
+ const installedPaths = {};
56
+ for (const agentName of targetAgents) {
57
+ const cfg = agents[agentName];
58
+ const targetRoot = useGlobal
59
+ ? cfg.globalSkillsDir
60
+ : join(process.cwd(), cfg.projectSkillsDir);
61
+ mkdirSync(targetRoot, { recursive: true });
62
+ const agentPaths = [];
63
+ for (const skill of skillDirs) {
64
+ const dest = join(targetRoot, skill.name);
65
+ if (existsSync(dest))
66
+ rmSync(dest, { recursive: true, force: true });
67
+ cpSync(skill.full, dest, { recursive: true });
68
+ agentPaths.push(dest);
69
+ }
70
+ installedPaths[agentName] = agentPaths;
71
+ ok(`${cfg.displayName.padEnd(28)} ${dim('→')} ${shortenHome(targetRoot)} (${skillDirs.length})`);
72
+ }
73
+ const now = new Date().toISOString();
74
+ upsertLicense(opts.key, {
75
+ bundleSlug: bundle.slug,
76
+ installedVersion: version.version,
77
+ installedAt: now,
78
+ updatedAt: now,
79
+ agents: targetAgents,
80
+ paths: installedPaths,
81
+ });
82
+ await postEvent(opts.key, 'install', {
83
+ version: version.version,
84
+ installed: skillDirs.length,
85
+ agents: targetAgents,
86
+ via: 'cli',
87
+ });
88
+ console.log('');
89
+ ok(`Installed ${bundle.name} v${version.version} · ${skillDirs.length} skills across ${targetAgents.length} agents`);
90
+ console.log('');
91
+ console.log('Next steps:');
92
+ console.log(' 1. Restart your coding agent so it picks up the new skills');
93
+ console.log(` 2. Run ${dim('gosuper info')} to inspect your license`);
94
+ console.log(` 3. Run ${dim('gosuper update')} any time to pull a newer version`);
95
+ }
96
+ finally {
97
+ rmSync(tmpRoot, { recursive: true, force: true });
98
+ }
99
+ }
100
+ async function downloadTo(url, dest) {
101
+ const res = await fetch(url);
102
+ if (!res.ok || !res.body) {
103
+ throw new Error(`Download failed: ${res.status} ${res.statusText}`);
104
+ }
105
+ // res.body is a Web ReadableStream; convert to Node stream.
106
+ const nodeStream = Readable.fromWeb(res.body);
107
+ await pipeline(nodeStream, createWriteStream(dest));
108
+ }
109
+ function sha256OfFile(path) {
110
+ const buf = readFileSync(path);
111
+ return createHash('sha256').update(buf).digest('hex');
112
+ }
113
+ function extractTar(tarballPath, destDir) {
114
+ var _a, _b;
115
+ const result = spawnSync('tar', ['-xzf', tarballPath, '-C', destDir], {
116
+ stdio: ['ignore', 'pipe', 'pipe'],
117
+ });
118
+ if (result.status !== 0) {
119
+ const stderr = (_b = (_a = result.stderr) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '';
120
+ throw new Error(`tar -xzf failed: ${stderr.trim() || 'unknown error'}`);
121
+ }
122
+ }
123
+ function formatBytes(n) {
124
+ if (n < 1024)
125
+ return `${n} B`;
126
+ if (n < 1024 * 1024)
127
+ return `${(n / 1024).toFixed(1)} KB`;
128
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
129
+ }
130
+ function shortenHome(p) {
131
+ var _a;
132
+ const home = (_a = process.env.HOME) !== null && _a !== void 0 ? _a : '';
133
+ if (home && p.startsWith(home))
134
+ return p.replace(home, '~');
135
+ return p;
136
+ }
package/dist/lock.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Local lock file at ~/.config/gosuper/lock.json.
3
+ *
4
+ * Tracks every license that has installed a bundle on this machine, plus the
5
+ * skill folder paths owned by each license per agent. Used by:
6
+ * - `gosuper info` → list installed licenses
7
+ * - `gosuper update` → know which agents to refresh
8
+ * - `gosuper uninstall` → know which folders to remove (safer than wildcard)
9
+ *
10
+ * Format adapted from vercel-labs/skills' skill-lock.ts (MIT). See NOTICE.
11
+ */
12
+ import type { AgentType, LicenseEntry, LockFile } from './types.js';
13
+ export declare function getConfigDir(): string;
14
+ export declare function getLockPath(): string;
15
+ export declare function readLock(): LockFile;
16
+ export declare function writeLock(lock: LockFile): void;
17
+ export declare function upsertLicense(key: string, entry: LicenseEntry): void;
18
+ export declare function removeLicense(key: string): LicenseEntry | null;
19
+ export declare function getLicense(key: string): LicenseEntry | null;
20
+ export declare function listLicenses(): Array<{
21
+ key: string;
22
+ entry: LicenseEntry;
23
+ }>;
24
+ export declare function maskKey(key: string): string;
25
+ export type { AgentType };
package/dist/lock.js ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Local lock file at ~/.config/gosuper/lock.json.
3
+ *
4
+ * Tracks every license that has installed a bundle on this machine, plus the
5
+ * skill folder paths owned by each license per agent. Used by:
6
+ * - `gosuper info` → list installed licenses
7
+ * - `gosuper update` → know which agents to refresh
8
+ * - `gosuper uninstall` → know which folders to remove (safer than wildcard)
9
+ *
10
+ * Format adapted from vercel-labs/skills' skill-lock.ts (MIT). See NOTICE.
11
+ */
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
13
+ import { homedir } from 'node:os';
14
+ import { dirname, join } from 'node:path';
15
+ const CURRENT_VERSION = 1;
16
+ export function getConfigDir() {
17
+ var _a;
18
+ return (((_a = process.env.GOSUPER_CONFIG_DIR) === null || _a === void 0 ? void 0 : _a.trim()) ||
19
+ join(homedir(), '.config', 'gosuper'));
20
+ }
21
+ export function getLockPath() {
22
+ return join(getConfigDir(), 'lock.json');
23
+ }
24
+ export function readLock() {
25
+ var _a;
26
+ const path = getLockPath();
27
+ if (!existsSync(path)) {
28
+ return { version: CURRENT_VERSION, licenses: {} };
29
+ }
30
+ try {
31
+ const raw = readFileSync(path, 'utf8');
32
+ const parsed = JSON.parse(raw);
33
+ if (parsed.version !== CURRENT_VERSION) {
34
+ // On schema bumps, start fresh rather than partially migrate.
35
+ return { version: CURRENT_VERSION, licenses: {} };
36
+ }
37
+ return {
38
+ version: CURRENT_VERSION,
39
+ licenses: (_a = parsed.licenses) !== null && _a !== void 0 ? _a : {},
40
+ };
41
+ }
42
+ catch (_b) {
43
+ return { version: CURRENT_VERSION, licenses: {} };
44
+ }
45
+ }
46
+ export function writeLock(lock) {
47
+ const path = getLockPath();
48
+ mkdirSync(dirname(path), { recursive: true });
49
+ writeFileSync(path, JSON.stringify(lock, null, 2) + '\n', { mode: 0o600 });
50
+ }
51
+ export function upsertLicense(key, entry) {
52
+ var _a;
53
+ const lock = readLock();
54
+ const existing = lock.licenses[key];
55
+ lock.licenses[key] = Object.assign(Object.assign({}, entry), { installedAt: (_a = existing === null || existing === void 0 ? void 0 : existing.installedAt) !== null && _a !== void 0 ? _a : entry.installedAt, updatedAt: entry.updatedAt });
56
+ writeLock(lock);
57
+ }
58
+ export function removeLicense(key) {
59
+ const lock = readLock();
60
+ const entry = lock.licenses[key];
61
+ if (!entry)
62
+ return null;
63
+ delete lock.licenses[key];
64
+ writeLock(lock);
65
+ return entry;
66
+ }
67
+ export function getLicense(key) {
68
+ var _a;
69
+ return (_a = readLock().licenses[key]) !== null && _a !== void 0 ? _a : null;
70
+ }
71
+ export function listLicenses() {
72
+ const lock = readLock();
73
+ return Object.entries(lock.licenses).map(([key, entry]) => ({ key, entry }));
74
+ }
75
+ export function maskKey(key) {
76
+ if (key.length <= 8)
77
+ return '****';
78
+ return `${key.slice(0, 4)}…${key.slice(-4)}`;
79
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tiny ANSI helpers — no dependency on picocolors/chalk to keep install size minimal.
3
+ */
4
+ export declare const cyan: (s: string) => string;
5
+ export declare const green: (s: string) => string;
6
+ export declare const yellow: (s: string) => string;
7
+ export declare const red: (s: string) => string;
8
+ export declare const dim: (s: string) => string;
9
+ export declare const bold: (s: string) => string;
10
+ export declare const say: (msg: string) => void;
11
+ export declare const ok: (msg: string) => void;
12
+ export declare const warn: (msg: string) => void;
13
+ export declare const fail: (msg: string) => never;
package/dist/output.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Tiny ANSI helpers — no dependency on picocolors/chalk to keep install size minimal.
3
+ */
4
+ const isTty = process.stdout.isTTY === true;
5
+ const color = (code, msg) => isTty ? `\x1b[${code}m${msg}\x1b[0m` : msg;
6
+ export const cyan = (s) => color('36', s);
7
+ export const green = (s) => color('32', s);
8
+ export const yellow = (s) => color('33', s);
9
+ export const red = (s) => color('31', s);
10
+ export const dim = (s) => color('2', s);
11
+ export const bold = (s) => color('1', s);
12
+ export const say = (msg) => console.log(`${cyan('▸')} ${msg}`);
13
+ export const ok = (msg) => console.log(`${green('✓')} ${msg}`);
14
+ export const warn = (msg) => console.log(`${yellow('!')} ${msg}`);
15
+ export const fail = (msg) => {
16
+ console.error(`${red('✗')} ${msg}`);
17
+ process.exit(1);
18
+ };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Shared types for the gosuper CLI.
3
+ *
4
+ * AgentType + AgentConfig are adapted from vercel-labs/skills (MIT). See NOTICE.
5
+ */
6
+ export type AgentType = 'claude-code' | 'cursor' | 'codex' | 'opencode' | 'gemini-cli' | 'universal';
7
+ export interface AgentConfig {
8
+ /** Stable id used on the CLI (`-a claude-code`). */
9
+ name: AgentType;
10
+ /** Human label for prompts and logs. */
11
+ displayName: string;
12
+ /** Project-level skills directory (relative to cwd). */
13
+ projectSkillsDir: string;
14
+ /** Global (user-home) skills directory. Absolute path. */
15
+ globalSkillsDir: string;
16
+ /** Heuristic: does this agent appear to be installed on the machine? */
17
+ detect(): boolean;
18
+ }
19
+ export interface ResolveResponse {
20
+ license: {
21
+ customer_name: string | null;
22
+ status: 'active' | 'revoked' | 'expired';
23
+ expires_at: string | null;
24
+ };
25
+ bundle: {
26
+ slug: string;
27
+ name: string;
28
+ description: string | null;
29
+ };
30
+ version: {
31
+ version: string;
32
+ sha256: string;
33
+ byte_size: number;
34
+ skill_count: number;
35
+ changelog: string | null;
36
+ };
37
+ tarball_url: string;
38
+ tarball_ttl_seconds: number;
39
+ }
40
+ export interface InfoResponse {
41
+ customer_name: string | null;
42
+ customer_email: string;
43
+ bundle_slug: string;
44
+ bundle_name: string;
45
+ bundle_description: string | null;
46
+ current_version: string | null;
47
+ last_seen_version: string | null;
48
+ status: 'active' | 'revoked' | 'expired';
49
+ expires_at: string | null;
50
+ install_count: number;
51
+ last_installed_at: string | null;
52
+ }
53
+ export interface ManifestResponse {
54
+ bundle_slug: string;
55
+ bundle_name: string;
56
+ version: string;
57
+ sha256: string;
58
+ byte_size: number;
59
+ skill_count: number;
60
+ changelog: string | null;
61
+ skills: Array<{
62
+ slug: string;
63
+ sha256?: string;
64
+ }>;
65
+ }
66
+ export interface LockFile {
67
+ /** Schema version. Bump when shape changes. */
68
+ version: 1;
69
+ /** Per-license install record. */
70
+ licenses: Record<string, LicenseEntry>;
71
+ }
72
+ export interface LicenseEntry {
73
+ bundleSlug: string;
74
+ installedVersion: string;
75
+ installedAt: string;
76
+ updatedAt: string;
77
+ /** Agent ids that have a copy of this bundle on disk. */
78
+ agents: AgentType[];
79
+ /** Map of agent → list of skill folder paths owned by this license. */
80
+ paths: Partial<Record<AgentType, string[]>>;
81
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared types for the gosuper CLI.
3
+ *
4
+ * AgentType + AgentConfig are adapted from vercel-labs/skills (MIT). See NOTICE.
5
+ */
6
+ export {};
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "gosuper",
3
+ "version": "0.1.0",
4
+ "description": "Install and manage GoSuper skill bundles across Claude Code, Cursor, Codex, OpenCode, Gemini CLI, and more.",
5
+ "keywords": [
6
+ "agent-skills",
7
+ "ai-agents",
8
+ "claude-code",
9
+ "cli",
10
+ "codex",
11
+ "cursor",
12
+ "gemini-cli",
13
+ "gosuper",
14
+ "installer",
15
+ "opencode",
16
+ "skills"
17
+ ],
18
+ "homepage": "https://gosuper.ai",
19
+ "bugs": {
20
+ "url": "https://github.com/aksoneng/gosuper/issues",
21
+ "email": "hello@gosuper.ai"
22
+ },
23
+ "license": "MIT",
24
+ "author": {
25
+ "name": "GoSuper",
26
+ "email": "hello@gosuper.ai",
27
+ "url": "https://gosuper.ai"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/aksoneng/gosuper.git",
32
+ "directory": "packages/cli"
33
+ },
34
+ "bin": {
35
+ "gosuper": "./bin/cli.mjs"
36
+ },
37
+ "files": [
38
+ "bin",
39
+ "dist",
40
+ "LICENSE",
41
+ "NOTICE",
42
+ "README.md"
43
+ ],
44
+ "type": "module",
45
+ "main": "./dist/index.js",
46
+ "types": "./dist/index.d.ts",
47
+ "exports": {
48
+ ".": {
49
+ "types": "./dist/index.d.ts",
50
+ "default": "./dist/index.js"
51
+ }
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc -p tsconfig.build.json",
58
+ "clean": "git clean -xdf .turbo dist node_modules",
59
+ "dev": "node --import tsx/esm src/cli.ts",
60
+ "prepublishOnly": "pnpm run build",
61
+ "test": "vitest run",
62
+ "test:watch": "vitest",
63
+ "typecheck": "tsc --noEmit"
64
+ },
65
+ "dependencies": {},
66
+ "devDependencies": {
67
+ "@kit/tsconfig": "workspace:*",
68
+ "@types/node": "catalog:",
69
+ "tsx": "^4.19.0",
70
+ "typescript": "catalog:",
71
+ "vitest": "catalog:"
72
+ },
73
+ "engines": {
74
+ "node": ">=18"
75
+ }
76
+ }