kubun 0.10.0 → 0.12.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/README.md +22 -234
- package/bin/dev.js +12 -4
- package/bin/run.js +11 -2
- package/lib/account.d.ts +2 -0
- package/lib/account.js +5 -0
- package/lib/client.d.ts +2 -0
- package/{dist → lib}/client.js +4 -1
- package/lib/commands/account.d.ts +2 -0
- package/lib/commands/account.js +24 -0
- package/lib/commands/graph.d.ts +2 -0
- package/lib/commands/graph.js +76 -0
- package/lib/commands/graphql.d.ts +2 -0
- package/lib/commands/graphql.js +33 -0
- package/lib/commands/hub.d.ts +2 -0
- package/lib/commands/hub.js +105 -0
- package/lib/commands/mcp.d.ts +2 -0
- package/lib/commands/mcp.js +21 -0
- package/lib/commands/model.d.ts +2 -0
- package/lib/commands/model.js +71 -0
- package/lib/commands/peer.d.ts +2 -0
- package/lib/commands/peer.js +275 -0
- package/lib/commands/serve.d.ts +2 -0
- package/lib/commands/serve.js +86 -0
- package/lib/engine.d.ts +74 -0
- package/lib/engine.js +154 -0
- package/lib/fs.d.ts +4 -0
- package/{dist → lib}/fs.js +6 -2
- package/lib/hub.d.ts +38 -0
- package/lib/hub.js +109 -0
- package/lib/identity.d.ts +19 -0
- package/lib/identity.js +31 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +19 -0
- package/lib/options.d.ts +19 -0
- package/lib/options.js +47 -0
- package/lib/program.d.ts +2 -0
- package/lib/program.js +32 -0
- package/lib/ui.d.ts +5 -0
- package/lib/ui.js +29 -0
- package/package.json +47 -50
- package/bin/dev.cmd +0 -3
- package/bin/run.cmd +0 -3
- package/dist/account.js +0 -5
- package/dist/commands/account/generate.js +0 -10
- package/dist/commands/account/id.js +0 -18
- package/dist/commands/graph/deploy.js +0 -32
- package/dist/commands/graph/mutate.js +0 -32
- package/dist/commands/graph/query.js +0 -32
- package/dist/commands/graphql/schema.js +0 -41
- package/dist/commands/mcp.js +0 -33
- package/dist/commands/model/cluster.js +0 -35
- package/dist/commands/model/create.js +0 -69
- package/dist/commands/serve.js +0 -126
- package/dist/index.js +0 -1
- package/oclif.manifest.json +0 -533
package/{dist → lib}/fs.js
RENAMED
|
@@ -4,10 +4,14 @@ export function resolvePath(value) {
|
|
|
4
4
|
return isAbsolute(value) ? value : resolve(process.cwd(), value);
|
|
5
5
|
}
|
|
6
6
|
export async function readJSON(path) {
|
|
7
|
-
return JSON.parse(await read(resolvePath(path), {
|
|
7
|
+
return JSON.parse(await read(resolvePath(path), {
|
|
8
|
+
encoding: 'utf8'
|
|
9
|
+
}));
|
|
8
10
|
}
|
|
9
11
|
export async function writeFile(path, value) {
|
|
10
|
-
await write(resolvePath(path), value, {
|
|
12
|
+
await write(resolvePath(path), value, {
|
|
13
|
+
encoding: 'utf8'
|
|
14
|
+
});
|
|
11
15
|
}
|
|
12
16
|
export async function writeJSON(path, value, format = false) {
|
|
13
17
|
await writeFile(path, format ? JSON.stringify(value, null, 2) : JSON.stringify(value));
|
package/lib/hub.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type HubAuthConfig, type Relay, type RelayObservers } from '@kubun/hub';
|
|
2
|
+
export type StartHubParams = {
|
|
3
|
+
/**
|
|
4
|
+
* Who may use the relay. Required: an unset `auth` would leave `createRelay`
|
|
5
|
+
* with no access rules at all, which refuses every DID without saying so.
|
|
6
|
+
*/
|
|
7
|
+
auth: HubAuthConfig;
|
|
8
|
+
/** SQLite path, `:memory:`, or a postgres URL. Defaults to `:memory:`. */
|
|
9
|
+
db?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Base64 private key for the hub's signing identity. Absent generates an
|
|
12
|
+
* ephemeral one — every client holding the old DID then has to rediscover it
|
|
13
|
+
* from `/info`, which is why `resolveIdentity` warns about it.
|
|
14
|
+
*/
|
|
15
|
+
privateKey?: string;
|
|
16
|
+
allowedOrigin?: string;
|
|
17
|
+
/** Bind port. Absent probes for a free one, preferring {@link DEFAULT_HUB_PORT}. */
|
|
18
|
+
port?: number;
|
|
19
|
+
/** Relay diagnostics — auth refusals, stores, fetches. */
|
|
20
|
+
observers?: RelayObservers;
|
|
21
|
+
};
|
|
22
|
+
export type StartedHub = {
|
|
23
|
+
/** The relay endpoint a device binds a group to. */
|
|
24
|
+
url: string;
|
|
25
|
+
/** The hub's DID — the audience of every token it accepts, served at `/info`. */
|
|
26
|
+
did: string;
|
|
27
|
+
relay: Relay;
|
|
28
|
+
dispose: () => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Serve a blind relay hub over HTTP.
|
|
32
|
+
*
|
|
33
|
+
* The hub holds no group state and is a member of nothing: it stores encrypted
|
|
34
|
+
* envelopes and serves topic subscriptions for groups it cannot read. Its only
|
|
35
|
+
* authentication is `auth`, checked against the issuer of each signed Enkaku
|
|
36
|
+
* envelope — there is nothing to check at the HTTP layer.
|
|
37
|
+
*/
|
|
38
|
+
export declare function startHub(params: StartHubParams): Promise<StartedHub>;
|
package/lib/hub.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { serve } from '@hono/node-server';
|
|
2
|
+
import { NodeSQLiteAdapter } from '@kubun/db-node-sqlite';
|
|
3
|
+
import { PostgresAdapter } from '@kubun/db-postgres';
|
|
4
|
+
import { createRelay } from '@kubun/hub';
|
|
5
|
+
import { getKubunLogger } from '@kubun/logger';
|
|
6
|
+
import { getPort } from '@tejika/env';
|
|
7
|
+
import { resolvePath } from './fs.js';
|
|
8
|
+
import { resolveIdentity } from './identity.js';
|
|
9
|
+
/**
|
|
10
|
+
* Where the relay's Enkaku transport is mounted. The relay is path-agnostic —
|
|
11
|
+
* it is one transport behind a `fetch` — so this prefix is ours to pick, and it
|
|
12
|
+
* is the URL a device binds a group to.
|
|
13
|
+
*/ const RELAY_PATH = '/relay';
|
|
14
|
+
/**
|
|
15
|
+
* The name `/info` advertises the relay mount under, mirroring how a device's
|
|
16
|
+
* HTTP surface advertises its own protocols. A device binds the mount path
|
|
17
|
+
* directly, so this is discovery for a reader that has only the origin.
|
|
18
|
+
*/ const RELAY_PROTOCOL_NAME = 'relay';
|
|
19
|
+
/** Preferred port when none is given; a taken one yields another free port. */ const DEFAULT_HUB_PORT = 4321;
|
|
20
|
+
/**
|
|
21
|
+
* Serve a blind relay hub over HTTP.
|
|
22
|
+
*
|
|
23
|
+
* The hub holds no group state and is a member of nothing: it stores encrypted
|
|
24
|
+
* envelopes and serves topic subscriptions for groups it cannot read. Its only
|
|
25
|
+
* authentication is `auth`, checked against the issuer of each signed Enkaku
|
|
26
|
+
* envelope — there is nothing to check at the HTTP layer.
|
|
27
|
+
*/ export async function startHub(params) {
|
|
28
|
+
const logger = getKubunLogger('cli');
|
|
29
|
+
// No `--id` counterpart here: the relay signs its own response tokens, so an
|
|
30
|
+
// identity that cannot sign is not a hub identity.
|
|
31
|
+
const identity = resolveIdentity({
|
|
32
|
+
privateKey: params.privateKey
|
|
33
|
+
}, logger);
|
|
34
|
+
const adapter = params.db == null || params.db === ':memory:' ? new NodeSQLiteAdapter({
|
|
35
|
+
database: ':memory:'
|
|
36
|
+
}) : params.db.startsWith('postgres://') || params.db.startsWith('postgresql://') ? new PostgresAdapter({
|
|
37
|
+
url: params.db
|
|
38
|
+
}) : new NodeSQLiteAdapter({
|
|
39
|
+
database: resolvePath(params.db)
|
|
40
|
+
});
|
|
41
|
+
const relay = await createRelay({
|
|
42
|
+
db: adapter,
|
|
43
|
+
identity,
|
|
44
|
+
auth: params.auth,
|
|
45
|
+
retention: {
|
|
46
|
+
olderThanSeconds: 30 * 24 * 60 * 60
|
|
47
|
+
},
|
|
48
|
+
...params.allowedOrigin == null ? {} : {
|
|
49
|
+
allowedOrigin: params.allowedOrigin
|
|
50
|
+
},
|
|
51
|
+
...params.observers == null ? {} : {
|
|
52
|
+
observers: params.observers
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
// OPTIONS is served alongside POST: the relay answers the CORS preflight
|
|
56
|
+
// itself, and a POST-only mount leaves a browser client unable to reach it.
|
|
57
|
+
const handler = async (request)=>{
|
|
58
|
+
const { pathname } = new URL(request.url);
|
|
59
|
+
if (pathname === RELAY_PATH || pathname.startsWith(`${RELAY_PATH}/`)) {
|
|
60
|
+
return request.method === 'POST' || request.method === 'OPTIONS' ? await relay.fetch(request) : new Response(null, {
|
|
61
|
+
status: 405
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (request.method === 'GET' && pathname === '/health') {
|
|
65
|
+
return Response.json({
|
|
66
|
+
status: 'ok'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// Discovery, in the shape a device already reads from another device's
|
|
70
|
+
// `/info`: the DID every token addressed to this hub must name, plus where
|
|
71
|
+
// the relay is mounted. A hub advertising a DID it cannot sign with is
|
|
72
|
+
// simply unreachable — its own server refuses the audience — so nothing
|
|
73
|
+
// here has to be verified by the reader.
|
|
74
|
+
if (request.method === 'GET' && pathname === '/info') {
|
|
75
|
+
return Response.json({
|
|
76
|
+
did: identity.id,
|
|
77
|
+
protocols: {
|
|
78
|
+
[RELAY_PROTOCOL_NAME]: RELAY_PATH
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return new Response(null, {
|
|
83
|
+
status: 404
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
const port = params.port ?? await getPort('kubun', {
|
|
87
|
+
default: DEFAULT_HUB_PORT
|
|
88
|
+
});
|
|
89
|
+
const httpServer = await new Promise((resolve)=>{
|
|
90
|
+
const server = serve({
|
|
91
|
+
fetch: handler,
|
|
92
|
+
port
|
|
93
|
+
}, ()=>resolve(server));
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
url: `http://localhost:${port}${RELAY_PATH}`,
|
|
97
|
+
did: identity.id,
|
|
98
|
+
relay,
|
|
99
|
+
dispose: async ()=>{
|
|
100
|
+
await new Promise((resolve, reject)=>{
|
|
101
|
+
httpServer.close((error)=>{
|
|
102
|
+
if (error == null) resolve();
|
|
103
|
+
else reject(error);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
await relay.dispose();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Identity, OwnIdentity } from '@kokuin/token';
|
|
2
|
+
type Logger = {
|
|
3
|
+
warn: (message: string, properties?: Record<string, unknown>) => void;
|
|
4
|
+
};
|
|
5
|
+
export type IdentityOptions = {
|
|
6
|
+
privateKey?: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
p2p?: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the server signing identity from CLI options.
|
|
12
|
+
*
|
|
13
|
+
* The random-identity fallback logs only the DID — never the private key — and
|
|
14
|
+
* flags that the identity is ephemeral so it is not mistaken for a persistent
|
|
15
|
+
* one. Logging the key would leak the server's signing secret to console sinks
|
|
16
|
+
* and log aggregation.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveIdentity(opts: IdentityOptions, logger: Logger): Identity | OwnIdentity;
|
|
19
|
+
export {};
|
package/lib/identity.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createFullIdentity, randomIdentity } from '@kokuin/token';
|
|
2
|
+
import { fromB64 } from '@sozai/codec';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the server signing identity from CLI options.
|
|
5
|
+
*
|
|
6
|
+
* The random-identity fallback logs only the DID — never the private key — and
|
|
7
|
+
* flags that the identity is ephemeral so it is not mistaken for a persistent
|
|
8
|
+
* one. Logging the key would leak the server's signing secret to console sinks
|
|
9
|
+
* and log aggregation.
|
|
10
|
+
*/ export function resolveIdentity(opts, logger) {
|
|
11
|
+
if (opts.privateKey != null) {
|
|
12
|
+
const privateKey = fromB64(opts.privateKey);
|
|
13
|
+
return {
|
|
14
|
+
...createFullIdentity(privateKey),
|
|
15
|
+
privateKey
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (opts.id != null) {
|
|
19
|
+
if (opts.p2p) {
|
|
20
|
+
throw new Error('P2P mode requires a signing identity. Use --private-key instead of --id.');
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
id: opts.id
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const generated = randomIdentity();
|
|
27
|
+
logger.warn('No identity provided; generated an ephemeral one (DID: {id}). It is discarded on exit — pass --private-key or --id to persist.', {
|
|
28
|
+
id: generated.id
|
|
29
|
+
});
|
|
30
|
+
return generated;
|
|
31
|
+
}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { buildProgram } from './program.js';
|
|
2
|
+
export { buildProgram };
|
|
3
|
+
export async function run(argv) {
|
|
4
|
+
const program = buildProgram();
|
|
5
|
+
if (argv.slice(2).length === 0) {
|
|
6
|
+
program.outputHelp();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
await program.parseAsync(argv);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13
|
+
process.stderr.write(`✘ ${message}\n`);
|
|
14
|
+
if (process.env.KUBUN_DEBUG === '1' && error instanceof Error && error.stack != null) {
|
|
15
|
+
process.stderr.write(`${error.stack}\n`);
|
|
16
|
+
}
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/lib/options.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/** Add `-k, --private-key <key>`, bound to KUBUN_PRIVATE_KEY, optionally required. */
|
|
3
|
+
export declare function withPrivateKey(cmd: Command, opts?: {
|
|
4
|
+
required?: boolean;
|
|
5
|
+
}): Command;
|
|
6
|
+
/** Add `--url <url>` for the Kubun server URL. */
|
|
7
|
+
export declare function withURL(cmd: Command): Command;
|
|
8
|
+
/** Add `-l, --log-level <level>` with the `warning` default and the logtape level set. */
|
|
9
|
+
export declare function withLogLevel(cmd: Command): Command;
|
|
10
|
+
/** Add `-p, --port <port>`, default resolved lazily from @tejika/env at action time. */
|
|
11
|
+
export declare function withPort(cmd: Command): Command;
|
|
12
|
+
/**
|
|
13
|
+
* Add `-a, --allowed-origin <origin>`, bound to KUBUN_ALLOWED_ORIGIN.
|
|
14
|
+
*
|
|
15
|
+
* No default: an unset option denies cross-origin browser requests. Opening a
|
|
16
|
+
* surface is an explicit choice — pass `-a '*'` to reflect any origin, or a
|
|
17
|
+
* comma-separated list to allow only those.
|
|
18
|
+
*/
|
|
19
|
+
export declare function withAllowedOrigin(cmd: Command): Command;
|
package/lib/options.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getLogLevels } from '@logtape/logtape';
|
|
2
|
+
import { getAppEnvVar, getPort } from '@tejika/env';
|
|
3
|
+
/** Add `-k, --private-key <key>`, bound to KUBUN_PRIVATE_KEY, optionally required. */ export function withPrivateKey(cmd, opts = {}) {
|
|
4
|
+
cmd.option('-k, --private-key <key>', 'base64-encoded private key (env: KUBUN_PRIVATE_KEY)');
|
|
5
|
+
cmd.hook('preAction', (_thisCmd, actionCmd)=>{
|
|
6
|
+
if (actionCmd.opts().privateKey == null && process.env.KUBUN_PRIVATE_KEY != null) {
|
|
7
|
+
actionCmd.setOptionValue('privateKey', process.env.KUBUN_PRIVATE_KEY);
|
|
8
|
+
}
|
|
9
|
+
if (opts.required && actionCmd.opts().privateKey == null) {
|
|
10
|
+
throw new Error('--private-key is required (or set KUBUN_PRIVATE_KEY)');
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
return cmd;
|
|
14
|
+
}
|
|
15
|
+
/** Add `--url <url>` for the Kubun server URL. */ export function withURL(cmd) {
|
|
16
|
+
return cmd.option('--url <url>', 'Kubun server URL');
|
|
17
|
+
}
|
|
18
|
+
/** Add `-l, --log-level <level>` with the `warning` default and the logtape level set. */ export function withLogLevel(cmd) {
|
|
19
|
+
return cmd.option('-l, --log-level <level>', `log level (${getLogLevels().join(', ')})`, 'warning');
|
|
20
|
+
}
|
|
21
|
+
/** Add `-p, --port <port>`, default resolved lazily from @tejika/env at action time. */ export function withPort(cmd) {
|
|
22
|
+
cmd.option('-p, --port <port>', 'port to listen on');
|
|
23
|
+
cmd.hook('preAction', async (_thisCmd, actionCmd)=>{
|
|
24
|
+
if (actionCmd.opts().port == null) {
|
|
25
|
+
actionCmd.setOptionValue('port', await getPort('kubun'));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return cmd;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Add `-a, --allowed-origin <origin>`, bound to KUBUN_ALLOWED_ORIGIN.
|
|
32
|
+
*
|
|
33
|
+
* No default: an unset option denies cross-origin browser requests. Opening a
|
|
34
|
+
* surface is an explicit choice — pass `-a '*'` to reflect any origin, or a
|
|
35
|
+
* comma-separated list to allow only those.
|
|
36
|
+
*/ export function withAllowedOrigin(cmd) {
|
|
37
|
+
cmd.option('-a, --allowed-origin <origin>', 'allowed HTTP origin(s), comma-separated (env: KUBUN_ALLOWED_ORIGIN); unset denies cross-origin browser requests');
|
|
38
|
+
cmd.hook('preAction', (_thisCmd, actionCmd)=>{
|
|
39
|
+
if (actionCmd.opts().allowedOrigin == null) {
|
|
40
|
+
const envOrigin = getAppEnvVar('kubun', 'ALLOWED_ORIGIN');
|
|
41
|
+
if (envOrigin != null) {
|
|
42
|
+
actionCmd.setOptionValue('allowedOrigin', envOrigin);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return cmd;
|
|
47
|
+
}
|
package/lib/program.d.ts
ADDED
package/lib/program.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { buildProgram as tejikaBuildProgram } from '@tejika/cli';
|
|
5
|
+
import { createAccountCommand } from './commands/account.js';
|
|
6
|
+
import { createGraphCommand } from './commands/graph.js';
|
|
7
|
+
import { createGraphQLCommand } from './commands/graphql.js';
|
|
8
|
+
import { createHubCommand } from './commands/hub.js';
|
|
9
|
+
import { createMCPCommand } from './commands/mcp.js';
|
|
10
|
+
import { createModelCommand } from './commands/model.js';
|
|
11
|
+
import { createPeerCommand } from './commands/peer.js';
|
|
12
|
+
import { createServeCommand } from './commands/serve.js';
|
|
13
|
+
const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '../package.json');
|
|
14
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
15
|
+
export function buildProgram() {
|
|
16
|
+
const program = tejikaBuildProgram({
|
|
17
|
+
name: 'kubun',
|
|
18
|
+
version: pkg.version,
|
|
19
|
+
commands: [
|
|
20
|
+
createAccountCommand(),
|
|
21
|
+
createGraphCommand(),
|
|
22
|
+
createGraphQLCommand(),
|
|
23
|
+
createHubCommand(),
|
|
24
|
+
createModelCommand(),
|
|
25
|
+
createMCPCommand(),
|
|
26
|
+
createPeerCommand(),
|
|
27
|
+
createServeCommand()
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
program.description(pkg.description);
|
|
31
|
+
return program;
|
|
32
|
+
}
|
package/lib/ui.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type SystemNoticeVariant } from '@tejika/ui';
|
|
2
|
+
/** Render a one-shot status notice to stderr, then unmount. */
|
|
3
|
+
export declare function renderNotice(variant: SystemNoticeVariant, text: string): void;
|
|
4
|
+
/** Show a live spinner on stderr for the duration of `fn`, then unmount. */
|
|
5
|
+
export declare function withSpinner<T>(label: string, fn: () => Promise<T>): Promise<T>;
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Spinner, SystemNotice } from '@tejika/ui';
|
|
3
|
+
import { render } from 'ink';
|
|
4
|
+
const renderToStderr = {
|
|
5
|
+
stdout: process.stderr
|
|
6
|
+
};
|
|
7
|
+
/** Render a one-shot status notice to stderr, then unmount. */ export function renderNotice(variant, text) {
|
|
8
|
+
const { unmount } = render(/*#__PURE__*/ _jsx(SystemNotice, {
|
|
9
|
+
variant: variant,
|
|
10
|
+
text: text
|
|
11
|
+
}), {
|
|
12
|
+
...renderToStderr,
|
|
13
|
+
// Nothing to protect from interleaved logs: this mounts and unmounts in one
|
|
14
|
+
// turn. Patching the global console for that costs a process-wide side
|
|
15
|
+
// effect, and throws outright wherever `console` is already a stand-in.
|
|
16
|
+
patchConsole: false
|
|
17
|
+
});
|
|
18
|
+
unmount();
|
|
19
|
+
}
|
|
20
|
+
/** Show a live spinner on stderr for the duration of `fn`, then unmount. */ export async function withSpinner(label, fn) {
|
|
21
|
+
const app = render(/*#__PURE__*/ _jsx(Spinner, {
|
|
22
|
+
label: label
|
|
23
|
+
}), renderToStderr);
|
|
24
|
+
try {
|
|
25
|
+
return await fn();
|
|
26
|
+
} finally{
|
|
27
|
+
app.unmount();
|
|
28
|
+
}
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,68 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kubun",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"license": "see LICENSE.md",
|
|
3
|
+
"version": "0.12.0",
|
|
5
4
|
"description": "Kubun CLI",
|
|
6
5
|
"keywords": [],
|
|
6
|
+
"license": "see LICENSE.md",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"
|
|
9
|
-
|
|
8
|
+
"main": "lib/index.js",
|
|
9
|
+
"types": "lib/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"kubun": "./bin/run.js"
|
|
10
12
|
},
|
|
11
|
-
"main": "dist/index.js",
|
|
12
|
-
"types": "dist/index.d.ts",
|
|
13
13
|
"files": [
|
|
14
14
|
"/bin",
|
|
15
|
-
"/
|
|
16
|
-
"/oclif.manifest.json"
|
|
15
|
+
"/lib"
|
|
17
16
|
],
|
|
18
|
-
"bin": {
|
|
19
|
-
"kubun": "./bin/run.js"
|
|
20
|
-
},
|
|
21
|
-
"oclif": {
|
|
22
|
-
"bin": "kubun",
|
|
23
|
-
"dirname": "kubun",
|
|
24
|
-
"commands": "./dist/commands",
|
|
25
|
-
"plugins": [
|
|
26
|
-
"@oclif/plugin-help"
|
|
27
|
-
],
|
|
28
|
-
"topicSeparator": " "
|
|
29
|
-
},
|
|
30
17
|
"dependencies": {
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
33
|
-
"@logtape/logtape": "^2.
|
|
34
|
-
"@mokei/context-server": "^0.
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"@kubun/
|
|
44
|
-
"@kubun/
|
|
45
|
-
"@kubun/
|
|
46
|
-
"@kubun/
|
|
47
|
-
"@kubun/
|
|
48
|
-
"@kubun/
|
|
49
|
-
"@kubun/
|
|
50
|
-
"@kubun/
|
|
51
|
-
"@kubun/
|
|
18
|
+
"@hono/node-server": "^2.1.0",
|
|
19
|
+
"@kokuin/token": "^0.3.0",
|
|
20
|
+
"@logtape/logtape": "^2.3.0",
|
|
21
|
+
"@mokei/context-server": "^0.11.0",
|
|
22
|
+
"@sozai/codec": "^0.4.0",
|
|
23
|
+
"@tejika/cli": "^0.4.0",
|
|
24
|
+
"@tejika/env": "^0.4.0",
|
|
25
|
+
"@tejika/ui": "^0.4.0",
|
|
26
|
+
"commander": "^15.0.0",
|
|
27
|
+
"graphql": "^16.14.2",
|
|
28
|
+
"ink": "^7.1.1",
|
|
29
|
+
"react": "19.2.3",
|
|
30
|
+
"@kubun/db": "^0.12.0",
|
|
31
|
+
"@kubun/client": "^0.12.0",
|
|
32
|
+
"@kubun/db-node-sqlite": "^0.12.0",
|
|
33
|
+
"@kubun/graphql": "^0.12.0",
|
|
34
|
+
"@kubun/http-client": "^0.12.0",
|
|
35
|
+
"@kubun/logger": "^0.12.0",
|
|
36
|
+
"@kubun/db-postgres": "^0.12.0",
|
|
37
|
+
"@kubun/mcp": "^0.12.0",
|
|
38
|
+
"@kubun/hub": "^0.12.0",
|
|
39
|
+
"@kubun/engine": "^0.12.0",
|
|
40
|
+
"@kubun/plugin-p2p": "^0.12.0",
|
|
41
|
+
"@kubun/plugin-http": "^0.12.0",
|
|
42
|
+
"@kubun/protocol": "^0.12.0",
|
|
43
|
+
"@kubun/plugin-rpc": "^0.12.0"
|
|
52
44
|
},
|
|
53
45
|
"devDependencies": {
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
46
|
+
"ink-testing-library": "^4.0.0",
|
|
47
|
+
"@kubun/store-graph": "^0.12.0",
|
|
48
|
+
"@kubun/id": "^0.12.0",
|
|
49
|
+
"@kubun/store-p2p": "^0.12.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=22.0.0"
|
|
58
53
|
},
|
|
59
54
|
"scripts": {
|
|
55
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
56
|
+
"build:clean": "del lib",
|
|
57
|
+
"build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
58
|
+
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
59
|
+
"build:types:ci": "tsc --emitDeclarationOnly --skipLibCheck --declarationMap false",
|
|
60
60
|
"kubun": "./bin/dev.js",
|
|
61
|
-
"build": "del dist && tsc -b",
|
|
62
|
-
"posttest": "pnpm run lint",
|
|
63
|
-
"test:types": "tsc --noEmit",
|
|
64
|
-
"test:unit": "OCLIF_TEST_ROOT=. vitest run",
|
|
65
61
|
"test": "pnpm run test:types && pnpm run test:unit",
|
|
66
|
-
"
|
|
62
|
+
"test:types": "tsc --noEmit --skipLibCheck -p tsconfig.test.json",
|
|
63
|
+
"test:unit": "vitest run"
|
|
67
64
|
}
|
|
68
65
|
}
|
package/bin/dev.cmd
DELETED
package/bin/run.cmd
DELETED
package/dist/account.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { toB64 } from '@enkaku/codec';
|
|
2
|
-
import { randomPrivateKey } from '@enkaku/token';
|
|
3
|
-
import { Command } from '@oclif/core';
|
|
4
|
-
export default class GenerateAccount extends Command {
|
|
5
|
-
static description = 'Generate a random private key to use as account signer';
|
|
6
|
-
async run() {
|
|
7
|
-
const key = await randomPrivateKey();
|
|
8
|
-
this.log(toB64(key));
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import { getSigner } from '../../account.js';
|
|
3
|
-
export default class AccountID extends Command {
|
|
4
|
-
static description = 'Display the DID of an account given its private key';
|
|
5
|
-
static flags = {
|
|
6
|
-
privateKey: Flags.string({
|
|
7
|
-
description: 'base64-encoded private key',
|
|
8
|
-
char: 'k',
|
|
9
|
-
env: 'KUBUN_PRIVATE_KEY',
|
|
10
|
-
required: true,
|
|
11
|
-
}),
|
|
12
|
-
};
|
|
13
|
-
async run() {
|
|
14
|
-
const { flags } = await this.parse(AccountID);
|
|
15
|
-
const signer = getSigner(flags.privateKey);
|
|
16
|
-
this.log(signer.id);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import { createClient } from '../../client.js';
|
|
4
|
-
import { readJSON } from '../../fs.js';
|
|
5
|
-
export default class DeployGraph extends Command {
|
|
6
|
-
static description = 'Deploy a documents model graph';
|
|
7
|
-
static flags = {
|
|
8
|
-
cluster: Flags.string({
|
|
9
|
-
description: 'path of the JSON file of the cluster',
|
|
10
|
-
char: 'c',
|
|
11
|
-
required: true,
|
|
12
|
-
multiple: true,
|
|
13
|
-
}),
|
|
14
|
-
id: Flags.string({ description: 'graph ID' }),
|
|
15
|
-
name: Flags.string({ description: 'graph name', char: 'n' }),
|
|
16
|
-
privateKey: Flags.string({
|
|
17
|
-
description: 'base64-encoded private key',
|
|
18
|
-
char: 'k',
|
|
19
|
-
env: 'KUBUN_PRIVATE_KEY',
|
|
20
|
-
required: true,
|
|
21
|
-
}),
|
|
22
|
-
url: Flags.string({ description: 'Kubun server URL' }),
|
|
23
|
-
};
|
|
24
|
-
async run() {
|
|
25
|
-
const spinner = ora('Deploying graph clusters...').start();
|
|
26
|
-
const { flags } = await this.parse(DeployGraph);
|
|
27
|
-
const client = createClient(flags.privateKey, flags.url);
|
|
28
|
-
const clusters = await Promise.all(flags.cluster.map((path) => readJSON(path)));
|
|
29
|
-
const graph = await client.deployGraph({ clusters, id: flags.id, name: flags.name });
|
|
30
|
-
spinner.succeed(`Deployed graph with ID: ${graph.id}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import { createClient } from '../../client.js';
|
|
4
|
-
export default class MutateGraph extends Command {
|
|
5
|
-
static description = 'Execute a GraphQL mutation on a graph';
|
|
6
|
-
static args = {
|
|
7
|
-
id: Args.string({ description: 'graph ID', required: true }),
|
|
8
|
-
text: Args.string({ description: 'mutation text', required: true }),
|
|
9
|
-
};
|
|
10
|
-
static flags = {
|
|
11
|
-
privateKey: Flags.string({
|
|
12
|
-
description: 'base64-encoded private key',
|
|
13
|
-
char: 'k',
|
|
14
|
-
env: 'KUBUN_PRIVATE_KEY',
|
|
15
|
-
required: true,
|
|
16
|
-
}),
|
|
17
|
-
url: Flags.string({ description: 'Kubun server URL' }),
|
|
18
|
-
variables: Flags.string({ description: 'query variables as JSON string' }),
|
|
19
|
-
};
|
|
20
|
-
async run() {
|
|
21
|
-
const spinner = ora('Executing graph mutation...').start();
|
|
22
|
-
const { args, flags } = await this.parse(MutateGraph);
|
|
23
|
-
const client = createClient(flags.privateKey, flags.url);
|
|
24
|
-
const result = await client.mutateGraph({
|
|
25
|
-
id: args.id,
|
|
26
|
-
text: args.text,
|
|
27
|
-
variables: flags.variables ? JSON.parse(flags.variables) : {},
|
|
28
|
-
});
|
|
29
|
-
spinner.succeed('Graph mutation executed');
|
|
30
|
-
this.log(JSON.stringify(result, null, 2));
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import { createClient } from '../../client.js';
|
|
4
|
-
export default class QueryGraph extends Command {
|
|
5
|
-
static description = 'Execute a GraphQL query on a graph';
|
|
6
|
-
static args = {
|
|
7
|
-
id: Args.string({ description: 'graph ID', required: true }),
|
|
8
|
-
text: Args.string({ description: 'query text', required: true }),
|
|
9
|
-
};
|
|
10
|
-
static flags = {
|
|
11
|
-
privateKey: Flags.string({
|
|
12
|
-
description: 'base64-encoded private key',
|
|
13
|
-
char: 'k',
|
|
14
|
-
env: 'KUBUN_PRIVATE_KEY',
|
|
15
|
-
required: true,
|
|
16
|
-
}),
|
|
17
|
-
url: Flags.string({ description: 'Kubun server URL' }),
|
|
18
|
-
variables: Flags.string({ description: 'query variables as JSON string' }),
|
|
19
|
-
};
|
|
20
|
-
async run() {
|
|
21
|
-
const spinner = ora('Querying graph...').start();
|
|
22
|
-
const { args, flags } = await this.parse(QueryGraph);
|
|
23
|
-
const client = createClient(flags.privateKey, flags.url);
|
|
24
|
-
const result = await client.queryGraph({
|
|
25
|
-
id: args.id,
|
|
26
|
-
text: args.text,
|
|
27
|
-
variables: flags.variables ? JSON.parse(flags.variables) : {},
|
|
28
|
-
});
|
|
29
|
-
spinner.succeed('Graph query executed');
|
|
30
|
-
this.log(JSON.stringify(result, null, 2));
|
|
31
|
-
}
|
|
32
|
-
}
|