@michinarai/cli 0.0.0-stage → 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.
Files changed (102) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +150 -2
  3. package/bin/run.js +4 -0
  4. package/dist/api/client.d.ts +24 -0
  5. package/dist/api/client.js +56 -0
  6. package/dist/api/client.js.map +1 -0
  7. package/dist/api/tool-flag.d.ts +9 -0
  8. package/dist/api/tool-flag.js +15 -0
  9. package/dist/api/tool-flag.js.map +1 -0
  10. package/dist/api/tools.d.ts +17 -0
  11. package/dist/api/tools.js +39 -0
  12. package/dist/api/tools.js.map +1 -0
  13. package/dist/auth/loopback.d.ts +11 -0
  14. package/dist/auth/loopback.js +54 -0
  15. package/dist/auth/loopback.js.map +1 -0
  16. package/dist/auth/oauth.d.ts +52 -0
  17. package/dist/auth/oauth.js +101 -0
  18. package/dist/auth/oauth.js.map +1 -0
  19. package/dist/auth/session.d.ts +7 -0
  20. package/dist/auth/session.js +41 -0
  21. package/dist/auth/session.js.map +1 -0
  22. package/dist/base-command.d.ts +29 -0
  23. package/dist/base-command.js +65 -0
  24. package/dist/base-command.js.map +1 -0
  25. package/dist/commands/build.d.ts +9 -0
  26. package/dist/commands/build.js +34 -0
  27. package/dist/commands/build.js.map +1 -0
  28. package/dist/commands/dev.d.ts +14 -0
  29. package/dist/commands/dev.js +55 -0
  30. package/dist/commands/dev.js.map +1 -0
  31. package/dist/commands/init.d.ts +12 -0
  32. package/dist/commands/init.js +63 -0
  33. package/dist/commands/init.js.map +1 -0
  34. package/dist/commands/login.d.ts +10 -0
  35. package/dist/commands/login.js +37 -0
  36. package/dist/commands/login.js.map +1 -0
  37. package/dist/commands/logout.d.ts +5 -0
  38. package/dist/commands/logout.js +19 -0
  39. package/dist/commands/logout.js.map +1 -0
  40. package/dist/commands/tools/list.d.ts +8 -0
  41. package/dist/commands/tools/list.js +23 -0
  42. package/dist/commands/tools/list.js.map +1 -0
  43. package/dist/commands/tools/push.d.ts +11 -0
  44. package/dist/commands/tools/push.js +53 -0
  45. package/dist/commands/tools/push.js.map +1 -0
  46. package/dist/commands/tools/register.d.ts +13 -0
  47. package/dist/commands/tools/register.js +66 -0
  48. package/dist/commands/tools/register.js.map +1 -0
  49. package/dist/commands/tools/tokens/create.d.ts +12 -0
  50. package/dist/commands/tools/tokens/create.js +26 -0
  51. package/dist/commands/tools/tokens/create.js.map +1 -0
  52. package/dist/commands/tools/tokens/list.d.ts +8 -0
  53. package/dist/commands/tools/tokens/list.js +13 -0
  54. package/dist/commands/tools/tokens/list.js.map +1 -0
  55. package/dist/commands/tools/tokens/revoke.d.ts +11 -0
  56. package/dist/commands/tools/tokens/revoke.js +18 -0
  57. package/dist/commands/tools/tokens/revoke.js.map +1 -0
  58. package/dist/commands/validate.d.ts +6 -0
  59. package/dist/commands/validate.js +27 -0
  60. package/dist/commands/validate.js.map +1 -0
  61. package/dist/commands.d.ts +26 -0
  62. package/dist/commands.js +29 -0
  63. package/dist/commands.js.map +1 -0
  64. package/dist/generated/api.d.ts +1499 -0
  65. package/dist/generated/api.js +6 -0
  66. package/dist/generated/api.js.map +1 -0
  67. package/dist/lib/credentials.d.ts +8 -0
  68. package/dist/lib/credentials.js +45 -0
  69. package/dist/lib/credentials.js.map +1 -0
  70. package/dist/lib/deps.d.ts +12 -0
  71. package/dist/lib/deps.js +23 -0
  72. package/dist/lib/deps.js.map +1 -0
  73. package/dist/lib/digest.d.ts +10 -0
  74. package/dist/lib/digest.js +37 -0
  75. package/dist/lib/digest.js.map +1 -0
  76. package/dist/lib/errors.d.ts +26 -0
  77. package/dist/lib/errors.js +39 -0
  78. package/dist/lib/errors.js.map +1 -0
  79. package/dist/lib/http.d.ts +2 -0
  80. package/dist/lib/http.js +11 -0
  81. package/dist/lib/http.js.map +1 -0
  82. package/dist/lib/lock.d.ts +4 -0
  83. package/dist/lib/lock.js +58 -0
  84. package/dist/lib/lock.js.map +1 -0
  85. package/dist/lib/output.d.ts +21 -0
  86. package/dist/lib/output.js +44 -0
  87. package/dist/lib/output.js.map +1 -0
  88. package/dist/lib/plan.d.ts +13 -0
  89. package/dist/lib/plan.js +10 -0
  90. package/dist/lib/plan.js.map +1 -0
  91. package/dist/lib/project.d.ts +8 -0
  92. package/dist/lib/project.js +37 -0
  93. package/dist/lib/project.js.map +1 -0
  94. package/package.json +59 -4
  95. package/templates/tool/.claude/skills/narai-tool/SKILL.md +16 -0
  96. package/templates/tool/AGENTS.md +30 -0
  97. package/templates/tool/narai.tool.json +25 -0
  98. package/templates/tool/package.json +14 -0
  99. package/templates/tool/scripts/build.mjs +12 -0
  100. package/templates/tool/src/index.html +19 -0
  101. package/templates/tool/src/tool.css +2 -0
  102. package/templates/tool/src/tool.js +21 -0
