implto-tos-axi 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/dist/bin/implto-tos-axi.d.ts +2 -0
- package/dist/bin/implto-tos-axi.js +16 -0
- package/dist/bin/implto-tos-axi.js.map +1 -0
- package/dist/src/auth/loopback.d.ts +29 -0
- package/dist/src/auth/loopback.js +110 -0
- package/dist/src/auth/loopback.js.map +1 -0
- package/dist/src/auth/open-browser.d.ts +9 -0
- package/dist/src/auth/open-browser.js +24 -0
- package/dist/src/auth/open-browser.js.map +1 -0
- package/dist/src/auth/provider.d.ts +39 -0
- package/dist/src/auth/provider.js +106 -0
- package/dist/src/auth/provider.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +183 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/components.d.ts +40 -0
- package/dist/src/commands/components.js +417 -0
- package/dist/src/commands/components.js.map +1 -0
- package/dist/src/commands/context.d.ts +10 -0
- package/dist/src/commands/context.js +46 -0
- package/dist/src/commands/context.js.map +1 -0
- package/dist/src/commands/guide.d.ts +16 -0
- package/dist/src/commands/guide.js +203 -0
- package/dist/src/commands/guide.js.map +1 -0
- package/dist/src/commands/home.d.ts +9 -0
- package/dist/src/commands/home.js +33 -0
- package/dist/src/commands/home.js.map +1 -0
- package/dist/src/commands/init.d.ts +12 -0
- package/dist/src/commands/init.js +65 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/login.d.ts +13 -0
- package/dist/src/commands/login.js +137 -0
- package/dist/src/commands/login.js.map +1 -0
- package/dist/src/commands/tasks.d.ts +8 -0
- package/dist/src/commands/tasks.js +344 -0
- package/dist/src/commands/tasks.js.map +1 -0
- package/dist/src/commands/tools.d.ts +66 -0
- package/dist/src/commands/tools.js +401 -0
- package/dist/src/commands/tools.js.map +1 -0
- package/dist/src/commands/whoami.d.ts +5 -0
- package/dist/src/commands/whoami.js +66 -0
- package/dist/src/commands/whoami.js.map +1 -0
- package/dist/src/config/binding.d.ts +29 -0
- package/dist/src/config/binding.js +42 -0
- package/dist/src/config/binding.js.map +1 -0
- package/dist/src/config/credentials.d.ts +39 -0
- package/dist/src/config/credentials.js +62 -0
- package/dist/src/config/credentials.js.map +1 -0
- package/dist/src/config/profiles.d.ts +24 -0
- package/dist/src/config/profiles.js +64 -0
- package/dist/src/config/profiles.js.map +1 -0
- package/dist/src/errors.d.ts +21 -0
- package/dist/src/errors.js +40 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/flags.d.ts +50 -0
- package/dist/src/flags.js +143 -0
- package/dist/src/flags.js.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/client.d.ts +85 -0
- package/dist/src/mcp/client.js +130 -0
- package/dist/src/mcp/client.js.map +1 -0
- package/dist/src/output.d.ts +8 -0
- package/dist/src/output.js +2 -0
- package/dist/src/output.js.map +1 -0
- package/dist/src/resolve.d.ts +16 -0
- package/dist/src/resolve.js +48 -0
- package/dist/src/resolve.js.map +1 -0
- package/dist/src/scopes.d.ts +9 -0
- package/dist/src/scopes.js +16 -0
- package/dist/src/scopes.js.map +1 -0
- package/dist/src/session.d.ts +40 -0
- package/dist/src/session.js +75 -0
- package/dist/src/session.js.map +1 -0
- package/dist/src/skill.d.ts +32 -0
- package/dist/src/skill.js +149 -0
- package/dist/src/skill.js.map +1 -0
- package/dist/src/version.d.ts +5 -0
- package/dist/src/version.js +6 -0
- package/dist/src/version.js.map +1 -0
- package/package.json +70 -0
- package/skills/implto-tos-delivery/SKILL.md +45 -0
- package/skills/implto-tos-tasks/SKILL.md +48 -0
- package/skills/implto-tos-workspace/SKILL.md +45 -0
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { runAxiCli } from "axi-sdk-js";
|
|
2
|
+
import { encode as toon } from "@toon-format/toon";
|
|
3
|
+
import { isTosError } from "./errors.js";
|
|
4
|
+
import { VERSION } from "./version.js";
|
|
5
|
+
import { homeView } from "./commands/home.js";
|
|
6
|
+
import { guideCommand, guideHelp } from "./commands/guide.js";
|
|
7
|
+
import { loginCommand, LOGIN_FLAGS } from "./commands/login.js";
|
|
8
|
+
import { whoamiCommand, logoutCommand, PROFILE_FLAGS } from "./commands/whoami.js";
|
|
9
|
+
import { initCommand, INIT_FLAGS } from "./commands/init.js";
|
|
10
|
+
import { contextCommand } from "./commands/context.js";
|
|
11
|
+
import { tasksCommand, SEARCH_FLAGS, CREATE_FLAGS, UPDATE_FLAGS } from "./commands/tasks.js";
|
|
12
|
+
import { componentsCommand, COMPONENTS_LIST_FLAGS, COMPONENTS_CREATE_FLAGS, COMPONENTS_UPDATE_FLAGS, } from "./commands/components.js";
|
|
13
|
+
import { toolsCommand, LIST_FLAGS, CALL_FLAGS } from "./commands/tools.js";
|
|
14
|
+
import { renderFlagHelp } from "./flags.js";
|
|
15
|
+
export const DESCRIPTION = "Work with Transformation OS from a repository - tasks, deliverables, and the rest of the workspace";
|
|
16
|
+
/**
|
|
17
|
+
* Top-level help.
|
|
18
|
+
*
|
|
19
|
+
* Terse by design: this block plus `<command> --help` is what an agent reads,
|
|
20
|
+
* and every line costs tokens. Anything longer lives behind `guide <topic>` so
|
|
21
|
+
* it is pulled one topic at a time.
|
|
22
|
+
*/
|
|
23
|
+
const TOP_LEVEL_HELP = `${[
|
|
24
|
+
`usage: implto-tos-axi <command> [args] [flags]`,
|
|
25
|
+
``,
|
|
26
|
+
`commands:`,
|
|
27
|
+
` login Sign in through the browser, or --token to paste a key`,
|
|
28
|
+
` logout Forget this machine's credential`,
|
|
29
|
+
` whoami Who this credential acts as, and what it can reach`,
|
|
30
|
+
` init Bind this directory to a workspace and project`,
|
|
31
|
+
` context Project, statuses, types and who you are. Run this first`,
|
|
32
|
+
` tasks search <words> Find tasks. --open for work in flight, --mine for yours`,
|
|
33
|
+
` tasks show <key> One task in full`,
|
|
34
|
+
` tasks mine Your tasks`,
|
|
35
|
+
` tasks create <title> File one. --assign me by default`,
|
|
36
|
+
` tasks update <key> Change status, assignee, title, description, due date`,
|
|
37
|
+
` tasks comment <key> Leave a note on it`,
|
|
38
|
+
` components list The deliverables, as a tree. show|create|update too`,
|
|
39
|
+
` tools Every area the server has, and what this key holds`,
|
|
40
|
+
` tools call <name> Run any tool. Everything not covered above`,
|
|
41
|
+
` guide [topic] Topic-sized guidance. Run bare to list topics`,
|
|
42
|
+
``,
|
|
43
|
+
`flags must come after the command. \`--version\`, \`--help\` excepted.`,
|
|
44
|
+
].join("\n")}\n`;
|
|
45
|
+
function commandHelp(name, description, usage, specs) {
|
|
46
|
+
const flags = renderFlagHelp(specs);
|
|
47
|
+
return `${[
|
|
48
|
+
`command: ${name}`,
|
|
49
|
+
`description: ${description}`,
|
|
50
|
+
``,
|
|
51
|
+
`usage:`,
|
|
52
|
+
...usage.map((u) => ` ${u}`),
|
|
53
|
+
...(flags.length > 0 ? [``, `flags:`, ...flags] : []),
|
|
54
|
+
].join("\n")}\n`;
|
|
55
|
+
}
|
|
56
|
+
const COMMAND_HELP = {
|
|
57
|
+
guide: guideHelp(),
|
|
58
|
+
login: commandHelp("login", "Sign in to a deployment. Opens a browser unless you paste a key", [
|
|
59
|
+
"implto-tos-axi login",
|
|
60
|
+
"implto-tos-axi login --profile dev",
|
|
61
|
+
"implto-tos-axi login --token tos_… Headless, no browser",
|
|
62
|
+
"implto-tos-axi login --scopes tasks Ask for less",
|
|
63
|
+
], LOGIN_FLAGS),
|
|
64
|
+
logout: commandHelp("logout", "Remove this machine's credential", ["implto-tos-axi logout"], PROFILE_FLAGS),
|
|
65
|
+
whoami: commandHelp("whoami", "Who this credential acts as, which workspaces it reaches, and how to assign to yourself", ["implto-tos-axi whoami"], PROFILE_FLAGS),
|
|
66
|
+
init: commandHelp("init", "Bind this directory to a workspace and project. Run bare to list what you can reach", ["implto-tos-axi init", 'implto-tos-axi init --workspace "Implemented Today" --project DT'], INIT_FLAGS),
|
|
67
|
+
context: commandHelp("context", "The project, its statuses and types, and who you are. The first call of a session", ["implto-tos-axi context"], PROFILE_FLAGS),
|
|
68
|
+
components: `${[
|
|
69
|
+
"command: components",
|
|
70
|
+
"description: The deliverables of the workspace, in a tree",
|
|
71
|
+
"",
|
|
72
|
+
"usage:",
|
|
73
|
+
" implto-tos-axi components list --tree",
|
|
74
|
+
' implto-tos-axi components show "Billing API"',
|
|
75
|
+
' implto-tos-axi components create "Billing API" --parent Platform --estimate 40',
|
|
76
|
+
' implto-tos-axi components update "Billing API" --parent "Payments"',
|
|
77
|
+
' implto-tos-axi components update "Billing API" --parent none # to the top level',
|
|
78
|
+
"",
|
|
79
|
+
"list flags:",
|
|
80
|
+
...renderFlagHelp(COMPONENTS_LIST_FLAGS),
|
|
81
|
+
"",
|
|
82
|
+
"create flags:",
|
|
83
|
+
...renderFlagHelp(COMPONENTS_CREATE_FLAGS),
|
|
84
|
+
"",
|
|
85
|
+
"update flags:",
|
|
86
|
+
...renderFlagHelp(COMPONENTS_UPDATE_FLAGS),
|
|
87
|
+
].join("\n")}\n`,
|
|
88
|
+
tools: `${[
|
|
89
|
+
"command: tools",
|
|
90
|
+
"description: The MCP server itself - its areas, its tools, and a way to call one",
|
|
91
|
+
"",
|
|
92
|
+
"usage:",
|
|
93
|
+
" implto-tos-axi tools The areas, and which you hold",
|
|
94
|
+
" implto-tos-axi tools domains process_maps One area, and its rules",
|
|
95
|
+
" implto-tos-axi tools list --domain organization",
|
|
96
|
+
" implto-tos-axi tools show create_person",
|
|
97
|
+
' implto-tos-axi tools call create_person name="Ann Lee" email=ann@acme.com',
|
|
98
|
+
" implto-tos-axi tools call update_process_nodes --json-file nodes.json",
|
|
99
|
+
"",
|
|
100
|
+
"arguments are name=value. `key=@file` reads a value from a file, `key=@-`",
|
|
101
|
+
"from stdin, and `--json` passes the whole object. workspaceId is filled in",
|
|
102
|
+
"from the binding when you leave it out.",
|
|
103
|
+
"",
|
|
104
|
+
"list flags:",
|
|
105
|
+
...renderFlagHelp(LIST_FLAGS),
|
|
106
|
+
"",
|
|
107
|
+
"call flags:",
|
|
108
|
+
...renderFlagHelp(CALL_FLAGS),
|
|
109
|
+
].join("\n")}\n`,
|
|
110
|
+
tasks: `${[
|
|
111
|
+
"command: tasks",
|
|
112
|
+
"description: Find, read and change tasks in the bound project",
|
|
113
|
+
"",
|
|
114
|
+
"usage:",
|
|
115
|
+
' implto-tos-axi tasks search "login redirect" --open',
|
|
116
|
+
" implto-tos-axi tasks mine --open",
|
|
117
|
+
" implto-tos-axi tasks show TO-112",
|
|
118
|
+
' implto-tos-axi tasks create "Fix the import guard" --assign me',
|
|
119
|
+
' implto-tos-axi tasks update TO-112 --status "In Progress"',
|
|
120
|
+
' implto-tos-axi tasks comment TO-112 "Shipped in #204"',
|
|
121
|
+
"",
|
|
122
|
+
"search flags:",
|
|
123
|
+
...renderFlagHelp(SEARCH_FLAGS),
|
|
124
|
+
"",
|
|
125
|
+
"create flags:",
|
|
126
|
+
...renderFlagHelp(CREATE_FLAGS),
|
|
127
|
+
"",
|
|
128
|
+
"update flags:",
|
|
129
|
+
...renderFlagHelp(UPDATE_FLAGS),
|
|
130
|
+
].join("\n")}\n`,
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Our errors, rendered the way every successful payload is.
|
|
134
|
+
*
|
|
135
|
+
* The `help` list is the same field a success carries, so recovering from a
|
|
136
|
+
* failure reads like reading a result - an agent does not have to learn a
|
|
137
|
+
* second shape for the unhappy path.
|
|
138
|
+
*
|
|
139
|
+
* VALIDATION_ERROR is the only code that exits 2, so a script can tell a
|
|
140
|
+
* mistake it made from work that ran and failed.
|
|
141
|
+
*/
|
|
142
|
+
function formatError(error) {
|
|
143
|
+
if (isTosError(error)) {
|
|
144
|
+
return {
|
|
145
|
+
output: `${toon({
|
|
146
|
+
error: error.message,
|
|
147
|
+
code: error.code,
|
|
148
|
+
...(error.suggestions.length > 0 ? { help: error.suggestions } : {}),
|
|
149
|
+
})}\n`,
|
|
150
|
+
exitCode: error.code === "VALIDATION_ERROR" ? 2 : 1,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
output: `${toon({
|
|
155
|
+
error: error instanceof Error ? error.message : String(error),
|
|
156
|
+
code: "UNKNOWN",
|
|
157
|
+
})}\n`,
|
|
158
|
+
exitCode: 1,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export async function main(argv) {
|
|
162
|
+
await runAxiCli({
|
|
163
|
+
formatError,
|
|
164
|
+
description: DESCRIPTION,
|
|
165
|
+
version: VERSION,
|
|
166
|
+
topLevelHelp: TOP_LEVEL_HELP,
|
|
167
|
+
...(argv ? { argv } : {}),
|
|
168
|
+
home: () => homeView(),
|
|
169
|
+
commands: {
|
|
170
|
+
login: (args) => loginCommand(args),
|
|
171
|
+
logout: (args) => logoutCommand(args),
|
|
172
|
+
whoami: (args) => whoamiCommand(args),
|
|
173
|
+
init: (args) => initCommand(args),
|
|
174
|
+
context: (args) => contextCommand(args),
|
|
175
|
+
tasks: (args) => tasksCommand(args),
|
|
176
|
+
components: (args) => componentsCommand(args),
|
|
177
|
+
tools: (args) => toolsCommand(args),
|
|
178
|
+
guide: (args) => guideCommand(args),
|
|
179
|
+
},
|
|
180
|
+
getCommandHelp: (command) => COMMAND_HELP[command],
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type FlagSpecs } from "../flags.js";
|
|
2
|
+
import type { AxiRenderable } from "../output.js";
|
|
3
|
+
export declare const COMPONENTS_LIST_FLAGS: FlagSpecs;
|
|
4
|
+
export declare const COMPONENTS_SHOW_FLAGS: FlagSpecs;
|
|
5
|
+
export declare const COMPONENTS_WRITE_FLAGS: FlagSpecs;
|
|
6
|
+
export declare const COMPONENTS_CREATE_FLAGS: FlagSpecs;
|
|
7
|
+
export declare const COMPONENTS_UPDATE_FLAGS: FlagSpecs;
|
|
8
|
+
interface Component {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
type?: string | null;
|
|
12
|
+
importance?: string | null;
|
|
13
|
+
shortDescription?: string | null;
|
|
14
|
+
description?: string | null;
|
|
15
|
+
originalEstimateSecs?: number | null;
|
|
16
|
+
parentComponentId?: string | null;
|
|
17
|
+
parentComponentName?: string | null;
|
|
18
|
+
projectId?: string | null;
|
|
19
|
+
projectName?: string | null;
|
|
20
|
+
workspaceSystemId?: string | null;
|
|
21
|
+
workspaceSystemName?: string | null;
|
|
22
|
+
responsibleUserIds?: string[];
|
|
23
|
+
phaseEstimates?: {
|
|
24
|
+
phaseId: string;
|
|
25
|
+
phaseName: string;
|
|
26
|
+
estimateSecs: number;
|
|
27
|
+
}[];
|
|
28
|
+
issuesCount?: number;
|
|
29
|
+
processStepsCount?: number;
|
|
30
|
+
}
|
|
31
|
+
export declare function componentsCommand(argv: readonly string[]): Promise<AxiRenderable>;
|
|
32
|
+
/**
|
|
33
|
+
* The hierarchy as text.
|
|
34
|
+
*
|
|
35
|
+
* A flat list with a `parent` column is fine for filtering and useless for
|
|
36
|
+
* seeing where a new component belongs, which is the question somebody asks
|
|
37
|
+
* right before creating one.
|
|
38
|
+
*/
|
|
39
|
+
export declare function drawTree(components: readonly Component[]): string;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { parseFlags } from "../flags.js";
|
|
3
|
+
import { openSession, resolveWorkspaceId } from "../session.js";
|
|
4
|
+
import { TosError } from "../errors.js";
|
|
5
|
+
import { isClearing, looksLikeId, pick } from "../resolve.js";
|
|
6
|
+
import { PROFILE_FLAGS } from "./whoami.js";
|
|
7
|
+
/**
|
|
8
|
+
* The deliverables of a workspace: what is being built, as a tree.
|
|
9
|
+
*
|
|
10
|
+
* First-class rather than left to `tools call` because it is the other half of
|
|
11
|
+
* what a coding agent does in a repository. A change belongs to a task, and a
|
|
12
|
+
* task belongs to a component - and the component for the thing being built is
|
|
13
|
+
* as often missing as the task is. Creating one, and hanging it under the
|
|
14
|
+
* right parent, should cost one line.
|
|
15
|
+
*/
|
|
16
|
+
const COMMON = PROFILE_FLAGS;
|
|
17
|
+
const WORKSPACE_FLAG = {
|
|
18
|
+
workspace: {
|
|
19
|
+
kind: "string",
|
|
20
|
+
description: "Which workspace, by name or id (default: the binding)",
|
|
21
|
+
placeholder: "name",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
export const COMPONENTS_LIST_FLAGS = {
|
|
25
|
+
...COMMON,
|
|
26
|
+
...WORKSPACE_FLAG,
|
|
27
|
+
tree: { kind: "boolean", description: "Draw the hierarchy instead of a flat list" },
|
|
28
|
+
parent: { kind: "string", description: "Only children of this one", placeholder: "name" },
|
|
29
|
+
project: { kind: "string", description: "Only ones linked to this project", placeholder: "key" },
|
|
30
|
+
search: { kind: "string", description: "Only names matching this word", placeholder: "word" },
|
|
31
|
+
};
|
|
32
|
+
export const COMPONENTS_SHOW_FLAGS = { ...COMMON, ...WORKSPACE_FLAG };
|
|
33
|
+
export const COMPONENTS_WRITE_FLAGS = {
|
|
34
|
+
...COMMON,
|
|
35
|
+
...WORKSPACE_FLAG,
|
|
36
|
+
parent: {
|
|
37
|
+
kind: "string",
|
|
38
|
+
description: "Hang it under this component, or none for the top level",
|
|
39
|
+
placeholder: "name",
|
|
40
|
+
},
|
|
41
|
+
short: { kind: "string", description: "One-line summary shown in lists", placeholder: "text" },
|
|
42
|
+
description: { kind: "string", description: "Markdown documentation", placeholder: "text" },
|
|
43
|
+
"description-file": {
|
|
44
|
+
kind: "string",
|
|
45
|
+
description: "Read the documentation from a file, or - for stdin",
|
|
46
|
+
placeholder: "path",
|
|
47
|
+
},
|
|
48
|
+
type: {
|
|
49
|
+
kind: "string",
|
|
50
|
+
description: "SERVICE, MODULE, UI, API, INTEGRATION, DATABASE, INFRASTRUCTURE, OTHER",
|
|
51
|
+
placeholder: "type",
|
|
52
|
+
example: "SERVICE",
|
|
53
|
+
},
|
|
54
|
+
importance: { kind: "string", description: "Business criticality", placeholder: "level" },
|
|
55
|
+
estimate: {
|
|
56
|
+
kind: "number",
|
|
57
|
+
description: "Overall estimate, in hours",
|
|
58
|
+
placeholder: "hours",
|
|
59
|
+
min: 0,
|
|
60
|
+
},
|
|
61
|
+
system: {
|
|
62
|
+
kind: "string",
|
|
63
|
+
description: "Link to a workspace system, by name or id, or none",
|
|
64
|
+
placeholder: "name",
|
|
65
|
+
},
|
|
66
|
+
project: {
|
|
67
|
+
kind: "string",
|
|
68
|
+
description: "Link to a project, by key or id, or none (default: the bound project)",
|
|
69
|
+
placeholder: "key",
|
|
70
|
+
},
|
|
71
|
+
responsible: {
|
|
72
|
+
kind: "string",
|
|
73
|
+
description: "me, user ids, or none",
|
|
74
|
+
placeholder: "who",
|
|
75
|
+
example: "me",
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
export const COMPONENTS_CREATE_FLAGS = { ...COMPONENTS_WRITE_FLAGS };
|
|
79
|
+
export const COMPONENTS_UPDATE_FLAGS = {
|
|
80
|
+
...COMPONENTS_WRITE_FLAGS,
|
|
81
|
+
name: { kind: "string", description: "Rename it", placeholder: "text" },
|
|
82
|
+
};
|
|
83
|
+
const HOUR = 3600;
|
|
84
|
+
const hours = (secs) => typeof secs === "number" ? Math.round((secs / HOUR) * 10) / 10 : "";
|
|
85
|
+
export async function componentsCommand(argv) {
|
|
86
|
+
// A leading flag is a missing subcommand, not a subcommand named `--profile`.
|
|
87
|
+
const leads = argv[0];
|
|
88
|
+
const named = leads !== undefined && !leads.startsWith("-");
|
|
89
|
+
const sub = named ? leads : undefined;
|
|
90
|
+
const rest = named ? argv.slice(1) : argv;
|
|
91
|
+
switch (sub) {
|
|
92
|
+
case "list":
|
|
93
|
+
return list(rest);
|
|
94
|
+
case "show":
|
|
95
|
+
return show(rest);
|
|
96
|
+
case "create":
|
|
97
|
+
return create(rest);
|
|
98
|
+
case "update":
|
|
99
|
+
return update(rest);
|
|
100
|
+
default:
|
|
101
|
+
throw new TosError(sub ? `Unknown subcommand "${sub}".` : "components needs a subcommand.", "VALIDATION_ERROR", ["components list|show|create|update", "implto-tos-axi components --help"]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function sessionFor(flags) {
|
|
105
|
+
return openSession({
|
|
106
|
+
profile: flags["profile"],
|
|
107
|
+
url: flags["url"],
|
|
108
|
+
mcpUrl: flags["mcp-url"],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async function allComponents(session, workspaceId) {
|
|
112
|
+
const rows = (await session.call("list_components", { workspaceId }));
|
|
113
|
+
return Array.isArray(rows) ? rows : [];
|
|
114
|
+
}
|
|
115
|
+
/** One component by id or name, resolved against the workspace's own list. */
|
|
116
|
+
function find(components, wanted) {
|
|
117
|
+
if (looksLikeId(wanted)) {
|
|
118
|
+
const byId = components.find((c) => c.id === wanted);
|
|
119
|
+
if (byId)
|
|
120
|
+
return byId;
|
|
121
|
+
}
|
|
122
|
+
return pick(components, wanted, (c) => [c.id, c.name], "No component here");
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The hierarchy as text.
|
|
126
|
+
*
|
|
127
|
+
* A flat list with a `parent` column is fine for filtering and useless for
|
|
128
|
+
* seeing where a new component belongs, which is the question somebody asks
|
|
129
|
+
* right before creating one.
|
|
130
|
+
*/
|
|
131
|
+
export function drawTree(components) {
|
|
132
|
+
const children = new Map();
|
|
133
|
+
for (const c of components) {
|
|
134
|
+
const key = c.parentComponentId ?? null;
|
|
135
|
+
children.set(key, [...(children.get(key) ?? []), c]);
|
|
136
|
+
}
|
|
137
|
+
// A component whose parent is outside the filtered set would otherwise
|
|
138
|
+
// vanish, so anything unreachable from the roots is drawn at the top level.
|
|
139
|
+
const known = new Set(components.map((c) => c.id));
|
|
140
|
+
const roots = components.filter((c) => !c.parentComponentId || !known.has(c.parentComponentId));
|
|
141
|
+
const lines = [];
|
|
142
|
+
const walk = (node, depth, seen) => {
|
|
143
|
+
if (seen.has(node.id))
|
|
144
|
+
return;
|
|
145
|
+
seen.add(node.id);
|
|
146
|
+
const estimate = hours(node.originalEstimateSecs);
|
|
147
|
+
const tail = [
|
|
148
|
+
node.type && node.type !== "OTHER" ? node.type : "",
|
|
149
|
+
estimate ? `${estimate}h` : "",
|
|
150
|
+
]
|
|
151
|
+
.filter(Boolean)
|
|
152
|
+
.join(", ");
|
|
153
|
+
lines.push(`${" ".repeat(depth)}${node.name}${tail ? ` (${tail})` : ""}`);
|
|
154
|
+
for (const child of children.get(node.id) ?? [])
|
|
155
|
+
walk(child, depth + 1, seen);
|
|
156
|
+
};
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
for (const root of roots)
|
|
159
|
+
walk(root, 0, seen);
|
|
160
|
+
// A cycle has no root, so nothing above would have drawn it. The server
|
|
161
|
+
// refuses to create one, but a tree that silently loses rows is worse than
|
|
162
|
+
// one that draws a cycle flat.
|
|
163
|
+
for (const node of components)
|
|
164
|
+
if (!seen.has(node.id))
|
|
165
|
+
walk(node, 0, seen);
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
|
168
|
+
async function list(argv) {
|
|
169
|
+
const { flags } = parseFlags(argv, COMPONENTS_LIST_FLAGS);
|
|
170
|
+
const session = sessionFor(flags);
|
|
171
|
+
const workspaceId = await resolveWorkspaceId(session, flags["workspace"]);
|
|
172
|
+
const all = await allComponents(session, workspaceId);
|
|
173
|
+
const parent = flags["parent"];
|
|
174
|
+
const parentId = parent ? find(all, parent).id : undefined;
|
|
175
|
+
const project = flags["project"];
|
|
176
|
+
const search = flags["search"]?.toLowerCase();
|
|
177
|
+
const matched = all
|
|
178
|
+
.filter((c) => !parentId || c.parentComponentId === parentId)
|
|
179
|
+
.filter((c) => !project ||
|
|
180
|
+
c.projectId === project ||
|
|
181
|
+
(c.projectName ?? "").toLowerCase().includes(project.toLowerCase()))
|
|
182
|
+
.filter((c) => !search || c.name.toLowerCase().includes(search));
|
|
183
|
+
if (flags["tree"]) {
|
|
184
|
+
// The shape is the answer here, so it is printed as a shape. TOON would
|
|
185
|
+
// render it as one string with \n in it, which is the format fighting the
|
|
186
|
+
// content.
|
|
187
|
+
const drawn = drawTree(matched);
|
|
188
|
+
return `${drawn || "(no components yet)"}\n\n${matched.length} components. implto-tos-axi components show <name>\n`;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
count: matched.length,
|
|
192
|
+
components: matched.map((c) => ({
|
|
193
|
+
name: c.name,
|
|
194
|
+
type: c.type ?? "",
|
|
195
|
+
parent: c.parentComponentName ?? "",
|
|
196
|
+
project: c.projectName ?? "",
|
|
197
|
+
system: c.workspaceSystemName ?? "",
|
|
198
|
+
tasks: c.issuesCount ?? 0,
|
|
199
|
+
estimate_h: hours(c.originalEstimateSecs),
|
|
200
|
+
})),
|
|
201
|
+
help: [
|
|
202
|
+
"implto-tos-axi components list --tree",
|
|
203
|
+
"implto-tos-axi components show <name>",
|
|
204
|
+
'implto-tos-axi components create "<name>" --parent "<parent>"',
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
async function show(argv) {
|
|
209
|
+
const { positionals, flags } = parseFlags(argv, COMPONENTS_SHOW_FLAGS);
|
|
210
|
+
const wanted = positionals.join(" ").trim();
|
|
211
|
+
if (!wanted) {
|
|
212
|
+
throw new TosError("Name the component to show.", "VALIDATION_ERROR", [
|
|
213
|
+
'implto-tos-axi components show "Billing API"',
|
|
214
|
+
]);
|
|
215
|
+
}
|
|
216
|
+
const session = sessionFor(flags);
|
|
217
|
+
const workspaceId = await resolveWorkspaceId(session, flags["workspace"]);
|
|
218
|
+
const found = find(await allComponents(session, workspaceId), wanted);
|
|
219
|
+
const component = (await session.call("get_component", { id: found.id }));
|
|
220
|
+
return {
|
|
221
|
+
name: component.name,
|
|
222
|
+
type: component.type ?? "",
|
|
223
|
+
importance: component.importance ?? "",
|
|
224
|
+
parent: component.parentComponentName ?? "",
|
|
225
|
+
project: component.projectName ?? "",
|
|
226
|
+
system: component.workspaceSystemName ?? "",
|
|
227
|
+
estimate_h: hours(component.originalEstimateSecs),
|
|
228
|
+
tasks: component.issuesCount ?? 0,
|
|
229
|
+
process_steps: component.processStepsCount ?? 0,
|
|
230
|
+
phases: (component.phaseEstimates ?? []).map((e) => ({
|
|
231
|
+
phase: e.phaseName,
|
|
232
|
+
hours: hours(e.estimateSecs),
|
|
233
|
+
})),
|
|
234
|
+
summary: component.shortDescription ?? "",
|
|
235
|
+
description: component.description ?? "",
|
|
236
|
+
id: component.id,
|
|
237
|
+
help: [
|
|
238
|
+
`implto-tos-axi components update "${component.name}" --parent "<parent>"`,
|
|
239
|
+
"implto-tos-axi tools call search_tasks componentId=" + component.id,
|
|
240
|
+
],
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function bodyFrom(flags) {
|
|
244
|
+
const file = flags["description-file"];
|
|
245
|
+
if (file)
|
|
246
|
+
return file === "-" ? readFileSync(0, "utf8") : readFileSync(file, "utf8");
|
|
247
|
+
return flags["description"];
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* A workspace system by name.
|
|
251
|
+
*
|
|
252
|
+
* Systems are only listed by a process-maps tool, so a credential narrowed to
|
|
253
|
+
* delivery planning can pass an id but cannot look one up by name. That is
|
|
254
|
+
* what the refusal says, rather than "not found".
|
|
255
|
+
*/
|
|
256
|
+
async function systemId(session, workspaceId, wanted) {
|
|
257
|
+
if (looksLikeId(wanted))
|
|
258
|
+
return wanted;
|
|
259
|
+
let systems;
|
|
260
|
+
try {
|
|
261
|
+
const context = (await session.call("get_process_map_workspace_context", { workspaceId }));
|
|
262
|
+
systems = context.systems ?? context.workspaceSystems ?? [];
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
throw new TosError(`Cannot look up a system by name with this credential: ${error instanceof Error ? error.message : String(error)}`, "FORBIDDEN", [
|
|
266
|
+
"Pass the system id instead, or sign in again with the process_maps area.",
|
|
267
|
+
"implto-tos-axi tools domains",
|
|
268
|
+
]);
|
|
269
|
+
}
|
|
270
|
+
return pick(systems, wanted, (s) => [s.id, s.localName ?? s.name], "No system here").id;
|
|
271
|
+
}
|
|
272
|
+
async function projectId(session, wanted) {
|
|
273
|
+
if (looksLikeId(wanted))
|
|
274
|
+
return wanted;
|
|
275
|
+
const me = (await session.call("get_current_user"));
|
|
276
|
+
const projects = (me.workspaces ?? []).flatMap((w) => w.projects ?? []);
|
|
277
|
+
return pick(projects, wanted, (p) => [p.id, p.key, p.name], "No project you can reach").id;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The fields a create and an update share.
|
|
281
|
+
*
|
|
282
|
+
* `undefined` leaves a field alone and `null` clears it, which is the server's
|
|
283
|
+
* own contract - so `--parent none` un-nests a component and omitting
|
|
284
|
+
* `--parent` does not touch it.
|
|
285
|
+
*/
|
|
286
|
+
async function writableFields(session, workspaceId, flags, siblings) {
|
|
287
|
+
const fields = {};
|
|
288
|
+
const parent = flags["parent"];
|
|
289
|
+
if (parent !== undefined) {
|
|
290
|
+
fields["parentComponentId"] = isClearing(parent) ? null : find(siblings, parent).id;
|
|
291
|
+
}
|
|
292
|
+
const system = flags["system"];
|
|
293
|
+
if (system !== undefined) {
|
|
294
|
+
fields["workspaceSystemId"] = isClearing(system)
|
|
295
|
+
? null
|
|
296
|
+
: await systemId(session, workspaceId, system);
|
|
297
|
+
}
|
|
298
|
+
const project = flags["project"];
|
|
299
|
+
if (project !== undefined) {
|
|
300
|
+
fields["projectId"] = isClearing(project) ? null : await projectId(session, project);
|
|
301
|
+
}
|
|
302
|
+
const responsible = flags["responsible"];
|
|
303
|
+
if (responsible !== undefined) {
|
|
304
|
+
if (isClearing(responsible)) {
|
|
305
|
+
fields["responsibleUserIds"] = [];
|
|
306
|
+
}
|
|
307
|
+
else if (responsible === "me") {
|
|
308
|
+
const me = (await session.call("get_current_user"));
|
|
309
|
+
fields["responsibleUserIds"] = [me.id];
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
fields["responsibleUserIds"] = responsible.split(",").map((id) => id.trim());
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const short = flags["short"];
|
|
316
|
+
if (short !== undefined)
|
|
317
|
+
fields["shortDescription"] = isClearing(short) ? null : short;
|
|
318
|
+
const body = bodyFrom(flags);
|
|
319
|
+
if (body !== undefined)
|
|
320
|
+
fields["description"] = isClearing(body) ? null : body;
|
|
321
|
+
// Types are upper-case in the schema; importance is deliberately lower-case,
|
|
322
|
+
// so only one of the two can be normalised. Anything the server does not
|
|
323
|
+
// know comes back naming everything it does.
|
|
324
|
+
const type = flags["type"];
|
|
325
|
+
if (type !== undefined)
|
|
326
|
+
fields["type"] = type.toUpperCase();
|
|
327
|
+
const importance = flags["importance"];
|
|
328
|
+
if (importance !== undefined) {
|
|
329
|
+
fields["importance"] = isClearing(importance) ? null : importance;
|
|
330
|
+
}
|
|
331
|
+
const estimate = flags["estimate"];
|
|
332
|
+
if (estimate !== undefined)
|
|
333
|
+
fields["originalEstimateSecs"] = Math.round(estimate * HOUR);
|
|
334
|
+
return fields;
|
|
335
|
+
}
|
|
336
|
+
async function create(argv) {
|
|
337
|
+
const { positionals, flags } = parseFlags(argv, COMPONENTS_CREATE_FLAGS);
|
|
338
|
+
const name = positionals.join(" ").trim();
|
|
339
|
+
if (!name) {
|
|
340
|
+
throw new TosError("A component needs a name.", "VALIDATION_ERROR", [
|
|
341
|
+
'implto-tos-axi components create "Billing API" --parent Platform',
|
|
342
|
+
]);
|
|
343
|
+
}
|
|
344
|
+
const session = sessionFor(flags);
|
|
345
|
+
const workspaceId = await resolveWorkspaceId(session, flags["workspace"]);
|
|
346
|
+
const existing = await allComponents(session, workspaceId);
|
|
347
|
+
const fields = await writableFields(session, workspaceId, flags, existing);
|
|
348
|
+
// A duplicate is the same name in the same place. The same name elsewhere in
|
|
349
|
+
// the tree is ordinary - two subtrees each have their "Docs" - so refusing
|
|
350
|
+
// that would block a legitimate create with no way around it.
|
|
351
|
+
const parentId = fields["parentComponentId"] ?? null;
|
|
352
|
+
const clash = existing.find((c) => c.name.toLowerCase() === name.toLowerCase() && (c.parentComponentId ?? null) === parentId);
|
|
353
|
+
if (clash) {
|
|
354
|
+
throw new TosError(`"${clash.name}" already exists ${clash.parentComponentName ? `under "${clash.parentComponentName}"` : "at the top level"}.`, "VALIDATION_ERROR", [
|
|
355
|
+
`implto-tos-axi components show "${clash.name}"`,
|
|
356
|
+
`implto-tos-axi components update "${clash.name}" --parent "<parent>"`,
|
|
357
|
+
]);
|
|
358
|
+
}
|
|
359
|
+
// A component filed from a bound checkout belongs to that checkout's project
|
|
360
|
+
// unless it is told otherwise, which is what --project none is for.
|
|
361
|
+
if (fields["projectId"] === undefined && session.binding) {
|
|
362
|
+
fields["projectId"] = session.binding.projectId;
|
|
363
|
+
}
|
|
364
|
+
const created = (await session.call("create_component", {
|
|
365
|
+
workspaceId,
|
|
366
|
+
name,
|
|
367
|
+
...fields,
|
|
368
|
+
}));
|
|
369
|
+
return {
|
|
370
|
+
created: created.name,
|
|
371
|
+
parent: created.parentComponentName ?? "(top level)",
|
|
372
|
+
project: created.projectName ?? "",
|
|
373
|
+
type: created.type ?? "",
|
|
374
|
+
id: created.id,
|
|
375
|
+
help: [
|
|
376
|
+
`implto-tos-axi components show "${created.name}"`,
|
|
377
|
+
"implto-tos-axi components list --tree",
|
|
378
|
+
],
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
async function update(argv) {
|
|
382
|
+
const { positionals, flags } = parseFlags(argv, COMPONENTS_UPDATE_FLAGS);
|
|
383
|
+
const wanted = positionals.join(" ").trim();
|
|
384
|
+
if (!wanted) {
|
|
385
|
+
throw new TosError("Name the component to update.", "VALIDATION_ERROR", [
|
|
386
|
+
'implto-tos-axi components update "Billing API" --parent Platform',
|
|
387
|
+
]);
|
|
388
|
+
}
|
|
389
|
+
const session = sessionFor(flags);
|
|
390
|
+
const workspaceId = await resolveWorkspaceId(session, flags["workspace"]);
|
|
391
|
+
const all = await allComponents(session, workspaceId);
|
|
392
|
+
const component = find(all, wanted);
|
|
393
|
+
const fields = await writableFields(session, workspaceId, flags, all);
|
|
394
|
+
const rename = flags["name"];
|
|
395
|
+
if (rename)
|
|
396
|
+
fields["name"] = rename;
|
|
397
|
+
if (Object.keys(fields).length === 0) {
|
|
398
|
+
throw new TosError("Nothing to change.", "VALIDATION_ERROR", [
|
|
399
|
+
"implto-tos-axi components update --help",
|
|
400
|
+
]);
|
|
401
|
+
}
|
|
402
|
+
if (fields["parentComponentId"] === component.id) {
|
|
403
|
+
throw new TosError("A component cannot be its own parent.", "VALIDATION_ERROR");
|
|
404
|
+
}
|
|
405
|
+
const updated = (await session.call("update_component", {
|
|
406
|
+
id: component.id,
|
|
407
|
+
...fields,
|
|
408
|
+
}));
|
|
409
|
+
return {
|
|
410
|
+
updated: updated.name,
|
|
411
|
+
parent: updated.parentComponentName ?? "(top level)",
|
|
412
|
+
project: updated.projectName ?? "",
|
|
413
|
+
changed: Object.keys(fields).join(", "),
|
|
414
|
+
help: [`implto-tos-axi components show "${updated.name}"`],
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
//# sourceMappingURL=components.js.map
|