@@ -0,0 +1,52 @@
1
+ import { NaraiError } from "../lib/errors.js";
2
+ /** What a developer's session may do: the dev scopes of the registry (mo-narai-sdk schema/scopes.json). */
3
+ export declare const SCOPES: readonly ["dev:tools.read", "dev:tools.write", "dev:versions.upload", "dev:launch"];
4
+ export declare const clientId: (naraiUrl: string) => string;
5
+ export declare const resource: (naraiUrl: string) => string;
6
+ export interface ServerMetadata {
7
+ issuer: string;
8
+ authorization_endpoint: string;
9
+ token_endpoint: string;
10
+ revocation_endpoint: string;
11
+ iss_parameter: boolean;
12
+ }
13
+ export declare class OAuthError extends NaraiError {
14
+ readonly error: string;
15
+ constructor(error: string, description: string, exitCode?: number);
16
+ }
17
+ /** RFC 8414. The issuer is Narai's own address byte for byte, and every endpoint is on it: a document that points
18
+ * the code or the refresh token anywhere else is refused, not followed. */
19
+ export declare function discover(naraiUrl: string): Promise<ServerMetadata>;
20
+ export interface Pkce {
21
+ verifier: string;
22
+ challenge: string;
23
+ }
24
+ export declare function pkce(): Pkce;
25
+ export declare const newState: () => string;
26
+ export declare function authorizationUrl(meta: ServerMetadata, p: {
27
+ naraiUrl: string;
28
+ redirectUri: string;
29
+ challenge: string;
30
+ state: string;
31
+ }): string;
32
+ export interface TokenSet {
33
+ accessToken: string;
34
+ refreshToken: string;
35
+ expiresIn: number;
36
+ scopes: string[];
37
+ }
38
+ export declare function exchangeCode(meta: ServerMetadata, p: {
39
+ naraiUrl: string;
40
+ code: string;
41
+ verifier: string;
42
+ redirectUri: string;
43
+ }): Promise<TokenSet>;
44
+ /** Rotation: the answer carries a new refresh token and the old one is dead; store the new one before anything else. */
45
+ export declare function refresh(meta: ServerMetadata, p: {
46
+ naraiUrl: string;
47
+ refreshToken: string;
48
+ }): Promise<TokenSet>;
49
+ export declare function revoke(meta: ServerMetadata, p: {
50
+ naraiUrl: string;
51
+ token: string;
52
+ }): Promise<void>;
@@ -0,0 +1,101 @@
1
+ // Narai's OAuth 2.1 authorization server, as a public native client (RFC 8252): authorization code + PKCE (S256) +
2
+ // state, the response's issuer checked (RFC 9207), tokens for the tools API only (RFC 8707), refresh tokens that
3
+ // rotate on every use, revocation on sign-out (RFC 7009). The client is identified by its metadata document (CIMD).
4
+ import { createHash, randomBytes } from "node:crypto";
5
+ import { NaraiError } from "../lib/errors.js";
6
+ import { send } from "../lib/http.js";
7
+ /** What a developer's session may do: the dev scopes of the registry (mo-narai-sdk schema/scopes.json). */
8
+ export const SCOPES = ["dev:tools.read", "dev:tools.write", "dev:versions.upload", "dev:launch"];
9
+ export const clientId = (naraiUrl) => `${naraiUrl}/oauth/clients/narai-cli.json`;
10
+ export const resource = (naraiUrl) => `${naraiUrl}/api/tools`;
11
+ export class OAuthError extends NaraiError {
12
+ error;
13
+ constructor(error, description, exitCode = 3) {
14
+ super(`sign-in failed: ${error}${description ? ` (${description})` : ""}`, `oauth_${error}`, exitCode);
15
+ this.error = error;
16
+ }
17
+ }
18
+ /** RFC 8414. The issuer is Narai's own address byte for byte, and every endpoint is on it: a document that points
19
+ * the code or the refresh token anywhere else is refused, not followed. */
20
+ export async function discover(naraiUrl) {
21
+ const response = await send(`${naraiUrl}/.well-known/oauth-authorization-server`, { headers: { accept: "application/json" } });
22
+ if (!response.ok)
23
+ throw new NaraiError(`${naraiUrl} has no sign-in (HTTP ${response.status})`, "oauth_metadata_missing");
24
+ const doc = (await response.json().catch(() => ({})));
25
+ if (doc["issuer"] !== naraiUrl)
26
+ throw new NaraiError(`${naraiUrl} names another issuer; not signing in`, "oauth_issuer_mismatch");
27
+ const endpoint = (field) => {
28
+ const value = doc[field];
29
+ if (typeof value !== "string" || new URL(value).origin !== naraiUrl) {
30
+ throw new NaraiError(`${naraiUrl}'s ${field} is missing or not on ${naraiUrl}`, "oauth_metadata_invalid");
31
+ }
32
+ return value;
33
+ };
34
+ const methods = doc["code_challenge_methods_supported"];
35
+ if (!Array.isArray(methods) || !methods.includes("S256"))
36
+ throw new NaraiError(`${naraiUrl} doesn't do PKCE S256`, "oauth_metadata_invalid");
37
+ return {
38
+ issuer: naraiUrl,
39
+ authorization_endpoint: endpoint("authorization_endpoint"),
40
+ token_endpoint: endpoint("token_endpoint"),
41
+ revocation_endpoint: endpoint("revocation_endpoint"),
42
+ iss_parameter: doc["authorization_response_iss_parameter_supported"] === true,
43
+ };
44
+ }
45
+ export function pkce() {
46
+ const verifier = randomBytes(32).toString("base64url");
47
+ return { verifier, challenge: createHash("sha256").update(verifier).digest("base64url") };
48
+ }
49
+ export const newState = () => randomBytes(32).toString("base64url");
50
+ export function authorizationUrl(meta, p) {
51
+ const url = new URL(meta.authorization_endpoint);
52
+ url.search = new URLSearchParams({
53
+ response_type: "code",
54
+ client_id: clientId(p.naraiUrl),
55
+ redirect_uri: p.redirectUri,
56
+ code_challenge: p.challenge,
57
+ code_challenge_method: "S256",
58
+ scope: SCOPES.join(" "),
59
+ state: p.state,
60
+ resource: resource(p.naraiUrl),
61
+ }).toString();
62
+ return url.toString();
63
+ }
64
+ async function tokenRequest(meta, form) {
65
+ const response = await send(meta.token_endpoint, {
66
+ method: "POST",
67
+ headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
68
+ body: new URLSearchParams(form).toString(),
69
+ });
70
+ const body = (await response.json().catch(() => null));
71
+ if (!response.ok) {
72
+ const error = typeof body?.["error"] === "string" ? body["error"] : `http_${response.status}`;
73
+ throw new OAuthError(error, typeof body?.["error_description"] === "string" ? body["error_description"] : "");
74
+ }
75
+ if (typeof body?.["access_token"] !== "string" || typeof body["refresh_token"] !== "string" || typeof body["expires_in"] !== "number"
76
+ || String(body["token_type"]).toLowerCase() !== "bearer") {
77
+ throw new NaraiError("the token endpoint answered without a bearer token and a refresh token", "oauth_bad_response");
78
+ }
79
+ const scopes = typeof body["scope"] === "string" ? body["scope"].split(" ").filter(Boolean) : [];
80
+ return { accessToken: body["access_token"], refreshToken: body["refresh_token"], expiresIn: body["expires_in"], scopes };
81
+ }
82
+ export function exchangeCode(meta, p) {
83
+ return tokenRequest(meta, {
84
+ grant_type: "authorization_code", client_id: clientId(p.naraiUrl), code: p.code, code_verifier: p.verifier,
85
+ redirect_uri: p.redirectUri, resource: resource(p.naraiUrl),
86
+ });
87
+ }
88
+ /** Rotation: the answer carries a new refresh token and the old one is dead; store the new one before anything else. */
89
+ export function refresh(meta, p) {
90
+ return tokenRequest(meta, { grant_type: "refresh_token", client_id: clientId(p.naraiUrl), refresh_token: p.refreshToken, resource: resource(p.naraiUrl) });
91
+ }
92
+ export async function revoke(meta, p) {
93
+ const response = await send(meta.revocation_endpoint, {
94
+ method: "POST",
95
+ headers: { "content-type": "application/x-www-form-urlencoded" },
96
+ body: new URLSearchParams({ token: p.token, token_type_hint: "refresh_token", client_id: clientId(p.naraiUrl) }).toString(),
97
+ });
98
+ if (!response.ok)
99
+ throw new NaraiError(`Narai didn't revoke the session (HTTP ${response.status})`, "oauth_revoke_failed");
100
+ }
101
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /** Where the lock that serializes a refresh lives: one per Narai host, shared by every `narai` on this machine. */
2
+ export declare const lockFile: (naraiUrl: string) => string;
3
+ /** The bearer token for a call: NARAI_TOKEN when set (a CI token, never stored), else an access token from the stored
4
+ * session's refresh token. The refresh rotates it, so it runs under the lock and stores the new one first. */
5
+ export declare function bearer(naraiUrl: string): Promise<string>;
6
+ /** For tests: forget the access tokens held in this process. */
7
+ export declare const forgetAccessTokens: () => void;
@@ -0,0 +1,41 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { deps } from "../lib/deps.js";
4
+ import { AuthError } from "../lib/errors.js";
5
+ import { withFileLock } from "../lib/lock.js";
6
+ import { discover, OAuthError, refresh } from "./oauth.js";
7
+ /** Where the lock that serializes a refresh lives: one per Narai host, shared by every `narai` on this machine. */
8
+ export const lockFile = (naraiUrl) => join(process.env["XDG_STATE_HOME"] ?? join(homedir(), ".local", "state"), "narai", `${new URL(naraiUrl).host}.lock`);
9
+ const fresh = new Map();
10
+ /** The bearer token for a call: NARAI_TOKEN when set (a CI token, never stored), else an access token from the stored
11
+ * session's refresh token. The refresh rotates it, so it runs under the lock and stores the new one first. */
12
+ export async function bearer(naraiUrl) {
13
+ const env = process.env["NARAI_TOKEN"];
14
+ if (env)
15
+ return env;
16
+ const cached = fresh.get(naraiUrl);
17
+ if (cached && cached.until > deps.now())
18
+ return cached.token;
19
+ return withFileLock(lockFile(naraiUrl), async () => {
20
+ const stored = await deps.credentials.get(naraiUrl);
21
+ if (!stored)
22
+ throw new AuthError(`not signed in to ${new URL(naraiUrl).host}: run narai login`);
23
+ const meta = await discover(naraiUrl);
24
+ try {
25
+ const tokens = await refresh(meta, { naraiUrl, refreshToken: stored });
26
+ await deps.credentials.set(naraiUrl, tokens.refreshToken);
27
+ fresh.set(naraiUrl, { token: tokens.accessToken, until: deps.now() + Math.max(0, tokens.expiresIn - 30) * 1000 });
28
+ return tokens.accessToken;
29
+ }
30
+ catch (error) {
31
+ if (error instanceof OAuthError && error.error === "invalid_grant") {
32
+ await deps.credentials.delete(naraiUrl);
33
+ throw new AuthError("the session has ended (signed out elsewhere, or expired): run narai login", "session_ended");
34
+ }
35
+ throw error;
36
+ }
37
+ });
38
+ }
39
+ /** For tests: forget the access tokens held in this process. */
40
+ export const forgetAccessTokens = () => fresh.clear();
41
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3D,mHAAmH;AACnH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC;AAE3K,MAAM,KAAK,GAAG,IAAI,GAAG,EAA4C,CAAC;AAElE;+GAC+G;AAC/G,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,QAAgB;IAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACvC,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC;IACpB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC;IAC7D,OAAO,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,SAAS,CAAC,oBAAoB,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC;QAChG,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;YACvE,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;YAC1D,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;YAClH,OAAO,MAAM,CAAC,WAAW,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,UAAU,IAAI,KAAK,CAAC,KAAK,KAAK,eAAe,EAAE,CAAC;gBACnE,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACxC,MAAM,IAAI,SAAS,CAAC,2EAA2E,EAAE,eAAe,CAAC,CAAC;YACpH,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAS,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { Command, type Interfaces } from "@oclif/core";
2
+ import { Output } from "./lib/output.js";
3
+ export type FlagsOf<T extends typeof Command> = Interfaces.InferredFlags<(typeof NaraiCommand)["baseFlags"] & T["flags"]>;
4
+ export type ArgsOf<T extends typeof Command> = Interfaces.InferredArgs<T["args"]>;
5
+ /** Narai's own address: the beta, where Narai runs today (the developer beta included). */
6
+ export declare const NARAI_URL = "https://beta.michinar.ai";
7
+ /** Every command: the global flags, one output (results to stdout, progress to stderr), and every failure mapped to
8
+ * the exit codes: 0 ok, 1 error, 2 usage, 3 auth. */
9
+ export declare abstract class NaraiCommand<T extends typeof Command = typeof Command> extends Command {
10
+ static baseFlags: {
11
+ json: Interfaces.BooleanFlag<boolean>;
12
+ yes: Interfaces.BooleanFlag<boolean>;
13
+ "no-input": Interfaces.BooleanFlag<boolean>;
14
+ "narai-url": Interfaces.OptionFlag<string | undefined, Interfaces.CustomOptions>;
15
+ };
16
+ protected flags: FlagsOf<T>;
17
+ protected args: ArgsOf<T>;
18
+ protected out: Output;
19
+ init(): Promise<void>;
20
+ /** The Narai this command talks to: `--narai-url`, else NARAI_URL, else Narai. No trailing slash. */
21
+ protected get naraiUrl(): string;
22
+ /** Prompts only with a person at the keyboard. */
23
+ protected get interactive(): boolean;
24
+ /** Confirm a change: `--yes` answers; with no one to ask, fail with what would have been asked. */
25
+ protected confirm(question: string, details?: Record<string, unknown>): Promise<void>;
26
+ catch(error: Error & {
27
+ exitCode?: number;
28
+ }): Promise<never>;
29
+ }
@@ -0,0 +1,65 @@
1
+ import { Command, Errors, Flags } from "@oclif/core";
2
+ import { deps } from "./lib/deps.js";
3
+ import { ExitCode, NaraiError, NeedsInputError } from "./lib/errors.js";
4
+ import { Output } from "./lib/output.js";
5
+ /** Narai's own address: the beta, where Narai runs today (the developer beta included). */
6
+ export const NARAI_URL = "https://beta.michinar.ai";
7
+ /** Every command: the global flags, one output (results to stdout, progress to stderr), and every failure mapped to
8
+ * the exit codes: 0 ok, 1 error, 2 usage, 3 auth. */
9
+ export class NaraiCommand extends Command {
10
+ static baseFlags = {
11
+ json: Flags.boolean({ description: "Print the result as JSON (the default when stdout isn't a terminal).", helpGroup: "GLOBAL" }),
12
+ yes: Flags.boolean({ description: "Answer yes to every confirmation.", helpGroup: "GLOBAL" }),
13
+ "no-input": Flags.boolean({ description: "Never prompt: a needed confirmation fails instead.", helpGroup: "GLOBAL" }),
14
+ "narai-url": Flags.string({ description: `Narai's address (default ${NARAI_URL}; also NARAI_URL).`, helpGroup: "GLOBAL" }),
15
+ };
16
+ flags;
17
+ args;
18
+ out;
19
+ async init() {
20
+ await super.init();
21
+ const ctor = this.ctor;
22
+ const { args, flags } = await this.parse({ args: ctor.args, baseFlags: ctor.baseFlags, flags: ctor.flags, strict: ctor.strict });
23
+ this.flags = flags;
24
+ this.args = args;
25
+ this.out = new Output({ json: this.flags.json || !deps.stdoutIsTTY() });
26
+ }
27
+ /** The Narai this command talks to: `--narai-url`, else NARAI_URL, else Narai. No trailing slash. */
28
+ get naraiUrl() {
29
+ const url = new URL(this.flags["narai-url"] ?? process.env["NARAI_URL"] ?? NARAI_URL);
30
+ const local = url.protocol === "http:" && ["localhost", "127.0.0.1"].includes(url.hostname);
31
+ if (url.protocol !== "https:" && !local)
32
+ throw new NaraiError(`${url.origin} isn't https`, "insecure_url");
33
+ return url.origin;
34
+ }
35
+ /** Prompts only with a person at the keyboard. */
36
+ get interactive() {
37
+ return !this.flags["no-input"] && deps.stdinIsTTY();
38
+ }
39
+ /** Confirm a change: `--yes` answers; with no one to ask, fail with what would have been asked. */
40
+ async confirm(question, details = {}) {
41
+ if (this.flags.yes)
42
+ return;
43
+ if (!this.interactive)
44
+ throw new NeedsInputError(`${question}: needs confirmation; run again with --yes`, details);
45
+ if (!(await deps.confirm(`${question}?`)))
46
+ throw new NaraiError("cancelled", "cancelled");
47
+ }
48
+ async catch(error) {
49
+ if (error instanceof Errors.ExitError)
50
+ throw error;
51
+ const out = this.out ?? new Output({ json: this.argv.includes("--json") });
52
+ if (error instanceof NaraiError) {
53
+ out.error(error.code, error.message, error.details);
54
+ this.exit(error.exitCode);
55
+ }
56
+ if (error instanceof Errors.CLIError) {
57
+ // flag and argument parsing
58
+ out.error("usage", error.message);
59
+ this.exit(ExitCode.usage);
60
+ }
61
+ out.error("unexpected", error.message);
62
+ this.exit(ExitCode.error);
63
+ }
64
+ }
65
+ //# sourceMappingURL=base-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-command.js","sourceRoot":"","sources":["../src/base-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { NaraiCommand } from "../base-command.js";
2
+ export default class Build extends NaraiCommand<typeof Build> {
3
+ static summary: string;
4
+ static description: string;
5
+ static flags: {
6
+ dir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,34 @@
1
+ import { stat, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { Flags } from "@oclif/core";
4
+ import { manifestProblems } from "@michinarai/sdk";
5
+ import { NaraiCommand } from "../base-command.js";
6
+ import { bundleDigest, digestFiles } from "../lib/digest.js";
7
+ import { NaraiError } from "../lib/errors.js";
8
+ import { MANIFEST, checkedManifest, toolRoot } from "../lib/project.js";
9
+ export default class Build extends NaraiCommand {
10
+ static summary = "Seal a release: every built file with its sha256, and the bundle digest, in the release's manifest.";
11
+ static description = `Run after the tool's own build. Writes <dir>/${MANIFEST}: the manifest with \`files\` (sorted by path) and \`bundle_digest\`, which Narai checks an upload against.`;
12
+ static flags = {
13
+ dir: Flags.string({ description: "The tool's built files.", default: "dist" }),
14
+ };
15
+ async run() {
16
+ const root = await toolRoot(process.cwd());
17
+ const manifest = await checkedManifest(root);
18
+ const dir = resolve(root, this.flags.dir);
19
+ if (!(await stat(dir).catch(() => null))?.isDirectory()) {
20
+ throw new NaraiError(`${this.flags.dir}/ isn't there: build the tool first`, "no_build");
21
+ }
22
+ const files = await digestFiles(dir);
23
+ if (files.length === 0)
24
+ throw new NaraiError(`${this.flags.dir}/ is empty`, "no_build");
25
+ const release = { ...manifest, files, bundle_digest: bundleDigest(files) };
26
+ const problems = manifestProblems(release);
27
+ if (problems.length)
28
+ throw new NaraiError("the release doesn't match the contract", "release_invalid", 1, { problems });
29
+ await writeFile(join(dir, MANIFEST), `${JSON.stringify(release, null, 2)}\n`);
30
+ const bytes = files.reduce((n, f) => n + f.size, 0);
31
+ this.out.result({ id: manifest.id, version: manifest.version, files: files.length, bytes, bundle_digest: release.bundle_digest, manifest: join(this.flags.dir, MANIFEST) }, () => [`${manifest.id} ${manifest.version}: ${files.length} files, ${bytes} bytes`, release.bundle_digest]);
32
+ }
33
+ }
34
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { NaraiCommand } from "../base-command.js";
2
+ export default class Dev extends NaraiCommand<typeof Dev> {
3
+ static summary: string;
4
+ static description: string;
5
+ static flags: {
6
+ dir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ port: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ "tool-port": import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ locale: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ preset: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,55 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { Flags } from "@oclif/core";
4
+ import { startHarness } from "@michinarai/sdk/harness";
5
+ import { NaraiCommand } from "../base-command.js";
6
+ import { NaraiError } from "../lib/errors.js";
7
+ import { checkedManifest, toolRoot } from "../lib/project.js";
8
+ async function json(path, what) {
9
+ if (!path)
10
+ return undefined;
11
+ try {
12
+ return JSON.parse(await readFile(path, "utf8"));
13
+ }
14
+ catch (error) {
15
+ throw new NaraiError(`${what} ${path}: ${error.message}`, "bad_input");
16
+ }
17
+ }
18
+ export default class Dev extends NaraiCommand {
19
+ static summary = "Run the tool in the local harness, mounted as a lesson mounts it.";
20
+ static description = "Narai's host on http://localhost, the tool on http://127.0.0.1 in a sandboxed iframe: two origins, the real handshake, every message checked against the manifest. Open the host's address in a browser; stop with Ctrl-C.";
21
+ static flags = {
22
+ dir: Flags.string({ description: "The tool's built files (served at the embed URL's path).", default: "dist" }),
23
+ port: Flags.integer({ description: "The host page's port (default: any free one)." }),
24
+ "tool-port": Flags.integer({ description: "The tool's port (default: any free one)." }),
25
+ locale: Flags.string({ description: "The locale the tool starts in (default: its first)." }),
26
+ task: Flags.string({ description: "A JSON file: the task Narai would start it on ({id, kind, assist, prompt})." }),
27
+ preset: Flags.string({ description: "A JSON file: the step's preset payload ({schema, ...})." }),
28
+ };
29
+ async run() {
30
+ const root = await toolRoot(process.cwd());
31
+ const manifest = await checkedManifest(root);
32
+ const options = { manifest, toolDir: resolve(root, this.flags.dir) };
33
+ if (this.flags.port !== undefined)
34
+ options.hostPort = this.flags.port;
35
+ if (this.flags["tool-port"] !== undefined)
36
+ options.toolPort = this.flags["tool-port"];
37
+ if (this.flags.locale)
38
+ options.locale = this.flags.locale;
39
+ const task = await json(this.flags.task, "task");
40
+ const preset = await json(this.flags.preset, "preset");
41
+ if (task)
42
+ options.task = task;
43
+ if (preset)
44
+ options.preset = preset;
45
+ const harness = await startHarness(options);
46
+ this.out.result({ host: harness.hostUrl, tool: harness.toolUrl }, () => [`host ${harness.hostUrl}`, `tool ${harness.toolUrl}`]);
47
+ this.out.progress("Ctrl-C to stop");
48
+ await new Promise((done) => {
49
+ const stop = () => { void harness.close().then(done); };
50
+ process.once("SIGINT", stop);
51
+ process.once("SIGTERM", stop);
52
+ });
53
+ }
54
+ }
55
+ //# sourceMappingURL=dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { NaraiCommand } from "../base-command.js";
2
+ export default class Init extends NaraiCommand<typeof Init> {
3
+ static summary: string;
4
+ static args: {
5
+ name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static flags: {
8
+ publisher: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,63 @@
1
+ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname, join, relative, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { Args, Flags } from "@oclif/core";
5
+ import { manifestProblems } from "@michinarai/sdk";
6
+ import { NaraiCommand } from "../base-command.js";
7
+ import { NaraiError } from "../lib/errors.js";
8
+ import { MANIFEST } from "../lib/project.js";
9
+ /** The template, shipped in the package beside dist/. */
10
+ const TEMPLATE = fileURLToPath(new URL("../../templates/tool/", import.meta.url));
11
+ const SLUG = /^[a-z0-9][a-z0-9-]{0,62}$/;
12
+ async function walk(dir) {
13
+ const entries = await readdir(dir, { recursive: true, withFileTypes: true });
14
+ return entries.filter((e) => e.isFile()).map((e) => join(e.parentPath, e.name));
15
+ }
16
+ /** The SDK the template depends on: the version this CLI is built with (it pins one), or a later compatible one. */
17
+ async function sdkSpec() {
18
+ const pkg = JSON.parse(await readFile(fileURLToPath(new URL("../../package.json", import.meta.url)), "utf8"));
19
+ const own = pkg.dependencies["@michinarai/sdk"] ?? "^1";
20
+ return /^\d+\.\d+\.\d+$/.test(own) ? `^${own}` : own;
21
+ }
22
+ export default class Init extends NaraiCommand {
23
+ static summary = "Start a new tool from the template: a manifest, a page, the SDK, an AGENTS.md and its skill.";
24
+ static args = { name: Args.string({ description: "The tool's name (lowercase, digits, -).", required: true }) };
25
+ static flags = {
26
+ publisher: Flags.string({ description: "Your publisher's slug: the tool's id is <publisher>/<name>.", required: true }),
27
+ dir: Flags.string({ description: "Where to write it (default: ./<name>)." }),
28
+ };
29
+ async run() {
30
+ const { name } = this.args;
31
+ const { publisher } = this.flags;
32
+ for (const [what, value] of [["name", name], ["publisher", publisher]]) {
33
+ if (!SLUG.test(value))
34
+ throw new NaraiError(`${what} ${value}: lowercase letters, digits and -, up to 63`, "bad_slug", 2);
35
+ }
36
+ const target = resolve(this.flags.dir ?? name);
37
+ const existing = await readdir(target).catch(() => []);
38
+ if (existing.length > 0)
39
+ throw new NaraiError(`${relative(process.cwd(), target) || "."} isn't empty`, "not_empty");
40
+ const family = name.replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").replace(/^(\d)/, "t$1");
41
+ const values = { id: `${publisher}/${name}`, name, publisher, family, sdk: await sdkSpec() };
42
+ const fill = (text) => text.replace(/\{\{(\w+)\}\}/g, (m, key) => values[key] ?? m);
43
+ const written = [];
44
+ for (const source of await walk(TEMPLATE)) {
45
+ const path = relative(TEMPLATE, source);
46
+ const out = join(target, path);
47
+ await mkdir(dirname(out), { recursive: true });
48
+ await writeFile(out, fill(await readFile(source, "utf8")));
49
+ written.push(path.split("\\").join("/"));
50
+ }
51
+ const problems = manifestProblems(JSON.parse(await readFile(join(target, MANIFEST), "utf8")));
52
+ if (problems.length)
53
+ throw new NaraiError("the template's manifest doesn't match the contract (a bug in narai)", "template_invalid", 1, { problems });
54
+ written.sort();
55
+ this.out.result({ id: values["id"], dir: target, files: written }, () => [
56
+ `${values["id"]} in ${relative(process.cwd(), target) || "."}`,
57
+ ...written.map((f) => ` ${f}`),
58
+ "",
59
+ `next: cd ${relative(process.cwd(), target) || "."} && npm install && npm run dev`,
60
+ ]);
61
+ }
62
+ }
63
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { NaraiCommand } from "../base-command.js";
2
+ export default class Login extends NaraiCommand<typeof Login> {
3
+ static summary: string;
4
+ static description: string;
5
+ static flags: {
6
+ "no-browser": import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,37 @@
1
+ import { Flags } from "@oclif/core";
2
+ import { NaraiCommand } from "../base-command.js";
3
+ import { startLoopback } from "../auth/loopback.js";
4
+ import { lockFile } from "../auth/session.js";
5
+ import { authorizationUrl, discover, exchangeCode, newState, pkce } from "../auth/oauth.js";
6
+ import { deps } from "../lib/deps.js";
7
+ import { withFileLock } from "../lib/lock.js";
8
+ export default class Login extends NaraiCommand {
9
+ static summary = "Sign in to Narai in the browser; the session stays in the OS keychain.";
10
+ static description = "Opens Narai's sign-in page; the answer comes back to this machine only (127.0.0.1). The session's refresh token is kept in the OS keychain and rotates on every use. In CI, set NARAI_TOKEN to a tool's token instead.";
11
+ static flags = {
12
+ "no-browser": Flags.boolean({ description: "Print the sign-in address; don't open it." }),
13
+ timeout: Flags.integer({ description: "Seconds to wait for the browser.", default: 300 }),
14
+ };
15
+ async run() {
16
+ const naraiUrl = this.naraiUrl;
17
+ const meta = await discover(naraiUrl);
18
+ const { verifier, challenge } = pkce();
19
+ const state = newState();
20
+ const loopback = await startLoopback({ state, issuer: meta.iss_parameter ? meta.issuer : null, timeoutMs: this.flags.timeout * 1000 });
21
+ try {
22
+ const url = authorizationUrl(meta, { naraiUrl, redirectUri: loopback.redirectUri, challenge, state });
23
+ this.out.prompt(`Sign in to Narai: ${url}`);
24
+ if (!this.flags["no-browser"])
25
+ deps.openBrowser(url);
26
+ const code = await loopback.code;
27
+ const tokens = await exchangeCode(meta, { naraiUrl, code, verifier, redirectUri: loopback.redirectUri });
28
+ const host = new URL(naraiUrl).host;
29
+ await withFileLock(lockFile(naraiUrl), () => deps.credentials.set(naraiUrl, tokens.refreshToken));
30
+ this.out.result({ signed_in: host, scopes: tokens.scopes }, () => `signed in to ${host}`);
31
+ }
32
+ finally {
33
+ loopback.close();
34
+ }
35
+ }
36
+ }
37
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,MAAM,CAAC,OAAO,OAAO,KAAM,SAAQ,YAA0B;IAC3D,MAAM,CAAU,OAAO,GAAG,wEAAwE,CAAC;IACnG,MAAM,CAAU,WAAW,GAAG,wNAAwN,CAAC;IACvP,MAAM,CAAU,KAAK,GAAG;QACtB,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC;QACzF,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,kCAAkC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;KAC1F,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC,CAAC;QACvI,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YACtG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;YAC5C,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;gBAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACrD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC;YACjC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;YACzG,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;YACpC,MAAM,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;YAClG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC;QAC5F,CAAC;gBAAS,CAAC;YACT,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;IACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { NaraiCommand } from "../base-command.js";
2
+ export default class Logout extends NaraiCommand<typeof Logout> {
3
+ static summary: string;
4
+ run(): Promise<void>;
5
+ }